Compare commits

...

670 Commits

Author SHA1 Message Date
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
Joel Falknau
055077fa77
Version Bump 4.6.4 2025-03-03 11:49:43 +10:00
Joel Falknau
f342ccbc6a
Revert "[REMOVE] Unused imports"
This reverts commit f497c18e5b5f6cd504ff57913a88beb5818ffdcb.
2025-03-03 11:47:19 +10:00
Joel Falknau
37ffd0a1ac
Version Bump 4.6.3 2025-03-03 11:29:26 +10:00
Ariel Rin
a1f705381e Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1687
2025-03-03 01:28:36 +00:00
Ariel Rin
c0970ad4fa Updates for project Alliance Auth 2025-03-03 01:28:36 +00:00
Ariel Rin
3818d0c6d1 Merge branch 'crontab' into 'master'
Crontab

See merge request allianceauth/allianceauth!1695
2025-03-03 01:19:32 +00:00
Ariel Rin
95411c79cb Crontab 2025-03-03 01:19:32 +00:00
Ariel Rin
eeccbbacfc Merge branch 'limit-slixmpp' into 'master'
[CHANGE] Limit `slixmpp` version

See merge request allianceauth/allianceauth!1694
2025-03-03 01:19:09 +00:00
Peter Pfeufer
f6c4180502
[CHANGE] Limit slixmpp version
An update to `slixmpp` v1.9.0 pulls in `pyasn1` v0.6.1, which causes a dependency issue with `python-jose`

> ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
> python-jose 3.4.0 requires pyasn1<0.5.0,>=0.4.1, but you have pyasn1 0.6.1 which is incompatible.
2025-02-28 11:09:22 +01:00
Ariel Rin
f4d3d6c0b1 Merge branch 'sorting-data' into 'master'
[ADD] Sorting data to the tables datetime fields

See merge request allianceauth/allianceauth!1691
2025-02-27 03:37:30 +00:00
Ariel Rin
e9d2d11297 Merge branch 'correct-locale-link' into 'master'
[FIX] Correct locale link for moment js

See merge request allianceauth/allianceauth!1692
2025-02-27 03:37:27 +00:00
Peter Pfeufer
96204b29e8
[REMOVE] Redundant locale information
Since momentJS is already loaded with `locale=True`, this is not needed anymore.
2025-02-26 22:57:19 +01:00
Peter Pfeufer
47842c1243
[FIX] Correct locale link for moment js
The other one was the directory for the node modules …
2025-02-26 21:53:27 +01:00
Peter Pfeufer
9b494106bc
[ADD] Sorting data to the table field
Since the localized date and time values can have different formats, sorting with them wasn't gonna work reliably, so we add the original datetime string as sorting data.
2025-02-26 21:33:21 +01:00
Ariel Rin
d51e730a7f Merge branch 'mumble-stats-date-fixes' into 'master'
[CHANGE] Datetime Improvements for Mumble Connection History

See merge request allianceauth/allianceauth!1689
2025-02-26 10:21:39 +00:00
Ariel Rin
363909c0c4 Merge branch 'remove-stray-a-tag' into 'master'
[REMOVE] Stray `a` tag from `header-nav-brand`

See merge request allianceauth/allianceauth!1688
2025-02-26 10:19:38 +00:00
Ariel Rin
82273f68fe Merge branch 'expand-checks' into 'master'
Expand checks

See merge request allianceauth/allianceauth!1685
2025-02-26 10:19:10 +00:00
Ariel Rin
12fa38b446 Merge branch 'lcode-links' into 'master'
[ADD] Links to available languages for mapping

See merge request allianceauth/allianceauth!1690
2025-02-26 10:18:31 +00:00
Peter Pfeufer
c26af593ff
[ADD] Links to available languaes for mapping
Makes it easier for adding new languages in the future
2025-02-26 11:11:18 +01:00
Peter Pfeufer
8e9a53c494
[CHANGE] Localze datetime output 2025-02-25 18:17:51 +01:00
Peter Pfeufer
5559ce5fbb
[CHANGE] Return empty field when no datetime available 2025-02-25 18:01:58 +01:00
Peter Pfeufer
faa529a55b
[REMOVE] Stray a tag from header-nav-brand 2025-02-25 17:33:02 +01:00
Peter Pfeufer
4ccfe20c14
[CHANGE] Set email to dummy@example.net 2025-02-25 11:52:25 +01:00
Joel Falknau
960c9625fe
Version Bump 4.6.2a 2025-02-25 19:29:36 +10:00
Ariel Rin
7b92d103d6 Merge branch 'crontab' into 'master'
Crontab fixes

See merge request allianceauth/allianceauth!1686
2025-02-25 09:16:59 +00:00
Ariel Rin
c1e2449084 Crontab fixes 2025-02-25 09:16:59 +00:00
Peter Pfeufer
3acb651650
[CHANGE] Update test 2025-02-19 10:18:35 +01:00
Peter Pfeufer
2de57b334b
[ADD] ESI_USER_CONTACT_EMAIL to test settings 2025-02-19 10:09:00 +01:00
Peter Pfeufer
0498f5bb1b
[MISC] Format for better readability 2025-02-19 10:00:18 +01:00
Peter Pfeufer
929485a8f9
[ADD] Check for empty SITE_URL 2025-02-19 09:39:22 +01:00
Peter Pfeufer
28cb62f373
[ADD] Check for ESI_USER_CONTACT_EMAIL 2025-02-19 09:31:49 +01:00
Ariel Rin
02214b74d0 Merge branch 'cleanup-unused-imports' into 'master'
[REMOVE] Unused imports

See merge request allianceauth/allianceauth!1684
2025-02-16 23:25:58 +00:00
Peter Pfeufer
f497c18e5b
[REMOVE] Unused imports 2025-02-16 23:20:36 +01:00
Ariel Rin
cb57d922e6 Merge branch 'pre-commit-updates' into 'master'
Pre commit updates

See merge request allianceauth/allianceauth!1682
2025-02-11 05:15:48 +00:00
Ariel Rin
805d138b09 Merge branch 'fix-timerboard-initial-sort-order' into 'master'
[FIX] Timerboard initial sort order

See merge request allianceauth/allianceauth!1681
2025-02-11 05:14:48 +00:00
Ariel Rin
09a583fb1d Merge branch 'authorized-apps-url' into 'master'
[CHANGE] Update URL to apps management

See merge request allianceauth/allianceauth!1683
2025-02-09 08:18:23 +00:00
Peter Pfeufer
146c4c8d94
[CHANGE] Update URL to apps management 2025-02-09 08:55:51 +01:00
Peter Pfeufer
c2ae680f72
[CHANGE] Apply new pre-commit config 2025-02-01 16:22:36 +01:00
Peter Pfeufer
b5ad1c8a1a
[CHANGE] Use global exclude instead of per hook 2025-02-01 16:18:08 +01:00
Peter Pfeufer
8be2760fc4
[CHANGNE] Update repos and dependencies 2025-02-01 16:14:36 +01:00
Peter Pfeufer
f047943eb7
[CHANGE] Define language versions 2025-02-01 16:11:44 +01:00
Peter Pfeufer
43906f41b3
[FIX] Timerboard initial sort order 2025-02-01 16:00:03 +01:00
Ariel Rin
a18ec98877 Merge branch 'django-checks' into 'master'
Improve Django checks

See merge request allianceauth/allianceauth!1679
2025-01-28 09:16:14 +00:00
Ariel Rin
14163d2c0c Merge branch 'fix-menu-chevron' into 'master'
[FIX] Active menu chevron

See merge request allianceauth/allianceauth!1680
2025-01-28 09:13:20 +00:00
Peter Pfeufer
81d9c41cf6
[FIX] Active menu chevron 2025-01-26 19:24:24 +01:00
Peter Pfeufer
58f5a5b41d
[CHANGE] elif to safe potential if checks and time 2025-01-22 04:23:19 +01:00
Peter Pfeufer
6363bb706a
[CHANGE] Put some of the hint links in variables 2025-01-22 04:12:33 +01:00
Peter Pfeufer
baf3be4cb2
[CHANGE] Simplify if blocks for MariaDB and MySQL checks 2025-01-22 04:05:02 +01:00
Joel Falknau
e69444fe79
Version Bump 4.6.1 2025-01-20 19:03:17 +10:00
Ariel Rin
7483fcb876 Merge branch 'offsetfix' into 'master'
Cron Offset Fix

See merge request allianceauth/allianceauth!1678
2025-01-20 08:58:24 +00:00
Joel Falknau
a57d55504d
Cosmetic fix to * cron 2025-01-20 18:45:37 +10:00
Joel Falknau
affb30e9f4
add migration 2025-01-20 18:37:22 +10:00
Joel Falknau
588cb1b7ca
Version Bump 4.6.0 2025-01-20 15:37:29 +10:00
Ariel Rin
a609faa91b Merge branch 'language-code-mapping' into 'master'
[ADD] Locale mapping for DataTables and Moment.JS

See merge request allianceauth/allianceauth!1677
2025-01-20 04:14:27 +00:00
Peter Pfeufer
856e939c21
[ADD] Locale mapping for DataTables and Moment.JS 2025-01-20 04:35:10 +01:00
Ariel Rin
1b6cf98885 Merge branch '1416-timerboard-datatables' into 'master'
[CHANGE] Timertables to Datatables

Closes #1416

See merge request allianceauth/allianceauth!1676
2025-01-18 03:50:34 +00:00
Peter Pfeufer
92c2af9975
[CHANGE] Timertables to Datatables 2025-01-12 17:12:23 +01:00
Ariel Rin
5ef70bb031 Merge branch 'cronbuilder' into 'master'
Cron Offset Tasks

See merge request allianceauth/allianceauth!1672
2025-01-10 12:10:50 +00:00
Ariel Rin
60998bffc2 Cron Offset Tasks 2025-01-10 12:10:49 +00:00
Ariel Rin
a5971314f5 Merge branch 'structure-timer-updates' into 'master'
Timerboard Updates

See merge request allianceauth/allianceauth!1674
2025-01-10 11:22:24 +00:00
Peter Pfeufer
a03c766840
[FIX] Spelling mistakes
Thx @soratidus999
2025-01-10 11:41:31 +01:00
Ariel Rin
ad47ff2c54 Merge branch 'dockerbaremetalanalytics' into 'master'
Docker/Baremetal Analytics

See merge request allianceauth/allianceauth!1671
2025-01-10 08:29:02 +00:00
Ariel Rin
3efdb8f12b Docker/Baremetal Analytics 2025-01-10 08:29:02 +00:00
Ariel Rin
823fc82d19 Merge branch 'sri-hashes' into 'master'
[ADD] `django-sri` to provide integrity hashes for local static files

See merge request allianceauth/allianceauth!1673
2025-01-10 08:27:28 +00:00
Ariel Rin
a93e510895 Merge branch 'fix-moment.js-language-code' into 'master'
[FIX] Moment.JS Localisation

See merge request allianceauth/allianceauth!1675
2025-01-10 08:21:20 +00:00
Peter Pfeufer
d99f5858d8
[FIX] Moment.JS Localisation
Load the right JS when our language code has 2 parts
- fr_FR
- it_IT
- pl_PL
And so on
2025-01-08 23:04:29 +01:00
Peter Pfeufer
4578ecf21d
[ADD] Missing migration 2025-01-06 18:17:21 +01:00
Peter Pfeufer
b737504d52
[REMOVE] Unused imports 2025-01-06 18:08:52 +01:00
Peter Pfeufer
c6b6443901
[CHANGE] Move structure label BG detection to Python instead of Django
Python is faster and needs less memory …
It's also much mire readable that way.
2025-01-06 18:08:20 +01:00
Peter Pfeufer
f51523dc07
[CHANGE] Use TextChoices displayed name in template 2025-01-06 17:03:00 +01:00
Peter Pfeufer
bd4dd60c98
[CHANGE] Rename I-Hub to Sovereignty Hub 2025-01-06 16:48:02 +01:00
Peter Pfeufer
a4ea48e14e
[CHANGE] Group timer types by BG color BS class 2025-01-06 16:43:59 +01:00
Peter Pfeufer
646d3f5408
[ADD] Mercenary Dens to structure type selection 2025-01-06 16:24:30 +01:00
Peter Pfeufer
0f057ffa84
[ADD] django-sri to provide integrity hashes for local static files 2025-01-02 16:54:37 +01:00
Joel Falknau
7033406ba6
Version Bump 4.5.0 2024-12-10 13:07:03 +10:00
Ariel Rin
6b395ca1d4 Merge branch 'executableflag' into 'master'
Executableflag

See merge request allianceauth/allianceauth!1667
2024-12-09 23:57:02 +00:00
Ariel Rin
795a7e006f Merge branch 'randomdelay' into 'master'
Spread esi tasks over 10 minutes

See merge request allianceauth/allianceauth!1666
2024-12-09 23:56:13 +00:00
Ariel Rin
2a894cd62c Merge branch 'dockermariadbcnf' into 'master'
DockerMariaDB Config Template

See merge request allianceauth/allianceauth!1668
2024-12-09 23:54:33 +00:00
Ariel Rin
9ada26e849 DockerMariaDB Config Template 2024-12-09 23:54:33 +00:00
Ariel Rin
7120b3956c Merge branch 'group_display' into 'master'
fix group display for Groups that are Group Leaders

See merge request allianceauth/allianceauth!1670
2024-12-09 23:54:18 +00:00
root
4da67cfaf6 fix group display for Groups that are Group Leaders 2024-12-08 13:01:59 -06:00
Joel Falknau
0a940810bd
dont need this now the flag is set correctly, more consistent 2024-12-05 11:49:54 +10:00
Joel Falknau
a868438492
force these flags on setup 2024-12-05 11:49:32 +10:00
Joel Falknau
dc1ed8c570
+x 2024-12-05 11:48:45 +10:00
Joel Falknau
8489f204dd
fix test patch 2024-12-04 22:10:06 +10:00
Ariel Rin
1478588016 Merge branch 'add-js-type' into 'master'
[ADD] `js_type` parameter to allow JS modules to be loaded

See merge request allianceauth/allianceauth!1664
2024-12-04 11:24:21 +00:00
Ariel Rin
a16eb4b7f7 Merge branch 'image-overflow-fix' into 'master'
[FIX] Prevent images from overflowing their parent element

See merge request allianceauth/allianceauth!1665
2024-12-04 11:23:38 +00:00
Joel Falknau
292fb7b29d
Add docs for smoothing out task execution 2024-12-04 18:35:07 +10:00
Joel Falknau
c6890dd2c6
Spread esi tasks over 10 minutes 2024-12-04 18:01:01 +10:00
Ariel Rin
702564d15e correct top level indentation to be able to be directly copy-pasteable 2024-12-03 09:50:55 +00:00
Peter Pfeufer
cef2e86ea1
[FIX] Prevent images from overflowing their parent element 2024-11-26 22:28:35 +01:00
Peter Pfeufer
50681b023b
[CHANGE] Move the if inside the script tag
Makes the code better readable and maintainable.
2024-11-23 01:55:04 +01:00
Peter Pfeufer
2822775fb8
[ADD] js_type parameter to allow JS modules to be loaded 2024-11-22 15:01:32 +01:00
Ariel Rin
ef7c8be7b5 Merge branch 'fix/default-collation' into 'master'
Fix Different collations being used by docker and baremetal installations

See merge request allianceauth/allianceauth!1662
2024-11-17 01:57:59 +00:00
Ariel Rin
d639617eba Merge branch 'master' into 'master'
Celery documentation improvements

See merge request allianceauth/allianceauth!1663
2024-11-17 01:57:05 +00:00
r0kym
2125192f72 Fix typo in the long-running tasks example 2024-11-15 15:13:25 +01:00
r0kym
8d63801b00 Change logging imports and commands
Now uses `get_extension_logger` as is recommanded by https://allianceauth.readthedocs.io/en/v4.4.2/development/custom/logging.html
2024-11-15 15:12:03 +01:00
Matteo Ghia
e053fb7d96
also in baremetal docs 2024-11-09 12:45:59 +01:00
Matteo Ghia
ae7ed5c297
add default keyword required by mariadb 2024-11-09 12:45:31 +01:00
Matteo Ghia
d624ba4427
set default collation in startup script 2024-11-09 12:01:57 +01:00
Joel Falknau
164cd4fbb2
Merge branch 'master' of gitlab.com:allianceauth/allianceauth 2024-11-08 12:48:21 +10:00
Joel Falknau
94b52c850e
Version Bump 4.4.2 2024-11-08 12:47:55 +10:00
Ariel Rin
4d19ceb388 Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1660
2024-11-08 02:43:11 +00:00
Ariel Rin
9a64728311 Updates for project Alliance Auth 2024-11-08 02:43:11 +00:00
Ariel Rin
dcc0c78daf Merge branch 'fix-language-selector' into 'master'
[FIX] Language selector

See merge request allianceauth/allianceauth!1661
2024-11-08 02:17:30 +00:00
Peter Pfeufer
5507c30af4
[CHANGE] Use language local name 2024-11-07 10:49:31 +01:00
Peter Pfeufer
66b97835d4
[FIX] Language selector 2024-10-22 21:20:00 +02:00
Ariel Rin
fd66a7cb20 Merge branch 'fix-mumble-chart-font-color' into 'master'
[FIX] Make chart labels better readable for darker themes

See merge request allianceauth/allianceauth!1659
2024-10-22 02:14:36 +00:00
Peter Pfeufer
c0f0f8db73
[FIX] Make chart labels better readable for darker themes 2024-10-21 05:35:22 +02:00
Joel Falknau
4210b2eabc
Version Bump 4.4.0 2024-10-21 13:00:43 +10:00
Ariel Rin
225e68647e Merge branch 'add-missing-mumble-migration' into 'master'
[ADD] Missing Mumble migration

See merge request allianceauth/allianceauth!1655
2024-10-18 02:37:50 +00:00
Ariel Rin
7e2f864ebf Merge branch 'optimer-fix' into 'master'
Fix maximum character limit for duration in the optimer form

See merge request allianceauth/allianceauth!1656
2024-10-18 02:37:19 +00:00
Ariel Rin
f2384ba45b Merge branch 'master' into 'master'
Use https where applicable

See merge request allianceauth/allianceauth!1657
2024-10-18 02:29:33 +00:00
salartarium
c5918b9b3c Use https where applicable 2024-10-18 02:29:33 +00:00
Aaron Kable
ffedc4103d fix max chars on duration 2024-10-14 19:22:46 +08:00
Peter Pfeufer
0467b23a1a
[ADD] Missing Mumble migration
Running migrations:
  No migrations to apply.
  Your models in app(s): 'mumble' have changes that are not yet reflected in a migration, and so won't be applied.
  Run 'manage.py makemigrations' to make new migrations, and then re-run 'manage.py migrate' to apply them.
2024-10-07 15:42:51 +02:00
Ariel Rin
cda5ce739f Merge branch 'mumbleconnectionhistory' into 'master'
Mumble Connection History Page

See merge request allianceauth/allianceauth!1648
2024-10-04 12:12:17 +00:00
Ariel Rin
e5c8426ea3 Merge branch 'fixing-bs3-side-menu' into 'master'
[REMOVE] Duplicate menu item

See merge request allianceauth/allianceauth!1654
2024-10-04 12:07:22 +00:00
Ariel Rin
b2bd489ddc Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1653
2024-10-04 12:06:53 +00:00
Ariel Rin
6397cf358a Updates for project Alliance Auth 2024-10-04 12:06:53 +00:00
Ariel Rin
90fcc4a811 Merge branch 'master' into 'master'
Add an example to the notification documentation

See merge request allianceauth/allianceauth!1652
2024-10-04 12:05:17 +00:00
Ariel Rin
ec7472fe22 Merge branch 'bootstrap-dark-fixes' into 'master'
[FIX] Re-add the dark bootstrap html tag

See merge request allianceauth/allianceauth!1651
2024-10-04 12:03:25 +00:00
Peter Pfeufer
5148b1914d
[REMOVE] Duplicate menu item 2024-10-04 13:56:57 +02:00
Ariel Rin
2e0716f5ae Merge branch 'mumbleconnectionhistory-improvements' into 'mumbleconnectionhistory'
[CHANGE] Some improvements

See merge request soratidus999/allianceauth!12
2024-09-23 23:33:01 +00:00
Peter Pfeufer
1fb091acb2
[CHANGE] Some improvements
- Fixed Bootstraps cards, rows and cols
- Replaced style argument with Bootstrap class
- Removed unused Django templatetag
- JS modernized and moved to its own script tag instead of concatenating it with other more or less "global" scripts
- Fixed Bootstrap classes
2024-09-23 15:09:35 +02:00
r0kym
07c62ed32a Add an example to the notification documentation 2024-09-23 13:15:19 +02:00
Aaron Kable
660fe79d08 re-add the dark bootstrap html tag 2024-09-17 21:24:01 +08:00
Joel Falknau
42ee06470c
Version Bump 4.3.1 2024-09-13 20:22:31 +10:00
Joel Falknau
69aaa9652f
compilemessages 2024-09-13 20:09:46 +10:00
Ariel Rin
1ccfff50e5 Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1649
2024-09-13 10:09:10 +00:00
Ariel Rin
57a39557fd Updates for project Alliance Auth 2024-09-13 10:09:09 +00:00
Joel Falknau
c36dea08e3
compilemessages 2024-09-13 20:01:33 +10:00
Joel Falknau
d3acd821b7
remove excess translations 2024-09-13 20:01:20 +10:00
Joel Falknau
0a17427169
update language codes 2024-09-13 19:59:40 +10:00
Ariel Rin
ce8935e621 Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1647
2024-09-13 08:55:07 +00:00
Ariel Rin
efff946a56 Updates for project Alliance Auth 2024-09-13 08:55:07 +00:00
Joel Falknau
1dea92ed76
add Connection History 2024-09-12 15:54:14 +10:00
Joel Falknau
3f54d49d8b
update help text from mumble definition 2024-09-12 15:54:06 +10:00
Joel Falknau
77da6928b2
add Chart.js 4.4.1 to Bundles 2024-09-12 15:53:53 +10:00
Joel Falknau
bd17b95cac
Version Bump 4.3.0 2024-09-09 14:05:58 +10:00
Joel Falknau
4ed1c5b7c4
bring test celery in line with project 2024-09-09 13:59:43 +10:00
Ariel Rin
271fd8e8c4 Merge branch 'docs-user-creation-fix' into 'master'
[Docs] Add shell option to adduser command for Ubuntu install docs

See merge request allianceauth/allianceauth!1622
2024-09-09 03:58:47 +00:00
colcrunch
9b4321281a [Docs] Add shell option to adduser command for Ubuntu install docs 2024-09-09 03:58:47 +00:00
Ariel Rin
052c35c8e5 Merge branch 'metenox' into 'master'
Timerboard Improvements

Closes #1384

See merge request allianceauth/allianceauth!1645
2024-09-09 03:57:21 +00:00
Ariel Rin
0fcb517b0b Timerboard Improvements 2024-09-09 03:57:21 +00:00
Ariel Rin
dcfddf0add Merge branch 'docs2' into 'master'
Documentation

See merge request allianceauth/allianceauth!1646
2024-09-09 03:43:30 +00:00
Joel Falknau
4a4258d0e6
use docker command inside docker 2024-09-09 13:32:18 +10:00
Joel Falknau
dd15a221aa
split repo from install 2024-09-09 13:32:06 +10:00
Joel Falknau
737e02293a
expand mumble service install docs 2024-09-09 12:59:49 +10:00
Joel Falknau
c34efebacf
define charsets for db backed apps 2024-09-03 12:27:50 +10:00
Joel Falknau
4602097399
remove DB step from mumble install, sqlite has been reccommended for a while 2024-09-03 12:27:02 +10:00
Joel Falknau
7051e06564
update features from readme 2024-09-03 12:26:43 +10:00
Joel Falknau
9767ce79d8
cleanup some local.py references 2024-09-03 12:26:31 +10:00
Joel Falknau
0c090f1486
Ubuntu 2004 should not be reccommended for new isntalls 2024-09-03 12:25:01 +10:00
Joel Falknau
618ee81f9b
remove unneeded analytics docs for removed features 2024-09-03 12:24:37 +10:00
Joel Falknau
98efb9f887
split maintenance tasks into bare metal / docker 2024-09-03 12:24:17 +10:00
Ariel Rin
cbe6c821cc Merge branch 'custom-css' into 'master'
[ADD] Custom CSS Module

See merge request allianceauth/allianceauth!1643
2024-08-21 05:01:38 +00:00
Ariel Rin
de9d2b39a6 Merge branch 'theme-html-tags' into 'master'
[ADD] Theme html tags

