Compare commits

...

200 Commits

Author SHA1 Message Date
Joel Falknau
c679ec0646 Version Bump 4.11.1 2025-11-13 10:24:14 +10:00
Joel Falknau
69a51bb08a update pre-commit 2025-11-13 10:17:23 +10:00
Ariel Rin
442c893c39 Merge branch 'master' into 'master'
Edit manager.py

See merge request allianceauth/allianceauth!1778
2025-11-03 10:33:00 +00:00
salartarium
33df15f882 Edit manager.py 2025-11-02 05:52:03 +00:00
Ariel Rin
5d56f0a66c Merge branch 'limit-max-python-version' into 'master'
[CHANGE] Limit max. Python version to what we test for

See merge request allianceauth/allianceauth!1773
2025-10-29 10:41:46 +00:00
Ariel Rin
0fbdbf3a8d Merge branch 'sidebar-total-notification-counter' into 'master'
Sidebar total notification counter

See merge request allianceauth/allianceauth!1774
2025-10-29 10:31:51 +00:00
Ariel Rin
5291bf6896 Merge branch 'translation-fixes' into 'master'
[FIX] Django `makemessages` is ignoring f-strings for translations now

See merge request allianceauth/allianceauth!1772
2025-10-29 00:23:39 +00:00
Ariel Rin
bd6c0fede5 Merge branch 'fix-directories' into 'master'
[HOTFIX] Directories

See merge request allianceauth/allianceauth!1775
2025-10-29 00:23:04 +00:00
Peter Pfeufer
23fe1703c3 [FIX] Directories 2025-10-28 22:33:33 +01:00
Peter Pfeufer
9139b0da56 [CHANGE] JS filename to better reflect what it is for 2025-10-28 09:41:33 +01:00
Peter Pfeufer
80145b313f [ADD] Total notification counter to sidebar 2025-10-28 09:38:20 +01:00
Peter Pfeufer
e96bdd12f9 [CHANGE] Limit max. Python version to what we test for 2025-10-27 11:23:03 +01:00
Joel Falknau
513b7b88f4 another trixie runner, not available to python 3.8 2025-10-27 20:08:15 +10:00
Joel Falknau
5e3fc5c1cb i dont understand this behaviour but okay fine 2025-10-27 19:51:29 +10:00
Ariel Rin
d3069db046 Merge branch 'srp_allow-missing-trailling-slash' into 'master'
SRP - Allow zkillboard links without a trailling slash to be pasted without errors

See merge request allianceauth/allianceauth!1771
2025-10-26 06:26:48 +00:00
Ariel Rin
24863eaf9e Merge branch 'srp_diplay-errors' into 'master'
SRP - Display form errors

See merge request allianceauth/allianceauth!1770
2025-10-26 06:25:27 +00:00
Joel Falknau
d138bd61c5 py38 not available on debian trixie 2025-10-26 12:45:49 +10:00
Joel Falknau
bff20ddd5d remove deprecated future 2025-10-26 11:25:06 +10:00
Joel Falknau
493e694410 update precommit 2025-10-26 11:24:57 +10:00
Peter Pfeufer
8c41d9da58 [FIX] Django makemessages is ignoring f-strings for translations now 2025-10-25 11:14:41 +02:00
Joel Falknau
9e0358a3ce bump test images to trixie 2025-10-25 18:39:24 +10:00
T'rahk Rokym
19bb6856a2 Allow zkillboard links without a trailling slash to be pasted without errors 2025-10-18 00:02:29 +02:00
T'rahk Rokym
9751315b97 Cleaner error display applied to any bootstrap_form in allianceauth
Credit to @ppfeufer for the code snipet
2025-10-17 23:15:42 +02:00
Joel Falknau
99df847e1f Version Bump 4.11.0 (again) 2025-10-17 16:00:45 +10:00
Joel Falknau
60ba82c653 Merge branch 'master' of gitlab.com:allianceauth/allianceauth 2025-10-17 14:38:34 +10:00
Joel Falknau
bd7f13358a Version Bump 4.11.0 2025-10-17 14:37:45 +10:00
Joel Falknau
4edd0fab9e update precommit 2025-10-17 14:34:56 +10:00
Ariel Rin
fced909b4d Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1764
2025-10-17 04:28:59 +00:00
Ariel Rin
e2b96da460 Updates for project Alliance Auth 2025-10-17 04:28:59 +00:00
Ariel Rin
ecd27b823e Merge branch 'supervisor-env-variables' into 'master'
[ADD] Environment variables to supervisor config

See merge request allianceauth/allianceauth!1739
2025-10-17 04:20:22 +00:00
Ariel Rin
dae4afddb1 Merge branch 'hide-menu' into 'master'
[ADD] User setting to keep the sidebar menu minimized

See merge request allianceauth/allianceauth!1769
2025-10-17 04:20:10 +00:00
Peter Pfeufer
d507663316 [CHANGE] Hide setting on mobile devices to avoid confusion 2025-10-15 10:54:44 +02:00
Peter Pfeufer
67081ab465 [ADD] Device detection and always minimize the sidebar on mobile 2025-10-15 10:40:44 +02:00
Peter Pfeufer
bce90344f8 [CHANGE] Always minimize when not authenticated
We have no user settings here, so keep it minimized on public pages.
2025-10-15 09:45:15 +02:00
Peter Pfeufer
29c6fa292a [REMOVE] JS local storage usage
To make it an actual choice through the setting
2025-10-15 09:42:07 +02:00
Peter Pfeufer
295361a541 [ADD] User setting to keep the sidebar menu minimized 2025-10-15 01:13:42 +02:00
Ariel Rin
92a1bd40a3 Merge branch 'inline-js-overhaul' into 'master'
[CHANGE] InlineJS overhaul

See merge request allianceauth/allianceauth!1766
2025-10-10 09:54:36 +00:00
Ariel Rin
b8bbd0d1c1 Merge branch 'fix-deprecated-utcnow' into 'master'
[FIX] Calls to deprecated `utcnow` method of `datetime.datetime`

See merge request allianceauth/allianceauth!1767
2025-10-10 09:52:45 +00:00
Peter Pfeufer
b7a6c9379a Merge remote-tracking branch 'origin/fix-deprecated-utcnow' into fix-deprecated-utcnow 2025-09-27 07:51:53 +02:00
Peter Pfeufer
e5f3a67919 [FIX] Calls to deprecated utcnow method of datetime.datetime
datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).

```python
@classmethod
@deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .now(datetime.UTC)")
def utcnow(cls) -> Self
```
2025-09-27 07:51:46 +02:00
Ariel Rin
58cc4b84dd Merge branch 'codeowners' into 'master'
[ADD] CODEOWNERS file

See merge request allianceauth/allianceauth!1765
2025-09-26 23:37:55 +00:00
Ariel Rin
331dc7d4d0 Merge branch 'limit-bcrypt-version' into 'master'
[CHANGE] Temporarily limit `bcyrpt` to below v5.0.0

See merge request allianceauth/allianceauth!1768
2025-09-26 23:32:56 +00:00
Peter Pfeufer
01991b78c9 [CHANGE] Temporarily limit bcyrpt to below v5.0.0
See: https://gitlab.com/allianceauth/allianceauth/-/issues/1436
2025-09-26 17:47:50 +02:00
Peter Pfeufer
466113e6cb [FIX] Calls to deprecated utcnow method of datetime.datetime
datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).

