Compare commits

...

264 Commits

Author SHA1 Message Date
Ariel Rin
564f4fb5f9 Merge branch 'use-django-3.2' into 'v2.9.x'
Bumped minimum Django version to 3.2.7

See merge request allianceauth/allianceauth!1336
2021-09-22 13:43:51 +00:00
Peter Pfeufer
688c11ff18 bumped min Django version to 3.2.7 2021-09-19 18:05:39 +02:00
Ariel Rin
4a9a2a670c Version Bump 2.9.0b1 2021-09-14 05:08:15 +00:00
Ariel Rin
5b25637de5 Debian Bullseye now Stable, libmariadbclient-dev name deprecated, now removed 2021-09-14 05:02:15 +00:00
Ariel Rin
cf2e368978 Merge branch 'docs-updates' into 'v2.9.x'
Docs Updates

See merge request allianceauth/allianceauth!1334
2021-09-14 04:21:23 +00:00
Peter Pfeufer
0d67673542 Docs Updates 2021-09-14 04:21:22 +00:00
Ariel Rin
d7e58fb557 Merge branch 'django-admin-favicons' into 'v2.9.x'
Add favicon support to django admin backend

See merge request allianceauth/allianceauth!1335
2021-09-14 04:19:33 +00:00
Peter Pfeufer
f8cffb64a1 add favicon support to django admin backend 2021-08-25 22:37:37 +02:00
Joel Falknau
dc97fb1cc5 Forgot pre-commit for master > 2.9.x 2021-08-20 14:50:44 +10:00
Joel Falknau
392a0c4dcb Merge remote-tracking branch 'upstream/master' into v2.9.x 2021-08-20 14:41:38 +10:00
Ariel Rin
970a111386 Merge branch 'analytics' into 'v2.9.x'
Analytics Enable migration typo

See merge request allianceauth/allianceauth!1331
2021-08-20 04:04:33 +00:00
Ariel Rin
cafa7cbf17 Merge branch 'top-menu-redesign' into 'v2.9.x'
Top menu redesign

See merge request allianceauth/allianceauth!1329
2021-08-20 04:04:23 +00:00
Ariel Rin
0f0c0441a9 Merge branch 'fix_logging_notifications' into 'master'
Fix logging notifications

See merge request allianceauth/allianceauth!1332
2021-08-20 04:03:48 +00:00
Erik Kalkoken
a0db8e8e2c Fix logging notifications 2021-08-20 04:03:48 +00:00
Peter Pfeufer
641356f31d css properly formatted 2021-08-18 07:40:47 +02:00
Peter Pfeufer
191b238a04 using "public/lang_select.html" 2021-08-18 07:39:36 +02:00
Joel Falknau
f6936c5f33 Typo in token enable flag 2021-08-18 11:43:05 +10:00
Ariel Rin
e8f8cb8aa3 Merge branch 'fix-for-the-fix' into 'v2.9.x'
(Hotfix) Missing import added

See merge request allianceauth/allianceauth!1330
2021-08-18 01:39:15 +00:00
Peter Pfeufer
96170a668f missing import added 2021-08-17 23:47:15 +02:00
Peter Pfeufer
4a3e807066 menu item text for mobile view 2021-08-14 14:02:25 +02:00
Peter Pfeufer
ab369d9aac [FIX] text color 2021-08-14 13:58:01 +02:00
Peter Pfeufer
742864fe6c adding eve time to top menu 2021-08-11 16:19:13 +02:00
Peter Pfeufer
c3df1c4d1f adding user menu 2021-08-11 16:18:59 +02:00
Ariel Rin
63d7fb80e6 Merge branch 'srp_evetools_killboard' into 'v2.9.x'
Add EveTools kill board as accepted source for srp request kill mails

See merge request allianceauth/allianceauth!1326
2021-08-11 13:06:19 +00:00
Ariel Rin
a7f468efd1 Merge branch 'fix_group_creation' into 'master'
Fix Group Creation

Closes #1161

See merge request allianceauth/allianceauth!1327
2021-08-11 06:07:24 +00:00
Ariel Rin
9f4ab9540b Merge branch 'improve_notifications' into 'master'
Improve notifications

See merge request allianceauth/allianceauth!1324
2021-08-11 06:06:24 +00:00
Erik Kalkoken
1e133b7c5d Improve notifications 2021-08-11 06:06:23 +00:00
Ariel Rin
4aa7530bbc Merge branch 'fix_hasgroupleader' into 'master'
Fix `Has Leader` column for groups that have group leader groups.