See merge request allianceauth/allianceauth!1642
2024-08-21 04:59:18 +00:00
Peter Pfeufer
0d5f22288b
Merge branch 'switch-to-django-solo' into custom-css 2024-08-20 14:41:52 +02:00
Peter Pfeufer
e0d76dc268
[CHANGE] Switch to Django Solo 2024-08-20 14:41:43 +02:00
Peter Pfeufer
ecc9e68330
[CHANGE] Consolidate migrations 2024-08-14 13:25:49 +02:00
Peter Pfeufer
710149ec21
[FIX] Check if the CustomCSS object exists 2024-08-14 13:22:23 +02:00
Peter Pfeufer
3c2c137dad
[CHANGE] improve try block in template tag 2024-08-14 13:05:32 +02:00
Peter Pfeufer
a8271c4189
[CHANGE] Remove custom CSS file when it will be empty 2024-08-14 12:58:01 +02:00
Peter Pfeufer
3315ae7778
[ADD] Module to base settings file 2024-08-14 12:47:38 +02:00
Peter Pfeufer
d2f048f8fe
[ADD Example template for admin overrides
For when we might want to add syntax highlight ti it, which is a completely different can of worms though.
2024-08-14 12:45:57 +02:00
Peter Pfeufer
0fe2855faa
[ADD] Custom CSS to base file
Check if the CSS file exists and add it to the HTML output
2024-08-14 12:44:51 +02:00
Peter Pfeufer
79a1fa3d7c
[ADD] CSS compression on save 2024-08-14 12:42:52 +02:00
Peter Pfeufer
96fe88d5c7
[REMOVE] highlight.js and leave it as an example in the widget code 2024-08-14 11:53:02 +02:00
Peter Pfeufer
59391ad3c5
[ADD] Custom CSS module (First steps) 2024-08-11 22:34:16 +02:00
Peter Pfeufer
94e9c08422
[ADD] Theme html tags 2024-08-08 10:22:14 +02:00
Joel Falknau
acff3695bc
Version Bump 4.2.2 2024-08-06 12:43:06 +10:00
Ariel Rin
43ec8514aa Merge branch 'improve-redis-installation-on-ubuntu' into 'master'
[MISC] Improve Redis installation instructions for Ubuntu

See merge request allianceauth/allianceauth!1641
2024-08-05 01:26:07 +00:00
Peter Pfeufer
4c629b193f
[CHANGE] Redis hint link 2024-08-05 03:21:14 +02:00
Peter Pfeufer
c651da4011
[MISC] Improve Redis installation instructions for Ubuntu 2024-08-05 03:12:24 +02:00
Ariel Rin
da382cffd1 Merge branch 'fix-mumble-url' into 'master'
[FIX] Mumble URL in service card

See merge request allianceauth/allianceauth!1637
2024-08-05 01:04:24 +00:00
Ariel Rin
4ecfc3afd8 Merge branch 'docs-docker-headlines' into 'master'
[CHANGE] Clarify that these instructions are for Docker

See merge request allianceauth/allianceauth!1638
2024-08-05 01:02:57 +00:00
Ariel Rin
4eb7dbbe62 Merge branch 'add-margin' into 'master'
[ADD] A bit margin to the notifications

See merge request allianceauth/allianceauth!1639
2024-08-05 01:02:52 +00:00
Ariel Rin
c96ba65296 Merge branch 'avoid-KeyError-in-checks' into 'master'
[FIX] Avoid `KeyError` in `celery_settings` checks

See merge request allianceauth/allianceauth!1640
2024-08-05 01:02:40 +00:00
Peter Pfeufer
ff2f60f7f3
[FIX] Avoid KeyError in celery_settings checks 2024-08-04 18:29:43 +02:00
Peter Pfeufer
3000545c98
[ADD] A bit margin to the notifications 2024-07-26 23:09:15 +02:00
Peter Pfeufer
f3ad092ef2
[CHANGNE] Clarify that these instructions are for Docker
This should also fix the menu item title, hopefully …
2024-07-19 19:13:48 +02:00
Peter Pfeufer
a012e7df2f
[FIX] Mumble URL in service card 2024-07-19 17:33:32 +02:00
Ariel Rin
1fa77412c0 Merge branch 'fix/missing-celery-setting' into 'master'
fix missing setting in celery

See merge request allianceauth/allianceauth!1636
2024-07-15 13:39:28 +00:00
Matteo Ghia
e56caeb22b
fix missing setting in celery 2024-07-15 14:53:45 +02:00
Joel Falknau
ceb07ebc67
Version Bump 4.2.1 2024-07-15 22:20:32 +10:00
Ariel Rin
237075d45c Merge branch 'checklib' into 'master'
More Detail on the SQL charset check

See merge request allianceauth/allianceauth!1635
2024-07-15 12:18:47 +00:00
Ariel Rin
7099b1946d More Detail on the SQL charset check 2024-07-15 12:18:47 +00:00
Joel Falknau
e416ab8ff2
Version Bump 4.2.0 2024-07-15 21:25:00 +10:00
Joel Falknau
2802ed03a5
Add Celery to classifiers 2024-07-15 21:22:58 +10:00
Ariel Rin
4af73c76fe Merge branch 'analytics' into 'master'
Analytics

See merge request allianceauth/allianceauth!1632
2024-07-15 11:00:27 +00:00
Ariel Rin
b6149979aa Analytics 2024-07-15 11:00:26 +00:00
Ariel Rin
cb20288427 Merge branch 'checklib' into 'master'
More Checks for System Packages and Configs

See merge request allianceauth/allianceauth!1633
2024-07-15 11:00:06 +00:00
Ariel Rin
db6f4c91dc More Checks for System Packages and Configs 2024-07-15 11:00:06 +00:00
Ariel Rin
57ac7a5277 Merge branch 'center-error-messages' into 'master'
[CHANGE] Center HTTPError messages

See merge request allianceauth/allianceauth!1634
2024-07-15 03:32:34 +00:00
Peter Pfeufer
136438f9c2
[CHANGE] Center HTTPError messages 2024-07-13 00:31:44 +02:00
Ariel Rin
e2be8b3440 Merge branch 'composelint' into 'master'
Optimize Dockerfile

See merge request allianceauth/allianceauth!1630
2024-06-17 08:42:39 +00:00
Ariel Rin
04f3473ef3 Optimize Dockerfile 2024-06-17 08:42:39 +00:00
Ariel Rin
255cb0da8d Merge branch 'bootstrap-class-fixes' into 'master'
[CHANGE] Remove unnecessary bootstrap classes from the dashboard

See merge request allianceauth/allianceauth!1631
2024-06-17 08:42:34 +00:00
Peter Pfeufer
069352fb0f
[FIX] Switch to bottom margin instead of top/bottom padding
This is the much more commonly used approach.
2024-06-01 12:01:44 +02:00
Peter Pfeufer
66e8ddb684
[CHANGE] Remove unnecessary bootstrap classes from the dashboard 2024-05-31 21:59:24 +02:00
Ariel Rin
179c26975c
Version Bump 4.1.0 2024-05-27 14:31:41 +10:00
Ariel Rin
e17f6e799b
correct capitalization 2024-05-27 14:31:21 +10:00
Ariel Rin
7cd8294104 Merge branch 'widget-title-to-framework' into 'master'
[ADD] Widget title partial to AA framework

See merge request allianceauth/allianceauth!1629
2024-05-27 04:25:56 +00:00
Peter Pfeufer
ede5540335
[ADD] Widget title partial to AA framework 2024-05-26 16:52:15 +02:00
Ariel Rin
747279b773 Merge branch 'yet-another-padding-fix' into 'master'
Some more widget fixes

See merge request allianceauth/allianceauth!1628
2024-05-26 09:08:00 +00:00
Peter Pfeufer
44f8b1c477
[FIX] Don't try to underline these links
Fixing this while I'm at it …
2024-05-26 10:06:07 +02:00
Peter Pfeufer
7c6ebd9bf6
[FIX] Yet another padding in the dashboard widgets 2024-05-26 09:52:03 +02:00
Ariel Rin
430469b708 Merge branch 'widget-padding-fix' into 'master'
[FIX] Dashboard widget padding

See merge request allianceauth/allianceauth!1627
2024-05-26 06:51:20 +00:00
Peter Pfeufer
efbb3cee31
[FIX] Dashboard widget padding 2024-05-26 08:23:57 +02:00
Ariel Rin
21094ed4dd
lint 2024-05-26 13:30:21 +10:00
Ariel Rin
5f326efc7e Merge branch 'v4docs' into 'master'
Expand Tuning Doccs

See merge request allianceauth/allianceauth!1624
2024-05-26 03:22:08 +00:00
Ariel Rin
b6e34ace35 Expand Tuning Doccs 2024-05-26 03:22:08 +00:00
Ariel Rin
fe4a8965e3 Merge branch 'master' into 'master'
Improve Error Troubleshooting with Log Documentation

See merge request allianceauth/allianceauth!1619
2024-05-26 02:58:23 +00:00
Ariel Rin
23371c233d Merge branch 'bmtuk-master-patch-12042' into 'master'
Timerboard - Fix Timer deletion

See merge request allianceauth/allianceauth!1625
2024-05-26 02:56:35 +00:00
Ariel Rin
7a3bbf0d7f Merge branch 'staff-menu-item' into 'master'
Allow staff access to admin button in user menu

See merge request allianceauth/allianceauth!1626
2024-05-26 02:56:05 +00:00
Aaron Kable
89a1bec9c1 Allow staff access to admin button in user menu 2024-05-26 02:56:04 +00:00
Ben Thomas
1c1e70619a Fix ability to delete timers 2024-05-21 23:42:19 +00:00
Ariel Rin
0ff4374efa
Version Bump 4.0.2 2024-05-12 19:51:02 +10:00
Ariel Rin
18d0e58a48
Merge branch 'master' of gitlab.com:allianceauth/allianceauth 2024-05-12 19:50:10 +10:00
Ariel Rin
84f44338dc Merge branch 'mute-subtitle' into 'master'
[CHANGE] Mute subtitle text

See merge request allianceauth/allianceauth!1623
2024-05-12 09:48:47 +00:00
Ariel Rin
2ba0412890
Add Polish to language selections 2024-05-12 19:44:30 +10:00
Peter Pfeufer
2326522b29
[CHANGE] Docs updated with the missing subtitle attribute 2024-05-12 11:40:47 +02:00
Peter Pfeufer
a7cb6ee434
[CHANGE] Mute subtitle text 2024-05-12 11:35:42 +02:00
Ariel Rin
2aeef63565 Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1609
2024-05-12 09:23:26 +00:00
Ariel Rin
3c9e7335ef Updates for project Alliance Auth 2024-05-12 09:23:26 +00:00
Ariel Rin
49067de325
apply precommit here 2024-05-12 19:21:58 +10:00
Ariel Rin
471e7e29ae
Update Source EN 2024-05-12 19:15:56 +10:00
Ariel Rin
3ec5775406
remove unneeded legacy transifex config 2024-05-12 19:15:29 +10:00
Ariel Rin
e804d2b60d
update (and sort) precommit 2024-05-12 19:15:14 +10:00
Ariel Rin
742438a95d Merge branch 'timerboard-bg-fix' into 'master'
Make table bg a little less oppressive in timerboard

See merge request allianceauth/allianceauth!1617
2024-04-27 05:09:37 +00:00
Ariel Rin
5c60086baa Merge branch 'add-missing-padding' into 'master'
[ADD] missing padding

See merge request allianceauth/allianceauth!1620
2024-04-27 05:05:22 +00:00
Ariel Rin
e49041bb14 Merge branch 'Haffi-master-patch-21354' into 'master'
updates package registry reference

Closes #1403

See merge request allianceauth/allianceauth!1621
2024-04-25 09:48:10 +00:00
Haffi
f3cbe91883 updates package registry reference 2024-04-25 08:42:18 +00:00
Peter Pfeufer
ea439a2176
[FIX] Indentation 2024-04-15 17:16:01 +02:00
Peter Pfeufer
56e1e76f11
[ADD] Missing theme padding on public pages 2024-04-15 17:15:35 +02:00
entropylaw
634e7357be Update troubleshooting.md. Fixed Typo. 2024-04-13 21:21:13 -05:00
entropylaw
08dc88da1a Update troubleshooting.md 2024-04-13 20:59:19 -05:00
entropylaw
3d206e445c Update troubleshooting.md 2024-04-13 20:55:10 -05:00
entropylaw
64686cdad1 Update troubleshooting.md 2024-04-13 20:49:46 -05:00
AnomicDev
d7fe09bdf1 Update troubleshooting.md 2024-04-14 01:19:51 +00:00
AnomicDev
6da50da92f Update troubleshooting.md 2024-04-14 01:10:38 +00:00
AnomicDev
51e4dd986f Update troubleshooting.md 2024-04-14 01:09:56 +00:00
AnomicDev
bee6522182 Update troubleshooting.md 2024-04-14 01:07:08 +00:00
AnomicDev
1711a9dd33 Update troubleshooting.md 2024-04-14 01:05:25 +00:00
AnomicDev
3914626379 Update troubleshooting.md 2024-04-14 01:04:59 +00:00
AnomicDev
df276cb32d Update troubleshooting.md 2024-04-14 00:59:19 +00:00
AnomicDev
daad7d8b10 Update troubleshooting.md 2024-04-14 00:58:44 +00:00
AnomicDev
3bf5bc0fe3 Update troubleshooting.md 2024-04-14 00:58:09 +00:00
AnomicDev
96abae553a Update troubleshooting.md 2024-04-14 00:57:12 +00:00
AnomicDev
f9cbfb1562 Update troubleshooting.md 2024-04-14 00:55:59 +00:00
AnomicDev
8eaa94e179 Update troubleshooting.md 2024-04-14 00:55:24 +00:00
AnomicDev
4f876b648b Update troubleshooting.md 2024-04-14 00:53:16 +00:00
AnomicDev
cd738137c0 Update troubleshooting.md 2024-04-14 00:52:07 +00:00
Aaron Kable
5605eb129d Make table bg a little less opressive in timerboard 2024-04-10 16:27:09 +08:00
Ariel Rin
87ef0f21a3 Merge branch 'add-missing-colon' into 'master'
[ADD] Missing colon

See merge request allianceauth/allianceauth!1616
2024-04-10 01:12:30 +00:00
Ariel Rin
a1c7ce827e Merge branch 'topnav-revival' into 'master'
V4 Theme - Re-add the Top Nav for unauthenticated users

See merge request allianceauth/allianceauth!1615
2024-04-10 01:09:03 +00:00
Aaron Kable
97466bcdfb V4 Theme - Re-add the Top Nav for unauthenticated users 2024-04-10 01:09:02 +00:00
Peter Pfeufer
ff3096b106
[ADD] Missing colon 2024-04-07 15:58:35 +02:00
Ariel Rin
98f0d77f3f Merge branch 'pre-commit-updates' into 'master'
[MISC] pre-commit config updates

See merge request allianceauth/allianceauth!1614
2024-03-29 02:24:49 +00:00
Peter Pfeufer
92548ba402
[ADD] pyproject.toml validation 2024-03-28 20:40:45 +01:00
Peter Pfeufer
c46741d311
[MISC] Update repo versions 2024-03-28 20:34:31 +01:00
Peter Pfeufer
7c7c1abf7c
[CHANGE] Project file linter
We don't use setup.cfg anymore, so we should lint the pyproject.toml file now.
2024-03-28 20:33:16 +01:00
Ariel Rin
fc303b1b0a Merge branch 'fix-phpbb3-mysql-user' into 'master'
[FIX] DB user for phpBB

See merge request allianceauth/allianceauth!1613
2024-03-26 10:19:07 +00:00
Peter Pfeufer
4e220a9679
[FIX] DB user for phpBB 2024-03-24 17:08:34 +01:00
Ariel Rin
b17b1f7504
Version Bump 4.0.1 2024-03-21 19:34:02 +10:00
Ariel Rin
7081fc0e76 Merge branch 'docs' into 'master'
Documentation

See merge request allianceauth/allianceauth!1610
2024-03-21 09:17:43 +00:00
Ariel Rin
68e4574f19 Merge branch 'analytics' into 'master'
Pass Version to analytics user properties

See merge request allianceauth/allianceauth!1612
2024-03-21 09:12:53 +00:00
Ariel Rin
e6e0a70012 Pass Version to analytics user properties 2024-03-21 09:12:53 +00:00
Ariel Rin
13e38da942 Merge branch 'timers-row-background' into 'master'
[CHANGE] Restore the "old" color coding in table rows

See merge request allianceauth/allianceauth!1611
2024-03-21 09:12:25 +00:00
Peter Pfeufer
468c1de26b
[CHANGE] restore the "old" color coding in table rows 2024-03-20 19:22:03 +01:00
Ariel Rin
22ef5ac0e5
raw file contents incase someone wgets 2024-03-16 15:44:39 +10:00
Ariel Rin
ef2dc08958
Version Bump 4.0.0 2024-03-16 14:17:29 +10:00
Ariel Rin
6b84ffa16c
update pre-commit 2024-03-16 14:16:50 +10:00
Ariel Rin
d7a1096413 Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1595
2024-03-16 04:08:52 +00:00
Ariel Rin
93b94a8bc2 Updates for project Alliance Auth 2024-03-16 04:08:52 +00:00
Ariel Rin
9a95716105 Merge branch 'nav-active-passthrough' into 'master'
[Enhancement] Keep menu folders open when a sub item is active

See merge request allianceauth/allianceauth!1608
2024-03-16 04:00:06 +00:00
Aaron Kable
dbfcf5d87a [Enhancement] Keep menu folders open when a sub item is active 2024-03-16 04:00:06 +00:00
Ariel Rin
105d7d53b3 Merge branch 'no-perm-folders' into 'master'
[FIX] Hide empty folders in menu

See merge request allianceauth/allianceauth!1607
2024-03-16 03:59:54 +00:00
Aaron Kable
01cefe1457 [FIX] Hide empty folders in menu 2024-03-16 03:59:54 +00:00
Ariel Rin
7fe3db8017
drop pypy testing 2024-03-13 21:44:48 +10:00
Ariel Rin
e0945fac80
Version Bump 4.0.0rc1 2024-03-13 19:12:11 +10:00
Ariel Rin
40fa190820 Merge branch 'v4docs' into 'v4.x'
Expand Docker Docs for v4

See merge request allianceauth/allianceauth!1577
2024-03-13 09:06:10 +00:00
Ariel Rin
670580f8f3 Merge branch 'sw-version-button-same-height' into 'v4.x'
[FIX] Make SW Version buttons the same height

See merge request allianceauth/allianceauth!1606
2024-03-12 13:23:32 +00:00
Peter Pfeufer
323a0bcf16
[FIX] Make SW Version buttons the same height 2024-03-12 12:42:34 +01:00
Ariel Rin
6e995edd80 Merge branch 'sha-fixes' into 'v4.x'
[FIX] Bootstrap SHA hashes

See merge request allianceauth/allianceauth!1605
2024-03-12 11:30:16 +00:00
Peter Pfeufer
8d86e45b7a
[FIX] Bootstrap SHA hashes 2024-03-12 12:29:21 +01:00
Ariel Rin
2aa6df4461 Merge branch 'bootswatch' into 'v4.x'
Bump and unify on bootstrap 5.3.3

See merge request allianceauth/allianceauth!1603
2024-03-12 03:36:19 +00:00
Ariel Rin
cf6f989502 Bump and unify on bootstrap 5.3.3 2024-03-12 03:36:19 +00:00
Ariel Rin
3e1d8ae334 Merge branch 'widgets' into 'v4.x'
When providing a single time, clarify EVE Time

See merge request allianceauth/allianceauth!1604
2024-03-12 03:34:33 +00:00
Ariel Rin
bcfe9484b5 When providing a single time, clarify EVE Time 2024-03-12 03:34:33 +00:00
Ariel Rin
5e4d1b9cfd Merge branch 'timerboard-perms' into 'v4.x'
Add Dashboard Widget View Perms

See merge request allianceauth/allianceauth!1602
2024-03-10 09:36:51 +00:00
Aaron Kable
3b463e7305 Fix timer query, Corp is not nullable 2024-03-08 17:37:11 +08:00
Aaron Kable
eedf5082fa remove the locked hieght from the ops/tiemrs dashboard widgets 2024-03-08 17:15:03 +08:00
Aaron Kable
2ea5b15175 Add perms to ops dashboard view 2024-03-08 17:14:36 +08:00
Aaron Kable
7a9808aad3 Add permision check to timerboard dashboard view 2024-03-08 15:54:39 +08:00
Ariel Rin
a1d712694c Merge branch 'theme-fixes' into 'v4.x'
[V4] Add reject button back to group management.

See merge request allianceauth/allianceauth!1601
2024-03-07 10:35:39 +00:00
Ariel Rin
ca11c726a3 Merge branch 'login-screen-theming' into 'v4.x'
[ADD] Theme CSS to login base template

See merge request allianceauth/allianceauth!1600
2024-03-07 10:34:53 +00:00
Ariel Rin
6e0f7a35bd Merge branch 'login-box-fix' into 'v4.x'
[CHANGE] Give the login box a bit more space

See merge request allianceauth/allianceauth!1599
2024-03-07 10:34:16 +00:00
Aaron Kable
7375b001ca Add reject button back in 2024-03-04 17:18:19 +08:00
Peter Pfeufer
0287086633
[ADD] Theme CSS to login base template 2024-02-25 20:53:52 +01:00
Peter Pfeufer
9eb2becbb5
[CHANGE] Give the login box a bit more space 2024-02-25 20:37:32 +01:00
Ariel Rin
12f1444fe7
add update command 2024-02-24 20:41:10 +10:00
Ariel Rin
d6372bd093
add notes to building custom image 2024-02-24 16:05:50 +10:00
Ariel Rin
3935a9cdd2
Version Bump 4.0.0b2 2024-02-24 14:44:49 +10:00
Ariel Rin
49fb6c39d5 Merge branch 'navactive' into 'v4.x'
Errors thrown here cause 404 error handling to fail. 404s cant url resolve.

See merge request allianceauth/allianceauth!1598
2024-02-24 04:42:48 +00:00
Ariel Rin
8821f18b21 Errors thrown here cause 404 error handling to fail. 404s cant url resolve. 2024-02-24 04:42:48 +00:00
Ariel Rin
f4d8ead54e
remove justified text 2024-02-23 23:25:20 +10:00
Ariel Rin
7427e13505
move into docker and add to toctree 2024-02-23 23:21:09 +10:00
Ariel Rin
445683c3d5
Merge branch 'v4.x' of gitlab.com:allianceauth/allianceauth into v4docs 2024-02-23 22:26:38 +10:00
Ariel Rin
677c46c48a
very basic migration guide to our v4 docker stack 2024-02-23 22:23:12 +10:00
Ariel Rin
87b9e3f87a
use latest python minor 2024-02-23 20:37:41 +10:00
Ariel Rin
da2a5aff2f
missing ` 2024-02-23 20:37:18 +10:00
Ariel Rin
65d77743dc
fix toctree after myst conversion 2024-02-23 20:37:06 +10:00
Ariel Rin
1c7f8256d0 Merge branch 'fix-filenames' into 'v4.x'
[AA4] Fix dots in file names

See merge request allianceauth/allianceauth!1597
2024-02-22 03:00:47 +00:00
Erik Kalkoken
61f0aae5d9 [AA4] Fix dots in file names 2024-02-22 03:00:47 +00:00
Ariel Rin
4d56030edf
Version Bump 4.0.0b1 2024-02-17 20:56:51 +10:00
Ariel Rin
2dfe194a3b Merge branch 'v4.x-banned' into 'v4.x'
[v4.x] ESI Alerts Dashboard Widget

See merge request allianceauth/allianceauth!1591
2024-02-17 10:48:37 +00:00
Aaron Kable
ebb40deb7f [v4.x] ESI Alerts Dashboard Widget 2024-02-17 10:48:37 +00:00
Ariel Rin
a3a2d3d35b Merge branch 'darklyrollback' into 'v4.x'
rollback darkly, fixes #1396

See merge request allianceauth/allianceauth!1594
2024-02-17 10:37:18 +00:00
Ariel Rin
d4dee519b8
rollback darkly, fixes #1396 2024-02-17 20:29:20 +10:00
Ariel Rin
dfcbad3476
Update source EN 2024-02-17 20:09:53 +10:00
Ariel Rin
e03c1307a3
Merge branch 'master' of gitlab.com:allianceauth/allianceauth into v4.x 2024-02-17 20:01:50 +10:00
Ariel Rin
054ef27fa4
Version Bump 3.8.1 2024-02-17 19:39:14 +10:00
Ariel Rin
97e224b8e6 Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1593
2024-02-17 09:30:43 +00:00
Ariel Rin
3b8fa415bc Updates for project Alliance Auth 2024-02-17 09:30:43 +00:00
Ariel Rin
b94fd7ed19
I18N Maintenance 2024-02-17 18:52:42 +10:00
Ariel Rin
d1dac61135 Merge branch 'translations_7f31a07ccd4e4a66b1dd7b6bc2dbddb5' into 'master'
Updates for project Alliance Auth

See merge request allianceauth/allianceauth!1563
2024-02-17 08:48:55 +00:00
Ariel Rin
d2a095217f Updates for project Alliance Auth 2024-02-17 08:48:55 +00:00
Ariel Rin
3a95b89779 Merge branch 'cherry-pick-2418023d' into 'v4.x'
remove venv from python command

See merge request allianceauth/allianceauth!1592
2024-02-17 08:39:47 +00:00
Ariel Rin
4f5b231bdf remove venv
(cherry picked from commit 2418023dddc0c9f23f5c03b962664b31a6011f6a)
2024-02-17 08:02:32 +00:00
Ariel Rin
40c0b8d862 Merge branch 'improve-menu-app' into 'v4.x'
Improve menu app

See merge request allianceauth/allianceauth!1589
2024-02-17 07:56:38 +00:00
Erik Kalkoken
62c936f1c0 Improve menu app 2024-02-17 07:56:38 +00:00
Ariel Rin
2a762df9b3 Merge branch 'bs5-datatable-filterdropdown' into 'v4.x'
Re-enable filter dropdown for Datatables

See merge request allianceauth/allianceauth!1590
2024-02-15 01:56:21 +00:00
Ariel Rin
8fb5a488f7 Merge branch 'bs-merge' into 'v4.x'
[v4.x] Merge Bootstrap/Bootstrap Dark Themes

See merge request allianceauth/allianceauth!1587
2024-02-15 01:55:15 +00:00
Ariel Rin
dc239d5396 Merge branch 'more-framework-functions' into 'v4.x'
[ADD] Evecharacter API functions

See merge request allianceauth/allianceauth!1588
2024-02-15 01:54:12 +00:00
Ariel Rin
2815ebaa07 Merge branch 'template-stuff' into 'v4.x'
Auth Menu Changes and Other Template Fixes

See merge request allianceauth/allianceauth!1586
2024-02-15 01:53:38 +00:00
Peter Pfeufer
34dd4802a8
[CHANGE] Some margins modified to be in line with the dashboard 2024-02-11 15:03:49 +01:00
Peter Pfeufer
3ebf11308c
[CHANGE] Re-enable filterDropdown JS 2024-02-04 13:40:48 +01:00
Peter Pfeufer
5cd5180a91
[CHANGE] Use the now official release version 2024-02-04 13:35:05 +01:00
Peter Pfeufer
ba213db493
[CHANGE] Update filterDropDown JS for Bootstrap 5 2024-02-04 13:35:05 +01:00
Peter Pfeufer
8362d11714
[FIX] Mark selected theme in the theme dropdown in the user menu 2024-01-28 10:27:47 +01:00
Peter Pfeufer
7ba65968ed
[CHANGE] Make it IDs instead of classes 2024-01-27 09:22:03 +01:00
Peter Pfeufer
7fdbac20cb
[REMOVE] Unnecessary bg and text classes from user menu element 2024-01-26 15:01:44 +01:00
Peter Pfeufer
76efcb5266
[ADD] Classes to the dashboard panels to identify them
This is nice to have for theming :-)
2024-01-26 14:58:35 +01:00
Peter Pfeufer
6aacb8c2e3
[REMOVE] Unnecessary active class 2024-01-25 16:29:00 +01:00
Peter Pfeufer
465fba3a18
[FIX] It's user not User 2024-01-24 21:57:03 +01:00
Peter Pfeufer
9ae6addc71
[FIX] Stop Python 3.8 tests from failing (hopefully) 2024-01-24 21:11:04 +01:00
Peter Pfeufer
76ae9b8849
[ADD] get_all_characters_from_user function to User API 2024-01-24 21:03:09 +01:00
Peter Pfeufer
13a8b7678f
[ADD] Evecharacter API functions 2024-01-24 20:41:36 +01:00
Aaron Kable
c166fc0ef9 Merge the 2 Bootstrap Themes they dont need to be separate 2024-01-24 17:41:19 +08:00
Peter Pfeufer
9da61588eb
[CHANGE] Change the chevron for submenu depending on its toggle status 2024-01-24 10:24:14 +01:00
Peter Pfeufer
7425176b3f
[CHANGE] Side menu improvements
- Increase width to 325px
- Add some CSS tricks to ensure long link names don't break the menu
2024-01-23 15:23:48 +01:00
Peter Pfeufer
2bb518f7e0
[CHANGE] Don't generate an empty <p> when no group description
Also, better margins on the badges.
2024-01-23 08:17:18 +01:00
Peter Pfeufer
84d5693583
[CHANGE] Use the right BS class 2024-01-17 22:29:22 +01:00
Peter Pfeufer
867e2a1ded
[CHANGE] Replaced style="width: 100%;" with the appropriate BS class 2024-01-17 22:28:17 +01:00
Peter Pfeufer
fd6c6991f5
[REMOVED] Unnecessary text class 2024-01-17 22:25:54 +01:00
Peter Pfeufer
333fe8497d
[FIX] Register template to Bootstrap 5 2024-01-17 22:21:16 +01:00
Peter Pfeufer
8a3fb17147
[CHANGE] Reduce the width of the side menu to 300px
This gives the apps a bit of wiggle room for all the tables we have there.
2024-01-17 20:04:07 +01:00
Peter Pfeufer
f8baeb19a7
[FIX] Static no longer needed here 2024-01-16 21:29:46 +01:00
Peter Pfeufer
10096862e5
[CHANGE] Better padding 2024-01-16 20:58:24 +01:00
Peter Pfeufer
89193d2fcf
[CHANGE] Set min width, so it's properly readable 2024-01-16 20:32:45 +01:00
Peter Pfeufer
fd08b987bd
[CHANGE] Ok, the overflow is needed for keep the menu in its bounds … 2024-01-16 20:24:32 +01:00
Peter Pfeufer
4e9e22cb4b
[REMOVED] Unnecessary z-index and overflow
Width fixed as well
2024-01-16 20:18:46 +01:00
Peter Pfeufer
f8fbbb5ba7
[CHANGE] Auth menu for public pages 2024-01-16 20:00:53 +01:00
Peter Pfeufer
dd3ef41396
[FIX] Attribuite name and mandatory alt attribute added 2024-01-16 19:36:54 +01:00
Ariel Rin
d5fda05dc9 Merge branch 'add-missing-bootstrap-class' into 'v4.x'
[ADD] Missing Bootstrap class to select field

See merge request allianceauth/allianceauth!1585
2024-01-16 01:56:13 +00:00
Peter Pfeufer
d815fad0e6
[ADD] Missing Bootstrap class to select field 2024-01-15 07:31:52 +01:00
Ariel Rin
e109198782 Merge branch 'v4.x-theme-work' into 'v4.x'
Move User Actions into new Top Nav areas across the internal apps

See merge request allianceauth/allianceauth!1581
2024-01-15 05:35:19 +00:00
Aaron Kable
fbd4672454 Move User Actions into new Top Nav areas across the internal apps 2024-01-15 05:35:19 +00:00
Ariel Rin
a29bd567c2 Merge branch 'v4.x-user-menu' into 'v4.x'
New User Menu

See merge request allianceauth/allianceauth!1582
2024-01-15 05:34:19 +00:00
Aaron Kable
960aef95ad New User Menu 2024-01-15 05:34:19 +00:00
Ariel Rin
4aae5497bb Merge branch 'callout-box-fixes' into 'v4.x'
Different callout box sizes (through padding)

See merge request allianceauth/allianceauth!1584
2024-01-15 05:29:33 +00:00
Ariel Rin
6081cbe900 Merge branch 'grafanabootstrap' into 'v4.x'
Generate a grafana password during bootstrap script

See merge request allianceauth/allianceauth!1583
2024-01-15 05:27:13 +00:00
Ariel Rin
5e9b47cf79 Merge branch 'docs-improvements' into 'master'
Docs improvements

See merge request allianceauth/allianceauth!1574
2024-01-15 05:25:51 +00:00
Ariel Rin
853826c140 Merge branch 'add-email-timeout' into 'master'
Add email timeout

See merge request allianceauth/allianceauth!1580
2024-01-15 05:19:57 +00:00
Peter Pfeufer
ce0d8342e3
[REMOVE] Wrong modifier class in docs 2024-01-07 15:27:42 +01:00
Peter Pfeufer
006785e592
[CHANGE] Use default alert box margin as base 2024-01-07 15:05:41 +01:00
Peter Pfeufer
df05070a55
[ADD] Bootstrap class for bottom margin to callout box in SRP module 2024-01-07 14:02:59 +01:00
Peter Pfeufer
e81450baf3
[CHANGE] Docs updated 2024-01-07 13:57:47 +01:00
Peter Pfeufer
24b6c19aca
[ADD] Different sizes (through padding) 2024-01-07 13:34:50 +01:00
Ariel Rin
9f4bf13cc9
Expand documentation on building a custom image, provide example 2024-01-05 15:42:39 +10:00
Ariel Rin
2a156302f0 Merge branch 'top-nav-fix' into 'v4.x'
[ADD] Missing Bootstrap class to top navigation blocks

See merge request allianceauth/allianceauth!1578
2024-01-05 05:42:09 +00:00
Ariel Rin
c4d3bde106
Generate a grafana password during bootstrap script 2024-01-05 15:34:27 +10:00
ErikKalkoken
9c7de58989 Add email timeout 2024-01-02 11:48:27 +01:00
Peter Pfeufer
0900806f68
[REMOVE] Unnecessary empty div 2023-12-29 20:10:06 +01:00
Peter Pfeufer
a0d32d8c2d
[ADD] Missing Bootstrap class 2023-12-29 19:57:15 +01:00
Ariel Rin
e16252842a
Version Bump 4.0.0a5 2023-12-26 21:40:20 +10:00
Ariel Rin
950ae34093
loosen sphinx_rtd_theme for sphinx 7 2023-12-26 21:38:04 +10:00
Ariel Rin
0ba94c53aa
Two more MyST converted toctrees 2023-12-26 21:37:49 +10:00
Ariel Rin
96cc55d174
missed myst conversions 2023-12-26 21:23:37 +10:00
Ariel Rin
a9062c4389
test starting redis on RTD 2023-12-26 21:15:07 +10:00
Ariel Rin
51b86f88b9
generate some basic docker install guides for services 2023-12-26 20:41:53 +10:00
Ariel Rin
8184461b48
use docker compose instead of dash, add hint 2023-12-26 19:35:11 +10:00
Ariel Rin
ca0cdd6e15
remove old docs images 2023-12-26 19:34:25 +10:00
Ariel Rin
036a17ad3b
note the container name changes after refactor 2023-12-26 19:34:09 +10:00
Ariel Rin
2418023ddd
remove venv 2023-12-26 19:28:34 +10:00
Ariel Rin
c602cf0b00
Merge branch 'master' of gitlab.com:allianceauth/allianceauth into v4.x 2023-12-25 19:49:54 +10:00
Ariel Rin
3de988369f Merge branch 'fix-orphan-tokens' into 'master'
Fix orphan tokens and remove unused messages from backends

Closes #1391

See merge request allianceauth/allianceauth!1570
2023-12-25 09:48:15 +00:00
Ariel Rin
b28b51916c Merge branch 'aa-framework-part-2' into 'v4.x'
Alliance Auth Framework (Part 2)

See merge request allianceauth/allianceauth!1558
2023-12-25 09:46:57 +00:00
Ariel Rin
0db0978d5f Merge branch 'header-nav-improvement' into 'v4.x'
Header navigation improvements

See merge request allianceauth/allianceauth!1575
2023-12-25 09:45:19 +00:00
Ariel Rin
0531da1128 Merge branch 'middleware-menu' into 'v4.x'
swap to middleware to sync menu hooks

See merge request allianceauth/allianceauth!1573
2023-12-25 09:43:52 +00:00
Peter Pfeufer
547d047f59 Merge branch 'v4.x' into 'aa-framework-part-2'
# Conflicts:
#   allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkpersonalmonthlystatisticsview.html
#   allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html
2023-12-24 17:34:07 +00:00
Ariel Rin
5cd92e29d3 Merge branch 'whitespaces' into 'v4.x'
[REMOVE] Unnecessary whitespaces

See merge request allianceauth/allianceauth!1576
2023-12-24 11:10:06 +00:00
Peter Pfeufer
bb44194cfc
[CHANGE] Moved corpstats menu and search to heaver navbar
Also:
- Sorted alts alphabetically
- Added table headers
2023-12-22 12:23:23 +01:00
Peter Pfeufer
aa1cb96c8a
[REMOVE] Unnecessary whitespaces
I know, purely aesthetic, but it bugs the hell out of me …
2023-12-21 12:20:51 +01:00
Peter Pfeufer
40ae092306
[ADD] New template block for the character control icons top right
This commit adds a new template block called "header_nav_user_character_control" to the base-bs5.html file. This block is intended to display character control icons in the top right section of the navigation bar. The existing code has been modified to include this new block and rearrange the layout accordingly.

With this change it is possible to add app specific menu items to the right side as well without having to re-add the character control menu items all the time.

Thanks to @aaronkable for discussing this idea with me.
2023-12-21 02:33:11 +01:00
Peter Pfeufer
b2e70c3702
[ADD] Docu for templates and bundles 2023-12-18 01:22:55 +01:00
Peter Pfeufer
eebeb26001
[ADD] API docu 2023-12-18 00:59:36 +01:00
Peter Pfeufer
003a67224e
[CHANGE] Trying a different syntax … 2023-12-18 00:30:37 +01:00
Peter Pfeufer
d3a3810456
[CHANGE] Try to use img tags instead of MD 2023-12-18 00:22:44 +01:00
Peter Pfeufer
f55008559e
[CHANGE] Wrapping table in {eval-rst} 2023-12-17 23:59:15 +01:00
Peter Pfeufer
dbc19c76c5
[CHANGE] Switch to header template where ever possible
Also fixed some Bootstrap misuse
2023-12-17 23:50:13 +01:00
Peter Pfeufer
ced7972962
[FIX] Docs syntax 2023-12-17 23:50:13 +01:00
Peter Pfeufer
ad508bd880
[ADD] Page header template 2023-12-17 23:50:13 +01:00
Peter Pfeufer
5c128f2c78
[FIX] Spelling 2023-12-17 23:50:13 +01:00
Peter Pfeufer
1caaca86cf
[CHANGE] Table formatted, hope that helps … 2023-12-17 23:50:13 +01:00
Peter Pfeufer
33ad1413d5
[ADD] Attempt to explain the sentinel user function
Let's see if the docs are generated properly with this
2023-12-17 23:50:13 +01:00
Peter Pfeufer
cbb5c80b94
[ADD] CSS cursor classes docs 2023-12-17 23:50:13 +01:00
Peter Pfeufer
d2edd288f9
[ADD] Some more tests 2023-12-17 23:50:13 +01:00
Peter Pfeufer
21e80f6961
[ADD] __init__.py to test … 2023-12-17 23:50:13 +01:00
Peter Pfeufer
a747951d19
[CHANGE] split it into 2 functions
- `get_main_character_from_user` to return the `EveCharacter` object
- `get_main_character_name_from_user` to return the main character name as string
2023-12-17 23:50:13 +01:00
Peter Pfeufer
4cc7135ace
[ADD] get_main_character_from_user 2023-12-17 23:50:13 +01:00
Peter Pfeufer
6a990c11e6
[ADD] sentinel user and migrate fleetactivitytracking 2023-12-17 23:50:13 +01:00
Peter Pfeufer
3f47cecbfc
[CHANGE] Move auth-framework.css to allianceauth.framework app 2023-12-17 23:50:13 +01:00
Peter Pfeufer
77c126ea2f
[ADD] Alliance Auth Framework base app 2023-12-17 23:50:13 +01:00
Peter Pfeufer
6e3219fd1b
[FIX] Grammar and spelling 2023-12-17 20:21:11 +01:00
Peter Pfeufer
8aeb061635
[ADD] Remark on how to stop Gunicorn when testing 2023-12-17 17:45:29 +01:00
Peter Pfeufer
84e2107b62
[CHANGE] Switch to adduser for Ubuntu
This will create the users' hoe directory and make it a no-login user in one command.
2023-12-17 17:42:08 +01:00
Ariel Rin
8b7e57494c Merge branch 'menu-fa-cleanup' into 'v4.x'
[CHANGE] FA classes in menu hooks updated

See merge request allianceauth/allianceauth!1571
2023-12-17 03:52:09 +00:00
Ariel Rin
20fcf5efa4 Merge branch 'docs' into 'master'
Docs fixes

See merge request allianceauth/allianceauth!1569
2023-12-17 03:49:41 +00:00
Peter Pfeufer
8ff3d854ba
[CHANGE] Reduce overall margin a bit while I'm at it 2023-12-12 10:27:53 +01:00
Peter Pfeufer
49ff355d50
[CHANGE] FA classes in menu hooks updated 2023-12-12 02:40:18 +01:00
colcrunch
c15b955d5e Make pre-commit happy 2023-12-11 18:37:35 -05:00
colcrunch
65e1545a66 Remove all references to messages as they are never relayed to the user. 2023-12-11 18:16:34 -05:00
colcrunch
c558a980e1 Add more detail to error message displayed on failed alt login. 2023-12-11 18:14:09 -05:00
colcrunch
bd8ef84862 Delete tokens that can not be used for logins. 2023-12-11 18:13:24 -05:00
Ariel Rin
6f670da1db
Merge branches 'v4.x' and 'v4.x' of gitlab.com:allianceauth/allianceauth into v4.x 2023-12-11 22:20:38 +10:00
Ariel Rin
c558f3785f
typo 2023-12-11 22:20:31 +10:00
Ariel Rin
04cdd4c64f Merge branch 'aa-services-fixes' into 'v4.x'
Template Updates (I hope I got them all)

See merge request allianceauth/allianceauth!1566
2023-12-11 12:18:51 +00:00
Ariel Rin
42e96d2f14
close the python section to fix mysql tabs/steps 2023-12-11 22:15:12 +10:00
Peter Pfeufer
00fcebd8e3
[CHANGE] Some minor cleanups 2023-12-11 11:59:32 +01:00
Peter Pfeufer
995c84481c
[CHANGE] Public templates updated to BS 5 2023-12-11 11:14:17 +01:00
Peter Pfeufer
4dd42da993
[REMOVE] Unused/Undefined class 2023-12-11 10:45:27 +01:00
Peter Pfeufer
8295cc51a3
[FIX] FA icon 2023-12-11 10:40:02 +01:00
Peter Pfeufer
f7e1d7c47e
[CHANGE] Token Management template updated 2023-12-11 10:35:23 +01:00
Peter Pfeufer
234191218a
[FIX] Semantic headline order 2023-12-11 10:26:08 +01:00
Peter Pfeufer
de12b49527
[CHANGE] JS updated to ES8+ standard where ever possible 2023-12-10 14:56:15 +01:00
Peter Pfeufer
021b7b2edb
[REMOVE] Unnecessary span 2023-12-10 14:15:51 +01:00
Peter Pfeufer
20b959f273
[FIX] Make notification bell solid again
Looks much better
2023-12-10 14:12:12 +01:00
Peter Pfeufer
e16bb2a737
[FIX] Table width and header 2023-12-10 14:06:04 +01:00
Peter Pfeufer
0a6a6e0bf9
[FIX] Datatable in permission audit templates 2023-12-10 13:56:14 +01:00
Peter Pfeufer
f0fe3929d4
[CHANGE] Update FA icons to new classes
Base Classes:
- `far` => `fa-regular`
- `fas` => `fa-solid`
- `fab` => `fa-brand`
2023-12-10 13:44:32 +01:00
Peter Pfeufer
9a62c729eb
[CHANGE] Using the same FA icon for delete/remove
`<i class="fa-solid fa-trash-can"></i>`
2023-12-10 13:15:26 +01:00
Peter Pfeufer
eaedcd5bb7
[FIX] More Bootstrap class usage fixes 2023-12-09 20:14:42 +01:00
Peter Pfeufer
d742257c74
[FIX] Button class 2023-12-09 19:53:26 +01:00
Peter Pfeufer
ad92d7a916
[FIX] Groupmanagement / Index template
Somehow this got royally messed up in one of the backport merges
2023-12-09 19:35:51 +01:00
Peter Pfeufer
af22222bdf
[FIX] Test test_should_not_hide_leave_requests_tab_when_there_are_open_requests 2023-12-09 19:12:21 +01:00
Peter Pfeufer
01f49bd125
[FIX] Show Leave tab when GROUPMANAGEMENT_AUTO_LEAVE = True 2023-12-09 19:07:35 +01:00
Peter Pfeufer
af5930b9d0
[FIX] Form classes 2023-12-09 18:51:56 +01:00
Peter Pfeufer
95ba19a827
[FIX] groupmanagement/index.html template 2023-12-09 18:44:35 +01:00
Peter Pfeufer
c75647301f
[CHANGE] Switch to django_bootstrap5 form 2023-12-09 18:27:43 +01:00
Peter Pfeufer
adedf7534f
[FIX] Use of Bootstrap classes 2023-12-09 18:24:01 +01:00
Peter Pfeufer
d0684862fe
[CHANGE] Minor corrections 2023-12-09 18:14:13 +01:00
Peter Pfeufer
2440e5d2b2
[CHANGE] Switch to django_bootstrap5 forms 2023-12-09 18:13:51 +01:00
Peter Pfeufer
5f51ad4a6a
[CHANGE] Use cards 2023-12-09 17:58:50 +01:00
Peter Pfeufer
0bd3acc230
[CHANGE] Moved timers table to its own template 2023-12-09 17:53:03 +01:00
Peter Pfeufer
11a32c90a8
[CHANGE] JS corrections 2023-12-09 17:20:07 +01:00
Peter Pfeufer
6f0b853a60
[CHANGE] Switch to django_bootstrap5 forms 2023-12-09 17:19:47 +01:00
Peter Pfeufer
583a6d4c7f
[FIX] Tables and buttons 2023-12-09 16:51:39 +01:00
Peter Pfeufer
155494afea
[CHANGE] Switch to BS5 base template 2023-12-09 16:18:17 +01:00
Peter Pfeufer
b0aa58b910
[CHANGE] Minor corrections 2023-12-09 16:08:36 +01:00
Peter Pfeufer
1adce85422
[CHANGE] Switched to django_bootstrap5 form 2023-12-09 16:01:24 +01:00
Peter Pfeufer
cd47eadcdc
[REMOVE] Wrong div 2023-12-09 15:51:14 +01:00
Peter Pfeufer
247058a30f
[CHANGE] Switch to BS5 base template 2023-12-09 15:38:05 +01:00
Peter Pfeufer
e0fa615e90
[CHANGE] Gylphicons to Font-Awesome 2023-12-09 15:37:39 +01:00
Peter Pfeufer
61ec67183c
[REMOVE] Unnecessary <br> 2023-12-09 15:11:53 +01:00
Peter Pfeufer
4369813478
[ADD] Roles attribut to pills menu in corputils template 2023-12-09 15:11:53 +01:00
Peter Pfeufer
8bb3d35252
[CHANGE] hrapplication templates updated for BS 5 2023-12-09 15:11:53 +01:00
Peter Pfeufer
398a980fb5
[CHANGE] Switched from Glyphicons to Font Awesome icons 2023-12-09 15:11:53 +01:00
Peter Pfeufer
e14a295ce6
[CHANGE] Format and switch to BS5 base template 2023-12-09 15:11:53 +01:00
Peter Pfeufer
e0cd5c6fb9
[CHANGE] Fat templates updated (Part 1) 2023-12-09 15:11:53 +01:00
Peter Pfeufer
c37ece49d5
[CHANGE] Template renamed to better reflect what it is for 2023-12-09 15:11:53 +01:00
Peter Pfeufer
8adab8bae0
[CHANGE] Replaced Glyphicons with Fontawesome icons 2023-12-09 15:11:53 +01:00
Peter Pfeufer
a9c87bc25a
[FIX] Corputils search template 2023-12-09 15:11:53 +01:00
Peter Pfeufer
a4901802c0
[REMOVE] Unnecessary div 2023-12-09 15:11:53 +01:00
Peter Pfeufer
079dcc5d28
[FIX] Tabbed navigation 2023-12-09 15:11:53 +01:00
Peter Pfeufer
10c5a8906d
[FIX] Dropdown menu 2023-12-09 15:11:53 +01:00
Peter Pfeufer
e6306ea7aa
[FIX] Legend in services.html template 2023-12-09 15:11:53 +01:00
Ariel Rin
20067c1133
Merge branch 'master' of gitlab.com:allianceauth/allianceauth into v4.x 2023-12-08 14:49:56 +10:00
Ariel Rin
dba3c651dc Merge branch 'py3.12' into 'v4.x'
Py3.12 test changes, drop pypy

See merge request allianceauth/allianceauth!1535
2023-12-05 13:05:19 +00:00
Ariel Rin
0b4d2b819b Py3.12 test changes, drop pypy 2023-12-05 13:05:18 +00:00
Ariel Rin
23a3dd1ab9 Merge branch 'fantabular' into 'master'
Doc Modernisation

Closes #1331

See merge request allianceauth/allianceauth!1550
2023-12-05 13:00:50 +00:00
Ariel Rin
81e5bc5337 Merge branch 'master' into 'fantabular'
# Conflicts:
#   docs/maintenance/tuning/index.md
2023-12-02 01:38:18 +00:00
Ariel Rin
7eebf4d953 Merge branch 'docker-optimizations' into 'v4.x'
Docker Healthchecks, loosen redis, NPM sqlite

See merge request allianceauth/allianceauth!1561
2023-12-02 01:37:38 +00:00
Ariel Rin
9e45d2eac7 Merge branch 'classifiers-update' into 'v4.x'
[ADD] Python 3.12 to `pyporject.toml` classifiers

See merge request allianceauth/allianceauth!1557
2023-12-02 01:35:11 +00:00
Ariel Rin
e3017f1ec7 Merge branch 'sw-version-background-fix' into 'v4.x'
[FIX] Background for "Software Version" panel

See merge request allianceauth/allianceauth!1568
2023-12-02 01:33:43 +00:00
Ariel Rin
a8ef844fe7 Merge branch 'tuning' into 'master'
Docs: Sql Tuning and python version comparison

See merge request allianceauth/allianceauth!1545
2023-12-02 01:31:52 +00:00
Ariel Rin
12709b1b56 Merge branch 'fix-custom-error-views' into 'v4.x'
Fix custom error views

See merge request allianceauth/allianceauth!1567
2023-12-02 01:30:26 +00:00
Erik Kalkoken
51ae604efd Fix custom error views 2023-12-02 01:30:26 +00:00
Ariel Rin
63071ec359 Merge branch 'v4bumps' into 'v4.x'
Bump Libraries

See merge request allianceauth/allianceauth!1564
2023-12-02 01:30:13 +00:00
Ariel Rin
0032e4a01f Merge branch 'salartarium-v4.x-patch-91342' into 'v4.x'
Remove redirect. Use stylized EVE to match article title.

See merge request allianceauth/allianceauth!1565
2023-12-02 01:28:10 +00:00
Peter Pfeufer
25ab78a41e
[FIX] Background for "Software Version" panel 2023-12-01 12:16:15 +01:00
salartarium
3aa0e323d2 Remove redirect. Use stylized EVE to match article title. 2023-11-28 00:21:33 +00:00
Ariel Rin
cadbb7e61c
correct redis healthcheck path 2023-11-09 20:43:12 +10:00
Ariel Rin
115263eb5a
scale up logsizes 2023-11-09 20:42:45 +10:00
Ariel Rin
18fec5f614
redis healthcheck script 2023-11-09 18:54:19 +10:00
Ariel Rin
02ab064ec3
include js_template not css 2023-11-09 18:25:21 +10:00
Ariel Rin
ba25f99cb4
bump libraries 2023-11-09 18:14:01 +10:00
Ariel Rin
28fd1b07ea
add log rotation 2023-11-09 18:12:36 +10:00
Ariel Rin
5de19c43df
Version Bump 4.0.0a4 2023-11-09 00:13:45 +10:00
Ariel Rin
9ce1939040
Version Bump 3.8.0 2023-11-09 00:00:28 +10:00
Ariel Rin
322131cd4f
Update source language strings 2023-11-08 23:56:16 +10:00
Ariel Rin
55e6e92da5 Merge branch 'transifex' into 'master'
Update from Transifex

See merge request allianceauth/allianceauth!1562
2023-11-08 13:48:26 +00:00
Ariel Rin
e5d29629a5 Update from Transifex 2023-11-08 13:48:26 +00:00
Ariel Rin
6a0ddc9a83 Merge branch 'docker-superlance' into 'v4.x'
Add superlance/memmon path to the project bootstrap

See merge request allianceauth/allianceauth!1560
2023-11-08 13:39:11 +00:00
Ariel Rin
03be66d11f
multilines dont work in our yaml 2023-11-08 23:26:12 +10:00
Ariel Rin
26e187e4c8 Merge branch 'assign-users-on-group-form' into 'master'
New Feature: Assign/remove users on group form

See merge request allianceauth/allianceauth!1543
2023-11-08 13:05:25 +00:00
Erik Kalkoken
3480c4e0e8 New Feature: Assign/remove users on group form 2023-11-08 13:05:24 +00:00
Ariel Rin
1544f097e0 Merge branch 'fix-leave-tab-for-autoleave' into 'master'
Fix leave tab for autoleave

See merge request allianceauth/allianceauth!1536
2023-11-08 13:04:27 +00:00
Erik Kalkoken
2477c31656 Fix leave tab for autoleave 2023-11-08 13:04:26 +00:00
Ariel Rin
0dc631d69e Merge branch 'master' into 'master'
Stop renaming discord nick when setting has it disabled

See merge request allianceauth/allianceauth!1540
2023-11-08 13:04:12 +00:00
Dusty Meg
2a9981cdb9 Stop renaming discord nick when setting has it disabled 2023-11-08 13:04:11 +00:00
Ariel Rin
3d92008069
use SQLite by default, leave framework for mariadb backed npm 2023-11-08 21:20:23 +10:00
Ariel Rin
59e47c24c2
loosen redis to 7 major (allow 7.2) 2023-11-08 21:19:36 +10:00
Ariel Rin
6d942555ff
add healthchecks 2023-11-08 21:11:38 +10:00
Ariel Rin
7e312bb95f
four args now 2023-11-08 20:25:24 +10:00
Ariel Rin
c92fee78e2
add superlance/memmon path to the bootstrap 2023-11-08 20:01:07 +10:00
Ariel Rin
004c48b8ad
mild formatting changes 2023-11-08 13:45:43 +10:00
Ariel Rin
658a8cd6ce Merge branch 'task-queue-progressbar-background' into 'v4.x'
[FIX] Celery task status bar background

See merge request allianceauth/allianceauth!1559
2023-11-08 03:31:48 +00:00
Peter Pfeufer
c1dc130766
[FIX] Celery task status bar background 2023-11-07 15:21:44 +01:00
Ariel Rin
35f5573b63 Merge branch 'js-fixes' into 'v4.x'
JS Fixes

See merge request allianceauth/allianceauth!1556
2023-11-06 13:49:31 +00:00
Ariel Rin
4d66b7d456
and gitlab ci update for new docs 2023-11-01 13:28:32 +10:00
Ariel Rin
77e5747a23
move tox tests to new dependency format 2023-11-01 13:15:18 +10:00
Peter Pfeufer
a9ebecdec6
[ADD] Python 3.12 to pyporject.toml classifiers 2023-10-31 22:25:39 +01:00
Peter Pfeufer
21f0a96422
[CHANGE] Modernize and convert to ES6+ 2023-10-31 22:00:43 +01:00
Peter Pfeufer
9e47d19337
[ADD] Missing semicolons 2023-10-31 21:48:18 +01:00
Peter Pfeufer
2c5972d0ab
[ADD] Refresh notification icon script
Similar to what we have in AAv3 for the notification count
2023-10-31 21:47:40 +01:00
Ariel Rin
6118c0ddec
override non compatible dark mode check css 2023-11-01 01:10:39 +10:00
Ariel Rin
ce25deeca1
minor formatting 2023-10-31 23:52:34 +10:00
Ariel Rin
60084de3db
Pygments lexer name 'math' is not known 2023-10-31 23:43:52 +10:00
Ariel Rin
e16c68e255
increase anchor generation 2023-10-31 23:43:45 +10:00
Ariel Rin
bf14e9c7c3
merged back into sphinxcontrib-django 2023-10-31 23:36:42 +10:00
Ariel Rin
98e91fe207
MyST conversion 2023-10-31 23:31:41 +10:00
Ariel Rin
ee41d62c13 Merge branch 'quickfix-services-control-template' into 'v4.x'
[REMOVE] Deprecated overrides …

See merge request allianceauth/allianceauth!1555
2023-10-31 13:29:48 +00:00
Peter Pfeufer
346b4014a9
[ADD] Missing semicolon
Just for the sake of it …
2023-10-31 14:13:01 +01:00
Peter Pfeufer
9b56a441ed
[REMOVE] Deprecated overrides … 2023-10-31 14:10:26 +01:00
Ariel Rin
068bf1ae7a Merge branch 'services-template-improvements' into 'v4.x'
Services template improvements

See merge request allianceauth/allianceauth!1553
2023-10-31 12:42:08 +00:00
Peter Pfeufer
5be686e3ca
[FIX] Username check 2023-10-31 13:28:38 +01:00
Ariel Rin
a215b4411c Merge branch 'v4theme' into 'v4.x'
Missing Import on views

See merge request allianceauth/allianceauth!1554
2023-10-31 12:14:15 +00:00
Ariel Rin
e15cfa0fb1 Missing Import on views 2023-10-31 12:14:14 +00:00
Peter Pfeufer
46d51699f4
[CHANGE] Service delete confirm template converted to BS5 2023-10-31 11:59:12 +01:00
Peter Pfeufer
ff30a136d5
[CHANGE] Service credentials template converted to BS5 2023-10-31 11:56:18 +01:00
Peter Pfeufer
6dcf3304d5
Merge remote-tracking branch 'origin/services-template-improvements' into services-template-improvements 2023-10-31 11:49:49 +01:00
Peter Pfeufer
beddeea338
[CHANGE] Discourse service status badge text 2023-10-31 11:49:18 +01:00
Ariel Rin
69723937f7 Merge branch 'update-project-classifier' into 'v4.x'
[CHANGE] Update project classifier for Django

See merge request allianceauth/allianceauth!1551
2023-10-31 10:01:14 +00:00
Ariel Rin
c541f56ee2 Merge branch 'fix-dashboard-timers' into 'v4.x'
[FIX] EveCorporationInfo matching query does not exist

See merge request allianceauth/allianceauth!1552
2023-10-31 10:01:03 +00:00
Peter Pfeufer
7e887e5e34
[ADD] General template include for username line 2023-10-31 10:47:06 +01:00
Peter Pfeufer
072327c79f
[CHANGE] Comment active section for nor for Discourse service
The Discourse service doesn't seem to have anything to determine weather it's active or not.
2023-10-31 10:38:01 +01:00
Peter Pfeufer
28af3ff11e
[CHANGE] Sort service card information to be a bit more uniform in apperance 2023-10-31 10:27:49 +01:00
Peter Pfeufer
e3b151f2fb
[CHANGE] Use BS5 forms 2023-10-31 10:25:04 +01:00
Peter Pfeufer
f87d7dbdf8
[FIX] Normalization of TeamSpeak3 service name 2023-10-31 10:12:02 +01:00
Peter Pfeufer
a04e6ae3d0
[FIX] Normalization of IPSuite4 service name 2023-10-31 10:09:43 +01:00
Peter Pfeufer
15042f5e77
[FIX] Capitalization of Discord service name 2023-10-31 10:07:47 +01:00
Peter Pfeufer
6e25361d5e
[FIX] Capitalization of XenForo service name 2023-10-31 10:06:36 +01:00
Peter Pfeufer
9e639a0eeb
[FIX] Capitalization of SMF service name 2023-10-31 10:04:55 +01:00
Peter Pfeufer
257fbdef36
[FIX] Capitalization of Jabber service name 2023-10-31 10:03:06 +01:00
Peter Pfeufer
df003c8ec5
[CHANGE] TS³ service template 2023-10-31 09:57:54 +01:00
Peter Pfeufer
ba22685eb8
[CHANGE] Discourse service template 2023-10-31 09:53:09 +01:00
Peter Pfeufer
773288072a
[CHANGE] Discord service 2023-10-31 09:48:29 +01:00
Peter Pfeufer
63afb13d25
[CHANGE] Mumble service template 2023-10-31 09:48:07 +01:00
Peter Pfeufer
5dd286bbe7
[CHANGE] Set a default via status template 2023-10-31 09:47:22 +01:00
Peter Pfeufer
8aaa8172ca
[CHANGE] Only show username when there is a username 2023-10-31 08:58:30 +01:00
Peter Pfeufer
b68b401146
[FIX] EveCorporationInfo matching query does not exist 2023-10-29 11:55:42 +01:00
Aaron Kable
f7821b647f swap to middleware to sync menu hooks 2023-10-28 15:26:48 +08:00
Peter Pfeufer
a6526d6f78
[CHANGE] Update project classifier for Django 2023-10-28 00:12:40 +02:00
Ariel Rin
7024552c4e
remove ubuntu 1804 2023-10-27 22:20:27 +10:00
Ariel Rin
a0719e4b86
tabify 2023-10-27 22:20:11 +10:00
Ariel Rin
906c589f14
more automated upgrades 2023-10-27 22:19:28 +10:00
Ariel Rin
ffb526ab0c
find and replace fixes, will introduce errors 2023-10-27 16:37:53 +10:00
Ariel Rin
b9d128259e
correct path 2023-10-27 16:37:30 +10:00
Ariel Rin
13d866bd0d
RST to MyST, pyproject doc dependencies, add sphinx tabs 2023-10-27 16:37:09 +10:00
Ariel Rin
ea1887b9ec
mild clarifications 2023-10-26 11:26:39 +10:00
Ariel Rin
d2f8c2a42f
Merge branch 'master' of gitlab.com:allianceauth/allianceauth into tuning 2023-10-26 10:49:29 +10:00
Ariel Rin
200e8f2ff1
initial sql tuning and python version comparison 2023-10-11 12:33:17 +10:00
492 changed files with 43209 additions and 14463 deletions

View File

@ -19,5 +19,6 @@ exclude_lines =
if __name__ == .__main__.: if __name__ == .__main__.:
def __repr__ def __repr__
raise AssertionError raise AssertionError
if TYPE_CHECKING:
ignore_errors = True ignore_errors = True

1
.gitignore vendored
View File

@ -73,3 +73,4 @@ celerybeat-schedule
.flake8 .flake8
.pylintrc .pylintrc
Makefile Makefile
alliance_auth.sqlite3

View File

@ -25,12 +25,12 @@ before_script:
pre-commit-check: pre-commit-check:
<<: *only-default <<: *only-default
stage: pre-commit stage: pre-commit
image: python:3.11-bullseye image: python:3.11-bookworm
variables: # variables:
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit # PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
cache: # cache:
paths: # paths:
- ${PRE_COMMIT_HOME} # - ${PRE_COMMIT_HOME}
script: script:
- pip install pre-commit - pip install pre-commit
- pre-commit run --all-files - pre-commit run --all-files
@ -53,7 +53,7 @@ secret_detection:
test-3.8-core: test-3.8-core:
<<: *only-default <<: *only-default
image: python:3.8-bullseye image: python:3.8-bookworm
script: script:
- tox -e py38-core - tox -e py38-core
artifacts: artifacts:
@ -65,7 +65,7 @@ test-3.8-core:
test-3.9-core: test-3.9-core:
<<: *only-default <<: *only-default
image: python:3.9-bullseye image: python:3.9-bookworm
script: script:
- tox -e py39-core - tox -e py39-core
artifacts: artifacts:
@ -77,7 +77,7 @@ test-3.9-core:
test-3.10-core: test-3.10-core:
<<: *only-default <<: *only-default
image: python:3.10-bullseye image: python:3.10-bookworm
script: script:
- tox -e py310-core - tox -e py310-core
artifacts: artifacts:
@ -89,7 +89,7 @@ test-3.10-core:
test-3.11-core: test-3.11-core:
<<: *only-default <<: *only-default
image: python:3.11-bullseye image: python:3.11-bookworm
script: script:
- tox -e py311-core - tox -e py311-core
artifacts: artifacts:
@ -99,22 +99,9 @@ test-3.11-core:
coverage_format: cobertura coverage_format: cobertura
path: coverage.xml path: coverage.xml
test-pvpy-core:
<<: *only-default
image: pypy:3.9-bullseye
script:
- tox -e pypy-all
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
allow_failure: true
test-3.12-core: test-3.12-core:
<<: *only-default <<: *only-default
image: python:3.12-rc-bullseye image: python:3.12-bookworm
script: script:
- tox -e py312-core - tox -e py312-core
artifacts: artifacts:
@ -123,11 +110,10 @@ test-3.12-core:
coverage_report: coverage_report:
coverage_format: cobertura coverage_format: cobertura
path: coverage.xml path: coverage.xml
allow_failure: true
test-3.8-all: test-3.8-all:
<<: *only-default <<: *only-default
image: python:3.8-bullseye image: python:3.8-bookworm
script: script:
- tox -e py38-all - tox -e py38-all
artifacts: artifacts:
@ -139,7 +125,7 @@ test-3.8-all:
test-3.9-all: test-3.9-all:
<<: *only-default <<: *only-default
image: python:3.9-bullseye image: python:3.9-bookworm
script: script:
- tox -e py39-all - tox -e py39-all
artifacts: artifacts:
@ -151,7 +137,7 @@ test-3.9-all:
test-3.10-all: test-3.10-all:
<<: *only-default <<: *only-default
image: python:3.10-bullseye image: python:3.10-bookworm
script: script:
- tox -e py310-all - tox -e py310-all
artifacts: artifacts:
@ -163,7 +149,7 @@ test-3.10-all:
test-3.11-all: test-3.11-all:
<<: *only-default <<: *only-default
image: python:3.11-bullseye image: python:3.11-bookworm
script: script:
- tox -e py311-all - tox -e py311-all
artifacts: artifacts:
@ -174,22 +160,9 @@ test-3.11-all:
path: coverage.xml path: coverage.xml
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/' coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
test-pvpy-all:
<<: *only-default
image: pypy:3.9-bullseye
script:
- tox -e pypy-all
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: coverage.xml
allow_failure: true
test-3.12-all: test-3.12-all:
<<: *only-default <<: *only-default
image: python:3.12-rc-bullseye image: python:3.12-bookworm
script: script:
- tox -e py312-all - tox -e py312-all
artifacts: artifacts:
@ -198,11 +171,10 @@ test-3.12-all:
coverage_report: coverage_report:
coverage_format: cobertura coverage_format: cobertura
path: coverage.xml path: coverage.xml
allow_failure: true
build-test: build-test:
stage: test stage: test
image: python:3.11-bullseye image: python:3.11-bookworm
before_script: before_script:
- python -m pip install --upgrade pip - python -m pip install --upgrade pip
@ -221,13 +193,13 @@ build-test:
test-docs: test-docs:
<<: *only-default <<: *only-default
image: python:3.11-bullseye image: python:3.11-bookworm
script: script:
- tox -e docs - tox -e docs
deploy_production: deploy_production:
stage: deploy stage: deploy
image: python:3.11-bullseye image: python:3.11-bookworm
before_script: before_script:
- python -m pip install --upgrade pip - python -m pip install --upgrade pip
@ -259,16 +231,7 @@ build-image:
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-* docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
docker run --privileged --rm tonistiigi/binfmt --install all docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx create --use --name new-builder docker buildx create --use --name new-builder
docker buildx build . docker buildx build . --tag $IMAGE_TAG --tag $CURRENT_TAG --tag $MINOR_TAG --tag $MAJOR_TAG --tag $LATEST_TAG --file docker/Dockerfile --platform linux/amd64,linux/arm64 --push --build-arg AUTH_VERSION=$(echo $CI_COMMIT_TAG | cut -c 2-)
--tag $IMAGE_TAG
--tag $CURRENT_TAG
--tag $MINOR_TAG
--tag $MAJOR_TAG
--tag $LATEST_TAG
--file docker/Dockerfile
--platform linux/amd64,linux/arm64
--push
--build-arg AUTH_VERSION=$(echo $CI_COMMIT_TAG | cut -c 2-)
rules: rules:
- if: $CI_COMMIT_TAG - if: $CI_COMMIT_TAG
when: delayed when: delayed
@ -288,12 +251,7 @@ build-image-dev:
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-* docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
docker run --privileged --rm tonistiigi/binfmt --install all docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx create --use --name new-builder docker buildx create --use --name new-builder
docker buildx build . docker buildx build . --tag $IMAGE_TAG --file docker/Dockerfile --platform linux/amd64,linux/arm64 --push --build-arg AUTH_PACKAGE=git+https://gitlab.com/allianceauth/allianceauth@$CI_COMMIT_BRANCH
--tag $IMAGE_TAG
--file docker/Dockerfile
--platform linux/amd64,linux/arm64
--push
--build-arg AUTH_PACKAGE=git+https://gitlab.com/allianceauth/allianceauth@$CI_COMMIT_BRANCH
rules: rules:
- if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == ""' - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == ""'
when: manual when: manual
@ -314,12 +272,7 @@ build-image-mr:
docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-* docker run --privileged --rm tonistiigi/binfmt --uninstall qemu-*
docker run --privileged --rm tonistiigi/binfmt --install all docker run --privileged --rm tonistiigi/binfmt --install all
docker buildx create --use --name new-builder docker buildx create --use --name new-builder
docker buildx build . docker buildx build . --tag $IMAGE_TAG --file docker/Dockerfile --platform linux/amd64,linux/arm64 --push --build-arg AUTH_PACKAGE=git+$CI_MERGE_REQUEST_SOURCE_PROJECT_URL@$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
--tag $IMAGE_TAG
--file docker/Dockerfile
--platform linux/amd64,linux/arm64
--push
--build-arg AUTH_PACKAGE=git+$CI_MERGE_REQUEST_SOURCE_PROJECT_URL@$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME
rules: rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
when: manual when: manual

View File

@ -3,9 +3,41 @@
# Update this file: # Update this file:
# pre-commit autoupdate # pre-commit autoupdate
# Set the default language versions for the hooks
default_language_version:
python: python3 # Force all Python hooks to use Python 3
node: 22.12.0 # Force all Node hooks to use Node 22.12.0
# Globally exclude files
# https://pre-commit.com/#top_level-exclude
exclude: |
(?x)(
LICENSE|
allianceauth\/static\/allianceauth\/css\/themes\/bootstrap-locals.less|
\.min\.css|
\.min\.js|
\.po|
\.mo|
swagger\.json|
static/(.*)/libs/
)
repos: repos:
# Code Upgrades
- repo: https://github.com/asottile/pyupgrade
rev: v3.20.0
hooks:
- id: pyupgrade
args: [--py38-plus]
- repo: https://github.com/adamchainz/django-upgrade
rev: 1.25.0
hooks:
- id: django-upgrade
args: [--target-version=4.2]
# Formatting
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0 rev: v5.0.0
hooks: hooks:
# Identify invalid files # Identify invalid files
- id: check-ast - id: check-ast
@ -13,73 +45,51 @@ repos:
- id: check-json - id: check-json
- id: check-toml - id: check-toml
- id: check-xml - id: check-xml
# git checks # git checks
- id: check-merge-conflict - id: check-merge-conflict
- id: check-added-large-files - id: check-added-large-files
args: [ --maxkb=1000 ] args: [--maxkb=1000]
- id: detect-private-key - id: detect-private-key
- id: check-case-conflict - id: check-case-conflict
# Python checks # Python checks
# - id: check-docstring-first # - id: check-docstring-first
- id: debug-statements - id: debug-statements
# - id: requirements-txt-fixer # - id: requirements-txt-fixer
- id: fix-encoding-pragma - id: fix-encoding-pragma
args: [ --remove ] args: [--remove]
- id: fix-byte-order-marker - id: fix-byte-order-marker
# General quality checks # General quality checks
- id: mixed-line-ending - id: mixed-line-ending
args: [ --fix=lf ] args: [--fix=lf]
- id: trailing-whitespace - id: trailing-whitespace
args: [ --markdown-linebreak-ext=md ] args: [--markdown-linebreak-ext=md]
exclude: |
(?x)(
\.min\.css|
\.min\.js|
\.po|
\.mo|
swagger\.json
)
- id: check-executables-have-shebangs - id: check-executables-have-shebangs
- id: end-of-file-fixer - id: end-of-file-fixer
exclude: |
(?x)(
\.min\.css|
\.min\.js|
\.po|
\.mo|
swagger\.json
)
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python - repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: 2.7.2 rev: 3.2.1
hooks: hooks:
- id: editorconfig-checker - id: editorconfig-checker
exclude: | - repo: https://github.com/igorshubovych/markdownlint-cli
(?x)( rev: v0.45.0
LICENSE|
allianceauth\/static\/allianceauth\/css\/themes\/bootstrap-locals.less|
\.po|
\.mo|
swagger\.json
)
- repo: https://github.com/asottile/pyupgrade
rev: v3.10.1
hooks: hooks:
- id: pyupgrade - id: markdownlint
args: [ --py38-plus ] language: node
args:
- repo: https://github.com/adamchainz/django-upgrade - --disable=MD013
rev: 1.14.0 # Infrastructure
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.6.0
hooks: hooks:
- id: django-upgrade - id: pyproject-fmt
args: [--target-version=4.2] name: pyproject.toml formatter
description: "Format the pyproject.toml file."
- repo: https://github.com/asottile/setup-cfg-fmt args:
rev: v2.3.0 - --indent=4
additional_dependencies:
- tox==4.24.1 # https://github.com/tox-dev/tox/releases/latest
- repo: https://github.com/abravalheri/validate-pyproject
rev: v0.24.1
hooks: hooks:
- id: setup-cfg-fmt - id: validate-pyproject
args: [ --include-version-classifiers ] name: Validate pyproject.toml
description: "Validate the pyproject.toml file."

View File

@ -7,11 +7,14 @@ version: 2
# Set the version of Python and other tools you might need # Set the version of Python and other tools you might need
build: build:
os: ubuntu-20.04 os: ubuntu-22.04
apt_packages: apt_packages:
- redis - redis
tools: tools:
python: "3.8" python: "3.11"
jobs:
post_system_dependencies:
- redis-server --daemonize yes
# Build documentation in the docs/ directory with Sphinx # Build documentation in the docs/ directory with Sphinx
sphinx: sphinx:
@ -20,7 +23,7 @@ sphinx:
# Optionally build your docs in additional formats such as PDF and ePub # Optionally build your docs in additional formats such as PDF and ePub
formats: all formats: all
# Optionally set the version of Python and requirements required to build your docs # Python requirements required to build your docs
python: python:
install: install:
- method: pip - method: pip

View File

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

View File

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

10
.tx/transifex.yml Normal file
View File

@ -0,0 +1,10 @@
filters:
- filter_type: file
file_format: PO
source_file: allianceauth/locale/en/LC_MESSAGES/django.po
source_language: en
translation_files_expression: allianceauth/locale/<lang>/LC_MESSAGES/django.po
settings:
language_mapping:
zh-Hans: zh_Hans

View File

@ -1,50 +1,56 @@
# Alliance Auth # Alliance Auth
[![license](https://img.shields.io/badge/license-GPLv2-green)](https://pypi.org/project/allianceauth/) [![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/) [![Python Versions](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/) [![Django Versions](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/) [![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) [![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)](http://allianceauth.readthedocs.io/?badge=latest) [![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) [![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 ## Content
- [Overview](#overview) - [Overview](#overview)
- [Documentation](http://allianceauth.rtfd.io) - [Documentation](https://allianceauth.rtfd.io)
- [Support](#support) - [Support](#support)
- [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases) - [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases)
- [Developer Team](#developer-team) - [Developer Team](#development-team)
- [Contributing](#contributing) - [Contributing](#contributing)
## Overview ## 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: 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. - 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) - Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
- English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr:, Russian :flag_ru:, Italian :flag_it:, French :flag_fr:, Japanese :flag_jp: and Ukrainian :flag_ua: Localization - English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr:, Russian :flag_ru:, Italian :flag_it:, French :flag_fr:, Japanese :flag_jp: and Ukrainian :flag_ua: Localization
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io). For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](https://allianceauth.rtfd.io).
## Screenshot ## 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 ## Support

View File

@ -5,7 +5,7 @@ manage online service access.
# This will make sure the app is always imported when # This will make sure the app is always imported when
# Django starts so that shared_task will use this app. # Django starts so that shared_task will use this app.
__version__ = '4.0.0a3' __version__ = '4.8.0'
__title__ = 'Alliance Auth' __title__ = 'Alliance Auth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth' __url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = f'{__title__} v{__version__}' NAME = f'{__title__} v{__version__}'

View File

@ -1,15 +1,16 @@
from django.contrib import admin from django.contrib import admin
from .models import AnalyticsIdentifier, AnalyticsTokens from .models import AnalyticsIdentifier, AnalyticsTokens
from solo.admin import SingletonModelAdmin
@admin.register(AnalyticsIdentifier) @admin.register(AnalyticsIdentifier)
class AnalyticsIdentifierAdmin(admin.ModelAdmin): class AnalyticsIdentifierAdmin(SingletonModelAdmin):
search_fields = ['identifier', ] search_fields = ['identifier', ]
list_display = ('identifier',) list_display = ['identifier', ]
@admin.register(AnalyticsTokens) @admin.register(AnalyticsTokens)
class AnalyticsTokensAdmin(admin.ModelAdmin): class AnalyticsTokensAdmin(admin.ModelAdmin):
search_fields = ['name', ] search_fields = ['name', ]
list_display = ('name', 'type',) list_display = ['name', 'type', ]

View File

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

View File

@ -0,0 +1,17 @@
# Generated by Django 4.2.16 on 2024-12-11 02:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('analytics', '0009_remove_analyticstokens_ignore_paths_and_more'),
]
operations = [
migrations.AlterModelOptions(
name='analyticsidentifier',
options={'verbose_name': 'Analytics Identifier'},
),
]

View File

@ -1,23 +1,19 @@
from typing import Literal
from django.db import models from django.db import models
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from solo.models import SingletonModel
from uuid import uuid4 from uuid import uuid4
class AnalyticsIdentifier(models.Model): class AnalyticsIdentifier(SingletonModel):
identifier = models.UUIDField( identifier = models.UUIDField(default=uuid4, editable=False)
default=uuid4,
editable=False)
def save(self, *args, **kwargs): def __str__(self) -> Literal['Analytics Identifier']:
if not self.pk and AnalyticsIdentifier.objects.exists(): return "Analytics Identifier"
# Force a single object
raise ValidationError('There is can be only one \ class Meta:
AnalyticsIdentifier instance') verbose_name = "Analytics Identifier"
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
return super().save(*args, **kwargs)
class AnalyticsTokens(models.Model): class AnalyticsTokens(models.Model):

View File

@ -5,10 +5,13 @@ from django.apps import apps
from celery import shared_task from celery import shared_task
from .models import AnalyticsTokens, AnalyticsIdentifier from .models import AnalyticsTokens, AnalyticsIdentifier
from .utils import ( from .utils import (
existence_baremetal_or_docker,
install_stat_addons, install_stat_addons,
install_stat_tokens, install_stat_tokens,
install_stat_users) install_stat_users)
from allianceauth import __version__
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
BASE_URL = "https://www.google-analytics.com" BASE_URL = "https://www.google-analytics.com"
@ -65,8 +68,8 @@ def analytics_event(namespace: str,
value=value).apply_async(priority=9) value=value).apply_async(priority=9)
@shared_task() @shared_task
def analytics_daily_stats(): def analytics_daily_stats() -> None:
"""Celery Task: Do not call directly """Celery Task: Do not call directly
Gathers a series of daily statistics Gathers a series of daily statistics
@ -75,6 +78,7 @@ def analytics_daily_stats():
users = install_stat_users() users = install_stat_users()
tokens = install_stat_tokens() tokens = install_stat_tokens()
addons = install_stat_addons() addons = install_stat_addons()
existence_type = existence_baremetal_or_docker()
logger.debug("Running Daily Analytics Upload") logger.debug("Running Daily Analytics Upload")
analytics_event(namespace='allianceauth.analytics', analytics_event(namespace='allianceauth.analytics',
@ -82,6 +86,11 @@ def analytics_daily_stats():
label='existence', label='existence',
value=1, value=1,
event_type='Stats') event_type='Stats')
analytics_event(namespace='allianceauth.analytics',
task='send_install_stats',
label=existence_type,
value=1,
event_type='Stats')
analytics_event(namespace='allianceauth.analytics', analytics_event(namespace='allianceauth.analytics',
task='send_install_stats', task='send_install_stats',
label='users', label='users',
@ -97,16 +106,42 @@ def analytics_daily_stats():
label='addons', label='addons',
value=addons, value=addons,
event_type='Stats') event_type='Stats')
for appconfig in apps.get_app_configs(): for appconfig in apps.get_app_configs():
analytics_event(namespace='allianceauth.analytics', if appconfig.label in [
task='send_extension_stats', "django_celery_beat",
label=appconfig.label, "bootstrapform",
value=1, "messages",
event_type='Stats') "sessions",
"auth",
"staticfiles",
"users",
"addons",
"admin",
"humanize",
"contenttypes",
"sortedm2m",
"django_bootstrap5",
"tokens",
"authentication",
"services",
"framework",
"notifications"
"eveonline",
"navhelper",
"analytics",
"menu",
"theme"
]:
pass
else:
analytics_event(namespace='allianceauth.analytics',
task='send_extension_stats',
label=appconfig.label,
value=1,
event_type='Stats')
@shared_task() @shared_task
def send_ga_tracking_celery_event( def send_ga_tracking_celery_event(
measurement_id: str, measurement_id: str,
secret: str, secret: str,
@ -136,10 +171,10 @@ def send_ga_tracking_celery_event(
} }
payload = { payload = {
'client_id': AnalyticsIdentifier.objects.get(id=1).identifier.hex, 'client_id': AnalyticsIdentifier.get_solo().identifier.hex,
"user_properties": { "user_properties": {
"allianceauth_version": { "allianceauth_version": {
"value": "allianceauth_version" "value": __version__
} }
}, },
'non_personalized_ads': True, 'non_personalized_ads': True,

View File

@ -1,9 +1,8 @@
from allianceauth.analytics.models import AnalyticsIdentifier from allianceauth.analytics.models import AnalyticsIdentifier
from django.core.exceptions import ValidationError
from django.test.testcases import TestCase from django.test.testcases import TestCase
from uuid import UUID, uuid4 from uuid import uuid4
# Identifiers # Identifiers
@ -14,14 +13,4 @@ uuid_2 = "7aa6bd70701f44729af5e3095ff4b55c"
class TestAnalyticsIdentifier(TestCase): class TestAnalyticsIdentifier(TestCase):
def test_identifier_random(self): def test_identifier_random(self):
self.assertNotEqual(AnalyticsIdentifier.objects.get(), uuid4) self.assertNotEqual(AnalyticsIdentifier.get_solo(), uuid4)
def test_identifier_singular(self):
AnalyticsIdentifier.objects.all().delete()
AnalyticsIdentifier.objects.create(identifier=uuid_1)
# Yeah i have multiple asserts here, they all do the same thing
with self.assertRaises(ValidationError):
AnalyticsIdentifier.objects.create(identifier=uuid_2)
self.assertEqual(AnalyticsIdentifier.objects.count(), 1)
self.assertEqual(AnalyticsIdentifier.objects.get(
pk=1).identifier, UUID(uuid_1))

View File

@ -1,3 +1,4 @@
import os
from django.apps import apps from django.apps import apps
from allianceauth.authentication.models import User from allianceauth.authentication.models import User
from esi.models import Token from esi.models import Token
@ -34,3 +35,16 @@ def install_stat_addons() -> int:
The Number of Installed Apps""" The Number of Installed Apps"""
addons = len(list(apps.get_app_configs())) addons = len(list(apps.get_app_configs()))
return addons return addons
def existence_baremetal_or_docker() -> str:
"""Checks the Installation Type of an install
Returns
-------
str
existence_baremetal or existence_docker"""
docker_tag = os.getenv('AA_DOCKER_TAG')
if docker_tag:
return "existence_docker"
return "existence_baremetal"

View File

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

View File

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

View File

@ -1,6 +1,6 @@
from allianceauth.hooks import DashboardItemHook from allianceauth.hooks import DashboardItemHook
from allianceauth import hooks from allianceauth import hooks
from .views import dashboard_characters, dashboard_groups, dashboard_admin from .views import dashboard_characters, dashboard_esi_check, dashboard_groups, dashboard_admin
class UserCharactersHook(DashboardItemHook): class UserCharactersHook(DashboardItemHook):
@ -26,6 +26,15 @@ class AdminHook(DashboardItemHook):
DashboardItemHook.__init__( DashboardItemHook.__init__(
self, self,
dashboard_admin, dashboard_admin,
1
)
class ESICheckHook(DashboardItemHook):
def __init__(self):
DashboardItemHook.__init__(
self,
dashboard_esi_check,
0 0
) )
@ -43,3 +52,8 @@ def register_groups_hook():
@hooks.register('dashboard_hook') @hooks.register('dashboard_hook')
def register_admin_hook(): def register_admin_hook():
return AdminHook() return AdminHook()
@hooks.register('dashboard_hook')
def register_esi_hook():
return ESICheckHook()

View File

@ -2,7 +2,6 @@ import logging
from django.contrib.auth.backends import ModelBackend from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User, Permission from django.contrib.auth.models import User, Permission
from django.contrib import messages
from .models import UserProfile, CharacterOwnership, OwnershipRecord from .models import UserProfile, CharacterOwnership, OwnershipRecord
@ -41,9 +40,7 @@ class StateBackend(ModelBackend):
if ownership.user.profile.main_character: if ownership.user.profile.main_character:
if ownership.user.profile.main_character.character_id == token.character_id: if ownership.user.profile.main_character.character_id == token.character_id:
return ownership.user return ownership.user
else: ## this is an alt, enforce main only. else: # this is an alt, enforce main only.
if request:
messages.error("Unable to authenticate with this Character, Please log in with the main character associated with this account.")
return None return None
else: else:
logger.debug(f'{token.character_name} has changed ownership. Creating new user account.') logger.debug(f'{token.character_name} has changed ownership. Creating new user account.')
@ -66,9 +63,7 @@ class StateBackend(ModelBackend):
user = records[0].user user = records[0].user
if user.profile.main_character: if user.profile.main_character:
if user.profile.main_character.character_id != token.character_id: if user.profile.main_character.character_id != token.character_id:
## this is an alt, enforce main only due to trust issues in SSO. # this is an alt, enforce main only due to trust issues in SSO.
if request:
messages.error("Unable to authenticate with this Character, Please log in with the main character associated with this account. Then add this character from the dashboard.")
return None return None
token.user = user token.user = user

View File

@ -0,0 +1,12 @@
from django.utils.translation import gettext_lazy as _
# Overide ESI messages in the dashboard widget
# when the returned messages are not helpful or out of date
ESI_ERROR_MESSAGE_OVERRIDES = {
420: _("This software has exceeded the error limit for ESI. "
"If you are a user, please contact the maintainer of this software."
" If you are a developer/maintainer, please make a greater "
"effort in the future to receive valid responses. For tips on how, "
"come have a chat with us in ##3rd-party-dev-and-esi on the EVE "
"Online Discord. https://www.eveonline.com/discord")
}

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.13 on 2024-05-12 09:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0022_userprofile_theme'),
]
operations = [
migrations.AlterField(
model_name='userprofile',
name='language',
field=models.CharField(blank=True, choices=[('en', 'English'), ('de', 'German'), ('es', 'Spanish'), ('zh-hans', 'Chinese Simplified'), ('ru', 'Russian'), ('ko', 'Korean'), ('fr', 'French'), ('ja', 'Japanese'), ('it', 'Italian'), ('uk', 'Ukrainian'), ('pl', 'Polish')], default='', max_length=10, verbose_name='Language'),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2 on 2024-09-13 09:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('authentication', '0023_alter_userprofile_language'),
]
operations = [
migrations.AlterField(
model_name='userprofile',
name='language',
field=models.CharField(blank=True, choices=[('en', 'English'), ('cs-cz', 'Czech'), ('de', 'German'), ('es', 'Spanish'), ('it-it', 'Italian'), ('ja', 'Japanese'), ('ko-kr', 'Korean'), ('fr-fr', 'French'), ('ru', 'Russian'), ('nl-nl', 'Dutch'), ('pl-pl', 'Polish'), ('uk', 'Ukrainian'), ('zh-hans', 'Simplified Chinese')], default='', max_length=10, verbose_name='Language'),
),
]

View File

@ -1,4 +1,5 @@
import logging import logging
from typing import ClassVar
from django.contrib.auth.models import User, Permission from django.contrib.auth.models import User, Permission
from django.db import models, transaction from django.db import models, transaction
@ -27,7 +28,7 @@ class State(models.Model):
help_text="Factions to whose members this state is available.") help_text="Factions to whose members this state is available.")
public = models.BooleanField(default=False, help_text="Make this state available to any character.") public = models.BooleanField(default=False, help_text="Make this state available to any character.")
objects = StateManager() objects: ClassVar[StateManager] = StateManager()
class Meta: class Meta:
ordering = ['-priority'] ordering = ['-priority']
@ -67,17 +68,20 @@ class UserProfile(models.Model):
""" """
Choices for UserProfile.language Choices for UserProfile.language
""" """
# Sorted by Language Code alphabetical order + English at top
ENGLISH = 'en', _('English') ENGLISH = 'en', _('English')
CZECH = 'cs-cz', _("Czech") # Not yet at 50% translated
GERMAN = 'de', _('German') GERMAN = 'de', _('German')
SPANISH = 'es', _('Spanish') SPANISH = 'es', _('Spanish')
CHINESE = 'zh-hans', _('Chinese Simplified') ITALIAN = 'it-it', _('Italian')
RUSSIAN = 'ru', _('Russian')
KOREAN = 'ko', _('Korean')
FRENCH = 'fr', _('French')
JAPANESE = 'ja', _('Japanese') JAPANESE = 'ja', _('Japanese')
ITALIAN = 'it', _('Italian') KOREAN = 'ko-kr', _('Korean')
FRENCH = 'fr-fr', _('French')
RUSSIAN = 'ru', _('Russian')
DUTCH = 'nl-nl', _("Dutch")
POLISH = 'pl-pl', _("Polish")
UKRAINIAN = 'uk', _('Ukrainian') UKRAINIAN = 'uk', _('Ukrainian')
CHINESE = 'zh-hans', _('Simplified Chinese')
user = models.OneToOneField( user = models.OneToOneField(
User, User,
@ -134,8 +138,10 @@ class UserProfile(models.Model):
sender=self.__class__, user=self.user, state=self.state sender=self.__class__, user=self.user, state=self.state
) )
def __str__(self): def __str__(self) -> str:
return str(self.user) return str(self.user)
class CharacterOwnership(models.Model): class CharacterOwnership(models.Model):
class Meta: class Meta:
default_permissions = ('change', 'delete') default_permissions = ('change', 'delete')
@ -145,7 +151,7 @@ class CharacterOwnership(models.Model):
owner_hash = models.CharField(max_length=28, unique=True) owner_hash = models.CharField(max_length=28, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='character_ownerships') user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='character_ownerships')
objects = CharacterOwnershipManager() objects: ClassVar[CharacterOwnershipManager] = CharacterOwnershipManager()
def __str__(self): def __str__(self):
return f"{self.user}: {self.character}" return f"{self.user}: {self.character}"

View File

@ -7,7 +7,7 @@
{% translate "Dashboard" %} {% translate "Dashboard" %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="d-flex justify-content-around align-self-center flex-wrap"> <div class="row">
{% for dash in views %} {% for dash in views %}
{{ dash | safe }} {{ dash | safe }}
{% endfor %} {% endfor %}

View File

@ -0,0 +1,10 @@
{% extends 'allianceauth/base.html' %}
{% block page_title %}Dashboard{% endblock page_title %}
{% block content %}
<div>
<h1>Dashboard Dummy</h1>
</div>
{% endblock %}

View File

@ -1,13 +1,11 @@
{% load i18n %} {% load i18n %}
<div class="col-12 col-xl-8 align-self-stretch p-2 ps-0"> <div id="aa-dashboard-panel-characters" class="col-12 col-xl-8 mb-3">
<div class="card"> <div class="card h-100">
<div class="card-body"> <div class="card-body">
<div class="d-flex align-items-center"> {% translate "Characters" as widget_title %}
<h4 class="ms-auto me-auto"> {% include "framework/dashboard/widget-title.html" with title=widget_title %}
{% translate "Characters" %}
</h4> <div>
</div>
<div class="card-body">
<div style="height: 300px; overflow-y:auto;"> <div style="height: 300px; overflow-y:auto;">
<div class="d-flex"> <div class="d-flex">
<a href="{% url 'authentication:add_character' %}" class="btn btn-primary flex-fill m-1" title="{% translate 'Add Character' %}"> <a href="{% url 'authentication:add_character' %}" class="btn btn-primary flex-fill m-1" title="{% translate 'Add Character' %}">

View File

@ -1,11 +1,13 @@
{% load i18n %} {% load i18n %}
<div class="col-12 col-xl-4 align-self-stretch py-2 ps-2"> <div id="aa-dashboard-panel-membership" class="col-12 col-xl-4 mb-3">
<div class="card h-100"> <div class="card h-100">
<div class="card-body"> <div class="card-body">
<h4 class="card-title text-center">{% translate "Membership" %}</h4> {% translate "Membership" as widget_title %}
<div class="card-body"> {% include "framework/dashboard/widget-title.html" with title=widget_title %}
<div>
<div style="height: 300px; overflow-y:auto;"> <div style="height: 300px; overflow-y:auto;">
<h6 class="text-center">{% translate "State:" %} {{ request.user.profile.state }}</h6> <h5 class="text-center">{% translate "State:" %} {{ request.user.profile.state }}</h5>
<table class="table"> <table class="table">
{% for group in groups %} {% for group in groups %}
<tr> <tr>

View File

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

View File

@ -1,23 +1,25 @@
{% load theme_tags %}
{% load static %} {% load static %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en" {% theme_html_tags %}>
<head> <head>
<!-- Required meta tags -->
<meta charset="utf-8"> <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="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content=""> <!-- End Required meta tags -->
<meta name="author" content="">
<!-- TODO Bundle all the site specific stuff up into its own template for easy overide -->
<meta property="og:title" content="{{ SITE_NAME }}">
<meta property="og:image" content="{{ SITE_URL }}{% static 'allianceauth/icons/apple-touch-icon.png' %}">
<meta property="og:description" content="Alliance Auth - An auth system for EVE Online to help in-game organizations manage online service access.">
<!-- Meta tags -->
{% include 'allianceauth/opengraph.html' %}
{% include 'allianceauth/icons.html' %} {% include 'allianceauth/icons.html' %}
<!-- Meta tags -->
<title>{% block title %}{% block page_title %}{% endblock page_title %} - {{ SITE_NAME }}{% endblock title %}</title> <title>{% block title %}{% block page_title %}{% endblock page_title %} - {{ SITE_NAME }}{% endblock title %}</title>
{% include 'bundles/bootstrap-css.html' %} {% theme_css %}
{% include 'bundles/fontawesome.html' %} {% include 'bundles/fontawesome.html' %}
{% include 'bundles/auth-framework-css.html' %}
{% block extra_include %} {% block extra_include %}
{% endblock %} {% endblock %}
@ -30,25 +32,23 @@
background-size: cover; background-size: cover;
} }
.panel-transparent { .card-login {
background: rgba(48 48 48 / 0.7); background: rgba(48 48 48 / 0.7);
color: #ffffff; color: rgb(255 255 255);
padding-bottom: 21px; padding-bottom: 21px;
} }
.panel-body {
}
#lang-select { #lang-select {
width: 40%; width: 40%;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
} }
{% block extra_style %} {% block extra_style %}
{% endblock %} {% endblock %}
</style> </style>
</head> </head>
<body> <body>
<div class="container" style="margin-top:150px;"> <div class="container" style="margin-top:150px;">
{% block content %} {% block content %}

View File

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

View File

@ -3,39 +3,41 @@
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<div class="col-md-4 col-md-offset-4"> <div class="row justify-content-center">
{% if messages %} <div class="col-md-6">
{% for message in messages %} {% if messages %}
<div class="alert alert-{{ message.level_tag}}">{{ message }}</div> {% for message in messages %}
{% endfor %} <div class="alert alert-{{ message.level_tag}}">{{ message }}</div>
{% endif %} {% endfor %}
{% endif %}
<div class="panel panel-default panel-transparent"> <div class="card card-login border-secondary p-3">
<div class="panel-body"> <div class="card-body">
<div class="col-md-12"> <div class="text-center">
{% block middle_box_content %} {% block middle_box_content %}
{% endblock %} {% endblock %}
</div>
</div> </div>
{% include 'public/lang_select.html' %}
<p class="text-center mt-3">
{% translate "For information on SSO, ESI and security read the CCP Dev Blog" %}<br>
<a class="text-reset" href="https://www.eveonline.com/news/view/introducing-esi" target="_blank" rel="noopener noreferrer">
{% translate "Introducing ESI - A New API For EVE Online" %}
</a>
</p>
<p class="text-center">
<a class="text-reset" href="https://developers.eveonline.com/authorized-apps" target="_blank" rel="noopener noreferrer">
{% translate "Manage ESI Applications" %}
</a>
</p>
</div> </div>
{% include 'public/lang_select.html' %}
<p class="text-center" style="margin-top: 2rem;">
{% translate "For information on SSO, ESI and security read the CCP Dev Blog" %}<br>
<a href="https://www.eveonline.com/article/introducing-esi" target="_blank" rel="noopener noreferrer">
{% translate "Introducing ESI - A New API For Eve Online" %}
</a>
</p>
<p class="text-center">
<a href="https://community.eveonline.com/support/third-party-applications/" target="_blank" rel="noopener noreferrer">
{% translate "Manage ESI Applications" %}
</a>
</p>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block extra_include %} {% block extra_include %}
{% include 'bundles/bootstrap-js.html' %} {% include 'bundles/bootstrap-js-bs5.html' %}
{% endblock %} {% endblock %}

View File

@ -1,27 +1,31 @@
{% extends 'public/base.html' %} {% extends 'public/base.html' %}
{% load bootstrap %} {% load django_bootstrap5 %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% translate "Registration" %}{% endblock %} {% block page_title %}{% translate "Registration" %}{% endblock %}
{% block extra_include %} {% block extra_include %}
{% include 'bundles/bootstrap-css.html' %} {% include 'bundles/bootstrap-css-bs5.html' %}
{% include 'bundles/fontawesome.html' %} {% include 'bundles/fontawesome.html' %}
{% include 'bundles/bootstrap-js.html' %} {% include 'bundles/bootstrap-js-bs5.html' %}
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="col-md-4 col-md-offset-4"> <div class="row justify-content-center">
<div class="panel panel-default panel-transparent"> <div class="col-md-6">
<div class="panel-body"> <div class="card card-login border-secondary p-3">
<form method="POST"> <div class="card-body">
{% csrf_token %} <form method="POST">
{{ form|bootstrap }} {% csrf_token %}
<button class="btn btn-lg btn-primary btn-block" type="submit">{% translate "Register" %}</button> {% bootstrap_form form %}
</form>
<button class="btn btn-primary btn-block" type="submit">{% translate "Register" %}</button>
</form>
{% include 'public/lang_select.html' %}
</div>
</div> </div>
</div> </div>
{% include 'public/lang_select.html' %}
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,5 +1,7 @@
{% extends 'public/middle_box.html' %} {% extends 'public/middle_box.html' %}
{% load i18n %} {% load i18n %}
{% block middle_box_content %} {% block middle_box_content %}
<div class="alert alert-danger">{% translate 'Invalid or expired activation link.' %}</div> <div class="alert alert-danger">{% translate 'Invalid or expired activation link.' %}</div>
{% endblock %} {% endblock %}

View File

@ -1,10 +1,12 @@
import json import json
import requests_mock
from unittest.mock import patch from unittest.mock import patch
from django.test import RequestFactory, TestCase from django.test import RequestFactory, TestCase
from allianceauth.authentication.views import task_counts from allianceauth.authentication.views import task_counts, esi_check
from allianceauth.tests.auth_utils import AuthUtils from allianceauth.tests.auth_utils import AuthUtils
from allianceauth.authentication.constants import ESI_ERROR_MESSAGE_OVERRIDES
MODULE_PATH = "allianceauth.authentication.views" MODULE_PATH = "allianceauth.authentication.views"
@ -21,6 +23,8 @@ class TestRunningTasksCount(TestCase):
super().setUpClass() super().setUpClass()
cls.factory = RequestFactory() cls.factory = RequestFactory()
cls.user = AuthUtils.create_user("bruce_wayne") cls.user = AuthUtils.create_user("bruce_wayne")
cls.user.is_superuser = True
cls.user.save()
def test_should_return_data( def test_should_return_data(
self, mock_active_tasks_count, mock_queued_tasks_count self, mock_active_tasks_count, mock_queued_tasks_count
@ -35,5 +39,164 @@ class TestRunningTasksCount(TestCase):
# then # then
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertDictEqual( self.assertDictEqual(
jsonresponse_to_dict(response), {"tasks_running": 2, "tasks_queued": 3} jsonresponse_to_dict(response), {
"tasks_running": 2, "tasks_queued": 3}
) )
def test_su_only(
self, mock_active_tasks_count, mock_queued_tasks_count
):
self.user.is_superuser = False
self.user.save()
self.user.refresh_from_db()
# given
mock_active_tasks_count.return_value = 2
mock_queued_tasks_count.return_value = 3
request = self.factory.get("/")
request.user = self.user
# when
response = task_counts(request)
# then
self.assertEqual(response.status_code, 302)
class TestEsiCheck(TestCase):
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
cls.factory = RequestFactory()
cls.user = AuthUtils.create_user("bruce_wayne")
cls.user.is_superuser = True
cls.user.save()
@requests_mock.Mocker()
def test_401_data_returns_200(
self, m
):
error_json = {
"error": "You have been banned from using ESI. Please contact Technical Support. (support@eveonline.com)"
}
status_code = 401
m.get(
"https://esi.evetech.net/latest/status/?datasource=tranquility",
text=json.dumps(error_json),
status_code=status_code
)
# given
request = self.factory.get("/")
request.user = self.user
# when
response = esi_check(request)
# then
self.assertEqual(response.status_code, 200)
self.assertDictEqual(
jsonresponse_to_dict(response), {
"status": status_code,
"data": error_json
}
)
@requests_mock.Mocker()
def test_504_data_returns_200(
self, m
):
error_json = {
"error": "Gateway timeout message",
"timeout": 5000
}
status_code = 504
m.get(
"https://esi.evetech.net/latest/status/?datasource=tranquility",
text=json.dumps(error_json),
status_code=status_code
)
# given
request = self.factory.get("/")
request.user = self.user
# when
response = esi_check(request)
# then
self.assertEqual(response.status_code, 200)
self.assertDictEqual(
jsonresponse_to_dict(response), {
"status": status_code,
"data": error_json
}
)
@requests_mock.Mocker()
def test_420_data_override(
self, m
):
error_json = {
"error": "message from CCP",
}
status_code = 420
m.get(
"https://esi.evetech.net/latest/status/?datasource=tranquility",
text=json.dumps(error_json),
status_code=status_code
)
# given
request = self.factory.get("/")
request.user = self.user
# when
response = esi_check(request)
# then
self.assertEqual(response.status_code, 200)
self.assertNotEqual(
jsonresponse_to_dict(response)["data"],
error_json
)
self.assertDictEqual(
jsonresponse_to_dict(response), {
"status": status_code,
"data": {
"error": ESI_ERROR_MESSAGE_OVERRIDES.get(status_code)
}
}
)
@requests_mock.Mocker()
def test_200_data_returns_200(
self, m
):
good_json = {
"players": 5,
"server_version": "69420",
"start_time": "2030-01-01T23:59:59Z"
}
status_code = 200
m.get(
"https://esi.evetech.net/latest/status/?datasource=tranquility",
text=json.dumps(good_json),
status_code=status_code
)
# given
request = self.factory.get("/")
request.user = self.user
# when
response = esi_check(request)
# then
self.assertEqual(response.status_code, 200)
self.assertDictEqual(
jsonresponse_to_dict(response), {
"status": status_code,
"data": good_json
}
)
def test_su_only(
self,
):
self.user.is_superuser = False
self.user.save()
self.user.refresh_from_db()
# given
request = self.factory.get("/")
request.user = self.user
# when
response = esi_check(request)
# then
self.assertEqual(response.status_code, 302)

View File

@ -38,5 +38,7 @@ urlpatterns = [
name='token_refresh' name='token_refresh'
), ),
path('dashboard/', views.dashboard, name='dashboard'), path('dashboard/', views.dashboard, name='dashboard'),
path('dashboard_bs3/', views.dashboard_bs3, name='dashboard_bs3'),
path('task-counts/', views.task_counts, name='task_counts'), path('task-counts/', views.task_counts, name='task_counts'),
path('esi-check/', views.esi_check, name='esi_check'),
] ]

View File

@ -1,6 +1,6 @@
import logging import logging
from allianceauth.hooks import get_hooks
import requests
from django_registration.backends.activation.views import ( from django_registration.backends.activation.views import (
REGISTRATION_SALT, ActivationView as BaseActivationView, REGISTRATION_SALT, ActivationView as BaseActivationView,
RegistrationView as BaseRegistrationView, RegistrationView as BaseRegistrationView,
@ -10,7 +10,7 @@ from django_registration.signals import user_registered
from django.conf import settings from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import authenticate, login from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required, user_passes_test
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core import signing from django.core import signing
from django.http import JsonResponse from django.http import JsonResponse
@ -23,14 +23,16 @@ from esi.decorators import token_required
from esi.models import Token from esi.models import Token
from allianceauth.eveonline.models import EveCharacter from allianceauth.eveonline.models import EveCharacter
from allianceauth.hooks import get_hooks
from .constants import ESI_ERROR_MESSAGE_OVERRIDES
from .core.celery_workers import active_tasks_count, queued_tasks_count from .core.celery_workers import active_tasks_count, queued_tasks_count
from .forms import RegistrationForm from .forms import RegistrationForm
from .models import CharacterOwnership from .models import CharacterOwnership
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS: if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True _has_auto_groups = True
from allianceauth.eveonline.autogroups.models import * from allianceauth.eveonline.autogroups.models import * # noqa: F401, F403
else: else:
_has_auto_groups = False _has_auto_groups = False
@ -54,7 +56,7 @@ def dashboard_groups(request):
context = { context = {
'groups': groups, 'groups': groups,
} }
return render_to_string('authentication/dashboard.groups.html', context=context, request=request) return render_to_string('authentication/dashboard_groups.html', context=context, request=request)
def dashboard_characters(request): def dashboard_characters(request):
@ -66,7 +68,7 @@ def dashboard_characters(request):
context = { context = {
'characters': characters 'characters': characters
} }
return render_to_string('authentication/dashboard.characters.html', context=context, request=request) return render_to_string('authentication/dashboard_characters.html', context=context, request=request)
def dashboard_admin(request): def dashboard_admin(request):
@ -76,6 +78,13 @@ def dashboard_admin(request):
return "" return ""
def dashboard_esi_check(request):
if request.user.is_superuser:
return render_to_string('allianceauth/admin-status/esi_check.html', request=request)
else:
return ""
@login_required @login_required
def dashboard(request): def dashboard(request):
_dash_items = list() _dash_items = list()
@ -135,23 +144,30 @@ def token_refresh(request, token_id=None):
@login_required @login_required
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES) @token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
def main_character_change(request, token): def main_character_change(request, token):
logger.debug(f"main_character_change called by user {request.user} for character {token.character_name}") logger.debug(
f"main_character_change called by user {request.user} for character {token.character_name}")
try: try:
co = CharacterOwnership.objects.get(character__character_id=token.character_id, user=request.user) co = CharacterOwnership.objects.get(
character__character_id=token.character_id, user=request.user)
except CharacterOwnership.DoesNotExist: except CharacterOwnership.DoesNotExist:
if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists(): if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists():
co = CharacterOwnership.objects.create_by_token(token) co = CharacterOwnership.objects.create_by_token(token)
else: else:
messages.error( messages.error(
request, request,
_('Cannot change main character to %(char)s: character owned by a different account.') % ({'char': token.character_name}) _('Cannot change main character to %(char)s: character owned by a different account.') % (
{'char': token.character_name})
) )
co = None co = None
if co: if co:
request.user.profile.main_character = co.character request.user.profile.main_character = co.character
request.user.profile.save(update_fields=['main_character']) request.user.profile.save(update_fields=['main_character'])
messages.success(request, _('Changed main character to %(char)s') % {"char": co.character}) messages.success(request, _('Changed main character to %s') % co.character)
logger.info('Changed user %(user)s main character to %(char)s' % ({'user': request.user, 'char': co.character})) logger.info(
'Changed user {user} main character to {char}'.format(
user=request.user, char=co.character
)
)
return redirect("authentication:dashboard") return redirect("authentication:dashboard")
@ -159,9 +175,11 @@ def main_character_change(request, token):
def add_character(request, token): def add_character(request, token):
if CharacterOwnership.objects.filter(character__character_id=token.character_id).filter( if CharacterOwnership.objects.filter(character__character_id=token.character_id).filter(
owner_hash=token.character_owner_hash).filter(user=request.user).exists(): owner_hash=token.character_owner_hash).filter(user=request.user).exists():
messages.success(request, _('Added %(name)s to your account.' % ({'name': token.character_name}))) messages.success(request, _(
'Added %(name)s to your account.' % ({'name': token.character_name})))
else: else:
messages.error(request, _('Failed to add %(name)s to your account: they already have an account.' % ({'name': token.character_name}))) messages.error(request, _('Failed to add %(name)s to your account: they already have an account.' % (
{'name': token.character_name})))
return redirect('authentication:dashboard') return redirect('authentication:dashboard')
@ -200,7 +218,15 @@ def sso_login(request, token):
request.session['registration_uid'] = user.pk request.session['registration_uid'] = user.pk
# Go to Step 2 # Go to Step 2
return redirect('registration_register') return redirect('registration_register')
messages.error(request, _('Unable to authenticate as the selected character.')) # Logging in with an alt is not allowed due to security concerns.
token.delete()
messages.error(
request,
_(
'Unable to authenticate as the selected character. '
'Please log in with the main character associated with this account.'
)
)
return redirect(settings.LOGIN_URL) return redirect(settings.LOGIN_URL)
@ -272,7 +298,8 @@ class RegistrationView(BaseRegistrationView):
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def register(self, form): def register(self, form):
user = User.objects.get(pk=self.request.session.get('registration_uid')) user = User.objects.get(
pk=self.request.session.get('registration_uid'))
user.email = form.cleaned_data['email'] user.email = form.cleaned_data['email']
user_registered.send(self.__class__, user=user, request=self.request) user_registered.send(self.__class__, user=user, request=self.request)
if getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True): if getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
@ -289,7 +316,8 @@ class RegistrationView(BaseRegistrationView):
def get_email_context(self, activation_key): def get_email_context(self, activation_key):
context = super().get_email_context(activation_key) context = super().get_email_context(activation_key)
context['url'] = context['site'].domain + reverse('registration_activate', args=[activation_key]) context['url'] = context['site'].domain + \
reverse('registration_activate', args=[activation_key])
return context return context
@ -322,20 +350,24 @@ class ActivationView(BaseActivationView):
def registration_complete(request): def registration_complete(request):
messages.success(request, _('Sent confirmation email. Please follow the link to confirm your email address.')) messages.success(request, _(
'Sent confirmation email. Please follow the link to confirm your email address.'))
return redirect('authentication:login') return redirect('authentication:login')
def activation_complete(request): def activation_complete(request):
messages.success(request, _('Confirmed your email address. Please login to continue.')) messages.success(request, _(
'Confirmed your email address. Please login to continue.'))
return redirect('authentication:dashboard') return redirect('authentication:dashboard')
def registration_closed(request): def registration_closed(request):
messages.error(request, _('Registration of new accounts is not allowed at this time.')) messages.error(request, _(
'Registration of new accounts is not allowed at this time.'))
return redirect('authentication:login') return redirect('authentication:login')
@user_passes_test(lambda u: u.is_superuser)
def task_counts(request) -> JsonResponse: def task_counts(request) -> JsonResponse:
"""Return task counts as JSON for an AJAX call.""" """Return task counts as JSON for an AJAX call."""
data = { data = {
@ -343,3 +375,31 @@ def task_counts(request) -> JsonResponse:
"tasks_queued": queued_tasks_count() "tasks_queued": queued_tasks_count()
} }
return JsonResponse(data) return JsonResponse(data)
def check_for_override_esi_error_message(response):
if response.status_code in ESI_ERROR_MESSAGE_OVERRIDES:
return {"error": ESI_ERROR_MESSAGE_OVERRIDES.get(response.status_code)}
else:
return response.json()
@user_passes_test(lambda u: u.is_superuser)
def esi_check(request) -> JsonResponse:
"""Return if ESI ok With error messages and codes as JSON"""
_r = requests.get("https://esi.evetech.net/latest/status/?datasource=tranquility")
data = {
"status": _r.status_code,
"data": check_for_override_esi_error_message(_r)
}
return JsonResponse(data)
@login_required
def dashboard_bs3(request):
"""Render dashboard view with BS3 theme.
This is an internal view used for testing BS3 backward compatibility in AA4 only.
"""
return render(request, 'authentication/dashboard_bs3.html')

View File

@ -12,13 +12,14 @@ class StartProject(BaseStartProject):
parser.add_argument('--python', help='The path to the python executable.') parser.add_argument('--python', help='The path to the python executable.')
parser.add_argument('--celery', help='The path to the celery executable.') parser.add_argument('--celery', help='The path to the celery executable.')
parser.add_argument('--gunicorn', help='The path to the gunicorn executable.') parser.add_argument('--gunicorn', help='The path to the gunicorn executable.')
parser.add_argument('--memmon', help='The path to the memmon executable.')
def create_project(parser, options, args): def create_project(parser, options, args):
# Validate args # Validate args
if len(args) < 2: if len(args) < 2:
parser.error("Please specify a name for your Alliance Auth installation.") parser.error("Please specify a name for your Alliance Auth installation.")
elif len(args) > 3: elif len(args) > 4:
parser.error("Too many arguments.") parser.error("Too many arguments.")
# First find the path to Alliance Auth # First find the path to Alliance Auth

562
allianceauth/checks.py Normal file
View File

@ -0,0 +1,562 @@
"""
Django system checks for Alliance Auth
"""
from typing import List
from django import db
from django.core.checks import CheckMessage, Error, register, Warning
from allianceauth.utils.cache import get_redis_client
from django.utils import timezone
from packaging.version import InvalidVersion, Version as Pep440Version
from celery import current_app
from django.conf import settings
from sqlite3.dbapi2 import sqlite_version_info
"""
A = System Packages
B = Configuration
"""
@register()
def django_settings(app_configs, **kwargs) -> List[CheckMessage]:
"""
Check that Django settings are correctly configured
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
errors: List[CheckMessage] = []
# Check for SITE_URL
if hasattr(settings, "SITE_URL"):
# Check if SITE_URL is empty
if settings.SITE_URL == "":
errors.append(
Error(
msg="'SITE_URL' is empty.",
hint="Make sure to set 'SITE_URL' to the URL of your Auth instance. (Without trailing slash)",
id="allianceauth.checks.B011",
)
)
# Check if SITE_URL has a trailing slash
elif settings.SITE_URL[-1] == "/":
errors.append(
Warning(
msg="'SITE_URL' has a trailing slash. This may lead to incorrect links being generated by Auth.",
hint="",
id="allianceauth.checks.B005",
)
)
# SITE_URL not found
else:
errors.append(
Error(
msg="No 'SITE_URL' found is settings. This may lead to incorrect links being generated by Auth or Errors in 3rd party modules.",
hint="",
id="allianceauth.checks.B006",
)
)
# Check for CSRF_TRUSTED_ORIGINS
if hasattr(settings, "CSRF_TRUSTED_ORIGINS") and hasattr(settings, "SITE_URL"):
# Check if SITE_URL is not in CSRF_TRUSTED_ORIGINS
if settings.SITE_URL not in settings.CSRF_TRUSTED_ORIGINS:
errors.append(
Warning(
msg="'SITE_URL' not found in 'CSRF_TRUSTED_ORIGINS'. Auth may not load pages correctly until this is rectified.",
hint="",
id="allianceauth.checks.B007",
)
)
# CSRF_TRUSTED_ORIGINS not found
else:
errors.append(
Error(
msg="No 'CSRF_TRUSTED_ORIGINS' found is settings, Auth may not load pages correctly until this is rectified",
hint="",
id="allianceauth.checks.B008",
)
)
# Check for ESI_USER_CONTACT_EMAIL
if hasattr(settings, "ESI_USER_CONTACT_EMAIL"):
# Check if ESI_USER_CONTACT_EMAIL is empty
if settings.ESI_USER_CONTACT_EMAIL == "":
errors.append(
Error(
msg="'ESI_USER_CONTACT_EMAIL' is empty. A valid email is required as maintainer contact for CCP.",
hint="",
id="allianceauth.checks.B009",
)
)
# ESI_USER_CONTACT_EMAIL not found
else:
errors.append(
Error(
msg="No 'ESI_USER_CONTACT_EMAIL' found is settings. A valid email is required as maintainer contact for CCP.",
hint="",
id="allianceauth.checks.B010",
)
)
return errors
@register()
def system_package_redis(app_configs, **kwargs) -> List[CheckMessage]:
"""
Check that Redis is a supported version
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
allianceauth_redis_install_link = "https://allianceauth.readthedocs.io/en/latest/installation/allianceauth.html#redis-and-other-tools"
errors: List[CheckMessage] = []
try:
redis_version = Pep440Version(get_redis_client().info()["redis_version"])
except InvalidVersion:
errors.append(Warning("Unable to confirm Redis Version"))
return errors
if (
redis_version.major == 7
and redis_version.minor == 2
and timezone.now()
> timezone.datetime(year=2025, month=8, day=31, tzinfo=timezone.utc)
):
errors.append(
Error(
msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
hint=allianceauth_redis_install_link,
id="allianceauth.checks.A001",
)
)
elif redis_version.major == 7 and redis_version.minor == 0:
errors.append(
Warning(
msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
hint=allianceauth_redis_install_link,
id="allianceauth.checks.A002",
)
)
elif redis_version.major == 6 and redis_version.minor == 2:
errors.append(
Warning(
msg=f"Redis {redis_version.public} in Security Support only, Updating Suggested",
hint=allianceauth_redis_install_link,
id="allianceauth.checks.A018",
)
)
elif redis_version.major in [6, 5]:
errors.append(
Error(
msg=f"Redis {redis_version.public} EOL",
hint=allianceauth_redis_install_link,
id="allianceauth.checks.A003",
)
)
return errors
@register()
def system_package_mysql(app_configs, **kwargs) -> List[CheckMessage]:
"""
Check that MySQL is a supported version
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
mysql_quick_guide_link = "https://dev.mysql.com/doc/mysql-apt-repo-quick-guide/en/"
errors: List[CheckMessage] = []
for connection in db.connections.all():
if connection.vendor == "mysql":
try:
mysql_version = Pep440Version(
".".join(str(i) for i in connection.mysql_version)
)
except InvalidVersion:
errors.append(Warning("Unable to confirm MySQL Version"))
return errors
# MySQL 8
if mysql_version.major == 8:
if mysql_version.minor == 4 and timezone.now() > timezone.datetime(
year=2032, month=4, day=30, tzinfo=timezone.utc
):
errors.append(
Error(
msg=f"MySQL {mysql_version.public} EOL",
hint=mysql_quick_guide_link,
id="allianceauth.checks.A004",
)
)
elif mysql_version.minor == 3:
errors.append(
Warning(
msg=f"MySQL {mysql_version.public} Non LTS",
hint=mysql_quick_guide_link,
id="allianceauth.checks.A005",
)
)
elif mysql_version.minor == 2:
errors.append(
Warning(
msg=f"MySQL {mysql_version.public} Non LTS",
hint=mysql_quick_guide_link,
id="allianceauth.checks.A006",
)
)
elif mysql_version.minor == 1:
errors.append(
Error(
msg=f"MySQL {mysql_version.public} EOL",
hint=mysql_quick_guide_link,
id="allianceauth.checks.A007",
)
)
elif mysql_version.minor == 0 and timezone.now() > timezone.datetime(
year=2026, month=4, day=30, tzinfo=timezone.utc
):
errors.append(
Error(
msg=f"MySQL {mysql_version.public} EOL",
hint=mysql_quick_guide_link,
id="allianceauth.checks.A008",
)
)
# MySQL below 8
# This will also catch Mariadb 5.x
elif mysql_version.major < 8:
errors.append(
Error(
msg=f"MySQL or MariaDB {mysql_version.public} EOL",
hint=mysql_quick_guide_link,
id="allianceauth.checks.A009",
)
)
return errors
@register()
def system_package_mariadb(app_configs, **kwargs) -> List[CheckMessage]:
"""
Check that MariaDB is a supported version
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
mariadb_download_link = "https://mariadb.org/download/?t=repo-config"
errors: List[CheckMessage] = []
for connection in db.connections.all():
# TODO: Find a way to determine MySQL vs. MariaDB
if connection.vendor == "mysql":
try:
mariadb_version = Pep440Version(
".".join(str(i) for i in connection.mysql_version)
)
except InvalidVersion:
errors.append(Warning("Unable to confirm MariaDB Version"))
return errors
# MariaDB 11
if mariadb_version.major == 11:
if mariadb_version.minor == 4 and timezone.now() > timezone.datetime(
year=2029, month=5, day=19, tzinfo=timezone.utc
):
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A010",
)
)
elif mariadb_version.minor == 2:
errors.append(
Warning(
msg=f"MariaDB {mariadb_version.public} Non LTS",
hint=mariadb_download_link,
id="allianceauth.checks.A018",
)
)
if timezone.now() > timezone.datetime(
year=2024, month=11, day=21, tzinfo=timezone.utc
):
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A011",
)
)
elif mariadb_version.minor == 1:
errors.append(
Warning(
msg=f"MariaDB {mariadb_version.public} Non LTS",
hint=mariadb_download_link,
id="allianceauth.checks.A019",
)
)
if timezone.now() > timezone.datetime(
year=2024, month=8, day=21, tzinfo=timezone.utc
):
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A012",
)
)
# Demote versions down here once EOL
elif mariadb_version.minor in [0, 3]:
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A013",
)
)
# MariaDB 10
elif mariadb_version.major == 10:
if mariadb_version.minor == 11 and timezone.now() > timezone.datetime(
year=2028, month=2, day=10, tzinfo=timezone.utc
):
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A014",
)
)
elif mariadb_version.minor == 6 and timezone.now() > timezone.datetime(
year=2026, month=7, day=6, tzinfo=timezone.utc
):
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A0015",
)
)
elif mariadb_version.minor == 5 and timezone.now() > timezone.datetime(
year=2025, month=6, day=24, tzinfo=timezone.utc
):
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A016",
)
)
# Demote versions down here once EOL
elif mariadb_version.minor in [0, 1, 2, 3, 4, 7, 9, 10]:
errors.append(
Error(
msg=f"MariaDB {mariadb_version.public} EOL",
hint=mariadb_download_link,
id="allianceauth.checks.A017",
)
)
return errors
@register()
def system_package_sqlite(app_configs, **kwargs) -> List[CheckMessage]:
"""
Check that SQLite is a supported version
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
errors: List[CheckMessage] = []
for connection in db.connections.all():
if connection.vendor == "sqlite":
try:
sqlite_version = Pep440Version(
".".join(str(i) for i in sqlite_version_info)
)
except InvalidVersion:
errors.append(Warning("Unable to confirm SQLite Version"))
return errors
if sqlite_version.major == 3 and sqlite_version.minor < 27:
errors.append(
Error(
msg=f"SQLite {sqlite_version.public} Unsupported by Django",
hint="https://pkgs.org/download/sqlite3",
id="allianceauth.checks.A020",
)
)
return errors
@register()
def sql_settings(app_configs, **kwargs) -> List[CheckMessage]:
"""
Check that SQL settings are correctly configured
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
errors: List[CheckMessage] = []
for connection in db.connections.all():
if connection.vendor == "mysql":
try:
if connection.settings_dict["OPTIONS"]["charset"] != "utf8mb4":
errors.append(
Error(
msg=f"SQL Charset is not set to utf8mb4 DB: {connection.alias}",
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
id="allianceauth.checks.B001",
)
)
except KeyError:
errors.append(
Error(
msg=f"SQL Charset is not set to utf8mb4 DB: {connection.alias}",
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
id="allianceauth.checks.B001",
)
)
# This hasn't actually been set on AA yet
# try:
# if (
# connection.settings_dict["OPTIONS"]["collation"]
# != "utf8mb4_unicode_ci"
# ):
# errors.append(
# Error(
# msg=f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}",
# hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
# id="allianceauth.checks.B001",
# )
# )
# except KeyError:
# errors.append(
# Error(
# msg=f"SQL Collation is not set to utf8mb4_unicode_ci DB:{connection.alias}",
# hint="https://gitlab.com/allianceauth/allianceauth/-/commit/89be2456fb2d741b86417e889da9b6129525bec8",
# id="allianceauth.checks.B001",
# )
# )
# if connection.vendor == "sqlite":
return errors
@register()
def celery_settings(app_configs, **kwargs) -> List[CheckMessage]:
"""
Check that Celery settings are correctly configured
:param app_configs:
:type app_configs:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
errors: List[CheckMessage] = []
try:
if current_app.conf.broker_transport_options != {
"priority_steps": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
"queue_order_strategy": "priority",
}:
errors.append(
Error(
msg="Celery Priorities are not set correctly",
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc",
id="allianceauth.checks.B003",
)
)
except KeyError:
errors.append(
Error(
msg="Celery Priorities are not set",
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/8861ec0a61790eca0261f1adc1cc04ca5f243cbc",
id="allianceauth.checks.B003",
)
)
try:
if not current_app.conf.broker_connection_retry_on_startup:
errors.append(
Error(
msg="Celery broker_connection_retry_on_startup not set correctly",
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280",
id="allianceauth.checks.B004",
)
)
except KeyError:
errors.append(
Error(
msg="Celery broker_connection_retry_on_startup not set",
hint="https://gitlab.com/allianceauth/allianceauth/-/commit/380c41400b535447839e5552df2410af35a75280",
id="allianceauth.checks.B004",
)
)
return errors
# IDEAS
# Any other celery things weve manually changed over the years
# I'd be happy to add Community App checks, old versions the owners dont want to support etc.
# Check Default Collation on DB
# Check Charset Collation on all tables

View File

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

View File

@ -10,7 +10,7 @@ class CorpStats(MenuItemHook):
MenuItemHook.__init__( MenuItemHook.__init__(
self, self,
_('Corporation Stats'), _('Corporation Stats'),
'fas fa-share-alt fa-fw', 'fa-solid fa-share-nodes',
'corputils:view', 'corputils:view',
navactive=['corputils:'] navactive=['corputils:']
) )

View File

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

View File

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

View File

@ -1,178 +1,234 @@
{% extends 'corputils/base.html' %} {% extends 'corputils/base.html' %}
{% load aa_i18n %}
{% load i18n %} {% load i18n %}
{% load humanize %} {% load humanize %}
{% block member_data %} {% block member_data %}
{% if corpstats %} {% if corpstats %}
<div> <div>
<table class="table"> <table class="table text-center">
<tr> <tr>
<td class="text-center col-lg-6{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}"> <td>
<img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}" alt="{{ corpstats.corp.corporation_name }}"> <img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}" alt="{{ corpstats.corp.corporation_name }}">
</td> </td>
{% if corpstats.corp.alliance %} {% if corpstats.corp.alliance %}
<td class="text-center col-lg-6"> <td>
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}" alt="{{ corpstats.corp.alliance.alliance_name }}"> <img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}" alt="{{ corpstats.corp.alliance.alliance_name }}">
</td> </td>
{% endif %} {% endif %}
</tr> </tr>
<tr> <tr>
<td class="text-center"><h4>{{ corpstats.corp.corporation_name }}</h4></td> <td><p class="h4">{{ corpstats.corp.corporation_name }}</p></td>
{% if corpstats.corp.alliance %} {% if corpstats.corp.alliance %}
<td class="text-center"><h4>{{ corpstats.corp.alliance.alliance_name }}</h4></td> <td><p class="h4">{{ corpstats.corp.alliance.alliance_name }}</p></td>
{% endif %} {% endif %}
</tr> </tr>
</table> </table>
</div> </div>
<div> <div class="card card-default mt-4">
<div class="panel panel-default"> <div class="card-header clearfix" role="tablist">
<div class="panel-heading"> <ul class="nav nav-pills float-start">
<ul class="nav nav-pills pull-left"> <li class="nav-item" role="presentation">
<li class="active"><a href="#mains" data-toggle="pill">{% translate 'Mains' %} ({{ total_mains }})</a></li> <a
<li><a href="#members" data-toggle="pill">{% translate 'Members' %} ({{ corpstats.member_count }})</a></li> class="nav-link active"
<li><a href="#unregistered" data-toggle="pill">{% translate 'Unregistered' %} ({{ unregistered.count }})</a></li> id="mains"
</ul> data-bs-toggle="tab"
<div class="pull-right hidden-xs"> href="#tab-mains"
{% translate "Last update:" %} {{ corpstats.last_update|naturaltime }}&nbsp; role="tab"
<a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now"> aria-controls="tab-mains"
<span class="glyphicon glyphicon-refresh"></span> aria-selected="true"
>
{% translate 'Mains' %} ({{ total_mains }})
</a> </a>
</div> </li>
<div class="clearfix"></div>
</div>
<div class="panel-body"> <li class="nav-item" role="presentation">
<div class="tab-content"> <a
<div class="tab-pane fade in active" id="mains"> class="nav-link"
{% if mains %} id="members"
<div class="table-responsive"> data-bs-toggle="tab"
<table class="table table-hover" id="table-mains"> href="#tab-members"
<thead> role="tab"
aria-controls="tab-members"
aria-selected="false"
>
{% translate 'Members' %} ({{ corpstats.member_count }})
</a>
</li>
<li class="nav-item">
<a
class="nav-link"
id="unregistered"
data-bs-toggle="tab"
href="#tab-unregistered"
role="tab"
aria-controls="tab-unregistered"
aria-selected="false"
>
{% translate 'Unregistered' %} ({{ unregistered.count }})
</a>
</li>
</ul>
<div class="float-end d-none d-sm-block">
{% translate "Last update:" %} {{ corpstats.last_update|naturaltime }}
<a
class="btn btn-success btn-sm ms-2"
type="button"
href="{% url 'corputils:update' corpstats.corp.corporation_id %}"
title="{% translate 'Update Now' %}"
>
<i class="fa-solid fa-rotate"></i>
</a>
</div>
</div>
<div class="card-body">
<div class="tab-content">
<div class="tab-pane fade show active" id="tab-mains" role="tabpanel" aria-labelledby="tab-mains">
{% if mains %}
<div class="table-responsive">
<table class="table table-hover" id="table-mains">
<thead>
<tr>
<th>{% translate "Main character" %}</th>
<th>{% translate "Registered characters" %}</th>
</tr>
</thead>
<tbody>
{% for id, main in mains.items %}
<tr> <tr>
<th style="height:1em;"><!-- Must have text or height to prevent clipping --></th> <td class="text-center" style="vertical-align: middle;">
<th></th> <div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
</tr> <img src="{{ main.main.portrait_url_64 }}" class="img-circle" alt="{{ main.main }}">
</thead> <div class="caption">
<tbody> {{ main.main }}
{% for id, main in mains.items %}
<tr>
<td class="text-center" style="vertical-align:middle">
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
<img src="{{ main.main.portrait_url_64 }}" class="img-circle" alt="{{ main.main }}">
<div class="caption text-center">
{{ main.main }}
</div>
</div> </div>
</td> </div>
<td> </td>
<table class="table table-hover">
{% for alt in main.alts %} <td>
{% if forloop.first %} <table class="table table-hover">
<tr> {% for alt in main.alts|dictsort:"character_name" %}
<th></th> {% if forloop.first %}
<th class="text-center">{% translate "Character" %}</th>
<th class="text-center">{% translate "Corporation" %}</th>
<th class="text-center">{% translate "Alliance" %}</th>
<th class="text-center"></th>
</tr>
{% endif %}
<tr> <tr>
<td class="text-center" style="width:5%"> <th></th>
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;"> <th>{% translate "Character" %}</th>
<img src="{{ alt.portrait_url_32 }}" class="img-circle" alt="{{ alt.character_name }}"> <th>{% translate "Corporation" %}</th>
</div> <th>{% translate "Alliance" %}</th>
</td> <th></th>
<td class="text-center" style="width:30%">{{ alt.character_name }}</td>
<td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
<td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
<td class="text-center" style="width:5%">
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="badge bg-danger" target="_blank">
{% translate "Killboard" %}
</a>
</td>
</tr> </tr>
{% endfor %} {% endif %}
</table>
</td> <tr>
</tr> <td style="width: 5%;">
{% endfor %} <div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
</tbody> <img src="{{ alt.portrait_url_32 }}" class="img-circle" alt="{{ alt.character_name }}">
</table> </div>
</div> </td>
{% endif %} <td style="width: 30%;">{{ alt.character_name }}</td>
</div> <td style="width: 30%;">{{ alt.corporation_name }}</td>
<div class="tab-pane fade" id="members"> <td style="width: 30%;">{{ alt.alliance_name|default_if_none:"" }}</td>
{% if members %} <td style="width: 5%;">
<div class="table-responsive"> <a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="badge text-bg-danger" target="_blank">
<table class="table table-hover" id="table-members"> {% translate "Killboard" %}
<thead> </a>
<tr> </td>
<th></th> </tr>
<th class="text-center">{% translate "Character" %}</th> {% endfor %}
<th class="text-center"></th> </table>
<th class="text-center">{% translate "Main Character" %}</th> </td>
<th class="text-center">{% translate "Main Corporation" %}</th>
<th class="text-center">{% translate "Main Alliance" %}</th>
</tr> </tr>
</thead> {% endfor %}
<tbody> </tbody>
{% for member in members %} </table>
<tr> </div>
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td> {% endif %}
<td class="text-center">{{ member }}</td> </div>
<td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a> <div class="tab-pane fade" id="tab-members" role="tabpanel" aria-labelledby="tab-members">
</td> {% if members %}
<td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td> <div class="table-responsive">
<td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td> <table class="table table-hover" id="table-members">
<td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td> <thead>
</tr> <tr>
{% endfor %} <th></th>
{% for member in unregistered %} <th>{% translate "Character" %}</th>
<tr class="danger"> <th></th>
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td> <th>{% translate "Main Character" %}</th>
<td class="text-center">{{ member.character_name }}</td> <th>{% translate "Main Corporation" %}</th>
<td class="text-center"> <th>{% translate "Main Alliance" %}</th>
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank">{% translate "Killboard" %}</a> </tr>
</td> </thead>
<td class="text-center"></td>
<td class="text-center"></td> <tbody>
<td class="text-center"></td> {% for member in members %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<div class="tab-pane fade" id="unregistered">
{% if unregistered %}
<div class="table-responsive">
<table class="table table-hover" id="table-unregistered">
<thead>
<tr> <tr>
<th></th> <td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member }}"></td>
<th class="text-center">{% translate "Character" %}</th> <td>{{ member }}</td>
<th class="text-center"></th> <td>
<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>
<td>{{ member.character_ownership.user.profile.main_character.alliance_name|default_if_none:"" }}</td>
</tr> </tr>
</thead> {% endfor %}
<tbody>
{% for member in unregistered %} {% for member in unregistered %}
<tr class="danger"> <tr class="table-danger">
<td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td> <td><img src="{{ member.portrait_url }}" class="img-circle" alt="{{ member.character_name }}"></td>
<td class="text-center">{{ member.character_name }}</td> <td>{{ member.character_name }}</td>
<td class="text-center"> <td>
<a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge bg-danger" target="_blank"> <a href="https://zkillboard.com/character/{{ member.character_id }}/" class="badge text-bg-danger" target="_blank">{% translate "Killboard" %}</a>
{% translate "Killboard" %} </td>
</a> <td></td>
</td> <td></td>
</tr> <td></td>
{% endfor %} </tr>
</tbody> {% endfor %}
</table> </tbody>
</div> </table>
{% endif %} </div>
</div> {% endif %}
</div>
<div class="tab-pane fade" id="tab-unregistered" role="tabpanel" aria-labelledby="tab-unregistered">
{% if unregistered %}
<div class="table-responsive">
<table class="table table-hover" id="table-unregistered">
<thead>
<tr>
<th></th>
<th>{% translate "Character" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for member in unregistered %}
<tr class="table-danger">
<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 text-bg-danger" target="_blank">
{% translate "Killboard" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@ -181,40 +237,46 @@
{% endblock %} {% endblock %}
{% block extra_javascript %} {% block extra_javascript %}
{% include 'bundles/datatables-js.html' %} {% include 'bundles/datatables-js-bs5.html' %}
{% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
<script>
$(document).ready(() => {
$('#table-mains').DataTable({
"language": {"url": '{{ DT_LANG_PATH }}'},
"columnDefs": [
{ "sortable": false, "targets": [1] },
],
"stateSave": true,
"stateDuration": 0
});
$('#table-members').DataTable({
"language": {"url": '{{ DT_LANG_PATH }}'},
"columnDefs": [
{ "searchable": false, "targets": [0, 2] },
{ "sortable": false, "targets": [0, 2] },
],
"order": [[ 1, "asc" ]],
"stateSave": true,
"stateDuration": 0
});
$('#table-unregistered').DataTable({
"language": {"url": '{{ DT_LANG_PATH }}'},
"columnDefs": [
{ "searchable": false, "targets": [0, 2] },
{ "sortable": false, "targets": [0, 2] },
],
"order": [[ 1, "asc" ]],
"stateSave": true,
"stateDuration": 0
});
});
</script>
{% endblock %} {% endblock %}
{% block extra_css %} {% block extra_css %}
{% include 'bundles/datatables-css.html' %} {% include 'bundles/datatables-css-bs5.html' %}
{% endblock %}
{% block extra_script %}
$(document).ready(function(){
$('#table-mains').DataTable({
"columnDefs": [
{ "sortable": false, "targets": [1] },
],
"stateSave": true,
"stateDuration": 0
});
$('#table-members').DataTable({
"columnDefs": [
{ "searchable": false, "targets": [0, 2] },
{ "sortable": false, "targets": [0, 2] },
],
"order": [[ 1, "asc" ]],
"stateSave": true,
"stateDuration": 0
});
$('#table-unregistered').DataTable({
"columnDefs": [
{ "searchable": false, "targets": [0, 2] },
{ "sortable": false, "targets": [0, 2] },
],
"order": [[ 1, "asc" ]],
"stateSave": true,
"stateDuration": 0
});
});
{% endblock %} {% endblock %}

View File

@ -1,33 +1,37 @@
{% extends "corputils/base.html" %} {% extends "corputils/base.html" %}
{% load aa_i18n %}
{% load i18n %} {% load i18n %}
{% block member_data %} {% block member_data %}
<div class="panel panel-default"> <div class="card card-default">
<div class="panel-heading clearfix"> <div class="card-header clearfix">
<div class="panel-title pull-left">{% translate "Search Results" %}</div> <div class="card-title">{% translate "Search Results" %}</div>
</div> </div>
<div class="panel-body">
<div class="card-body mt-2">
<table class="table table-hover" id="table-search"> <table class="table table-hover" id="table-search">
<thead> <thead>
<tr> <tr>
<th class="text-center"></th> <th></th>
<th class="text-center">{% translate "Character" %}</th> <th>{% translate "Character" %}</th>
<th class="text-center">{% translate "Corporation" %}</th> <th>{% translate "Corporation" %}</th>
<th class="text-center">{% translate "zKillboard" %}</th> <th>{% translate "zKillboard" %}</th>
<th class="text-center">{% translate "Main Character" %}</th> <th>{% translate "Main Character" %}</th>
<th class="text-center">{% translate "Main Corporation" %}</th> <th>{% translate "Main Corporation" %}</th>
<th class="text-center">{% translate "Main Alliance" %}</th> <th>{% translate "Main Alliance" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for result in results %} {% for result in results %}
<tr {% if not result.1.registered %}class="danger"{% endif %}> <tr {% if not result.1.registered %}class="danger"{% endif %}>
<td class="text-center"><img src="{{ result.1.portrait_url }}" class="img-circle" alt="{{ result.1.character_name }}"></td> <td><img src="{{ result.1.portrait_url }}" class="img-circle" alt="{{ result.1.character_name }}"></td>
<td class="text-center">{{ result.1.character_name }}</td> <td>{{ result.1.character_name }}</td>
<td class="text-center">{{ result.0.corp.corporation_name }}</td> <td >{{ result.0.corp.corporation_name }}</td>
<td class="text-center"><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="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 class="text-center">{{ result.1.main_character.character_name }}</td> <td>{{ result.1.main_character.character_name }}</td>
<td class="text-center">{{ result.1.main_character.corporation_name }}</td> <td>{{ result.1.main_character.corporation_name }}</td>
<td class="text-center">{{ result.1.main_character.alliance_name }}</td> <td>{{ result.1.main_character.alliance_name }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -35,17 +39,23 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block extra_javascript %} {% block extra_javascript %}
{% include 'bundles/datatables-js.html' %} {% include 'bundles/datatables-js-bs5.html' %}
{% endblock %}
{% block extra_css %} {% get_datatables_language_static LANGUAGE_CODE as DT_LANG_PATH %}
{% include 'bundles/datatables-css.html' %}
{% endblock %} <script>
{% block extra_script %} $(document).ready(() => {
$(document).ready(function(){ $('#table-search').DataTable({
$('#table-search').DataTable({ "language": {"url": '{{ DT_LANG_PATH }}'},
"stateSave": true, "stateSave": true,
"stateDuration": 0 "stateDuration": 0
});
}); });
}); </script>
{% endblock %}
{% block extra_css %}
{% include 'bundles/datatables-css-bs5.html' %}
{% endblock %} {% endblock %}

View File

@ -0,0 +1,3 @@
"""
Alliance Auth Crontab Utilities
"""

View File

@ -0,0 +1,16 @@
"""
Crontab App Config
"""
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class CrontabConfig(AppConfig):
"""
Crontab App Config
"""
name = "allianceauth.crontab"
label = "crontab"
verbose_name = _("Crontab")

View File

@ -0,0 +1,29 @@
# Generated by Django 4.2.16 on 2025-01-20 06:16
import allianceauth.crontab.models
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='CronOffset',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('minute', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Minute Offset')),
('hour', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Hour Offset')),
('day_of_month', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Day of Month Offset')),
('month_of_year', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Month of Year Offset')),
('day_of_week', models.FloatField(default=allianceauth.crontab.models.random_default, verbose_name='Day of Week Offset')),
],
options={
'verbose_name': 'Cron Offsets',
},
),
]

View File

@ -0,0 +1,23 @@
from random import random
from django.db import models
from django.utils.translation import gettext_lazy as _
from solo.models import SingletonModel
def random_default() -> float:
return random()
class CronOffset(SingletonModel):
minute = models.FloatField(_("Minute Offset"), default=random_default)
hour = models.FloatField(_("Hour Offset"), default=random_default)
day_of_month = models.FloatField(_("Day of Month Offset"), default=random_default)
month_of_year = models.FloatField(_("Month of Year Offset"), default=random_default)
day_of_week = models.FloatField(_("Day of Week Offset"), default=random_default)
def __str__(self) -> str:
return "Cron Offsets"
class Meta:
verbose_name = "Cron Offsets"

View File

@ -0,0 +1,70 @@
from django.core.exceptions import ObjectDoesNotExist
from django_celery_beat.schedulers import (
DatabaseScheduler
)
from django_celery_beat.models import CrontabSchedule
from django.db.utils import OperationalError, ProgrammingError
from celery import schedules
from celery.utils.log import get_logger
from allianceauth.crontab.models import CronOffset
from allianceauth.crontab.utils import offset_cron
logger = get_logger(__name__)
class OffsetDatabaseScheduler(DatabaseScheduler):
"""
Customization of Django Celery Beat, Database Scheduler
Takes the Celery Schedule from local.py and applies our AA Framework Cron Offset, if apply_offset is true
Otherwise it passes it through as normal
"""
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
def update_from_dict(self, mapping):
s = {}
try:
cron_offset = CronOffset.get_solo()
except (OperationalError, ProgrammingError, ObjectDoesNotExist) as exc:
# This is just incase we haven't migrated yet or something
logger.warning(
"OffsetDatabaseScheduler: Could not fetch CronOffset (%r). "
"Defering to DatabaseScheduler",
exc
)
return super().update_from_dict(mapping)
for name, entry_fields in mapping.items():
try:
apply_offset = entry_fields.pop("apply_offset", False) # Ensure this pops before django tries to save to ORM
entry = self.Entry.from_entry(name, app=self.app, **entry_fields)
if apply_offset:
entry_fields.update({"apply_offset": apply_offset}) # Reapply this as its gets pulled from config inconsistently.
schedule_obj = entry.schedule
if isinstance(schedule_obj, schedules.crontab):
offset_cs = CrontabSchedule.from_schedule(offset_cron(schedule_obj))
offset_cs, created = CrontabSchedule.objects.get_or_create(
minute=offset_cs.minute,
hour=offset_cs.hour,
day_of_month=offset_cs.day_of_month,
month_of_year=offset_cs.month_of_year,
day_of_week=offset_cs.day_of_week,
timezone=offset_cs.timezone,
)
entry.schedule = offset_cron(schedule_obj) # This gets passed into Celery Beats Memory, important to keep it in sync with the model/DB
entry.model.crontab = offset_cs
entry.model.save()
logger.debug(f"Offset applied for '{name}' due to 'apply_offset' = True.")
if entry.model.enabled:
s[name] = entry
except Exception as e:
logger.exception("Error updating schedule for %s: %r", name, e)
self.schedule.update(s)

View File

View File

@ -0,0 +1,63 @@
from unittest.mock import patch
from django.test import TestCase
from allianceauth.crontab.models import CronOffset
class CronOffsetModelTest(TestCase):
def test_cron_offset_is_singleton(self):
"""
Test that CronOffset is indeed a singleton and that
multiple calls to get_solo() return the same instance.
"""
offset1 = CronOffset.get_solo()
offset2 = CronOffset.get_solo()
# They should be the exact same object in memory
self.assertEqual(offset1.pk, offset2.pk)
def test_default_values_random(self):
"""
Test that the default values are set via random_default() when
no explicit value is provided. We'll patch 'random.random' to
produce predictable output.
"""
with patch('allianceauth.crontab.models.random', return_value=0.1234):
# Force creation of a new CronOffset by clearing the existing one
CronOffset.objects.all().delete()
offset = CronOffset.get_solo() # This triggers creation
# All fields should be 0.1234, because we patched random()
self.assertAlmostEqual(offset.minute, 0.1234)
self.assertAlmostEqual(offset.hour, 0.1234)
self.assertAlmostEqual(offset.day_of_month, 0.1234)
self.assertAlmostEqual(offset.month_of_year, 0.1234)
self.assertAlmostEqual(offset.day_of_week, 0.1234)
def test_update_offset_values(self):
"""
Test that we can update the offsets and retrieve them.
"""
offset = CronOffset.get_solo()
offset.minute = 0.5
offset.hour = 0.25
offset.day_of_month = 0.75
offset.month_of_year = 0.99
offset.day_of_week = 0.33
offset.save()
# Retrieve again to ensure changes persist
saved_offset = CronOffset.get_solo()
self.assertEqual(saved_offset.minute, 0.5)
self.assertEqual(saved_offset.hour, 0.25)
self.assertEqual(saved_offset.day_of_month, 0.75)
self.assertEqual(saved_offset.month_of_year, 0.99)
self.assertEqual(saved_offset.day_of_week, 0.33)
def test_str_representation(self):
"""
Verify the __str__ method returns 'Cron Offsets'.
"""
offset = CronOffset.get_solo()
self.assertEqual(str(offset), "Cron Offsets")

View File

@ -0,0 +1,80 @@
# myapp/tests/test_tasks.py
import logging
from unittest.mock import patch
from django.test import TestCase
from django.db import ProgrammingError
from celery.schedules import crontab
from allianceauth.crontab.utils import offset_cron
from allianceauth.crontab.models import CronOffset
logger = logging.getLogger(__name__)
class TestOffsetCron(TestCase):
def test_offset_cron_normal(self):
"""
Test that offset_cron modifies the minute/hour fields
based on the CronOffset values when everything is normal.
"""
# We'll create a mock CronOffset instance
mock_offset = CronOffset(minute=0.5, hour=0.5)
# Our initial crontab schedule
original_schedule = crontab(
minute=[0, 5, 55],
hour=[0, 3, 23],
day_of_month='*',
month_of_year='*',
day_of_week='*'
)
# Patch CronOffset.get_solo to return our mock offset
with patch('allianceauth.crontab.models.CronOffset.get_solo', return_value=mock_offset):
new_schedule = offset_cron(original_schedule)
# Check the new minute/hour
# minute 0 -> 0 + round(60 * 0.5) = 30 % 60 = 30
# minute 5 -> 5 + 30 = 35 % 60 = 35
# minute 55 -> 55 + 30 = 85 % 60 = 25 --> sorted => 25,30,35
self.assertEqual(new_schedule._orig_minute, '25,30,35')
# hour 0 -> 0 + round(24 * 0.5) = 12 % 24 = 12
# hour 3 -> 3 + 12 = 15 % 24 = 15
# hour 23 -> 23 + 12 = 35 % 24 = 11 --> sorted => 11,12,15
self.assertEqual(new_schedule._orig_hour, '11,12,15')
# Check that other fields are unchanged
self.assertEqual(new_schedule._orig_day_of_month, '*')
self.assertEqual(new_schedule._orig_month_of_year, '*')
self.assertEqual(new_schedule._orig_day_of_week, '*')
def test_offset_cron_programming_error(self):
"""
Test that if a ProgrammingError is raised (e.g. before migrations),
offset_cron just returns the original schedule.
"""
original_schedule = crontab(minute=[0, 15, 30], hour=[1, 2, 3])
# Force get_solo to raise ProgrammingError
with patch('allianceauth.crontab.models.CronOffset.get_solo', side_effect=ProgrammingError()):
new_schedule = offset_cron(original_schedule)
# Should return the original schedule unchanged
self.assertEqual(new_schedule, original_schedule)
def test_offset_cron_unexpected_exception(self):
"""
Test that if any other exception is raised, offset_cron
also returns the original schedule, and logs the error.
"""
original_schedule = crontab(minute='0', hour='0')
# Force get_solo to raise a generic Exception
with patch('allianceauth.crontab.models.CronOffset.get_solo', side_effect=Exception("Something bad")):
new_schedule = offset_cron(original_schedule)
# Should return the original schedule unchanged
self.assertEqual(new_schedule, original_schedule)

View File

@ -0,0 +1,50 @@
from celery.schedules import crontab
import logging
from allianceauth.crontab.models import CronOffset
from django.db import ProgrammingError
logger = logging.getLogger(__name__)
def offset_cron(schedule: crontab) -> crontab:
"""Take a crontab and apply a series of precalculated offsets to spread out tasks execution on remote resources
Args:
schedule (crontab): celery.schedules.crontab()
Returns:
crontab: A crontab with offsetted Minute and Hour fields
"""
try:
cron_offset = CronOffset.get_solo()
# Stops this shit from happening 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23
# It is only cosmetic, but still annoying
if schedule._orig_minute == '*':
new_minute = '*'
else:
new_minute = [(m + (round(60 * cron_offset.minute))) % 60 for m in schedule.minute]
if schedule._orig_hour == '*':
new_hour = '*'
else:
new_hour = [(m + (round(24 * cron_offset.hour))) % 24 for m in schedule.hour]
return crontab(
minute=",".join(str(m) for m in sorted(new_minute)),
hour=",".join(str(h) for h in sorted(new_hour)),
day_of_month=schedule._orig_day_of_month,
month_of_year=schedule._orig_month_of_year,
day_of_week=schedule._orig_day_of_week)
except ProgrammingError as e:
# If this is called before migrations are run hand back the default schedule
# These offsets are stored in a Singleton Model,
logger.error(e)
return schedule
except Exception as e:
# We absolutely cant fail to hand back a schedule
logger.error(e)
return schedule

View File

@ -0,0 +1,3 @@
"""
Initializes the custom_css module.
"""

View File

@ -0,0 +1,25 @@
"""
Admin classes for custom_css app
"""
# Django
from django.contrib import admin
# Django Solos
from solo.admin import SingletonModelAdmin
# Alliance Auth Custom CSS
from allianceauth.custom_css.models import CustomCSS
from allianceauth.custom_css.forms import CustomCSSAdminForm
@admin.register(CustomCSS)
class CustomCSSAdmin(SingletonModelAdmin):
"""
Custom CSS Admin
"""
form = CustomCSSAdminForm
# Leave this here for when we decide to add syntax highlighting to the CSS editor
# change_form_template = 'custom_css/admin/change_form.html'

View File

@ -0,0 +1,13 @@
"""
Django app configuration for custom_css
"""
# Django
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class CustomCSSConfig(AppConfig):
name = "allianceauth.custom_css"
label = "custom_css"
verbose_name = _("Custom CSS")

View File

@ -0,0 +1,29 @@
"""
Forms for custom_css app
"""
# Alliance Auth Custom CSS
from allianceauth.custom_css.models import CustomCSS
from allianceauth.custom_css.widgets import CssEditorWidget
# Django
from django import forms
class CustomCSSAdminForm(forms.ModelForm):
"""
Form for editing custom CSS
"""
class Meta:
model = CustomCSS
fields = ("css",)
widgets = {
"css": CssEditorWidget(
attrs={
"style": "width: 90%; height: 100%;",
"data-editor": "code-highlight",
"data-language": "css",
}
)
}

View File

@ -0,0 +1,42 @@
# Generated by Django 4.2.15 on 2024-08-14 11:25
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="CustomCSS",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"css",
models.TextField(
blank=True,
help_text="This CSS will be added to the site after the default CSS.",
null=True,
verbose_name="Your custom CSS",
),
),
("timestamp", models.DateTimeField(auto_now=True)),
],
options={
"verbose_name": "Custom CSS",
"verbose_name_plural": "Custom CSS",
"default_permissions": (),
},
),
]

View File

@ -0,0 +1,143 @@
"""
Models for the custom_css app
"""
import os
import re
# Django Solo
from solo.models import SingletonModel
# Django
from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
class CustomCSS(SingletonModel):
"""
Model for storing custom CSS for the site
"""
css = models.TextField(
blank=True,
null=True,
verbose_name=_("Your custom CSS"),
help_text=_("This CSS will be added to the site after the default CSS."),
)
timestamp = models.DateTimeField(auto_now=True)
class Meta:
"""
Meta for CustomCSS
"""
default_permissions = ()
verbose_name = _("Custom CSS")
verbose_name_plural = _("Custom CSS")
def __str__(self) -> str:
"""
String representation of CustomCSS
:return:
:rtype:
"""
return str(_("Custom CSS"))
def save(self, *args, **kwargs):
"""
Save method for CustomCSS
:param args:
:type args:
:param kwargs:
:type kwargs:
:return:
:rtype:
"""
self.pk = 1
if self.css and len(self.css.replace(" ", "")) > 0:
# Write the custom CSS to a file
custom_css_file = open(
f"{settings.STATIC_ROOT}allianceauth/custom-styles.css", "w+"
)
custom_css_file.write(self.compress_css())
custom_css_file.close()
else:
# Remove the custom CSS file
try:
os.remove(f"{settings.STATIC_ROOT}allianceauth/custom-styles.css")
except FileNotFoundError:
pass
super().save(*args, **kwargs)
def compress_css(self) -> str:
"""
Compress CSS
:return:
:rtype:
"""
css = self.css
new_css = ""
# Remove comments
css = re.sub(pattern=r"\s*/\*\s*\*/", repl="$$HACK1$$", string=css)
css = re.sub(pattern=r"/\*[\s\S]*?\*/", repl="", string=css)
css = css.replace("$$HACK1$$", "/**/")
# url() doesn't need quotes
css = re.sub(pattern=r'url\((["\'])([^)]*)\1\)', repl=r"url(\2)", string=css)
# Spaces may be safely collapsed as generated content will collapse them anyway.
css = re.sub(pattern=r"\s+", repl=" ", string=css)
# Shorten collapsable colors: #aabbcc to #abc
css = re.sub(
pattern=r"#([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3(\s|;)",
repl=r"#\1\2\3\4",
string=css,
)
# Fragment values can loose zeros
css = re.sub(
pattern=r":\s*0(\.\d+([cm]m|e[mx]|in|p[ctx]))\s*;", repl=r":\1;", string=css
)
for rule in re.findall(pattern=r"([^{]+){([^}]*)}", string=css):
# We don't need spaces around operators
selectors = [
re.sub(
pattern=r"(?<=[\[\(>+=])\s+|\s+(?=[=~^$*|>+\]\)])",
repl=r"",
string=selector.strip(),
)
for selector in rule[0].split(",")
]
# Order is important, but we still want to discard repetitions
properties = {}
porder = []
for prop in re.findall(pattern="(.*?):(.*?)(;|$)", string=rule[1]):
key = prop[0].strip().lower()
if key not in porder:
porder.append(key)
properties[key] = prop[1].strip()
# output rule if it contains any declarations
if properties:
new_css += "{}{{{}}}".format(
",".join(selectors),
"".join([f"{key}:{properties[key]};" for key in porder])[:-1],
)
return new_css

View File

@ -0,0 +1,48 @@
{% extends "admin/change_form.html" %}
{% block field_sets %}
{% for fieldset in adminform %}
<fieldset class="module aligned {{ fieldset.classes }}">
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
{% if fieldset.description %}
<div class="description">{{ fieldset.description|safe }}</div>
{% endif %}
{% for line in fieldset %}
<div class="form-row{% if line.fields|length == 1 and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
{% if line.fields|length == 1 %}{{ line.errors }}{% else %}<div class="flex-container form-multiline">{% endif %}
{% for field in line %}
<div>
{% if not line.fields|length == 1 and not field.is_readonly %}{{ field.errors }}{% endif %}
<div class="flex-container{% if not line.fields|length == 1 %} fieldBox{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}{% elif field.is_checkbox %} checkbox-row{% endif %}">
{% if field.is_checkbox %}
{{ field.field }}{{ field.label_tag }}
{% else %}
{{ field.label_tag }}
{% if field.is_readonly %}
<div class="readonly">{{ field.contents }}</div>
{% else %}
{{ field.field }}
{% endif %}
{% endif %}
</div>
{% if field.field.help_text %}
<div class="help"{% if field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{% endif %}>
<div>{{ field.field.help_text|safe }}</div>
</div>
{% endif %}
</div>
{% endfor %}
{% if not line.fields|length == 1 %}</div>{% endif %}
</div>
{% endfor %}
</fieldset>
{% endfor %}
{% endblock %}
{% block after_field_sets %}{% endblock %}

View File

@ -0,0 +1,3 @@
{% load custom_css %}
{% custom_css_static 'allianceauth/custom-styles.css' %}

View File

@ -0,0 +1,3 @@
"""
Init file for custom_css templatetags
"""

View File

@ -0,0 +1,48 @@
"""
Custom template tags for custom_css app
"""
# Alliance Auth Custom CSS
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
@register.simple_tag
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
:param path:
:type path:
:return:
:rtype:
"""
try:
Path(f"{settings.STATIC_ROOT}{path}").resolve(strict=True)
except FileNotFoundError:
return ""
else:
try:
custom_css = CustomCSS.objects.get(pk=1)
except CustomCSS.DoesNotExist:
return ""
else:
custom_css_changed = custom_css.timestamp.timestamp()
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
return mark_safe(f'<link rel="stylesheet" href="{versioned_url}">')

View File

@ -0,0 +1,38 @@
"""
Form widgets for custom_css app
"""
# Django
from django import forms
# Alliance Auth
from allianceauth.custom_css.models import CustomCSS
class CssEditorWidget(forms.Textarea):
"""
Widget for editing CSS
"""
def __init__(self, attrs=None):
default_attrs = {"class": "custom-css-editor"}
if attrs:
default_attrs.update(attrs)
super().__init__(default_attrs)
# For when we want to add some sort of syntax highlight to it, which is not that
# easy to do on a textarea field though.
# `highlight.js` is just used as an example here, and doesn't work on a textarea field.
# class Media:
# css = {
# "all": (
# "/static/custom_css/libs/highlight.js/11.10.0/styles/github.min.css",
# )
# }
# js = (
# "/static/custom_css/libs/highlight.js/11.10.0/highlight.min.js",
# "/static/custom_css/libs/highlight.js/11.10.0/languages/css.min.js",
# "/static/custom_css/javascript/custom-css.min.js",
# )

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
from allianceauth.eveonline.models import EveCorporationInfo
from django.test import TestCase from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils from allianceauth.tests.auth_utils import AuthUtils
@ -73,3 +74,51 @@ class AutogroupsConfigManagerTestCase(TestCase):
AutogroupsConfig.objects.update_groups_for_user(member) AutogroupsConfig.objects.update_groups_for_user(member)
self.assertTrue(update_groups.called) 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

@ -10,7 +10,7 @@ from . import (
) )
_BASE_URL = 'http://evemaps.dotlan.net' _BASE_URL = 'https://evemaps.dotlan.net'
def _build_url(category: str, name: str) -> str: def _build_url(category: str, name: str) -> str:

View File

@ -31,29 +31,29 @@ class TestDotlan(TestCase):
def test_alliance_url(self): def test_alliance_url(self):
self.assertEqual( self.assertEqual(
dotlan.alliance_url('Wayne Enterprices'), dotlan.alliance_url('Wayne Enterprices'),
'http://evemaps.dotlan.net/alliance/Wayne_Enterprices' 'https://evemaps.dotlan.net/alliance/Wayne_Enterprices'
) )
def test_corporation_url(self): def test_corporation_url(self):
self.assertEqual( self.assertEqual(
dotlan.corporation_url('Wayne Technology'), dotlan.corporation_url('Wayne Technology'),
'http://evemaps.dotlan.net/corp/Wayne_Technology' 'https://evemaps.dotlan.net/corp/Wayne_Technology'
) )
self.assertEqual( self.assertEqual(
dotlan.corporation_url('Crédit Agricole'), dotlan.corporation_url('Crédit Agricole'),
'http://evemaps.dotlan.net/corp/Cr%C3%A9dit_Agricole' 'https://evemaps.dotlan.net/corp/Cr%C3%A9dit_Agricole'
) )
def test_region_url(self): def test_region_url(self):
self.assertEqual( self.assertEqual(
dotlan.region_url('Black Rise'), dotlan.region_url('Black Rise'),
'http://evemaps.dotlan.net/map/Black_Rise' 'https://evemaps.dotlan.net/map/Black_Rise'
) )
def test_solar_system_url(self): def test_solar_system_url(self):
self.assertEqual( self.assertEqual(
dotlan.solar_system_url('Jita'), dotlan.solar_system_url('Jita'),
'http://evemaps.dotlan.net/system/Jita' 'https://evemaps.dotlan.net/system/Jita'
) )

View File

@ -14,10 +14,20 @@ class EveCharacterProviderManager:
class EveCharacterManager(models.Manager): class EveCharacterManager(models.Manager):
provider = EveCharacterProviderManager() 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)) 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( return self.create(
character_id=character.id, character_id=character.id,
character_name=character.name, character_name=character.name,

View File

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

View File

@ -1,4 +1,5 @@
import logging import logging
from random import randint
from celery import shared_task from celery import shared_task
@ -9,7 +10,8 @@ from . import providers
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
TASK_PRIORITY = 7 TASK_PRIORITY = 7
CHUNK_SIZE = 500 CHARACTER_AFFILIATION_CHUNK_SIZE = 500
EVEONLINE_TASK_JITTER = 600
def chunks(lst, n): def chunks(lst, n):
@ -19,13 +21,13 @@ def chunks(lst, n):
@shared_task @shared_task
def update_corp(corp_id): def update_corp(corp_id: int) -> None:
"""Update given corporation from ESI""" """Update given corporation from ESI"""
EveCorporationInfo.objects.update_corporation(corp_id) EveCorporationInfo.objects.update_corporation(corp_id)
@shared_task @shared_task
def update_alliance(alliance_id): def update_alliance(alliance_id: int) -> None:
"""Update given alliance from ESI""" """Update given alliance from ESI"""
EveAllianceInfo.objects.update_alliance(alliance_id).populate_alliance() EveAllianceInfo.objects.update_alliance(alliance_id).populate_alliance()
@ -37,23 +39,30 @@ def update_character(character_id: int) -> None:
@shared_task @shared_task
def run_model_update(): def run_model_update() -> None:
"""Update all alliances, corporations and characters from ESI""" """Update all alliances, corporations and characters from ESI"""
#update existing corp models # Queue update tasks for Known Corporation Models
for corp in EveCorporationInfo.objects.all().values('corporation_id'): for corp in EveCorporationInfo.objects.all().values('corporation_id'):
update_corp.apply_async(args=[corp['corporation_id']], priority=TASK_PRIORITY) update_corp.apply_async(
args=[corp['corporation_id']],
priority=TASK_PRIORITY,
countdown=randint(1, EVEONLINE_TASK_JITTER))
# update existing alliance models # Queue update tasks for Known Alliance Models
for alliance in EveAllianceInfo.objects.all().values('alliance_id'): for alliance in EveAllianceInfo.objects.all().values('alliance_id'):
update_alliance.apply_async(args=[alliance['alliance_id']], priority=TASK_PRIORITY) update_alliance.apply_async(
args=[alliance['alliance_id']],
priority=TASK_PRIORITY,
countdown=randint(1, EVEONLINE_TASK_JITTER))
# update existing character models # Queue update tasks for Known Character Models
character_ids = EveCharacter.objects.all().values_list('character_id', flat=True) character_ids = EveCharacter.objects.all().values_list('character_id', flat=True)
for character_ids_chunk in chunks(character_ids, CHUNK_SIZE): for character_ids_chunk in chunks(character_ids, CHARACTER_AFFILIATION_CHUNK_SIZE):
update_character_chunk.apply_async( update_character_chunk.apply_async(
args=[character_ids_chunk], priority=TASK_PRIORITY args=[character_ids_chunk],
) priority=TASK_PRIORITY,
countdown=randint(1, EVEONLINE_TASK_JITTER))
@shared_task @shared_task
@ -68,8 +77,9 @@ def update_character_chunk(character_ids_chunk: list):
logger.info("Failed to bulk update characters. Attempting single updates") logger.info("Failed to bulk update characters. Attempting single updates")
for character_id in character_ids_chunk: for character_id in character_ids_chunk:
update_character.apply_async( update_character.apply_async(
args=[character_id], priority=TASK_PRIORITY args=[character_id],
) priority=TASK_PRIORITY,
countdown=randint(1, EVEONLINE_TASK_JITTER))
return return
affiliations = { affiliations = {
@ -107,5 +117,5 @@ def update_character_chunk(character_ids_chunk: list):
if corp_changed or alliance_changed or name_changed: if corp_changed or alliance_changed or name_changed:
update_character.apply_async( update_character.apply_async(
args=[character.get('character_id')], priority=TASK_PRIORITY args=[character.get('character_id')],
) priority=TASK_PRIORITY)

View File

@ -14,15 +14,13 @@ Needs to be called with a context containing three objects:
{% block page_title %}Evelinks Examples{% endblock page_title %} {% block page_title %}Evelinks Examples{% endblock page_title %}
{% block content %} {% block content %}
<div>
{% include "framework/header/page-header.html" with title="Evelinks templatetags examples" %}
<div class="col-lg-12">
<h1 class="page-header text-center">Evelinks templatetags examples</h1>
<div class="col-lg-12 container"> <div class="col-lg-12 container">
<h2>profile URLs</h2> <h2>profile URLs</h2>
<div class="rows"> <div class="rows">
<div class="col-md-4"> <div class="col-md-4">
<h3>evewho</h3> <h3>evewho</h3>
<p><a href="{{ my_character|evewho_character_url }}">character from character object</a></p> <p><a href="{{ my_character|evewho_character_url }}">character from character object</a></p>
@ -57,7 +55,6 @@ Needs to be called with a context containing three objects:
<h2>image URLs</h2> <h2>image URLs</h2>
<div class="rows"> <div class="rows">
<div class="col-md-4"> <div class="col-md-4">
<p>character from ID: <img src="{{ my_character.character_id|character_portrait_url:128 }}"></p> <p>character from ID: <img src="{{ my_character.character_id|character_portrait_url:128 }}"></p>
<p>character from character object: <img src="{{ my_character|character_portrait_url:128 }}"></p> <p>character from character object: <img src="{{ my_character|character_portrait_url:128 }}"></p>
@ -77,5 +74,4 @@ Needs to be called with a context containing three objects:
</div> </div>
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -723,5 +723,5 @@ class TestEveSwaggerProvider(TestCase):
my_client = my_provider.client my_client = my_provider.client
operation = my_client.Universe.get_universe_factions() operation = my_client.Universe.get_universe_factions()
self.assertEqual( self.assertEqual(
operation.future.request.headers['User-Agent'], 'allianceauth v1.0.0' operation.future.request.headers['User-Agent'], 'allianceauth v1.0.0 dummy@example.net'
) )

View File

@ -84,7 +84,7 @@ class TestUpdateTasks(TestCase):
@override_settings(CELERY_ALWAYS_EAGER=True) @override_settings(CELERY_ALWAYS_EAGER=True)
@patch('allianceauth.eveonline.providers.esi_client_factory') @patch('allianceauth.eveonline.providers.esi_client_factory')
@patch('allianceauth.eveonline.tasks.providers') @patch('allianceauth.eveonline.tasks.providers')
@patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2) @patch('allianceauth.eveonline.tasks.CHARACTER_AFFILIATION_CHUNK_SIZE', 2)
class TestRunModelUpdate(TransactionTestCase): class TestRunModelUpdate(TransactionTestCase):
def test_should_run_updates(self, mock_providers, mock_esi_client_factory): def test_should_run_updates(self, mock_providers, mock_esi_client_factory):
# given # given
@ -139,7 +139,7 @@ class TestRunModelUpdate(TransactionTestCase):
@patch('allianceauth.eveonline.tasks.update_character', wraps=update_character) @patch('allianceauth.eveonline.tasks.update_character', wraps=update_character)
@patch('allianceauth.eveonline.providers.esi_client_factory') @patch('allianceauth.eveonline.providers.esi_client_factory')
@patch('allianceauth.eveonline.tasks.providers') @patch('allianceauth.eveonline.tasks.providers')
@patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2) @patch('allianceauth.eveonline.tasks.CHARACTER_AFFILIATION_CHUNK_SIZE', 2)
class TestUpdateCharacterChunk(TestCase): class TestUpdateCharacterChunk(TestCase):
@staticmethod @staticmethod
def _updated_character_ids(spy_update_character) -> set: def _updated_character_ids(spy_update_character) -> set:

View File

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

View File

@ -7,7 +7,7 @@ from allianceauth.services.hooks import UrlHook
@hooks.register('menu_item_hook') @hooks.register('menu_item_hook')
def register_menu(): def register_menu():
return MenuItemHook(_('Fleet Activity Tracking'), 'fas fa-users fa-fw', 'fatlink:view', return MenuItemHook(_('Fleet Activity Tracking'), 'fa-solid fa-users', 'fatlink:view',
navactive=['fatlink:']) navactive=['fatlink:'])

View File

@ -4,4 +4,4 @@ from django.utils.translation import gettext_lazy as _
class FatlinkForm(forms.Form): class FatlinkForm(forms.Form):
fleet = forms.CharField(label=_("Fleet Name"), max_length=50) fleet = forms.CharField(label=_("Fleet Name"), max_length=50)
duration = forms.IntegerField(label=_("Duration of fat-link"), required=True, initial=30, min_value=1, max_value=2147483647, help_text=_('minutes')) duration = forms.IntegerField(label=_("Duration of fat-link"), required=True, initial=30, min_value=1, max_value=2147483647, help_text=_('Duration of the fat-link in minutes'))

View File

@ -0,0 +1,26 @@
"""
Migration to AA Framework API method
"""
from django.conf import settings
from django.db import migrations, models
import allianceauth.framework.api.user
class Migration(migrations.Migration):
dependencies = [
("fleetactivitytracking", "0006_auto_20180803_0430"),
]
operations = [
migrations.AlterField(
model_name="fatlink",
name="creator",
field=models.ForeignKey(
on_delete=models.SET(allianceauth.framework.api.user.get_sentinel_user),
to=settings.AUTH_USER_MODEL
),
),
]

View File

@ -3,10 +3,7 @@ from django.db import models
from django.utils import timezone from django.utils import timezone
from allianceauth.eveonline.models import EveCharacter from allianceauth.eveonline.models import EveCharacter
from allianceauth.framework.api.user import get_sentinel_user
def get_sentinel_user():
return User.objects.get_or_create(username='deleted')[0]
class Fatlink(models.Model): class Fatlink(models.Model):

View File

@ -1,23 +1,43 @@
{% extends 'allianceauth/base-bs5.html' %} {% extends 'allianceauth/base-bs5.html' %}
{% load i18n %} {% load i18n %}
{% block page_title %} {% block page_title %}
{% translate "Fleet Participation" %} {% translate "Fleet Participation" %}
{% endblock %} {% endblock %}
{% block header_nav_brand %}
{% translate "Fleet Activity Tracking" %}
{% endblock header_nav_brand %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div>
<h1 class="page-header text-center">{% translate "Character not found!" %}</h1> {% translate "Character not found!" as page_header %}
<div class="col-lg-12 container" id="example"> {% include "framework/header/page-header.html" with title=page_header %}
<div class="col-lg-12 container">
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<div class="panel panel-default"> <div class="card card-default">
<div class="panel-heading">{{ character_name }}</div> <div class="card-header">
<div class="panel-body"> <div class="card-title mb-0">
{{ character_name }}
</div>
</div>
<div class="card-body">
<div class="col-lg-2 col-sm-2"> <div class="col-lg-2 col-sm-2">
<img class="ra-avatar img-responsive" src="{{ character_portrait_url }}" alt="{{ character_name }}"> <img class="ra-avatar img-responsive" src="{{ character_portrait_url }}" alt="{{ character_name }}">
</div> </div>
<div class="col-lg-10 col-sm-2"> <div class="col-lg-10 col-sm-2">
<div class="alert alert-danger" role="alert">{% translate "Character not registered!" %}</div> <div class="alert alert-danger" role="alert">
{% translate "This character is not associated with an auth account." %} <a href=" {% url 'authentication:add_character' %}">{% translate "Add it here" %}</a> {% translate "before attempting to click fleet attendance links." %} {% translate "Character not registered!" %}
</div>
{% translate "This character is not associated with an auth account." %}
<a href="{% url 'authentication:add_character' %}">{% translate "Add it here" %}</a>
{% translate "before attempting to click fleet attendance links." %}
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,53 @@
{% extends "allianceauth/base-bs5.html" %}
{% load django_bootstrap5 %}
{% load i18n %}
{% block page_title %}
{% translate "Create Fatlink" %}
{% endblock page_title %}
{% block header_nav_brand %}
{% translate "Fleet Activity Tracking" %}
{% endblock header_nav_brand %}
{% block content %}
<div>
{% translate "Create Fatlink" as page_header %}
{% include "framework/header/page-header.html" with title=page_header %}
<div>
{% if badrequest %}
<div class="alert alert-danger" role="alert">{% translate "Bad request!" %}</div>
{% endif %}
{% for message in errormessages %}
<div class="alert alert-danger" role="alert">{{ message }}</div>
{% endfor %}
<div class="card card-primary border-0">
<div class="card-header">
<div class="card-title mb-0">
{% translate "Fatlink details" %}
</div>
</div>
<div class="card-body">
<div class="row justify-content-center">
<div class="col-md-6">
<form role="form" action="" method="POST">
{% csrf_token %}
{% bootstrap_form form %}
<div class="form-group mt-3 clearfix">
{% translate "Create fatlink" as button_text %}
{% bootstrap_button button_class="btn btn-primary" content=button_text name="submit_fat" id="submit_fat" %}
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -1,31 +0,0 @@
{% extends "allianceauth/base-bs5.html" %}
{% load bootstrap %}
{% load i18n %}
{% block page_title %}
{% translate "Create Fatlink" %}
{% endblock page_title %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% translate "Create Fleet Operation" %}</h1>
<div class="container-fluid">
{% if badrequest %}
<div class="alert alert-danger" role="alert">{% translate "Bad request!" %}</div>
{% endif %}
{% for message in errormessages %}<div class="alert alert-danger" role="alert">{{ message }}</div>{% endfor %}
<div class="col-md-4 offset-md-4">
<div class="row">
<form class="form-signin" role="form" action="" method="POST">
{% csrf_token %}
{{ form|bootstrap }}
<br>
<button class="btn btn-lg btn-primary btn-block"
type="submit"
name="submit_fat">
{% translate "Create fatlink" %}
</button>
</form>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -1,45 +1,61 @@
{% extends "allianceauth/base-bs5.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% translate "Fatlink view" %}{% endblock page_title %}
{% block page_title %}
{% translate "Fatlink view" %}
{% endblock page_title %}
{% block header_nav_brand %}
{% translate "Fleet Activity Tracking" %}
{% endblock header_nav_brand %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div>
<h1 class="page-header text-center">{% translate "Edit fatlink" %} "{{ fatlink }}" <h1 class="page-header text-center mb-3">
<div class="text-end"> {% translate "Edit fatlink" %} "{{ fatlink }}"
<form>
<button type="submit" onclick="return confirm('Are you sure?')" class="btn btn-danger" name="deletefat" value="True">
{% translate "Delete fat" %}
</button>
</form>
</div>
</h1> </h1>
<div class="panel panel-default">
<div class="panel-heading">{% translate "Registered characters" %}</div> <div class="text-end mb-3">
<div class="panel-body"> <form>
<button type="submit" onclick="return confirm('{% translate "Are you sure?" %}')" class="btn btn-danger" name="deletefat" value="True">
{% translate "Delete fat" %}
</button>
</form>
</div>
<div class="card card-default">
<div class="card-header">
<div class="card-title mb-0">{% translate "Registered characters" %}</div>
</div>
<div class="card-body">
<table class="table table-responsive table-hover"> <table class="table table-responsive table-hover">
<tr> <tr>
<th class="text-center">{% translate "User" %}</th> <th class="text-center">{% translate "User" %}</th>
<th class="text-center">{% translate "Character" %}</th> <th class="text-center">{% translate "Character" %}</th>
<th class="text-center">{% translate "System" %}</th> <th class="text-center">{% translate "System" %}</th>
<th class="text-center">{% translate "Ship" %}</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> <th></th>
</tr> </tr>
{% for fat in registered_fats %} {% for fat in registered_fats %}
<tr> <tr>
<td class="text-center">{{ fat.user }}</td> <td class="text-center">{{ fat.user }}</td>
<td class="text-center">{{ fat.character.character_name }}</td> <td class="text-center">{{ fat.character.character_name }}</td>
{% if fat.station != "No Station" %} <td class="text-center">
<td class="text-center">{% blocktranslate %}Docked in {% endblocktranslate %}{{ fat.system }}</td> {% if fat.station != "No Station" %}
{% else %} {% translate "Docked in" %}
<td class="text-center">{{ fat.system }}</td> {% endif %}
{% endif %} {{ fat.system }}
</td>
<td class="text-center">{{ fat.shiptype }}</td> <td class="text-center">{{ fat.shiptype }}</td>
<td class="text-center">{{ fat.fatlink.fatdatetime }}</td> <td class="text-center">{{ fat.fatlink.fatdatetime }}</td>
<td class="text-center"> <td class="text-center">
<form> <form>
<button type="submit" class="btn btn-warning" name="removechar" value="{{ fat.character.character_id }}"> <button type="submit" class="btn btn-warning" name="removechar" value="{{ fat.character.character_id }}">
<span class="glyphicon glyphicon-remove"></span> <i class="fa-solid fa-trash-can fa-fw"></i>
</button> </button>
</form> </form>
</td> </td>

View File

@ -1,71 +1,104 @@
{% extends "allianceauth/base-bs5.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% translate "Personal fatlink statistics" %}{% endblock page_title %} {% block page_title %}
{% translate "Personal fatlink statistics" %}
{% endblock page_title %}
{% block header_nav_brand %}
{% translate "Fleet Activity Tracking" %}
{% endblock header_nav_brand %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div>
<h1 class="page-header text-center">{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %} <h1 class="page-header text-center mb-3">
{% if char_id %} {% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
<div class="text-end">
<a href="{% url 'fatlink:user_statistics_month' char_id previous_month|date:'Y' previous_month|date:'m' %}" class="btn btn-info">{% translate "Previous month" %}</a>
<a href="{% url 'fatlink:user_statistics_month' char_id next_month|date:'Y' next_month|date:'m' %}" class="btn btn-info">{% translate "Next month" %}</a>
</div>
{% endif %}
</h1> </h1>
<h2>
{% blocktranslate count links=n_fats trimmed %}
{{ user }} has collected one link this month.
{% plural %}
{{ user }} has collected {{ links }} links this month.
{% endblocktranslate %}
</h2>
<table class="table table-responsive">
<tr>
<th class="col-md-2 text-center">{% translate "Ship" %}</th>
<th class="col-md-2 text-center">{% translate "Times used" %}</th>
</tr>
{% for ship, n_fats in shipStats %}
<tr>
<td class="text-center">{{ ship }}</td>
<td class="text-center">{{ n_fats }}</td>
</tr>
{% endfor %}
</table>
{% if created_fats %}
<h2>
{% blocktranslate count links=n_created_fats trimmed %}
{{ user }} has created one link this month.
{% plural %}
{{ user }} has created {{ links }} links this month.
{% endblocktranslate %}
</h2>
{% if created_fats %}
<table class="table">
<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 "Duration" %}</th>
<th class="text-center">{% translate "Edit" %}</th>
</tr>
{% for link in created_fats %}
<tr>
<td class="text-center"><a href="{% url 'fatlink:click' link.hash %}" class="badge bg-primary">{{ link.fleet }}</a></td>
<td class="text-center">{{ link.creator.username }}</td>
<td class="text-center">{{ link.fatdatetime }}</td>
<td class="text-center">{{ link.duration }}</td>
<td class="text-center">
<a href="{% url 'fatlink:modify' link.hash %}">
<button type="button" class="btn btn-info"><span
class="glyphicon glyphicon-edit"></span></button>
</a>
</td>
</tr>
{% endfor %}
{% if char_id %}
<div class="text-end mb-3">
<a href="{% url 'fatlink:user_statistics_month' char_id previous_month|date:'Y' previous_month|date:'m' %}" class="btn btn-info">
{% translate "Previous month" %}
</a>
<a href="{% url 'fatlink:user_statistics_month' char_id next_month|date:'Y' next_month|date:'m' %}" class="btn btn-info">
{% translate "Next month" %}
</a>
</div>
{% endif %}
<div class="card card-default mb-3">
<div class="card-header">
<div class="card-title mb-0">
{% blocktranslate count links=n_fats trimmed %}
{{ user }} has collected one link this month.
{% plural %}
{{ user }} has collected {{ links }} links this month.
{% endblocktranslate %}
</div>
</div>
<div class="card-body">
<table class="table table-responsive">
<tr>
<th class="col-md-2 text-center">{% translate "Ship" %}</th>
<th class="col-md-2 text-center">{% translate "Times used" %}</th>
</tr>
{% for ship, n_fats in shipStats %}
<tr>
<td class="text-center">{{ ship }}</td>
<td class="text-center">{{ n_fats }}</td>
</tr>
{% endfor %}
</table> </table>
{% endif %} </div>
</div>
{% if created_fats %}
<div class="card card-default">
<div class="card-header">
<div class="card-title mb-0">
{% blocktranslate count links=n_created_fats trimmed %}
{{ user }} has created one link this month.
{% plural %}
{{ user }} has created {{ links }} links this month.
{% endblocktranslate %}
</div>
</div>
<div class="card-body">
<table class="table">
<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 "Duration" %}</th>
<th class="text-center">{% translate "Edit" %}</th>
</tr>
{% for link in created_fats %}
<tr>
<td class="text-center">
<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.fatdatetime }}</td>
<td class="text-center">{{ link.duration }}</td>
<td class="text-center">
<a href="{% url 'fatlink:modify' link.hash %}">
<button type="button" class="btn btn-info">
<i class="fa-solid fa-pen-to-square fa-fw"></i>
</button>
</a>
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endif %} {% endif %}
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -1,25 +1,36 @@
{% extends "allianceauth/base-bs5.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %} {% block page_title %}
{% translate "Personal fatlink statistics" %} {% translate "Personal fatlink statistics" %}
{% endblock page_title %} {% endblock page_title %}
{% block header_nav_brand %}
{% translate "Fleet Activity Tracking" %}
{% endblock header_nav_brand %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div>
<h1 class="page-header text-center"> <h1 class="page-header text-center mb-3">
{% blocktranslate %}Participation data statistics for {{ year }}{% endblocktranslate %} {% blocktranslate %}Participation data statistics for {{ year }}{% endblocktranslate %}
<div class="text-end">
<a href="{% url "fatlink:personal_statistics_year" previous_year %}" class="btn btn-info"><i class="fa-solid fa-chevron-left"></i> {% translate "Previous year" %}</a>
{% if next_year %}
<a href="{% url "fatlink:personal_statistics_year" next_year %}" class="btn btn-info">{% translate "Next year" %} <i class="fa-solid fa-chevron-right"></i></a>
{% endif %}
</div>
</h1> </h1>
<div class="text-end mb-3">
<a href="{% url "fatlink:personal_statistics_year" previous_year %}" class="btn btn-info"><i class="fa-solid fa-chevron-left"></i> {% translate "Previous year" %}</a>
{% if next_year %}
<a href="{% url "fatlink:personal_statistics_year" next_year %}" class="btn btn-info">{% translate "Next year" %} <i class="fa-solid fa-chevron-right"></i></a>
{% endif %}
</div>
<div class="col-lg-2 offset-lg-5"> <div class="col-lg-2 offset-lg-5">
<table class="table table-responsive"> <table class="table table-responsive">
<tr> <tr>
<th scope="col" class="col-md-2 text-center">{% translate "Month" %}</th> <th scope="col" class="col-md-2 text-center">{% translate "Month" %}</th>
<th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th> <th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
</tr> </tr>
{% for monthnr, month, n_fats in monthlystats %} {% for monthnr, month, n_fats in monthlystats %}
<tr> <tr>
<td class="text-center"> <td class="text-center">

View File

@ -1,50 +1,63 @@
{% extends "allianceauth/base-bs5.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %} {% block page_title %}
{% translate "Fatlink Corp Statistics" %} {% translate "Fatlink Corp Statistics" %}
{% endblock page_title %} {% endblock page_title %}
{% block header_nav_brand %}
{% translate "Fleet Activity Tracking" %}
{% endblock header_nav_brand %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div>
<h1 class="page-header text-center"> <h1 class="page-header text-center mb-3">
{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %} {% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
<div class="text-end">
<a href="{% url "fatlink:statistics_corp_month" corpid previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
{% if next_month %}
<a href="{% url "fatlink:statistics_corp_month" corpid next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
{% endif %}
</div>
</h1> </h1>
{% if fatStats %}
<div class="table-responsive"> <div class="text-end mb-3">
<table class="table table-striped"> <a href="{% url "fatlink:statistics_corp_month" corpid previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
<tr>
<th scope="col" class="col-md-1"></th> {% if next_month %}
<th scope="col" class="col-md-2 text-center">{% translate "Main Character" %}</th> <a href="{% url "fatlink:statistics_corp_month" corpid next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
<th scope="col" class="col-md-2 text-center">{% translate "Characters" %}</th> {% endif %}
<th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
<th scope="col" class="col-md-2 text-center">
{% translate "Average fats" %}
<i class="fa-solid fa-question" rel="tooltip" title="Fats / Characters"></i>
</th>
</tr>
{% for memberStat in fatStats %}
<tr>
<td>
<img src="{{ memberStat.mainchar.portrait_url_32 }}" class="ra-avatar img-responsive" alt="{{ memberStat.mainchar.character_name }}">
</td>
<td class="text-center">{{ memberStat.mainchar.character_name }}</td>
<td class="text-center">{{ memberStat.n_chars }}</td>
<td class="text-center">{{ memberStat.n_fats }}</td>
<td class="text-center">{{ memberStat.avg_fat }}</td>
</tr>
{% endfor %}
</table>
</div> </div>
{% if fatStats %}
<div class="table-responsive">
<table class="table table-striped">
<tr>
<th scope="col" class="col-md-1"></th>
<th scope="col" class="col-md-2 text-center">{% translate "Main Character" %}</th>
<th scope="col" class="col-md-2 text-center">{% translate "Characters" %}</th>
<th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
<th scope="col" class="col-md-2 text-center">
{% translate "Average fats" %}
<i class="fa-solid fa-question" rel="tooltip" title="Fats / Characters"></i>
</th>
</tr>
{% for memberStat in fatStats %}
<tr>
<td>
<img src="{{ memberStat.mainchar.portrait_url_32 }}" class="ra-avatar img-responsive" alt="{{ memberStat.mainchar.character_name }}">
</td>
<td class="text-center">{{ memberStat.mainchar.character_name }}</td>
<td class="text-center">{{ memberStat.n_chars }}</td>
<td class="text-center">{{ memberStat.n_fats }}</td>
<td class="text-center">{{ memberStat.avg_fat }}</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %} {% endif %}
</div> </div>
{% endblock content %} {% endblock content %}
{% block extra_script %}
$(document).ready(function () { {% block extra_javascript %}
$("[rel=tooltip]").tooltip(); <script>
}); $(document).ready(() => {
{% endblock extra_script %} $("[rel=tooltip]").tooltip();
});
</script>
{% endblock extra_javascript %}

View File

@ -1,54 +1,67 @@
{% extends "allianceauth/base-bs5.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %} {% block page_title %}
{% translate "Fatlink Statistics" %} {% translate "Fatlink Statistics" %}
{% endblock page_title %} {% endblock page_title %}
{% block header_nav_brand %}
{% translate "Fleet Activity Tracking" %}
{% endblock header_nav_brand %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div>
<h1 class="page-header text-center"> <h1 class="page-header text-center mb-3">
{% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %} {% blocktranslate %}Participation data statistics for {{ month }}, {{ year }}{% endblocktranslate %}
<div class="text-end">
<a href="{% url "fatlink:statistics_month" previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
{% if next_month %}
<a href="{% url 'fatlink:statistics_month' next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
{% endif %}
</div>
</h1> </h1>
{% if fatStats %}
<div class="table-responsive"> <div class="text-end mb-3">
<table class="table table-striped"> <a href="{% url "fatlink:statistics_month" previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% translate "Previous month" %}</a>
<tr>
<th scope="col" class="col-md-1"></th> {% if next_month %}
<th scope="col" class="col-md-2 text-center">{% translate "Ticker" %}</th> <a href="{% url 'fatlink:statistics_month' next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% translate "Next month" %}</a>
<th scope="col" class="col-md-5 text-center">{% translate "Corp" %}</th> {% endif %}
<th scope="col" class="col-md-2 text-center">{% translate "Members" %}</th>
<th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
<th scope="col" class="col-md-2 text-center">
{% translate "Average fats" %}
<i class="fa-solid fa-question" rel="tooltip" title="Fats / Characters"></i>
</th>
</tr>
{% for corpStat in fatStats %}
<tr>
<td>
<img src="{{ corpStat.corp.logo_url_32 }}" class="ra-avatar img-responsive" alt="{{ corpStat.corp.corporation_name }}">
</td>
<td class="text-center">
<a href="{% url 'fatlink:statistics_corp' corpStat.corp.corporation_id %}">[{{ corpStat.corp.corporation_ticker }}]</a>
</td>
<td class="text-center">{{ corpStat.corp.corporation_name }}</td>
<td class="text-center">{{ corpStat.corp.member_count }}</td>
<td class="text-center">{{ corpStat.n_fats }}</td>
<td class="text-center">{{ corpStat.avg_fat }}</td>
</tr>
{% endfor %}
</table>
</div> </div>
{% if fatStats %}
<div class="table-responsive">
<table class="table table-striped">
<tr>
<th scope="col" class="col-md-1"></th>
<th scope="col" class="col-md-2 text-center">{% translate "Ticker" %}</th>
<th scope="col" class="col-md-5 text-center">{% translate "Corp" %}</th>
<th scope="col" class="col-md-2 text-center">{% translate "Members" %}</th>
<th scope="col" class="col-md-2 text-center">{% translate "Fats" %}</th>
<th scope="col" class="col-md-2 text-center">
{% translate "Average fats" %}
<i class="fa-solid fa-question" rel="tooltip" title="Fats / Characters"></i>
</th>
</tr>
{% for corpStat in fatStats %}
<tr>
<td>
<img src="{{ corpStat.corp.logo_url_32 }}" class="ra-avatar img-responsive" alt="{{ corpStat.corp.corporation_name }}">
</td>
<td class="text-center">
<a href="{% url 'fatlink:statistics_corp' corpStat.corp.corporation_id %}">[{{ corpStat.corp.corporation_ticker }}]</a>
</td>
<td class="text-center">{{ corpStat.corp.corporation_name }}</td>
<td class="text-center">{{ corpStat.corp.member_count }}</td>
<td class="text-center">{{ corpStat.n_fats }}</td>
<td class="text-center">{{ corpStat.avg_fat }}</td>
</tr>
{% endfor %}
</table>
</div>
{% endif %} {% endif %}
</div> </div>
{% endblock content %} {% endblock content %}
{% block extra_script %}
$(document).ready(function () { {% block extra_javascript %}
$("[rel=tooltip]").tooltip(); <script>
}); $(document).ready(() => {
{% endblock extra_script %} $("[rel=tooltip]").tooltip();
});
</script>
{% endblock extra_javascript %}

View File

@ -1,11 +1,20 @@
{% extends "allianceauth/base-bs5.html" %} {% extends "allianceauth/base-bs5.html" %}
{% load i18n %} {% load i18n %}
{% block page_title %} {% block page_title %}
{% translate "Fatlink view" %} {% translate "Fatlink view" %}
{% endblock page_title %} {% endblock page_title %}
{% block header_nav_brand %}
{% translate "Fleet Activity Tracking" %}
{% endblock header_nav_brand %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div>
<h1 class="page-header text-center">{% translate "Participation data" %}</h1> {% translate "Participation data" as page_header %}
{% include "framework/header/page-header.html" with title=page_header %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped"> <table class="table table-striped">
<tr> <tr>
@ -15,11 +24,15 @@
</h4> </h4>
</th> </th>
<th class="col-md-2 align-self-end"> <th class="col-md-2 align-self-end">
<a href="{% url 'fatlink:personal_statistics' %}" class="btn btn-info"><i class="fa-solid fa-circle-info fa-fw"></i>{% translate "Personal statistics" %}</a> <a href="{% url 'fatlink:personal_statistics' %}" class="btn btn-info">
<i class="fa-solid fa-circle-info fa-fw"></i>
{% translate "Personal statistics" %}
</a>
</th> </th>
</tr> </tr>
</table> </table>
</div> </div>
{% if fats %} {% if fats %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped"> <table class="table table-striped">
@ -28,8 +41,9 @@
<th scope="col" class="text-center">{% translate "Character" %}</th> <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 "System" %}</th>
<th scope="col" class="text-center">{% translate "Ship" %}</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> </tr>
{% for fat in fats %} {% for fat in fats %}
<tr> <tr>
<td class="text-center">{{ fat.fatlink.fleet }}</td> <td class="text-center">{{ fat.fatlink.fleet }}</td>
@ -48,6 +62,7 @@
{% else %} {% else %}
<div class="alert alert-warning text-center">{% translate "No fleet activity on record." %}</div> <div class="alert alert-warning text-center">{% translate "No fleet activity on record." %}</div>
{% endif %} {% endif %}
{% if perms.auth.fleetactivitytracking %} {% if perms.auth.fleetactivitytracking %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped"> <table class="table table-striped">
@ -66,6 +81,7 @@
</tr> </tr>
</table> </table>
</div> </div>
{% if fatlinks %} {% if fatlinks %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped"> <table class="table table-striped">
@ -73,14 +89,15 @@
<th scope="col" class="text-center">{% translate "Name" %}</th> <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 "Creator" %}</th>
<th scope="col" class="text-center">{% translate "Fleet" %}</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 "Duration" %}</th>
<th scope="col" class="text-center">{% translate "Edit" %}</th> <th scope="col" class="text-center">{% translate "Edit" %}</th>
</tr> </tr>
{% for link in fatlinks %} {% for link in fatlinks %}
<tr> <tr>
<td class="text-center"> <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>
<td class="text-center">{{ link.creator.username }}</td> <td class="text-center">{{ link.creator.username }}</td>
<td class="text-center">{{ link.fleet }}</td> <td class="text-center">{{ link.fleet }}</td>

View File

@ -352,11 +352,11 @@ def create_fatlink_view(request):
for errorname, message in e.message_dict.items(): for errorname, message in e.message_dict.items():
messages.append(message[0].decode()) messages.append(message[0].decode())
context = {'form': form, 'errormessages': messages} context = {'form': form, 'errormessages': messages}
return render(request, 'fleetactivitytracking/fatlinkformatter.html', context=context) return render(request, 'fleetactivitytracking/fatlinkcreate.html', context=context)
else: else:
form = FatlinkForm() form = FatlinkForm()
context = {'form': form, 'badrequest': True} context = {'form': form, 'badrequest': True}
return render(request, 'fleetactivitytracking/fatlinkformatter.html', context=context) return render(request, 'fleetactivitytracking/fatlinkcreate.html', context=context)
return redirect('fatlink:view') return redirect('fatlink:view')
else: else:
@ -365,7 +365,7 @@ def create_fatlink_view(request):
context = {'form': form} context = {'form': form}
return render(request, 'fleetactivitytracking/fatlinkformatter.html', context=context) return render(request, 'fleetactivitytracking/fatlinkcreate.html', context=context)
@login_required @login_required
@ -377,12 +377,12 @@ def modify_fatlink_view(request, fat_hash=None):
if request.GET.get('removechar', None): if request.GET.get('removechar', None):
character_id = request.GET.get('removechar') character_id = request.GET.get('removechar')
character = EveCharacter.objects.get(character_id=character_id) character = EveCharacter.objects.get(character_id=character_id)
logger.debug(f"Removing character {character.character_name} from fleetactivitytracking {fatlink}") logger.debug(f"Removing character {character.character_name} from fleetactivitytracking {fatlink}")
Fat.objects.filter(fatlink=fatlink).filter(character=character).delete() Fat.objects.filter(fatlink=fatlink).filter(character=character).delete()
if request.GET.get('deletefat', None): if request.GET.get('deletefat', None):
logger.debug("Removing fleetactivitytracking %s" % fatlink) logger.debug("Removing fleetactivitytracking %s" % fatlink)
fatlink.delete() fatlink.delete()
return redirect('fatlink:view') return redirect('fatlink:view')

View File

@ -0,0 +1,3 @@
"""
Alliance Auth Framework
"""

View File

@ -0,0 +1,57 @@
"""
Alliance Auth Evecharacter API
"""
from typing import Optional
from django.contrib.auth.models import User
from allianceauth.authentication.models import CharacterOwnership
from allianceauth.eveonline.models import EveCharacter
from allianceauth.framework.api.user import get_sentinel_user
def get_main_character_from_evecharacter(
character: EveCharacter,
) -> Optional[EveCharacter]:
"""
Get the main character for a given EveCharacter or None when no main character is set
:param character:
:type character:
:return:
:rtype:
"""
try:
userprofile = character.character_ownership.user.profile
except (
AttributeError,
EveCharacter.character_ownership.RelatedObjectDoesNotExist,
CharacterOwnership.user.RelatedObjectDoesNotExist,
):
return None
return userprofile.main_character
def get_user_from_evecharacter(character: EveCharacter) -> User:
"""
Get the user for an EveCharacter or the sentinel user when no user is found
:param character:
:type character:
:return:
:rtype:
"""
try:
userprofile = character.character_ownership.user.profile
except (
AttributeError,
EveCharacter.character_ownership.RelatedObjectDoesNotExist,
CharacterOwnership.user.RelatedObjectDoesNotExist,
):
return get_sentinel_user()
return userprofile.user

View File

@ -0,0 +1,105 @@
"""
Alliance Auth User API
"""
from typing import Optional
from django.contrib.auth.models import User
from allianceauth.authentication.models import CharacterOwnership
from allianceauth.eveonline.models import EveCharacter
def get_all_characters_from_user(user: User, main_first: bool = False) -> list:
"""
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: 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:
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 []
return characters
def get_main_character_from_user(user: User) -> Optional[EveCharacter]:
"""
Get the main character from a user
:param user:
:type user:
:return:
:rtype:
"""
if user is None:
return None
try:
main_character = user.profile.main_character
except AttributeError:
return None
return main_character
def get_main_character_name_from_user(user: User) -> str:
"""
Get the main character name from a user
:param user:
:type user:
:return:
:rtype:
"""
if user is None:
sentinel_user = get_sentinel_user()
return sentinel_user.username
main_character = get_main_character_from_user(user=user)
try:
username = main_character.character_name
except AttributeError:
return str(user)
return username
def get_sentinel_user() -> User:
"""
Get the sentinel user or create one
:return:
"""
return User.objects.get_or_create(username="deleted")[0]

View File

@ -0,0 +1,16 @@
"""
Framework App Config
"""
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class FrameworkConfig(AppConfig):
"""
Framework App Config
"""
name = "allianceauth.framework"
label = "framework"
verbose_name = _("Framework")

View File

@ -0,0 +1,174 @@
/**
* Alliance Auth CSS Framework
*
* This provides some CSS classes together with a couple of Bootstrap fixes
* to be used throughout Alliance Auth and its Community Apps
*/
/* 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;
}
}
@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;
}
}
/* Image overflow fix
------------------------------------------------------------------------------------- */
@media all {
img {
max-width: 100%;
height: auto;
}
}
/* Side Navigation
------------------------------------------------------------------------------------- */
@media all {
#sidebar > div {
width: 325px;
}
/* Menu items in general */
#sidebar-menu li > a,
#sidebar-menu li > ul > li > a {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
max-width: 210px;
}
/* Parent items with chevron and possible badge */
#sidebar-menu li:has(span.badge) > a[data-bs-toggle="collapse"] {
max-width: 180px;
}
/* Child items with possible badge */
#sidebar-menu li > ul > li > a {
max-width: 189px;
}
/* Chevron icons */
#sidebar-menu 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"][aria-expanded="true"] > i.fa-chevron-right,
#sidebar-menu span[data-bs-toggle="collapse"][aria-expanded="false"] > i.fa-chevron-down {
display: none;
}
}
/* Cursor classes
------------------------------------------------------------------------------------- */
@media all {
.cursor-auto {
cursor: auto;
}
.cursor-default {
cursor: default;
}
.cursor-pointer {
cursor: pointer;
}
.cursor-wait {
cursor: wait;
}
.cursor-text {
cursor: text;
}
.cursor-move {
cursor: move;
}
.cursor-help {
cursor: help;
}
.cursor-not-allowed {
cursor: not-allowed;
}
.cursor-inherit {
cursor: inherit;
}
.cursor-zoom-in {
cursor: zoom-in;
}
.cursor-zoom-out {
cursor: zoom-out;
}
}
/* Callouts
*
* Not quite alerts, but custom and helpful notes for folks.
* Requires a base and modifier class.
------------------------------------------------------------------------------------- */
@media all {
/* Common styles for all types */
.aa-callout {
border: 1px solid var(--bs-border-color);
border-left-width: 0.25rem;
border-radius: 0.25rem;
margin-bottom: 1rem;
padding: 1rem;
}
.aa-callout.aa-callout-sm {
padding: 0.5rem;
}
.aa-callout.aa-callout-lg {
padding: 1.5rem;
}
/* Last item bottom margin should be 0 */
.aa-callout :last-child {
margin-bottom: 0;
}
/* Variations */
.aa-callout.aa-callout-danger {
border-left-color: var(--bs-danger);
}
.aa-callout.aa-callout-info {
border-left-color: var(--bs-info);
}
.aa-callout.aa-callout-success {
border-left-color: var(--bs-success);
}
.aa-callout.aa-callout-warning {
border-left-color: var(--bs-warning);
}
}

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