```python
@classmethod
@deprecated("Use timezone-aware objects to represent datetimes in UTC; e.g. by calling .now(datetime.UTC)")
def utcnow(cls) -> Self
```
2025-09-26 17:20:40 +02:00
Peter Pfeufer
b1f5aad9f9 [CHANGE] Move inline JS into $(document).ready(()) block 2025-09-25 09:38:20 +02:00
Peter Pfeufer
464016ac05 [CHANGE] Move task queue update JS into its own file 2025-09-25 09:14:03 +02:00
Peter Pfeufer
cc76f09a6e [CHANGE Move sidebar collapse JS into its own file 2025-09-25 08:11:17 +02:00
Peter Pfeufer
a7f6a74211 [ADD] CODEOWNERS file
See: https://docs.gitlab.com/user/project/codeowners/
2025-09-24 08:53:28 +02:00
Joel Falknau
8898c665cf Version Bump 4.10.0 2025-09-21 13:58:11 +10:00
Ariel Rin
313305ab22 Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1757
2025-09-21 03:43:37 +00:00
Ariel Rin
86559fc11f Merge branch 'refresh-task-queue' into 'master'
[CHANGE] Frequently update task queue section on dashboard

See merge request allianceauth/allianceauth!1752
2025-09-21 02:45:39 +00:00
Ariel Rin
1835b04dc8 Merge branch 'mumble-logo-svg' into 'master'
[ADD] Mumble logo to SVG sprite

See merge request allianceauth/allianceauth!1755
2025-09-21 02:45:03 +00:00
Ariel Rin
30180f9fe9 Merge branch 'number-formatt-to-js-framework' into 'master'
[ADD] Number formatter to JS framework

See merge request allianceauth/allianceauth!1756
2025-09-21 02:44:45 +00:00
Ariel Rin
f1eac7b84f Merge branch 'framework-css-position' into 'master'
[CHANGE] Load Framework CSS before theme CSS

See merge request allianceauth/allianceauth!1758
2025-09-21 02:43:56 +00:00
Ariel Rin
793df66f7a Merge branch 'doc-update' into 'master'
Change the installation instructions to match the correct packages

See merge request allianceauth/allianceauth!1759
2025-09-21 02:43:43 +00:00
Ariel Rin
188295daac Merge branch 'optimer-html-fix' into 'master'
[FIX] Optimer table HTML

See merge request allianceauth/allianceauth!1760
2025-09-21 02:43:26 +00:00
Ariel Rin
0447697106 Merge branch 'make-request-available' into 'master'
[CHANGE] Make the `request` object available in theme js/css templates

See merge request allianceauth/allianceauth!1761
2025-09-21 02:43:19 +00:00
Ariel Rin
1608950d43 Merge branch 'user-agent' into 'master'
[CHANGE] User-Agent to our proposed default format

See merge request allianceauth/allianceauth!1762
2025-09-21 02:41:49 +00:00
Ariel Rin
23c283c0bb Merge branch 'mobile-menu-template' into 'master'
[ADD] Mobile menu templates to framework

See merge request allianceauth/allianceauth!1763
2025-09-21 02:41:20 +00:00
Peter Pfeufer
b76fa4282a [ADD] Mobile menu templates to framework 2025-09-11 12:52:48 +02:00
Peter Pfeufer
d88cb57cf0 [CHANGE] User-Agent to our proposed default format 2025-09-09 11:06:37 +02:00
Peter Pfeufer
787140dd7e [CHANGE] Make the request object available in theme js/css templates 2025-09-07 09:39:22 +02:00
Peter Pfeufer
735e890de4 [FIX] Don't let Bootstrap override our override 2025-09-06 16:16:20 +02:00
Peter Pfeufer
77caa5543d [FIX] Optimer table HTML 2025-09-06 15:47:59 +02:00
r0kym
b2f0962527 Change the installation instructions to match the correct packages 2025-09-01 23:10:28 +02:00
Peter Pfeufer
efc0fcf11d [CHANGE] Load Framework CSS before theme CSS
So it's available when the theme CSS starts loading.
2025-08-31 18:04:56 +02:00
Ariel Rin
1b49ea571e Translate django.po in ru
82% of minimum 50% translated source file: 'django.po'
on 'ru'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format
2025-08-29 13:48:48 +00:00
Peter Pfeufer
3c21a3857a [ADD] Number formatter 2025-08-27 20:22:24 +02:00
Peter Pfeufer
d0a769f524 [ADD] Mumble logo to SVG sprite
Could be used in at least 2 community apps.
- Mumble Temp Links (https://github.com/Solar-Helix-Independent-Transport/allianceauth-mumble-temp)
- AA Mumble Quick Connect (https://github.com/ppfeufer/aa-mumble-quick-connect)
2025-08-18 16:23:15 +02:00
Ariel Rin
222547187e Merge branch 'master' into 'master'
Added member id on error to make error message more useful

See merge request allianceauth/allianceauth!1754
2025-08-18 06:00:33 +00:00
Rapid Fire
ce872d67bb Added Peter Pfeufer's rewording.
https://gitlab.com/allianceauth/allianceauth/-/merge_requests/1754#note_2691552686
2025-08-18 05:34:29 +00:00
Rapid Fire
fcffb3d2ff Added member id on error to make error message more useful 2025-08-18 01:41:15 +00:00
Ariel Rin
1930dfab77 Merge branch 'patch-1' into 'master'
fix 404

See merge request allianceauth/allianceauth!1753
2025-08-15 06:36:30 +00:00
salartarium
b4418c8c36 fix 404 2025-08-15 06:36:29 +00:00
Peter Pfeufer
295e5a04d8 [CHANGE] Frequently update task queue section on dashboard 2025-08-15 02:16:08 +02:00
Joel Falknau
152ebf86f9 Version Bump 4.9.0 2025-08-14 12:32:46 +10:00
Ariel Rin
68069d1043 Merge branch 'retract-group-application-button' into 'master'
[ADD] Retract group application button

See merge request allianceauth/allianceauth!1748
2025-08-14 02:28:42 +00:00
Ariel Rin
9ff926ae4d Merge branch '1745alt' into 'master'
Bump SQL and Redis for new installs

See merge request allianceauth/allianceauth!1751
2025-08-14 02:27:14 +00:00
Ariel Rin
b28cbdad31 Bump SQL and Redis for new installs 2025-08-14 02:27:14 +00:00
Ariel Rin
3c1bae463e Merge branch 'aa-framework-js-functions' into 'master'
[ADD] JS functions to the AA framework

See merge request allianceauth/allianceauth!1747
2025-08-14 02:09:10 +00:00
Ariel Rin
91fbdb9ec1 Merge branch 'fix-debug-warning-widget' into 'master'
[FIX] Debug warning dashboard widget

See merge request allianceauth/allianceauth!1746
2025-08-14 02:08:48 +00:00
Ariel Rin
c1abc56ebc Merge branch 'svg-sprite' into 'master'
[ADD] SVG sprite to the Alliance Auth framework

See merge request allianceauth/allianceauth!1750
2025-08-14 00:57:20 +00:00
Peter Pfeufer
f1582165bc [ADD] SVG sprite to the Alliance Auth framework 2025-08-13 11:46:36 +02:00
Peter Pfeufer
0f155369a1 [CHANGE] Move it to the top 2025-08-11 09:06:27 +02:00
Peter Pfeufer
d67ab108a0 [ADD] Environment variables to supervisor config 2025-08-10 12:59:56 +02:00
Ariel Rin
1e822729c3 Merge branch 'cursor-style' into 'master'
[FIX] Force AA framework cursor style

See merge request allianceauth/allianceauth!1749
2025-08-10 02:31:11 +00:00
Peter Pfeufer
f81c1d1b31 [ADD] Retract group application button
See #1401
2025-08-09 22:40:07 +02:00
Peter Pfeufer
80ac8f7feb [FIX] Force AA framework cursor style
Might be overwritten by Bootstrap otherwise.
2025-08-09 22:30:29 +02:00
Peter Pfeufer
0360184c2d [ADD] QuerySelector function to prevent forms from double submitting
This is to prevent forms from submitting multiple times when users double-click or even more …
2025-08-09 16:37:20 +02:00
Peter Pfeufer
099a39a2a2 [ADD] objectDeepMerge function
Recursively merges properties from source objects into a target object. If a property at the current level is an object,
and both target and source have it, the property is merged. Otherwise, the source property overwrites the target property.

This function does not modify the source objects and prevents prototype pollution by not allowing `__proto__`, `constructor`,
and `prototype` property names.
2025-08-09 15:54:39 +02:00
Peter Pfeufer
c1cd7ca64f [CHANGE] To async 2025-08-09 10:36:22 +02:00
Peter Pfeufer
4cc108ab7f [MISC] Formatting 2025-08-08 19:39:42 +02:00
Peter Pfeufer
0028310aa5 [CHANGE] Switch to framework fetch functions 2025-08-08 19:29:55 +02:00
Peter Pfeufer
18e9453fed [ADD] JS functions to the AA framework 2025-08-08 19:08:55 +02:00
Peter Pfeufer
db74ddfdf5 [FIX] Debug warning dashboard widget
Bringing and layout the markup in line with the rest of the dashboard widgets.
2025-08-07 13:47:58 +02:00
Ariel Rin
e68793f363 Merge branch 'debug-panel' into 'master'
Add an additionnal panel to the admin status displaying if DEBUG is turned on

See merge request allianceauth/allianceauth!1737
2025-08-07 10:58:50 +00:00
Ariel Rin
5155af8240 Merge branch 'perm-audit-details' into 'master'
Add application and model names in the permission audit

See merge request allianceauth/allianceauth!1738
2025-08-07 10:57:13 +00:00
Ariel Rin
575ddeed10 Merge branch 'crazydisi-master-patch-e658' into 'master'
fixed a typo in the uninstall manual

See merge request allianceauth/allianceauth!1744
2025-08-07 10:44:13 +00:00
Ariel Rin
16e1cd94e3 Merge branch 'fix-duplicate-class-attribute' into 'master'
[FIX] Duplicate class attribute

See merge request allianceauth/allianceauth!1743
2025-08-07 10:44:05 +00:00
Ariel Rin
2443d43c25 Merge branch 'dev-enviro' into 'master'
Developer Environment Docs Update

See merge request allianceauth/allianceauth!1741
2025-08-07 10:43:59 +00:00
Aaron Kable
d2accfb312 Developer Environment Docs Update 2025-08-07 10:43:59 +00:00
Ariel Rin
9f86971ce2 Merge branch 'smooth-chevron-transition' into 'master'
[CHANGE] Chevrons in side menu now rotate smoothly instead of snapping

See merge request allianceauth/allianceauth!1740
2025-08-07 10:43:23 +00:00
Ariel Rin
9abb216a3d Merge branch 'fix-custom-css-static' into 'master'
[FIX] Don't use `static` for the `custom-styles.css` file

See merge request allianceauth/allianceauth!1736
2025-08-07 10:41:47 +00:00
Ariel Rin
e978a8e4a5 Merge branch 'add-missing-fonts' into 'master'
[ADD] Missing fonts

See merge request allianceauth/allianceauth!1735
2025-08-07 10:41:23 +00:00
Ariel Rin
a2040ae4dd Merge branch 'django-esi-min-version' into 'master'
[CHANGE] Django ESI min version

See merge request allianceauth/allianceauth!1734
2025-08-07 10:38:07 +00:00
CrazyDisi
dae374412c fixed a typo in the uninstall manual 2025-08-06 12:34:51 +00:00
Peter Pfeufer
572294dd41 [FIX] Duplicate class attribute 2025-08-06 08:28:39 +02:00
Peter Pfeufer
a71aeb1f02 [CHANGE] Chevrons in side menu now rotate smoothly instead of snapping 2025-07-29 10:46:13 +02:00
T'rahk Rokym
d1487fa176 Switch separator to match Django's backend display 2025-07-09 21:54:24 +02:00
T'rahk Rokym
783a599081 Add application and model names in the permission audit 2025-07-09 21:46:17 +02:00
T'rahk Rokym
0d9fd0049b Add an additionnal panel to the admin status displaying if DEBUG is turned on 2025-07-09 21:36:45 +02:00
Peter Pfeufer
3f17da684e [FIX] Don't use static for the custom-styles.css file
Since this CSS file is not part of the regular static collection, but gets created on demand, `ManifestStaticFilesStorage` will not work here. So we have to call the file directly, instead via `static` or we get:

ValueError: Missing staticfiles manifest entry for 'allianceauth/custom-styles.css'
2025-07-08 22:12:43 +02:00
Peter Pfeufer
323beceda1 [ADD] Missing fonts 2025-07-07 09:06:00 +02:00
Peter Pfeufer
9f81c7fa0e [CHANGE] User agent generation 2025-07-05 14:36:48 +02:00
Peter Pfeufer
9c156c1115 [CHANGE] Django ESI min version
Set to v7.0.1, to ensure this fix is included » 501966c07c
2025-07-05 10:08:32 +02:00
Joel Falknau
34b94ae685 Version Bump 4.8.0 2025-07-03 09:19:25 +10:00
Ariel Rin
50fd900bdc Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1732
2025-07-02 23:17:15 +00:00
Ariel Rin
1bf8ec5bc6 Updates for project Alliance Auth 2025-07-02 23:17:15 +00:00
Ariel Rin
f849b75029 Merge branch 'fix-sidebar-localStorage-behavior' into 'master'
[FIX] Sidebar `localStorage` behavior

See merge request allianceauth/allianceauth!1733
2025-07-02 23:06:28 +00:00
Peter Pfeufer
25c27793fe [FIX] Sidebar localStorage behavior 2025-07-01 14:07:12 +02:00
Ariel Rin
6e413772ad Merge branch 'custom-static-file-storage' into 'master'
[ADD] Custom Static Files Storage Class

See merge request allianceauth/allianceauth!1726
2025-06-30 23:43:33 +00:00
Ariel Rin
137a202e1b Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1725
2025-06-19 10:31:37 +00:00
Joel Falknau
aaf718fe4d update pre-commit 2025-06-19 20:30:37 +10:00
Joel Falknau
a193d9959b makemessages 2025-06-19 20:23:21 +10:00
Ariel Rin
12250ef0c2 Merge branch 'exclude-biomassed-characters' into 'master'
[ADD] `exclude_biomassed` to `EveCharacterManager`

See merge request allianceauth/allianceauth!1727
2025-06-19 10:13:47 +00:00
Ariel Rin
bde9802583 Merge branch 'user-menu-fixes' into 'master'
[CHANGE] User Menu Template Improvements

See merge request allianceauth/allianceauth!1728
2025-06-19 10:12:16 +00:00
Ariel Rin
1b30b86d2b Merge branch 'improve-get_all_characters_from_user' into 'master'
[CHANGE] Improve `get_all_characters_from_user`

See merge request allianceauth/allianceauth!1729
2025-06-19 10:11:43 +00:00
Ariel Rin
0707b9b98c Merge branch 'error-not-warning' into 'master'
[FIX] Error codes should trigger errors, not warnings

See merge request allianceauth/allianceauth!1730
2025-06-19 10:11:09 +00:00
Peter Pfeufer
b22a379db2 [FIX] Error codes should trigger errors, not warnings 2025-06-17 17:27:51 +02:00
Peter Pfeufer
bb2e0aabbc [CHANGE] Improve get_all_characters_from_user
### Added

- `main_first` option to move the main character to the first position of the character list

### Changed

- Character list sorted alphabetically
2025-06-17 16:38:55 +02:00
Peter Pfeufer
449991d846 [FIX] Several HTML fixes
- Remove unnecessary HTML tags
- Remove unnecessary Bootstrap classes
- Add title attributes
- Make strings translatable
2025-06-15 12:15:14 +02:00
Peter Pfeufer
dd42c2b074 [CHANGE] Only show theme selection if more than 1 theme is available 2025-06-15 12:10:44 +02:00
Peter Pfeufer
abff1b0add [ADD] exclude_biomassed to EveCharacterManager 2025-06-13 11:29:05 +02:00
Peter Pfeufer
fc51f6bea2 [FIX] Cleanup file path name to work with CSS url("foobar") notations
This essentially removes quotes from the filename, which aren't allowed anyways.
2025-06-12 10:19:48 +02:00
Peter Pfeufer
6477c22308 [CHANGE] Use the same quotation marks for strings and not a mix of both
Just while we're at it …
2025-06-01 00:05:34 +02:00
Peter Pfeufer
329b3fecfb [ADD] Custom Static Files Storage Class 2025-06-01 00:02:09 +02:00
Ariel Rin
677505f22a Translate django.po in pl_PL
92% of minimum 50% translated source file: 'django.po'
on 'pl_PL'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format
2025-05-24 08:31:18 +00:00
Ariel Rin
f518166bd0 Translate django.po in uk
96% of minimum 50% translated source file: 'django.po'
on 'uk'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format
2025-05-24 08:31:11 +00:00
Ariel Rin
1f4c49f823 Translate django.po in ko_KR
92% of minimum 50% translated source file: 'django.po'
on 'ko_KR'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format
2025-05-24 08:31:00 +00:00
Ariel Rin
abcc4d47b5 Translate django.po in ru
76% of minimum 50% translated source file: 'django.po'
on 'ru'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format
2025-05-24 08:30:53 +00:00
Ariel Rin
3d4737df72 Translate django.po in it_IT
90% of minimum 50% translated source file: 'django.po'
on 'it_IT'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format
2025-05-24 08:30:47 +00:00
Ariel Rin
8f94885d8e Translate django.po in de
96% of minimum 50% translated source file: 'django.po'
on 'de'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format
2025-05-24 08:30:40 +00:00
Ariel Rin
993455d664 Translate django.po in fr_FR
92% of minimum 50% translated source file: 'django.po'
on 'fr_FR'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format
2025-05-24 08:30:33 +00:00
Ariel Rin
3cb0addee7 Translate django.po in ja
92% of minimum 50% translated source file: 'django.po'
on 'ja'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format
2025-05-24 08:30:26 +00:00
Ariel Rin
5530b76294 Translate django.po in es
80% of minimum 50% translated source file: 'django.po'
on 'es'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format
2025-05-24 08:30:16 +00:00
Ariel Rin
9fb51165ab Translate django.po in zh-Hans
67% of minimum 50% translated source file: 'django.po'
on 'zh-Hans'.

Sync of partially translated files: 
untranslated content is included with an empty translation 
or source language content depending on file format
2025-05-24 08:30:09 +00:00
Joel Falknau
a650f0730e Version Bump 4.7.0 2025-05-24 17:28:41 +10:00
Ariel Rin
63eb9edc9c Merge branch 'readme' into 'master'
Feedback Wanted - Update Readme

See merge request allianceauth/allianceauth!1696
2025-05-24 06:23:06 +00:00
Ariel Rin
d6e1eb9792 Feedback Wanted - Update Readme 2025-05-24 06:23:06 +00:00
Ariel Rin
b459f96e6b Merge branch 'autogroup-multiple-configs' into 'master'
[Fix] Multiple AutoGroupsConfig will override each others

See merge request allianceauth/allianceauth!1722
2025-05-24 04:05:28 +00:00
Ariel Rin
bf32f2c1ef Merge branch 'fix-instance-must-be-saved' into 'master'
Fix discord test errror

See merge request allianceauth/allianceauth!1723
2025-05-24 04:04:27 +00:00
Ariel Rin
7ca67ebaae Merge branch 'fix-active-class' into 'master'
[FIX] Active class on notification icon

See merge request allianceauth/allianceauth!1724
2025-05-24 04:00:59 +00:00
Peter Pfeufer
fa32f87a35 [FIX] Active class on notification icon 2025-05-22 09:21:05 +02:00
T'rahk Rokym
a630015451 Fix error on one of my tox settings
ValueError: Model instances passed to related filters must be saved.
2025-05-21 21:52:40 +02:00
T'rahk Rokym
bf43f59232 Fix the role distribution if several configs hold the same corporation/alliance 2025-05-21 21:29:21 +02:00
Ariel Rin
54910746e3 Merge branch 'bootstrap-class-fixes' into 'master'
[FIX] BG color classes

See merge request allianceauth/allianceauth!1716
2025-05-06 02:03:56 +00:00
Ariel Rin
07ae68333d Merge branch 'improve-add-character-icon' into 'master'
[CHANGE] Better "Add Character" Icon

See merge request allianceauth/allianceauth!1720
2025-05-06 02:03:50 +00:00
Ariel Rin
69e70a4c9b Merge branch '1426-fix-srp-datatable-warning' into 'master'
[FIX] SRP Datatable Warning about incorrect column count

Closes #1426

See merge request allianceauth/allianceauth!1721
2025-05-06 02:03:44 +00:00
Peter Pfeufer
b55f11ee74 [FIX] SRP Datatable Warning about incorrect column count
This was triggered by an incorrect column count due to missing permissions, or not taking missing permissions into account when loading the page and setting up the non-orderable columns.

Fixes #1426
2025-05-05 01:49:40 +02:00
Peter Pfeufer
94ee3c0203 [CHANGE] Better "Add Character" Icon 2025-05-02 01:06:26 +02:00
Peter Pfeufer
25cf329a50 [FIX] BG color classes
Use `text-bg-*` instead of just `bg-*` to make use of Bootstraps native text color selection for those backgrounds.
2025-04-29 07:39:11 +02:00
Ariel Rin
b02827cb3f Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1699
2025-04-29 02:57:50 +00:00
Ariel Rin
2bcc0570ad Updates for project Alliance Auth 2025-04-29 02:57:50 +00:00
Ariel Rin
a3ea0c65a1 Merge branch 'timerboard-skyhooks' into 'master'
Add a new `theft` timer type for skyhooks

See merge request allianceauth/allianceauth!1708
2025-04-29 02:52:38 +00:00
Ariel Rin
5e526da11c Merge branch 'add-apply_offset-to-esi_cleanup_token-task' into 'master'
[ADD] `apply_offset` to `esi_cleanup_token` task

See merge request allianceauth/allianceauth!1709
2025-04-29 02:49:56 +00:00
Ariel Rin
5c79265f90 Merge branch 'services-grid' into 'master'
[FIX] Top margin for service controls

See merge request allianceauth/allianceauth!1711
2025-04-29 02:48:11 +00:00
Ariel Rin
eb0134e716 Merge branch 'menu-separator' into 'master'
[ADD] Menu separator

See merge request allianceauth/allianceauth!1712
2025-04-29 02:48:00 +00:00
Ariel Rin
afde1f4729 Merge branch 'typehint-object-managers' into 'master'
Typehint object managers

See merge request allianceauth/allianceauth!1713
2025-04-29 02:47:12 +00:00
Ariel Rin
5c6dda0eac Typehint object managers 2025-04-29 02:47:11 +00:00
Ariel Rin
af453bc772 Merge branch 'aa_i18n-addition' into 'master'
[ADD] Temaplatetag for path relative to `static`

See merge request allianceauth/allianceauth!1714
2025-04-29 02:46:56 +00:00
Ariel Rin
e13674e886 Merge branch 'translatable-app-names' into 'master'
[CHANGE] Made app names translatable in Django admin

See merge request allianceauth/allianceauth!1715
2025-04-29 02:45:48 +00:00
Ariel Rin
e3e856b826 Merge branch 'admin-overview' into 'master'
Remove unused {% if %} tag

See merge request allianceauth/allianceauth!1718
2025-04-29 02:42:35 +00:00
T'rahk Rokym
9d1cd23a8f Remove unused {% if %} tag 2025-04-21 16:50:04 +02:00
Peter Pfeufer
148f7c116f [CHANGE] Made app names translatable in Django admin 2025-04-10 07:08:16 +02:00
Peter Pfeufer
33e7134d6f [ADD] Temaplatetag for path relative to static
AA-GDPR needs this
2025-04-09 20:54:51 +02:00
Peter Pfeufer
fb799551aa [CHANGE] Move what ever is possible to our framework CSS 2025-04-06 22:22:54 +02:00
Peter Pfeufer
7b95051fe1 [ADD] Menu separator
Between `#nav-right` and `#nav-right-character-control`. Only visible when `#nav-right` has menu items.
2025-04-06 22:01:54 +02:00
Peter Pfeufer
efb6a6db4f [FIX] Top margin for service controls 2025-04-05 23:49:58 +02:00
Peter Pfeufer
478aa1aa12 [ADD] apply_offset to esi_cleanup_token task 2025-04-03 00:59:04 +02:00
T'rahk Rokym
751e55ed6c Add a new theft timer type for skyhooks 2025-03-26 22:09:43 +01:00
Ariel Rin
9dad53f763 Merge branch 'master' into 'master'
Switch the doc example to use group instead of chain for long running tasks

See merge request allianceauth/allianceauth!1703
2025-03-26 01:48:29 +00:00
Ariel Rin
2d57064a7a Merge branch 'supervisor-config-template-updates' into 'master'
[CHANGE] Update supervisor.conf template

See merge request allianceauth/allianceauth!1700
2025-03-26 01:47:58 +00:00
Ariel Rin
833d12cf66 Merge branch 'opengraph-template' into 'master'
[ADD] Opengraph Template

See merge request allianceauth/allianceauth!1701
2025-03-26 01:47:35 +00:00
Ariel Rin
7b56caa4cb Merge branch 'perms_fix' into 'master'
Use the mumble.view_connection_history perm not superuser for Mumble service statistics

See merge request allianceauth/allianceauth!1702
2025-03-26 01:47:12 +00:00
Ariel Rin
5752644122 Merge branch 'fix-logo-height' into 'master'
[FIX] `logo_height` variable name

See merge request allianceauth/allianceauth!1704
2025-03-26 01:46:12 +00:00
Ariel Rin
cadc0cb534 Merge branch 'page-header' into 'master'
[CHANGE] Wrap page header in `header` element

See merge request allianceauth/allianceauth!1705
2025-03-26 01:46:04 +00:00
Ariel Rin
dcdab5ae1f Merge branch 'fix-spelling' into 'master'
[FIX] Spelling (It's EVE time, not Eve time)

See merge request allianceauth/allianceauth!1706
2025-03-26 01:45:58 +00:00
Ariel Rin
d64e896288 Merge branch 'fix-scrollbar-positioning' into 'master'
[FIX] Scrollbar positioning in content area

See merge request allianceauth/allianceauth!1707
2025-03-26 01:45:38 +00:00
Peter Pfeufer
500d8ede32 [FIX] Scrollbar positioning in content area 2025-03-24 17:47:40 +01:00
Peter Pfeufer
f4c5c7f6db [FIX] Spelling (It's EVE time, not Eve time) 2025-03-24 15:14:30 +01:00
Peter Pfeufer
43e1be4032 [CHANGE] Move margin class to header element 2025-03-24 14:49:51 +01:00
Peter Pfeufer
702def2a4d [CHANGE] Wrap page header in header element 2025-03-24 13:34:11 +01:00
Peter Pfeufer
a34baf4154 [FIX] logo_height variable name 2025-03-22 17:37:10 +01:00
r0kym
4de0774f15 Switch the doc example to use group instead of chain for long running tasks 2025-03-20 18:47:19 +01:00
Aaron Kable
83d2dfc7d9 use the view_connection_history perm not superuser 2025-03-20 19:11:08 +08:00
Peter Pfeufer
c93afd2d68 [ADD] Site name and URL 2025-03-18 21:40:47 +01:00
Peter Pfeufer
b7bacd11af [CHANGE] Bring public base templkate in line with the non-public version 2025-03-18 18:30:00 +01:00
Peter Pfeufer
f26835fae0 [ADD] Opengraph Template for easier override 2025-03-18 18:29:22 +01:00
Peter Pfeufer
4edb7cb678 [CHANGE] Assignment format to INI style
The supervisor.conf file uses INI style, so the format should reflect this.
2025-03-16 10:15:36 +01:00
Peter Pfeufer
2ce9ba997f [CHANGE] Update supervisor.conf template
Use `%(program_name)s` variable for logfile names, which makes it easier to avoid possible copy/paste issues and mistakes.
2025-03-16 10:11:27 +01:00
191 changed files with 8460 additions and 4291 deletions

View File

@@ -25,7 +25,7 @@ before_script:
pre-commit-check:
<<: *only-default
stage: pre-commit
image: python:3.11-bookworm
image: python:3.11-trixie
# variables:
# PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
# cache:
@@ -65,7 +65,7 @@ test-3.8-core:
test-3.9-core:
<<: *only-default
image: python:3.9-bookworm
image: python:3.9-trixie
script:
- tox -e py39-core
artifacts:
@@ -77,7 +77,7 @@ test-3.9-core:
test-3.10-core:
<<: *only-default
image: python:3.10-bookworm
image: python:3.10-trixie
script:
- tox -e py310-core
artifacts:
@@ -89,7 +89,7 @@ test-3.10-core:
test-3.11-core:
<<: *only-default
image: python:3.11-bookworm
image: python:3.11-trixie
script:
- tox -e py311-core
artifacts:
@@ -101,7 +101,7 @@ test-3.11-core:
test-3.12-core:
<<: *only-default
image: python:3.12-bookworm
image: python:3.12-trixie
script:
- tox -e py312-core
artifacts:
@@ -125,7 +125,7 @@ test-3.8-all:
test-3.9-all:
<<: *only-default
image: python:3.9-bookworm
image: python:3.9-trixie
script:
- tox -e py39-all
artifacts:
@@ -137,7 +137,7 @@ test-3.9-all:
test-3.10-all:
<<: *only-default
image: python:3.10-bookworm
image: python:3.10-trixie
script:
- tox -e py310-all
artifacts:
@@ -149,7 +149,7 @@ test-3.10-all:
test-3.11-all:
<<: *only-default
image: python:3.11-bookworm
image: python:3.11-trixie
script:
- tox -e py311-all
artifacts:
@@ -162,7 +162,7 @@ test-3.11-all:
test-3.12-all:
<<: *only-default
image: python:3.12-bookworm
image: python:3.12-trixie
script:
- tox -e py312-all
artifacts:
@@ -174,7 +174,7 @@ test-3.12-all:
build-test:
stage: test
image: python:3.11-bookworm
image: python:3.11-trixie
before_script:
- python -m pip install --upgrade pip
@@ -193,13 +193,13 @@ build-test:
test-docs:
<<: *only-default
image: python:3.11-bookworm
image: python:3.11-trixie
script:
- tox -e docs
deploy_production:
stage: deploy
image: python:3.11-bookworm
image: python:3.11-trixie
before_script:
- python -m pip install --upgrade pip

View File

@@ -25,19 +25,19 @@ exclude: |
repos:
# Code Upgrades
- repo: https://github.com/asottile/pyupgrade
rev: v3.19.1
rev: v3.21.1
hooks:
- id: pyupgrade
args: [--py38-plus]
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.22.2
rev: 1.29.1
hooks:
- id: django-upgrade
args: [--target-version=4.2]
# Formatting
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: v6.0.0
hooks:
# Identify invalid files
- id: check-ast
@@ -55,8 +55,6 @@ repos:
# - id: check-docstring-first
- id: debug-statements
# - id: requirements-txt-fixer
- id: fix-encoding-pragma
args: [--remove]
- id: fix-byte-order-marker
# General quality checks
- id: mixed-line-ending
@@ -66,11 +64,11 @@ repos:
- id: check-executables-have-shebangs
- id: end-of-file-fixer
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: 3.2.0
rev: 3.4.1
hooks:
- id: editorconfig-checker
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.44.0
rev: v0.45.0
hooks:
- id: markdownlint
language: node
@@ -78,7 +76,7 @@ repos:
- --disable=MD013
# Infrastructure
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.5.0
rev: v2.11.1
hooks:
- id: pyproject-fmt
name: pyproject.toml formatter
@@ -86,9 +84,9 @@ repos:
args:
- --indent=4
additional_dependencies:
- tox==4.24.1 # https://github.com/tox-dev/tox/releases/latest
- tox==4.32.0 # https://github.com/tox-dev/tox/releases/latest
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.23
rev: v0.24.1
hooks:
- id: validate-pyproject
name: Validate pyproject.toml

1
CODEOWNERS Normal file
View File

@@ -0,0 +1 @@
* @allianceauth

View File

@@ -1,15 +1,15 @@
# Alliance Auth
[![license](https://img.shields.io/badge/license-GPLv2-green)](https://pypi.org/project/allianceauth/)
[![python](https://img.shields.io/pypi/pyversions/allianceauth)](https://pypi.org/project/allianceauth/)
[![django](https://img.shields.io/pypi/djversions/allianceauth?label=django)](https://pypi.org/project/allianceauth/)
[![version](https://img.shields.io/pypi/v/allianceauth?label=release)](https://pypi.org/project/allianceauth/)
[![pipeline status](https://gitlab.com/allianceauth/allianceauth/badges/master/pipeline.svg)](https://gitlab.com/allianceauth/allianceauth/commits/master)
[![License](https://img.shields.io/badge/license-GPLv2-green)](https://pypi.org/project/allianceauth/)
[![Python Versions](https://img.shields.io/pypi/pyversions/allianceauth)](https://pypi.org/project/allianceauth/)
[![Django Versions](https://img.shields.io/pypi/djversions/allianceauth?label=django)](https://pypi.org/project/allianceauth/)
[![Stable AA Version](https://img.shields.io/pypi/v/allianceauth?label=release)](https://pypi.org/project/allianceauth/)
[![Pipeline Status](https://gitlab.com/allianceauth/allianceauth/badges/master/pipeline.svg)](https://gitlab.com/allianceauth/allianceauth/commits/master)
[![Documentation Status](https://readthedocs.org/projects/allianceauth/badge/?version=latest)](https://allianceauth.readthedocs.io/?badge=latest)
[![coverage report](https://gitlab.com/allianceauth/allianceauth/badges/master/coverage.svg)](https://gitlab.com/allianceauth/allianceauth/commits/master)
[![Test Coverage Report](https://gitlab.com/allianceauth/allianceauth/badges/master/coverage.svg)](https://gitlab.com/allianceauth/allianceauth/commits/master)
[![Chat on Discord](https://img.shields.io/discord/399006117012832262.svg)](https://discord.gg/fjnHAmk)
An auth system for EVE Online to help in-game organizations manage online service access.
A flexible authentication platform for EVE Online to help in-game organizations manage access to applications and services. AA provides both, a stable core, and a robust framework for community development and custom applications.
## Content
@@ -22,17 +22,17 @@ An auth system for EVE Online to help in-game organizations manage online servic
## Overview
Alliance Auth (AA) is a web site that helps Eve Online organizations efficiently manage access to applications and services.
Alliance Auth (AA) is a platform that helps Eve Online organizations efficiently manage access to applications and services.
Main features:
- Automatically grants or revokes user access to external services (e.g. Discord, Mumble) and web apps (e.g. SRP requests) based on the user's current membership to [in-game organizations](https://allianceauth.readthedocs.io/en/latest/features/core/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/core/groups/)
- Automatically grants or revokes user access to external services (e.g.: Discord, Mumble) based on the user's current membership to [a variety of EVE Online affiliation](https://allianceauth.readthedocs.io/en/latest/features/core/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/core/groups/)
- Provides a central web site where users can directly access web apps (e.g. SRP requests, Fleet Schedule) and manage their access to external services and groups.
- Includes a set of connectors (called ["services"](https://allianceauth.readthedocs.io/en/latest/features/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others
- Includes a set of connectors (called ["Services"](https://allianceauth.readthedocs.io/en/latest/features/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others
- Includes a set of web [apps](https://allianceauth.readthedocs.io/en/latest/features/apps/) which add many useful functions, e.g.: fleet schedule, timer board, SRP request management, fleet activity tracker
- Includes a set of web [Apps](https://allianceauth.readthedocs.io/en/latest/features/apps/) which add many useful functions, e.g.: fleet schedule, timer board, SRP request management, fleet activity tracker
- 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)
@@ -42,9 +42,15 @@ For further details about AA - including an installation guide and a full list o
## Screenshot
Here is an example of the Alliance Auth web site with some plug-ins apps and services enabled:
Here is an example of the Alliance Auth web site with a mixture of Services, Apps and Community Creations enabled:
![screenshot](https://i.imgur.com/2tnX9kD.png)
### Flatly Theme
![Flatly Theme](docs/_static/images/promotion/SampleInstallation-Flatly.png)
### Darkly Theme
![Darkly Theme](docs/_static/images/promotion/SampleInstallation-Darkly.png)
## Support
@@ -83,6 +89,6 @@ Alliance Auth is maintained and developed by the community and we welcome every
To see what needs to be worked on please review our issue list or chat with our active developers on Discord.
Also, please make sure you have signed the [License Agreement](https://developers.eveonline.com/resource/license-agreement) by logging in at [https://developers.eveonline.com](https://developers.eveonline.com) before submitting any pull requests.
Also, please make sure you have signed the [License Agreement](https://developers.eveonline.com/license-agreement) by logging in at [https://developers.eveonline.com](https://developers.eveonline.com) before submitting any pull requests.
In addition to the core AA system we also very much welcome contributions to our growing list of 3rd party services and plugin apps. Please see [AA Community Creations](https://gitlab.com/allianceauth/community-creations) for details.

View File

@@ -5,7 +5,8 @@ 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__ = '4.6.4'
__version__ = '4.11.1'
__title__ = 'Alliance Auth'
__title_useragent__ = 'AllianceAuth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = f'{__title__} v{__version__}'

View File

@@ -1,6 +1,8 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class AnalyticsConfig(AppConfig):
name = 'allianceauth.analytics'
label = 'analytics'
verbose_name = _('Analytics')

View File

@@ -1,10 +1,12 @@
from django.apps import AppConfig
from django.core.checks import register, Tags
from django.utils.translation import gettext_lazy as _
class AuthenticationConfig(AppConfig):
name = "allianceauth.authentication"
label = "authentication"
verbose_name = _("Authentication")
def ready(self):
from allianceauth.authentication import checks, signals # noqa: F401

View File

@@ -52,4 +52,10 @@ class UserSettingsMiddleware(MiddlewareMixin):
except Exception as e:
logger.exception(e)
# Minimize Menu
try:
request.session["MINIMIZE_SIDEBAR"] = request.user.profile.minimize_sidebar
except Exception as e:
pass # We don't care that an anonymous user has no profile (not logged in)
return response

View File

@@ -0,0 +1,22 @@
# Generated by Django 4.2.25 on 2025-10-14 22:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("authentication", "0024_alter_userprofile_language"),
]
operations = [
migrations.AddField(
model_name="userprofile",
name="minimize_sidebar",
field=models.BooleanField(
default=False,
help_text="Keep the sidebar menu minimized",
verbose_name="Minimize Sidebar Menu",
),
),
]

View File

@@ -1,4 +1,5 @@
import logging
from typing import ClassVar
from django.contrib.auth.models import User, Permission
from django.db import models, transaction
@@ -27,7 +28,7 @@ class State(models.Model):
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()
objects: ClassVar[StateManager] = StateManager()
class Meta:
ordering = ['-priority']
@@ -96,7 +97,8 @@ class UserProfile(models.Model):
on_delete=models.SET_DEFAULT,
default=get_guest_state_pk)
language = models.CharField(
_("Language"), max_length=10,
_("Language"),
max_length=10,
choices=Language.choices,
blank=True,
default='')
@@ -111,6 +113,12 @@ class UserProfile(models.Model):
null=True,
help_text="Bootstrap 5 Themes from https://bootswatch.com/ or Community Apps"
)
minimize_sidebar = models.BooleanField(
_("Minimize Sidebar Menu"),
default=False,
help_text=_("Keep the sidebar menu minimized")
)
def assign_state(self, state=None, commit=True):
if not state:
@@ -137,8 +145,10 @@ class UserProfile(models.Model):
sender=self.__class__, user=self.user, state=self.state
)
def __str__(self):
def __str__(self) -> str:
return str(self.user)
class CharacterOwnership(models.Model):
class Meta:
default_permissions = ('change', 'delete')
@@ -148,7 +158,7 @@ class CharacterOwnership(models.Model):
owner_hash = models.CharField(max_length=28, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='character_ownerships')
objects = CharacterOwnershipManager()
objects: ClassVar[CharacterOwnershipManager] = CharacterOwnershipManager()
def __str__(self):
return f"{self.user}: {self.character}"

View File

@@ -27,7 +27,7 @@ def dashboard_results(hours: int) -> _TaskCounts:
my_earliest = events.first_event(earliest=earliest)
return [my_earliest] if my_earliest else []
earliest = dt.datetime.utcnow() - dt.timedelta(hours=hours)
earliest = dt.datetime.now(dt.timezone.utc) - dt.timedelta(hours=hours)
earliest_events = []
succeeded_count = succeeded_tasks.count(earliest=earliest)
earliest_events += earliest_if_exists(succeeded_tasks, earliest)

View File

@@ -42,7 +42,7 @@ class EventSeries:
- event_time: timestamp of event. Will use current time if not specified.
"""
if not event_time:
event_time = dt.datetime.utcnow()
event_time = dt.datetime.now(dt.timezone.utc)
my_id = self._redis.incr(self._key_counter)
self._redis.zadd(self._key_sorted_set, {my_id: event_time.timestamp()})

View File

@@ -31,7 +31,7 @@
<tr>
<td style="white-space:initial;">
{% for s in t.scopes.all %}
<span class="badge bg-secondary">{{ s.name }}</span>
<span class="badge text-bg-secondary">{{ s.name }}</span>
{% endfor %}
</td>

View File

@@ -1,24 +1,24 @@
{% load theme_tags %}
{% load static %}
<!DOCTYPE html>
<html lang="en">
<html lang="en" {% theme_html_tags %}>
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<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 override -->
<meta property="og:title" content="{{ SITE_NAME }}">
<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.">
<!-- End Required meta tags -->
<!-- Meta tags -->
{% include 'allianceauth/opengraph.html' %}
{% include 'allianceauth/icons.html' %}
<!-- Meta tags -->
<title>{% block title %}{% block page_title %}{% endblock page_title %} - {{ SITE_NAME }}{% endblock title %}</title>
{% theme_css %}
{% include 'bundles/fontawesome.html' %}
{% include 'bundles/auth-framework-css.html' %}
{% block extra_include %}
{% endblock %}

View File

@@ -1,17 +1,15 @@
{% load i18n %}
<div class="dropdown">
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<form class="dropdown-item" action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<select class="form-select" onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
{% get_available_languages as LANGUAGES %}
<select class="form-select" onchange="this.form.submit()" id="lang-select" name="language">
{% get_available_languages as LANGUAGES %}
{% for lang_code, lang_name in LANGUAGES %}
<option lang="{{ lang_code }}" value="{{ lang_code }}"{% if lang_code == LANGUAGE_CODE %} selected{% endif %}>
{{ lang_code|language_name_local|capfirst }} ({{ lang_code }})
</option>
{% endfor %}
</select>
</form>
</div>
{% for lang_code, lang_name in LANGUAGES %}
<option lang="{{ lang_code }}" value="{{ lang_code }}"{% if lang_code == LANGUAGE_CODE %} selected{% endif %}>
{{ lang_code|language_name_local|capfirst }} ({{ lang_code }})
</option>
{% endfor %}
</select>
</form>

View File

@@ -88,6 +88,7 @@ class TestUserSettingsMiddlewareLoginFlow(TestCase):
self.request.LANGUAGE_CODE = 'en'
self.request.user.profile.language = 'de'
self.request.user.profile.night_mode = True
self.request.user.profile.minimize_sidebar = False
self.request.user.is_anonymous = False
self.response = Mock()
self.response.content = 'hello world'
@@ -173,3 +174,26 @@ class TestUserSettingsMiddlewareLoginFlow(TestCase):
self.response
)
self.assertEqual(self.request.session["NIGHT_MODE"], True)
def test_middleware_set_mimimize_sidebar(self):
"""
tests the middleware will always set minimize_sidebar to False (default)
"""
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.request.session["MINIMIZE_SIDEBAR"], False)
def test_middleware_minimize_sidebar_when_set(self):
"""
tests the middleware will set mimimize_sidebar to True from DB
"""
self.request.user.profile.minimize_sidebar = True
response = self.middleware.process_response(
self.request,
self.response
)
self.assertEqual(self.request.session["MINIMIZE_SIDEBAR"], True)

View File

@@ -9,6 +9,7 @@ from allianceauth.tests.auth_utils import AuthUtils
from allianceauth.authentication.constants import ESI_ERROR_MESSAGE_OVERRIDES
MODULE_PATH = "allianceauth.authentication.views"
TEMPLATETAGS_PATH = "allianceauth.templatetags.admin_status"
def jsonresponse_to_dict(response) -> dict:
@@ -17,6 +18,7 @@ def jsonresponse_to_dict(response) -> dict:
@patch(MODULE_PATH + ".queued_tasks_count")
@patch(MODULE_PATH + ".active_tasks_count")
@patch(MODULE_PATH + "._celery_stats")
class TestRunningTasksCount(TestCase):
@classmethod
def setUpClass(cls) -> None:
@@ -26,36 +28,64 @@ class TestRunningTasksCount(TestCase):
cls.user.is_superuser = True
cls.user.save()
def test_should_return_data(
self, mock_active_tasks_count, mock_queued_tasks_count
):
def test_should_return_data(self, mock_celery_stats, mock_tasks_queued, mock_tasks_running):
# given
mock_active_tasks_count.return_value = 2
mock_queued_tasks_count.return_value = 3
mock_tasks_running.return_value = 2
mock_tasks_queued.return_value = 3
mock_celery_stats.return_value = {
"tasks_succeeded": 5,
"tasks_retried": 1,
"tasks_failed": 4,
"tasks_total": 11,
"tasks_hours": 24,
"earliest_task": "2025-08-14T22:47:54.853Z",
}
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}
jsonresponse_to_dict(response),
{
"tasks_succeeded": 5,
"tasks_retried": 1,
"tasks_failed": 4,
"tasks_total": 11,
"tasks_hours": 24,
"earliest_task": "2025-08-14T22:47:54.853Z",
"tasks_running": 3,
"tasks_queued": 2,
}
)
def test_su_only(
self, mock_active_tasks_count, mock_queued_tasks_count
):
def test_su_only(self, mock_celery_stats, mock_tasks_queued, mock_tasks_running):
self.user.is_superuser = False
self.user.save()
self.user.refresh_from_db()
# given
mock_active_tasks_count.return_value = 2
mock_queued_tasks_count.return_value = 3
mock_tasks_running.return_value = 2
mock_tasks_queued.return_value = 3
mock_celery_stats.return_value = {
"tasks_succeeded": 5,
"tasks_retried": 1,
"tasks_failed": 4,
"tasks_total": 11,
"tasks_hours": 24,
"earliest_task": "2025-08-14T22:47:54.853Z",
}
request = self.factory.get("/")
request.user = self.user
# when
response = task_counts(request)
# then
self.assertEqual(response.status_code, 302)

View File

@@ -27,6 +27,7 @@ from allianceauth.hooks import get_hooks
from .constants import ESI_ERROR_MESSAGE_OVERRIDES
from .core.celery_workers import active_tasks_count, queued_tasks_count
from allianceauth.templatetags.admin_status import _celery_stats
from .forms import RegistrationForm
from .models import CharacterOwnership
@@ -370,10 +371,10 @@ def registration_closed(request):
@user_passes_test(lambda u: u.is_superuser)
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()
}
data = _celery_stats()
data.update(
{"tasks_running": active_tasks_count(), "tasks_queued": queued_tasks_count()}
)
return JsonResponse(data)

View File

@@ -13,6 +13,7 @@ class StartProject(BaseStartProject):
parser.add_argument('--celery', help='The path to the celery executable.')
parser.add_argument('--gunicorn', help='The path to the gunicorn executable.')
parser.add_argument('--memmon', help='The path to the memmon executable.')
parser.add_argument('--venv_directory', help='The path to the virtual environment directory.')
def create_project(parser, options, args):
@@ -27,7 +28,7 @@ def create_project(parser, options, args):
allianceauth_path = os.path.dirname(allianceauth.__file__)
template_path = os.path.join(allianceauth_path, 'project_template')
# Determine locations of commands to render supervisor cond
# Determine locations of commands to render supervisor configuration
command_options = {
'template': template_path,
'python': shutil.which('python'),
@@ -35,6 +36,7 @@ def create_project(parser, options, args):
'celery': shutil.which('celery'),
'memmon': shutil.which('memmon'),
'extensions': ['py', 'conf', 'json'],
'venv_directory': os.getenv('VIRTUAL_ENV'),
}
# Strip 'start' out of the arguments, leaving project name (and optionally destination dir)

View File

@@ -1,6 +1,8 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class CorpUtilsConfig(AppConfig):
name = 'allianceauth.corputils'
label = 'corputils'
verbose_name = _('Corporation Stats')

View File

@@ -1,5 +1,6 @@
import logging
import os
from typing import ClassVar
from allianceauth.authentication.models import CharacterOwnership, UserProfile
from bravado.exception import HTTPForbidden
@@ -40,9 +41,9 @@ class CorpStats(models.Model):
verbose_name = "corp stats"
verbose_name_plural = "corp stats"
objects = CorpStatsManager()
objects: ClassVar[CorpStatsManager] = CorpStatsManager()
def __str__(self):
def __str__(self) -> str:
return f"{self.__class__.__name__} for {self.corp}"
def update(self):

View File

@@ -11,7 +11,7 @@
{% endblock header_nav_brand %}
{% block header_nav_collapse_left %}
<li class="nav-item dropdown">
<li class="nav-item dropdown mb-2 mb-lg-0">
<a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">
{% translate "Corporations" %}
</a>
@@ -26,11 +26,7 @@
{% endfor %}
{% if perms.corputils.add_corpstats %}
{% if available.count >= 1 %}
<li>&nbsp;</li>
{% endif %}
<li>
<li class="mt-3">
<a class="dropdown-item" href="{% url 'corputils:add' %}">
{% translate "Add corporation" %}
</a>

View File

@@ -138,7 +138,7 @@
<td style="width: 30%;">{{ alt.corporation_name }}</td>
<td style="width: 30%;">{{ alt.alliance_name|default_if_none:"" }}</td>
<td style="width: 5%;">
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="badge bg-danger" target="_blank">
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="badge text-bg-danger" target="_blank">
{% translate "Killboard" %}
</a>
</td>
@@ -175,7 +175,7 @@
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td>
<td>{{ member }}</td>
<td>
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a>
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge text-bg-danger" target="_blank">{% translate "Killboard" %}</a>
</td>
<td>{{ member.character_ownership.user.profile.main_character.character_name }}</td>
<td>{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
@@ -188,7 +188,7 @@
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
<td>{{ member.character_name }}</td>
<td>
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a>
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge text-bg-danger" target="_blank">{% translate "Killboard" %}</a>
</td>
<td></td>
<td></td>
@@ -219,7 +219,7 @@
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
<td>{{ member.character_name }}</td>
<td>
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge text-bg-danger" target="_blank">
{% translate "Killboard" %}
</a>
</td>

View File

@@ -28,7 +28,7 @@
<td><img src="{{ result.1.portrait_url }}" class="img-circle" alt="{{ result.1.character_name }}"></td>
<td>{{ result.1.character_name }}</td>
<td >{{ result.0.corp.corporation_name }}</td>
<td><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a></td>
<td><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="badge text-bg-danger" target="_blank">{% translate "Killboard" %}</a></td>
<td>{{ result.1.main_character.character_name }}</td>
<td>{{ result.1.main_character.corporation_name }}</td>
<td>{{ result.1.main_character.alliance_name }}</td>

View File

@@ -3,6 +3,7 @@ Crontab App Config
"""
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class CrontabConfig(AppConfig):
@@ -12,3 +13,4 @@ class CrontabConfig(AppConfig):
name = "allianceauth.crontab"
label = "crontab"
verbose_name = _("Crontab")

View File

@@ -8,7 +8,6 @@ from allianceauth.custom_css.models import CustomCSS
# Django
from django.conf import settings
from django.template.defaulttags import register
from django.templatetags.static import static
from django.utils.safestring import mark_safe
from pathlib import Path
@@ -20,7 +19,7 @@ def custom_css_static(path: str) -> str:
Versioned static URL
This is to make sure to break the browser cache on CSS updates.
Example: /static/allianceauth/custom-styles.css?v=1234567890
Example: /static/allianceauth/custom-styles.css?v=1752004819.555084
:param path:
:type path:
@@ -42,7 +41,6 @@ def custom_css_static(path: str) -> str:
custom_css_version = (
str(custom_css_changed).replace(" ", "").replace(":", "").replace("-", "")
) # remove spaces, colons, and dashes
static_url = static(path)
versioned_url = static_url + "?v=" + custom_css_version
versioned_url = f"{settings.STATIC_URL}{path}?v={custom_css_version}"
return mark_safe(f'<link rel="stylesheet" href="{versioned_url}">')

View File

@@ -1,6 +1,8 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class EveonlineConfig(AppConfig):
name = 'allianceauth.eveonline'
label = 'eveonline'
verbose_name = _('EVE Online')

View File

@@ -1,9 +1,11 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class EveAutogroupsConfig(AppConfig):
name = 'allianceauth.eveonline.autogroups'
label = 'eve_autogroups'
verbose_name = _('EVE Online Autogroups')
def ready(self):
import allianceauth.eveonline.autogroups.signals

View File

@@ -1,4 +1,5 @@
import logging
from typing import ClassVar
from django.db import models, transaction
from django.contrib.auth.models import Group, User
from django.core.exceptions import ObjectDoesNotExist
@@ -38,13 +39,13 @@ class AutogroupsConfigManager(models.Manager):
"""
if state is None:
state = user.profile.state
for config in self.filter(states=state):
# grant user new groups for their state
config.update_group_membership_for_user(user)
for config in self.exclude(states=state):
# ensure user does not have groups from previous state
config.remove_user_from_alliance_groups(user)
config.remove_user_from_corp_groups(user)
for config in self.filter(states=state):
# grant user new groups for their state
config.update_group_membership_for_user(user)
class AutogroupsConfig(models.Model):
@@ -78,7 +79,7 @@ class AutogroupsConfig(models.Model):
max_length=10, default='', blank=True,
help_text='Any spaces in the group name will be replaced with this.')
objects = AutogroupsConfigManager()
objects: ClassVar[AutogroupsConfigManager] = AutogroupsConfigManager()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

View File

@@ -1,3 +1,4 @@
from allianceauth.eveonline.models import EveCorporationInfo
from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils
@@ -73,3 +74,51 @@ class AutogroupsConfigManagerTestCase(TestCase):
AutogroupsConfig.objects.update_groups_for_user(member)
self.assertTrue(update_groups.called)
def test_update_group_membership_corp_in_two_configs(self):
# given
member = AuthUtils.create_member('test member')
AuthUtils.add_main_character_2(
member,
character_id='1234',
name='test character',
corp_id='2345',
corp_name='corp name',
corp_ticker='TIKK',
)
corp = EveCorporationInfo.objects.create(
corporation_id='2345',
corporation_name='corp name',
corporation_ticker='TIKK',
member_count=10,
)
member_state = AuthUtils.get_member_state()
member_config = AutogroupsConfig.objects.create(corp_groups=True)
member_config.states.add(member_state)
blue_state = AuthUtils.get_blue_state()
blue_state.member_corporations.add(corp)
blue_config = AutogroupsConfig.objects.create(corp_groups=True)
blue_config.states.add(blue_state)
member.profile.state = blue_state
member.profile.save()
AutogroupsConfig.objects.update_groups_for_user(member)
# Checks before test that the role is correctly applied
group = blue_config.get_corp_group(corp)
self.assertIn(group, member.groups.all())
# when
blue_state.member_corporations.remove(corp)
member_state.member_corporations.add(corp)
member.profile.state = member_state
member.profile.save()
# then
AutogroupsConfig.objects.update_groups_for_user(member)
group = member_config.get_corp_group(corp)
self.assertIn(group, member.groups.all())

View File

@@ -14,10 +14,20 @@ class EveCharacterProviderManager:
class EveCharacterManager(models.Manager):
provider = EveCharacterProviderManager()
def create_character(self, character_id):
def exclude_biomassed(self):
"""
Get a queryset of EveCharacter objects, excluding the "Doomheim" corporation (1000001).
:return:
:rtype:
"""
return self.exclude(corporation_id=1000001)
def create_character(self, character_id) -> models.Model:
return self.create_character_obj(self.provider.get_character(character_id))
def create_character_obj(self, character: providers.Character):
def create_character_obj(self, character: providers.Character) -> models.Model:
return self.create(
character_id=character.id,
character_name=character.name,

View File

@@ -1,5 +1,5 @@
import logging
from typing import Union
from typing import ClassVar, Union
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
@@ -75,8 +75,8 @@ class EveAllianceInfo(models.Model):
alliance_ticker = models.CharField(max_length=254)
executor_corp_id = models.PositiveIntegerField()
objects = EveAllianceManager()
provider = EveAllianceProviderManager()
objects: ClassVar[EveAllianceManager] = EveAllianceManager()
provider: ClassVar[EveAllianceProviderManager] = EveAllianceProviderManager()
class Meta:
indexes = [models.Index(fields=['executor_corp_id',])]
@@ -147,7 +147,7 @@ class EveCorporationInfo(models.Model):
EveAllianceInfo, blank=True, null=True, on_delete=models.SET_NULL
)
objects = EveCorporationManager()
objects: ClassVar[EveCorporationManager] = EveCorporationManager()
provider = EveCorporationProviderManager()
class Meta:
@@ -214,7 +214,7 @@ class EveCharacter(models.Model):
faction_id = models.PositiveIntegerField(blank=True, null=True, default=None)
faction_name = models.CharField(max_length=254, blank=True, null=True, default='')
objects = EveCharacterManager()
objects: ClassVar[EveCharacterManager] = EveCharacterManager()
provider = EveCharacterProviderManager()
class Meta:

View File

@@ -7,7 +7,7 @@ from jsonschema.exceptions import RefResolutionError
from django.conf import settings
from esi.clients import esi_client_factory
from allianceauth import __version__
from allianceauth import __version__, __title_useragent__, __url__
from allianceauth.utils.django import StartupCommand
@@ -185,7 +185,9 @@ class EveSwaggerProvider(EveProvider):
self._client = esi_client_factory(
token=token,
spec_file=SWAGGER_SPEC_PATH,
app_info_text=f"allianceauth v{__version__}"
ua_appname=__title_useragent__,
ua_version=__version__,
ua_url=__url__
)
except (HTTPError, RefResolutionError):
logger.exception(
@@ -202,7 +204,11 @@ class EveSwaggerProvider(EveProvider):
def client(self):
if self._client is None:
self._client = esi_client_factory(
token=self._token, spec_file=SWAGGER_SPEC_PATH, app_info_text=("allianceauth v" + __version__)
token=self._token,
spec_file=SWAGGER_SPEC_PATH,
ua_appname=__title_useragent__,
ua_version=__version__,
ua_url=__url__
)
return self._client

View File

@@ -6,6 +6,12 @@ from jsonschema.exceptions import RefResolutionError
from django.test import TestCase
from allianceauth import __url__ as aa_url
from allianceauth import __version__ as aa_version
from esi import __url__ as esi_url
from esi import __version__ as esi_version
from . import set_logger
from .esi_client_stub import EsiClientStub
from ..providers import (
@@ -717,11 +723,11 @@ class TestEveSwaggerProvider(TestCase):
self.assertIsNotNone(my_provider._client)
self.assertEqual(my_client, 'my_client')
@patch(MODULE_PATH + '.__version__', '1.0.0')
def test_user_agent_header(self):
my_provider = EveSwaggerProvider()
my_client = my_provider.client
operation = my_client.Universe.get_universe_factions()
self.assertEqual(
operation.future.request.headers['User-Agent'], 'allianceauth v1.0.0 dummy@example.net'
operation.future.request.headers['User-Agent'],
f'AllianceAuth/{aa_version} (dummy@example.net; +{aa_url}) Django-ESI/{esi_version} (+{esi_url})'
)

View File

@@ -1,6 +1,8 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class FatConfig(AppConfig):
name = 'allianceauth.fleetactivitytracking'
label = 'fleetactivitytracking'
verbose_name = _('Fleet Activity Tracking')

View File

@@ -36,7 +36,7 @@
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center">{% translate "System" %}</th>
<th class="text-center">{% translate "Ship" %}</th>
<th class="text-center">{% translate "Eve Time" %}</th>
<th class="text-center">{% translate "EVE time" %}</th>
<th></th>
</tr>

View File

@@ -72,7 +72,7 @@
<tr>
<th class="text-center">{% translate "Fleet" %}</th>
<th class="text-center">{% translate "Creator" %}</th>
<th class="text-center">{% translate "Eve Time" %}</th>
<th class="text-center">{% translate "EVE time" %}</th>
<th class="text-center">{% translate "Duration" %}</th>
<th class="text-center">{% translate "Edit" %}</th>
</tr>
@@ -80,7 +80,7 @@
{% for link in created_fats %}
<tr>
<td class="text-center">
<a href="{% url 'fatlink:click' link.hash %}" class="badge bg-primary">
<a href="{% url 'fatlink:click' link.hash %}" class="badge text-bg-primary">
{{ link.fleet }}
</a>
</td>

View File

@@ -41,7 +41,7 @@
<th scope="col" class="text-center">{% translate "Character" %}</th>
<th scope="col" class="text-center">{% translate "System" %}</th>
<th scope="col" class="text-center">{% translate "Ship" %}</th>
<th scope="col" class="text-center">{% translate "Eve Time" %}</th>
<th scope="col" class="text-center">{% translate "EVE time" %}</th>
</tr>
{% for fat in fats %}
@@ -89,7 +89,7 @@
<th scope="col" class="text-center">{% translate "Name" %}</th>
<th scope="col" class="text-center">{% translate "Creator" %}</th>
<th scope="col" class="text-center">{% translate "Fleet" %}</th>
<th scope="col" class="text-center">{% translate "Eve Time" %}</th>
<th scope="col" class="text-center">{% translate "EVE time" %}</th>
<th scope="col" class="text-center">{% translate "Duration" %}</th>
<th scope="col" class="text-center">{% translate "Edit" %}</th>
</tr>
@@ -97,7 +97,7 @@
{% for link in fatlinks %}
<tr>
<td class="text-center">
<a href="{% url 'fatlink:click' link.hash %}" class="badge bg-primary">{{ link.fleet }}</a>
<a href="{% url 'fatlink:click' link.hash %}" class="badge text-bg-primary">{{ link.fleet }}</a>
</td>
<td class="text-center">{{ link.creator.username }}</td>
<td class="text-center">{{ link.fleet }}</td>

View File

@@ -320,8 +320,8 @@ def click_fatlink_view(request, token, fat_hash=None):
messages.warning(
request,
_(
f"Cannot register the fleet participation for {character.character_name}. The character needs to be online."
),
"Cannot register the fleet participation for {character_name}. The character needs to be online."
).format(character_name=character.character_name)
)
return redirect('fatlink:view')

View File

@@ -10,24 +10,39 @@ from allianceauth.authentication.models import CharacterOwnership
from allianceauth.eveonline.models import EveCharacter
def get_all_characters_from_user(user: User) -> list:
def get_all_characters_from_user(user: User, main_first: bool = False) -> list:
"""
Get all characters from a user or an empty list
when no characters are found for the user or the user is None
Get all characters from a user
This function retrieves all characters associated with a given user, optionally ordering them
with the main character first.
If the user is None, an empty list is returned.
:param user:
:type user:
:return:
:rtype:
:param user: The user whose characters are to be retrieved
:type user: User
:param main_first: If True, the main character will be listed first
:type main_first: bool
:return: A list of EveCharacter objects associated with the user
:rtype: list[EveCharacter]
"""
if user is None:
return []
try:
characters = [
char.character for char in CharacterOwnership.objects.filter(user=user)
]
if main_first:
characters = [
char.character
for char in CharacterOwnership.objects.filter(user=user).order_by(
"-character__userprofile", "character__character_name"
)
]
else:
characters = [
char.character
for char in CharacterOwnership.objects.filter(user=user).order_by(
"character__character_name"
)
]
except AttributeError:
return []

View File

@@ -3,6 +3,7 @@ Framework App Config
"""
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class FrameworkConfig(AppConfig):
@@ -12,3 +13,4 @@ class FrameworkConfig(AppConfig):
name = "allianceauth.framework"
label = "framework"
verbose_name = _("Framework")

View File

@@ -5,11 +5,33 @@
* to be used throughout Alliance Auth and its Community Apps
*/
/* General
------------------------------------------------------------------------------------- */
@media all {
.navbar-toggler.collapsed {
transform: rotate(180deg);
}
ul#nav-right:has(li) + ul#nav-right-character-control > li:first-child {
display: list-item !important;
}
form.is-submitting button[type="submit"] {
cursor: not-allowed;
}
}
@media all and (max-width: 991px) {
ul#nav-left:has(li) + ul#nav-right + ul#nav-right-character-control > li:first-child {
display: list-item !important;
}
}
/* Bootstrap fixes
------------------------------------------------------------------------------------- */
@media all {
.table {
--bs-table-bg: transparent;
--bs-table-bg: transparent !important;
}
}
@@ -25,6 +47,12 @@
/* Side Navigation
------------------------------------------------------------------------------------- */
@media all {
.sidemenu-total-notifications-badge {
position: absolute;
left: 28px;
font-size: 42.5% !important;
}
#sidebar > div {
width: 325px;
}
@@ -49,15 +77,12 @@
}
/* Chevron icons */
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="true"] > i.fa-chevron-down,
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="false"] > i.fa-chevron-right {
display: block;
width: 16px;
#sidebar-menu span[data-bs-toggle="collapse"] > i.fa-chevron-right {
transition: 0.25s transform ease-in-out;
}
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="true"] > i.fa-chevron-right,
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="false"] > i.fa-chevron-down {
display: none;
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="true"] > i.fa-chevron-right {
transform: rotate(90deg);
}
}
@@ -65,47 +90,47 @@
------------------------------------------------------------------------------------- */
@media all {
.cursor-auto {
cursor: auto;
cursor: auto !important;
}
.cursor-default {
cursor: default;
cursor: default !important;
}
.cursor-pointer {
cursor: pointer;
cursor: pointer !important;
}
.cursor-wait {
cursor: wait;
cursor: wait !important;
}
.cursor-text {
cursor: text;
cursor: text !important;
}
.cursor-move {
cursor: move;
cursor: move !important;
}
.cursor-help {
cursor: help;
cursor: help !important;
}
.cursor-not-allowed {
cursor: not-allowed;
cursor: not-allowed !important;
}
.cursor-inherit {
cursor: inherit;
cursor: inherit !important;
}
.cursor-zoom-in {
cursor: zoom-in;
cursor: zoom-in !important;
}
.cursor-zoom-out {
cursor: zoom-out;
cursor: zoom-out !important;
}
}

View File

@@ -0,0 +1,343 @@
/**
* Functions and utilities for the Alliance Auth framework.
*/
/* jshint -W097 */
'use strict';
/**
* Checks if the given item is an array.
*
* @usage
* ```javascript
* if (isArray(someVariable)) {
* console.log('This is an array');
* } else {
* console.log('This is not an array');
* }
* ```
*
* @param {*} item - The item to check.
* @returns {boolean} True if the item is an array, false otherwise.
*/
const isArray = (item) => {
return Array.isArray(item);
};
/**
* Checks if the given item is a plain object, excluding arrays and dates.
*
* @usage
* ```javascript
* if (isObject(someVariable)) {
* console.log('This is a plain object');
* } else {
* console.log('This is not a plain object');
* }
* ```
*
* @param {*} item - The item to check.
* @returns {boolean} True if the item is a plain object, false otherwise.
*/
const isObject = (item) => {
return (
item && typeof item === 'object' && !isArray(item) && !(item instanceof Date)
);
};
/**
* Fetch data from an ajax URL
*
* Do not call this function directly, use `fetchGet` or `fetchPost` instead.
*
* @param {string} url The URL to fetch data from
* @param {string} method The HTTP method to use for the request (default: 'get')
* @param {string|null} csrfToken The CSRF token to include in the request headers (default: null)
* @param {string|null} payload The payload (JSON|Object) to send with the request (default: null)
* @param {boolean} responseIsJson Whether the response is expected to be JSON or not (default: true)
* @returns {Promise<string>} The fetched data
* @throws {Error} Throws an error when:
* - The method is not valid (only `get` and `post` are allowed).
* - The CSRF token is required but not provided for POST requests.
* - The payload is not an object when using POST method.
* - The response status is not OK (HTTP 200-299).
* - There is a network error or if the response cannot be parsed as JSON.
*/
const _fetchAjaxData = async ({
url,
method = 'get',
csrfToken = null,
payload = null,
responseIsJson = true
}) => {
const normalizedMethod = method.toLowerCase();
// Validate the method
const validMethods = ['get', 'post'];
if (!validMethods.includes(normalizedMethod)) {
throw new Error(`Invalid method: ${method}. Valid methods are: get, post`);
}
const headers = {};
// Set headers based on response type
if (responseIsJson) {
headers['Accept'] = 'application/json'; // jshint ignore:line
headers['Content-Type'] = 'application/json';
}
let requestUrl = url;
let body = null;
if (normalizedMethod === 'post') {
if (!csrfToken) {
throw new Error('CSRF token is required for POST requests');
}
headers['X-CSRFToken'] = csrfToken;
if (payload !== null && !isObject(payload)) {
throw new Error('Payload must be an object when using POST method');
}
body = payload ? JSON.stringify(payload) : null;
} else if (normalizedMethod === 'get' && payload) {
const queryParams = new URLSearchParams(payload).toString(); // jshint ignore:line
requestUrl += (url.includes('?') ? '&' : '?') + queryParams;
}
/**
* Throws an error with a formatted message.
*
* @param {Response} response The error object containing the message to throw.
*/
const throwHTTPStatusError = (response) => {
throw new Error(`Error: ${response.status} - ${response.statusText}`);
};
try {
const response = await fetch(requestUrl, {
method: method.toUpperCase(),
headers: headers,
body: body
});
/**
* Throws an error if the response status is not OK (HTTP 200-299).
* This is used to handle HTTP errors gracefully.
*/
if (!response.ok) {
throwHTTPStatusError(response);
}
return responseIsJson ? await response.json() : await response.text();
} catch (error) {
// Log the error message to the console
console.log(`Error: ${error.message}`);
throw error;
}
};
/**
* Fetch data from an ajax URL using the GET method.
* This function is a wrapper around _fetchAjaxData to simplify GET requests.
*
* @usage
* ```javascript
* fetchGet({
* url: url,
* responseIsJson: false
* }).then((data) => {
* // Process the fetched data
* }).catch((error) => {
* console.error(`Error: ${error.message}`);
*
* // Handle the error appropriately
* });
* ```
*
* @param {string} url The URL to fetch data from
* @param {string|null} payload The payload (JSON) to send with the request (default: null)
* @param {boolean} responseIsJson Whether the response is expected to be JSON or not (default: true)
* @return {Promise<string>} The fetched data
*/
const fetchGet = async ({
url,
payload = null,
responseIsJson = true
}) => {
return await _fetchAjaxData({
url: url,
method: 'get',
payload: payload,
responseIsJson: responseIsJson
});
};
/**
* Fetch data from an ajax URL using the POST method.
* This function is a wrapper around _fetchAjaxData to simplify POST requests.
* It requires a CSRF token for security purposes.
*
* @usage
* ```javascript
* fetchPost({
* url: url,
* csrfToken: csrfToken,
* payload: {
* key: 'value',
* anotherKey: 'anotherValue'
* },
* responseIsJson: true
* }).then((data) => {
* // Process the fetched data
* }).catch((error) => {
* console.error(`Error: ${error.message}`);
*
* // Handle the error appropriately
* });
* ```
*
* @param {string} url The URL to fetch data from
* @param {string|null} csrfToken The CSRF token to include in the request headers (default: null)
* @param {string|null} payload The payload (JSON) to send with the request (default: null)
* @param {boolean} responseIsJson Whether the response is expected to be JSON or not (default: true)
* @return {Promise<string>} The fetched data
*/
const fetchPost = async ({
url,
csrfToken,
payload = null,
responseIsJson = true
}) => {
return await _fetchAjaxData({
url: url,
method: 'post',
csrfToken: csrfToken,
payload: payload,
responseIsJson: responseIsJson
});
};
/**
* Recursively merges properties from source objects into a target object. If a property at the current level is an object,
* and both target and source have it, the property is merged. Otherwise, the source property overwrites the target property.
* This function does not modify the source objects and prevents prototype pollution by not allowing __proto__, constructor,
* and prototype property names.
*
* @usage
* ```javascript
* const target = {a: 1, b: {c: 2}};
* const source1 = {b: {d: 3}, e: 4 };
* const source2 = {a: 5, b: {c: 6}};
*
* const merged = objectDeepMerge(target, source1, source2);
*
* console.log(merged); // {a: 5, b: {c: 6, d: 3}, e: 4}
* ```
*
* @param {Object} target The target object to merge properties into.
* @param {...Object} sources One or more source objects from which to merge properties.
* @returns {Object} The target object after merging properties from sources.
*/
function objectDeepMerge (target, ...sources) {
if (!sources.length) {
return target;
}
// Iterate through each source object without modifying the `sources` array.
sources.forEach(source => {
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue; // Skip potentially dangerous keys to prevent prototype pollution.
}
if (!target[key] || !isObject(target[key])) {
target[key] = {};
}
objectDeepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
});
return target;
}
/**
* Formats a number according to the specified locale.
* This function uses the Intl.NumberFormat API to format the number.
*
* @usage
* In your Django template get the current language code:
* ```django
* {% get_current_language as LANGUAGE_CODE %}
* ```
* Then use it in your JavaScript:
* ```javascript
* const userLocale = '{{ LANGUAGE_CODE }}'; // e.g., 'en-US', 'de-DE'
* const number = 1234567.89;
* const formattedNumber = numberFormatter({
* value: number,
* locales: userLocale,
* options: {
* style: 'currency',
* currency: 'ISK'
* }
* });
*
* // Output will vary based on locale
* // e.g., '1,234,567.89' for 'en-US', '1.234.567,89' for 'de-DE'
* console.log(formattedNumber);
* ```
*
* @param {number} value The number to format
* @param {string | string[]} locales The locale(s) to use for formatting (e.g., 'en-US', 'de-DE', ['en-US', 'de-DE']). If not provided, the browser's default locale will be used and any language settings from the user will be ignored.
* @param {Object} [options={}] Additional options for number formatting (see `Intl.NumberFormat` documentation - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat)
* @return {string} The formatted number as a string
*/
const numberFormatter = ({value, locales, options = {}}) => {
console.log('Formatting number:', value, 'for locale(s):', locales, 'with options:', options);
const formatter = new Intl.NumberFormat(locales, {
maximumFractionDigits: 2,
minimumFractionDigits: 0,
...options
});
return formatter.format(value);
};
/**
* When the document is ready …
*/
$(document).ready(() => {
/**
* Prevent double form submits by adding a class to the form
* when it is submitted.
*
* This class can be used to show a visual indicator that the form is being
* submitted, such as a spinner.
*
* This is useful to prevent users from double-clicking the submit button
* and submitting the form multiple times.
*/
document.querySelectorAll('form').forEach((form) => {
form.addEventListener('submit', (e) => {
// Prevent if already submitting
if (form.classList.contains('is-submitting')) {
e.preventDefault();
}
// Add class to hook our visual indicator on
form.classList.add('is-submitting');
});
});
});

View File

@@ -0,0 +1,105 @@
"""
Custom static files storage for Alliance Auth.
This module defines a custom static files storage class for
Alliance Auth, named `AaManifestStaticFilesStorage`.
Using `ManifestStaticFilesStorage` will give us a hashed name for
our static files, which is useful for cache busting.
This storage class extends Django's `ManifestStaticFilesStorage` to ignore missing files,
which the original class does not handle, and log them in debug mode.
It is useful for handling cases where static files may not exist, such as when a
CSS file references a background image that is not present in the static files directory.
With debug mode enabled, it will print a message for each missing file when running `collectstatic`,
which can help identify issues with static file references during development.
"""
from django.conf import settings
from django.contrib.staticfiles.storage import ManifestStaticFilesStorage
class AaManifestStaticFilesStorage(ManifestStaticFilesStorage):
"""
Custom static files storage that ignores missing files.
"""
@classmethod
def _cleanup_name(cls, name: str) -> str:
"""
Clean up the name by removing quotes.
This method is used to ensure that the name does not contain any quotes,
which can cause issues with file paths.
:param name: The name of the static file.
:type name: str
:return: The cleaned-up name without quotes.
:rtype: str
"""
# Remove quotes from the name
return name.replace('"', "").replace("'", "")
def __init__(self, *args, **kwargs):
"""
Initialize the static files storage, ignoring missing files.
:param args:
:type args:
:param kwargs:
:type kwargs:
"""
self.missing_files = []
super().__init__(*args, **kwargs)
def hashed_name(self, name, content=None, filename=None):
"""
Generate a hashed name for the given static file, ignoring missing files.
Ignore missing files, e.g. non-existent background image referenced from css.
Returns the original filename if the referenced file doesn't exist.
:param name: The name of the static file to hash.
:type name: str
:param content: The content of the static file, if available.
:type content: bytes | None
:param filename: The original filename of the static file, if available.
:type filename: str | None
:return: The hashed name of the static file, or the original name if the file is missing.
:rtype: str
"""
try:
clean_name = self._cleanup_name(name)
return super().hashed_name(clean_name, content, filename)
except ValueError as e:
if settings.DEBUG:
# In debug mode, we log the missing file message
message = e.args[0].split(" with ")[0]
self.missing_files.append(message)
# print(f'\x1b[0;30;41m{message}\x1b[0m')
return name
def post_process(self, *args, **kwargs):
"""
Post-process the static files, printing any missing files in debug mode.
:param args:
:type args:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
yield from super().post_process(*args, **kwargs)
if settings.DEBUG:
# In debug mode, print the missing files
for message in sorted(set(self.missing_files)):
print(f"\x1b[0;30;41m{message}\x1b[0m")

View File

@@ -0,0 +1,13 @@
<li class="nav-item">
<a href="{{ url }}" class="nav-link py-lg-0">
<span class="btn btn-{{ btn_modifier|default:'primary' }} d-none d-lg-inline-block">
{% if fa_icon and icon_on_desktop %}<i class="{{ fa_icon }} me-2"></i>{% endif %}
{{ title }}
</span>
<span class="d-inline-block d-lg-none">
{% if fa_icon and icon_on_mobile %}<i class="{{ fa_icon }} fa-fw me-2"></i>{% endif %}
{{ title }}
</span>
</a>
</li>

View File

@@ -0,0 +1,12 @@
<li class="nav-item">
<a href="{{ url }}" class="nav-link">
<span class="d-none d-lg-inline-block" title="{{ title }}">
<i class="{{ fa_icon }}"></i>
</span>
<span class="d-inline-block d-lg-none">
{% if icon_on_mobile %}<i class="{{ fa_icon }} me-2 fa-fw"></i>{% endif %}
{{ title }}
</span>
</a>
</li>

View File

@@ -2,12 +2,14 @@
{# {% include "framework/header/page-header.html" with title="Foobar" subtitle="Barfoo" %}#}
{% if title %}
<h1 class="page-header text-center mb-3">
{{ title }}
<header class="aa-page-header mb-3">
<h1 class="page-header text-center">
{{ title }}
{% if subtitle %}
<br>
<small class="text-muted">{{ subtitle }}</small>
{% endif %}
</h1>
{% if subtitle %}
<br>
<small class="text-muted">{{ subtitle }}</small>
{% endif %}
</h1>
</header>
{% endif %}

View File

@@ -0,0 +1,75 @@
<svg id="alliance-auth-svg-sprite" width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- Alliance Auth Logo -->
<symbol id="aa-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<g transform="translate(41.953499,36.607802)">
<path style="display:inline;fill:#e14852;stroke-width:0.32" d="M 131.07236,159.67687 C 109.26615,147.02458 91.302022,136.55002 91.152067,136.40007 l -0.272649,-0.27265 23.786292,-13.82371 c 13.08247,-7.60304 23.9186,-13.82025 24.08029,-13.81602 l 0.294,0.008 15.93273,36.83413 c 8.763,20.25877 15.891,36.95054 15.84,37.09283 l -0.0927,0.25869 z" />
<path style="display:inline;fill:#436195;stroke-width:0.32" d="m 1.28,182.46369 c 0,-0.16969 17.354495,-40.46543 38.565546,-89.546103 L 78.411088,3.68 C 79.919052,1.4903841 82.294641,0.02199886 86.08,0.01224344 89.865359,0.00248802 92.288,1.4677954 93.674477,3.5158445 l 21.668143,50.1206965 21.66814,50.120699 -0.26538,0.23285 C 136.59942,104.11816 106.528,121.61441 69.92,142.87065 33.312,164.12688 2.892,181.80046 2.32,182.14527 l -1.04,0.62693 z" />
</g>
</symbol>
<!-- Loading Spinner -->
<symbol id="aa-loading-spinner" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" viewBox="0 0 24 24">
<g>
<circle cx="12" cy="12" r="10" fill="none" stroke-width="1" stroke-linecap="round">
<animate attributeName="stroke-dasharray" dur="1.5s" calcMode="spline" values="0 150;42 150;42 150;42 150" keyTimes="0;0.475;0.95;1" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" repeatCount="indefinite" />
<animate attributeName="stroke-dashoffset" dur="1.5s" calcMode="spline" values="0;-16;-59;-59" keyTimes="0;0.475;0.95;1" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" repeatCount="indefinite" />
</circle>
<animateTransform attributeName="transform" type="rotate" dur="2s" values="0 12 12;360 12 12" repeatCount="indefinite" />
</g>
</symbol>
<!-- Mumble Logo -->
<symbol id="aa-mumble-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400">
<defs>
<radialGradient id="c" cx="206.64" cy="214.43" r="190.25" gradientTransform="matrix(.97267 .016175 -.016656 .97474 9.2188 2.0744)" gradientUnits="userSpaceOnUse">
<stop stop-opacity="0" offset="0" />
<stop stop-opacity=".019608" offset=".81721" />
<stop stop-opacity=".1451" offset=".89931" />
<stop stop-opacity=".20784" offset=".91199" />
<stop stop-opacity=".25098" offset=".95598" />
<stop stop-opacity=".33333" offset="1" />
</radialGradient>
</defs>
<g transform="translate(0 -652.36)">
<path transform="matrix(1.0811 0 0 1.1043 -22.438 617.98)" d="m385.62 214.43c0 96.124-80.133 174.05-178.98 174.05-98.849 0-178.98-77.924-178.98-174.05s80.133-174.05 178.98-174.05c98.849 0 178.98 77.924 178.98 174.05z" fill="#1a1a1a" stroke="#000" stroke-linejoin="round" stroke-width="4.576" />
<path transform="matrix(1.0706 0 0 1.101 -22.082 583.62)" d="m385.62 214.43c0 96.124-80.133 174.05-178.98 174.05-98.849 0-178.98-77.924-178.98-174.05s80.133-174.05 178.98-174.05c98.849 0 178.98 77.924 178.98 174.05z" opacity="0" />
<path transform="matrix(1.0423 0 0 1.0695 -13.736 622.74)" d="m385.62 214.43c0 96.124-80.133 174.05-178.98 174.05-98.849 0-178.98-77.924-178.98-174.05s80.133-174.05 178.98-174.05c98.849 0 178.98 77.924 178.98 174.05z" fill="#fff" opacity=".9" />
<path transform="matrix(1.0641 0 0 1.0787 -20.794 620.64)" d="m385.62 214.43c0 96.124-80.133 174.05-178.98 174.05-98.849 0-178.98-77.924-178.98-174.05s80.133-174.05 178.98-174.05c98.849 0 178.98 77.924 178.98 174.05z" fill="#fff" stroke="#333" stroke-linejoin="round" stroke-width="1.4127" />
<path transform="matrix(1.0857 0 0 1.109 -24.345 616.21)" d="m385.62 214.43c0 96.124-80.133 174.05-178.98 174.05-98.849 0-178.98-77.924-178.98-174.05s80.133-174.05 178.98-174.05c98.849 0 178.98 77.924 178.98 174.05z" fill="none" stroke="#000" stroke-linejoin="round" stroke-width="1.8304" />
</g>
<g>
<path transform="matrix(1.0765 0 0 1.1009 -20.514 -34.696)" d="m385.62 214.43a178.98 174.05 0 1 1-357.96 0 178.98 174.05 0 1 1 357.96 0z" fill="url(#c)" opacity=".75" />
</g>
<g fill-rule="evenodd">
<path transform="matrix(1.05 0 0 1.05 -5.3555 .50955)" d="m152.41 31.61c-24.652-0.61541-49.623 15.705-55.853 40.126-1.4511 5.9204-2.0429 11.533-2.1475 17.251v63.623h25c0.0881-22.382-0.12668-44.644 0.1701-67.072 0.76858-14.243 11.773-29.258 27.049-29.084 0.11203 22.669-0.22918 45.351 0.18004 68.011 1.3028 18.426 18.762 33.676 37.243 32.114 11.546-0.2802 23.178 0.67313 34.648-0.72475 17.466-3.2744 29.553-21.063 27.929-38.449v-60.857c15.888-1.1603 27.938 14.263 28.642 29.084 0.29501 22.427 0.0825 44.692 0.1701 67.072h25v-67.5c-0.81797-7.2761-1.9718-16.18-5.9149-23.198-10.229-20.751-34.153-31.948-56.717-30.261-6.591-0.83713-13.681 3.6197-15.487 9.8666 0.10876 26.739 0.18577 53.486-0.015 80.22-0.75343 11.2-11.79 19.764-22.805 18.342-7.7921 0.33854-16.594 0.0136-21.908-6.6817-7.1623-7.5704-4.7632-18.405-5.1836-27.812 0.0193-21.719-0.0713-43.418 0.1249-65.1-3.2593-6.5913-10.503-9.9936-17.679-8.9119l-1.1877-0.01641-1.2582-0.04042h2.5e-4z" stroke="#fff" />
<path d="m107.27 156.26v177.84c-35.128-3.8535-62.737-42.188-62.737-88.922 0-46.734 27.609-85.068 62.737-88.922z" fill="url(#l)" opacity=".9666" />
</g>
<g fill-rule="evenodd" stroke="#fff">
<path transform="matrix(1.05 0 0 1.05 -5.3555 .50955)" d="m290.42 313.16c-0.69916-7e-3 -3.3105-0.57507-3.9404-0.16697 0 0-1.0356 3.0168-4.6042 5.6725-3.1327 2.3314-6.1076 4.5662-9.2946 6.6625-2.8616 1.8823-5.9328 3.9177-8.8098 5.3024-2.264 1.0896-4.114 1.2482-4.114 1.2482h-32.22c-2.0127 0-3.6618 1.5872-3.6618 3.5625v0.875c0 1.9753 1.649 3.5938 3.6618 3.5938h33.879c0.77968 0 3.5971-0.82022 5.2725-1.5553 4.1768-1.8326 6.899-4.166 11.71-7.0274 5.1144-3.2712 14.573-10.886 14.573-10.886 1.6797-1.0883 2.1278-3.289 1.0189-4.9375l-0.47763-0.75c-0.69304-1.0303-1.8278-1.5824-2.9931-1.5938z" />
<path transform="matrix(1.05 0 0 1.05 -5.3555 .50955)" d="m288.25 148.44v169.38c33.455-3.67 59.75-40.179 59.75-84.688s-26.295-81.018-59.75-84.688z" opacity=".9666" />
<path transform="matrix(1.05 0 0 1.05 -5.3555 .50955)" d="m106.22 149.34v169.38c-33.455-3.67-59.75-40.179-59.75-84.688s26.295-81.018 59.75-84.688z" opacity=".9666" />
<path transform="matrix(1.3048 0 0 1.2146 -20.461 -43.8)" d="m194.74 325.86a22.13 13.831 0 1 1-44.26 0 22.13 13.831 0 1 1 44.26 0z" opacity=".9666" />
<rect transform="matrix(1.0433 0 0 1.05 -4.6563 .094873)" x="274.72" y="146.09" width="13.329" height="171.95" rx="3.8877" ry="3.5401" opacity=".9666" stroke-width="1.0538" />
<rect transform="matrix(1.0433 0 0 1.05 -3.8348 .094873)" x="106.56" y="147.08" width="13.063" height="171.96" rx="3.8101" ry="3.5403" opacity=".9666" stroke-width="1.0432" />
</g>
<g>
<rect x="131.64" y="188.83" width="140.83" height="111.89" fill-rule="evenodd" />
<path transform="matrix(1.1007 0 0 2.0001 -23.812 -190.28)" d="m189.84 226.69c-4e-5 2.3125-0.43754 4.3438-1.3125 6.0938-0.87504 1.75-2.0521 3.1979-3.5312 4.3438-1.75 1.375-3.6719 2.3542-5.7656 2.9375-2.0938 0.58334-4.7552 0.875-7.9844 0.875h-18.625v-46.531h16.438c3.4166 5e-5 6.0052 0.13026 7.7656 0.39063 1.7604 0.26046 3.4114 0.80734 4.9531 1.6406 1.6666 0.89588 2.9114 2.0938 3.7344 3.5938 0.82288 1.5 1.2343 3.2292 1.2344 5.1875-4e-5 2.2709-0.56775 4.2917-1.7031 6.0625-1.1354 1.7709-2.7032 3.073-4.7031 3.9062v0.25c2.875 0.6042 5.177 1.8386 6.9062 3.7031 1.7291 1.8646 2.5937 4.3802 2.5938 7.5469zm-14.969-19.125c-3e-5 -0.74996-0.19274-1.5208-0.57813-2.3125-0.38544-0.79163-0.9844-1.3645-1.7969-1.7188-0.77086-0.33329-1.6823-0.51558-2.7344-0.54687-1.0521-0.0312-2.6198-0.0468-4.7031-0.0469h-0.8125v9.8438h1.4688c2 3e-5 3.401-0.0208 4.2031-0.0625 0.80207-0.0416 1.6302-0.26038 2.4844-0.65625 0.93747-0.43747 1.5833-1.0416 1.9375-1.8125 0.35414-0.7708 0.53122-1.6666 0.53125-2.6875zm2.9375 18.906c-3e-5 -1.4375-0.2917-2.5625-0.875-3.375-0.58336-0.81248-1.4584-1.4271-2.625-1.8438-0.70836-0.27081-1.6823-0.42185-2.9219-0.45312-1.2396-0.0312-2.9011-0.0469-4.9844-0.0469h-2.1562v11.656h0.625c3.0416 1e-5 5.1458-0.0208 6.3125-0.0625 1.1666-0.0416 2.3541-0.3229 3.5625-0.84375 1.0625-0.45832 1.8385-1.1302 2.3281-2.0156 0.48956-0.88541 0.73435-1.8906 0.73438-3.0156z" fill="url(#f)" />
<path transform="matrix(1.1007 0 0 2.0001 -28.291 -190.6)" d="m189.84 226.69c-4e-5 2.3125-0.43754 4.3438-1.3125 6.0938-0.87504 1.75-2.0521 3.1979-3.5312 4.3438-1.75 1.375-3.6719 2.3542-5.7656 2.9375-2.0938 0.58334-4.7552 0.875-7.9844 0.875h-18.625v-46.531h16.438c3.4166 5e-5 6.0052 0.13026 7.7656 0.39063 1.7604 0.26046 3.4114 0.80734 4.9531 1.6406 1.6666 0.89588 2.9114 2.0938 3.7344 3.5938 0.82288 1.5 1.2343 3.2292 1.2344 5.1875-4e-5 2.2709-0.56775 4.2917-1.7031 6.0625-1.1354 1.7709-2.7032 3.073-4.7031 3.9062v0.25c2.875 0.6042 5.177 1.8386 6.9062 3.7031 1.7291 1.8646 2.5937 4.3802 2.5938 7.5469zm-14.969-19.125c-3e-5 -0.74996-0.19274-1.5208-0.57813-2.3125-0.38544-0.79163-0.9844-1.3645-1.7969-1.7188-0.77086-0.33329-1.6823-0.51558-2.7344-0.54687-1.0521-0.0312-2.6198-0.0468-4.7031-0.0469h-0.8125v9.8438h1.4688c2 3e-5 3.401-0.0208 4.2031-0.0625 0.80207-0.0416 1.6302-0.26038 2.4844-0.65625 0.93747-0.43747 1.5833-1.0416 1.9375-1.8125 0.35414-0.7708 0.53122-1.6666 0.53125-2.6875zm2.9375 18.906c-3e-5 -1.4375-0.2917-2.5625-0.875-3.375-0.58336-0.81248-1.4584-1.4271-2.625-1.8438-0.70836-0.27081-1.6823-0.42185-2.9219-0.45312-1.2396-0.0312-2.9011-0.0469-4.9844-0.0469h-2.1562v11.656h0.625c3.0416 1e-5 5.1458-0.0208 6.3125-0.0625 1.1666-0.0416 2.3541-0.3229 3.5625-0.84375 1.0625-0.45832 1.8385-1.1302 2.3281-2.0156 0.48956-0.88541 0.73435-1.8906 0.73438-3.0156z" fill="#fff" />
<path transform="matrix(1.1007 0 0 2.0001 -23.812 -190.28)" d="m227.56 240.94h-31.062v-46.531h11.688v37.656h19.375z" fill="url(#e)" />
<path d="m187.31 197.56v94.531h14.125 0.0625 21.375v-19.25h-21.375v-75.281h-14.188z" fill="#fff" />
<path transform="matrix(1.1007 0 0 2.0001 -23.812 -190.28)" d="m233.12 240.94v-46.531h31.469v8.875h-19.844v8.1562h18.281v8.875h-18.281v11.75h19.844v8.875z" fill="url(#d)" />
<path d="m227.81 197.5v94.531h14.188 21.375v-19.25h-21.375v-22.281h19.656v-18.656h-19.656v-15.094h21.375v-19.25h-21.375-0.0625-14.125z" fill="#fff" />
</g>
<g fill-rule="evenodd">
<path d="m155.37 33.01c-25.884-0.64618-52.104 16.49-58.645 42.133-1.5237 6.2165-2.1451 12.11-2.2549 18.114v66.804h26.25c0.0925-23.501-0.13301-46.876 0.17861-70.426 0.807-14.955 12.362-30.721 28.401-30.539 0.11763 23.802-0.24064 47.619 0.18905 71.412 1.368 19.347 19.7 35.36 39.106 33.72 12.123-0.29421 24.336 0.70678 36.38-0.76099 18.339-3.4381 31.031-22.116 29.325-40.372v-63.9c16.682-1.2183 29.334 14.976 30.074 30.539 0.30976 23.549 0.0866 46.927 0.17861 70.426h26.25v-70.875c-0.85887-7.6399-2.0704-16.989-6.2106-24.358-10.74-21.789-35.861-33.545-59.553-31.774-6.9206-0.87899-14.366 3.8007-16.261 10.36 0.1142 28.075 0.19506 56.161-0.0157 84.231-0.7911 11.76-12.38 20.752-23.945 19.259-8.1817 0.35546-17.423 0.0143-23.004-7.0158-7.5204-7.949-5.0013-19.326-5.4428-29.202 0.0203-22.805-0.0749-45.589 0.13115-68.355-3.4223-6.9208-11.028-10.493-18.563-9.3575l-1.2471-0.01723-1.3211-0.04244h2.6e-4z" fill="url(#i)" />
<path d="m154.53 34.269c-31.655 0-56.169 21.233-59.981 48.759h26.972c3.6448-12.426 13.114-21.517 24.511-22.575 0.74035-0.69976 1.772-1.1484 2.9203-1.1484h26.611l0.0984-15.619s-2.3784-3.7253-4.2984-5.4797c-2.1754-1.9877-5.1118-3.4283-8.0062-3.7406-2.4252-0.26165-5.1996-0.04115-8.8266-0.19688z" fill="url(#j)" />
<path transform="matrix(1.3048 0 0 1.2146 -20.434 -43.907)" d="m194.74 325.86a22.13 13.831 0 1 1-44.26 0 22.13 13.831 0 1 1 44.26 0z" fill="url(#b)" opacity=".9666" />
<path d="m298.66 157.26v177.84c35.128-3.8535 62.738-42.188 62.738-88.922 0-46.734-27.609-85.068-62.738-88.922z" fill="url(#k)" opacity=".9666" />
<path d="m247.97 33.422c31.655 0 56.169 21.233 59.981 48.759h-26.972c-3.6448-12.426-13.114-21.517-24.511-22.575-0.74036-0.69976-1.772-1.1484-2.9203-1.1484h-26.611l-0.0984-15.619s2.3784-3.7253 4.2984-5.4797c2.1754-1.9877 5.1118-3.4283 8.0062-3.7406 2.4252-0.26165 5.1996-0.04115 8.8266-0.19688z" fill="url(#h)" />
<path d="m107.92 156.15v177.84c-35.128-3.8535-62.737-42.188-62.737-88.922 0-46.734 27.609-85.068 62.737-88.922z" fill="url(#g)" opacity=".9666" />
</g>
</symbol>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -1,10 +1,11 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class GroupManagementConfig(AppConfig):
name = 'allianceauth.groupmanagement'
label = 'groupmanagement'
verbose_name = 'Group Management'
verbose_name = _('Group Management')
def ready(self):
from . import signals # noqa: F401

View File

@@ -39,12 +39,12 @@
<td>
{% if group.authgroup.hidden %}
<span class="badge bg-info">{% translate "Hidden" %}</span>
<span class="badge text-bg-info">{% translate "Hidden" %}</span>
{% endif %}
{% if group.authgroup.open %}
<span class="badge bg-success">{% translate "Open" %}</span>
<span class="badge text-bg-success">{% translate "Open" %}</span>
{% else %}
<span class="badge bg-secondary">{% translate "Requestable" %}</span>
<span class="badge text-bg-secondary">{% translate "Requestable" %}</span>
{% endif %}
</td>

View File

@@ -17,7 +17,7 @@
<li class="nav-item">
<a class="nav-link" href="{% url 'groupmanagement:management' %}">{% translate "Group Management" %}
{% if req_count %}
<span class="badge bg-secondary">{{ req_count }}</span>
<span class="badge text-bg-secondary">{{ req_count }}</span>
{% endif %}
</a>
</li>
@@ -33,8 +33,8 @@
<th>{% translate "Description" %}</th>
<th>
{% translate "Leaders" %}<br>
<span class="my-1 me-1 fw-lighter badge bg-primary">{% translate "User" %}</span>
<span class="my-1 me-1 fw-lighter badge bg-secondary">{% translate "Group" %}</span>
<span class="my-1 me-1 fw-lighter badge text-bg-primary">{% translate "User" %}</span>
<span class="my-1 me-1 fw-lighter badge text-bg-secondary">{% translate "Group" %}</span>
</th>
<th></th>
</tr>
@@ -53,13 +53,13 @@
{% if g.group.authgroup.group_leaders.all.count %}
{% for leader in g.group.authgroup.group_leaders.all %}
{% if leader.profile.main_character %}
<span class="my-1 me-1 badge bg-primary">{{leader.profile.main_character}}</span>
<span class="my-1 me-1 badge text-bg-primary">{{leader.profile.main_character}}</span>
{% endif %}
{% endfor %}
{% endif %}
{% if g.group.authgroup.group_leader_groups.all.count %}
{% for group in g.group.authgroup.group_leader_groups.all %}
<span class="my-1 me-1 badge bg-secondary">{{group.name}}</span>
<span class="my-1 me-1 badge text-bg-secondary">{{group.name}}</span>
{% endfor %}
{% endif %}
</td>
@@ -70,8 +70,8 @@
{% translate "Leave" %}
</a>
{% else %}
<button type="button" class="btn btn-primary" disabled>
{% translate "Pending" %}
<button type="button" class="btn btn-secondary cursor-help me-1" data-bs-tooltip="aa-tooltip" title="{% translate 'Request pending' %}">
<i class="fa-regular fa-hourglass-half"></i>
</button>
{% endif %}
{% elif not g.request %}
@@ -85,9 +85,13 @@
</a>
{% endif %}
{% else %}
<button type="button" class="btn btn-primary" disabled>
{% translate "Pending" %}
<button type="button" class="btn btn-secondary cursor-help me-1" data-bs-tooltip="aa-tooltip" title="{% translate 'Request pending' %}">
<i class="fa-regular fa-hourglass-half"></i>
</button>
<a href="{% url 'groupmanagement:request_retract' g.group.id %}" class="btn btn-danger">
{% translate "Retract" %}
</a>
{% endif %}
</td>
</tr>

View File

@@ -19,7 +19,7 @@
{% translate "Join Requests" %}
{% if acceptrequests %}
<span class="badge bg-secondary">{{ acceptrequests|length }}</span>
<span class="badge text-bg-secondary">{{ acceptrequests|length }}</span>
{% endif %}
</a>
</li>
@@ -30,7 +30,7 @@
{% translate "Leave Requests" %}
{% if leaverequests %}
<span class="badge bg-secondary">{{ leaverequests|length }}</span>
<span class="badge text-bg-secondary">{{ leaverequests|length }}</span>
{% endif %}
</a>
</li>

View File

@@ -10,6 +10,11 @@ urlpatterns = [
path(
"group/request/leave/<int:group_id>/", views.group_request_leave, name="request_leave"
),
path(
"group/request/retract/<int:group_id>/",
views.group_request_retract,
name="request_retract"
),
# group management
path("groupmanagement/requests/", views.group_management, name="management"),
path("groupmanagement/membership/", views.group_membership, name="membership"),

View File

@@ -420,3 +420,42 @@ def group_request_leave(request, group_id):
grouprequest.notify_leaders()
messages.success(request, _('Applied to leave group %(group)s.') % {"group": group})
return redirect("groupmanagement:groups")
@login_required
def group_request_retract(request, group_id):
logger.debug(
f"group_request_retract called by user {request.user} for group id {group_id}"
)
group = get_object_or_404(Group, id=group_id)
if not GroupManager.check_internal_group(group):
logger.warning(
f"User {request.user} attempted to retract group request for "
f"group id {group_id} but it is not a joinable group"
)
messages.warning(
request,
_("You cannot retract that request"),
)
return redirect('groupmanagement:groups')
try:
group_request = GroupRequest.objects.get(
user=request.user, group=group, leave_request=False
)
group_request.delete()
logger.info(f"Deleted group request for user {request.user} to group {group}")
messages.success(
request, _('Retracted application to group %(group)s.') % {"group": group}
)
except GroupRequest.DoesNotExist:
logger.info(
f"{request.user} attempted to retract group request for "
f"group id {group_id} but has no open request"
)
messages.warning(
request, _("You have no open request for that group.")
)
return redirect("groupmanagement:groups")

View File

@@ -1,6 +1,8 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class HRApplicationsConfig(AppConfig):
name = 'allianceauth.hrapplications'
label = 'hrapplications'
verbose_name = _('HR Applications')

View File

@@ -1,3 +1,4 @@
from typing import ClassVar
from django.contrib.auth.models import User
from django.db import models
from sortedm2m.fields import SortedManyToManyField
@@ -40,7 +41,7 @@ class Application(models.Model):
reviewer_character = models.ForeignKey(EveCharacter, on_delete=models.SET_NULL, blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
objects = ApplicationManager()
objects: ClassVar[ApplicationManager] = ApplicationManager()
def __str__(self):
return str(self.user) + " Application To " + str(self.form)

View File

@@ -43,11 +43,11 @@
<td class="text-center">{{ personal_app.form.corp.corporation_name }}</td>
<td class="text-center">
{% if personal_app.approved == None %}
<div class="badge bg-warning">{% translate "Pending" %}</div>
<div class="badge text-bg-warning">{% translate "Pending" %}</div>
{% elif personal_app.approved == True %}
<div class="badge bg-success">{% translate "Approved" %}</div>
<div class="badge text-bg-success">{% translate "Approved" %}</div>
{% else %}
<div class="badge bg-danger">{% translate "Rejected" %}</div>
<div class="badge text-bg-danger">{% translate "Rejected" %}</div>
{% endif %}
</td>
<td class="text-center">
@@ -133,14 +133,14 @@
<td class="text-center">
{% if app.approved == None %}
{% if app.reviewer_str %}
<div class="badge bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
<div class="badge text-bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
{% else %}
<div class="badge bg-warning">{% translate "Pending" %}</div>
<div class="badge text-bg-warning">{% translate "Pending" %}</div>
{% endif %}
{% elif app.approved == True %}
<div class="badge bg-success">{% translate "Approved" %}</div>
<div class="badge text-bg-success">{% translate "Approved" %}</div>
{% else %}
<div class="badge bg-danger">{% translate "Rejected" %}</div>
<div class="badge text-bg-danger">{% translate "Rejected" %}</div>
{% endif %}
</td>
<td class="text-center">
@@ -177,14 +177,14 @@
<td class="text-center">
{% if app.approved == None %}
{% if app.reviewer_str %}
<div class="badge bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
<div class="badge text-bg-info">{% translate "Reviewer:" %} {{ app.reviewer_str }}</div>
{% else %}
<div class="badge bg-warning">{% translate "Pending" %}</div>
<div class="badge text-bg-warning">{% translate "Pending" %}</div>
{% endif %}
{% elif app.approved == True %}
<div class="badge bg-success">{% translate "Approved" %}</div>
<div class="badge text-bg-success">{% translate "Approved" %}</div>
{% else %}
<div class="badge bg-danger">{% translate "Rejected" %}</div>
<div class="badge text-bg-danger">{% translate "Rejected" %}</div>
{% endif %}
</td>
<td class="text-center">

View File

@@ -43,11 +43,11 @@
<td>{{ app.form.corp }}</td>
<td class="text-center">
{% if app.approved == None %}
<div class="badge bg-warning">{% translate "Pending" %}</div>
<div class="badge text-bg-warning">{% translate "Pending" %}</div>
{% elif app.approved == True %}
<div class="badge bg-success">{% translate "Approved" %}</div>
<div class="badge text-bg-success">{% translate "Approved" %}</div>
{% else %}
<div class="badge bg-danger">{% translate "Rejected" %}</div>
<div class="badge text-bg-danger">{% translate "Rejected" %}</div>
{% endif %}
</td>
<td class="text-end">

View File

@@ -31,7 +31,7 @@
</div>
<div class="card mb-3">
<div class="card-header bg-info">
<div class="card-header text-bg-info">
<div class="card-title mb-0">{% translate "Applicant" %}</div>
</div>
@@ -50,7 +50,7 @@
</div>
<div class="card mb-3">
<div class="card-header bg-info">
<div class="card-header text-bg-info">
<div class="card-title mb-0">{% translate "Characters" %}</div>
</div>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,7 @@
import logging
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
logger = logging.getLogger(__name__)
@@ -12,6 +13,7 @@ logger = logging.getLogger(__name__)
class MenuConfig(AppConfig):
name = "allianceauth.menu"
label = "menu"
verbose_name = _("Menu")
def ready(self):
from allianceauth.menu.core import smart_sync

View File

@@ -1,4 +1,4 @@
from __future__ import annotations
from __future__ import annotations # Still needed for Python 3.8, replaced with better implementations in Py39+
import logging
from typing import TYPE_CHECKING

View File

@@ -1,3 +1,4 @@
from typing import ClassVar
from django.db import models
from django.utils.translation import gettext_lazy as _
@@ -68,7 +69,7 @@ class MenuItem(models.Model):
help_text=_("External URL this menu items will link to"),
)
objects = MenuItemManager()
objects: ClassVar[MenuItemManager] = MenuItemManager()
def __str__(self) -> str:
return self.text

View File

@@ -27,7 +27,7 @@
</a>
{% if item.count >= 1 %}
<span class="badge bg-primary m-2 align-self-center{% if not item.is_folder %} me-2{% endif %}">
<span class="badge text-bg-primary m-2 align-self-center{% if not item.is_folder %} me-2{% endif %}">
{{ item.count }}
</span>
{% elif item.url %}
@@ -43,8 +43,7 @@
aria-expanded="false"
aria-controls=""
>
<i class="fas fa-chevron-right"></i>
<i class="fas fa-chevron-down"></i>
<i class="fa-solid fa-chevron-right"></i>
</span>
<ul
class="collapse ps-1 w-100 border-start rounded-start border-light border-3"

View File

@@ -2,13 +2,13 @@
{% load navactive %}
{% load auth_notifications %}
<li class="nav-item {% navactive request 'notifications:' %}" id="menu_item_notifications">
<a class="nav-link" href="{% url 'notifications:list' %}">
<li class="nav-item" id="menu_item_notifications">
<a class="nav-link {% navactive request 'notifications:' %}" href="{% url 'notifications:list' %}">
{% with unread_count=request.user|user_unread_notification_count %}
<i class="fa-solid fa-bell{% if unread_count %} text-danger{% endif %}"></i>
<i class="fa-solid fa-bell me-2 fa-fw{% if unread_count %} text-danger{% endif %}"></i>
{% endwith %}
<span class="d-lg-none d-md-inline m-2">
<span class="d-lg-none d-md-inline">
{% translate "Notifications" %}
</span>
</a>

View File

@@ -2,28 +2,51 @@
{% load evelinks %}
{% load theme_tags %}
<div id="aa-user-info" class="w-100 d-flex flex-column justify-content-center align-items-center text-center py-1 border-top border-secondary {% if not user.is_authenticated %}position-absolute bottom-0{% endif %}">
<div
id="aa-user-info"
class="w-100 d-flex flex-column justify-content-center align-items-center text-center py-1 border-top border-secondary {% if not user.is_authenticated %}position-absolute bottom-0{% endif %}"
>
<div class="d-flex mb-0 w-100">
<div class="p-2 position-relative m-2">
{% if user.is_authenticated %}
{% with request.user.profile.main_character as main %}
<img class="rounded-circle" src="{{ main.character_id|character_portrait_url:64 }}" alt="{{ main.character_name }}">
<img class="rounded-circle position-absolute bottom-0 start-0" src="{{ main.corporation_logo_url_32 }}" alt="{{ main.corporation_name }}">
<img
class="rounded-circle"
src="{{ main.character_id|character_portrait_url:64 }}"
alt="{{ main.character_name }}"
>
<img
class="rounded-circle position-absolute bottom-0 start-0"
src="{{ main.corporation_logo_url_32 }}"
alt="{{ main.corporation_name }}"
>
{% if main.alliance_id %}
<img class="rounded-circle position-absolute bottom-0 end-0" src="{{ main.alliance_logo_url_32 }}" alt="{{ main.alliance_name }}">
<img
class="rounded-circle position-absolute bottom-0 end-0"
src="{{ main.alliance_logo_url_32 }}"
alt="{{ main.alliance_name }}"
>
{% elif main.faction_id %}
<img class="rounded-circle position-absolute bottom-0 end-0" src="{{ main.faction_logo_url_32 }}" alt="{{ main.faction_name }}">
<img
class="rounded-circle position-absolute bottom-0 end-0"
src="{{ main.faction_logo_url_32 }}"
alt="{{ main.faction_name }}"
>
{% endif %}
{% endwith %}
{% else %}
{% include "bundles/image-auth-logo.html" with logo_width="64px" %}
{% endif %}
</div>
<div class="align-self-center text-start">
{% if user.is_authenticated %}
{% with request.user.profile.main_character as main %}
<h5 class="m-0">{{ main.character_name }}</h5>
<p class="m-0 small">{{ main.corporation_name }}</p>
{% if main.alliance_id %}
<p class="m-0 small">{{ main.alliance_name }}</p>
{% elif main.faction_id %}
@@ -34,56 +57,131 @@
<h5 class="m-0">{{ SITE_NAME }}</h5>
{% endif %}
</div>
<div class="ms-auto dropup">
<button type="button" class="h-100 btn" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa-solid fa-gear fa-fw text-light"></i>
</button>
<ul class="dropdown-menu" style="min-width: 200px;">
<li><h6 class="dropdown-header">{% translate "Language" %}</h6></li>
<li>
<a class="dropdown-item">{% include "public/lang_select.html" %}</a>
</li>
<li><h6 class="dropdown-header">{% translate "Theme" %}</h6></li>
<li>
<a class="dropdown-item">
{% theme_select %}
</a>
{% include "public/lang_select.html" %}
</li>
{% theme_select %}
{% if user.is_authenticated and not request.is_mobile_device %}
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">{% translate "Sidebar" %}</h6></li>
<li>
<form class="dropdown-item" action="{% url 'minimize_sidebar' %}?next={{ request.path|urlencode }}" method="post">
{% csrf_token %}
<div class="form-check form-switch">
<input
class="form-check-input"
type="checkbox"
role="switch"
id="toggle-sidebar"
onchange="this.form.submit()"
{% if request.session.MINIMIZE_SIDEBAR %}checked{% endif %}
>
<label class="form-check-label" for="toggle-sidebar">
{% translate "Minimize Sidebar" %}
</label>
</div>
</form>
</li>
{% endif %}
{% if user.is_superuser %}
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">{% translate "Super User" %}</h6></li>
<li>
<a class="dropdown-item" href="https://allianceauth.readthedocs.io/" title="Alliance Auth Documentation"><i class="fa-solid fa-book fa-fw"></i> Alliance Auth Documentation</a>
</li>
<li>
<a class="dropdown-item" href="https://discord.gg/fjnHAmk" title="Alliance Auth Discord"><i class="fa-brands fa-discord fa-fw"></i> Alliance Auth Discord</a>
</li>
<li>
<a class="dropdown-item" href="https://gitlab.com/allianceauth/allianceauth" title="Alliance Auth Git"><i class="fa-brands fa-gitlab fa-fw"></i> Alliance Auth Git</a>
</li>
{% endif %}
{% if user.is_staff %}
<li>
<a class="dropdown-item" href="{% url 'admin:index' %}">
<i class="fa-solid fa-gear fa-fw"></i> {% translate "Admin" %}
</a>
</li>
{% endif %}
<li><hr class="dropdown-divider"></li>
{% if user.is_authenticated %}
<li>
<a class="dropdown-item" href="{% url 'authentication:token_management' %}">
<i class="fa-solid fa-user-lock fa-fw"></i> Token Management
<a
class="dropdown-item"
href="https://allianceauth.readthedocs.io/"
title="{% translate 'Alliance Auth Documentation' %}"
>
<i class="fa-solid fa-book fa-fw"></i>
{% translate "Alliance Auth Documentation" %}
</a>
</li>
<li>
<a class="dropdown-item text-danger" href="{% url 'logout' %}" title="{% translate 'Sign Out' %}"><i class="fa-solid fa-right-from-bracket fa-fw "></i> {% translate 'Sign Out' %}</a>
<a
class="dropdown-item"
href="https://discord.gg/fjnHAmk"
title="{% translate 'Alliance Auth Discord' %}"
>
<i class="fa-brands fa-discord fa-fw"></i>
{% translate "Alliance Auth Discord" %}
</a>
</li>
<li>
<a
class="dropdown-item"
href="https://gitlab.com/allianceauth/allianceauth"
title="{% translate 'Alliance Auth Git' %}"
>
<i class="fa-brands fa-gitlab fa-fw"></i>
{% translate "Alliance Auth Git" %}
</a>
</li>
{% endif %}
{% if user.is_staff %}
<li>
<a
class="dropdown-item"
href="{% url 'admin:index' %}"
title="{% translate 'Admin' %}"
>
<i class="fa-solid fa-gear fa-fw"></i>
{% translate "Admin" %}
</a>
</li>
{% endif %}
<li><hr class="dropdown-divider"></li>
{% if user.is_authenticated %}
<li>
<a
class="dropdown-item"
href="{% url 'authentication:token_management' %}"
title="{% translate 'Token Management' %}"
>
<i class="fa-solid fa-user-lock fa-fw"></i>
{% translate "Token Management" %}
</a>
</li>
<li>
<a
class="dropdown-item text-danger"
href="{% url 'logout' %}"
title="{% translate 'Sign Out' %}"
>
<i class="fa-solid fa-right-from-bracket fa-fw"></i>
{% translate 'Sign Out' %}
</a>
</li>
{% else %}
<li>
<a class="dropdown-item text-success" href="{% url 'authentication:login' %}" title="{% translate 'Sign In' %}"> <i class="fa-solid fa-right-to-bracket fa-fw "></i> {% translate 'Sign In' %}</a>
<a
class="dropdown-item text-success"
href="{% url 'authentication:login' %}"
title="{% translate 'Sign In' %}"
>
<i class="fa-solid fa-right-to-bracket fa-fw"></i>
{% translate 'Sign In' %}
</a>
</li>
{% endif %}
</ul>

View File

@@ -3,9 +3,9 @@
{% load menu_menu_items %}
<div class="col-auto px-0">
<div class="collapse collapse-horizontal" tabindex="-1" id="sidebar">
<div class="collapse collapse-horizontal {% if user.is_authenticated and not request.is_mobile_device and not request.session.MINIMIZE_SIDEBAR %}show{% endif %}" tabindex="-1" id="sidebar">
<div>
<div class="nav-padding navbar-dark bg-dark text-light px-0 d-flex flex-column overflow-hidden vh-100 {% if not user.is_authenticated %}position-relative{% endif %}">
<div class="nav-padding navbar-dark text-bg-dark px-0 d-flex flex-column overflow-hidden vh-100 {% if not user.is_authenticated %}position-relative{% endif %}">
{% if user.is_authenticated %}
<ul id="sidebar-menu" class="navbar-nav flex-column mb-auto overflow-auto pt-2">
<li class="d-flex flex-wrap m-2 p-2 pt-0 pb-0 mt-0 mb-0 me-0 pe-0">

View File

@@ -0,0 +1,65 @@
"""
Alliance Auth Middleware
"""
from user_agents import parse
class DeviceDetectionMiddleware:
"""
Middleware to detect the type of device making the request.
Sets flags on the request object for easy access in views and templates.
Flags include:
- is_mobile: True if the device is a mobile phone.
- is_tablet: True if the device is a tablet.
- is_mobile_device: True if the device is either a mobile phone or a tablet.
- is_touch_capable: True if the device has touch capabilities.
- is_pc: True if the device is a desktop or laptop computer.
- is_bot: True if the device is identified as a bot or crawler.
"""
def __init__(self, get_response):
"""
Initialize the middleware with the get_response callable.
:param get_response:
:type get_response:
"""
self.get_response = get_response
def __call__(self, request):
"""
Process the incoming request to determine if it's from a mobile device.
This method is called when the middleware is invoked. It inspects the
`user-agent` header of the incoming HTTP request to determine the type
of client making the request (e.g., mobile, tablet, PC, bot, etc.).
Flags are set on the `request` object to indicate the client type.
:param request: The HTTP request object.
:type request: HttpRequest
:return: The HTTP response object after processing the request.
:rtype: HttpResponse
"""
# Retrieve the user-agent string from the request headers
user_agent_string = request.headers.get("user-agent", "")
# Parse the user-agent string to extract client information
user_agent = parse(user_agent_string)
# Set flags on the request object based on the client type
request.is_mobile = user_agent.is_mobile # True if the client is a mobile phone
request.is_tablet = user_agent.is_tablet # True if the client is a tablet
request.is_mobile_device = user_agent.is_mobile or user_agent.is_tablet # True if mobile phone or tablet
request.is_touch_capable = user_agent.is_touch_capable # True if the client supports touch input
request.is_pc = user_agent.is_pc # True if the client is a PC
request.is_bot = user_agent.is_bot # True if the client is a bot
# Pass the request to the next middleware or view and get the response
response = self.get_response(request)
# Return the processed response
return response

View File

@@ -1,6 +1,8 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class NotificationsConfig(AppConfig):
name = 'allianceauth.notifications'
label = 'notifications'
verbose_name = _('Notifications')

View File

@@ -1,4 +1,5 @@
import logging
from typing import ClassVar
from django.db import models
from django.contrib.auth.models import User
@@ -56,7 +57,7 @@ class Notification(models.Model):
timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
viewed = models.BooleanField(default=False, db_index=True)
objects = NotificationManager()
objects: ClassVar[NotificationManager] = NotificationManager()
def __str__(self) -> str:
return f"{self.user}: {self.title}"

View File

@@ -15,30 +15,26 @@
<li class="nav-item">
<a class="nav-link active" id="unread-tab" data-bs-toggle="tab" data-bs-target="#unread" type="button" role="tab" aria-controls="unread" aria-selected="true">
{% translate "Unread" %}
<span class="badge bg-secondary">{{ unread|length }}</span>
<span class="badge text-bg-secondary">{{ unread|length }}</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="read-tab" data-bs-toggle="tab" data-bs-target="#read" type="button" role="tab" aria-controls="read" aria-selected="false">
{% translate "Read" %}
<span class="badge bg-secondary">{{ read|length }}</span>
<span class="badge text-bg-secondary">{{ read|length }}</span>
</a>
</li>
{% endblock %}
{% block header_nav_collapse_right %}
<li class="nav-item">
<a href="{% url 'notifications:mark_all_read' %}" class="nav-link" title="{% translate 'Mark all notifications as read' %}">
<i class="fa-solid fa-check-double"></i>
</a>
</li>
{% translate "Mark all notifications as read" as nav_item_title %}
{% url "notifications:mark_all_read" as nav_item_link %}
{% include "framework/header/nav-collapse-icon.html" with fa_icon="fa-solid fa-check-double" url=nav_item_link title=nav_item_title icon_on_mobile=True %}
<li class="nav-item">
<a href="{% url 'notifications:delete_all_read' %}" class="nav-link" title="{% translate 'Delete all read notifications' %}">
<i class="fa-solid fa-trash-can"></i>
</a>
</li>
{% translate "Delete all read notifications" as nav_item_title %}
{% url "notifications:mark_all_read" as nav_item_link %}
{% include "framework/header/nav-collapse-icon.html" with fa_icon="fa-solid fa-trash-can" url=nav_item_link title=nav_item_title icon_on_mobile=True %}
{% endblock %}
{% block content %}

View File

@@ -17,7 +17,7 @@
</div>
<div class="card border-{{ notif.level }}">
<div class="card-header bg-{{ notif.level }}">
<div class="card-header text-bg-{{ notif.level }}">
{{ notif.timestamp }}
{{ notif.title }}
</div>

View File

@@ -1,6 +1,8 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class OptimerConfig(AppConfig):
name = 'allianceauth.optimer'
label = 'optimer'
verbose_name = _('Fleet Operations')

View File

@@ -14,7 +14,7 @@
<th class="text-center">{% translate "Operation" %}</th>
<th class="text-center">{% translate "Type" %}</th>
<th class="text-center">{% translate "Form Up System" %}</th>
<th class="text-center">{% translate "EVE Time" %}</th>
<th class="text-center">{% translate "EVE time" %}</th>
</tr>
</thead>

View File

@@ -22,8 +22,8 @@
</tr>
</thead>
{% for ops in timers %}
<tbody>
<tbody>
{% for ops in timers %}
<tr>
<td>
{{ ops.operation_name }}
@@ -55,8 +55,8 @@
</td>
{% endif %}
</tr>
</tbody>
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
{% endblock content %}

View File

@@ -13,19 +13,17 @@
{% block header_nav_collapse_right %}
{% if perms.auth.optimer_management %}
<li class="nav-item">
<a class="btn btn-success" href="{% url 'optimer:add' %}">
{% translate "Create Operation" %}
</a>
</li>
{% translate "Create Operation" as nav_item_title %}
{% url "optimer:add" as nav_item_link %}
{% include "framework/header/nav-collapse-button.html" with btn_modifier="success" url=nav_item_link title=nav_item_title fa_icon="fa-solid fa-plus" icon_on_mobile=True %}
{% endif %}
{% endblock header_nav_collapse_right %}
{% block content %}
<div>
<div class="text-center mb-3">
<div class="badge bg-primary text-start">
<b>{% translate "Current Eve Time:" %}</b>
<div class="badge text-bg-primary text-start">
<b>{% translate "Current EVE time:" %}</b>
<span id="current-time"></span>
</div>
</div>
@@ -67,83 +65,85 @@
{% include 'bundles/timers-js.html' %}
<script>
// Data
const timers = [
{% for op in optimer %}
{
'id': {{ op.id }},
'start': moment("{{ op.start | date:"c" }}"),
'expired': false
},
{% endfor %}
];
$(document).ready(() => {
// Data
const timers = [
{% for op in optimer %}
{
'id': {{ op.id }},
'start': moment("{{ op.start | date:"c" }}"),
'expired': false
},
{% endfor %}
];
/**
* Update a timer
* @param timer Timer information
*/
const updateTimer = (timer) => {
if (timer.start.isAfter(Date.now())) {
const duration = moment.duration(timer.start - moment(), 'milliseconds');
/**
* Update a timer
* @param timer Timer information
*/
const updateTimer = (timer) => {
if (timer.start.isAfter(Date.now())) {
const duration = moment.duration(timer.start - moment(), 'milliseconds');
document.getElementById("countdown" + timer.id).innerHTML = getDurationString(duration);
} else {
timer.expired = true;
document.getElementById("countdown" + timer.id).innerHTML = getDurationString(duration);
} else {
timer.expired = true;
document.getElementById("countdown" + timer.id).innerHTML = "";
}
};
document.getElementById("countdown" + timer.id).innerHTML = "";
}
};
/**
* Update all timers
*/
const updateAllTimers = () => {
const l = timers.length;
/**
* Update all timers
*/
const updateAllTimers = () => {
const l = timers.length;
for (let i=0; i < l; ++i) {
if (timers[i].expired) continue;
for (let i=0; i < l; ++i) {
if (timers[i].expired) continue;
updateTimer(timers[i]);
}
};
updateTimer(timers[i]);
}
};
/**
* Set the local time info for the timer
* @param timer Timer information
*/
const setLocalTime = (timer) => {
document.getElementById("localtime" + timer.id).innerHTML = timer.start.format("ddd @ LT");
};
/**
* Set the local time info for the timer
* @param timer Timer information
*/
const setLocalTime = (timer) => {
document.getElementById("localtime" + timer.id).innerHTML = timer.start.format("ddd @ LT");
};
/**
* Set all local time fields
*/
const setAllLocalTimes = () => {
const l = timers.length;
/**
* Set all local time fields
*/
const setAllLocalTimes = () => {
const l = timers.length;
for (let i=0; i < l; ++i) {
setLocalTime(timers[i]);
}
};
for (let i=0; i < l; ++i) {
setLocalTime(timers[i]);
}
};
/**
* Get the current Eve time as a string
* @returns {string} Eve time string
*/
const updateClock = () => {
document.getElementById("current-time").innerHTML = getCurrentEveTimeString();
};
/**
* Get the current EVE time as a string
* @returns {string} EVE time string
*/
const updateClock = () => {
document.getElementById("current-time").innerHTML = getCurrentEveTimeString();
};
const timedUpdate = () => {
updateClock();
updateAllTimers();
};
const timedUpdate = () => {
updateClock();
updateAllTimers();
};
// Set initial values
setAllLocalTimes();
timedUpdate();
// Set initial values
setAllLocalTimes();
timedUpdate();
// Start timed updates
setInterval(timedUpdate, 1000);
// Start timed updates
setInterval(timedUpdate, 1000);
});
</script>
{% endblock content %}

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