See merge request allianceauth/allianceauth!1328
2021-08-11 06:06:13 +00:00
colcrunch
2e0ddf2e7a has_leader should return true when a group has a group_leader_group 2021-07-13 18:00:21 -04:00
colcrunch
e24bc2a05d Revert "Update tests."
This reverts commit 7c1d1074f9.
2021-07-13 11:35:19 -04:00
colcrunch
a8c0db3fd7 Revert "Update autogroups."
This reverts commit eaa1cde01a.
2021-07-13 11:35:06 -04:00
colcrunch
7b77a6cd40 Revert "Add authutil for creating groups with authgroups."
This reverts commit 15db817382.
2021-07-13 11:34:55 -04:00
colcrunch
b8b8e470f2 Revert "Update tests to use the create_group util."
This reverts commit 0897383e41.
2021-07-13 11:34:46 -04:00
colcrunch
ad92ea243d Revert "Fix missed test."
This reverts commit 37005b1c68.
2021-07-13 11:34:35 -04:00
colcrunch
489a8456f7 Revert "More test fixes."
This reverts commit 6c3650d9f2.
2021-07-13 11:34:19 -04:00
colcrunch
122e389c38 Add signals back. 2021-07-13 11:33:51 -04:00
colcrunch
8318add6d5 Update test_admin.py 2021-07-13 10:18:39 -04:00
colcrunch
6c3650d9f2 More test fixes. 2021-07-13 09:28:31 -04:00
colcrunch
37005b1c68 Fix missed test. 2021-07-13 09:18:53 -04:00
colcrunch
0897383e41 Update tests to use the create_group util. 2021-07-13 09:13:47 -04:00
colcrunch
15db817382 Add authutil for creating groups with authgroups. 2021-07-13 09:13:17 -04:00
colcrunch
eaa1cde01a Update autogroups. 2021-07-13 08:41:36 -04:00
colcrunch
7c1d1074f9 Update tests. 2021-07-13 08:41:25 -04:00
colcrunch
0f0f9b6062 Fix group creation ignoring AuthGroup settings. (Fixes #1161) 2021-07-13 08:09:40 -04:00
Peter Pfeufer
839232dc98 add evetools killboard as accepted source for srp requests 2021-07-04 15:04:25 +02:00
Ariel Rin
356df45583 Version Bump v2.9.0a4 2021-07-03 04:44:17 +00:00
Ariel Rin
6b78992f07 Merge branch 'googleanalytics' into 'v2.9.x'
Analytics Module

See merge request allianceauth/allianceauth!1286
2021-07-03 04:43:17 +00:00
Ariel Rin
5af33facb6 Analytics Module 2021-07-03 04:43:16 +00:00
Ariel Rin
a7b9bcf3e8 Merge branch 'bump-celery-version' into 'v2.9.x'
allow celery 5 since it is pulled in by celery-beat>2.2.0

See merge request allianceauth/allianceauth!1325
2021-07-03 04:25:54 +00:00
Ariel Rin
eafc6074c1 Merge branch 'purge_state_cache' into 'v2.9.x'
Refresh user profile on state change to force permission changes.

See merge request allianceauth/allianceauth!1296
2021-07-03 04:25:34 +00:00
Peter Pfeufer
99383a482b allow celery 5 since it is pulled in by celery-beat
This commit is so that celery 5 can be tested
2021-07-02 18:56:54 +02:00
Ariel Rin
6f2807cba2 Version Bump v2.8.6 2021-07-02 16:47:06 +00:00
Ariel Rin
39a40a8c43 Limit Django-Celery-Beat to 2.2.0 for Celery 4.x 2021-07-02 16:42:07 +00:00
Ariel Rin
31123f7e97 Merge branch 'v2.9.x' of https://gitlab.com/allianceauth/allianceauth into v2.9.x 2021-06-29 14:02:00 +10:00
Ariel Rin
96300a012a Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v2.9.x 2021-06-29 14:01:52 +10:00
Ariel Rin
5f98b5350e Version Bump v2.8.5 2021-06-29 03:27:30 +00:00
Ariel Rin
8874eceb5f Merge branch 'name-formatting' into 'v2.9.x'
Name formatter fixes

See merge request allianceauth/allianceauth!1321
2021-06-29 03:15:02 +00:00
Ariel Rin
9de4d557e3 Merge branch 'discord-existing' into 'master'
Update member when they are already in Discord server.

See merge request allianceauth/allianceauth!1322
2021-06-29 03:10:23 +00:00
Aaron Kable
1d5f2634f1 Update member when they are already in Discord server. 2021-06-29 03:10:23 +00:00
Ariel Rin
710d26d36d Merge branch 'message-icons-fix' into 'master'
[FIX] icons in alert messages

See merge request allianceauth/allianceauth!1323
2021-06-29 03:10:01 +00:00
Peter Pfeufer
47793e6198 [FIX] icons in alert messages
This way they don't interfere with possible HTML in the message itself.
2021-06-28 21:20:59 +02:00
Peter Pfeufer
3c799eb40b None is neither an allince, a corp or a character 2021-06-23 12:29:33 +02:00
Ariel Rin
0d7b8e315f tidy up missed pre-commits from master merge 2021-06-23 10:03:57 +10:00
Ariel Rin
ae7cfbff35 Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v2.9.x 2021-06-23 09:58:25 +10:00
Ariel Rin
a0a497ab33 Merge branch 'sanity-checks-and-editorconfig' into 'v2.9.x'
Basic code sanity checks and editorconfig

See merge request allianceauth/allianceauth!1316
2021-06-22 23:38:54 +00:00
Peter Pfeufer
fd86471a0f exluded .po, .mo and swagger.json from checks
For demonstration, the last empty line has been removed from swagger.json. Usually it would have been added again through pre-commit, but not with the exclusion here.
2021-06-22 16:42:10 +02:00
Ariel Rin
5fcb56a087 Merge branch 'notification_with_icons' into 'master'
Improve messages

See merge request allianceauth/allianceauth!1320
2021-06-21 10:39:56 +00:00
Erik Kalkoken
808080d5ee Improve messages 2021-06-21 10:39:56 +00:00
Ariel Rin
e6037f1680 Typo (Allince<Alliance) Credit @Thalimet 2021-06-21 10:23:40 +00:00
Ariel Rin
5c3ded6b07 Merge branch 'admin_performance_tuning' into 'master'
Admin performance tuning for group and state

See merge request allianceauth/allianceauth!1318
2021-06-01 11:40:32 +00:00
Erik Kalkoken
0c14e35d15 Admin performance tuning for group and state 2021-06-01 11:40:32 +00:00
Ariel Rin
c13be5d39a Merge branch 'ts3_update_button' into 'master'
Add button to update TS3 groups to admin page

See merge request allianceauth/allianceauth!1313
2021-06-01 11:38:24 +00:00
Erik Kalkoken
e4b515c1d5 Add button to update TS3 groups to admin page 2021-06-01 11:38:24 +00:00
Ariel Rin
65e2c87e8f Merge branch 'improve_eveonline_manager' into 'master'
Improve performance in EveCharacter manager

See merge request allianceauth/allianceauth!1314
2021-06-01 11:36:29 +00:00
Ariel Rin
68ce25854a Merge branch 'quick-syntax-fix-in-docu' into 'master'
quick fix in documentation

See merge request allianceauth/allianceauth!1317
2021-06-01 11:35:20 +00:00
Peter Pfeufer
1f80a02be9 version updated 2021-05-17 23:27:17 +02:00
Peter Pfeufer
3df6643513 this is a shell command ... 2021-05-17 17:41:28 +02:00
Peter Pfeufer
e6a4cea4de editorconfig applied 2021-05-17 11:42:28 +02:00
Peter Pfeufer
bad36a69e8 added editorconfig 2021-05-17 09:52:29 +02:00
Peter Pfeufer
2697fb5317 remove coding pragma. it's not needed since python 3.x 2021-05-17 09:51:09 +02:00
Peter Pfeufer
b47392ba7f check for LF as line ending 2021-05-17 09:49:58 +02:00
Peter Pfeufer
a99a375375 one and only one empty line at the end of the file 2021-05-17 09:48:57 +02:00
Peter Pfeufer
8c3df89d52 remove trailing whitespaces 2021-05-17 09:46:11 +02:00
Ariel Rin
6ea0ebc9f9 Merge branch 'db-install-docs-update' into 'master'
Added a quick hint that it's ok to leave the SQL shell, and some commas

See merge request allianceauth/allianceauth!1315
2021-05-16 02:38:43 +00:00
Peter Pfeufer
26236f5886 Added a quick hint that it's ok to leave the SQL shell, and some commas 2021-05-15 15:27:50 +02:00
ErikKalkoken
1420c71ec5 Improve get_character_by_id() 2021-05-15 13:58:23 +02:00
Ariel Rin
10bd77d761 Version Bump v2.9.0a2 2021-05-13 21:43:24 +10:00
Ariel Rin
192d286cf2 Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into v2.9.x 2021-05-13 21:38:22 +10:00
Ariel Rin
8ab9d2d5ad Merge branch 'revert-to-autofield' into 'v2.9.x'
revert migrations & set DEFAULT_AUTO_FIELD to django.db.models.AutoField

See merge request allianceauth/allianceauth!1312
2021-05-10 11:53:47 +00:00
Peter Pfeufer
3630812b92 revert migrations & set DEFAULT_AUTO_FIELD to django.db.models.AutoField 2021-05-10 13:35:40 +02:00
Ariel Rin
2f320bc256 Version Bump 2.9.0a1 2021-05-09 06:45:17 +00:00
Ariel Rin
45b8d42b8e Merge branch 'javascript-modernization' into 'v2.9.x'
Cleanup and modernize JS

See merge request allianceauth/allianceauth!1305
2021-05-09 06:43:58 +00:00
Peter Pfeufer
bd2d19f867 Cleanup and modernize JS 2021-05-09 06:43:58 +00:00
Ariel Rin
0be404baca Merge branch 'py36remove' into 'v2.9.x'
Remove Python36 Testing and Support

See merge request allianceauth/allianceauth!1301
2021-05-09 06:42:56 +00:00
Ariel Rin
e6cee9ac83 Remove Python36 Testing and Support 2021-05-09 06:42:56 +00:00
Ariel Rin
5a2c9243c4 Version Bump v2.8.4 2021-05-09 05:52:20 +00:00
Ariel Rin
fecd748198 Merge branch 'mumble-version' into 'master'
Model updates for Mumble Authenticator 1.1

See merge request allianceauth/allianceauth!1303
2021-05-09 05:41:05 +00:00
Ariel Rin
85351b2c66 Model updates for Mumble Authenticator 1.1 2021-05-09 05:41:05 +00:00
Ariel Rin
8b3e5b6462 Merge branch 'improve_eveonline_admin' into 'master'
Improve admin pages for eveonline models

See merge request allianceauth/allianceauth!1306
2021-05-09 05:39:46 +00:00
Erik Kalkoken
93af920b8f Improve admin pages for eveonline models 2021-05-09 05:39:46 +00:00
Ariel Rin
b1248d9845 Merge branch 'improve_groups_admin' into 'master'
Improve group management admin pages

See merge request allianceauth/allianceauth!1308
2021-05-09 05:38:28 +00:00
Erik Kalkoken
c86abef07d Improve group management admin pages 2021-05-09 05:38:28 +00:00
Ariel Rin
17ad5dfe33 Merge branch 'improve_authentication_admin' into 'master'
Improve authentication admin

See merge request allianceauth/allianceauth!1307
2021-05-09 05:31:45 +00:00
Erik Kalkoken
e1843fe1f1 Improve authentication admin 2021-05-09 05:31:45 +00:00
Ariel Rin
256a21f058 Merge branch 'fix_table_styles_for_dark_mode' into 'master'
Fix table styles and nav tabs for dark mode

See merge request allianceauth/allianceauth!1310
2021-05-09 05:25:45 +00:00
Erik Kalkoken
1473259e41 Fix table styles and nav tabs for dark mode 2021-05-09 05:25:45 +00:00
Ariel Rin
e935b91e93 Merge branch 'improve_gitlab_error_handling' into 'master'
Improve handling of gitlab HTTP errors

See merge request allianceauth/allianceauth!1311
2021-05-09 05:17:56 +00:00
ErikKalkoken
07258a6914 Log HTTP errors from gitlab as warning instead of error 2021-05-08 13:20:57 +02:00
Ariel Rin
cb35808508 Merge branch 'improve_ci_script' into 'master'
Remove redis ping and consolidate before steps

See merge request allianceauth/allianceauth!1309
2021-05-05 09:04:43 +00:00
Erik Kalkoken
937d656227 Remove redis ping and consolidate before steps 2021-05-05 09:04:43 +00:00
Ariel Rin
d173a59441 Merge branch 'add-robots-txt' into 'v2.9.x'
Adding robots.txt to fix random 404 message

See merge request allianceauth/allianceauth!1302
2021-04-27 14:11:14 +00:00
Ariel Rin
75a3adb2c9 Merge branch 'cron-fix' into 'master'
[Docs] Fix Discord Task Schedule

See merge request allianceauth/allianceauth!1304
2021-04-27 13:53:37 +00:00
colcrunch
148e208476 Update cron schedule for discord.update_all_usernames task in docs. 2021-04-18 22:14:52 -04:00
Peter Pfeufer
8f59f2549a robots.txt added
This fixes the randomly appearing 404 error in Auth  (issue #1232)
2021-04-15 20:59:25 +02:00
Ariel Rin
630400fee4 Merge branch 'django-3-2-update' into 'v2.9.x'
Django 3.2 compatibility update

See merge request allianceauth/allianceauth!1300
2021-04-08 05:07:01 +00:00
Ariel Rin
1ec6929e91 Merge branch 'python39' into 'v2.9.x'
Add  Python3.9 Testing

See merge request allianceauth/allianceauth!1284
2021-04-08 04:49:15 +00:00
Ariel Rin
8c6bdd8ae2 Add Python3.9 Testing 2021-04-08 04:49:15 +00:00
Peter Pfeufer
e04138bced migrations 2021-04-07 18:40:39 +02:00
Peter Pfeufer
db5ad85811 add new default for PKs 2021-04-07 18:16:33 +02:00
Peter Pfeufer
5b44fd376d revert version cap on Django, so we get 3.2 again 2021-04-07 18:11:01 +02:00
Ariel Rin
0036e8b280 Version Bump v2.8.3 2021-04-07 15:18:08 +00:00
Ariel Rin
ea2b5bfecf Merge branch 'srp-killmail-link-fix-issue-1282' into 'master'
Proper error message on non kill mail zkb links

See merge request allianceauth/allianceauth!1297
2021-04-07 15:12:56 +00:00
Peter Pfeufer
aa7495fa60 Proper error message on non kill mail zkb links 2021-04-07 15:12:56 +00:00
Ariel Rin
162ec1bd86 Merge branch 'docs-permissioncleanup' into 'master'
Permissions Cleanup Docs

See merge request allianceauth/allianceauth!1293
2021-04-07 13:23:28 +00:00
Ariel Rin
2668884008 Merge branch 'srp-copypaste' into 'master'
SRP Character Name Copy-Paste

See merge request allianceauth/allianceauth!1295
2021-04-07 13:22:24 +00:00
Ariel Rin
abdc3f3485 SRP Character Name Copy-Paste 2021-04-07 13:22:23 +00:00
Ariel Rin
911f21ee7c Merge branch 'add-support-discord-link' into 'master'
support discord link added to admin notifications on dashboard

See merge request allianceauth/allianceauth!1298
2021-04-07 13:21:26 +00:00
Ariel Rin
2387c40254 Limit Django to 3.1.x 2021-04-07 13:12:15 +00:00
Peter Pfeufer
76e18a79b3 support discord link added to admin notifications on dashboard 2021-04-06 10:44:12 +02:00
Ariel Rin
9725c9c947 Update .gitlab-ci.yml file 2021-03-27 08:38:40 +00:00
Aaron Kable
b1b79d1245 Refresh user profile on state change to force permision changes. 2021-03-26 18:20:39 +08:00
Ariel Rin
247ed7cc64 Merge branch 'nootification_log_spam' into 'master'
Use default by default in Notification system

See merge request allianceauth/allianceauth!1294
2021-02-25 11:04:18 +00:00
Aaron Kable
2fd2af793e Use default by defaut in notification system 2021-02-21 18:14:42 +08:00
Ariel Rin
3fc36b9ce1 Permissions Cleanup Tool 2021-02-18 13:33:23 +10:00
Ariel Rin
c12fd2d7bc Merge branch 'improve_developer_docs' into 'master'
Improve developer docs

See merge request allianceauth/allianceauth!1292
2021-02-18 01:52:15 +00:00
ErikKalkoken
7fe1ba2fb2 Improve docs for WSL setup 2021-02-17 09:50:01 +01:00
ErikKalkoken
ab7eb3e5df Fix sphinx issues in docs 2021-02-17 09:35:18 +01:00
Ariel Rin
3452c3acd1 Version Bump v2.8.2 2021-02-05 12:19:25 +00:00
Ariel Rin
3c305fbf37 Merge branch 'wheel' into 'master'
Build wheel

See merge request allianceauth/allianceauth!1290
2021-01-26 12:59:56 +00:00
Rebecca Claire Murphy
a5e0721ec1 Build wheel 2021-01-26 12:59:56 +00:00
Ariel Rin
164a0d5376 Merge branch 'gitlab-announcements-fix' into 'master'
Only show open announcements

See merge request allianceauth/allianceauth!1291
2021-01-26 12:55:11 +00:00
Ariel Rin
2bcf6ec39a Merge branch 'master' into 'master'
Squash mumble migrations

See merge request allianceauth/allianceauth!1289
2021-01-26 12:30:35 +00:00
Peter Pfeufer
40824156bf Only show open announcements 2021-01-25 21:13:43 +01:00
Rebecca Claire Murphy
cec4495034 Merge branch 'master' of gitlab.com:allianceauth/allianceauth 2021-01-15 19:43:39 -05:00
Ariel Rin
74eb8621d9 Merge branch 'notifications_refresh' into 'master'
Notifications refresh v2 and session caching

See merge request allianceauth/allianceauth!1218
2021-01-16 00:09:49 +00:00
Erik Kalkoken
4394d25961 Notifications refresh v2 and session caching 2021-01-16 00:09:49 +00:00
Rebecca Claire Murphy
8113327d31 Squash mumble migrations 2021-01-08 12:28:12 -05:00
Ariel Rin
aeeb35bc60 Merge branch 'ceo_id' into 'master'
Add CEO ID to Corp Eve Model

See merge request allianceauth/allianceauth!1287
2021-01-08 10:37:33 +00:00
colcrunch
630aa3f320 Merge branch 'doc-fixes' into 'master'
It's TeamSpeak not Discord here

See merge request allianceauth/allianceauth!1288
2021-01-07 19:16:22 +00:00
Peter Pfeufer
3487a945c2 It's TeamSpeak not Discord here 2021-01-06 10:42:01 +01:00
Ariel Rin
1936ae44a3 Merge branch 'opengroupauditlogs' into 'master'
Add Open Group Audit Logs

Closes #1262

See merge request allianceauth/allianceauth!1279
2021-01-06 02:38:48 +00:00
Ariel Rin
3d7a84e786 Revert "Add Audit Logs to Open Groups and Autoleaves"
This reverts commit f64d81292e8b6c0531d610799098c3e3cf105c87.
2021-01-06 02:38:47 +00:00
Ariel Rin
a4d6730cb0 Merge branch 'docs' into 'master'
Document Permissions and other Docs Improvments

Closes #1253

See merge request allianceauth/allianceauth!1283
2021-01-06 02:37:29 +00:00
Ariel Rin
b724227a29 Merge branch 'dependencies' into 'master'
Bump Django-Redis-Cache to 3.x branch

Closes #1264

See merge request allianceauth/allianceauth!1280
2021-01-06 02:36:52 +00:00
AaronKable
d72964fd7c Add CEO Id to Corp Eve Model 2021-01-05 22:14:52 +08:00
Ariel Rin
d4a41cfb60 Merge branch 'transifex' into 'master'
Update from Transifex

See merge request allianceauth/allianceauth!1285
2020-12-28 07:11:50 +00:00
Ariel Rin
05859453df Update from Transifex 2020-12-28 07:11:50 +00:00
Ariel Rin
240d910c9b Document Permissions and other Docs Improvments 2020-12-26 16:10:42 +10:00
Ariel Rin
0c31bce7d0 Bump Django-Redis-Cache up to 3.x branch 2020-12-23 17:59:36 +10:00
Ariel Rin
f4c4ae36ed Merge branch 'fix_celeryonce_dependency' into 'master'
Prevent autoretry_for bug in celery_once

See merge request allianceauth/allianceauth!1278
2020-12-19 02:08:36 +00:00
Ariel Rin
b9931b2ceb Merge branch 'improve_groups_state_binding' into 'master'
Enable group state restrictions to work with internal groups

See merge request allianceauth/allianceauth!1276
2020-12-19 02:05:05 +00:00
Erik Kalkoken
4fd6f06c0b Enable group state restrictions to work with internal groups 2020-12-19 02:05:05 +00:00
ErikKalkoken
09573ba7ef fix dependency for celery_once 2020-12-17 22:53:27 +01:00
Ariel Rin
08e9676760 Version Bump v2.8.1 2020-12-07 04:46:55 +00:00
Ariel Rin
15ae737522 Merge branch 'user-agent' into 'master'
Bump minimum django-esi and add User Agent header.

See merge request allianceauth/allianceauth!1275
2020-12-07 04:32:10 +00:00
Ariel Rin
50ec9e563e Merge branch 'celery_result_logger' into 'master'
Remove result from default log message on task success

See merge request allianceauth/allianceauth!1273
2020-12-07 04:30:52 +00:00
colcrunch
cb40649f8b Add note to local.py on ESI_CONTACT_EMAIL setting 2020-12-06 22:54:31 -05:00
colcrunch
bccead0881 Add mention of ESI_USER_CONTACT_EMAIL to install documentation. 2020-12-02 17:42:03 -05:00
colcrunch
35ae710624 Add ESI_USER_CONTACT_EMAIL setting to local.py 2020-12-01 23:25:48 -05:00
colcrunch
75de4518f2 Use app_info_text in EveSwaggerProvider to set the User-Agent header on ESI requests. 2020-12-01 22:40:09 -05:00
colcrunch
bfcdfea6c8 Update minimum django-esi version to ensure support for user-agent header. 2020-12-01 17:17:55 -05:00
Ariel Rin
471553fa88 Add a minimal set of django deps needed to build docs 2020-12-01 07:14:22 +00:00
Ariel Rin
db59f5f69b Add Django to docs requirements 2020-11-30 14:41:01 +00:00
Ariel Rin
1b5413646e Correct Typos in Docs Requirements 2020-11-30 14:37:42 +00:00
Ariel Rin
0337d2517c use limited set of python packages for docs 2020-11-30 14:30:23 +00:00
ErikKalkoken
87e6eb9688 Remove result from default log message on task success 2020-11-28 22:10:41 +01:00
Ariel Rin
7b8c246ef8 Merge branch 'master' into 'master'
[Quickfix] Missing ^ added

See merge request allianceauth/allianceauth!1271
2020-11-19 13:26:53 +00:00
Peter Pfeufer
a268a8980a Missing ^ added 2020-11-18 22:50:27 +01:00
Ariel Rin
3b516c338e Merge branch 'groupmanagement-revamp' into 'master'
Group management improvements

See merge request allianceauth/allianceauth!1270
2020-11-17 11:56:16 +00:00
Peter Pfeufer
9952685805 textfield should not be Null=True 2020-11-16 18:04:57 +01:00
Peter Pfeufer
2f59c8df22 And of course I missed something ... 2020-10-26 01:10:41 +01:00
Peter Pfeufer
6cd0a42791 reverted code formatting for Aaron :-) 2020-10-26 01:06:58 +01:00
Peter Pfeufer
4c42153bfd stop member count header from line breaking 2020-10-25 13:53:17 +01:00
Peter Pfeufer
603bd9c37c make description a real description
textfield instead of charfield. a bit more user friendly in the backend
2020-10-25 12:40:10 +01:00
Peter Pfeufer
87c0c3ac73 tables formatted properly
align center is horrible to read ....
2020-10-25 11:55:03 +01:00
Peter Pfeufer
8a91e7f6ac navactive status fixed in left menu and top menu 2020-10-25 11:22:10 +01:00
Peter Pfeufer
281dbdbb01 proper URL structure established 2020-10-25 11:21:35 +01:00
Ariel Rin
8980d8d32f Version bump to 2.8.0 Stable 2020-10-13 05:33:54 +00:00
Ariel Rin
9d6cf9a62e Merge branch 'transifex' into 'master'
Add French and Japanese Translations + QoL Translation Fixes

See merge request allianceauth/allianceauth!1263
2020-10-13 04:17:37 +00:00
Ariel Rin
0fabb2b368 Add French and Japanese Translations + QoL Translation Fixes 2020-10-13 04:17:37 +00:00
Ariel Rin
9801ca0314 Merge branch 'emailoverrideredirect' into 'master'
Fix Redirect to dashboard if not verifying email

See merge request allianceauth/allianceauth!1268
2020-10-13 04:08:15 +00:00
Ariel Rin
fd86b26b39 Redirect to dashboard if not verifying email 2020-10-13 13:25:00 +10:00
Ariel Rin
b0dbef1587 Merge branch 'master' into 'master'
Expand mumble pwhash field to allow for passlib 1.7.3+

See merge request allianceauth/allianceauth!1267
2020-10-11 10:34:44 +00:00
Ariel Rin
a6e60bc23b expand mumble certhash to allow for hmac-sha-256 2020-10-11 20:11:56 +10:00
Ariel Rin
137e8a876d Merge branch 'add-corp-and-alliance-tags-to-srp' into 'master'
Add alliance and corp ticker to pilot name

See merge request allianceauth/allianceauth!1265
2020-10-11 03:42:56 +00:00
Ariel Rin
fe9538253f Merge branch 'highlight-active-menu-item' into 'master'
Highlight active menu item

See merge request allianceauth/allianceauth!1266
2020-10-11 03:41:17 +00:00
Peter Pfeufer
edda2c248e highlight active menu item 2020-10-02 20:12:21 +02:00
Peter Pfeufer
62275639e3 Added alliance and corp ticker to pilot name
Takes care of https://gitlab.com/allianceauth/allianceauth/-/issues/1228
2020-10-01 20:14:54 +02:00
Ariel Rin
d0c68b82f4 Merge branch 'docs' into 'master'
Docs Sphinx Upgrades

See merge request allianceauth/allianceauth!1264
2020-10-01 09:17:36 +00:00
Ariel Rin
78b5953bdf Merge branch 'master' of https://gitlab.com/allianceauth/allianceauth into docs 2020-10-01 18:59:14 +10:00
Ariel Rin
25e565b099 Docs Requirement upgrades 2020-10-01 18:54:04 +10:00
Ariel Rin
1fe0f78ad7 Exclude django-redis-cache 2.1.3 2020-10-01 01:59:48 +00:00
Ariel Rin
fc8b68156f Merge branch 'patch-4' into 'master'
Fixing FA icon

See merge request allianceauth/allianceauth!1262
2020-09-29 05:10:30 +00:00
Peter Pfeufer
b47cd197ce Fixing FA icon 2020-09-28 21:07:08 +00:00
Ariel Rin
3943426c4c Version Bump 2.8.0a2 2020-09-21 06:25:16 +00:00
Ariel Rin
08a9bd42a3 Merge branch 'django3' into 'master'
Django 3.1.1 bring up

See merge request allianceauth/allianceauth!1256
2020-09-21 06:16:44 +00:00
Ariel Rin
b2ff339efe Merge branch 'gitlabci2' into 'master'
Update Gitlab Deploy Python Version, Debian Distro to Stable

See merge request allianceauth/allianceauth!1260
2020-09-21 06:15:40 +00:00
Ariel Rin
c604131e04 Update Deploy Runner 2020-09-21 12:12:02 +10:00
Ariel Rin
f17607f126 Merge branch 'transifex' into 'master'
Update From Transifex

See merge request allianceauth/allianceauth!1258
2020-09-21 01:45:16 +00:00
Ariel Rin
94e455a57b Update From Transifex 2020-09-21 01:45:16 +00:00
Ariel Rin
3c9149db4a Merge branch 'improve_authtuilts_add_permission' into 'master'
Improve auth utils for permissions

See merge request allianceauth/allianceauth!1255
2020-09-21 01:09:03 +00:00
Ariel Rin
96cc615c07 Merge branch 'docs_mumbleavatars' into 'master'
Docs: Mumble Avatars Feature

See merge request allianceauth/allianceauth!1250
2020-09-21 00:01:35 +00:00
Ariel Rin
cd1f4a1c2b Docs: Mumble Avatars Feature 2020-09-21 00:01:35 +00:00
Ariel Rin
e0f99a42db Merge branch 'exiom-srp-update' into 'master'
SRP Module - Added Datatables & Sorting, Standardized Date/Time for Overall AA Consistency

See merge request allianceauth/allianceauth!1254
2020-09-20 23:55:22 +00:00
Exiom
3506e417d4 SRP Module - Added Datatables & Sorting, Standardized Date/Time for Overall AA Consistency 2020-09-20 23:55:22 +00:00
AaronKable
36197e2212 swap to reverse_lazy 2020-09-18 23:32:36 +08:00
AaronKable
fc5f42d01e remove whitespace in setup.py 2020-09-18 22:16:49 +08:00
AaronKable
e26d3767e0 update models as NullBooleanField is deprecated. 2020-09-18 22:16:24 +08:00
AaronKable
046b37c76a update auth and group model admins for django 3.1 2020-09-18 22:04:59 +08:00
AaronKable
925ff3e116 remove django req's from tox, they are managed in setup.py 2020-09-18 22:04:05 +08:00
AaronKable
e5ede4f7b6 Cant Reference what is already deleted 2020-09-18 22:03:16 +08:00
AaronKable
ded9301527 initial django3 bringup 2020-09-18 11:45:37 +08:00
ErikKalkoken
bd1ed6ff73 Add user as return value to add permission methods 2020-09-15 12:35:58 +02:00
Ariel Rin
feb65980d4 Merge branch 'fix_group_count_badge' into 'master'
Fix group count badge showing at zero

Closes #1258

See merge request allianceauth/allianceauth!1253
2020-09-12 02:05:22 +00:00
Ariel Rin
e81d75a782 Merge branch 'docs_settings_fix' into 'master'
Remove erroneous indents from settings in service module docs

See merge request allianceauth/allianceauth!1252
2020-09-12 02:04:37 +00:00
ErikKalkoken
ff305d13ae Fix group count badge showing at zero 2020-09-11 23:54:56 +02:00
colcrunch
8bcbc1a779 Remove erroneous indents from settings in service module docs. (Checked other docs, and there do not appear to be any more errors of this type) 2020-09-11 12:51:17 -04:00
Ariel Rin
3874aa6fee Version Bump 2.8.0a1 2020-09-11 11:52:20 +00:00
Ariel Rin
103e9f3a11 Merge branch 'discourse_beta' into 'master'
Discourse API with external package

See merge request allianceauth/allianceauth!1251
2020-09-11 11:33:19 +00:00
Ariel Rin
d02c25f421 Merge branch 'feature_menu_item_badges' into 'master'
Add menu item badge feature and update group icons

See merge request allianceauth/allianceauth!1240
2020-09-11 11:33:14 +00:00
Erik Kalkoken
228af38a4a Add menu item badge feature and update group icons 2020-09-11 11:33:14 +00:00
AaronKable
051a48885c discourse API with external package 2020-09-11 17:19:54 +08:00
Ariel Rin
6bcdc6052f Merge branch 'local-delivery' into 'master'
JS/CSS/Font Refactoring for use with AA-GDPR Package

Closes #1217

See merge request allianceauth/allianceauth!1247
2020-09-11 04:13:01 +00:00
Ariel Rin
af3527e64f Revert "load bootswatch less locally #1217"
This reverts commit 3a9a7267ea8734ba0a5cf1d0fea632eb2276d45c.
2020-09-11 04:13:01 +00:00
Ariel Rin
17ef3dd07a Merge branch 'srp_fix' into 'master'
Use request.scheme to get the http/https for the site

See merge request allianceauth/allianceauth!1249
2020-09-11 03:05:06 +00:00
AaronKable
1f165ecd2a use request.scheme to get the http/https for the site 2020-09-07 19:10:58 +08:00
Ariel Rin
70d1d450a9 Add Korean and Russian as features 2020-09-03 12:24:23 +00:00
Ariel Rin
b667892698 Version Bump 2.7.5 2020-09-01 02:09:05 +00:00
Ariel Rin
dc11add0e9 Merge branch 'discordapp.com-deprecation' into 'master'
discord.com replaces discordapp.com

See merge request allianceauth/allianceauth!1248
2020-09-01 01:53:25 +00:00
Ariel Rin
cb429a0b88 discord.com replaces discordapp.com 2020-09-01 11:20:57 +10:00
Ariel Rin
b51039cfc0 Merge branch 'docs' into 'master'
Add Optimizing Mumble to Services docs

See merge request allianceauth/allianceauth!1243
2020-09-01 01:11:49 +00:00
Ariel Rin
eadd959d95 Merge branch 'fix_celery_once_backend' into 'master'
Fix celery once not working properly

See merge request allianceauth/allianceauth!1246
2020-08-25 06:13:59 +00:00
Erik Kalkoken
1856e03d88 Fix celery once not working properly 2020-08-25 06:13:59 +00:00
Ariel Rin
7dcfa622a3 Merge branch 'issue_1234_exiom' into 'master'
Month Ordering Fix for Group Management Audit Logs

See merge request allianceauth/allianceauth!1245
2020-08-25 06:07:45 +00:00
Exiom
64251b9b3c Fix Date/Time Month Ordering #1234 2020-08-21 03:57:24 +00:00
Exiom
6b073dd5fc Fix Date/Time Month Ordering #1234 2020-08-21 03:33:35 +00:00
Ariel Rin
0911fabfb2 Merge branch 'issue_1234' into 'master'
Correct Month Ordering in Group Management Audit Logs

Closes #1234

See merge request allianceauth/allianceauth!1244
2020-08-20 07:11:47 +00:00
Ariel Rin
050d3f5e63 use month numerical 2020-08-20 16:55:18 +10:00
Ariel Rin
bbe3f78ad1 Grammar and Spelling Corrections 2020-08-20 15:19:50 +10:00
Ariel Rin
8204c18895 Add Optimzing Mumble 2020-08-20 15:09:07 +10:00
Ariel Rin
b91c788897 Merge branch 'bulk-affiliations' into 'master'
Reduce run time for eve online model updates

See merge request allianceauth/allianceauth!1227
2020-08-20 03:14:40 +00:00
Erik Kalkoken
1d20a3029f Only update characters if they have changed corp or alliance by bulk calling affiliations before calling character tasks. 2020-08-20 03:14:40 +00:00
Ariel Rin
9cfebc9ae3 Version Bump to 2.7.4 2020-08-17 06:34:55 +00:00
Ariel Rin
ddabb4539b Merge branch 'fix_admin_status_tags_bug' into 'master'
Bugfix: Loading of dashboard fails with 'NoneType object is not iterable' from status_tags

See merge request allianceauth/allianceauth!1237
2020-08-14 02:26:12 +00:00
Ariel Rin
ada35e221b Merge branch 'transifex' into 'master'
Update from Transifex

See merge request allianceauth/allianceauth!1241
2020-08-14 02:24:07 +00:00
Ariel Rin
6fef9d904e Update from Transifex 2020-08-14 02:24:07 +00:00
Ariel Rin
67cf2b5904 Merge branch 'fontawesomev5' into 'master'
Remove FA v4 shims, bump FA to 5.14

Closes #1248

See merge request allianceauth/allianceauth!1242
2020-08-14 02:21:41 +00:00
Ariel Rin
3bebe792f6 Remove FA v4 shims, bump to fa 5.14 2020-08-14 12:01:08 +10:00
ErikKalkoken
00b4d89181 Fix status tags bug and remove unused context from status_overview tag 2020-07-27 14:53:25 +02:00
Ariel Rin
f729c6b650 Merge branch 'patch-3' into 'master'
Update Mumble documentation on setting a server password

Closes #1252

See merge request allianceauth/allianceauth!1236
2020-07-24 11:18:02 +00:00
colcrunch
df95f8c3f3 Merge branch 'discord_improvements' into 'master'
Fix error 500 on service page for Discord and add feature "group_to_role"

See merge request allianceauth/allianceauth!1235
2020-07-23 20:58:26 +00:00
Erik Kalkoken
fe36e57d72 Fix error 500 on service page for Discord and add feature "group_to_role" 2020-07-23 20:58:26 +00:00
Peter Pfeufer
31197812b6 Update Mumble documentation on setting a server password (#1252) 2020-07-22 12:18:52 +00:00
Ariel Rin
bd3fe01a12 correct task import for manual model population 2020-07-14 12:25:05 +00:00
470 changed files with 14871 additions and 6284 deletions

24
.editorconfig Normal file
View File

@@ -0,0 +1,24 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
tab_width = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{yaml,yml,less}]
indent_size = 2
[*.md]
indent_size = 2
# Makefiles always use tabs for indentation
[Makefile]
indent_style = tab
[*.bat]
indent_style = tab

View File

@@ -1,53 +1,106 @@
stages: stages:
- pre-commit
- gitlab
- test - test
- deploy - deploy
before_script: include:
- apt-get update && apt-get install redis-server -y - template: Dependency-Scanning.gitlab-ci.yml
- redis-server --daemonize yes - template: Security/SAST.gitlab-ci.yml
- redis-cli ping
- python -V
- pip install wheel tox
test-3.6-core: before_script:
- apt-get update && apt-get install redis-server -y
- redis-server --daemonize yes
- python -V
- pip install wheel tox
pre-commit-check:
stage: pre-commit
image: python:3.6-buster image: python:3.6-buster
variables:
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
cache:
paths:
- ${PRE_COMMIT_HOME}
script: script:
- tox -e py36-core - pip install pre-commit
- pre-commit run --all-files
sast:
stage: gitlab
before_script: []
dependency_scanning:
stage: gitlab
before_script:
- apt-get update && apt-get install redis-server libmariadb-dev -y
- redis-server --daemonize yes
- python -V
- pip install wheel tox
test-3.7-core: test-3.7-core:
image: python:3.7-buster image: python:3.7-bullseye
script: script:
- tox -e py37-core - tox -e py37-core
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.8-core: test-3.8-core:
image: python:3.8-buster image: python:3.8-bullseye
script: script:
- tox -e py38-core - tox -e py38-core
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.6-all: test-3.9-core:
image: python:3.6-buster image: python:3.9-bullseye
script: script:
- tox -e py36-all - tox -e py39-core
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.7-all: test-3.7-all:
image: python:3.7-buster image: python:3.7-bullseye
script: script:
- tox -e py37-all - tox -e py37-all
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.8-all: test-3.8-all:
image: python:3.8-buster image: python:3.8-bullseye
script: script:
- tox -e py38-all - tox -e py38-all
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.9-all:
image: python:3.9-bullseye
script:
- tox -e py39-all
artifacts:
when: always
reports:
cobertura: coverage.xml
deploy_production: deploy_production:
stage: deploy stage: deploy
image: python:3.6-stretch image: python:3.9-bullseye
before_script: before_script:
- pip install twine - pip install twine wheel
script: script:
- python setup.py sdist - python setup.py sdist bdist_wheel
- twine upload dist/* - twine upload dist/*
rules: rules:

28
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,28 @@
# Apply to all files without committing:
# pre-commit run --all-files
# Update this file:
# pre-commit autoupdate
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
hooks:
- id: check-case-conflict
- id: check-json
- id: check-xml
- id: check-yaml
- id: fix-byte-order-marker
- id: trailing-whitespace
exclude: (\.min\.css|\.min\.js|\.mo|\.po|swagger\.json)$
- id: end-of-file-fixer
exclude: (\.min\.css|\.min\.js|\.mo|\.po|swagger\.json)$
- id: mixed-line-ending
args: [ '--fix=lf' ]
- id: fix-encoding-pragma
args: [ '--remove' ]
- repo: https://github.com/editorconfig-checker/editorconfig-checker.python
rev: 2.3.5
hooks:
- id: editorconfig-checker
exclude: ^(LICENSE|allianceauth\/static\/css\/themes\/bootstrap-locals.less|allianceauth\/eveonline\/swagger.json|(.*.po)|(.*.mo))

View File

@@ -20,8 +20,4 @@ formats: all
python: python:
version: 3.7 version: 3.7
install: install:
- method: pip - requirements: docs/requirements.txt
path: .
extra_requirements:
- testing
system_packages: true

View File

@@ -337,4 +337,3 @@ proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. Public License instead of this License.

View File

@@ -36,7 +36,7 @@ Main features:
- Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations) - 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)
- Chinese :cn:, English :us:, German :de: and Spanish :es: localization - English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr: and Russian :flag_ru: 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](http://allianceauth.rtfd.io).

View File

@@ -1,7 +1,7 @@
# 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__ = '2.7.3' __version__ = '2.9.0b1'
__title__ = 'Alliance Auth' __title__ = 'Alliance Auth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth' __url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = '%s v%s' % (__title__, __version__) NAME = '%s v%s' % (__title__, __version__)

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,21 @@
[
{
"model": "analytics.AnalyticsTokens",
"pk": 1,
"fields": {
"name": "AA Team Public Google Analytics (Universal)",
"type": "GA-V4",
"token": "UA-186249766-2",
"send_page_views": "False",
"send_celery_tasks": "False",
"send_stats": "False"
}
},
{
"model": "analytics.AnalyticsIdentifier",
"pk": 1,
"fields": {
"identifier": "ab33e241fbf042b6aa77c7655a768af7"
}
}
]

View File

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

View File

@@ -0,0 +1,42 @@
# Generated by Django 3.1.4 on 2020-12-30 13:11
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='AnalyticsIdentifier',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('identifier', models.UUIDField(default=uuid.uuid4, editable=False)),
],
),
migrations.CreateModel(
name='AnalyticsPath',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ignore_path', models.CharField(default='/example/', max_length=254)),
],
),
migrations.CreateModel(
name='AnalyticsTokens',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=254)),
('type', models.CharField(choices=[('GA-U', 'Google Analytics Universal'), ('GA-V4', 'Google Analytics V4')], max_length=254)),
('token', models.CharField(max_length=254)),
('send_page_views', models.BooleanField(default=False)),
('send_celery_tasks', models.BooleanField(default=False)),
('send_stats', models.BooleanField(default=False)),
('ignore_paths', models.ManyToManyField(blank=True, to='analytics.AnalyticsPath')),
],
),
]

View File

@@ -0,0 +1,34 @@
# Generated by Django 3.1.4 on 2020-12-30 08:53
from django.db import migrations
def add_aa_team_token(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
token = Tokens()
token.type = 'GA-U'
token.token = 'UA-186249766-2'
token.send_page_views = True
token.send_celery_tasks = True
token.send_stats = True
token.name = 'AA Team Public Google Analytics (Universal)'
token.save()
def remove_aa_team_token(apps, schema_editor):
# Have to define some code to remove this identifier
# In case of migration rollback?
Tokens = apps.get_model('analytics', 'AnalyticsTokens')
token = Tokens.objects.filter(token="UA-186249766-2").delete()
class Migration(migrations.Migration):
dependencies = [
('analytics', '0001_initial'),
]
operations = [migrations.RunPython(add_aa_team_token, remove_aa_team_token)
]

View File

@@ -0,0 +1,30 @@
# Generated by Django 3.1.4 on 2020-12-30 08:53
from uuid import uuid4
from django.db import migrations
def generate_identifier(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
Identifier = apps.get_model('analytics', 'AnalyticsIdentifier')
identifier = Identifier()
identifier.id = 1
identifier.save()
def zero_identifier(apps, schema_editor):
# Have to define some code to remove this identifier
# In case of migration rollback?
Identifier = apps.get_model('analytics', 'AnalyticsIdentifier')
Identifier.objects.filter(id=1).delete()
class Migration(migrations.Migration):
dependencies = [
('analytics', '0002_add_AA_Team_Token'),
]
operations = [migrations.RunPython(generate_identifier, zero_identifier)
]

View File

@@ -0,0 +1,38 @@
from django.db import models
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from uuid import uuid4
class AnalyticsIdentifier(models.Model):
identifier = models.UUIDField(default=uuid4,
editable=False)
def save(self, *args, **kwargs):
if not self.pk and AnalyticsIdentifier.objects.exists():
# Force a single object
raise ValidationError('There is can be only one \
AnalyticsIdentifier instance')
self.pk = self.id = 1 # If this happens to be deleted and recreated, force it to be 1
return super(AnalyticsIdentifier, self).save(*args, **kwargs)
class AnalyticsPath(models.Model):
ignore_path = models.CharField(max_length=254, default="/example/")
class AnalyticsTokens(models.Model):
class Analytics_Type(models.TextChoices):
GA_U = 'GA-U', _('Google Analytics Universal')
GA_V4 = 'GA-V4', _('Google Analytics V4')
name = models.CharField(max_length=254)
type = models.CharField(max_length=254, choices=Analytics_Type.choices)
token = models.CharField(max_length=254, blank=False)
send_page_views = models.BooleanField(default=False)
send_celery_tasks = models.BooleanField(default=False)
send_stats = models.BooleanField(default=False)
ignore_paths = models.ManyToManyField(AnalyticsPath, blank=True)

View File

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

View File

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

View File

View File

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

View File

@@ -0,0 +1,26 @@
from allianceauth.analytics.models import AnalyticsIdentifier
from django.core.exceptions import ValidationError
from django.test.testcases import TestCase
from uuid import UUID, uuid4
# Identifiers
uuid_1 = "ab33e241fbf042b6aa77c7655a768af7"
uuid_2 = "7aa6bd70701f44729af5e3095ff4b55c"
class TestAnalyticsIdentifier(TestCase):
def test_identifier_random(self):
self.assertNotEqual(AnalyticsIdentifier.objects.get(), 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

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

View File

@@ -0,0 +1,55 @@
from django.apps import apps
from allianceauth.authentication.models import User
from esi.models import Token
from allianceauth.analytics.utils import install_stat_users, install_stat_tokens, install_stat_addons
from django.test.testcases import TestCase
def create_testdata():
User.objects.all().delete()
User.objects.create_user(
'user_1'
'abc@example.com',
'password'
)
User.objects.create_user(
'user_2'
'abc@example.com',
'password'
)
#Token.objects.all().delete()
#Token.objects.create(
# character_id=101,
# character_name='character1',
# access_token='my_access_token'
#)
#Token.objects.create(
# character_id=102,
# character_name='character2',
# access_token='my_access_token'
#)
class TestAnalyticsUtils(TestCase):
def test_install_stat_users(self):
create_testdata()
expected = 2
users = install_stat_users()
self.assertEqual(users, expected)
#def test_install_stat_tokens(self):
# create_testdata()
# expected = 2
#
# tokens = install_stat_tokens()
# self.assertEqual(tokens, expected)
def test_install_stat_addons(self):
# this test does what its testing...
# but helpful for existing as a sanity check
expected = len(list(apps.get_app_configs()))
addons = install_stat_addons()
self.assertEqual(addons, expected)

View File

@@ -0,0 +1,36 @@
from django.apps import apps
from allianceauth.authentication.models import User
from esi.models import Token
def install_stat_users() -> int:
"""Count and Return the number of User accounts
Returns
-------
int
The Number of User objects"""
users = User.objects.count()
return users
def install_stat_tokens() -> int:
"""Count and Return the number of ESI Tokens Stored
Returns
-------
int
The Number of Token Objects"""
tokens = Token.objects.count()
return tokens
def install_stat_addons() -> int:
"""Count and Return the number of Django Applications Installed
Returns
-------
int
The Number of Installed Apps"""
addons = len(list(apps.get_app_configs()))
return addons

View File

@@ -1,10 +1,8 @@
from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User as BaseUser, \ from django.contrib.auth.models import User as BaseUser, \
Permission as BasePermission, Group Permission as BasePermission, Group
from django.db.models import Q, F from django.db.models import Count, Q
from allianceauth.services.hooks import ServicesHook from allianceauth.services.hooks import ServicesHook
from django.db.models.signals import pre_save, post_save, pre_delete, \ from django.db.models.signals import pre_save, post_save, pre_delete, \
post_delete, m2m_changed post_delete, m2m_changed
@@ -24,11 +22,6 @@ from allianceauth.eveonline.tasks import update_character
from .app_settings import AUTHENTICATION_ADMIN_USERS_MAX_GROUPS, \ from .app_settings import AUTHENTICATION_ADMIN_USERS_MAX_GROUPS, \
AUTHENTICATION_ADMIN_USERS_MAX_CHARS AUTHENTICATION_ADMIN_USERS_MAX_CHARS
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True
else:
_has_auto_groups = False
def make_service_hooks_update_groups_action(service): def make_service_hooks_update_groups_action(service):
""" """
@@ -92,7 +85,6 @@ class UserProfileInline(admin.StackedInline):
query |= Q(userprofile__isnull=True) query |= Q(userprofile__isnull=True)
else: else:
query |= Q(character_ownership__user=obj) query |= Q(character_ownership__user=obj)
qs = EveCharacter.objects.filter(query)
formset = super().get_formset(request, obj=obj, **kwargs) formset = super().get_formset(request, obj=obj, **kwargs)
def get_kwargs(self, index): def get_kwargs(self, index):
@@ -100,7 +92,7 @@ class UserProfileInline(admin.StackedInline):
formset.get_form_kwargs = get_kwargs formset.get_form_kwargs = get_kwargs
return formset return formset
def has_add_permission(self, request): def has_add_permission(self, request, obj=None):
return False return False
def has_delete_permission(self, request, obj=None): def has_delete_permission(self, request, obj=None):
@@ -121,6 +113,8 @@ def user_profile_pic(obj):
) )
else: else:
return None return None
user_profile_pic.short_description = '' user_profile_pic.short_description = ''
@@ -152,6 +146,7 @@ def user_username(obj):
user_obj.username, user_obj.username,
) )
user_username.short_description = 'user / main' user_username.short_description = 'user / main'
user_username.admin_order_field = 'username' user_username.admin_order_field = 'username'
@@ -168,7 +163,8 @@ def user_main_organization(obj):
else: else:
corporation = user_obj.profile.main_character.corporation_name corporation = user_obj.profile.main_character.corporation_name
if user_obj.profile.main_character.alliance_id: if user_obj.profile.main_character.alliance_id:
result = format_html('{}<br>{}', result = format_html(
'{}<br>{}',
corporation, corporation,
user_obj.profile.main_character.alliance_name user_obj.profile.main_character.alliance_name
) )
@@ -176,6 +172,7 @@ def user_main_organization(obj):
result = corporation result = corporation
return result return result
user_main_organization.short_description = 'Corporation / Alliance (Main)' user_main_organization.short_description = 'Corporation / Alliance (Main)'
user_main_organization.admin_order_field = \ user_main_organization.admin_order_field = \
'profile__main_character__corporation_name' 'profile__main_character__corporation_name'
@@ -205,13 +202,13 @@ class MainCorporationsFilter(admin.SimpleListFilter):
return qs.all() return qs.all()
else: else:
if qs.model == User: if qs.model == User:
return qs\ return qs.filter(
.filter(profile__main_character__corporation_id=\ profile__main_character__corporation_id=self.value()
self.value()) )
else: else:
return qs\ return qs.filter(
.filter(user__profile__main_character__corporation_id=\ user__profile__main_character__corporation_id=self.value()
self.value()) )
class MainAllianceFilter(admin.SimpleListFilter): class MainAllianceFilter(admin.SimpleListFilter):
@@ -239,12 +236,11 @@ class MainAllianceFilter(admin.SimpleListFilter):
return qs.all() return qs.all()
else: else:
if qs.model == User: if qs.model == User:
return qs\ return qs.filter(profile__main_character__alliance_id=self.value())
.filter(profile__main_character__alliance_id=self.value())
else: else:
return qs\ return qs.filter(
.filter(user__profile__main_character__alliance_id=\ user__profile__main_character__alliance_id=self.value()
self.value()) )
def update_main_character_model(modeladmin, request, queryset): def update_main_character_model(modeladmin, request, queryset):
@@ -259,6 +255,7 @@ def update_main_character_model(modeladmin, request, queryset):
'Update from ESI started for {} characters'.format(tasks_count) 'Update from ESI started for {} characters'.format(tasks_count)
) )
update_main_character_model.short_description = \ update_main_character_model.short_description = \
'Update main character model from ESI' 'Update main character model from ESI'
@@ -267,7 +264,6 @@ class UserAdmin(BaseUserAdmin):
"""Extending Django's UserAdmin model """Extending Django's UserAdmin model
Behavior of groups and characters columns can be configured via settings Behavior of groups and characters columns can be configured via settings
""" """
class Media: class Media:
@@ -275,24 +271,9 @@ class UserAdmin(BaseUserAdmin):
"all": ("authentication/css/admin.css",) "all": ("authentication/css/admin.css",)
} }
class RealGroupsFilter(admin.SimpleListFilter): def get_queryset(self, request):
"""Custom filter to get groups w/o Autogroups""" qs = super().get_queryset(request)
title = 'group' return qs.prefetch_related("character_ownerships__character", "groups")
parameter_name = 'group_id__exact'
def lookups(self, request, model_admin):
qs = Group.objects.all().order_by(Lower('name'))
if _has_auto_groups:
qs = qs\
.filter(managedalliancegroup__isnull=True)\
.filter(managedcorpgroup__isnull=True)
return tuple([(x.pk, x.name) for x in qs])
def queryset(self, request, queryset):
if self.value() is None:
return queryset.all()
else:
return queryset.filter(groups__pk=self.value())
def get_actions(self, request): def get_actions(self, request):
actions = super(BaseUserAdmin, self).get_actions(request) actions = super(BaseUserAdmin, self).get_actions(request)
@@ -341,11 +322,9 @@ class UserAdmin(BaseUserAdmin):
return result return result
inlines = BaseUserAdmin.inlines + [UserProfileInline] inlines = BaseUserAdmin.inlines + [UserProfileInline]
ordering = ('username', ) ordering = ('username', )
list_select_related = True list_select_related = ('profile__state', 'profile__main_character')
show_full_result_count = True show_full_result_count = True
list_display = ( list_display = (
user_profile_pic, user_profile_pic,
user_username, user_username,
@@ -358,10 +337,9 @@ class UserAdmin(BaseUserAdmin):
'_role' '_role'
) )
list_display_links = None list_display_links = None
list_filter = ( list_filter = (
'profile__state', 'profile__state',
RealGroupsFilter, 'groups',
MainCorporationsFilter, MainCorporationsFilter,
MainAllianceFilter, MainAllianceFilter,
'is_active', 'is_active',
@@ -375,21 +353,15 @@ class UserAdmin(BaseUserAdmin):
) )
def _characters(self, obj): def _characters(self, obj):
my_characters = [ character_ownerships = list(obj.character_ownerships.all())
x.character.character_name characters = [obj.character.character_name for obj in character_ownerships]
for x in CharacterOwnership.objects\
.filter(user=obj)\
.order_by('character__character_name')\
.select_related()
]
return self._list_2_html_w_tooltips( return self._list_2_html_w_tooltips(
my_characters, sorted(characters),
AUTHENTICATION_ADMIN_USERS_MAX_CHARS AUTHENTICATION_ADMIN_USERS_MAX_CHARS
) )
_characters.short_description = 'characters' _characters.short_description = 'characters'
def _state(self, obj): def _state(self, obj):
return obj.profile.state.name return obj.profile.state.name
@@ -397,19 +369,9 @@ class UserAdmin(BaseUserAdmin):
_state.admin_order_field = 'profile__state' _state.admin_order_field = 'profile__state'
def _groups(self, obj): def _groups(self, obj):
if not _has_auto_groups: my_groups = sorted([group.name for group in list(obj.groups.all())])
my_groups = [x.name for x in obj.groups.order_by('name')]
else:
my_groups = [
x.name for x in obj.groups\
.filter(managedalliancegroup__isnull=True)\
.filter(managedcorpgroup__isnull=True)\
.order_by('name')
]
return self._list_2_html_w_tooltips( return self._list_2_html_w_tooltips(
my_groups, my_groups, AUTHENTICATION_ADMIN_USERS_MAX_GROUPS
AUTHENTICATION_ADMIN_USERS_MAX_GROUPS
) )
_groups.short_description = 'groups' _groups.short_description = 'groups'
@@ -446,9 +408,14 @@ class StateAdmin(admin.ModelAdmin):
list_select_related = True list_select_related = True
list_display = ('name', 'priority', '_user_count') list_display = ('name', 'priority', '_user_count')
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.annotate(user_count=Count("userprofile__id"))
def _user_count(self, obj): def _user_count(self, obj):
return obj.userprofile_set.all().count() return obj.user_count
_user_count.short_description = 'Users' _user_count.short_description = 'Users'
_user_count.admin_order_field = 'user_count'
fieldsets = ( fieldsets = (
(None, { (None, {
@@ -481,6 +448,8 @@ class StateAdmin(admin.ModelAdmin):
elif db_field.name == "member_alliances": elif db_field.name == "member_alliances":
kwargs["queryset"] = EveAllianceInfo.objects.all()\ kwargs["queryset"] = EveAllianceInfo.objects.all()\
.order_by(Lower('alliance_name')) .order_by(Lower('alliance_name'))
elif db_field.name == "permissions":
kwargs["queryset"] = Permission.objects.select_related("content_type").all()
return super().formfield_for_manytomany(db_field, request, **kwargs) return super().formfield_for_manytomany(db_field, request, **kwargs)
def has_delete_permission(self, request, obj=None): def has_delete_permission(self, request, obj=None):
@@ -504,7 +473,8 @@ class BaseOwnershipAdmin(admin.ModelAdmin):
"all": ("authentication/css/admin.css",) "all": ("authentication/css/admin.css",)
} }
list_select_related = True list_select_related = (
'user__profile__state', 'user__profile__main_character', 'character')
list_display = ( list_display = (
user_profile_pic, user_profile_pic,
user_username, user_username,
@@ -542,6 +512,7 @@ class CharacterOwnershipAdmin(BaseOwnershipAdmin):
class PermissionAdmin(admin.ModelAdmin): class PermissionAdmin(admin.ModelAdmin):
actions = None actions = None
readonly_fields = [field.name for field in BasePermission._meta.fields] readonly_fields = [field.name for field in BasePermission._meta.fields]
search_fields = ('codename', )
list_display = ('admin_name', 'name', 'codename', 'content_type') list_display = ('admin_name', 'name', 'codename', 'content_type')
list_filter = ('content_type__app_label',) list_filter = ('content_type__app_label',)
@@ -549,7 +520,7 @@ class PermissionAdmin(admin.ModelAdmin):
def admin_name(obj): def admin_name(obj):
return str(obj) return str(obj)
def has_add_permission(self, request): def has_add_permission(self, request, obj=None):
return False return False
def has_delete_permission(self, request, obj=None): def has_delete_permission(self, request, obj=None):

View File

@@ -43,4 +43,3 @@ AUTHENTICATION_ADMIN_USERS_MAX_GROUPS = \
AUTHENTICATION_ADMIN_USERS_MAX_CHARS = \ AUTHENTICATION_ADMIN_USERS_MAX_CHARS = \
_clean_setting('AUTHENTICATION_ADMIN_USERS_MAX_CHARS', 5) _clean_setting('AUTHENTICATION_ADMIN_USERS_MAX_CHARS', 5)

View File

@@ -1,6 +1,8 @@
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from allianceauth.authentication.models import User
class RegistrationForm(forms.Form): class RegistrationForm(forms.Form):
email = forms.EmailField(label=_('Email'), max_length=254, required=True) email = forms.EmailField(label=_('Email'), max_length=254, required=True)
class _meta:
model = User

View File

@@ -10,5 +10,5 @@ urlpatterns = [
url(r'^register/$', views.RegistrationView.as_view(), name='registration_register'), url(r'^register/$', views.RegistrationView.as_view(), name='registration_register'),
url(r'^register/complete/$', views.registration_complete, name='registration_complete'), url(r'^register/complete/$', views.registration_complete, name='registration_complete'),
url(r'^register/closed/$', views.registration_closed, name='registration_disallowed'), url(r'^register/closed/$', views.registration_closed, name='registration_disallowed'),
url(r'', include('registration.auth_urls')), url(r'', include('django.contrib.auth.urls')),
] ]

View File

@@ -23,8 +23,7 @@ class CharacterOwnershipManager(Manager):
def create_by_token(self, token): def create_by_token(self, token):
if not EveCharacter.objects.filter(character_id=token.character_id).exists(): if not EveCharacter.objects.filter(character_id=token.character_id).exists():
EveCharacter.objects.create_character(token.character_id) EveCharacter.objects.create_character(token.character_id)
return self.create(character=EveCharacter.objects.get(character_id=token.character_id), user=token.user, return self.create(character=EveCharacter.objects.get(character_id=token.character_id), user=token.user, owner_hash=token.character_owner_hash)
owner_hash=token.character_owner_hash)
class StateQuerySet(QuerySet): class StateQuerySet(QuerySet):

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-05 21:38 # Generated by Django 1.10.1 on 2016-09-05 21:38
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-07 19:14 # Generated by Django 1.10.1 on 2016-09-07 19:14
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-09 20:29 # Generated by Django 1.10.1 on 2016-09-09 20:29
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-09 23:19 # Generated by Django 1.10.1 on 2016-09-09 23:19
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-09 23:11 # Generated by Django 1.10.1 on 2016-09-09 23:11
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-10 05:42 # Generated by Django 1.10.1 on 2016-09-10 05:42
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-10 21:50 # Generated by Django 1.10.1 on 2016-09-10 21:50
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-12 13:04 # Generated by Django 1.10.1 on 2016-09-12 13:04
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-21 02:28 # Generated by Django 1.10.2 on 2016-10-21 02:28
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2017-01-07 06:47 # Generated by Django 1.10.1 on 2017-01-07 06:47
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2017-01-07 07:11 # Generated by Django 1.10.1 on 2017-01-07 07:11
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-01-12 00:59 # Generated by Django 1.10.5 on 2017-01-12 00:59
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-12-11 23:14 # Generated by Django 1.10.2 on 2016-12-11 23:14
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-09 23:19 # Generated by Django 1.10.1 on 2016-09-09 23:19
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-22 23:09 # Generated by Django 1.10.5 on 2017-03-22 23:09
from __future__ import unicode_literals from __future__ import unicode_literals
@@ -171,8 +170,7 @@ def recreate_authservicesinfo(apps, schema_editor):
# repopulate main characters # repopulate main characters
for profile in UserProfile.objects.exclude(main_character__isnull=True).select_related('user', 'main_character'): for profile in UserProfile.objects.exclude(main_character__isnull=True).select_related('user', 'main_character'):
AuthServicesInfo.objects.update_or_create(user=profile.user, AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'main_char_id': profile.main_character.character_id})
defaults={'main_char_id': profile.main_character.character_id})
# repopulate states we understand # repopulate states we understand
for profile in UserProfile.objects.exclude(state__name='Guest').filter( for profile in UserProfile.objects.exclude(state__name='Guest').filter(

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations from django.db import migrations

View File

@@ -14,15 +14,11 @@ logger = logging.getLogger(__name__)
class State(models.Model): class State(models.Model):
name = models.CharField(max_length=20, unique=True) name = models.CharField(max_length=20, unique=True)
permissions = models.ManyToManyField(Permission, blank=True) permissions = models.ManyToManyField(Permission, blank=True)
priority = models.IntegerField(unique=True, priority = models.IntegerField(unique=True, help_text="Users get assigned the state with the highest priority available to them.")
help_text="Users get assigned the state with the highest priority available to them.")
member_characters = models.ManyToManyField(EveCharacter, blank=True, member_characters = models.ManyToManyField(EveCharacter, blank=True, help_text="Characters to which this state is available.")
help_text="Characters to which this state is available.") member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True, help_text="Corporations to whose members this state is available.")
member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True, member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True, help_text="Alliances to whose members this state is available.")
help_text="Corporations to whose members this state is available.")
member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True,
help_text="Alliances to whose members this state is available.")
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 = StateManager()
@@ -81,6 +77,11 @@ class UserProfile(models.Model):
'info' 'info'
) )
from allianceauth.authentication.signals import state_changed from allianceauth.authentication.signals import state_changed
# We need to ensure we get up to date perms here as they will have just changed.
# Clear all attribute caches and reload the model that will get passed to the signals!
self.refresh_from_db()
state_changed.send( state_changed.send(
sender=self.__class__, user=self.user, state=self.state sender=self.__class__, user=self.user, state=self.state
) )

View File

@@ -75,8 +75,7 @@ def create_required_models(sender, instance, created, *args, **kwargs):
@receiver(post_save, sender=Token) @receiver(post_save, sender=Token)
def record_character_ownership(sender, instance, created, *args, **kwargs): def record_character_ownership(sender, instance, created, *args, **kwargs):
if created: if created:
logger.debug('New token for {0} character {1} saved. Evaluating ownership.'.format(instance.user, logger.debug('New token for {0} character {1} saved. Evaluating ownership.'.format(instance.user, instance.character_name))
instance.character_name))
if instance.user: if instance.user:
query = Q(owner_hash=instance.character_owner_hash) & Q(user=instance.user) query = Q(owner_hash=instance.character_owner_hash) & Q(user=instance.user)
else: else:
@@ -85,18 +84,14 @@ def record_character_ownership(sender, instance, created, *args, **kwargs):
CharacterOwnership.objects.filter(character__character_id=instance.character_id).exclude(query).delete() CharacterOwnership.objects.filter(character__character_id=instance.character_id).exclude(query).delete()
# create character if needed # create character if needed
if EveCharacter.objects.filter(character_id=instance.character_id).exists() is False: if EveCharacter.objects.filter(character_id=instance.character_id).exists() is False:
logger.debug('Token is for a new character. Creating model for {0} ({1})'.format(instance.character_name, logger.debug('Token is for a new character. Creating model for {0} ({1})'.format(instance.character_name, instance.character_id))
instance.character_id))
EveCharacter.objects.create_character(instance.character_id) EveCharacter.objects.create_character(instance.character_id)
char = EveCharacter.objects.get(character_id=instance.character_id) char = EveCharacter.objects.get(character_id=instance.character_id)
# check if we need to create ownership # check if we need to create ownership
if instance.user and not CharacterOwnership.objects.filter( if instance.user and not CharacterOwnership.objects.filter(
character__character_id=instance.character_id).exists(): character__character_id=instance.character_id).exists():
logger.debug("Character {0} is not yet owned. Assigning ownership to {1}".format(instance.character_name, logger.debug("Character {0} is not yet owned. Assigning ownership to {1}".format(instance.character_name, instance.user))
instance.user)) CharacterOwnership.objects.update_or_create(character=char, defaults={'owner_hash': instance.character_owner_hash, 'user': instance.user})
CharacterOwnership.objects.update_or_create(character=char,
defaults={'owner_hash': instance.character_owner_hash,
'user': instance.user})
@receiver(pre_delete, sender=CharacterOwnership) @receiver(pre_delete, sender=CharacterOwnership)

View File

@@ -1,5 +1,5 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base.html" %}
{% load staticfiles %} {% load static %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% trans "Dashboard" %}{% endblock %} {% block page_title %}{% trans "Dashboard" %}{% endblock %}

View File

@@ -1,7 +1,7 @@
{% load staticfiles %} {% extends 'public/base.html' %}
{% load static %}
{% load bootstrap %} {% load bootstrap %}
{% load i18n %} {% load i18n %}
{% extends 'public/base.html' %}
{% block page_title %}Registration{% endblock %} {% block page_title %}Registration{% endblock %}
{% block extra_include %} {% block extra_include %}
{% include 'bundles/bootstrap-css.html' %} {% include 'bundles/bootstrap-css.html' %}

View File

@@ -10,6 +10,7 @@ def get_admin_change_view_url(obj: object) -> str:
args=(obj.pk,) args=(obj.pk,)
) )
def get_admin_search_url(ModelClass: type) -> str: def get_admin_search_url(ModelClass: type) -> str:
"""returns URL to search URL for model of given object""" """returns URL to search URL for model of given object"""
return '{}{}/'.format( return '{}{}/'.format(

View File

@@ -1,10 +1,8 @@
from urllib.parse import quote from urllib.parse import quote
from unittest.mock import patch, MagicMock from unittest.mock import patch, MagicMock
from django.conf import settings
from django.contrib import admin
from django.contrib.admin.sites import AdminSite from django.contrib.admin.sites import AdminSite
from django.contrib.auth.models import User as BaseUser, Group from django.contrib.auth.models import Group
from django.test import TestCase, RequestFactory, Client from django.test import TestCase, RequestFactory, Client
from allianceauth.authentication.models import ( from allianceauth.authentication.models import (
@@ -19,7 +17,6 @@ from allianceauth.tests.auth_utils import AuthUtils
from ..admin import ( from ..admin import (
BaseUserAdmin, BaseUserAdmin,
CharacterOwnershipAdmin, CharacterOwnershipAdmin,
PermissionAdmin,
StateAdmin, StateAdmin,
MainCorporationsFilter, MainCorporationsFilter,
MainAllianceFilter, MainAllianceFilter,
@@ -35,11 +32,6 @@ from ..admin import (
) )
from . import get_admin_change_view_url, get_admin_search_url from . import get_admin_change_view_url, get_admin_search_url
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True
from allianceauth.eveonline.autogroups.models import AutogroupsConfig
else:
_has_auto_groups = False
MODULE_PATH = 'allianceauth.authentication.admin' MODULE_PATH = 'allianceauth.authentication.admin'
@@ -48,6 +40,7 @@ class MockRequest(object):
def __init__(self, user=None): def __init__(self, user=None):
self.user = user self.user = user
class TestCaseWithTestData(TestCase): class TestCaseWithTestData(TestCase):
@classmethod @classmethod
@@ -279,6 +272,7 @@ class TestStateAdmin(TestCaseWithTestData):
expected = 200 expected = 200
self.assertEqual(response.status_code, expected) self.assertEqual(response.status_code, expected)
class TestUserAdmin(TestCaseWithTestData): class TestUserAdmin(TestCaseWithTestData):
def setUp(self): def setUp(self):
@@ -288,23 +282,11 @@ class TestUserAdmin(TestCaseWithTestData):
) )
self.character_1 = self.user_1.character_ownerships.first().character self.character_1 = self.user_1.character_ownerships.first().character
def _create_autogroups(self):
"""create autogroups for corps and alliances"""
if _has_auto_groups:
autogroups_config = AutogroupsConfig(
corp_groups = True,
alliance_groups = True
)
autogroups_config.save()
for state in State.objects.all():
autogroups_config.states.add(state)
autogroups_config.update_corp_group_membership(self.user_1)
# column rendering
def test_user_profile_pic_u1(self): def test_user_profile_pic_u1(self):
expected = ('<img src="https://images.evetech.net/characters/1001/' expected = (
'portrait?size=32" class="img-circle">') '<img src="https://images.evetech.net/characters/1001/'
'portrait?size=32" class="img-circle">'
)
self.assertEqual(user_profile_pic(self.user_1), expected) self.assertEqual(user_profile_pic(self.user_1), expected)
def test_user_profile_pic_u3(self): def test_user_profile_pic_u3(self):
@@ -352,36 +334,16 @@ class TestUserAdmin(TestCaseWithTestData):
self.assertEqual(result, expected) self.assertEqual(result, expected)
def test_groups_u1(self): def test_groups_u1(self):
self._create_autogroups()
expected = 'Group 1' expected = 'Group 1'
result = self.modeladmin._groups(self.user_1) result = self.modeladmin._groups(self.user_1)
self.assertEqual(result, expected) self.assertEqual(result, expected)
def test_groups_u2(self): def test_groups_u2(self):
self._create_autogroups()
expected = 'Group 2' expected = 'Group 2'
result = self.modeladmin._groups(self.user_2) result = self.modeladmin._groups(self.user_2)
self.assertEqual(result, expected) self.assertEqual(result, expected)
def test_groups_u3(self): def test_groups_u3(self):
self._create_autogroups()
result = self.modeladmin._groups(self.user_3)
self.assertIsNone(result)
@patch(MODULE_PATH + '._has_auto_groups', False)
def test_groups_u1_no_autogroups(self):
expected = 'Group 1'
result = self.modeladmin._groups(self.user_1)
self.assertEqual(result, expected)
@patch(MODULE_PATH + '._has_auto_groups', False)
def test_groups_u2_no_autogroups(self):
expected = 'Group 2'
result = self.modeladmin._groups(self.user_2)
self.assertEqual(result, expected)
@patch(MODULE_PATH + '._has_auto_groups', False)
def test_groups_u3_no_autogroups(self):
result = self.modeladmin._groups(self.user_3) result = self.modeladmin._groups(self.user_3)
self.assertIsNone(result) self.assertIsNone(result)
@@ -413,8 +375,10 @@ class TestUserAdmin(TestCaseWithTestData):
def test_list_2_html_w_tooltips_w_cutoff(self): def test_list_2_html_w_tooltips_w_cutoff(self):
items = ['one', 'two', 'three'] items = ['one', 'two', 'three']
expected = ('<span data-tooltip="one, two, three" ' expected = (
'class="tooltip">one, two, (...)</span>') '<span data-tooltip="one, two, three" '
'class="tooltip">one, two, (...)</span>'
)
result = self.modeladmin._list_2_html_w_tooltips(items, 2) result = self.modeladmin._list_2_html_w_tooltips(items, 2)
self.assertEqual(expected, result) self.assertEqual(expected, result)
@@ -440,62 +404,6 @@ class TestUserAdmin(TestCaseWithTestData):
# filters # filters
def test_filter_real_groups_with_autogroups(self):
class UserAdminTest(BaseUserAdmin):
list_filter = (UserAdmin.RealGroupsFilter,)
self._create_autogroups()
my_modeladmin = UserAdminTest(User, AdminSite())
# Make sure the lookups are correct
request = self.factory.get('/')
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
filters = changelist.get_filters(request)
filterspec = filters[0][0]
expected = [
(self.group_1.pk, self.group_1.name),
(self.group_2.pk, self.group_2.name),
]
self.assertEqual(filterspec.lookup_choices, expected)
# Make sure the correct queryset is returned
request = self.factory.get('/', {'group_id__exact': self.group_1.pk})
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request)
expected = User.objects.filter(groups__in=[self.group_1])
self.assertSetEqual(set(queryset), set(expected))
@patch(MODULE_PATH + '._has_auto_groups', False)
def test_filter_real_groups_no_autogroups(self):
class UserAdminTest(BaseUserAdmin):
list_filter = (UserAdmin.RealGroupsFilter,)
my_modeladmin = UserAdminTest(User, AdminSite())
# Make sure the lookups are correct
request = self.factory.get('/')
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
filters = changelist.get_filters(request)
filterspec = filters[0][0]
expected = [
(self.group_1.pk, self.group_1.name),
(self.group_2.pk, self.group_2.name),
]
self.assertEqual(filterspec.lookup_choices, expected)
# Make sure the correct queryset is returned
request = self.factory.get('/', {'group_id__exact': self.group_1.pk})
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request)
expected = User.objects.filter(groups__in=[self.group_1])
self.assertSetEqual(set(queryset), set(expected))
def test_filter_main_corporations(self): def test_filter_main_corporations(self):
class UserAdminTest(BaseUserAdmin): class UserAdminTest(BaseUserAdmin):
@@ -603,7 +511,6 @@ class TestMakeServicesHooksActions(TestCaseWithTestData):
def sync_nicknames_bulk(self, user): def sync_nicknames_bulk(self, user):
pass pass
def test_service_has_update_groups_only(self): def test_service_has_update_groups_only(self):
service = self.MyServicesHookTypeA() service = self.MyServicesHookTypeA()
mock_service = MagicMock(spec=service) mock_service = MagicMock(spec=service)

View File

@@ -1,10 +1,10 @@
from math import ceil from math import ceil
from unittest.mock import patch from unittest.mock import patch
from requests import RequestException
import requests_mock import requests_mock
from packaging.version import Version as Pep440Version from packaging.version import Version as Pep440Version
from django.core.cache import cache
from django.test import TestCase from django.test import TestCase
from allianceauth.templatetags.admin_status import ( from allianceauth.templatetags.admin_status import (
@@ -13,7 +13,6 @@ from allianceauth.templatetags.admin_status import (
_current_notifications, _current_notifications,
_current_version_summary, _current_version_summary,
_fetch_notification_issues_from_gitlab, _fetch_notification_issues_from_gitlab,
_fetch_tags_from_gitlab,
_latests_versions _latests_versions
) )
@@ -84,8 +83,7 @@ class TestStatusOverviewTag(TestCase):
mock_current_version_info.return_value = version_info mock_current_version_info.return_value = version_info
mock_fetch_celery_queue_length.return_value = 3 mock_fetch_celery_queue_length.return_value = 3
context = {} result = status_overview()
result = status_overview(context)
expected = { expected = {
'notifications': GITHUB_NOTIFICATION_ISSUES[:5], 'notifications': GITHUB_NOTIFICATION_ISSUES[:5],
'latest_major': True, 'latest_major': True,
@@ -104,28 +102,51 @@ class TestStatusOverviewTag(TestCase):
class TestNotifications(TestCase): class TestNotifications(TestCase):
def setUp(self) -> None:
cache.clear()
@requests_mock.mock() @requests_mock.mock()
def test_fetch_notification_issues_from_gitlab(self, requests_mocker): def test_fetch_notification_issues_from_gitlab(self, requests_mocker):
# given
url = ( url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues' 'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues'
'?labels=announcement' '?labels=announcement'
) )
requests_mocker.get(url, json=GITHUB_NOTIFICATION_ISSUES) requests_mocker.get(url, json=GITHUB_NOTIFICATION_ISSUES)
# when
result = _fetch_notification_issues_from_gitlab() result = _fetch_notification_issues_from_gitlab()
# then
self.assertEqual(result, GITHUB_NOTIFICATION_ISSUES) self.assertEqual(result, GITHUB_NOTIFICATION_ISSUES)
@patch(MODULE_PATH + '.admin_status.cache') @patch(MODULE_PATH + '.admin_status.cache')
def test_current_notifications_normal(self, mock_cache): def test_current_notifications_normal(self, mock_cache):
# given
mock_cache.get_or_set.return_value = GITHUB_NOTIFICATION_ISSUES mock_cache.get_or_set.return_value = GITHUB_NOTIFICATION_ISSUES
# when
result = _current_notifications() result = _current_notifications()
# then
self.assertEqual(result['notifications'], GITHUB_NOTIFICATION_ISSUES[:5]) self.assertEqual(result['notifications'], GITHUB_NOTIFICATION_ISSUES[:5])
@patch(MODULE_PATH + '.admin_status.cache') @requests_mock.mock()
def test_current_notifications_failed(self, mock_cache): def test_current_notifications_failed(self, requests_mocker):
mock_cache.get_or_set.side_effect = RequestException # given
url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues'
'?labels=announcement'
)
requests_mocker.get(url, status_code=404)
# when
result = _current_notifications() result = _current_notifications()
# then
self.assertEqual(result['notifications'], list())
@patch(MODULE_PATH + '.admin_status.cache')
def test_current_notifications_is_none(self, mock_cache):
# given
mock_cache.get_or_set.return_value = None
# when
result = _current_notifications()
# then
self.assertEqual(result['notifications'], list()) self.assertEqual(result['notifications'], list())
@@ -137,12 +158,17 @@ class TestCeleryQueueLength(TestCase):
class TestVersionTags(TestCase): class TestVersionTags(TestCase):
def setUp(self) -> None:
cache.clear()
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION) @patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
@patch(MODULE_PATH + '.admin_status.cache') @patch(MODULE_PATH + '.admin_status.cache')
def test_current_version_info_normal(self, mock_cache): def test_current_version_info_normal(self, mock_cache):
# given
mock_cache.get_or_set.return_value = GITHUB_TAGS mock_cache.get_or_set.return_value = GITHUB_TAGS
# when
result = _current_version_summary() result = _current_version_summary()
# then
self.assertTrue(result['latest_major']) self.assertTrue(result['latest_major'])
self.assertTrue(result['latest_minor']) self.assertTrue(result['latest_minor'])
self.assertTrue(result['latest_patch']) self.assertTrue(result['latest_patch'])
@@ -152,23 +178,41 @@ class TestVersionTags(TestCase):
self.assertEqual(result['latest_beta_version'], '2.4.6a1') self.assertEqual(result['latest_beta_version'], '2.4.6a1')
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION) @patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
@patch(MODULE_PATH + '.admin_status.cache') @requests_mock.mock()
def test_current_version_info_failed(self, mock_cache): def test_current_version_info_failed(self, requests_mocker):
mock_cache.get_or_set.side_effect = RequestException # given
url = (
expected = {} 'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth'
'/repository/tags'
)
requests_mocker.get(url, status_code=500)
# when
result = _current_version_summary() result = _current_version_summary()
self.assertEqual(result, expected) # then
self.assertEqual(result, {})
@requests_mock.mock() @requests_mock.mock()
def test_fetch_tags_from_gitlab(self, requests_mocker): def test_fetch_tags_from_gitlab(self, requests_mocker):
# given
url = ( url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth' 'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth'
'/repository/tags' '/repository/tags'
) )
requests_mocker.get(url, json=GITHUB_TAGS) requests_mocker.get(url, json=GITHUB_TAGS)
result = _fetch_tags_from_gitlab() # when
self.assertEqual(result, GITHUB_TAGS) result = _current_version_summary()
# then
self.assertTrue(result)
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
@patch(MODULE_PATH + '.admin_status.cache')
def test_current_version_info_return_no_data(self, mock_cache):
# given
mock_cache.get_or_set.return_value = None
# when
result = _current_version_summary()
# then
self.assertEqual(result, {})
class TestLatestsVersion(TestCase): class TestLatestsVersion(TestCase):

View File

@@ -6,20 +6,21 @@ from django.contrib.auth import login, authenticate
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
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.urls import reverse from django.http import JsonResponse
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from allianceauth.eveonline.models import EveCharacter from allianceauth.eveonline.models import EveCharacter
from esi.decorators import token_required from esi.decorators import token_required
from esi.models import Token from esi.models import Token
from registration.backends.hmac.views import ( from django_registration.backends.activation.views import (
RegistrationView as BaseRegistrationView, RegistrationView as BaseRegistrationView,
ActivationView as BaseActivationView, ActivationView as BaseActivationView,
REGISTRATION_SALT REGISTRATION_SALT
) )
from registration.signals import user_registered from django_registration.signals import user_registered
from .models import CharacterOwnership from .models import CharacterOwnership
from .forms import RegistrationForm from .forms import RegistrationForm
@@ -134,11 +135,14 @@ def sso_login(request, token):
# Step 2 # Step 2
class RegistrationView(BaseRegistrationView): class RegistrationView(BaseRegistrationView):
form_class = RegistrationForm form_class = RegistrationForm
success_url = 'authentication:dashboard' template_name = "public/register.html"
email_body_template = "registration/activation_email.txt"
email_subject_template = "registration/activation_email_subject.txt"
success_url = reverse_lazy('registration_complete')
def get_success_url(self, user): def get_success_url(self, user):
if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True): if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
return 'authentication:dashboard', (), {} return reverse_lazy('authentication:dashboard')
return super().get_success_url(user) return super().get_success_url(user)
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
@@ -176,6 +180,9 @@ class RegistrationView(BaseRegistrationView):
# Step 3 # Step 3
class ActivationView(BaseActivationView): class ActivationView(BaseActivationView):
template_name = "registration/activate.html"
success_url = reverse_lazy('registration_activation_complete')
def validate_key(self, activation_key): def validate_key(self, activation_key):
try: try:
dump = signing.loads(activation_key, salt=REGISTRATION_SALT, dump = signing.loads(activation_key, salt=REGISTRATION_SALT,
@@ -207,5 +214,5 @@ def activation_complete(request):
def registration_closed(request): def registration_closed(request):
messages.error(request, _('Registraion of new accounts it 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')

View File

@@ -6,11 +6,13 @@ from allianceauth.corputils import urls
class CorpStats(MenuItemHook): class CorpStats(MenuItemHook):
def __init__(self): def __init__(self):
MenuItemHook.__init__(self, MenuItemHook.__init__(
self,
_('Corporation Stats'), _('Corporation Stats'),
'fas fa-share-alt fa-fw', 'fas fa-share-alt fa-fw',
'corputils:view', 'corputils:view',
navactive=['corputils:']) navactive=['corputils:']
)
def render(self, request): def render(self, request):
if request.user.has_perm('corputils.view_corp_corpstats') or request.user.has_perm( if request.user.has_perm('corputils.view_corp_corpstats') or request.user.has_perm(

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-14 21:36 # Generated by Django 1.10.1 on 2016-12-14 21:36
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-14 21:48 # Generated by Django 1.10.1 on 2016-12-14 21:48
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-22 23:35 # Generated by Django 1.10.5 on 2017-03-22 23:35
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-26 20:13 # Generated by Django 1.10.5 on 2017-03-26 20:13
from __future__ import unicode_literals from __future__ import unicode_literals
@@ -13,8 +12,7 @@ def convert_json_to_members(apps, schema_editor):
for cs in CorpStats.objects.all(): for cs in CorpStats.objects.all():
members = json.loads(cs._members) members = json.loads(cs._members)
CorpMember.objects.bulk_create( CorpMember.objects.bulk_create(
[CorpMember(corpstats=cs, character_id=member_id, character_name=member_name) for member_id, member_name in [CorpMember(corpstats=cs, character_id=member_id, character_name=member_name) for member_id, member_name in members.items()]
members.items()]
) )

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-10 15:34 # Generated by Django 1.11.2 on 2017-06-10 15:34
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -6,8 +6,7 @@ from bravado.exception import HTTPForbidden
from django.db import models from django.db import models
from esi.errors import TokenError from esi.errors import TokenError
from esi.models import Token from esi.models import Token
from allianceauth.eveonline.models import EveCorporationInfo, EveCharacter,\ from allianceauth.eveonline.models import EveCorporationInfo, EveCharacter, EveAllianceInfo
EveAllianceInfo
from allianceauth.notifications import notify from allianceauth.notifications import notify
from allianceauth.corputils.managers import CorpStatsManager from allianceauth.corputils.managers import CorpStatsManager
@@ -49,8 +48,7 @@ class CorpStats(models.Model):
def update(self): def update(self):
try: try:
c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH) c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[ assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()['corporation_id'] == int(self.corp.corporation_id)
'corporation_id'] == int(self.corp.corporation_id)
member_ids = c.Corporation.get_corporations_corporation_id_members( member_ids = c.Corporation.get_corporations_corporation_id_members(
corporation_id=self.corp.corporation_id).result() corporation_id=self.corp.corporation_id).result()
@@ -58,18 +56,15 @@ class CorpStats(models.Model):
# the swagger spec doesn't have a maxItems count # the swagger spec doesn't have a maxItems count
# manual testing says we can do over 350, but let's not risk it # manual testing says we can do over 350, but let's not risk it
member_id_chunks = [member_ids[i:i + 255] for i in range(0, len(member_ids), 255)] member_id_chunks = [member_ids[i:i + 255] for i in range(0, len(member_ids), 255)]
member_name_chunks = [c.Universe.post_universe_names(ids=id_chunk).result() for id_chunk in member_name_chunks = [c.Universe.post_universe_names(ids=id_chunk).result() for id_chunk in member_id_chunks]
member_id_chunks]
member_list = {} member_list = {}
for name_chunk in member_name_chunks: for name_chunk in member_name_chunks:
member_list.update({m['id']: m['name'] for m in name_chunk}) member_list.update({m['id']: m['name'] for m in name_chunk})
# bulk create new member models # bulk create new member models
missing_members = [m_id for m_id in member_ids if missing_members = [m_id for m_id in member_ids if not CorpMember.objects.filter(corpstats=self, character_id=m_id).exists()]
not CorpMember.objects.filter(corpstats=self, character_id=m_id).exists()]
CorpMember.objects.bulk_create( CorpMember.objects.bulk_create(
[CorpMember(character_id=m_id, character_name=member_list[m_id], corpstats=self) for m_id in [CorpMember(character_id=m_id, character_name=member_list[m_id], corpstats=self) for m_id in missing_members])
missing_members])
# purge old members # purge old members
self.members.exclude(character_id__in=member_ids).delete() self.members.exclude(character_id__in=member_ids).delete()
@@ -80,20 +75,21 @@ class CorpStats(models.Model):
except TokenError as e: except TokenError as e:
logger.warning("%s failed to update: %s" % (self, e)) logger.warning("%s failed to update: %s" % (self, e))
if self.token.user: if self.token.user:
notify(self.token.user, "%s failed to update with your ESI token." % self, notify(
self.token.user, "%s failed to update with your ESI token." % self,
message="Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.", message="Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.",
level="error") level="error")
self.delete() self.delete()
except HTTPForbidden as e: except HTTPForbidden as e:
logger.warning("%s failed to update: %s" % (self, e)) logger.warning("%s failed to update: %s" % (self, e))
if self.token.user: if self.token.user:
notify(self.token.user, "%s failed to update with your ESI token." % self, notify(self.token.user, "%s failed to update with your ESI token." % self, message="%s: %s" % (e.status_code, e.message), level="error")
message="%s: %s" % (e.status_code, e.message), level="error")
self.delete() self.delete()
except AssertionError: except AssertionError:
logger.warning("%s token character no longer in corp." % self) logger.warning("%s token character no longer in corp." % self)
if self.token.user: if self.token.user:
notify(self.token.user, "%s cannot update with your ESI token." % self, notify(
self.token.user, "%s cannot update with your ESI token." % self,
message="%s cannot update with your ESI token as you have left corp." % self, level="error") message="%s cannot update with your ESI token as you have left corp." % self, level="error")
self.delete() self.delete()
@@ -127,9 +123,7 @@ class CorpStats(models.Model):
@property @property
def mains(self): def mains(self):
return self.members.filter(pk__in=[m.pk for m in self.members.all() if return self.members.filter(pk__in=[m.pk for m in self.members.all() if m.main_character and int(m.main_character.character_id) == int(m.character_id)])
m.main_character and int(m.main_character.character_id) == int(
m.character_id)])
def visible_to(self, user): def visible_to(self, user):
return CorpStats.objects.filter(pk=self.pk).visible_to(user).exists() return CorpStats.objects.filter(pk=self.pk).visible_to(user).exists()

View File

@@ -58,8 +58,7 @@
{% for id, main in mains.items %} {% for id, main in mains.items %}
<tr> <tr>
<td class="text-center" style="vertical-align:middle"> <td class="text-center" style="vertical-align:middle">
<div class="thumbnail" <div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
style="border: 0 none; box-shadow: none; background: transparent;">
<img src="{{ main.main.portrait_url_64 }}" class="img-circle"> <img src="{{ main.main.portrait_url_64 }}" class="img-circle">
<div class="caption text-center"> <div class="caption text-center">
{{ main.main }} {{ main.main }}
@@ -88,8 +87,7 @@
<td class="text-center" style="width:30%">{{ alt.corporation_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:30%">{{ alt.alliance_name }}</td>
<td class="text-center" style="width:5%"> <td class="text-center" style="width:5%">
<a href="https://zkillboard.com/character/{{ alt.character_id }}/" <a href="https://zkillboard.com/character/{{ alt.character_id }}/" class="label label-danger" target="_blank">
class="label label-danger" target="_blank">
{% trans "Killboard" %} {% trans "Killboard" %}
</a> </a>
</td> </td>
@@ -123,10 +121,9 @@
<tr> <tr>
<td><img src="{{ member.portrait_url }}" class="img-circle"></td> <td><img src="{{ member.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ member }}</td> <td class="text-center">{{ member }}</td>
<td class="text-center"><a <td class="text-center">
href="https://zkillboard.com/character/{{ member.character_id }}/" <a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a>
class="label label-danger" </td>
target="_blank">{% trans "Killboard" %}</a></td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td> <td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td> <td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
<td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td> <td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td>
@@ -136,10 +133,9 @@
<tr class="danger"> <tr class="danger">
<td><img src="{{ member.portrait_url }}" class="img-circle"></td> <td><img src="{{ member.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ member.character_name }}</td> <td class="text-center">{{ member.character_name }}</td>
<td class="text-center"><a <td class="text-center">
href="https://zkillboard.com/character/{{ member.character_id }}/" <a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a>
class="label label-danger" </td>
target="_blank">{% trans "Killboard" %}</a></td>
<td class="text-center"></td> <td class="text-center"></td>
<td class="text-center"></td> <td class="text-center"></td>
<td class="text-center"></td> <td class="text-center"></td>
@@ -167,9 +163,7 @@
<td><img src="{{ member.portrait_url }}" class="img-circle"></td> <td><img src="{{ member.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ member.character_name }}</td> <td class="text-center">{{ member.character_name }}</td>
<td class="text-center"> <td class="text-center">
<a href="https://zkillboard.com/character/{{ member.character_id }}/" <a href="https://zkillboard.com/character/{{ member.character_id }}/" class="label label-danger" target="_blank">
class="label label-danger"
target="_blank">
{% trans "Killboard" %} {% trans "Killboard" %}
</a> </a>
</td> </td>

View File

@@ -96,24 +96,62 @@ class EveAllianceForm(EveEntityForm):
@admin.register(EveCorporationInfo) @admin.register(EveCorporationInfo)
class EveCorporationInfoAdmin(admin.ModelAdmin): class EveCorporationInfoAdmin(admin.ModelAdmin):
search_fields = ['corporation_name']
list_display = ('corporation_name', 'alliance')
list_select_related = ('alliance',)
list_filter = (('alliance', admin.RelatedOnlyFieldListFilter),)
ordering = ('corporation_name',)
def has_change_permission(self, request, obj=None):
return False
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
if not obj or not obj.pk: if not obj or not obj.pk:
return EveCorporationForm return EveCorporationForm
return super(EveCorporationInfoAdmin, self).get_form(request, obj=obj, **kwargs) return super().get_form(request, obj=obj, **kwargs)
@admin.register(EveAllianceInfo) @admin.register(EveAllianceInfo)
class EveAllianceInfoAdmin(admin.ModelAdmin): class EveAllianceInfoAdmin(admin.ModelAdmin):
search_fields = ['alliance_name']
list_display = ('alliance_name',)
ordering = ('alliance_name',)
def has_change_permission(self, request, obj=None):
return False
def get_form(self, request, obj=None, **kwargs): def get_form(self, request, obj=None, **kwargs):
if not obj or not obj.pk: if not obj or not obj.pk:
return EveAllianceForm return EveAllianceForm
return super(EveAllianceInfoAdmin, self).get_form(request, obj=obj, **kwargs) return super().get_form(request, obj=obj, **kwargs)
@admin.register(EveCharacter) @admin.register(EveCharacter)
class EveCharacterAdmin(admin.ModelAdmin): class EveCharacterAdmin(admin.ModelAdmin):
search_fields = ['character_name', 'corporation_name', 'alliance_name', 'character_ownership__user__username'] search_fields = [
list_display = ('character_name', 'corporation_name', 'alliance_name', 'user', 'main_character') 'character_name',
'corporation_name',
'alliance_name',
'character_ownership__user__username'
]
list_display = (
'character_name', 'corporation_name', 'alliance_name', 'user', 'main_character'
)
list_select_related = (
'character_ownership', 'character_ownership__user__profile__main_character'
)
list_filter = (
'corporation_name',
'alliance_name',
(
'character_ownership__user__profile__main_character',
admin.RelatedOnlyFieldListFilter
),
)
ordering = ('character_name', )
def has_change_permission(self, request, obj=None):
return False
@staticmethod @staticmethod
def user(obj): def user(obj):

View File

@@ -39,4 +39,3 @@ class AutogroupsConfigAdmin(admin.ModelAdmin):
admin.site.register(AutogroupsConfig, AutogroupsConfigAdmin) admin.site.register(AutogroupsConfig, AutogroupsConfigAdmin)
admin.site.register(ManagedCorpGroup) admin.site.register(ManagedCorpGroup)
admin.site.register(ManagedAllianceGroup) admin.site.register(ManagedAllianceGroup)

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-12-23 04:30 # Generated by Django 1.11.6 on 2017-12-23 04:30
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -57,25 +57,21 @@ class AutogroupsConfig(models.Model):
states = models.ManyToManyField(State, related_name='autogroups') states = models.ManyToManyField(State, related_name='autogroups')
corp_groups = models.BooleanField(default=False, corp_groups = models.BooleanField(default=False, help_text="Setting this to false will delete all the created groups.")
help_text="Setting this to false will delete all the created groups.")
corp_group_prefix = models.CharField(max_length=50, default='Corp ', blank=True) corp_group_prefix = models.CharField(max_length=50, default='Corp ', blank=True)
corp_name_source = models.CharField(max_length=20, choices=NAME_OPTIONS, default=OPT_NAME) corp_name_source = models.CharField(max_length=20, choices=NAME_OPTIONS, default=OPT_NAME)
alliance_groups = models.BooleanField(default=False, alliance_groups = models.BooleanField(default=False, help_text="Setting this to false will delete all the created groups.")
help_text="Setting this to false will delete all the created groups.")
alliance_group_prefix = models.CharField(max_length=50, default='Alliance ', blank=True) alliance_group_prefix = models.CharField(max_length=50, default='Alliance ', blank=True)
alliance_name_source = models.CharField(max_length=20, choices=NAME_OPTIONS, default=OPT_NAME) alliance_name_source = models.CharField(max_length=20, choices=NAME_OPTIONS, default=OPT_NAME)
corp_managed_groups = models.ManyToManyField( corp_managed_groups = models.ManyToManyField(
Group, through='ManagedCorpGroup', related_name='corp_managed_config', Group, through='ManagedCorpGroup', related_name='corp_managed_config',
help_text='A list of corporation groups created and maintained by this AutogroupConfig. ' help_text='A list of corporation groups created and maintained by this AutogroupConfig. You should not edit this list unless you know what you\'re doing.')
'You should not edit this list unless you know what you\'re doing.')
alliance_managed_groups = models.ManyToManyField( alliance_managed_groups = models.ManyToManyField(
Group, through='ManagedAllianceGroup', related_name='alliance_managed_config', Group, through='ManagedAllianceGroup', related_name='alliance_managed_config',
help_text='A list of alliance groups created and maintained by this AutogroupConfig. ' help_text='A list of alliance groups created and maintained by this AutogroupConfig. You should not edit this list unless you know what you\'re doing.')
'You should not edit this list unless you know what you\'re doing.')
replace_spaces = models.BooleanField(default=False) replace_spaces = models.BooleanField(default=False)
replace_spaces_with = models.CharField( replace_spaces_with = models.CharField(

View File

@@ -32,9 +32,11 @@ class EveCharacterManager(models.Manager):
def update_character(self, character_id): def update_character(self, character_id):
return self.get(character_id=character_id).update_character() return self.get(character_id=character_id).update_character()
def get_character_by_id(self, char_id): def get_character_by_id(self, character_id: int):
if self.filter(character_id=char_id).exists(): """Return character by character ID or None if not found."""
return self.get(character_id=char_id) try:
return self.get(character_id=character_id)
except self.model.DoesNotExist:
return None return None

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-05 21:39 # Generated by Django 1.10.1 on 2016-09-05 21:39
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-10 20:20 # Generated by Django 1.10.1 on 2016-09-10 20:20
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-10-26 01:49 # Generated by Django 1.10.2 on 2016-10-26 01:49
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-11-01 04:20 # Generated by Django 1.10.2 on 2016-11-01 04:20
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-12-16 23:22 # Generated by Django 1.10.1 on 2016-12-16 23:22
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2017-01-02 19:23 # Generated by Django 1.10.1 on 2017-01-02 19:23
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-01-18 13:20 # Generated by Django 1.10.5 on 2017-01-18 13:20
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-22 23:09 # Generated by Django 1.10.5 on 2017-03-22 23:09
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2017-09-28 02:16 # Generated by Django 1.11.5 on 2017-09-28 02:16
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.1.1 on 2021-01-05 14:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('eveonline', '0012_index_additions'),
]
operations = [
migrations.AddField(
model_name='evecorporationinfo',
name='ceo_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True),
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 3.1.1 on 2021-01-05 14:13
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('eveonline', '0013_evecorporationinfo_ceo_id'),
]
operations = [
migrations.AddIndex(
model_name='evecorporationinfo',
index=models.Index(fields=['ceo_id'], name='eveonline_e_ceo_id_eea7b8_idx'),
),
]

View File

@@ -82,6 +82,7 @@ class EveCorporationInfo(models.Model):
corporation_name = models.CharField(max_length=254, unique=True) corporation_name = models.CharField(max_length=254, unique=True)
corporation_ticker = models.CharField(max_length=254) corporation_ticker = models.CharField(max_length=254)
member_count = models.IntegerField() member_count = models.IntegerField()
ceo_id = models.PositiveIntegerField(blank=True, null=True, default=None)
alliance = models.ForeignKey( alliance = models.ForeignKey(
EveAllianceInfo, blank=True, null=True, on_delete=models.SET_NULL EveAllianceInfo, blank=True, null=True, on_delete=models.SET_NULL
) )
@@ -89,10 +90,14 @@ class EveCorporationInfo(models.Model):
objects = EveCorporationManager() objects = EveCorporationManager()
provider = EveCorporationProviderManager() provider = EveCorporationProviderManager()
class Meta:
indexes = [models.Index(fields=['ceo_id',]),]
def update_corporation(self, corp: providers.Corporation = None): def update_corporation(self, corp: providers.Corporation = None):
if corp is None: if corp is None:
corp = self.provider.get_corporation(self.corporation_id) corp = self.provider.get_corporation(self.corporation_id)
self.member_count = corp.members self.member_count = corp.members
self.ceo_id = corp.ceo_id
try: try:
self.alliance = EveAllianceInfo.objects.get(alliance_id=corp.alliance_id) self.alliance = EveAllianceInfo.objects.get(alliance_id=corp.alliance_id)
except EveAllianceInfo.DoesNotExist: except EveAllianceInfo.DoesNotExist:

View File

@@ -7,6 +7,8 @@ from jsonschema.exceptions import RefResolutionError
from django.conf import settings from django.conf import settings
from esi.clients import esi_client_factory from esi.clients import esi_client_factory
from allianceauth import __version__
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname( SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(
os.path.abspath(__file__)), 'swagger.json' os.path.abspath(__file__)), 'swagger.json'
@@ -166,7 +168,7 @@ class EveSwaggerProvider(EveProvider):
else: else:
try: try:
self._client = esi_client_factory( self._client = esi_client_factory(
token=token, spec_file=SWAGGER_SPEC_PATH token=token, spec_file=SWAGGER_SPEC_PATH, app_info_text=("allianceauth v" + __version__)
) )
except (HTTPError, RefResolutionError): except (HTTPError, RefResolutionError):
logger.exception( logger.exception(
@@ -182,7 +184,7 @@ class EveSwaggerProvider(EveProvider):
def client(self): def client(self):
if self._client is None: if self._client is None:
self._client = esi_client_factory( self._client = esi_client_factory(
token=self._token, spec_file=SWAGGER_SPEC_PATH token=self._token, spec_file=SWAGGER_SPEC_PATH, app_info_text=("allianceauth v" + __version__)
) )
return self._client return self._client

View File

@@ -5,35 +5,96 @@ from .models import EveAllianceInfo
from .models import EveCharacter from .models import EveCharacter
from .models import EveCorporationInfo from .models import EveCorporationInfo
from . import providers
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
TASK_PRIORITY = 7 TASK_PRIORITY = 7
CHUNK_SIZE = 500
def chunks(lst, n):
"""Yield successive n-sized chunks from lst."""
for i in range(0, len(lst), n):
yield lst[i:i + n]
@shared_task @shared_task
def update_corp(corp_id): def update_corp(corp_id):
"""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):
"""Update given alliance from ESI"""
EveAllianceInfo.objects.update_alliance(alliance_id).populate_alliance() EveAllianceInfo.objects.update_alliance(alliance_id).populate_alliance()
@shared_task @shared_task
def update_character(character_id): def update_character(character_id):
"""Update given character from ESI"""
EveCharacter.objects.update_character(character_id) EveCharacter.objects.update_character(character_id)
@shared_task @shared_task
def run_model_update(): def run_model_update():
"""Update all alliances, corporations and characters from ESI"""
# update existing corp models # update existing corp 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
)
# update existing alliance models # update existing 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
)
#update existing character models # update existing character models
for character in EveCharacter.objects.all().values('character_id'): character_ids = EveCharacter.objects.all().values_list('character_id', flat=True)
update_character.apply_async(args=[character['character_id']], priority=TASK_PRIORITY) for character_ids_chunk in chunks(character_ids, CHUNK_SIZE):
affiliations_raw = providers.provider.client.Character\
.post_characters_affiliation(characters=character_ids_chunk).result()
character_names = providers.provider.client.Universe\
.post_universe_names(ids=character_ids_chunk).result()
affiliations = {
affiliation.get('character_id'): affiliation
for affiliation in affiliations_raw
}
# add character names to affiliations
for character in character_names:
character_id = character.get('id')
if character_id in affiliations:
affiliations[character_id]['name'] = character.get('name')
# fetch current characters
characters = EveCharacter.objects.filter(character_id__in=character_ids_chunk)\
.values('character_id', 'corporation_id', 'alliance_id', 'character_name')
for character in characters:
character_id = character.get('character_id')
if character_id in affiliations:
affiliation = affiliations[character_id]
corp_changed = (
character.get('corporation_id') != affiliation.get('corporation_id')
)
alliance_id = character.get('alliance_id')
if not alliance_id:
alliance_id = None
alliance_changed = alliance_id != affiliation.get('alliance_id')
name_changed = False
fetched_name = affiliation.get('name', False)
if fetched_name:
name_changed = character.get('character_name') != fetched_name
if corp_changed or alliance_changed or name_changed:
update_character.apply_async(
args=[character.get('character_id')], priority=TASK_PRIORITY
)

View File

@@ -592,3 +592,12 @@ class TestEveSwaggerProvider(TestCase):
self.assertTrue(mock_esi_client_factory.called) self.assertTrue(mock_esi_client_factory.called)
self.assertIsNotNone(my_provider._client) self.assertIsNotNone(my_provider._client)
self.assertEqual(my_client, 'my_client') self.assertEqual(my_client, 'my_client')
@patch(MODULE_PATH + '.__version__', '1.0.0')
def test_user_agent_header(self):
my_provider = EveSwaggerProvider()
my_client = my_provider.client
operation = my_client.Status.get_status()
self.assertEqual(
operation.future.request.headers['User-Agent'], 'allianceauth v1.0.0'
)

View File

@@ -1,4 +1,4 @@
from unittest.mock import patch from unittest.mock import patch, Mock
from django.test import TestCase from django.test import TestCase
@@ -44,15 +44,17 @@ class TestTasks(TestCase):
mock_EveCharacter.objects.update_character.call_args[0][0], 42 mock_EveCharacter.objects.update_character.call_args[0][0], 42
) )
@patch('allianceauth.eveonline.tasks.update_character')
@patch('allianceauth.eveonline.tasks.update_alliance') @patch('allianceauth.eveonline.tasks.update_character')
@patch('allianceauth.eveonline.tasks.update_corp') @patch('allianceauth.eveonline.tasks.update_alliance')
def test_run_model_update( @patch('allianceauth.eveonline.tasks.update_corp')
self, @patch('allianceauth.eveonline.providers.provider')
mock_update_corp, @patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2)
mock_update_alliance, class TestRunModelUpdate(TestCase):
mock_update_character,
): @classmethod
def setUpClass(cls):
super().setUpClass()
EveCorporationInfo.objects.all().delete() EveCorporationInfo.objects.all().delete()
EveAllianceInfo.objects.all().delete() EveAllianceInfo.objects.all().delete()
EveCharacter.objects.all().delete() EveCharacter.objects.all().delete()
@@ -60,39 +62,184 @@ class TestTasks(TestCase):
EveCorporationInfo.objects.create( EveCorporationInfo.objects.create(
corporation_id=2345, corporation_id=2345,
corporation_name='corp.name', corporation_name='corp.name',
corporation_ticker='corp.ticker', corporation_ticker='c.c.t',
member_count=10, member_count=10,
alliance=None, alliance=None,
) )
EveAllianceInfo.objects.create( EveAllianceInfo.objects.create(
alliance_id=3456, alliance_id=3456,
alliance_name='alliance.name', alliance_name='alliance.name',
alliance_ticker='alliance.ticker', alliance_ticker='a.t',
executor_corp_id='78910', executor_corp_id=5,
) )
EveCharacter.objects.create( EveCharacter.objects.create(
character_id=1234, character_id=1,
character_name='character.name', character_name='character.name1',
corporation_id=2345, corporation_id=2345,
corporation_name='character.corp.name', corporation_name='character.corp.name',
corporation_ticker='c.c.t', # max 5 chars corporation_ticker='c.c.t', # max 5 chars
alliance_id=None
)
EveCharacter.objects.create(
character_id=2,
character_name='character.name2',
corporation_id=9876,
corporation_name='character.corp.name',
corporation_ticker='c.c.t', # max 5 chars
alliance_id=3456, alliance_id=3456,
alliance_name='character.alliance.name', alliance_name='character.alliance.name',
) )
EveCharacter.objects.create(
character_id=3,
character_name='character.name3',
corporation_id=9876,
corporation_name='character.corp.name',
corporation_ticker='c.c.t', # max 5 chars
alliance_id=3456,
alliance_name='character.alliance.name',
)
EveCharacter.objects.create(
character_id=4,
character_name='character.name4',
corporation_id=9876,
corporation_name='character.corp.name',
corporation_ticker='c.c.t', # max 5 chars
alliance_id=3456,
alliance_name='character.alliance.name',
)
"""
EveCharacter.objects.create(
character_id=5,
character_name='character.name5',
corporation_id=9876,
corporation_name='character.corp.name',
corporation_ticker='c.c.t', # max 5 chars
alliance_id=3456,
alliance_name='character.alliance.name',
)
"""
def setUp(self):
self.affiliations = [
{'character_id': 1, 'corporation_id': 5},
{'character_id': 2, 'corporation_id': 9876, 'alliance_id': 3456},
{'character_id': 3, 'corporation_id': 9876, 'alliance_id': 7456},
{'character_id': 4, 'corporation_id': 9876, 'alliance_id': 3456}
]
self.names = [
{'id': 1, 'name': 'character.name1'},
{'id': 2, 'name': 'character.name2'},
{'id': 3, 'name': 'character.name3'},
{'id': 4, 'name': 'character.name4_new'}
]
def test_normal_run(
self,
mock_provider,
mock_update_corp,
mock_update_alliance,
mock_update_character,
):
def get_affiliations(characters: list):
response = [x for x in self.affiliations if x['character_id'] in characters]
mock_operator = Mock(**{'result.return_value': response})
return mock_operator
def get_names(ids: list):
response = [x for x in self.names if x['id'] in ids]
mock_operator = Mock(**{'result.return_value': response})
return mock_operator
mock_provider.client.Character.post_characters_affiliation.side_effect \
= get_affiliations
mock_provider.client.Universe.post_universe_names.side_effect = get_names
run_model_update() run_model_update()
self.assertEqual(
mock_provider.client.Character.post_characters_affiliation.call_count, 2
)
self.assertEqual(
mock_provider.client.Universe.post_universe_names.call_count, 2
)
# character 1 has changed corp
# character 2 no change
# character 3 has changed alliance
# character 4 has changed name
self.assertEqual(mock_update_corp.apply_async.call_count, 1) self.assertEqual(mock_update_corp.apply_async.call_count, 1)
self.assertEqual( self.assertEqual(
int(mock_update_corp.apply_async.call_args[1]['args'][0]), 2345 int(mock_update_corp.apply_async.call_args[1]['args'][0]), 2345
) )
self.assertEqual(mock_update_alliance.apply_async.call_count, 1) self.assertEqual(mock_update_alliance.apply_async.call_count, 1)
self.assertEqual( self.assertEqual(
int(mock_update_alliance.apply_async.call_args[1]['args'][0]), 3456 int(mock_update_alliance.apply_async.call_args[1]['args'][0]), 3456
) )
characters_updated = {
x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list
}
excepted = {1, 3, 4}
self.assertSetEqual(characters_updated, excepted)
self.assertEqual(mock_update_character.apply_async.call_count, 1) def test_ignore_character_not_in_affiliations(
self.assertEqual( self,
int(mock_update_character.apply_async.call_args[1]['args'][0]), 1234 mock_provider,
) mock_update_corp,
mock_update_alliance,
mock_update_character,
):
def get_affiliations(characters: list):
response = [x for x in self.affiliations if x['character_id'] in characters]
mock_operator = Mock(**{'result.return_value': response})
return mock_operator
def get_names(ids: list):
response = [x for x in self.names if x['id'] in ids]
mock_operator = Mock(**{'result.return_value': response})
return mock_operator
del self.affiliations[0]
mock_provider.client.Character.post_characters_affiliation.side_effect \
= get_affiliations
mock_provider.client.Universe.post_universe_names.side_effect = get_names
run_model_update()
characters_updated = {
x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list
}
excepted = {3, 4}
self.assertSetEqual(characters_updated, excepted)
def test_ignore_character_not_in_names(
self,
mock_provider,
mock_update_corp,
mock_update_alliance,
mock_update_character,
):
def get_affiliations(characters: list):
response = [x for x in self.affiliations if x['character_id'] in characters]
mock_operator = Mock(**{'result.return_value': response})
return mock_operator
def get_names(ids: list):
response = [x for x in self.names if x['id'] in ids]
mock_operator = Mock(**{'result.return_value': response})
return mock_operator
del self.names[3]
mock_provider.client.Character.post_characters_affiliation.side_effect \
= get_affiliations
mock_provider.client.Universe.post_universe_names.side_effect = get_names
run_model_update()
characters_updated = {
x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list
}
excepted = {1, 3}
self.assertSetEqual(characters_updated, excepted)

View File

@@ -1,2 +0,0 @@

View File

@@ -5,6 +5,4 @@ from django.utils.translation import ugettext_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, duration = forms.IntegerField(label=_("Duration of fat-link"), required=True, initial=30, min_value=1, max_value=2147483647, help_text=_('minutes'))
max_value=2147483647, help_text=_('minutes'))

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-05 21:39 # Generated by Django 1.10.1 on 2016-09-05 21:39
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-05 22:20 # Generated by Django 1.10.1 on 2016-09-05 22:20
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-06 23:54 # Generated by Django 1.10.1 on 2016-09-06 23:54
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-22 23:35 # Generated by Django 1.10.5 on 2017-03-22 23:35
from __future__ import unicode_literals from __future__ import unicode_literals

View File

@@ -1,6 +1,6 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base.html" %}
{% load bootstrap %} {% load bootstrap %}
{% load staticfiles %} {% load static %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% trans "Create Fatlink" %}{% endblock page_title %} {% block page_title %}{% trans "Create Fatlink" %}{% endblock page_title %}
@@ -30,4 +30,3 @@
</div> </div>
{% endblock content %} {% endblock content %}

View File

@@ -1,6 +1,6 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base.html" %}
{% load bootstrap %} {% load bootstrap %}
{% load staticfiles %} {% load static %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% trans "Fatlink view" %}{% endblock page_title %} {% block page_title %}{% trans "Fatlink view" %}{% endblock page_title %}

View File

@@ -1,6 +1,6 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base.html" %}
{% load bootstrap %} {% load bootstrap %}
{% load staticfiles %} {% load static %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% trans "Personal fatlink statistics" %}{% endblock page_title %} {% block page_title %}{% trans "Personal fatlink statistics" %}{% endblock page_title %}

View File

@@ -1,6 +1,6 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base.html" %}
{% load bootstrap %} {% load bootstrap %}
{% load staticfiles %} {% load static %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% trans "Personal fatlink statistics" %}{% endblock page_title %} {% block page_title %}{% trans "Personal fatlink statistics" %}{% endblock page_title %}

View File

@@ -1,6 +1,6 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base.html" %}
{% load bootstrap %} {% load bootstrap %}
{% load staticfiles %} {% load static %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% trans "Fatlink Corp Statistics" %}{% endblock page_title %} {% block page_title %}{% trans "Fatlink Corp Statistics" %}{% endblock page_title %}
@@ -42,6 +42,7 @@
</div> </div>
{% endblock content %} {% endblock content %}
{% block extra_script %} {% block extra_script %}
$(document).ready(function(){ $(document).ready(function () {
$("[rel=tooltip]").tooltip(); $("[rel=tooltip]").tooltip();
});
{% endblock extra_script %} {% endblock extra_script %}

View File

@@ -1,6 +1,6 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base.html" %}
{% load bootstrap %} {% load bootstrap %}
{% load staticfiles %} {% load static %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% trans "Fatlink statistics" %}{% endblock page_title %} {% block page_title %}{% trans "Fatlink statistics" %}{% endblock page_title %}
@@ -44,6 +44,7 @@
</div> </div>
{% endblock content %} {% endblock content %}
{% block extra_script %} {% block extra_script %}
$(document).ready(function(){ $(document).ready(function () {
$("[rel=tooltip]").tooltip(); $("[rel=tooltip]").tooltip();
});
{% endblock extra_script %} {% endblock extra_script %}

View File

@@ -1,6 +1,6 @@
{% extends "allianceauth/base.html" %} {% extends "allianceauth/base.html" %}
{% load bootstrap %} {% load bootstrap %}
{% load staticfiles %} {% load static %}
{% load i18n %} {% load i18n %}
{% block page_title %}{% trans "Fatlink view" %}{% endblock page_title %} {% block page_title %}{% trans "Fatlink view" %}{% endblock page_title %}

View File

@@ -135,8 +135,7 @@ def fatlink_statistics_corp_view(request, corpid, year=None, month=None):
stat_list.sort(key=lambda stat: stat.mainchar.character_name) stat_list.sort(key=lambda stat: stat.mainchar.character_name)
stat_list.sort(key=lambda stat: (stat.n_fats, stat.avg_fat), reverse=True) stat_list.sort(key=lambda stat: (stat.n_fats, stat.avg_fat), reverse=True)
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year, context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year, 'previous_month': start_of_previous_month, 'corpid': corpid}
'previous_month': start_of_previous_month, 'corpid': corpid}
if datetime.datetime.now() > start_of_next_month: if datetime.datetime.now() > start_of_next_month:
context.update({'next_month': start_of_next_month}) context.update({'next_month': start_of_next_month})
@@ -163,16 +162,14 @@ def fatlink_statistics_view(request, year=datetime.date.today().year, month=date
for fat in fats_in_span.exclude(character__corporation_id__in=fat_stats): for fat in fats_in_span.exclude(character__corporation_id__in=fat_stats):
if EveCorporationInfo.objects.filter(corporation_id=fat.character.corporation_id).exists(): if EveCorporationInfo.objects.filter(corporation_id=fat.character.corporation_id).exists():
fat_stats[fat.character.corporation_id] = CorpStat(fat.character.corporation_id, start_of_month, fat_stats[fat.character.corporation_id] = CorpStat(fat.character.corporation_id, start_of_month, start_of_next_month)
start_of_next_month)
# collect and sort stats # collect and sort stats
stat_list = [fat_stats[x] for x in fat_stats] stat_list = [fat_stats[x] for x in fat_stats]
stat_list.sort(key=lambda stat: stat.corp.corporation_name) stat_list.sort(key=lambda stat: stat.corp.corporation_name)
stat_list.sort(key=lambda stat: (stat.n_fats, stat.avg_fat), reverse=True) stat_list.sort(key=lambda stat: (stat.n_fats, stat.avg_fat), reverse=True)
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year, context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year, 'previous_month': start_of_previous_month}
'previous_month': start_of_previous_month}
if datetime.datetime.now() > start_of_next_month: if datetime.datetime.now() > start_of_next_month:
context.update({'next_month': start_of_next_month}) context.update({'next_month': start_of_next_month})
@@ -199,8 +196,7 @@ def fatlink_personal_statistics_view(request, year=datetime.date.today().year):
monthlystats = [(i + 1, datetime.date(year, i + 1, 1).strftime("%h"), monthlystats[i]) for i in range(12)] monthlystats = [(i + 1, datetime.date(year, i + 1, 1).strftime("%h"), monthlystats[i]) for i in range(12)]
if datetime.datetime.now() > datetime.datetime(year + 1, 1, 1): if datetime.datetime.now() > datetime.datetime(year + 1, 1, 1):
context = {'user': user, 'monthlystats': monthlystats, 'year': year, 'previous_year': year - 1, context = {'user': user, 'monthlystats': monthlystats, 'year': year, 'previous_year': year - 1, 'next_year': year + 1}
'next_year': year + 1}
else: else:
context = {'user': user, 'monthlystats': monthlystats, 'year': year, 'previous_year': year - 1} context = {'user': user, 'monthlystats': monthlystats, 'year': year, 'previous_year': year - 1}
@@ -229,9 +225,11 @@ def fatlink_monthly_personal_statistics_view(request, year, month, char_id=None)
for fat in personal_fats: for fat in personal_fats:
ship_statistics[fat.shiptype] = ship_statistics.setdefault(fat.shiptype, 0) + 1 ship_statistics[fat.shiptype] = ship_statistics.setdefault(fat.shiptype, 0) + 1
n_fats += 1 n_fats += 1
context = {'user': user, 'shipStats': sorted(ship_statistics.items()), 'month': start_of_month.strftime("%h"), context = {
'user': user, 'shipStats': sorted(ship_statistics.items()), 'month': start_of_month.strftime("%h"),
'year': year, 'n_fats': n_fats, 'char_id': char_id, 'previous_month': start_of_previous_month, 'year': year, 'n_fats': n_fats, 'char_id': char_id, 'previous_month': start_of_previous_month,
'next_month': start_of_next_month} 'next_month': start_of_next_month
}
created_fats = Fatlink.objects.filter(creator=user).filter(fatdatetime__gte=start_of_month).filter( created_fats = Fatlink.objects.filter(creator=user).filter(fatdatetime__gte=start_of_month).filter(
fatdatetime__lt=start_of_next_month) fatdatetime__lt=start_of_next_month)
@@ -257,8 +255,7 @@ def click_fatlink_view(request, token, fat_hash=None):
location = c.Location.get_characters_character_id_location(character_id=token.character_id).result() location = c.Location.get_characters_character_id_location(character_id=token.character_id).result()
ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result() ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
location['solar_system_name'] = \ location['solar_system_name'] = \
c.Universe.get_universe_systems_system_id(system_id=location['solar_system_id']).result()[ c.Universe.get_universe_systems_system_id(system_id=location['solar_system_id']).result()['name']
'name']
if location['station_id']: if location['station_id']:
location['station_name'] = \ location['station_name'] = \
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name'] c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']

View File

@@ -1,5 +1,5 @@
from django.conf import settings from django.apps import apps
from django.contrib.auth.models import Permission
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.models import Group as BaseGroup, User from django.contrib.auth.models import Group as BaseGroup, User
from django.db.models import Count from django.db.models import Count
@@ -10,9 +10,8 @@ from django.dispatch import receiver
from .models import AuthGroup from .models import AuthGroup
from .models import GroupRequest from .models import GroupRequest
from . import signals
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS: if 'eve_autogroups' in apps.app_configs:
_has_auto_groups = True _has_auto_groups = True
else: else:
_has_auto_groups = False _has_auto_groups = False
@@ -41,9 +40,6 @@ class AuthGroupInlineAdmin(admin.StackedInline):
kwargs["queryset"] = Group.objects.order_by(Lower('name')) kwargs["queryset"] = Group.objects.order_by(Lower('name'))
return super().formfield_for_manytomany(db_field, request, **kwargs) return super().formfield_for_manytomany(db_field, request, **kwargs)
def has_add_permission(self, request):
return False
def has_delete_permission(self, request, obj=None): def has_delete_permission(self, request, obj=None):
return False return False
@@ -97,9 +93,10 @@ class HasLeaderFilter(admin.SimpleListFilter):
else: else:
return queryset return queryset
class GroupAdmin(admin.ModelAdmin): class GroupAdmin(admin.ModelAdmin):
list_select_related = True list_select_related = ('authgroup',)
ordering = ('name', ) ordering = ('name',)
list_display = ( list_display = (
'name', 'name',
'_description', '_description',
@@ -121,6 +118,9 @@ class GroupAdmin(admin.ModelAdmin):
def get_queryset(self, request): def get_queryset(self, request):
qs = super().get_queryset(request) qs = super().get_queryset(request)
if _has_auto_groups:
qs = qs.prefetch_related('managedalliancegroup_set', 'managedcorpgroup_set')
qs = qs.prefetch_related('authgroup__group_leaders').select_related('authgroup')
qs = qs.annotate( qs = qs.annotate(
member_count=Count('user', distinct=True), member_count=Count('user', distinct=True),
) )
@@ -136,7 +136,7 @@ class GroupAdmin(admin.ModelAdmin):
_member_count.admin_order_field = 'member_count' _member_count.admin_order_field = 'member_count'
def has_leader(self, obj): def has_leader(self, obj):
return obj.authgroup.group_leaders.exists() return obj.authgroup.group_leaders.exists() or obj.authgroup.group_leader_groups.exists()
has_leader.boolean = True has_leader.boolean = True
@@ -166,6 +166,18 @@ class GroupAdmin(admin.ModelAdmin):
filter_horizontal = ('permissions',) filter_horizontal = ('permissions',)
inlines = (AuthGroupInlineAdmin,) inlines = (AuthGroupInlineAdmin,)
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "permissions":
kwargs["queryset"] = Permission.objects.select_related("content_type").all()
return super().formfield_for_manytomany(db_field, request, **kwargs)
def save_formset(self, request, form, formset, change):
for inline_form in formset:
ag_instance = inline_form.save(commit=False)
ag_instance.group = form.instance
ag_instance.save()
formset.save()
class Group(BaseGroup): class Group(BaseGroup):
class Meta: class Meta:
@@ -173,13 +185,29 @@ class Group(BaseGroup):
verbose_name = BaseGroup._meta.verbose_name verbose_name = BaseGroup._meta.verbose_name
verbose_name_plural = BaseGroup._meta.verbose_name_plural verbose_name_plural = BaseGroup._meta.verbose_name_plural
try: try:
admin.site.unregister(BaseGroup) admin.site.unregister(BaseGroup)
finally: finally:
admin.site.register(Group, GroupAdmin) admin.site.register(Group, GroupAdmin)
admin.site.register(GroupRequest) @admin.register(GroupRequest)
class GroupRequestAdmin(admin.ModelAdmin):
search_fields = ('user__username', )
list_display = ('id', 'group', 'user', '_leave_request', 'status')
list_filter = (
('group', admin.RelatedOnlyFieldListFilter),
('user', admin.RelatedOnlyFieldListFilter),
'leave_request',
'status'
)
def _leave_request(self, obj) -> True:
return obj.leave_request
_leave_request.short_description = 'is leave request'
_leave_request.boolean = True
@receiver(pre_save, sender=Group) @receiver(pre_save, sender=Group)

View File

@@ -5,3 +5,6 @@ class GroupManagementConfig(AppConfig):
name = 'allianceauth.groupmanagement' name = 'allianceauth.groupmanagement'
label = 'groupmanagement' label = 'groupmanagement'
verbose_name = 'Group Management' verbose_name = 'Group Management'
def ready(self):
from . import signals # noqa: F401

View File

@@ -0,0 +1,43 @@
from django.utils.translation import ugettext_lazy as _
from allianceauth.services.hooks import MenuItemHook, UrlHook
from allianceauth import hooks
from . import urls
from .managers import GroupManager
class GroupManagementMenuItem(MenuItemHook):
""" This class ensures only authorized users will see the menu entry """
def __init__(self):
# setup menu entry for sidebar
MenuItemHook.__init__(
self,
text=_("Group Management"),
classes="fas fa-users-cog fa-fw",
url_name="groupmanagement:management",
order=50,
navactive=[
"groupmanagement:management", # group requests view
"groupmanagement:membership", # group membership view
"groupmanagement:audit_log", # group audit log view
],
)
def render(self, request):
if GroupManager.can_manage_groups(request.user):
app_count = GroupManager.pending_requests_count_for_user(request.user)
self.count = app_count if app_count and app_count > 0 else None
return MenuItemHook.render(self, request)
return ""
@hooks.register("menu_item_hook")
def register_menu():
return GroupManagementMenuItem()
@hooks.register("url_hook")
def register_urls():
return UrlHook(urls, "group", r"^groups/")

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