Compare commits

...

188 Commits

Author SHA1 Message Date
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
col_crunch
39f7f32b7d Version bump. 2020-07-13 15:32:54 -04:00
colcrunch
b4522a1277 Merge branch 'enable_django_esi_20' into 'master'
Enable django-esi 2.0 dependency

See merge request allianceauth/allianceauth!1232
2020-07-13 18:54:21 +00:00
colcrunch
bb6a7e8327 Merge branch 'issue_1250' into 'master'
Fix Discord service issues and improve dashboard

Closes #1250

See merge request allianceauth/allianceauth!1229
2020-07-13 18:54:08 +00:00
colcrunch
9bd42a7579 Merge branch 'docu_update_contributions' into 'master'
Add contributing chapter to docs

See merge request allianceauth/allianceauth!1233
2020-07-13 18:52:30 +00:00
Erik Kalkoken
b41430e5a3 Add contributing chapter to docs 2020-07-13 18:52:29 +00:00
ErikKalkoken
595353e838 Enable using django-esi 2.0 2020-07-11 12:35:16 +02:00
ErikKalkoken
f1a21bb856 Add state info to dashboard, improve stat change notifications, improve auth tests 2020-07-03 14:58:45 +02:00
ErikKalkoken
e44c2935f9 Version bump for alpha testing 2020-07-03 13:29:00 +02:00
ErikKalkoken
4d546f948d Fix state role not always updated due to lazy properties 2020-07-02 21:26:40 +02:00
ErikKalkoken
3bab349d7b Fix tests 2020-06-30 00:15:48 +02:00
ErikKalkoken
eef6126ef8 Improve state backend fix, add tests for state backend and service signal changes 2020-06-30 00:01:40 +02:00
ErikKalkoken
5c7478fa39 Fix update_nickname runs on ever save of userprofile, fix nickname not updated after main's name changes 2020-06-28 21:38:25 +02:00
ErikKalkoken
64b72d0b06 Fix service signals for state change 2020-06-28 17:14:16 +02:00
ErikKalkoken
b266a98b25 Add more tests for state change 2020-06-28 14:56:01 +02:00
ErikKalkoken
8a27de5df8 Fix state change does not update groups 2020-06-28 01:45:51 +02:00
ErikKalkoken
f9b5310fce Fix user account not deleted when demoted to guest 2020-06-27 23:32:12 +02:00
ErikKalkoken
fdce173969 Add tests to fix new bugs 2020-06-27 16:18:06 +02:00
ErikKalkoken
7b9ddf90c1 Fix tests for tox 2020-06-25 23:38:20 +02:00
ErikKalkoken
580c8c19de Update request timeout default 2020-06-25 22:32:29 +02:00
ErikKalkoken
55cc77140e Fix bug blocking superuser from adding Discord bot 2020-06-25 22:19:48 +02:00
Ariel Rin
93c89dd7cc Merge branch 'wother-master-patch-74872' into 'master'
Updated discord.md

See merge request allianceauth/allianceauth!1228
2020-06-22 03:19:27 +00:00
Carter Foulger
c970cbbd2d Updated discord.md with additional troubleshooting
steps.
2020-06-19 19:45:22 +00:00
Ariel Rin
9ea55fa51f Version Bump 2.7.2 2020-06-11 03:49:06 +00:00
Ariel Rin
5775a11b4e Merge branch 'replace_context_manager_groups' into 'master'
Improve page load performance by replacing groups context manager

See merge request allianceauth/allianceauth!1219
2020-06-11 03:41:31 +00:00
Ariel Rin
1a666b6584 Merge branch 'fontawesomev5' into 'master'
Font Awesome V5 Update

Closes #1207

See merge request allianceauth/allianceauth!1224
2020-06-11 03:33:37 +00:00
Ariel Rin
35407a2108 Font Awesome V5 Update 2020-06-11 03:33:37 +00:00
Ariel Rin
71fb19aa22 Merge branch 'version_battle' into 'master'
Make version relevant to an admin

See merge request allianceauth/allianceauth!1220
2020-06-11 03:13:13 +00:00
AaronKable
b7d7f7b8ce latest stable 2020-06-11 10:47:05 +08:00
Ariel Rin
59b983edcc Merge branch 'future' into 'master'
Remove Future dependency

Closes #1242

See merge request allianceauth/allianceauth!1223
2020-06-11 01:01:23 +00:00
Ariel Rin
1734d034e1 Merge branch 'evemodel_integers' into 'master'
Change EveModels to Integer ID fields

See merge request allianceauth/allianceauth!1211
2020-06-09 13:10:10 +00:00
Aaron Kable
7f7500ff0c Change EveModels to Integer ID fields 2020-06-09 13:10:10 +00:00
Ariel Rin
ce77c24e5c Exclude Celery 4.4.4 2020-06-09 11:30:13 +10:00
Ariel Rin
5469a591c0 Remove Future dependency 2020-06-09 11:10:32 +10:00
Ariel Rin
a4befc5e59 Version Bump 2.7.1 2020-06-09 00:25:30 +00:00
Ariel Rin
1ee8065592 Merge branch 'issue_1244' into 'master'
Fix sleep length must be non-negative

Closes #1244

See merge request allianceauth/allianceauth!1222
2020-06-09 00:23:36 +00:00
ErikKalkoken
e4e3bd44fc Fix sleep length must be non-negative 2020-06-08 14:59:22 +02:00
AaronKable
c75de07c2e Only show Pre-Release when available 2020-06-08 20:47:05 +08:00
AaronKable
e928131809 make version relevant to an admin 2020-06-08 19:18:11 +08:00
Ariel Rin
4f802e82a9 Version Bump to 2.7.0 2020-06-07 06:53:52 +00:00
Ariel Rin
0c90bd462e Merge branch 'remove_test_logging' into 'master'
Remove Test Logging from discourse

See merge request allianceauth/allianceauth!1207
2020-06-07 06:52:08 +00:00
ErikKalkoken
bbb70c93d9 Initial 2020-06-06 17:59:23 +02:00
Ariel Rin
f6e6ba775c Merge branch 'revert-3a984e8a' into 'master'
Revert "Merge branch 'notifications_refresh' into 'master'"

See merge request allianceauth/allianceauth!1216
2020-06-04 14:22:11 +00:00
Ariel Rin
06646be907 Merge branch 'fix_celery_4.4.4_issue' into 'master'
Add future to dependencies to fix celery 4.4.4 issue

See merge request allianceauth/allianceauth!1217
2020-06-04 11:58:28 +00:00
ErikKalkoken
1b4c1a4b9e Add future to dependencies 2020-06-04 13:52:34 +02:00
Ariel Rin
ae3f5a0f62 Revert "Merge branch 'notifications_refresh' into 'master'"
This reverts merge request !1215
2020-06-04 11:21:50 +00:00
Ariel Rin
3a984e8a4d Merge branch 'notifications_refresh' into 'master'
Add notifications auto refresh

See merge request allianceauth/allianceauth!1215
2020-06-04 08:25:01 +00:00
Erik Kalkoken
7d711a54bc Add notifications auto refresh 2020-06-04 08:25:01 +00:00
Ariel Rin
d92d629c25 Merge branch 'orm_fix' into 'master'
Fix Group Managment ORM Queries

See merge request allianceauth/allianceauth!1214
2020-05-27 02:23:37 +00:00
Ariel Rin
21e630209a Merge branch 'issue_1238' into 'master'
PEP440 versioning for admin dashboard

See merge request allianceauth/allianceauth!1213
2020-05-27 02:21:01 +00:00
Erik Kalkoken
e3933998ef PEP440 versioning for admin dashboard 2020-05-27 02:21:00 +00:00
Ariel Rin
667afe9051 Merge branch 'master' into 'master'
Update Extension Logger

Closes #1230

See merge request allianceauth/allianceauth!1206
2020-05-27 02:17:43 +00:00
AaronKable
26dc2881eb fix group managment ORM queries 2020-05-27 08:15:22 +08:00
Col Crunch
250cb33285 Raise an error if get_extension_logger recieves a non-string argument. 2020-05-26 13:49:06 -04:00
Col Crunch
db51abec1f Change default logging level for extension logger. 2020-05-26 13:26:19 -04:00
Col Crunch
530716d458 Fix docstring n get_extension_logger 2020-05-26 13:25:48 -04:00
Ariel Rin
f3065d79b3 Merge branch 'improve_evelinks' into 'master'
Add support for type icons to evelinks

See merge request allianceauth/allianceauth!1210
2020-05-25 14:51:42 +00:00
Erik Kalkoken
bca5f0472e Add support for type icons to evelinks 2020-05-25 14:51:41 +00:00
Ariel Rin
8e54c43917 Merge branch 'mumble-certhash' into 'master'
Add certhash field to Mumble user

See merge request allianceauth/allianceauth!1204
2020-05-25 14:46:03 +00:00
Ariel Rin
946df1d7a0 Merge branch 'issue_1234' into 'master'
Fix issue #1234 and add badges to group requests

Closes #1234

See merge request allianceauth/allianceauth!1212
2020-05-25 13:36:29 +00:00
ErikKalkoken
55f00f742c Fix issue #1234 and add badges to group requests 2020-05-25 15:22:08 +02:00
Ariel Rin
97b2cb71b7 Version Bump to 2.6.6a10 2020-05-25 12:55:05 +00:00
Ariel Rin
ba3a5ba53c Merge branch 'discord_delete_user_bug' into 'master'
Fix API exception handling for delete_user

See merge request allianceauth/allianceauth!1209
2020-05-25 01:28:25 +00:00
ErikKalkoken
953c09c999 Fix backoff exception handling for delete_user 2020-05-24 19:13:31 +02:00
Ariel Rin
b4cc325b07 Merge branch 'fix_test_order_issues' into 'master'
Make notification tests less order dependent

See merge request allianceauth/allianceauth!1208
2020-05-23 04:58:07 +00:00
AaronKable
28c1343f3e make tests less order dependant 2020-05-23 12:54:09 +08:00
Col Crunch
c16fd94c4a Update CI config for new version of gitlab. 2020-05-23 00:31:27 -04:00
Ariel Rin
bb2cc20838 Merge branch 'discord_ignore_managed_roles' into 'master'
Enable Discord service to deal with managed roles

See merge request allianceauth/allianceauth!1205
2020-05-23 04:28:01 +00:00
Erik Kalkoken
7b815fd010 Enable Discord service to deal with managed roles 2020-05-23 04:28:01 +00:00
AaronKable
18584974df remove test logging 2020-05-23 12:18:05 +08:00
Ariel Rin
15823b7785 Merge branch 'stop_context_spam' into 'master'
Stop the notification context provider from hitting the database for every menu hook

See merge request allianceauth/allianceauth!1201
2020-05-23 04:14:10 +00:00
Col Crunch
6c275d4cd2 Add comment to local describing how to change logger level. Also update docs to be consistent with this change. 2020-05-23 00:08:37 -04:00
Col Crunch
2d64ee5e2a Base extension logger level on the level of its parent. 2020-05-23 00:08:00 -04:00
AaronKable
3ae5ffa3f6 tests 2020-05-23 12:04:06 +08:00
Ben Cole
57d9ddc2c6 Add certhash field to Mumble user 2020-05-22 14:04:35 +01:00
Ariel Rin
8b84def494 Merge branch 'discord_update_usernames' into 'master'
Add update username feature

See merge request allianceauth/allianceauth!1203
2020-05-19 03:18:01 +00:00
ErikKalkoken
546f01ceb2 Add update username feature 2020-05-19 00:13:19 +02:00
Ariel Rin
be720d0e0f Merge branch 'redundant-sql' into 'master'
Use existing character variable on dashboard.

See merge request allianceauth/allianceauth!1202
2020-05-18 01:02:08 +00:00
Ariel Rin
72bed03244 Merge branch 'discord_service_overhaul' into 'master'
Discord service major overhaul

See merge request allianceauth/allianceauth!1200
2020-05-18 01:01:13 +00:00
Erik Kalkoken
38083ed284 Discord service major overhaul 2020-05-18 01:01:13 +00:00
Ariel Rin
53f1b94475 Correct typo in bzip2-devel dependency 2020-05-14 09:31:47 +00:00
AaronKable
ed4270a0e3 Use existing character variable. 2020-05-14 17:09:00 +08:00
Ariel Rin
f1d5cc8903 Merge branch 'discourse' into 'master'
API Headers for Discourse

See merge request allianceauth/allianceauth!1197
2020-05-12 00:00:43 +00:00
Ariel Rin
80efdec5d9 Version Bump 2.6.5 2020-05-10 03:17:39 +00:00
Ariel Rin
d49687400a Merge branch 'typo_fix' into 'master'
Typo Fix

See merge request allianceauth/allianceauth!1199
2020-05-10 03:13:35 +00:00
AaronKable
e6e03b50da Sorry Ariel. I promise to do better. 2020-05-08 23:48:18 +08:00
AaronKable
543fa3cfa9 extra logging for tests 2020-05-08 10:03:05 +08:00
Ariel Rin
899988c7c2 Merge branch 'i18n-chinese' into 'master'
Significant Korean Translation, Transifex Updates

See merge request allianceauth/allianceauth!1198
2020-05-08 01:12:37 +00:00
Ariel Rin
2f48dd449b Significant Korean Translation, Transifex Updates 2020-05-08 01:12:37 +00:00
AaronKable
f70fbbdfee headers for Discourse 2020-04-27 14:37:01 +08:00
Ariel Rin
2b09ca240d Merge branch 'fix_docu_py_upgrade' into 'master'
Docs only: Python upgrade guide and mumble installation guide

See merge request allianceauth/allianceauth!1193
2020-04-26 04:30:23 +00:00
Ariel Rin
0626ff84ad Merge branch 'group_add' into 'master'
Direct Group Link Copy Button

See merge request allianceauth/allianceauth!1196
2020-04-26 04:25:28 +00:00
Ariel Rin
62ec746ee3 Merge branch 'Jonnyw2k-master-patch-23183' into 'master'
Add OG metadata to base.html

Closes #1231

See merge request allianceauth/allianceauth!1195
2020-04-26 04:25:12 +00:00
ErikKalkoken
d0f12d7d56 Make customization its own chapter, add tuning section 2020-04-23 17:04:16 +02:00
ErikKalkoken
b806a69604 Improve mumble installation guide 2020-04-19 17:43:43 +02:00
AaronKable
a609d6360b add copy button to group manager screen 2020-04-18 10:43:01 +08:00
Jonnyw2k
dafbfc8644 Update allianceauth/authentication/templates/public/base.html 2020-04-18 02:12:12 +00:00
Jonnyw2k
55413eea19 Update allianceauth/authentication/templates/public/base.html 2020-04-18 01:22:07 +00:00
ErikKalkoken
5247c181af Fix django-celery-beat version in py upgrade guide 2020-04-18 01:14:27 +02:00
Ariel Rin
321af5ec87 Version Bump v2.6.4 2020-04-17 06:56:40 +00:00
Ariel Rin
9ccf340b3d Merge branch 'error-redirects' into 'master'
Add 500 and 400, 403, 404 error redirects back to dashboard with basic message

See merge request allianceauth/allianceauth!1152
2020-04-17 06:45:01 +00:00
Aaron Kable
d7dcacb899 Add 500 and 400, 403, 404 error redirects back to dashboard with basic message 2020-04-17 06:45:01 +00:00
Ariel Rin
8addd483c2 Merge branch 'issue_1221' into 'master'
Remove support for Python 3.5

Closes #1224 and #1221

See merge request allianceauth/allianceauth!1182
2020-04-15 11:25:44 +00:00
Ariel Rin
4d27e5ac9b Merge branch 'improve_help_icon' into 'master'
Improve style of help icon

See merge request allianceauth/allianceauth!1192
2020-04-15 11:24:55 +00:00
ErikKalkoken
31290f6e80 Improve style of help icon 2020-04-11 20:23:41 +02:00
Ariel Rin
c31cc4dbee Merge branch 'mumble_displaynames' into 'master'
Mumble Display Names

See merge request allianceauth/allianceauth!1185
2020-04-06 02:19:53 +00:00
Aaron Kable
cc1f94cf61 Mumble Display Names 2020-04-06 02:19:53 +00:00
Ariel Rin
a9132b8d50 Merge branch 'fix_dev_docs' into 'master'
Add config file for readthedocs

See merge request allianceauth/allianceauth!1191
2020-04-03 13:33:30 +00:00
ErikKalkoken
7b4a9891aa Add config file for readthedocs 2020-04-03 15:18:45 +02:00
Ariel Rin
dcaaf38ecc Merge branch 'esi_update' into 'master'
Update swagger files and remove swagger file dependency from srp package

See merge request allianceauth/allianceauth!1187
2020-04-03 12:39:34 +00:00
Ariel Rin
653a8aa850 Merge branch 'improve_docu_link' into 'master'
Move docu link to top menu and open in new window

See merge request allianceauth/allianceauth!1189
2020-04-03 12:04:06 +00:00
Ariel Rin
274af11385 Merge branch 'docu_devs_update' into 'master'
Extend developer docs

See merge request allianceauth/allianceauth!1188
2020-04-03 12:03:34 +00:00
Erik Kalkoken
170b246901 Extend developer docs 2020-04-03 12:03:34 +00:00
Ariel Rin
5250432ce3 Version Bump v2.6.3 2020-04-02 03:51:59 +00:00
Ariel Rin
53d6e973eb Merge branch 'i18n-chinese' into 'master'
Update Translations from Transifex

See merge request allianceauth/allianceauth!1190
2020-04-02 03:32:39 +00:00
Ariel Rin
c9bdd62d53 Update Translations from Transifex 2020-04-02 03:32:39 +00:00
Ariel Rin
7eb98af528 Merge branch 'issue_1225' into 'master'
Fix broken link and remove outdated migrations for services name formatter

Closes #1225

See merge request allianceauth/allianceauth!1183
2020-04-02 03:04:55 +00:00
Ariel Rin
385e3e21b3 Merge branch 'improve_groups_view' into 'master'
Add sorting to group view and add tests to group management

See merge request allianceauth/allianceauth!1172
2020-04-02 03:01:27 +00:00
Erik Kalkoken
127ec63d76 Add sorting to group view and add tests to group management 2020-04-02 03:01:27 +00:00
Ariel Rin
4988b5f260 Merge branch 'common_logger' into 'master'
Extensions Logging

See merge request allianceauth/allianceauth!1180
2020-04-02 02:59:55 +00:00
Ariel Rin
f28a50f92c Merge branch 'fix_translations_3' into 'master'
Add missing translations

See merge request allianceauth/allianceauth!1186
2020-03-26 03:06:11 +00:00
Ariel Rin
e8efe8e609 Merge branch 'i18n-chinese' into 'master'
Add Korean and Russian, Update from Transifex

See merge request allianceauth/allianceauth!1179
2020-03-26 02:50:25 +00:00
Ariel Rin
d7e7457bc5 Add Korean and Russian, Update from Transifex 2020-03-26 02:50:25 +00:00
Ariel Rin
daff927811 Merge branch 'prioritize_celery' into 'master'
Add Celery Priorities

See merge request allianceauth/allianceauth!1181
2020-03-26 02:20:02 +00:00
Aaron Kable
8861ec0a61 Add Celery Priorities 2020-03-26 02:20:02 +00:00
Ariel Rin
bd4321f61a Merge branch 'docs_update' into 'master'
Docs only: Harmonize gunicorn config, add localization feature

See merge request allianceauth/allianceauth!1184
2020-03-26 01:55:03 +00:00
Erik Kalkoken
d831482fe0 Docs only: Harmonize gunicorn config, add localization feature 2020-03-26 01:55:03 +00:00
ErikKalkoken
9ea79ea389 Move docu link to top menu and open in new window 2020-03-26 00:10:30 +01:00
ErikKalkoken
b6fdf840ef Update swagger files and remove swagger fle dependency from srp package 2020-03-25 18:00:23 +01:00
ErikKalkoken
73f262ce4b Add missing translations 2020-03-24 20:21:35 +01:00
ErikKalkoken
f63434adc3 Fix broken link and remove outdated migrations for services name formatter 2020-03-21 14:41:45 +01:00
ErikKalkoken
42948386ec Remove support for Python 3.5 2020-03-21 13:16:42 +01:00
Aaron Kable
1ce0dbde0e stop the notification context profider from hitting the database for each menu hook 2020-03-16 02:09:24 +00:00
Ariel Rin
32e0621b0a Merge branch 'improve_install_docu' into 'master'
Docs: Add python upgrade guide, remove old AA 1.15 upgrade guide, improve install guide

See merge request allianceauth/allianceauth!1177
2020-03-15 12:21:21 +00:00
Erik Kalkoken
78e05b84e9 Docs: Add python upgrade guide, remove old AA 1.15 upgrade guide, improve install guide 2020-03-15 12:21:21 +00:00
Col Crunch
76ebd21163 Add function to services.hooks to provide a concise way for creating loggers for extensions/plugins. Revise basic documentation to use this function. 2020-03-13 15:21:15 -04:00
Col Crunch
38aaf545c6 Add some very basic docs for logging changes 2020-03-13 14:42:09 -04:00
Col Crunch
527d7ef671 Change level of extension_file handler, rename the logger from allianceauth.extensions to extensions and remove propagate from the logger. 2020-03-13 04:42:09 -04:00
Col Crunch
e54b80e061 Add a common logger (and specific log file) for extensions to utilize. 2020-03-13 00:33:35 -04:00
Col Crunch
27f95a8b2c Remove Zone.Indentifier files. 2020-03-12 23:55:34 -04:00
Ariel Rin
a1e8903128 Version Bump v2.6.2 2020-03-09 15:53:51 +00:00
Ariel Rin
b00ac2aef4 Merge branch 'i18n-chinese' into 'master'
Update German and Spanish Locales, Add Chinese Simplified with Transifex

See merge request allianceauth/allianceauth!1174
2020-03-09 15:51:12 +00:00
Ariel Rin
8865d15ed9 Update German and Spanish Locales, Add Chinese Simplified with Transifex 2020-03-09 15:51:12 +00:00
Ariel Rin
fc3d4b7f33 Merge branch 'hotfix_groups' into 'master'
HOTFIX GroupManager for Groups as Group Leads

See merge request allianceauth/allianceauth!1178
2020-03-09 08:17:41 +00:00
Ariel Rin
934cc44540 Merge branch 'fix_dashboard_sorting' into 'master'
Improve groups and character lists on dashboard

See merge request allianceauth/allianceauth!1171
2020-03-09 08:14:26 +00:00
AaronKable
106de3dd4c HOTFIX Group manager for Groups as Group Leads 2020-03-09 15:48:30 +08:00
Ariel Rin
9b55cfcbe3 Merge branch 'issue_1214' into 'master'
Documentation overhaul

Closes #1216 and #1214

See merge request allianceauth/allianceauth!1166
2020-03-05 02:23:58 +00:00
Erik Kalkoken
8137f1023a Documentation overhaul 2020-03-05 02:23:58 +00:00
Ariel Rin
d670e33b6f Merge branch 'fix_translation_strings_2' into 'master'
fix broken translation strings (part 2)

See merge request allianceauth/allianceauth!1176
2020-03-04 23:00:08 +00:00
ErikKalkoken
3d3bb8fc94 fix broken translation strings 2020-03-03 13:53:55 +01:00
Ariel Rin
9c880eae8a Merge branch 'fix_translation_string_bugs' into 'master'
Fix translation string bugs

See merge request allianceauth/allianceauth!1175
2020-03-03 00:52:18 +00:00
ErikKalkoken
54a71630f1 Fix translation string bugs 2020-02-29 15:55:42 +01:00
Ariel Rin
923a8453cc Merge branch 'fix_coverage_for_core' into 'master'
Remove coverage output from core tests

See merge request allianceauth/allianceauth!1173
2020-02-22 15:57:48 +00:00
ErikKalkoken
00447ca819 Remove coverage output from core tests 2020-02-22 16:50:04 +01:00
Ariel Rin
ad4ee9d822 Version Bump v2.6.1 2020-02-22 12:59:41 +00:00
Ariel Rin
40e9dbfda2 Merge branch 'issue_1219' into 'master'
HOTFIX: Fix startup error when autogroups is not installed

Closes #1219

See merge request allianceauth/allianceauth!1169
2020-02-22 12:58:28 +00:00
ErikKalkoken
b9da6911e6 Fix Mumble search issue 2020-02-20 20:07:48 +01:00
ErikKalkoken
81f9211098 Fix update_main_character_model() bug 2020-02-20 17:16:28 +01:00
ErikKalkoken
8290081365 Fix missing import bug in UserAdmin, StateAdmin, add tests for those cases 2020-02-20 15:29:54 +01:00
ErikKalkoken
81af610c11 Add sorting to characters and groups and remove auto groups 2020-02-19 01:29:14 +01:00
ErikKalkoken
cfa2cf58f3 Fix issue #1219 2020-02-18 20:35:34 +01:00
ErikKalkoken
01c17d28f6 Extend tox setup to include core only testing 2020-02-18 19:34:44 +01:00
288 changed files with 24853 additions and 5677 deletions

8
.gitignore vendored
View File

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

View File

@@ -1,30 +1,43 @@
stages: stages:
- "test" - test
- deploy - deploy
before_script: before_script:
- apt-get update && apt-get install redis-server -y
- redis-server --daemonize yes
- redis-cli ping
- python -V - python -V
- pip install wheel tox - pip install wheel tox
test-3.5: test-3.6-core:
image: python:3.5-buster
script:
- tox -e py35
test-3.6:
image: python:3.6-buster image: python:3.6-buster
script: script:
- tox -e py36 - tox -e py36-core
test-3.7: test-3.7-core:
image: python:3.7-buster image: python:3.7-buster
script: script:
- tox -e py37 - tox -e py37-core
test-3.8: test-3.8-core:
image: python:3.8-buster image: python:3.8-buster
script: script:
- tox -e py38 - tox -e py38-core
test-3.6-all:
image: python:3.6-buster
script:
- tox -e py36-all
test-3.7-all:
image: python:3.7-buster
script:
- tox -e py37-all
test-3.8-all:
image: python:3.8-buster
script:
- tox -e py38-all
deploy_production: deploy_production:
stage: deploy stage: deploy
@@ -37,5 +50,5 @@ deploy_production:
- python setup.py sdist - python setup.py sdist
- twine upload dist/* - twine upload dist/*
only: rules:
- tags - if: $CI_COMMIT_TAG

27
.readthedocs.yml Normal file
View File

@@ -0,0 +1,27 @@
# .readthedocs.yml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/conf.py
# Build documentation with MkDocs
#mkdocs:
# configuration: mkdocs.yml
# Optionally build your docs in additional formats such as PDF and ePub
formats: all
# Optionally set the version of Python and requirements required to build your docs
python:
version: 3.7
install:
- method: pip
path: .
extra_requirements:
- testing
system_packages: true

View File

@@ -11,32 +11,34 @@
An auth system for EVE Online to help in-game organizations manage online service access. An auth system for EVE Online to help in-game organizations manage online service access.
## Contens ## Content
- [Overview](#overview) - [Overview](#overview)
- [Documentation](http://allianceauth.rtfd.io) - [Documentation](http://allianceauth.rtfd.io)
- [Support](#support) - [Support](#support)
- [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases) - [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases)
- [Devloper Team](#developer-team) - [Developer Team](#developer-team)
- [Contributing](#contributing) - [Contributing](#contributing)
## Overview ## Overview
Alliance Auth (AA) is a web application that helps Eve Online organizations efficiently manage access to their applications and services. Alliance Auth (AA) is a web site that helps Eve Online organizations efficiently manage access to applications and services.
Main features: Main features:
- Automatically grants or revokes user access to external applications / services (e.g. Discord, Mumble) and web apps (e.g. SRP requests) based on the user's current membership to [in-game organizations](https://allianceauth.readthedocs.io/en/latest/features/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/groups/) - Automatically grants or revokes user access to external services (e.g. Discord, Mumble) and web apps (e.g. SRP requests) based on the user's current membership to [in-game organizations](https://allianceauth.readthedocs.io/en/latest/features/core/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/core/groups/)
- Provides a central web site where users can directly access web apps (e.g. SRP requests, Fleet Schedule) and manage their access to external services and groups. - Provides a central web site where users can directly access web apps (e.g. SRP requests, Fleet Schedule) and manage their access to external services and groups.
- Includes a set of connectors (called ["services"](https://allianceauth.readthedocs.io/en/latest/installation/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others - Includes a set of connectors (called ["services"](https://allianceauth.readthedocs.io/en/latest/features/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others
- Includes a set of web apps called ["plug-in apps"](https://allianceauth.readthedocs.io/en/latest/features/) which add many useful functions: fleet schedule, timer board, SRP request management, fleet activity tracker and character application management - Includes a set of web [apps](https://allianceauth.readthedocs.io/en/latest/features/apps/) which add many useful functions, e.g.: fleet schedule, timer board, SRP request management, fleet activity tracker
- Can be easily extended with new services and plugin-apps. Many additional services and plugin-apps 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)
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [offical documentation](http://allianceauth.rtfd.io). - Chinese :cn:, English :us:, German :de: and Spanish :es: 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).
## Screenshot ## Screenshot

View File

@@ -1,6 +1,8 @@
# 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.6.0' __version__ = '2.7.5'
NAME = 'Alliance Auth v%s' % __version__ __title__ = 'Alliance Auth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = '%s v%s' % (__title__, __version__)
default_app_config = 'allianceauth.apps.AllianceAuthConfig' default_app_config = 'allianceauth.apps.AllianceAuthConfig'

View File

@@ -18,14 +18,14 @@ from django.utils.text import slugify
from allianceauth.authentication.models import State, get_guest_state,\ from allianceauth.authentication.models import State, get_guest_state,\
CharacterOwnership, UserProfile, OwnershipRecord CharacterOwnership, UserProfile, OwnershipRecord
from allianceauth.hooks import get_hooks from allianceauth.hooks import get_hooks
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\
EveAllianceInfo
from allianceauth.eveonline.tasks import update_character 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: if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True _has_auto_groups = True
from allianceauth.eveonline.autogroups.models import *
else: else:
_has_auto_groups = False _has_auto_groups = False
@@ -37,8 +37,11 @@ def make_service_hooks_update_groups_action(service):
:return: fn to update services groups for the selected users :return: fn to update services groups for the selected users
""" """
def update_service_groups(modeladmin, request, queryset): def update_service_groups(modeladmin, request, queryset):
for user in queryset: # queryset filtering doesn't work here? if hasattr(service, 'update_groups_bulk'):
service.update_groups(user) service.update_groups_bulk(queryset)
else:
for user in queryset: # queryset filtering doesn't work here?
service.update_groups(user)
update_service_groups.__name__ = str('update_{}_groups'.format(slugify(service.name))) update_service_groups.__name__ = str('update_{}_groups'.format(slugify(service.name)))
update_service_groups.short_description = "Sync groups for selected {} accounts".format(service.title) update_service_groups.short_description = "Sync groups for selected {} accounts".format(service.title)
@@ -52,8 +55,11 @@ def make_service_hooks_sync_nickname_action(service):
:return: fn to sync nickname for the selected users :return: fn to sync nickname for the selected users
""" """
def sync_nickname(modeladmin, request, queryset): def sync_nickname(modeladmin, request, queryset):
for user in queryset: # queryset filtering doesn't work here? if hasattr(service, 'sync_nicknames_bulk'):
service.sync_nickname(user) service.sync_nicknames_bulk(queryset)
else:
for user in queryset: # queryset filtering doesn't work here?
service.sync_nickname(user)
sync_nickname.__name__ = str('sync_{}_nickname'.format(slugify(service.name))) sync_nickname.__name__ = str('sync_{}_nickname'.format(slugify(service.name)))
sync_nickname.short_description = "Sync nicknames for selected {} accounts".format(service.title) sync_nickname.short_description = "Sync nicknames for selected {} accounts".format(service.title)
@@ -241,6 +247,22 @@ class MainAllianceFilter(admin.SimpleListFilter):
self.value()) self.value())
def update_main_character_model(modeladmin, request, queryset):
tasks_count = 0
for obj in queryset:
if obj.profile.main_character:
update_character.delay(obj.profile.main_character.character_id)
tasks_count += 1
modeladmin.message_user(
request,
'Update from ESI started for {} characters'.format(tasks_count)
)
update_main_character_model.short_description = \
'Update main character model from ESI'
class UserAdmin(BaseUserAdmin): class UserAdmin(BaseUserAdmin):
"""Extending Django's UserAdmin model """Extending Django's UserAdmin model
@@ -272,28 +294,13 @@ class UserAdmin(BaseUserAdmin):
else: else:
return queryset.filter(groups__pk=self.value()) return queryset.filter(groups__pk=self.value())
def update_main_character_model(self, request, queryset):
tasks_count = 0
for obj in queryset:
if obj.profile.main_character:
update_character.delay(obj.profile.main_character.character_id)
tasks_count += 1
self.message_user(
request,
'Update from ESI started for {} characters'.format(tasks_count)
)
update_main_character_model.short_description = \
'Update main character model from ESI'
def get_actions(self, request): def get_actions(self, request):
actions = super(BaseUserAdmin, self).get_actions(request) actions = super(BaseUserAdmin, self).get_actions(request)
actions[self.update_main_character_model.__name__] = ( actions[update_main_character_model.__name__] = (
self.update_main_character_model, update_main_character_model,
self.update_main_character_model.__name__, update_main_character_model.__name__,
self.update_main_character_model.short_description update_main_character_model.short_description
) )
for hook in get_hooks('services_hook'): for hook in get_hooks('services_hook'):
@@ -505,7 +512,7 @@ class BaseOwnershipAdmin(admin.ModelAdmin):
'character', 'character',
) )
search_fields = ( search_fields = (
'user__user', 'user__username',
'character__character_name', 'character__character_name',
'character__corporation_name', 'character__corporation_name',
'character__alliance_name' 'character__alliance_name'

View File

@@ -1,7 +1,8 @@
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import Permission
from django.contrib.auth.models import User
import logging import logging
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User, Permission
from .models import UserProfile, CharacterOwnership, OwnershipRecord from .models import UserProfile, CharacterOwnership, OwnershipRecord
@@ -11,9 +12,11 @@ logger = logging.getLogger(__name__)
class StateBackend(ModelBackend): class StateBackend(ModelBackend):
@staticmethod @staticmethod
def _get_state_permissions(user_obj): def _get_state_permissions(user_obj):
profile_state_field = UserProfile._meta.get_field('state') """returns permissions for state of given user object"""
user_state_query = 'state__%s__user' % profile_state_field.related_query_name() if hasattr(user_obj, "profile") and user_obj.profile:
return Permission.objects.filter(**{user_state_query: user_obj}) return Permission.objects.filter(state=user_obj.profile.state)
else:
return Permission.objects.none()
def get_state_permissions(self, user_obj, obj=None): def get_state_permissions(self, user_obj, obj=None):
return self._get_permissions(user_obj, obj, 'state') return self._get_permissions(user_obj, obj, 'state')

View File

@@ -73,11 +73,17 @@ class UserProfile(models.Model):
if commit: if commit:
logger.info('Updating {} state to {}'.format(self.user, self.state)) logger.info('Updating {} state to {}'.format(self.user, self.state))
self.save(update_fields=['state']) self.save(update_fields=['state'])
notify(self.user, _('State Changed'), notify(
_('Your user state has been changed to %(state)s') % ({'state': state}), self.user,
'info') _('State changed to: %s' % state),
_('Your user\'s state is now: %(state)s')
% ({'state': state}),
'info'
)
from allianceauth.authentication.signals import state_changed from allianceauth.authentication.signals import state_changed
state_changed.send(sender=self.__class__, user=self.user, state=self.state) state_changed.send(
sender=self.__class__, user=self.user, state=self.state
)
def __str__(self): def __str__(self):
return str(self.user) return str(self.user)

View File

@@ -23,9 +23,7 @@ def trigger_state_check(state):
check_states = State.objects.filter(priority__lt=state.priority) check_states = State.objects.filter(priority__lt=state.priority)
for profile in UserProfile.objects.filter(state__in=check_states): for profile in UserProfile.objects.filter(state__in=check_states):
if state.available_to_user(profile.user): if state.available_to_user(profile.user):
profile.state = state profile.assign_state(state)
profile.save(update_fields=['state'])
state_changed.send(sender=state.__class__, user=profile.user, state=state)
@receiver(m2m_changed, sender=State.member_characters.through) @receiver(m2m_changed, sender=State.member_characters.through)

View File

@@ -14,7 +14,11 @@
<div class="col-sm-6 text-center"> <div class="col-sm-6 text-center">
<div class="panel panel-primary" style="height:100%"> <div class="panel panel-primary" style="height:100%">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">{% trans "Main Character" %}</h3> <h3 class="panel-title">
{% blocktrans with state=request.user.profile.state %}
Main Character (State: {{ state }})
{% endblocktrans %}
</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
{% if request.user.profile.main_character %} {% if request.user.profile.main_character %}
@@ -94,12 +98,12 @@
<div class="col-sm-6 text-center"> <div class="col-sm-6 text-center">
<div class="panel panel-success" style="height:100%"> <div class="panel panel-success" style="height:100%">
<div class="panel-heading"> <div class="panel-heading">
<h3 class="panel-title">{% trans "Groups" %}</h3> <h3 class="panel-title">{% trans "Group Memberships" %}</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<div style="height: 240px;overflow:-moz-scrollbars-vertical;overflow-y:auto;"> <div style="height: 240px;overflow:-moz-scrollbars-vertical;overflow-y:auto;">
<table class="table table-aa"> <table class="table table-aa">
{% for group in user.groups.all %} {% for group in groups %}
<tr> <tr>
<td>{{ group.name }}</td> <td>{{ group.name }}</td>
</tr> </tr>
@@ -128,34 +132,30 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for ownership in request.user.character_ownerships.all %} {% for char in characters %}
{% with ownership.character as char %} <tr>
<tr> <td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}"> </td>
</td> <td class="text-center">{{ char.character_name }}</td>
<td class="text-center">{{ char.character_name }}</td> <td class="text-center">{{ char.corporation_name }}</td>
<td class="text-center">{{ char.corporation_name }}</td> <td class="text-center">{{ char.alliance_name }}</td>
<td class="text-center">{{ char.alliance_name }}</td> </tr>
</tr>
{% endwith %}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<table class="table table-aa visible-xs-block" style="width: 100%"> <table class="table table-aa visible-xs-block" style="width: 100%">
<tbody> <tbody>
{% for ownership in request.user.character_ownerships.all %} {% for char in characters %}
{% with ownership.character as char %} <tr>
<tr> <td class="text-center" style="vertical-align: middle">
<td class="text-center" style="vertical-align: middle"> <img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}"> </td>
</td> <td class="text-center" style="vertical-align: middle; width: 100%">
<td class="text-center" style="vertical-align: middle; width: 100%"> <strong>{{ char.character_name }}</strong><br>
<strong>{{ char.character_name }}</strong><br> {{ char.corporation_name }}<br>
{{ char.corporation_name }}<br> {{ char.alliance_name|default:"" }}
{{ char.alliance_name|default:"" }} </td>
</td> </tr>
</tr>
{% endwith %}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>

View File

@@ -6,6 +6,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content=""> <meta name="description" content="">
<meta name="author" content=""> <meta name="author" content="">
<meta property="og:title" content="{{ SITE_NAME }}">
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'icons/apple-touch-icon.png' %}">
<meta property="og:description" content="Alliance Auth - An auth system for EVE Online to help in-game organizations manage online service access.">
{% include 'allianceauth/icons.html' %} {% include 'allianceauth/icons.html' %}
<title>{% block title %}{{ SITE_NAME }}{% endblock %}</title> <title>{% block title %}{{ SITE_NAME }}{% endblock %}</title>

View File

@@ -1,5 +1,5 @@
{% load i18n %}{% autoescape off %} {% load i18n %}{% autoescape off %}
{% blocktrans %}You're receiving this email because you requested a password reset for your {% blocktrans trimmed %}You're receiving this email because you requested a password reset for your
user account.{% endblocktrans %} user account.{% endblocktrans %}
{% trans "Please go to the following page and choose a new password:" %} {% trans "Please go to the following page and choose a new password:" %}

View File

@@ -0,0 +1,18 @@
from django.urls import reverse
def get_admin_change_view_url(obj: object) -> str:
"""returns URL to admin change view for given object"""
return reverse(
'admin:{}_{}_change'.format(
obj._meta.app_label, type(obj).__name__.lower()
),
args=(obj.pk,)
)
def get_admin_search_url(ModelClass: type) -> str:
"""returns URL to search URL for model of given object"""
return '{}{}/'.format(
reverse('admin:app_list', args=(ModelClass._meta.app_label,)),
ModelClass.__name__.lower()
)

View File

@@ -1,43 +1,64 @@
from unittest.mock import patch from urllib.parse import quote
from unittest.mock import patch, MagicMock
from django.test import TestCase, RequestFactory from django.conf import settings
from django.contrib import admin 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 User as BaseUser, Group
from django.test import TestCase, RequestFactory, Client
from allianceauth.authentication.models import CharacterOwnership, State from allianceauth.authentication.models import (
from allianceauth.eveonline.autogroups.models import AutogroupsConfig CharacterOwnership, State, OwnershipRecord
)
from allianceauth.eveonline.models import ( from allianceauth.eveonline.models import (
EveCharacter, EveCorporationInfo, EveAllianceInfo EveCharacter, EveCorporationInfo, EveAllianceInfo
) )
from allianceauth.services.hooks import ServicesHook
from allianceauth.tests.auth_utils import AuthUtils
from ..admin import ( from ..admin import (
BaseUserAdmin, BaseUserAdmin,
CharacterOwnershipAdmin,
PermissionAdmin,
StateAdmin,
MainCorporationsFilter, MainCorporationsFilter,
MainAllianceFilter, MainAllianceFilter,
OwnershipRecordAdmin,
User, User,
UserAdmin, UserAdmin,
user_main_organization, user_main_organization,
user_profile_pic, user_profile_pic,
user_username, user_username,
update_main_character_model,
make_service_hooks_update_groups_action,
make_service_hooks_sync_nickname_action
) )
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'
class MockRequest(object): class MockRequest(object):
def __init__(self, user=None): def __init__(self, user=None):
self.user = user self.user = user
class TestCaseWithTestData(TestCase):
class TestUserAdmin(TestCase):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
super().setUpClass() super().setUpClass()
for MyModel in [
EveAllianceInfo, EveCorporationInfo, EveCharacter, Group, User
]:
MyModel.objects.all().delete()
# groups # groups
cls.group_1 = Group.objects.create( cls.group_1 = Group.objects.create(
name='Group 1' name='Group 1'
@@ -45,62 +66,62 @@ class TestUserAdmin(TestCase):
cls.group_2 = Group.objects.create( cls.group_2 = Group.objects.create(
name='Group 2' name='Group 2'
) )
# user 1 - corp and alliance, normal user # user 1 - corp and alliance, normal user
cls.character_1 = EveCharacter.objects.create( character_1 = EveCharacter.objects.create(
character_id='1001', character_id=1001,
character_name='Bruce Wayne', character_name='Bruce Wayne',
corporation_id='2001', corporation_id=2001,
corporation_name='Wayne Technologies', corporation_name='Wayne Technologies',
corporation_ticker='WT', corporation_ticker='WT',
alliance_id='3001', alliance_id=3001,
alliance_name='Wayne Enterprises', alliance_name='Wayne Enterprises',
alliance_ticker='WE', alliance_ticker='WE',
) )
cls.character_1a = EveCharacter.objects.create( character_1a = EveCharacter.objects.create(
character_id='1002', character_id=1002,
character_name='Batman', character_name='Batman',
corporation_id='2001', corporation_id=2001,
corporation_name='Wayne Technologies', corporation_name='Wayne Technologies',
corporation_ticker='WT', corporation_ticker='WT',
alliance_id='3001', alliance_id=3001,
alliance_name='Wayne Enterprises', alliance_name='Wayne Enterprises',
alliance_ticker='WE', alliance_ticker='WE',
) )
alliance = EveAllianceInfo.objects.create( alliance = EveAllianceInfo.objects.create(
alliance_id='3001', alliance_id=3001,
alliance_name='Wayne Enterprises', alliance_name='Wayne Enterprises',
alliance_ticker='WE', alliance_ticker='WE',
executor_corp_id='2001' executor_corp_id=2001
) )
EveCorporationInfo.objects.create( EveCorporationInfo.objects.create(
corporation_id='2001', corporation_id=2001,
corporation_name='Wayne Technologies', corporation_name='Wayne Technologies',
corporation_ticker='WT', corporation_ticker='WT',
member_count=42, member_count=42,
alliance=alliance alliance=alliance
) )
cls.user_1 = User.objects.create_user( cls.user_1 = User.objects.create_user(
cls.character_1.character_name.replace(' ', '_'), character_1.character_name.replace(' ', '_'),
'abc@example.com', 'abc@example.com',
'password' 'password'
) )
CharacterOwnership.objects.create( CharacterOwnership.objects.create(
character=cls.character_1, character=character_1,
owner_hash='x1' + cls.character_1.character_name, owner_hash='x1' + character_1.character_name,
user=cls.user_1 user=cls.user_1
) )
CharacterOwnership.objects.create( CharacterOwnership.objects.create(
character=cls.character_1a, character=character_1a,
owner_hash='x1' + cls.character_1a.character_name, owner_hash='x1' + character_1a.character_name,
user=cls.user_1 user=cls.user_1
) )
cls.user_1.profile.main_character = cls.character_1 cls.user_1.profile.main_character = character_1
cls.user_1.profile.save() cls.user_1.profile.save()
cls.user_1.groups.add(cls.group_1) cls.user_1.groups.add(cls.group_1)
# user 2 - corp only, staff # user 2 - corp only, staff
cls.character_2 = EveCharacter.objects.create( character_2 = EveCharacter.objects.create(
character_id=1003, character_id=1003,
character_name='Clark Kent', character_name='Clark Kent',
corporation_id=2002, corporation_id=2002,
@@ -116,23 +137,23 @@ class TestUserAdmin(TestCase):
alliance=None alliance=None
) )
cls.user_2 = User.objects.create_user( cls.user_2 = User.objects.create_user(
cls.character_2.character_name.replace(' ', '_'), character_2.character_name.replace(' ', '_'),
'abc@example.com', 'abc@example.com',
'password' 'password'
) )
CharacterOwnership.objects.create( CharacterOwnership.objects.create(
character=cls.character_2, character=character_2,
owner_hash='x1' + cls.character_2.character_name, owner_hash='x1' + character_2.character_name,
user=cls.user_2 user=cls.user_2
) )
cls.user_2.profile.main_character = cls.character_2 cls.user_2.profile.main_character = character_2
cls.user_2.profile.save() cls.user_2.profile.save()
cls.user_2.groups.add(cls.group_2) cls.user_2.groups.add(cls.group_2)
cls.user_2.is_staff = True cls.user_2.is_staff = True
cls.user_2.save() cls.user_2.save()
# user 3 - no main, no group, superuser # user 3 - no main, no group, superuser
cls.character_3 = EveCharacter.objects.create( character_3 = EveCharacter.objects.create(
character_id=1101, character_id=1101,
character_name='Lex Luthor', character_name='Lex Luthor',
corporation_id=2101, corporation_id=2101,
@@ -148,40 +169,136 @@ class TestUserAdmin(TestCase):
alliance=None alliance=None
) )
EveAllianceInfo.objects.create( EveAllianceInfo.objects.create(
alliance_id='3101', alliance_id=3101,
alliance_name='Lex World Domination', alliance_name='Lex World Domination',
alliance_ticker='LWD', alliance_ticker='LWD',
executor_corp_id='' executor_corp_id=2101
) )
cls.user_3 = User.objects.create_user( cls.user_3 = User.objects.create_user(
cls.character_3.character_name.replace(' ', '_'), character_3.character_name.replace(' ', '_'),
'abc@example.com', 'abc@example.com',
'password' 'password'
) )
CharacterOwnership.objects.create( CharacterOwnership.objects.create(
character=cls.character_3, character=character_3,
owner_hash='x1' + cls.character_3.character_name, owner_hash='x1' + character_3.character_name,
user=cls.user_3 user=cls.user_3
) )
cls.user_3.is_superuser = True cls.user_3.is_superuser = True
cls.user_3.save() cls.user_3.save()
def make_generic_search_request(ModelClass: type, search_term: str):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
return c.get(
'%s?q=%s' % (get_admin_search_url(ModelClass), quote(search_term))
)
class TestCharacterOwnershipAdmin(TestCaseWithTestData):
def setUp(self):
self.modeladmin = CharacterOwnershipAdmin(
model=User, admin_site=AdminSite()
)
def test_change_view_loads_normally(self):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
ownership = self.user_1.character_ownerships.first()
response = c.get(get_admin_change_view_url(ownership))
self.assertEqual(response.status_code, 200)
def test_search_works(self):
obj = CharacterOwnership.objects\
.filter(user=self.user_1)\
.first()
response = make_generic_search_request(type(obj), obj.user.username)
expected = 200
self.assertEqual(response.status_code, expected)
class TestOwnershipRecordAdmin(TestCaseWithTestData):
def setUp(self):
self.modeladmin = OwnershipRecordAdmin(
model=User, admin_site=AdminSite()
)
def test_change_view_loads_normally(self):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
ownership_record = OwnershipRecord.objects\
.filter(user=self.user_1)\
.first()
response = c.get(get_admin_change_view_url(ownership_record))
self.assertEqual(response.status_code, 200)
def test_search_works(self):
obj = OwnershipRecord.objects.first()
response = make_generic_search_request(type(obj), obj.user.username)
expected = 200
self.assertEqual(response.status_code, expected)
class TestStateAdmin(TestCaseWithTestData):
def setUp(self):
self.modeladmin = StateAdmin(
model=User, admin_site=AdminSite()
)
def test_change_view_loads_normally(self):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
guest_state = AuthUtils.get_guest_state()
response = c.get(get_admin_change_view_url(guest_state))
self.assertEqual(response.status_code, 200)
member_state = AuthUtils.get_member_state()
response = c.get(get_admin_change_view_url(member_state))
self.assertEqual(response.status_code, 200)
def test_search_works(self):
obj = State.objects.first()
response = make_generic_search_request(type(obj), obj.name)
expected = 200
self.assertEqual(response.status_code, expected)
class TestUserAdmin(TestCaseWithTestData):
def setUp(self): def setUp(self):
self.factory = RequestFactory() self.factory = RequestFactory()
self.modeladmin = UserAdmin( self.modeladmin = UserAdmin(
model=User, admin_site=AdminSite() model=User, admin_site=AdminSite()
) )
self.character_1 = self.user_1.character_ownerships.first().character
def _create_autogroups(self): def _create_autogroups(self):
"""create autogroups for corps and alliances""" """create autogroups for corps and alliances"""
autogroups_config = AutogroupsConfig( if _has_auto_groups:
corp_groups = True, autogroups_config = AutogroupsConfig(
alliance_groups = True corp_groups = True,
) alliance_groups = True
autogroups_config.save() )
for state in State.objects.all(): autogroups_config.save()
autogroups_config.states.add(state) for state in State.objects.all():
autogroups_config.update_corp_group_membership(self.user_1) autogroups_config.states.add(state)
autogroups_config.update_corp_group_membership(self.user_1)
# column rendering # column rendering
@@ -315,8 +432,8 @@ class TestUserAdmin(TestCase):
self, mock_task, mock_message_user self, mock_task, mock_message_user
): ):
users_qs = User.objects.filter(pk__in=[self.user_1.pk, self.user_2.pk]) users_qs = User.objects.filter(pk__in=[self.user_1.pk, self.user_2.pk])
self.modeladmin.update_main_character_model( update_main_character_model(
MockRequest(self.user_1), users_qs self.modeladmin, MockRequest(self.user_1), users_qs
) )
self.assertEqual(mock_task.delay.call_count, 2) self.assertEqual(mock_task.delay.call_count, 2)
self.assertTrue(mock_message_user.called) self.assertTrue(mock_message_user.called)
@@ -393,8 +510,8 @@ class TestUserAdmin(TestCase):
filters = changelist.get_filters(request) filters = changelist.get_filters(request)
filterspec = filters[0][0] filterspec = filters[0][0]
expected = [ expected = [
('2002', 'Daily Planet'), (2002, 'Daily Planet'),
('2001', 'Wayne Technologies'), (2001, 'Wayne Technologies'),
] ]
self.assertEqual(filterspec.lookup_choices, expected) self.assertEqual(filterspec.lookup_choices, expected)
@@ -423,7 +540,7 @@ class TestUserAdmin(TestCase):
filters = changelist.get_filters(request) filters = changelist.get_filters(request)
filterspec = filters[0][0] filterspec = filters[0][0]
expected = [ expected = [
('3001', 'Wayne Enterprises'), (3001, 'Wayne Enterprises'),
] ]
self.assertEqual(filterspec.lookup_choices, expected) self.assertEqual(filterspec.lookup_choices, expected)
@@ -436,4 +553,83 @@ class TestUserAdmin(TestCase):
changelist = my_modeladmin.get_changelist_instance(request) changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request) queryset = changelist.get_queryset(request)
expected = [self.user_1] expected = [self.user_1]
self.assertSetEqual(set(queryset), set(expected)) self.assertSetEqual(set(queryset), set(expected))
def test_change_view_loads_normally(self):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
response = c.get(get_admin_change_view_url(self.user_1))
self.assertEqual(response.status_code, 200)
def test_search_works(self):
obj = User.objects.first()
response = make_generic_search_request(type(obj), obj.username)
expected = 200
self.assertEqual(response.status_code, expected)
class TestMakeServicesHooksActions(TestCaseWithTestData):
class MyServicesHookTypeA(ServicesHook):
def __init__(self):
super().__init__()
self.name = 'My Service A'
def update_groups(self, user):
pass
def sync_nicknames(self, user):
pass
class MyServicesHookTypeB(ServicesHook):
def __init__(self):
super().__init__()
self.name = 'My Service B'
def update_groups(self, user):
pass
def update_groups_bulk(self, user):
pass
def sync_nicknames(self, user):
pass
def sync_nicknames_bulk(self, user):
pass
def test_service_has_update_groups_only(self):
service = self.MyServicesHookTypeA()
mock_service = MagicMock(spec=service)
action = make_service_hooks_update_groups_action(mock_service)
action(MagicMock(), MagicMock(), [self.user_1])
self.assertTrue(mock_service.update_groups.called)
def test_service_has_update_groups_bulk(self):
service = self.MyServicesHookTypeB()
mock_service = MagicMock(spec=service)
action = make_service_hooks_update_groups_action(mock_service)
action(MagicMock(), MagicMock(), [self.user_1])
self.assertFalse(mock_service.update_groups.called)
self.assertTrue(mock_service.update_groups_bulk.called)
def test_service_has_sync_nickname_only(self):
service = self.MyServicesHookTypeA()
mock_service = MagicMock(spec=service)
action = make_service_hooks_sync_nickname_action(mock_service)
action(MagicMock(), MagicMock(), [self.user_1])
self.assertTrue(mock_service.sync_nickname.called)
def test_service_has_sync_nicknames_bulk(self):
service = self.MyServicesHookTypeB()
mock_service = MagicMock(spec=service)
action = make_service_hooks_sync_nickname_action(mock_service)
action(MagicMock(), MagicMock(), [self.user_1])
self.assertFalse(mock_service.sync_nickname.called)
self.assertTrue(mock_service.sync_nicknames_bulk.called)

View File

@@ -1,11 +1,11 @@
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from django.test import TestCase from django.test import TestCase
from .. import app_settings from .. import app_settings
MODULE_PATH = 'allianceauth.authentication' MODULE_PATH = 'allianceauth.authentication'
class TestSetAppSetting(TestCase): class TestSetAppSetting(TestCase):
@patch(MODULE_PATH + '.app_settings.settings') @patch(MODULE_PATH + '.app_settings.settings')
@@ -17,7 +17,6 @@ class TestSetAppSetting(TestCase):
) )
self.assertEqual(result, False) self.assertEqual(result, False)
@patch(MODULE_PATH + '.app_settings.settings') @patch(MODULE_PATH + '.app_settings.settings')
def test_default_if_not_set_for_none(self, mock_settings): def test_default_if_not_set_for_none(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = Mock(spec=None) mock_settings.TEST_SETTING_DUMMY = Mock(spec=None)
@@ -28,7 +27,6 @@ class TestSetAppSetting(TestCase):
) )
self.assertEqual(result, None) self.assertEqual(result, None)
@patch(MODULE_PATH + '.app_settings.settings') @patch(MODULE_PATH + '.app_settings.settings')
def test_true_stays_true(self, mock_settings): def test_true_stays_true(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = True mock_settings.TEST_SETTING_DUMMY = True
@@ -56,7 +54,6 @@ class TestSetAppSetting(TestCase):
) )
self.assertEqual(result, False) self.assertEqual(result, False)
@patch(MODULE_PATH + '.app_settings.settings') @patch(MODULE_PATH + '.app_settings.settings')
def test_default_for_invalid_type_int(self, mock_settings): def test_default_for_invalid_type_int(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = 'invalid type' mock_settings.TEST_SETTING_DUMMY = 'invalid type'
@@ -95,7 +92,6 @@ class TestSetAppSetting(TestCase):
) )
self.assertEqual(result, 50) self.assertEqual(result, 50)
@patch(MODULE_PATH + '.app_settings.settings') @patch(MODULE_PATH + '.app_settings.settings')
def test_default_is_none_needs_required_type(self, mock_settings): def test_default_is_none_needs_required_type(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = 'invalid type' mock_settings.TEST_SETTING_DUMMY = 'invalid type'
@@ -104,5 +100,3 @@ class TestSetAppSetting(TestCase):
'TEST_SETTING_DUMMY', 'TEST_SETTING_DUMMY',
default_value=None default_value=None
) )

View File

@@ -0,0 +1,149 @@
from django.contrib.auth.models import User, Group
from django.test import TestCase
from allianceauth.eveonline.models import EveCharacter
from allianceauth.tests.auth_utils import AuthUtils
from esi.models import Token
from ..backends import StateBackend
from ..models import CharacterOwnership, UserProfile, OwnershipRecord
MODULE_PATH = 'allianceauth.authentication'
PERMISSION_1 = "authentication.add_user"
PERMISSION_2 = "authentication.change_user"
class TestStatePermissions(TestCase):
def setUp(self):
# permissions
self.permission_1 = AuthUtils.get_permission_by_name(PERMISSION_1)
self.permission_2 = AuthUtils.get_permission_by_name(PERMISSION_2)
# group
self.group_1 = Group.objects.create(name="Group 1")
self.group_2 = Group.objects.create(name="Group 2")
# state
self.state_1 = AuthUtils.get_member_state()
self.state_2 = AuthUtils.create_state("Other State", 75)
# user
self.user = AuthUtils.create_user("Bruce Wayne")
self.main = AuthUtils.add_main_character_2(self.user, self.user.username, 123)
def test_user_has_user_permissions(self):
self.user.user_permissions.add(self.permission_1)
user = User.objects.get(pk=self.user.pk)
self.assertTrue(user.has_perm(PERMISSION_1))
def test_user_has_group_permissions(self):
self.group_1.permissions.add(self.permission_1)
self.user.groups.add(self.group_1)
user = User.objects.get(pk=self.user.pk)
self.assertTrue(user.has_perm(PERMISSION_1))
def test_user_has_state_permissions(self):
self.state_1.permissions.add(self.permission_1)
self.state_1.member_characters.add(self.main)
user = User.objects.get(pk=self.user.pk)
self.assertTrue(user.has_perm(PERMISSION_1))
def test_when_user_changes_state_perms_change_accordingly(self):
self.state_1.permissions.add(self.permission_1)
self.state_1.member_characters.add(self.main)
user = User.objects.get(pk=self.user.pk)
self.assertTrue(user.has_perm(PERMISSION_1))
self.state_2.permissions.add(self.permission_2)
self.state_2.member_characters.add(self.main)
self.state_1.member_characters.remove(self.main)
user = User.objects.get(pk=self.user.pk)
self.assertFalse(user.has_perm(PERMISSION_1))
self.assertTrue(user.has_perm(PERMISSION_2))
def test_state_permissions_are_returned_for_current_user_object(self):
# verify state permissions are returns for the current user object
# and not for it's instance in the database, which might be outdated
self.state_1.permissions.add(self.permission_1)
self.state_2.permissions.add(self.permission_2)
self.state_1.member_characters.add(self.main)
user = User.objects.get(pk=self.user.pk)
user.profile.state = self.state_2
self.assertFalse(user.has_perm(PERMISSION_1))
self.assertTrue(user.has_perm(PERMISSION_2))
class TestAuthenticate(TestCase):
@classmethod
def setUpTestData(cls):
cls.main_character = EveCharacter.objects.create(
character_id=1,
character_name='Main Character',
corporation_id=1,
corporation_name='Corp',
corporation_ticker='CORP',
)
cls.alt_character = EveCharacter.objects.create(
character_id=2,
character_name='Alt Character',
corporation_id=1,
corporation_name='Corp',
corporation_ticker='CORP',
)
cls.unclaimed_character = EveCharacter.objects.create(
character_id=3,
character_name='Unclaimed Character',
corporation_id=1,
corporation_name='Corp',
corporation_ticker='CORP',
)
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
cls.old_user = AuthUtils.create_user('old_user', disconnect_signals=True)
AuthUtils.disconnect_signals()
CharacterOwnership.objects.create(user=cls.user, character=cls.main_character, owner_hash='1')
CharacterOwnership.objects.create(user=cls.user, character=cls.alt_character, owner_hash='2')
UserProfile.objects.update_or_create(user=cls.user, defaults={'main_character': cls.main_character})
AuthUtils.connect_signals()
def test_authenticate_main_character(self):
t = Token(character_id=self.main_character.character_id, character_owner_hash='1')
user = StateBackend().authenticate(token=t)
self.assertEquals(user, self.user)
def test_authenticate_alt_character(self):
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
user = StateBackend().authenticate(token=t)
self.assertEquals(user, self.user)
def test_authenticate_unclaimed_character(self):
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='3')
user = StateBackend().authenticate(token=t)
self.assertNotEqual(user, self.user)
self.assertEqual(user.username, 'Unclaimed_Character')
self.assertEqual(user.profile.main_character, self.unclaimed_character)
def test_authenticate_character_record(self):
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
user = StateBackend().authenticate(token=t)
self.assertEqual(user, self.old_user)
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
self.assertTrue(user.profile.main_character)
def test_iterate_username(self):
t = Token(character_id=self.unclaimed_character.character_id,
character_name=self.unclaimed_character.character_name, character_owner_hash='3')
username = StateBackend().authenticate(token=t).username
t.character_owner_hash = '4'
username_1 = StateBackend().authenticate(token=t).username
t.character_owner_hash = '5'
username_2 = StateBackend().authenticate(token=t).username
self.assertNotEqual(username, username_1, username_2)
self.assertTrue(username_1.endswith('_1'))
self.assertTrue(username_2.endswith('_2'))

View File

@@ -0,0 +1,35 @@
from io import StringIO
from django.core.management import call_command
from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils
from ..models import CharacterOwnership, UserProfile
class ManagementCommandTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('test user', disconnect_signals=True)
AuthUtils.add_main_character(cls.user, 'test character', '1', '2', 'test corp', 'test')
character = UserProfile.objects.get(user=cls.user).main_character
CharacterOwnership.objects.create(user=cls.user, character=character, owner_hash='test')
def setUp(self):
self.stdout = StringIO()
def test_ownership(self):
call_command('checkmains', stdout=self.stdout)
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
self.assertNotIn(self.user.username, self.stdout.getvalue())
self.assertIn('All main characters', self.stdout.getvalue())
def test_no_ownership(self):
user = AuthUtils.create_user('v1 user', disconnect_signals=True)
AuthUtils.add_main_character(user, 'v1 character', '10', '20', 'test corp', 'test')
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
call_command('checkmains', stdout=self.stdout)
self.assertEqual(UserProfile.objects.filter(main_character__isnull=True).count(), 1)
self.assertIn(user.username, self.stdout.getvalue())

View File

@@ -0,0 +1,68 @@
from unittest import mock
from urllib import parse
from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.http.response import HttpResponse
from django.shortcuts import reverse
from django.test import TestCase
from django.test.client import RequestFactory
from allianceauth.eveonline.models import EveCharacter
from allianceauth.tests.auth_utils import AuthUtils
from ..decorators import main_character_required
from ..models import CharacterOwnership
MODULE_PATH = 'allianceauth.authentication'
class DecoratorTestCase(TestCase):
@staticmethod
@main_character_required
def dummy_view(*args, **kwargs):
return HttpResponse(status=200)
@classmethod
def setUpTestData(cls):
cls.main_user = AuthUtils.create_user('main_user', disconnect_signals=True)
cls.no_main_user = AuthUtils.create_user(
'no_main_user', disconnect_signals=True
)
main_character = EveCharacter.objects.create(
character_id=1,
character_name='Main Character',
corporation_id=1,
corporation_name='Corp',
corporation_ticker='CORP',
)
CharacterOwnership.objects.create(
user=cls.main_user, character=main_character, owner_hash='1'
)
cls.main_user.profile.main_character = main_character
def setUp(self):
self.request = RequestFactory().get('/test/')
@mock.patch(MODULE_PATH + '.decorators.messages')
def test_login_redirect(self, m):
setattr(self.request, 'user', AnonymousUser())
response = self.dummy_view(self.request)
self.assertEqual(response.status_code, 302)
url = getattr(response, 'url', None)
self.assertEqual(parse.urlparse(url).path, reverse(settings.LOGIN_URL))
@mock.patch(MODULE_PATH + '.decorators.messages')
def test_main_character_redirect(self, m):
setattr(self.request, 'user', self.no_main_user)
response = self.dummy_view(self.request)
self.assertEqual(response.status_code, 302)
url = getattr(response, 'url', None)
self.assertEqual(url, reverse('authentication:dashboard'))
@mock.patch(MODULE_PATH + '.decorators.messages')
def test_successful_request(self, m):
setattr(self.request, 'user', self.main_user)
response = self.dummy_view(self.request)
self.assertEqual(response.status_code, 200)

View File

@@ -1,147 +1,20 @@
from unittest import mock from unittest import mock
from io import StringIO
from urllib import parse
from django.conf import settings from django.contrib.auth.models import User
from django.contrib.auth.models import AnonymousUser, User
from django.core.management import call_command
from django.http.response import HttpResponse
from django.shortcuts import reverse
from django.test import TestCase from django.test import TestCase
from django.test.client import RequestFactory
from allianceauth.authentication.decorators import main_character_required
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\ from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\
EveAllianceInfo EveAllianceInfo
from allianceauth.tests.auth_utils import AuthUtils from allianceauth.tests.auth_utils import AuthUtils
from esi.errors import IncompleteResponseError from esi.errors import IncompleteResponseError
from esi.models import Token from esi.models import Token
from ..backends import StateBackend from ..models import CharacterOwnership, State, get_guest_state
from ..models import CharacterOwnership, UserProfile, State, get_guest_state,\
OwnershipRecord
from ..tasks import check_character_ownership from ..tasks import check_character_ownership
MODULE_PATH = 'allianceauth.authentication' MODULE_PATH = 'allianceauth.authentication'
class DecoratorTestCase(TestCase):
@staticmethod
@main_character_required
def dummy_view(*args, **kwargs):
return HttpResponse(status=200)
@classmethod
def setUpTestData(cls):
cls.main_user = AuthUtils.create_user('main_user', disconnect_signals=True)
cls.no_main_user = AuthUtils.create_user('no_main_user', disconnect_signals=True)
main_character = EveCharacter.objects.create(
character_id=1,
character_name='Main Character',
corporation_id=1,
corporation_name='Corp',
corporation_ticker='CORP',
)
CharacterOwnership.objects.create(user=cls.main_user, character=main_character, owner_hash='1')
cls.main_user.profile.main_character = main_character
def setUp(self):
self.request = RequestFactory().get('/test/')
@mock.patch(MODULE_PATH + '.decorators.messages')
def test_login_redirect(self, m):
setattr(self.request, 'user', AnonymousUser())
response = self.dummy_view(self.request)
self.assertEqual(response.status_code, 302)
url = getattr(response, 'url', None)
self.assertEqual(parse.urlparse(url).path, reverse(settings.LOGIN_URL))
@mock.patch(MODULE_PATH + '.decorators.messages')
def test_main_character_redirect(self, m):
setattr(self.request, 'user', self.no_main_user)
response = self.dummy_view(self.request)
self.assertEqual(response.status_code, 302)
url = getattr(response, 'url', None)
self.assertEqual(url, reverse('authentication:dashboard'))
@mock.patch(MODULE_PATH + '.decorators.messages')
def test_successful_request(self, m):
setattr(self.request, 'user', self.main_user)
response = self.dummy_view(self.request)
self.assertEqual(response.status_code, 200)
class BackendTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.main_character = EveCharacter.objects.create(
character_id=1,
character_name='Main Character',
corporation_id=1,
corporation_name='Corp',
corporation_ticker='CORP',
)
cls.alt_character = EveCharacter.objects.create(
character_id=2,
character_name='Alt Character',
corporation_id=1,
corporation_name='Corp',
corporation_ticker='CORP',
)
cls.unclaimed_character = EveCharacter.objects.create(
character_id=3,
character_name='Unclaimed Character',
corporation_id=1,
corporation_name='Corp',
corporation_ticker='CORP',
)
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
cls.old_user = AuthUtils.create_user('old_user', disconnect_signals=True)
AuthUtils.disconnect_signals()
CharacterOwnership.objects.create(user=cls.user, character=cls.main_character, owner_hash='1')
CharacterOwnership.objects.create(user=cls.user, character=cls.alt_character, owner_hash='2')
UserProfile.objects.update_or_create(user=cls.user, defaults={'main_character': cls.main_character})
AuthUtils.connect_signals()
def test_authenticate_main_character(self):
t = Token(character_id=self.main_character.character_id, character_owner_hash='1')
user = StateBackend().authenticate(token=t)
self.assertEquals(user, self.user)
def test_authenticate_alt_character(self):
t = Token(character_id=self.alt_character.character_id, character_owner_hash='2')
user = StateBackend().authenticate(token=t)
self.assertEquals(user, self.user)
def test_authenticate_unclaimed_character(self):
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='3')
user = StateBackend().authenticate(token=t)
self.assertNotEqual(user, self.user)
self.assertEqual(user.username, 'Unclaimed_Character')
self.assertEqual(user.profile.main_character, self.unclaimed_character)
def test_authenticate_character_record(self):
t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4')
record = OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4')
user = StateBackend().authenticate(token=t)
self.assertEqual(user, self.old_user)
self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists())
self.assertTrue(user.profile.main_character)
def test_iterate_username(self):
t = Token(character_id=self.unclaimed_character.character_id,
character_name=self.unclaimed_character.character_name, character_owner_hash='3')
username = StateBackend().authenticate(token=t).username
t.character_owner_hash = '4'
username_1 = StateBackend().authenticate(token=t).username
t.character_owner_hash = '5'
username_2 = StateBackend().authenticate(token=t).username
self.assertNotEqual(username, username_1, username_2)
self.assertTrue(username_1.endswith('_1'))
self.assertTrue(username_2.endswith('_2'))
class CharacterOwnershipTestCase(TestCase): class CharacterOwnershipTestCase(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
@@ -343,10 +216,10 @@ class CharacterOwnershipCheckTestCase(TestCase):
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True) cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1', AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1',
corp_name='Test Corp', alliance_name='Test Alliance') corp_name='Test Corp', alliance_name='Test Alliance')
cls.character = EveCharacter.objects.get(character_id='1') cls.character = EveCharacter.objects.get(character_id=1)
cls.token = Token.objects.create( cls.token = Token.objects.create(
user=cls.user, user=cls.user,
character_id='1', character_id=1,
character_name='Test', character_name='Test',
character_owner_hash='1', character_owner_hash='1',
) )
@@ -378,30 +251,3 @@ class CharacterOwnershipCheckTestCase(TestCase):
filter.return_value.exists.return_value = False filter.return_value.exists.return_value = False
check_character_ownership(self.ownership) check_character_ownership(self.ownership)
self.assertTrue(filter.return_value.delete.called) self.assertTrue(filter.return_value.delete.called)
class ManagementCommandTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('test user', disconnect_signals=True)
AuthUtils.add_main_character(cls.user, 'test character', '1', '2', 'test corp', 'test')
character = UserProfile.objects.get(user=cls.user).main_character
CharacterOwnership.objects.create(user=cls.user, character=character, owner_hash='test')
def setUp(self):
self.stdout = StringIO()
def test_ownership(self):
call_command('checkmains', stdout=self.stdout)
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
self.assertNotIn(self.user.username, self.stdout.getvalue())
self.assertIn('All main characters', self.stdout.getvalue())
def test_no_ownership(self):
user = AuthUtils.create_user('v1 user', disconnect_signals=True)
AuthUtils.add_main_character(user, 'v1 character', '10', '20', 'test corp', 'test')
self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count())
call_command('checkmains', stdout=self.stdout)
self.assertEqual(UserProfile.objects.filter(main_character__isnull=True).count(), 1)
self.assertIn(user.username, self.stdout.getvalue())

View File

@@ -0,0 +1,290 @@
from math import ceil
from unittest.mock import patch
from requests import RequestException
import requests_mock
from packaging.version import Version as Pep440Version
from django.test import TestCase
from allianceauth.templatetags.admin_status import (
status_overview,
_fetch_list_from_gitlab,
_current_notifications,
_current_version_summary,
_fetch_notification_issues_from_gitlab,
_fetch_tags_from_gitlab,
_latests_versions
)
MODULE_PATH = 'allianceauth.templatetags'
def create_tags_list(tag_names: list):
return [{'name': str(tag_name)} for tag_name in tag_names]
GITHUB_TAGS = create_tags_list(['v2.4.6a1', 'v2.4.5', 'v2.4.0', 'v2.0.0', 'v1.1.1'])
GITHUB_NOTIFICATION_ISSUES = [
{
'id': 1,
'title': 'first issue'
},
{
'id': 2,
'title': 'second issue'
},
{
'id': 3,
'title': 'third issue'
},
{
'id': 4,
'title': 'forth issue'
},
{
'id': 5,
'title': 'fifth issue'
},
{
'id': 6,
'title': 'sixth issue'
},
]
TEST_VERSION = '2.6.5'
class TestStatusOverviewTag(TestCase):
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
@patch(MODULE_PATH + '.admin_status._fetch_celery_queue_length')
@patch(MODULE_PATH + '.admin_status._current_version_summary')
@patch(MODULE_PATH + '.admin_status._current_notifications')
def test_status_overview(
self,
mock_current_notifications,
mock_current_version_info,
mock_fetch_celery_queue_length
):
notifications = {
'notifications': GITHUB_NOTIFICATION_ISSUES[:5]
}
mock_current_notifications.return_value = notifications
version_info = {
'latest_major': True,
'latest_minor': True,
'latest_patch': True,
'latest_beta': False,
'current_version': TEST_VERSION,
'latest_major_version': '2.4.5',
'latest_minor_version': '2.4.0',
'latest_patch_version': '2.4.5',
'latest_beta_version': '2.4.4a1',
}
mock_current_version_info.return_value = version_info
mock_fetch_celery_queue_length.return_value = 3
result = status_overview()
expected = {
'notifications': GITHUB_NOTIFICATION_ISSUES[:5],
'latest_major': True,
'latest_minor': True,
'latest_patch': True,
'latest_beta': False,
'current_version': TEST_VERSION,
'latest_major_version': '2.4.5',
'latest_minor_version': '2.4.0',
'latest_patch_version': '2.4.5',
'latest_beta_version': '2.4.4a1',
'task_queue_length': 3,
}
self.assertEqual(result, expected)
class TestNotifications(TestCase):
@requests_mock.mock()
def test_fetch_notification_issues_from_gitlab(self, requests_mocker):
url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues'
'?labels=announcement'
)
requests_mocker.get(url, json=GITHUB_NOTIFICATION_ISSUES)
result = _fetch_notification_issues_from_gitlab()
self.assertEqual(result, GITHUB_NOTIFICATION_ISSUES)
@patch(MODULE_PATH + '.admin_status.cache')
def test_current_notifications_normal(self, mock_cache):
mock_cache.get_or_set.return_value = GITHUB_NOTIFICATION_ISSUES
result = _current_notifications()
self.assertEqual(result['notifications'], GITHUB_NOTIFICATION_ISSUES[:5])
@patch(MODULE_PATH + '.admin_status.cache')
def test_current_notifications_failed(self, mock_cache):
mock_cache.get_or_set.side_effect = RequestException
result = _current_notifications()
self.assertEqual(result['notifications'], list())
@patch(MODULE_PATH + '.admin_status.cache')
def test_current_notifications_is_none(self, mock_cache):
mock_cache.get_or_set.return_value = None
result = _current_notifications()
self.assertEqual(result['notifications'], list())
class TestCeleryQueueLength(TestCase):
def test_get_celery_queue_length(self):
pass
class TestVersionTags(TestCase):
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
@patch(MODULE_PATH + '.admin_status.cache')
def test_current_version_info_normal(self, mock_cache):
mock_cache.get_or_set.return_value = GITHUB_TAGS
result = _current_version_summary()
self.assertTrue(result['latest_major'])
self.assertTrue(result['latest_minor'])
self.assertTrue(result['latest_patch'])
self.assertEqual(result['latest_major_version'], '2.0.0')
self.assertEqual(result['latest_minor_version'], '2.4.0')
self.assertEqual(result['latest_patch_version'], '2.4.5')
self.assertEqual(result['latest_beta_version'], '2.4.6a1')
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
@patch(MODULE_PATH + '.admin_status.cache')
def test_current_version_info_failed(self, mock_cache):
mock_cache.get_or_set.side_effect = RequestException
expected = {}
result = _current_version_summary()
self.assertEqual(result, expected)
@requests_mock.mock()
def test_fetch_tags_from_gitlab(self, requests_mocker):
url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth'
'/repository/tags'
)
requests_mocker.get(url, json=GITHUB_TAGS)
result = _fetch_tags_from_gitlab()
self.assertEqual(result, GITHUB_TAGS)
@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):
mock_cache.get_or_set.return_value = None
expected = {}
result = _current_version_summary()
self.assertEqual(result, expected)
class TestLatestsVersion(TestCase):
def test_all_version_types_defined(self):
tags = create_tags_list(
['2.1.1', '2.1.0', '2.0.0', '2.1.1a1', '1.1.1', '1.1.0', '1.0.0']
)
major, minor, patch, beta = _latests_versions(tags)
self.assertEqual(major, Pep440Version('2.0.0'))
self.assertEqual(minor, Pep440Version('2.1.0'))
self.assertEqual(patch, Pep440Version('2.1.1'))
self.assertEqual(beta, Pep440Version('2.1.1a1'))
def test_major_and_minor_not_defined_with_zero(self):
tags = create_tags_list(
['2.1.2', '2.1.1', '2.0.1', '2.1.1a1', '1.1.1', '1.1.0', '1.0.0']
)
major, minor, patch, beta = _latests_versions(tags)
self.assertEqual(major, Pep440Version('2.0.1'))
self.assertEqual(minor, Pep440Version('2.1.1'))
self.assertEqual(patch, Pep440Version('2.1.2'))
self.assertEqual(beta, Pep440Version('2.1.1a1'))
def test_can_ignore_invalid_versions(self):
tags = create_tags_list(
['2.1.1', '2.1.0', '2.0.0', '2.1.1a1', 'invalid']
)
major, minor, patch, beta = _latests_versions(tags)
self.assertEqual(major, Pep440Version('2.0.0'))
self.assertEqual(minor, Pep440Version('2.1.0'))
self.assertEqual(patch, Pep440Version('2.1.1'))
self.assertEqual(beta, Pep440Version('2.1.1a1'))
class TestFetchListFromGitlab(TestCase):
page_size = 2
def setUp(self):
self.url = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth'
'/repository/tags'
)
@classmethod
def my_callback(cls, request, context):
page = int(request.qs['page'][0])
start = (page - 1) * cls.page_size
end = start + cls.page_size
return GITHUB_TAGS[start:end]
@requests_mock.mock()
def test_can_fetch_one_page_with_header(self, requests_mocker):
headers = {
'x-total-pages': '1'
}
requests_mocker.get(self.url, json=GITHUB_TAGS, headers=headers)
result = _fetch_list_from_gitlab(self.url)
self.assertEqual(result, GITHUB_TAGS)
self.assertEqual(requests_mocker.call_count, 1)
@requests_mock.mock()
def test_can_fetch_one_page_wo_header(self, requests_mocker):
requests_mocker.get(self.url, json=GITHUB_TAGS)
result = _fetch_list_from_gitlab(self.url)
self.assertEqual(result, GITHUB_TAGS)
self.assertEqual(requests_mocker.call_count, 1)
@requests_mock.mock()
def test_can_fetch_one_page_and_ignore_invalid_header(self, requests_mocker):
headers = {
'x-total-pages': 'invalid'
}
requests_mocker.get(self.url, json=GITHUB_TAGS, headers=headers)
result = _fetch_list_from_gitlab(self.url)
self.assertEqual(result, GITHUB_TAGS)
self.assertEqual(requests_mocker.call_count, 1)
@requests_mock.mock()
def test_can_fetch_multiple_pages(self, requests_mocker):
total_pages = ceil(len(GITHUB_TAGS) / self.page_size)
headers = {
'x-total-pages': str(total_pages)
}
requests_mocker.get(self.url, json=self.my_callback, headers=headers)
result = _fetch_list_from_gitlab(self.url)
self.assertEqual(result, GITHUB_TAGS)
self.assertEqual(requests_mocker.call_count, total_pages)
@requests_mock.mock()
def test_can_fetch_given_number_of_pages_only(self, requests_mocker):
total_pages = ceil(len(GITHUB_TAGS) / self.page_size)
headers = {
'x-total-pages': str(total_pages)
}
requests_mocker.get(self.url, json=self.my_callback, headers=headers)
max_pages = 2
result = _fetch_list_from_gitlab(self.url, max_pages=max_pages)
self.assertEqual(result, GITHUB_TAGS[:4])
self.assertEqual(requests_mocker.call_count, max_pages)

View File

@@ -7,11 +7,21 @@ from . import views
app_name = 'authentication' app_name = 'authentication'
urlpatterns = [ urlpatterns = [
url(r'^$', login_required(TemplateView.as_view(template_name='authentication/dashboard.html')),), url(r'^$', views.index, name='index'),
url(r'^account/login/$', TemplateView.as_view(template_name='public/login.html'), name='login'), url(
url(r'^account/characters/main/$', views.main_character_change, name='change_main_character'), r'^account/login/$',
url(r'^account/characters/add/$', views.add_character, name='add_character'), TemplateView.as_view(template_name='public/login.html'),
url(r'^help/$', login_required(TemplateView.as_view(template_name='allianceauth/help.html')), name='help'), name='login'
url(r'^dashboard/$', ),
login_required(TemplateView.as_view(template_name='authentication/dashboard.html')), name='dashboard'), url(
r'^account/characters/main/$',
views.main_character_change,
name='change_main_character'
),
url(
r'^account/characters/add/$',
views.add_character,
name='add_character'
),
url(r'^dashboard/$', views.dashboard, name='dashboard'),
] ]

View File

@@ -7,20 +7,58 @@ 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.urls import reverse
from django.shortcuts import redirect from django.shortcuts import redirect, render
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import gettext_lazy as _
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 RegistrationView as BaseRegistrationView, \
ActivationView as BaseActivationView, REGISTRATION_SALT from registration.backends.hmac.views import (
RegistrationView as BaseRegistrationView,
ActivationView as BaseActivationView,
REGISTRATION_SALT
)
from registration.signals import user_registered from registration.signals import user_registered
from .models import CharacterOwnership from .models import CharacterOwnership
from .forms import RegistrationForm from .forms import RegistrationForm
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True
from allianceauth.eveonline.autogroups.models import *
else:
_has_auto_groups = False
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@login_required
def index(request):
return redirect('authentication:dashboard')
@login_required
def dashboard(request):
groups = request.user.groups.all()
if _has_auto_groups:
groups = groups\
.filter(managedalliancegroup__isnull=True)\
.filter(managedcorpgroup__isnull=True)
groups = groups.order_by('name')
characters = EveCharacter.objects\
.filter(character_ownership__user=request.user)\
.select_related()\
.order_by('character_name')
context = {
'groups': groups,
'characters': characters
}
return render(request, 'authentication/dashboard.html', context)
@login_required @login_required
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES) @token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
def main_character_change(request, token): def main_character_change(request, token):
@@ -31,7 +69,10 @@ def main_character_change(request, token):
if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists(): if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists():
co = CharacterOwnership.objects.create_by_token(token) co = CharacterOwnership.objects.create_by_token(token)
else: else:
messages.error(request, 'Cannot change main character to %(char)s: character owned by a different account.' % ({'char': token.character_name})) messages.error(
request,
_('Cannot change main character to %(char)s: character owned by a different account.') % ({'char': token.character_name})
)
co = None co = None
if co: if co:
request.user.profile.main_character = co.character request.user.profile.main_character = co.character

View File

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

File diff suppressed because one or more lines are too long

View File

@@ -17,9 +17,9 @@ class CorpStatsManagerTestCase(TestCase):
cls.user = AuthUtils.create_user('test') cls.user = AuthUtils.create_user('test')
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST') AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST')
cls.user.profile.refresh_from_db() cls.user.profile.refresh_from_db()
cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2') cls.alliance = EveAllianceInfo.objects.create(alliance_id=3, alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id=2)
cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1) cls.corp = EveCorporationInfo.objects.create(corporation_id=2, corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1)
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id='1', character_name='test character', character_owner_hash='z') cls.token = Token.objects.create(user=cls.user, access_token='a', character_id=1, character_name='test character', character_owner_hash='z')
cls.corpstats = CorpStats.objects.create(corp=cls.corp, token=cls.token) cls.corpstats = CorpStats.objects.create(corp=cls.corp, token=cls.token)
cls.view_corp_permission = Permission.objects.get_by_natural_key('view_corp_corpstats', 'corputils', 'corpstats') cls.view_corp_permission = Permission.objects.get_by_natural_key('view_corp_corpstats', 'corputils', 'corpstats')
cls.view_alliance_permission = Permission.objects.get_by_natural_key('view_alliance_corpstats', 'corputils', 'corpstats') cls.view_alliance_permission = Permission.objects.get_by_natural_key('view_alliance_corpstats', 'corputils', 'corpstats')
@@ -66,9 +66,9 @@ class CorpStatsUpdateTestCase(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.user = AuthUtils.create_user('test') cls.user = AuthUtils.create_user('test')
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST') AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id=2, corp_name='test_corp', corp_ticker='TEST', alliance_id=3, alliance_name='TEST')
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id='1', character_name='test character', character_owner_hash='z') cls.token = Token.objects.create(user=cls.user, access_token='a', character_id=1, character_name='test character', character_owner_hash='z')
cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', member_count=1) cls.corp = EveCorporationInfo.objects.create(corporation_id=2, corporation_name='test corp', corporation_ticker='TEST', member_count=1)
def setUp(self): def setUp(self):
self.corpstats = CorpStats.objects.get_or_create(token=self.token, corp=self.corp)[0] self.corpstats = CorpStats.objects.get_or_create(token=self.token, corp=self.corp)[0]
@@ -88,11 +88,11 @@ class CorpStatsUpdateTestCase(TestCase):
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1] SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1]
SwaggerClient.from_spec.return_value.Universe.post_universe_names.return_value.result.return_value = [{'id': 1, 'name': 'test character'}] SwaggerClient.from_spec.return_value.Universe.post_universe_names.return_value.result.return_value = [{'id': 1, 'name': 'test character'}]
self.corpstats.update() self.corpstats.update()
self.assertTrue(CorpMember.objects.filter(character_id='1', character_name='test character', corpstats=self.corpstats).exists()) self.assertTrue(CorpMember.objects.filter(character_id=1, character_name='test character', corpstats=self.corpstats).exists())
@mock.patch('esi.clients.SwaggerClient') @mock.patch('esi.clients.SwaggerClient')
def test_update_remove_member(self, SwaggerClient): def test_update_remove_member(self, SwaggerClient):
CorpMember.objects.create(character_id='2', character_name='old test character', corpstats=self.corpstats) CorpMember.objects.create(character_id=2, character_name='old test character', corpstats=self.corpstats)
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2} SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1] SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1]
SwaggerClient.from_spec.return_value.Universe.post_universe_names.return_value.result.return_value = [{'id': 1, 'name': 'test character'}] SwaggerClient.from_spec.return_value.Universe.post_universe_names.return_value.result.return_value = [{'id': 1, 'name': 'test character'}]
@@ -130,15 +130,15 @@ class CorpStatsPropertiesTestCase(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.user = AuthUtils.create_user('test') cls.user = AuthUtils.create_user('test')
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST') AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id=2, corp_name='test_corp', corp_ticker='TEST', alliance_id=3, alliance_name='TEST')
cls.user.profile.refresh_from_db() cls.user.profile.refresh_from_db()
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id='1', character_name='test character', character_owner_hash='z') cls.token = Token.objects.create(user=cls.user, access_token='a', character_id=1, character_name='test character', character_owner_hash='z')
cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', member_count=1) cls.corp = EveCorporationInfo.objects.create(corporation_id=2, corporation_name='test corp', corporation_ticker='TEST', member_count=1)
cls.corpstats = CorpStats.objects.create(token=cls.token, corp=cls.corp) cls.corpstats = CorpStats.objects.create(token=cls.token, corp=cls.corp)
cls.character = EveCharacter.objects.create(character_name='another test character', character_id='4', corporation_id='2', corporation_name='test corp', corporation_ticker='TEST') cls.character = EveCharacter.objects.create(character_name='another test character', character_id=4, corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
def test_member_count(self): def test_member_count(self):
member = CorpMember.objects.create(corpstats=self.corpstats, character_id='1', character_name='test character') member = CorpMember.objects.create(corpstats=self.corpstats, character_id=2, character_name='test character')
self.assertEqual(self.corpstats.member_count, 1) self.assertEqual(self.corpstats.member_count, 1)
member.delete() member.delete()
self.assertEqual(self.corpstats.member_count, 0) self.assertEqual(self.corpstats.member_count, 0)
@@ -147,7 +147,7 @@ class CorpStatsPropertiesTestCase(TestCase):
AuthUtils.disconnect_signals() AuthUtils.disconnect_signals()
co = CharacterOwnership.objects.create(character=self.character, user=self.user, owner_hash='a') co = CharacterOwnership.objects.create(character=self.character, user=self.user, owner_hash='a')
AuthUtils.connect_signals() AuthUtils.connect_signals()
CorpMember.objects.create(corpstats=self.corpstats, character_id='4', character_name='test character') CorpMember.objects.create(corpstats=self.corpstats, character_id=4, character_name='test character')
self.assertEqual(self.corpstats.user_count, 1) self.assertEqual(self.corpstats.user_count, 1)
co.delete() co.delete()
self.assertEqual(self.corpstats.user_count, 0) self.assertEqual(self.corpstats.user_count, 0)
@@ -156,7 +156,8 @@ class CorpStatsPropertiesTestCase(TestCase):
AuthUtils.disconnect_signals() AuthUtils.disconnect_signals()
co = CharacterOwnership.objects.create(character=self.character, user=self.user, owner_hash='a') co = CharacterOwnership.objects.create(character=self.character, user=self.user, owner_hash='a')
AuthUtils.connect_signals() AuthUtils.connect_signals()
member = CorpMember.objects.create(corpstats=self.corpstats, character_id='4', character_name='test character') member = CorpMember.objects.create(corpstats=self.corpstats, character_id=4, character_name='test character')
self.corpstats.refresh_from_db()
self.assertIn(member, self.corpstats.registered_members) self.assertIn(member, self.corpstats.registered_members)
self.assertEqual(self.corpstats.registered_member_count, 1) self.assertEqual(self.corpstats.registered_member_count, 1)
@@ -165,7 +166,7 @@ class CorpStatsPropertiesTestCase(TestCase):
self.assertEqual(self.corpstats.registered_member_count, 0) self.assertEqual(self.corpstats.registered_member_count, 0)
def test_unregistered_members(self): def test_unregistered_members(self):
member = CorpMember.objects.create(corpstats=self.corpstats, character_id='4', character_name='test character') member = CorpMember.objects.create(corpstats=self.corpstats, character_id=4, character_name='test character')
self.corpstats.refresh_from_db() self.corpstats.refresh_from_db()
self.assertIn(member, self.corpstats.unregistered_members) self.assertIn(member, self.corpstats.unregistered_members)
self.assertEqual(self.corpstats.unregistered_member_count, 1) self.assertEqual(self.corpstats.unregistered_member_count, 1)
@@ -178,13 +179,13 @@ class CorpStatsPropertiesTestCase(TestCase):
def test_mains(self): def test_mains(self):
# test when is a main # test when is a main
member = CorpMember.objects.create(corpstats=self.corpstats, character_id='1', character_name='test character') member = CorpMember.objects.create(corpstats=self.corpstats, character_id=1, character_name='test character')
self.assertIn(member, self.corpstats.mains) self.assertIn(member, self.corpstats.mains)
self.assertEqual(self.corpstats.main_count, 1) self.assertEqual(self.corpstats.main_count, 1)
# test when is an alt # test when is an alt
old_main = self.user.profile.main_character old_main = self.user.profile.main_character
character = EveCharacter.objects.create(character_name='other character', character_id=10, corporation_name='test corp', corporation_id='2', corporation_ticker='TEST') character = EveCharacter.objects.create(character_name='other character', character_id=10, corporation_name='test corp', corporation_id=2, corporation_ticker='TEST')
AuthUtils.disconnect_signals() AuthUtils.disconnect_signals()
co = CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b') co = CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
self.user.profile.main_character = character self.user.profile.main_character = character
@@ -208,7 +209,7 @@ class CorpStatsPropertiesTestCase(TestCase):
self.assertEqual(self.corpstats.corp_logo(size=128), 'https://images.evetech.net/corporations/2/logo?size=128') self.assertEqual(self.corpstats.corp_logo(size=128), 'https://images.evetech.net/corporations/2/logo?size=128')
self.assertEqual(self.corpstats.alliance_logo(size=128), 'https://images.evetech.net/alliances/1/logo?size=128') self.assertEqual(self.corpstats.alliance_logo(size=128), 'https://images.evetech.net/alliances/1/logo?size=128')
alliance = EveAllianceInfo.objects.create(alliance_name='test alliance', alliance_id='3', alliance_ticker='TEST', executor_corp_id='2') alliance = EveAllianceInfo.objects.create(alliance_name='test alliance', alliance_id=3, alliance_ticker='TEST', executor_corp_id=2)
self.corp.alliance = alliance self.corp.alliance = alliance
self.corp.save() self.corp.save()
self.assertEqual(self.corpstats.alliance_logo(size=128), 'https://images.evetech.net/alliances/3/logo?size=128') self.assertEqual(self.corpstats.alliance_logo(size=128), 'https://images.evetech.net/alliances/3/logo?size=128')
@@ -221,14 +222,14 @@ class CorpMemberTestCase(TestCase):
cls.user = AuthUtils.create_user('test') cls.user = AuthUtils.create_user('test')
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST') AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST')
cls.user.profile.refresh_from_db() cls.user.profile.refresh_from_db()
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id='1', character_name='test character', character_owner_hash='a') cls.token = Token.objects.create(user=cls.user, access_token='a', character_id=1, character_name='test character', character_owner_hash='a')
cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', member_count=1) cls.corp = EveCorporationInfo.objects.create(corporation_id=2, corporation_name='test corp', corporation_ticker='TEST', member_count=1)
cls.corpstats = CorpStats.objects.create(token=cls.token, corp=cls.corp) cls.corpstats = CorpStats.objects.create(token=cls.token, corp=cls.corp)
cls.member = CorpMember.objects.create(corpstats=cls.corpstats, character_id='2', character_name='other test character') cls.member = CorpMember.objects.create(corpstats=cls.corpstats, character_id=2, character_name='other test character')
def test_character(self): def test_character(self):
self.assertIsNone(self.member.character) self.assertIsNone(self.member.character)
character = EveCharacter.objects.create(character_id='2', character_name='other test character', corporation_id='2', corporation_name='test corp', corporation_ticker='TEST') character = EveCharacter.objects.create(character_id=2, character_name='other test character', corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
self.assertEqual(self.member.character, character) self.assertEqual(self.member.character, character)
def test_main_character(self): def test_main_character(self):
@@ -238,7 +239,7 @@ class CorpMemberTestCase(TestCase):
self.assertIsNone(self.member.main_character) self.assertIsNone(self.member.main_character)
# test when member.character is not None but also not a main # test when member.character is not None but also not a main
character = EveCharacter.objects.create(character_id='2', character_name='other test character', corporation_id='2', corporation_name='test corp', corporation_ticker='TEST') character = EveCharacter.objects.create(character_id=2, character_name='other test character', corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b') CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
self.member.refresh_from_db() self.member.refresh_from_db()
self.assertNotEqual(self.member.main_character, self.member.character) self.assertNotEqual(self.member.main_character, self.member.character)
@@ -260,14 +261,14 @@ class CorpMemberTestCase(TestCase):
def test_alts(self): def test_alts(self):
self.assertListEqual(self.member.alts, []) self.assertListEqual(self.member.alts, [])
character = EveCharacter.objects.create(character_id='2', character_name='other test character', corporation_id='2', corporation_name='test corp', corporation_ticker='TEST') character = EveCharacter.objects.create(character_id=2, character_name='other test character', corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b') CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
self.assertIn(character, self.member.alts) self.assertIn(character, self.member.alts)
def test_registered(self): def test_registered(self):
self.assertFalse(self.member.registered) self.assertFalse(self.member.registered)
AuthUtils.disconnect_signals() AuthUtils.disconnect_signals()
character = EveCharacter.objects.create(character_id='2', character_name='other test character', corporation_id='2', corporation_name='test corp', corporation_ticker='TEST') character = EveCharacter.objects.create(character_id=2, character_name='other test character', corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b') CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
self.assertTrue(self.member.registered) self.assertTrue(self.member.registered)
AuthUtils.connect_signals() AuthUtils.connect_signals()

View File

@@ -4,14 +4,14 @@
# It contains of modules for views and templatetags for templates # It contains of modules for views and templatetags for templates
# list of all eve entity categories as defined in ESI # list of all eve entity categories as defined in ESI
ESI_CATEGORY_AGENT = "agent" _ESI_CATEGORY_AGENT = "agent"
ESI_CATEGORY_ALLIANCE = "alliance" _ESI_CATEGORY_ALLIANCE = "alliance"
ESI_CATEGORY_CHARACTER = "character" _ESI_CATEGORY_CHARACTER = "character"
ESI_CATEGORY_CONSTELLATION = "constellation" _ESI_CATEGORY_CONSTELLATION = "constellation"
ESI_CATEGORY_CORPORATION = "corporation" _ESI_CATEGORY_CORPORATION = "corporation"
ESI_CATEGORY_FACTION = "faction" _ESI_CATEGORY_FACTION = "faction"
ESI_CATEGORY_INVENTORYTYPE = "inventory_type" _ESI_CATEGORY_INVENTORYTYPE = "inventory_type"
ESI_CATEGORY_REGION = "region" _ESI_CATEGORY_REGION = "region"
ESI_CATEGORY_SOLARSYSTEM = "solar_system" _ESI_CATEGORY_SOLARSYSTEM = "solar_system"
ESI_CATEGORY_STATION = "station" _ESI_CATEGORY_STATION = "station"
ESI_CATEGORY_WORMHOLE = "wormhole" _ESI_CATEGORY_WORMHOLE = "wormhole"

View File

@@ -2,24 +2,30 @@
from urllib.parse import urljoin, quote from urllib.parse import urljoin, quote
from . import * from . import (
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_REGION,
_ESI_CATEGORY_SOLARSYSTEM
)
BASE_URL = 'http://evemaps.dotlan.net'
_BASE_URL = 'http://evemaps.dotlan.net'
def _build_url(category: str, name: str) -> str: def _build_url(category: str, name: str) -> str:
"""return url to profile page for an eve entity""" """return url to profile page for an eve entity"""
if category == ESI_CATEGORY_ALLIANCE: if category == _ESI_CATEGORY_ALLIANCE:
partial = 'alliance' partial = 'alliance'
elif category == ESI_CATEGORY_CORPORATION: elif category == _ESI_CATEGORY_CORPORATION:
partial = 'corp' partial = 'corp'
elif category == ESI_CATEGORY_REGION: elif category == _ESI_CATEGORY_REGION:
partial = 'map' partial = 'map'
elif category == ESI_CATEGORY_SOLARSYSTEM: elif category == _ESI_CATEGORY_SOLARSYSTEM:
partial = 'system' partial = 'system'
else: else:
@@ -28,7 +34,7 @@ def _build_url(category: str, name: str) -> str:
) )
url = urljoin( url = urljoin(
BASE_URL, _BASE_URL,
'{}/{}'.format(partial, quote(str(name).replace(" ", "_"))) '{}/{}'.format(partial, quote(str(name).replace(" ", "_")))
) )
@@ -37,16 +43,19 @@ def _build_url(category: str, name: str) -> str:
def alliance_url(name: str) -> str: def alliance_url(name: str) -> str:
"""url for page about given alliance on dotlan""" """url for page about given alliance on dotlan"""
return _build_url(ESI_CATEGORY_ALLIANCE, name) return _build_url(_ESI_CATEGORY_ALLIANCE, name)
def corporation_url(name: str) -> str: def corporation_url(name: str) -> str:
"""url for page about given corporation on dotlan""" """url for page about given corporation on dotlan"""
return _build_url(ESI_CATEGORY_CORPORATION, name) return _build_url(_ESI_CATEGORY_CORPORATION, name)
def region_url(name: str) -> str: def region_url(name: str) -> str:
"""url for page about given region on dotlan""" """url for page about given region on dotlan"""
return _build_url(ESI_CATEGORY_REGION, name) return _build_url(_ESI_CATEGORY_REGION, name)
def solar_system_url(name: str) -> str: def solar_system_url(name: str) -> str:
"""url for page about given solar system on dotlan""" """url for page about given solar system on dotlan"""
return _build_url(ESI_CATEGORY_SOLARSYSTEM, name) return _build_url(_ESI_CATEGORY_SOLARSYSTEM, name)

View File

@@ -0,0 +1,129 @@
from . import (
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CHARACTER,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_INVENTORYTYPE
)
_EVE_IMAGE_SERVER_URL = 'https://images.evetech.net'
_DEFAULT_IMAGE_SIZE = 32
def _eve_entity_image_url(
category: str,
entity_id: int,
size: int = 32,
variant: str = None,
tenant: str = None,
) -> str:
"""returns image URL for an Eve Online ID.
Supported categories: alliance, corporation, character, inventory_type
Arguments:
- category: category of the ID, see ESI category constants
- entity_id: Eve ID of the entity
- size: (optional) render size of the image.must be between 32 (default) and 1024
- variant: (optional) image variant for category. currently not relevant.
- tenant: (optional) Eve Server, either `tranquility`(default) or `singularity`
Returns:
- URL string for the requested image on the Eve image server
Exceptions:
- Throws ValueError on invalid input
"""
# input validations
categories = {
_ESI_CATEGORY_ALLIANCE: {
'endpoint': 'alliances',
'variants': ['logo']
},
_ESI_CATEGORY_CORPORATION: {
'endpoint': 'corporations',
'variants': ['logo']
},
_ESI_CATEGORY_CHARACTER: {
'endpoint': 'characters',
'variants': ['portrait']
},
_ESI_CATEGORY_INVENTORYTYPE: {
'endpoint': 'types',
'variants': ['icon', 'render']
}
}
tenants = ['tranquility', 'singularity']
if not entity_id:
raise ValueError('Invalid entity_id: {}'.format(entity_id))
else:
entity_id = int(entity_id)
if not size or size < 32 or size > 1024 or (size & (size - 1) != 0):
raise ValueError('Invalid size: {}'.format(size))
if category not in categories:
raise ValueError('Invalid category {}'.format(category))
else:
endpoint = categories[category]['endpoint']
if variant:
if variant not in categories[category]['variants']:
raise ValueError('Invalid variant {} for category {}'.format(
variant,
category
))
else:
variant = categories[category]['variants'][0]
if tenant and tenant not in tenants:
raise ValueError('Invalid tenant {}'.format(tenant))
# compose result URL
result = '{}/{}/{}/{}?size={}'.format(
_EVE_IMAGE_SERVER_URL,
endpoint,
entity_id,
variant,
size
)
if tenant:
result += '&tenant={}'.format(tenant)
return result
def alliance_logo_url(alliance_id: int, size: int = _DEFAULT_IMAGE_SIZE) -> str:
"""image URL for the given alliance ID"""
return _eve_entity_image_url(_ESI_CATEGORY_ALLIANCE, alliance_id, size)
def corporation_logo_url(
corporation_id: int, size: int = _DEFAULT_IMAGE_SIZE
) -> str:
"""image URL for the given corporation ID"""
return _eve_entity_image_url(
_ESI_CATEGORY_CORPORATION, corporation_id, size
)
def character_portrait_url(
character_id: int, size: int = _DEFAULT_IMAGE_SIZE
) -> str:
"""image URL for the given character ID"""
return _eve_entity_image_url(_ESI_CATEGORY_CHARACTER, character_id, size)
def type_icon_url(type_id: int, size: int = _DEFAULT_IMAGE_SIZE) -> str:
"""icon image URL for the given type ID"""
return _eve_entity_image_url(
_ESI_CATEGORY_INVENTORYTYPE, type_id, size, variant='icon'
)
def type_render_url(type_id: int, size: int = _DEFAULT_IMAGE_SIZE) -> str:
"""render image URL for the given type ID"""
return _eve_entity_image_url(
_ESI_CATEGORY_INVENTORYTYPE, type_id, size, variant='render'
)

View File

@@ -1,22 +1,27 @@
# this module generates profile URLs for evewho # this module generates profile URLs for evewho
from urllib.parse import urljoin, quote from urllib.parse import urljoin
from . import * from . import (
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_CHARACTER,
)
BASE_URL = 'https://evewho.com'
_BASE_URL = 'https://evewho.com'
def _build_url(category: str, eve_id: int) -> str: def _build_url(category: str, eve_id: int) -> str:
"""return url to profile page for an eve entity""" """return url to profile page for an eve entity"""
if category == ESI_CATEGORY_ALLIANCE: if category == _ESI_CATEGORY_ALLIANCE:
partial = 'alliance' partial = 'alliance'
elif category == ESI_CATEGORY_CORPORATION: elif category == _ESI_CATEGORY_CORPORATION:
partial = 'corporation' partial = 'corporation'
elif category == ESI_CATEGORY_CHARACTER: elif category == _ESI_CATEGORY_CHARACTER:
partial = 'character' partial = 'character'
else: else:
@@ -25,7 +30,7 @@ def _build_url(category: str, eve_id: int) -> str:
) )
url = urljoin( url = urljoin(
BASE_URL, _BASE_URL,
'{}/{}'.format(partial, int(eve_id)) '{}/{}'.format(partial, int(eve_id))
) )
return url return url
@@ -33,12 +38,14 @@ def _build_url(category: str, eve_id: int) -> str:
def alliance_url(eve_id: int) -> str: def alliance_url(eve_id: int) -> str:
"""url for page about given alliance on evewho""" """url for page about given alliance on evewho"""
return _build_url(ESI_CATEGORY_ALLIANCE, eve_id) return _build_url(_ESI_CATEGORY_ALLIANCE, eve_id)
def character_url(eve_id: int) -> str: def character_url(eve_id: int) -> str:
"""url for page about given character on evewho""" """url for page about given character on evewho"""
return _build_url(ESI_CATEGORY_CHARACTER, eve_id) return _build_url(_ESI_CATEGORY_CHARACTER, eve_id)
def corporation_url(eve_id: int) -> str: def corporation_url(eve_id: int) -> str:
"""url for page about given corporation on evewho""" """url for page about given corporation on evewho"""
return _build_url(ESI_CATEGORY_CORPORATION, eve_id) return _build_url(_ESI_CATEGORY_CORPORATION, eve_id)

View File

@@ -1,7 +1,7 @@
from django.test import TestCase from django.test import TestCase
from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from .. import dotlan, zkillboard, evewho from .. import dotlan, zkillboard, evewho, eveimageserver
from ...templatetags import evelinks from ...templatetags import evelinks
@@ -90,3 +90,115 @@ class TestZkillboard(TestCase):
'https://zkillboard.com/system/12345678/' 'https://zkillboard.com/system/12345678/'
) )
class TestEveImageServer(TestCase):
"""unit test for eveimageserver"""
def test_sizes(self):
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=32),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=64),
'https://images.evetech.net/characters/42/portrait?size=64'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=128),
'https://images.evetech.net/characters/42/portrait?size=128'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=256),
'https://images.evetech.net/characters/42/portrait?size=256'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=512),
'https://images.evetech.net/characters/42/portrait?size=512'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=1024),
'https://images.evetech.net/characters/42/portrait?size=1024'
)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('corporation', 42, size=-5)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('corporation', 42, size=0)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('corporation', 42, size=31)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('corporation', 42, size=1025)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('corporation', 42, size=2048)
def test_variant(self):
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, variant='portrait'),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('alliance', 42, variant='logo'),
'https://images.evetech.net/alliances/42/logo?size=32'
)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('character', 42, variant='logo')
def test_alliance(self):
self.assertEqual(
eveimageserver._eve_entity_image_url('alliance', 42),
'https://images.evetech.net/alliances/42/logo?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('corporation', 42),
'https://images.evetech.net/corporations/42/logo?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42),
'https://images.evetech.net/characters/42/portrait?size=32'
)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('station', 42)
def test_tenants(self):
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, tenant='tranquility'),
'https://images.evetech.net/characters/42/portrait?size=32&tenant=tranquility'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, tenant='singularity'),
'https://images.evetech.net/characters/42/portrait?size=32&tenant=singularity'
)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('character', 42, tenant='xxx')
def test_alliance_logo_url(self):
expected = 'https://images.evetech.net/alliances/42/logo?size=128'
self.assertEqual(eveimageserver.alliance_logo_url(42, 128), expected)
def test_corporation_logo_url(self):
expected = 'https://images.evetech.net/corporations/42/logo?size=128'
self.assertEqual(eveimageserver.corporation_logo_url(42, 128), expected)
def test_character_portrait_url(self):
expected = 'https://images.evetech.net/characters/42/portrait?size=128'
self.assertEqual(
eveimageserver.character_portrait_url(42, 128), expected
)
def test_type_icon_url(self):
expected = 'https://images.evetech.net/types/42/icon?size=128'
self.assertEqual(eveimageserver.type_icon_url(42, 128), expected)
def test_type_render_url(self):
expected = 'https://images.evetech.net/types/42/render?size=128'
self.assertEqual(eveimageserver.type_render_url(42, 128), expected)

View File

@@ -1,7 +1,7 @@
from django.test import TestCase from django.test import TestCase
from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from .. import dotlan, zkillboard, evewho from .. import eveimageserver, evewho, dotlan, zkillboard
from ...templatetags import evelinks from ...templatetags import evelinks
@@ -332,3 +332,28 @@ class TestTemplateTags(TestCase):
'' ''
) )
def test_type_icon_url(self):
expected = eveimageserver.type_icon_url(123)
self.assertEqual(evelinks.type_icon_url(123), expected)
expected = eveimageserver.type_icon_url(123, 128)
self.assertEqual(evelinks.type_icon_url(123, 128), expected)
expected = ''
self.assertEqual(evelinks.type_icon_url(123, 99), expected)
expected = ''
self.assertEqual(evelinks.type_icon_url(None), expected)
def test_type_render_url(self):
expected = eveimageserver.type_render_url(123)
self.assertEqual(evelinks.type_render_url(123), expected)
expected = eveimageserver.type_render_url(123, 128)
self.assertEqual(evelinks.type_render_url(123, 128), expected)
expected = ''
self.assertEqual(evelinks.type_render_url(123, 99), expected)
expected = ''
self.assertEqual(evelinks.type_render_url(None), expected)

View File

@@ -1,28 +1,35 @@
# this module generates profile URLs for zKillboard # this module generates profile URLs for zKillboard
from urllib.parse import urljoin, quote from urllib.parse import urljoin
from . import * from . import (
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_CHARACTER,
_ESI_CATEGORY_REGION,
_ESI_CATEGORY_SOLARSYSTEM
)
BASE_URL = 'https://zkillboard.com'
_BASE_URL = 'https://zkillboard.com'
def _build_url(category: str, eve_id: int) -> str: def _build_url(category: str, eve_id: int) -> str:
"""return url to profile page for an eve entity""" """return url to profile page for an eve entity"""
if category == ESI_CATEGORY_ALLIANCE: if category == _ESI_CATEGORY_ALLIANCE:
partial = 'alliance' partial = 'alliance'
elif category == ESI_CATEGORY_CORPORATION: elif category == _ESI_CATEGORY_CORPORATION:
partial = 'corporation' partial = 'corporation'
elif category == ESI_CATEGORY_CHARACTER: elif category == _ESI_CATEGORY_CHARACTER:
partial = 'character' partial = 'character'
elif category == ESI_CATEGORY_REGION: elif category == _ESI_CATEGORY_REGION:
partial = 'region' partial = 'region'
elif category == ESI_CATEGORY_SOLARSYSTEM: elif category == _ESI_CATEGORY_SOLARSYSTEM:
partial = 'system' partial = 'system'
else: else:
@@ -31,7 +38,7 @@ def _build_url(category: str, eve_id: int) -> str:
) )
url = urljoin( url = urljoin(
BASE_URL, _BASE_URL,
'{}/{}/'.format(partial, int(eve_id)) '{}/{}/'.format(partial, int(eve_id))
) )
return url return url
@@ -39,19 +46,23 @@ def _build_url(category: str, eve_id: int) -> str:
def alliance_url(eve_id: int) -> str: def alliance_url(eve_id: int) -> str:
"""url for page about given alliance on zKillboard""" """url for page about given alliance on zKillboard"""
return _build_url(ESI_CATEGORY_ALLIANCE, eve_id) return _build_url(_ESI_CATEGORY_ALLIANCE, eve_id)
def character_url(eve_id: int) -> str: def character_url(eve_id: int) -> str:
"""url for page about given character on zKillboard""" """url for page about given character on zKillboard"""
return _build_url(ESI_CATEGORY_CHARACTER, eve_id) return _build_url(_ESI_CATEGORY_CHARACTER, eve_id)
def corporation_url(eve_id: int) -> str: def corporation_url(eve_id: int) -> str:
"""url for page about given corporation on zKillboard""" """url for page about given corporation on zKillboard"""
return _build_url(ESI_CATEGORY_CORPORATION, eve_id) return _build_url(_ESI_CATEGORY_CORPORATION, eve_id)
def region_url(eve_id: int) -> str: def region_url(eve_id: int) -> str:
"""url for page about given region on zKillboard""" """url for page about given region on zKillboard"""
return _build_url(ESI_CATEGORY_REGION, eve_id) return _build_url(_ESI_CATEGORY_REGION, eve_id)
def solar_system_url(eve_id: int) -> str: def solar_system_url(eve_id: int) -> str:
return _build_url(ESI_CATEGORY_SOLARSYSTEM, eve_id) return _build_url(_ESI_CATEGORY_SOLARSYSTEM, eve_id)

View File

@@ -89,4 +89,6 @@ class EveCorporationManager(models.Manager):
) )
def update_corporation(self, corp_id): def update_corporation(self, corp_id):
return self.get(corporation_id=corp_id).update_corporation(self.provider.get_corporation(corp_id)) return self\
.get(corporation_id=corp_id)\
.update_corporation(self.provider.get_corporation(corp_id))

View File

@@ -0,0 +1,43 @@
# Generated by Django 2.2.12 on 2020-05-25 02:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('eveonline', '0010_alliance_ticker'),
]
operations = [
migrations.AlterField(
model_name='eveallianceinfo',
name='alliance_id',
field=models.PositiveIntegerField(unique=True),
),
migrations.AlterField(
model_name='eveallianceinfo',
name='executor_corp_id',
field=models.PositiveIntegerField(),
),
migrations.AlterField(
model_name='evecharacter',
name='alliance_id',
field=models.PositiveIntegerField(blank=True, default=None, null=True),
),
migrations.AlterField(
model_name='evecharacter',
name='character_id',
field=models.PositiveIntegerField(unique=True),
),
migrations.AlterField(
model_name='evecharacter',
name='corporation_id',
field=models.PositiveIntegerField(),
),
migrations.AlterField(
model_name='evecorporationinfo',
name='corporation_id',
field=models.PositiveIntegerField(unique=True),
),
]

View File

@@ -0,0 +1,33 @@
# Generated by Django 2.2.12 on 2020-05-26 02:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('eveonline', '0011_ids_to_integers'),
]
operations = [
migrations.AddIndex(
model_name='eveallianceinfo',
index=models.Index(fields=['executor_corp_id'], name='eveonline_e_executo_7f3280_idx'),
),
migrations.AddIndex(
model_name='evecharacter',
index=models.Index(fields=['corporation_id'], name='eveonline_e_corpora_cb4cd9_idx'),
),
migrations.AddIndex(
model_name='evecharacter',
index=models.Index(fields=['alliance_id'], name='eveonline_e_allianc_39ee2a_idx'),
),
migrations.AddIndex(
model_name='evecharacter',
index=models.Index(fields=['corporation_name'], name='eveonline_e_corpora_893c60_idx'),
),
migrations.AddIndex(
model_name='evecharacter',
index=models.Index(fields=['alliance_name'], name='eveonline_e_allianc_63fd98_idx'),
),
]

View File

@@ -5,109 +5,35 @@ from .managers import EveCharacterManager, EveCharacterProviderManager
from .managers import EveCorporationManager, EveCorporationProviderManager from .managers import EveCorporationManager, EveCorporationProviderManager
from .managers import EveAllianceManager, EveAllianceProviderManager from .managers import EveAllianceManager, EveAllianceProviderManager
from . import providers from . import providers
from .evelinks import eveimageserver
_DEFAULT_IMAGE_SIZE = 32
EVE_IMAGE_SERVER_URL = 'https://images.evetech.net'
def _eve_entity_image_url(
category: str,
id: int,
size: int = 32,
variant: str = None,
tenant: str = None,
) -> str:
"""returns image URL for an Eve Online ID.
Supported categories: `alliance`, `corporation`, `character`
Arguments:
- category: category of the ID
- id: Eve ID of the entity
- size: (optional) render size of the image.must be between 32 (default) and 1024
- variant: (optional) image variant for category. currently not relevant.
- tentant: (optional) Eve Server, either `tranquility`(default) or `singularity`
Returns:
- URL string for the requested image on the Eve image server
Exceptions:
- Throws ValueError on invalid input
"""
# input validations
categories = {
'alliance': {
'endpoint': 'alliances',
'variants': [
'logo'
]
},
'corporation': {
'endpoint': 'corporations',
'variants': [
'logo'
]
},
'character': {
'endpoint': 'characters',
'variants': [
'portrait'
]
}
}
tenants = ['tranquility', 'singularity']
if size < 32 or size > 1024 or (size & (size - 1) != 0):
raise ValueError('Invalid size: {}'.format(size))
if category not in categories:
raise ValueError('Invalid category {}'.format(category))
else:
endpoint = categories[category]['endpoint']
if variant:
if variant not in categories[category]['variants']:
raise ValueError('Invalid variant {} for category {}'.format(
variant,
category
))
else:
variant = categories[category]['variants'][0]
if tenant and tenant not in tenants:
raise ValueError('Invalid tentant {}'.format(tenant))
# compose result URL
result = '{}/{}/{}/{}?size={}'.format(
EVE_IMAGE_SERVER_URL,
endpoint,
id,
variant,
size
)
if tenant:
result += '&tenant={}'.format(tenant)
return result
class EveAllianceInfo(models.Model): class EveAllianceInfo(models.Model):
alliance_id = models.CharField(max_length=254, unique=True) alliance_id = models.PositiveIntegerField(unique=True)
alliance_name = models.CharField(max_length=254, unique=True) alliance_name = models.CharField(max_length=254, unique=True)
alliance_ticker = models.CharField(max_length=254) alliance_ticker = models.CharField(max_length=254)
executor_corp_id = models.CharField(max_length=254) executor_corp_id = models.PositiveIntegerField()
objects = EveAllianceManager() objects = EveAllianceManager()
provider = EveAllianceProviderManager() provider = EveAllianceProviderManager()
class Meta:
indexes = [models.Index(fields=['executor_corp_id',])]
def populate_alliance(self): def populate_alliance(self):
alliance = self.provider.get_alliance(self.alliance_id) alliance = self.provider.get_alliance(self.alliance_id)
for corp_id in alliance.corp_ids: for corp_id in alliance.corp_ids:
if not EveCorporationInfo.objects.filter(corporation_id=corp_id).exists(): if not EveCorporationInfo.objects.filter(corporation_id=corp_id).exists():
EveCorporationInfo.objects.create_corporation(corp_id) EveCorporationInfo.objects.create_corporation(corp_id)
EveCorporationInfo.objects.filter(corporation_id__in=alliance.corp_ids).update(alliance=self) EveCorporationInfo.objects.filter(
EveCorporationInfo.objects.filter(alliance=self).exclude(corporation_id__in=alliance.corp_ids).update( corporation_id__in=alliance.corp_ids).update(alliance=self
alliance=None) )
EveCorporationInfo.objects\
.filter(alliance=self)\
.exclude(corporation_id__in=alliance.corp_ids)\
.update(alliance=None)
def update_alliance(self, alliance: providers.Alliance = None): def update_alliance(self, alliance: providers.Alliance = None):
if alliance is None: if alliance is None:
@@ -120,11 +46,13 @@ class EveAllianceInfo(models.Model):
return self.alliance_name return self.alliance_name
@staticmethod @staticmethod
def generic_logo_url(alliance_id: int, size: int = 32) -> str: def generic_logo_url(
alliance_id: int, size: int = _DEFAULT_IMAGE_SIZE
) -> str:
"""image URL for the given alliance ID""" """image URL for the given alliance ID"""
return _eve_entity_image_url('alliance', alliance_id, size) return eveimageserver.alliance_logo_url(alliance_id, size)
def logo_url(self, size:int = 32) -> str: def logo_url(self, size: int = _DEFAULT_IMAGE_SIZE) -> str:
"""image URL of this alliance""" """image URL of this alliance"""
return self.generic_logo_url(self.alliance_id, size) return self.generic_logo_url(self.alliance_id, size)
@@ -150,11 +78,13 @@ class EveAllianceInfo(models.Model):
class EveCorporationInfo(models.Model): class EveCorporationInfo(models.Model):
corporation_id = models.CharField(max_length=254, unique=True) corporation_id = models.PositiveIntegerField(unique=True)
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()
alliance = models.ForeignKey(EveAllianceInfo, blank=True, null=True, on_delete=models.SET_NULL) alliance = models.ForeignKey(
EveAllianceInfo, blank=True, null=True, on_delete=models.SET_NULL
)
objects = EveCorporationManager() objects = EveCorporationManager()
provider = EveCorporationProviderManager() provider = EveCorporationProviderManager()
@@ -174,11 +104,13 @@ class EveCorporationInfo(models.Model):
return self.corporation_name return self.corporation_name
@staticmethod @staticmethod
def generic_logo_url(corporation_id: int, size: int = 32) -> str: def generic_logo_url(
corporation_id: int, size: int = _DEFAULT_IMAGE_SIZE
) -> str:
"""image URL for the given corporation ID""" """image URL for the given corporation ID"""
return _eve_entity_image_url('corporation', corporation_id, size) return eveimageserver.corporation_logo_url(corporation_id, size)
def logo_url(self, size:int = 32) -> str: def logo_url(self, size: int = _DEFAULT_IMAGE_SIZE) -> str:
"""image URL for this corporation""" """image URL for this corporation"""
return self.generic_logo_url(self.corporation_id, size) return self.generic_logo_url(self.corporation_id, size)
@@ -204,18 +136,26 @@ class EveCorporationInfo(models.Model):
class EveCharacter(models.Model): class EveCharacter(models.Model):
character_id = models.CharField(max_length=254, unique=True) character_id = models.PositiveIntegerField(unique=True)
character_name = models.CharField(max_length=254, unique=True) character_name = models.CharField(max_length=254, unique=True)
corporation_id = models.CharField(max_length=254) corporation_id = models.PositiveIntegerField()
corporation_name = models.CharField(max_length=254) corporation_name = models.CharField(max_length=254)
corporation_ticker = models.CharField(max_length=5) corporation_ticker = models.CharField(max_length=5)
alliance_id = models.CharField(max_length=254, blank=True, null=True, default='') alliance_id = models.PositiveIntegerField(blank=True, null=True, default=None)
alliance_name = models.CharField(max_length=254, blank=True, null=True, default='') alliance_name = models.CharField(max_length=254, blank=True, null=True, default='')
alliance_ticker = models.CharField(max_length=5, blank=True, null=True, default='') alliance_ticker = models.CharField(max_length=5, blank=True, null=True, default='')
objects = EveCharacterManager() objects = EveCharacterManager()
provider = EveCharacterProviderManager() provider = EveCharacterProviderManager()
class Meta:
indexes = [
models.Index(fields=['corporation_id',]),
models.Index(fields=['alliance_id',]),
models.Index(fields=['corporation_name',]),
models.Index(fields=['alliance_name',]),
]
@property @property
def alliance(self) -> Union[EveAllianceInfo, None]: def alliance(self) -> Union[EveAllianceInfo, None]:
""" """
@@ -253,11 +193,13 @@ class EveCharacter(models.Model):
return self.character_name return self.character_name
@staticmethod @staticmethod
def generic_portrait_url(character_id: int, size: int = 32) -> str: def generic_portrait_url(
character_id: int, size: int = _DEFAULT_IMAGE_SIZE
) -> str:
"""image URL for the given character ID""" """image URL for the given character ID"""
return _eve_entity_image_url('character', character_id, size) return eveimageserver.character_portrait_url(character_id, size)
def portrait_url(self, size = 32) -> str: def portrait_url(self, size=_DEFAULT_IMAGE_SIZE) -> str:
"""image URL for this character""" """image URL for this character"""
return self.generic_portrait_url(self.character_id, size) return self.generic_portrait_url(self.character_id, size)
@@ -281,7 +223,7 @@ class EveCharacter(models.Model):
"""image URL for this character""" """image URL for this character"""
return self.portrait_url(256) return self.portrait_url(256)
def corporation_logo_url(self, size = 32) -> str: def corporation_logo_url(self, size=_DEFAULT_IMAGE_SIZE) -> str:
"""image URL for corporation of this character""" """image URL for corporation of this character"""
return EveCorporationInfo.generic_logo_url(self.corporation_id, size) return EveCorporationInfo.generic_logo_url(self.corporation_id, size)
@@ -305,7 +247,7 @@ class EveCharacter(models.Model):
"""image URL for corporation of this character""" """image URL for corporation of this character"""
return self.corporation_logo_url(256) return self.corporation_logo_url(256)
def alliance_logo_url(self, size = 32) -> str: def alliance_logo_url(self, size=_DEFAULT_IMAGE_SIZE) -> str:
"""image URL for alliance of this character or empty string""" """image URL for alliance of this character or empty string"""
if self.alliance_id: if self.alliance_id:
return EveAllianceInfo.generic_logo_url(self.alliance_id, size) return EveAllianceInfo.generic_logo_url(self.alliance_id, size)

File diff suppressed because one or more lines are too long

View File

@@ -5,33 +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
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.delay(corp['corporation_id']) 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.delay(alliance['alliance_id']) update_alliance.apply_async(
args=[alliance['alliance_id']], priority=TASK_PRIORITY
)
for character in EveCharacter.objects.all().values('character_id'): # update existing character models
update_character.delay(character['character_id']) character_ids = EveCharacter.objects.all().values_list('character_id', flat=True)
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

@@ -15,7 +15,7 @@
from django import template from django import template
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from ..evelinks import evewho, dotlan, zkillboard from ..evelinks import eveimageserver, evewho, dotlan, zkillboard
register = template.Library() register = template.Library()
@@ -163,7 +163,7 @@ def dotlan_solar_system_url(eve_obj: object) -> str:
return _generic_evelinks_url(dotlan, 'solar_system_url', eve_obj) return _generic_evelinks_url(dotlan, 'solar_system_url', eve_obj)
#zkillboard # zkillboard
@register.filter @register.filter
def zkillboard_character_url(eve_obj: EveCharacter) -> str: def zkillboard_character_url(eve_obj: EveCharacter) -> str:
@@ -212,7 +212,6 @@ def zkillboard_solar_system_url(eve_obj: object) -> str:
# image urls # image urls
@register.filter @register.filter
def character_portrait_url( def character_portrait_url(
eve_obj: object, eve_obj: object,
@@ -284,3 +283,30 @@ def alliance_logo_url(
except ValueError: except ValueError:
return '' return ''
@register.filter
def type_icon_url(
type_id: int,
size: int = _DEFAULT_IMAGE_SIZE
) -> str:
"""generates a icon image URL for the given type ID
Returns URL or empty string
"""
try:
return eveimageserver.type_icon_url(type_id, size)
except ValueError:
return ''
@register.filter
def type_render_url(
type_id: int,
size: int = _DEFAULT_IMAGE_SIZE
) -> str:
"""generates a render image URL for the given type ID
Returns URL or empty string
"""
try:
return eveimageserver.type_render_url(type_id, size)
except ValueError:
return ''

View File

@@ -12,7 +12,7 @@ class EveCharacterProviderManagerTestCase(TestCase):
expected = Character() expected = Character()
provider.get_character.return_value = expected provider.get_character.return_value = expected
result = EveCharacter.provider.get_character('1234') result = EveCharacter.provider.get_character(1234)
self.assertEqual(expected, result) self.assertEqual(expected, result)
@@ -22,30 +22,30 @@ class EveCharacterManagerTestCase(TestCase):
class TestCharacter(Character): class TestCharacter(Character):
@property @property
def alliance(self): def alliance(self):
return Alliance(id='3456', name='Test Alliance') return Alliance(id=3456, name='Test Alliance')
@property @property
def corp(self): def corp(self):
return Corporation( return Corporation(
id='2345', id=2345,
name='Test Corp', name='Test Corp',
alliance_id='3456', alliance_id=3456,
ticker='0BUGS' ticker='0BUGS' #lies, blatant lies!
) )
@mock.patch('allianceauth.eveonline.managers.providers.provider') @mock.patch('allianceauth.eveonline.managers.providers.provider')
def test_create_character(self, provider): def test_create_character(self, provider):
# Also covers create_character_obj # Also covers create_character_obj
expected = self.TestCharacter( expected = self.TestCharacter(
id='1234', id=1234,
name='Test Character', name='Test Character',
corp_id='2345', corp_id=2345,
alliance_id='3456' alliance_id=3456
) )
provider.get_character.return_value = expected provider.get_character.return_value = expected
result = EveCharacter.objects.create_character('1234') result = EveCharacter.objects.create_character(1234)
self.assertEqual(result.character_id, expected.id) self.assertEqual(result.character_id, expected.id)
self.assertEqual(result.character_name, expected.name) self.assertEqual(result.character_name, expected.name)
@@ -59,25 +59,24 @@ class EveCharacterManagerTestCase(TestCase):
def test_update_character(self, provider): def test_update_character(self, provider):
# Also covers Model.update_character # Also covers Model.update_character
existing = EveCharacter.objects.create( existing = EveCharacter.objects.create(
character_id='1234', character_id=1234,
character_name='character.name', character_name='character.name',
corporation_id='character.corp.id', corporation_id=23457,
corporation_name='character.corp.name', corporation_name='character.corp.name',
corporation_ticker='character.corp.ticker', corporation_ticker='cc1',
alliance_id='character.alliance.id', alliance_id=34567,
alliance_name='character.alliance.name', alliance_name='character.alliance.name',
) )
expected = self.TestCharacter( expected = self.TestCharacter(
id='1234', id=1234,
name='Test Character', name='Test Character',
corp_id='2345', corp_id=2345,
alliance_id='3456' alliance_id=3456
) )
provider.get_character.return_value = expected provider.get_character.return_value = expected
result = EveCharacter.objects.update_character('1234') result = EveCharacter.objects.update_character(1234)
self.assertEqual(result.character_id, expected.id) self.assertEqual(result.character_id, expected.id)
self.assertEqual(result.character_name, expected.name) self.assertEqual(result.character_name, expected.name)
@@ -90,23 +89,23 @@ class EveCharacterManagerTestCase(TestCase):
def test_get_character_by_id(self): def test_get_character_by_id(self):
EveCharacter.objects.all().delete() EveCharacter.objects.all().delete()
EveCharacter.objects.create( EveCharacter.objects.create(
character_id='1234', character_id=1234,
character_name='character.name', character_name='character.name',
corporation_id='character.corp.id', corporation_id=2345,
corporation_name='character.corp.name', corporation_name='character.corp.name',
corporation_ticker='character.corp.ticker', corporation_ticker='cc1',
alliance_id='character.alliance.id', alliance_id=3456,
alliance_name='character.alliance.name', alliance_name='character.alliance.name',
) )
# try to get existing character # try to get existing character
result = EveCharacter.objects.get_character_by_id('1234') result = EveCharacter.objects.get_character_by_id(1234)
self.assertEqual(result.character_id, '1234') self.assertEqual(result.character_id, 1234)
self.assertEqual(result.character_name, 'character.name') self.assertEqual(result.character_name, 'character.name')
# try to get non existing character # try to get non existing character
self.assertIsNone(EveCharacter.objects.get_character_by_id('9999')) self.assertIsNone(EveCharacter.objects.get_character_by_id(9999))
class EveAllianceProviderManagerTestCase(TestCase): class EveAllianceProviderManagerTestCase(TestCase):
@@ -115,7 +114,7 @@ class EveAllianceProviderManagerTestCase(TestCase):
expected = Alliance() expected = Alliance()
provider.get_alliance.return_value = expected provider.get_alliance.return_value = expected
result = EveAllianceInfo.provider.get_alliance('1234') result = EveAllianceInfo.provider.get_alliance(1234)
self.assertEqual(expected, result) self.assertEqual(expected, result)
@@ -131,16 +130,16 @@ class EveAllianceManagerTestCase(TestCase):
def test_create_alliance(self, provider, populate_alliance): def test_create_alliance(self, provider, populate_alliance):
# Also covers create_alliance_obj # Also covers create_alliance_obj
expected = self.TestAlliance( expected = self.TestAlliance(
id='3456', id=3456,
name='Test Alliance', name='Test Alliance',
ticker='TEST', ticker='TEST',
corp_ids=['2345'], corp_ids=[2345],
executor_corp_id='2345' executor_corp_id=2345
) )
provider.get_alliance.return_value = expected provider.get_alliance.return_value = expected
result = EveAllianceInfo.objects.create_alliance('3456') result = EveAllianceInfo.objects.create_alliance(3456)
self.assertEqual(result.alliance_id, expected.id) self.assertEqual(result.alliance_id, expected.id)
self.assertEqual(result.alliance_name, expected.name) self.assertEqual(result.alliance_name, expected.name)
@@ -152,22 +151,22 @@ class EveAllianceManagerTestCase(TestCase):
def test_update_alliance(self, provider): def test_update_alliance(self, provider):
# Also covers Model.update_alliance # Also covers Model.update_alliance
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='at1',
executor_corp_id='alliance.executor_corp_id', executor_corp_id=2345,
) )
expected = self.TestAlliance( expected = self.TestAlliance(
id='3456', id=3456,
name='Test Alliance', name='Test Alliance',
ticker='TEST', ticker='TEST',
corp_ids=['2345'], corp_ids=[2345],
executor_corp_id='2345' executor_corp_id=2345
) )
provider.get_alliance.return_value = expected provider.get_alliance.return_value = expected
result = EveAllianceInfo.objects.update_alliance('3456') result = EveAllianceInfo.objects.update_alliance(3456)
# This is the only thing ever updated in code # This is the only thing ever updated in code
self.assertEqual(result.executor_corp_id, expected.executor_corp_id) self.assertEqual(result.executor_corp_id, expected.executor_corp_id)
@@ -179,7 +178,7 @@ class EveCorporationProviderManagerTestCase(TestCase):
expected = Corporation() expected = Corporation()
provider.get_corp.return_value = expected provider.get_corp.return_value = expected
result = EveCorporationInfo.provider.get_corporation('2345') result = EveCorporationInfo.provider.get_corporation(2345)
self.assertEqual(expected, result) self.assertEqual(expected, result)
@@ -190,39 +189,39 @@ class EveCorporationManagerTestCase(TestCase):
@property @property
def alliance(self): def alliance(self):
return EveAllianceManagerTestCase.TestAlliance( return EveAllianceManagerTestCase.TestAlliance(
id='3456', id=3456,
name='Test Alliance', name='Test Alliance',
ticker='TEST', ticker='TEST',
corp_ids=['2345'], corp_ids=[2345],
executor_corp_id='2345' executor_corp_id=2345
) )
@property @property
def ceo(self): def ceo(self):
return EveCharacterManagerTestCase.TestCharacter( return EveCharacterManagerTestCase.TestCharacter(
id='1234', id=1234,
name='Test Character', name='Test Character',
corp_id='2345', corp_id=2345,
alliance_id='3456' alliance_id=3456
) )
@mock.patch('allianceauth.eveonline.managers.providers.provider') @mock.patch('allianceauth.eveonline.managers.providers.provider')
def test_create_corporation(self, provider): def test_create_corporation(self, provider):
# Also covers create_corp_obj # Also covers create_corp_obj
exp_alliance = EveAllianceInfo.objects.create( exp_alliance = EveAllianceInfo.objects.create(
alliance_id='3456', alliance_id=3456,
alliance_name='alliance.name', alliance_name='alliance.name',
alliance_ticker='alliance.ticker', alliance_ticker='99bug',
executor_corp_id='alliance.executor_corp_id', executor_corp_id=2345,
) )
expected = self.TestCorporation( expected = self.TestCorporation(
id='2345', id=2345,
name='Test Corp', name='Test Corp',
ticker='0BUGS', ticker='0BUGS',
ceo_id='1234', ceo_id=1234,
members=1, members=1,
alliance_id='3456' alliance_id=3456
) )
provider.get_corp.return_value = expected provider.get_corp.return_value = expected
@@ -240,17 +239,17 @@ class EveCorporationManagerTestCase(TestCase):
# variant to test no alliance case # variant to test no alliance case
# Also covers create_corp_obj # Also covers create_corp_obj
expected = self.TestCorporation( expected = self.TestCorporation(
id='2345', id=2345,
name='Test Corp', name='Test Corp',
ticker='0BUGS', ticker='0BUGS',
ceo_id='1234', ceo_id=1234,
members=1, members=1,
alliance_id='3456' alliance_id=3456
) )
provider.get_corp.return_value = expected provider.get_corp.return_value = expected
result = EveCorporationInfo.objects.create_corporation('2345') result = EveCorporationInfo.objects.create_corporation(2345)
self.assertEqual(result.corporation_id, expected.id) self.assertEqual(result.corporation_id, expected.id)
self.assertEqual(result.corporation_name, expected.name) self.assertEqual(result.corporation_name, expected.name)
@@ -262,27 +261,27 @@ class EveCorporationManagerTestCase(TestCase):
def test_update_corporation(self, provider): def test_update_corporation(self, provider):
# Also covers Model.update_corporation # Also covers Model.update_corporation
exp_alliance = EveAllianceInfo.objects.create( exp_alliance = EveAllianceInfo.objects.create(
alliance_id='3456', alliance_id=3456,
alliance_name='alliance.name', alliance_name='alliance.name',
alliance_ticker='alliance.ticker', alliance_ticker='at1',
executor_corp_id='alliance.executor_corp_id', executor_corp_id=2345,
) )
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='cc1',
member_count=10, member_count=10,
alliance=None, alliance=None,
) )
expected = self.TestCorporation( expected = self.TestCorporation(
id='2345', id=2345,
name='Test Corp', name='Test Corp',
ticker='0BUGS', ticker='0BUGS',
ceo_id='1234', ceo_id=1234,
members=1, members=1,
alliance_id='3456' alliance_id=3456
) )
provider.get_corp.return_value = expected provider.get_corp.return_value = expected

View File

@@ -2,130 +2,40 @@ from unittest.mock import Mock, patch
from django.test import TestCase from django.test import TestCase
from ..models import EveCharacter, EveCorporationInfo, \ from ..models import (
EveAllianceInfo, _eve_entity_image_url EveCharacter, EveCorporationInfo, EveAllianceInfo
)
from ..providers import Alliance, Corporation, Character from ..providers import Alliance, Corporation, Character
from ..evelinks import eveimageserver
class EveUniverseImageUrlTestCase(TestCase):
"""unit test for _eve_entity_image_url()"""
def test_sizes(self):
self.assertEqual(
_eve_entity_image_url('character', 42),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
_eve_entity_image_url('character', 42, size=32),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
_eve_entity_image_url('character', 42, size=64),
'https://images.evetech.net/characters/42/portrait?size=64'
)
self.assertEqual(
_eve_entity_image_url('character', 42, size=128),
'https://images.evetech.net/characters/42/portrait?size=128'
)
self.assertEqual(
_eve_entity_image_url('character', 42, size=256),
'https://images.evetech.net/characters/42/portrait?size=256'
)
self.assertEqual(
_eve_entity_image_url('character', 42, size=512),
'https://images.evetech.net/characters/42/portrait?size=512'
)
self.assertEqual(
_eve_entity_image_url('character', 42, size=1024),
'https://images.evetech.net/characters/42/portrait?size=1024'
)
with self.assertRaises(ValueError):
_eve_entity_image_url('corporation', 42, size=-5)
with self.assertRaises(ValueError):
_eve_entity_image_url('corporation', 42, size=0)
with self.assertRaises(ValueError):
_eve_entity_image_url('corporation', 42, size=31)
with self.assertRaises(ValueError):
_eve_entity_image_url('corporation', 42, size=1025)
with self.assertRaises(ValueError):
_eve_entity_image_url('corporation', 42, size=2048)
def test_variant(self):
self.assertEqual(
_eve_entity_image_url('character', 42, variant='portrait'),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
_eve_entity_image_url('alliance', 42, variant='logo'),
'https://images.evetech.net/alliances/42/logo?size=32'
)
with self.assertRaises(ValueError):
_eve_entity_image_url('character', 42, variant='logo')
def test_alliance(self):
self.assertEqual(
_eve_entity_image_url('alliance', 42),
'https://images.evetech.net/alliances/42/logo?size=32'
)
self.assertEqual(
_eve_entity_image_url('corporation', 42),
'https://images.evetech.net/corporations/42/logo?size=32'
)
self.assertEqual(
_eve_entity_image_url('character', 42),
'https://images.evetech.net/characters/42/portrait?size=32'
)
with self.assertRaises(ValueError):
_eve_entity_image_url('station', 42)
def test_tenants(self):
self.assertEqual(
_eve_entity_image_url('character', 42, tenant='tranquility'),
'https://images.evetech.net/characters/42/portrait?size=32&tenant=tranquility'
)
self.assertEqual(
_eve_entity_image_url('character', 42, tenant='singularity'),
'https://images.evetech.net/characters/42/portrait?size=32&tenant=singularity'
)
with self.assertRaises(ValueError):
_eve_entity_image_url('character', 42, tenant='xxx')
class EveCharacterTestCase(TestCase): class EveCharacterTestCase(TestCase):
def test_corporation_prop(self): def test_corporation_prop(self):
""" """
Test that the correct corporation is returned by the corporation property Test that the correct corporation is returned by the corporation property
""" """
character = EveCharacter.objects.create( character = EveCharacter.objects.create(
character_id='1234', character_id=1234,
character_name='character.name', character_name='character.name',
corporation_id='2345', corporation_id=2345,
corporation_name='character.corp.name', corporation_name='character.corp.name',
corporation_ticker='character.corp.ticker', corporation_ticker='cc1',
alliance_id='character.alliance.id', alliance_id=12345,
alliance_name='character.alliance.name', alliance_name='character.alliance.name',
) )
expected = EveCorporationInfo.objects.create( expected = EveCorporationInfo.objects.create(
corporation_id='2345', corporation_id=2345,
corporation_name='corp.name', corporation_name='corp.name',
corporation_ticker='corp.ticker', corporation_ticker='cc1',
member_count=10, member_count=10,
alliance=None, alliance=None,
) )
incorrect = EveCorporationInfo.objects.create( incorrect = EveCorporationInfo.objects.create(
corporation_id='9999', corporation_id=9999,
corporation_name='corp.name1', corporation_name='corp.name1',
corporation_ticker='corp.ticker1', corporation_ticker='cc11',
member_count=10, member_count=10,
alliance=None, alliance=None,
) )
@@ -139,44 +49,44 @@ class EveCharacterTestCase(TestCase):
object is not in the database object is not in the database
""" """
character = EveCharacter.objects.create( character = EveCharacter.objects.create(
character_id='1234', character_id=1234,
character_name='character.name', character_name='character.name',
corporation_id='2345', corporation_id=2345,
corporation_name='character.corp.name', corporation_name='character.corp.name',
corporation_ticker='character.corp.ticker', corporation_ticker='cc1',
alliance_id='character.alliance.id', alliance_id=123456,
alliance_name='character.alliance.name', alliance_name='character.alliance.name',
) )
with self.assertRaises(EveCorporationInfo.DoesNotExist): with self.assertRaises(EveCorporationInfo.DoesNotExist):
result = character.corporation character.corporation
def test_alliance_prop(self): def test_alliance_prop(self):
""" """
Test that the correct alliance is returned by the alliance property Test that the correct alliance is returned by the alliance property
""" """
character = EveCharacter.objects.create( character = EveCharacter.objects.create(
character_id='1234', character_id=1234,
character_name='character.name', character_name='character.name',
corporation_id='2345', corporation_id=2345,
corporation_name='character.corp.name', corporation_name='character.corp.name',
corporation_ticker='character.corp.ticker', corporation_ticker='cc1',
alliance_id='3456', alliance_id=3456,
alliance_name='character.alliance.name', alliance_name='character.alliance.name',
) )
expected = EveAllianceInfo.objects.create( expected = EveAllianceInfo.objects.create(
alliance_id='3456', alliance_id=3456,
alliance_name='alliance.name', alliance_name='alliance.name',
alliance_ticker='alliance.ticker', alliance_ticker='ac2',
executor_corp_id='alliance.executor_corp_id', executor_corp_id=2345,
) )
incorrect = EveAllianceInfo.objects.create( incorrect = EveAllianceInfo.objects.create(
alliance_id='9001', alliance_id=9001,
alliance_name='alliance.name1', alliance_name='alliance.name1',
alliance_ticker='alliance.ticker1', alliance_ticker='ac1',
executor_corp_id='alliance.executor_corp_id1', executor_corp_id=2654,
) )
self.assertEqual(character.alliance, expected) self.assertEqual(character.alliance, expected)
@@ -188,28 +98,28 @@ class EveCharacterTestCase(TestCase):
object is not in the database object is not in the database
""" """
character = EveCharacter.objects.create( character = EveCharacter.objects.create(
character_id='1234', character_id=1234,
character_name='character.name', character_name='character.name',
corporation_id='2345', corporation_id=2345,
corporation_name='character.corp.name', corporation_name='character.corp.name',
corporation_ticker='character.corp.ticker', corporation_ticker='cc1',
alliance_id='3456', alliance_id=3456,
alliance_name='character.alliance.name', alliance_name='character.alliance.name',
) )
with self.assertRaises(EveAllianceInfo.DoesNotExist): with self.assertRaises(EveAllianceInfo.DoesNotExist):
result = character.alliance character.alliance
def test_alliance_prop_none(self): def test_alliance_prop_none(self):
""" """
Check that None is returned when the character has no alliance Check that None is returned when the character has no alliance
""" """
character = EveCharacter.objects.create( character = EveCharacter.objects.create(
character_id='1234', character_id=1234,
character_name='character.name', character_name='character.name',
corporation_id='2345', corporation_id=2345,
corporation_name='character.corp.name', corporation_name='character.corp.name',
corporation_ticker='character.corp.ticker', corporation_ticker='cc1',
alliance_id=None, alliance_id=None,
alliance_name=None, alliance_name=None,
) )
@@ -227,12 +137,12 @@ class EveCharacterTestCase(TestCase):
) )
my_character = EveCharacter.objects.create( my_character = EveCharacter.objects.create(
character_id='1001', character_id=1001,
character_name='Bruce Wayne', character_name='Bruce Wayne',
corporation_id='2001', corporation_id=2001,
corporation_name='Dummy Corp 1', corporation_name='Dummy Corp 1',
corporation_ticker='DC1', corporation_ticker='DC1',
alliance_id='3001', alliance_id=3001,
alliance_name='Dummy Alliance 1', alliance_name='Dummy Alliance 1',
) )
my_updated_character = Character( my_updated_character = Character(
@@ -244,90 +154,87 @@ class EveCharacterTestCase(TestCase):
# todo: add test cases not yet covered, e.g. with alliance # todo: add test cases not yet covered, e.g. with alliance
def test_image_url(self): def test_image_url(self):
self.assertEqual( self.assertEqual(
EveCharacter.generic_portrait_url(42), EveCharacter.generic_portrait_url(42),
_eve_entity_image_url('character', 42) eveimageserver._eve_entity_image_url('character', 42)
) )
self.assertEqual( self.assertEqual(
EveCharacter.generic_portrait_url(42, 256), EveCharacter.generic_portrait_url(42, 256),
_eve_entity_image_url('character', 42, 256) eveimageserver._eve_entity_image_url('character', 42, 256)
) )
def test_portrait_urls(self): def test_portrait_urls(self):
x = EveCharacter( x = EveCharacter(
character_id='42', character_id=42,
character_name='character.name', character_name='character.name',
corporation_id='123', corporation_id=123,
corporation_name='corporation.name', corporation_name='corporation.name',
corporation_ticker='ABC', corporation_ticker='ABC',
) )
self.assertEqual( self.assertEqual(
x.portrait_url(), x.portrait_url(),
_eve_entity_image_url('character', 42) eveimageserver._eve_entity_image_url('character', 42)
) )
self.assertEqual( self.assertEqual(
x.portrait_url(64), x.portrait_url(64),
_eve_entity_image_url('character', 42, size=64) eveimageserver._eve_entity_image_url('character', 42, size=64)
) )
self.assertEqual( self.assertEqual(
x.portrait_url_32, x.portrait_url_32,
_eve_entity_image_url('character', 42, size=32) eveimageserver._eve_entity_image_url('character', 42, size=32)
) )
self.assertEqual( self.assertEqual(
x.portrait_url_64, x.portrait_url_64,
_eve_entity_image_url('character', 42, size=64) eveimageserver._eve_entity_image_url('character', 42, size=64)
) )
self.assertEqual( self.assertEqual(
x.portrait_url_128, x.portrait_url_128,
_eve_entity_image_url('character', 42, size=128) eveimageserver._eve_entity_image_url('character', 42, size=128)
) )
self.assertEqual( self.assertEqual(
x.portrait_url_256, x.portrait_url_256,
_eve_entity_image_url('character', 42, size=256) eveimageserver._eve_entity_image_url('character', 42, size=256)
) )
def test_corporation_logo_urls(self): def test_corporation_logo_urls(self):
x = EveCharacter( x = EveCharacter(
character_id='42', character_id=42,
character_name='character.name', character_name='character.name',
corporation_id='123', corporation_id=123,
corporation_name='corporation.name', corporation_name='corporation.name',
corporation_ticker='ABC', corporation_ticker='ABC',
) )
self.assertEqual( self.assertEqual(
x.corporation_logo_url(), x.corporation_logo_url(),
_eve_entity_image_url('corporation', 123) eveimageserver._eve_entity_image_url('corporation', 123)
) )
self.assertEqual( self.assertEqual(
x.corporation_logo_url(256), x.corporation_logo_url(256),
_eve_entity_image_url('corporation', 123, size=256) eveimageserver._eve_entity_image_url('corporation', 123, size=256)
) )
self.assertEqual( self.assertEqual(
x.corporation_logo_url_32, x.corporation_logo_url_32,
_eve_entity_image_url('corporation', 123, size=32) eveimageserver._eve_entity_image_url('corporation', 123, size=32)
) )
self.assertEqual( self.assertEqual(
x.corporation_logo_url_64, x.corporation_logo_url_64,
_eve_entity_image_url('corporation', 123, size=64) eveimageserver._eve_entity_image_url('corporation', 123, size=64)
) )
self.assertEqual( self.assertEqual(
x.corporation_logo_url_128, x.corporation_logo_url_128,
_eve_entity_image_url('corporation', 123, size=128) eveimageserver._eve_entity_image_url('corporation', 123, size=128)
) )
self.assertEqual( self.assertEqual(
x.corporation_logo_url_256, x.corporation_logo_url_256,
_eve_entity_image_url('corporation', 123, size=256) eveimageserver._eve_entity_image_url('corporation', 123, size=256)
) )
def test_alliance_logo_urls(self): def test_alliance_logo_urls(self):
x = EveCharacter( x = EveCharacter(
character_id='42', character_id=42,
character_name='character.name', character_name='character.name',
corporation_id='123', corporation_id=123,
corporation_name='corporation.name', corporation_name='corporation.name',
corporation_ticker='ABC', corporation_ticker='ABC',
) )
@@ -354,27 +261,27 @@ class EveCharacterTestCase(TestCase):
x.alliance_id = 987 x.alliance_id = 987
self.assertEqual( self.assertEqual(
x.alliance_logo_url(), x.alliance_logo_url(),
_eve_entity_image_url('alliance', 987) eveimageserver._eve_entity_image_url('alliance', 987)
) )
self.assertEqual( self.assertEqual(
x.alliance_logo_url(128), x.alliance_logo_url(128),
_eve_entity_image_url('alliance', 987, size=128) eveimageserver._eve_entity_image_url('alliance', 987, size=128)
) )
self.assertEqual( self.assertEqual(
x.alliance_logo_url_32, x.alliance_logo_url_32,
_eve_entity_image_url('alliance', 987, size=32) eveimageserver._eve_entity_image_url('alliance', 987, size=32)
) )
self.assertEqual( self.assertEqual(
x.alliance_logo_url_64, x.alliance_logo_url_64,
_eve_entity_image_url('alliance', 987, size=64) eveimageserver._eve_entity_image_url('alliance', 987, size=64)
) )
self.assertEqual( self.assertEqual(
x.alliance_logo_url_128, x.alliance_logo_url_128,
_eve_entity_image_url('alliance', 987, size=128) eveimageserver._eve_entity_image_url('alliance', 987, size=128)
) )
self.assertEqual( self.assertEqual(
x.alliance_logo_url_256, x.alliance_logo_url_256,
_eve_entity_image_url('alliance', 987, size=256) eveimageserver._eve_entity_image_url('alliance', 987, size=256)
) )
@@ -456,7 +363,6 @@ class EveAllianceTestCase(TestCase):
# potential bug # potential bug
# update_alliance() is only updateting executor_corp_id when object is given # update_alliance() is only updateting executor_corp_id when object is given
def test_update_alliance_wo_object(self): def test_update_alliance_wo_object(self):
mock_EveAllianceProviderManager = Mock() mock_EveAllianceProviderManager = Mock()
mock_EveAllianceProviderManager.get_alliance.return_value = \ mock_EveAllianceProviderManager.get_alliance.return_value = \
@@ -475,11 +381,11 @@ class EveAllianceTestCase(TestCase):
) )
my_alliance.provider = mock_EveAllianceProviderManager my_alliance.provider = mock_EveAllianceProviderManager
my_alliance.save() my_alliance.save()
updated_alliance = Alliance( Alliance(
name='Dummy Alliance 2', name='Dummy Alliance 2',
corp_ids=[2004], corp_ids=[2004],
executor_corp_id=2004 executor_corp_id=2004
) )
my_alliance.update_alliance() my_alliance.update_alliance()
my_alliance.refresh_from_db() my_alliance.refresh_from_db()
self.assertEqual(int(my_alliance.executor_corp_id), 2004) self.assertEqual(int(my_alliance.executor_corp_id), 2004)
@@ -487,23 +393,22 @@ class EveAllianceTestCase(TestCase):
# potential bug # potential bug
# update_alliance() is only updateting executor_corp_id nothing else ??? # update_alliance() is only updateting executor_corp_id nothing else ???
def test_image_url(self): def test_image_url(self):
self.assertEqual( self.assertEqual(
EveAllianceInfo.generic_logo_url(42), EveAllianceInfo.generic_logo_url(42),
_eve_entity_image_url('alliance', 42) eveimageserver._eve_entity_image_url('alliance', 42)
) )
self.assertEqual( self.assertEqual(
EveAllianceInfo.generic_logo_url(42, 256), EveAllianceInfo.generic_logo_url(42, 256),
_eve_entity_image_url('alliance', 42, 256) eveimageserver._eve_entity_image_url('alliance', 42, 256)
) )
def test_logo_url(self): def test_logo_url(self):
x = EveAllianceInfo( x = EveAllianceInfo(
alliance_id='42', alliance_id=42,
alliance_name='alliance.name', alliance_name='alliance.name',
alliance_ticker='ABC', alliance_ticker='ABC',
executor_corp_id='123' executor_corp_id=123
) )
self.assertEqual( self.assertEqual(
x.logo_url(), x.logo_url(),
@@ -563,9 +468,7 @@ class EveCorporationTestCase(TestCase):
def test_update_corporation_no_object_w_alliance(self): def test_update_corporation_no_object_w_alliance(self):
mock_provider = Mock() mock_provider = Mock()
mock_provider.get_corporation.return_value = Corporation( mock_provider.get_corporation.return_value = Corporation(members=87)
members=87
)
self.my_corp.provider = mock_provider self.my_corp.provider = mock_provider
self.my_corp.update_corporation() self.my_corp.update_corporation()
@@ -585,15 +488,14 @@ class EveCorporationTestCase(TestCase):
self.assertEqual(my_corp2.member_count, 8) self.assertEqual(my_corp2.member_count, 8)
self.assertIsNone(my_corp2.alliance) self.assertIsNone(my_corp2.alliance)
def test_image_url(self): def test_image_url(self):
self.assertEqual( self.assertEqual(
EveCorporationInfo.generic_logo_url(42), EveCorporationInfo.generic_logo_url(42),
_eve_entity_image_url('corporation', 42) eveimageserver._eve_entity_image_url('corporation', 42)
) )
self.assertEqual( self.assertEqual(
EveCorporationInfo.generic_logo_url(42, 256), EveCorporationInfo.generic_logo_url(42, 256),
_eve_entity_image_url('corporation', 42, 256) eveimageserver._eve_entity_image_url('corporation', 42, 256)
) )
def test_logo_url(self): def test_logo_url(self):
@@ -621,4 +523,3 @@ class EveCorporationTestCase(TestCase):
self.my_corp.logo_url_256, self.my_corp.logo_url_256,
'https://images.evetech.net/corporations/2001/logo?size=256' 'https://images.evetech.net/corporations/2001/logo?size=256'
) )

View File

@@ -1,21 +1,28 @@
import os import os
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from bravado.exception import HTTPNotFound, HTTPUnprocessableEntity from bravado.exception import HTTPNotFound
from jsonschema.exceptions import RefResolutionError from jsonschema.exceptions import RefResolutionError
from django.test import TestCase from django.test import TestCase
from . import set_logger from . import set_logger
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo from ..providers import (
from ..providers import ObjectNotFound, Entity, Character, Corporation, \ ObjectNotFound,
Alliance, ItemType, EveProvider, EveSwaggerProvider Entity,
Character,
Corporation,
Alliance,
ItemType,
EveProvider,
EveSwaggerProvider
)
MODULE_PATH = 'allianceauth.eveonline.providers' MODULE_PATH = 'allianceauth.eveonline.providers'
SWAGGER_OLD_SPEC_PATH = os.path.join(os.path.dirname( SWAGGER_OLD_SPEC_PATH = os.path.join(os.path.dirname(
os.path.abspath(__file__)), 'swagger_old.json' os.path.abspath(__file__)), 'swagger_old.json'
) )
set_logger(MODULE_PATH, __file__) set_logger(MODULE_PATH, __file__)
@@ -51,7 +58,6 @@ class TestEntity(TestCase):
x = Entity() x = Entity()
self.assertEqual(repr(x), '<Entity (None): None>') self.assertEqual(repr(x), '<Entity (None): None>')
def test_bool(self): def test_bool(self):
x = Entity(1001) x = Entity(1001)
self.assertTrue(bool(x)) self.assertTrue(bool(x))
@@ -99,7 +105,6 @@ class TestCorporation(TestCase):
# should fetch alliance once only # should fetch alliance once only
self.assertEqual(mock_provider_get_alliance.call_count, 1) self.assertEqual(mock_provider_get_alliance.call_count, 1)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance') @patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
def test_alliance_not_defined(self, mock_provider_get_alliance): def test_alliance_not_defined(self, mock_provider_get_alliance):
mock_provider_get_alliance.return_value = None mock_provider_get_alliance.return_value = None
@@ -110,7 +115,6 @@ class TestCorporation(TestCase):
Entity(None, None) Entity(None, None)
) )
@patch(MODULE_PATH + '.EveSwaggerProvider.get_character') @patch(MODULE_PATH + '.EveSwaggerProvider.get_character')
def test_ceo(self, mock_provider_get_character): def test_ceo(self, mock_provider_get_character):
my_ceo = Character( my_ceo = Character(
@@ -200,7 +204,6 @@ class TestAlliance(TestCase):
# should be called once by used corp only # should be called once by used corp only
self.assertEqual(mock_provider_get_corp.call_count, 2) self.assertEqual(mock_provider_get_corp.call_count, 2)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_corp') @patch(MODULE_PATH + '.EveSwaggerProvider.get_corp')
def test_corps(self, mock_provider_get_corp): def test_corps(self, mock_provider_get_corp):
mock_provider_get_corp.side_effect = TestAlliance._get_corp mock_provider_get_corp.side_effect = TestAlliance._get_corp
@@ -253,7 +256,6 @@ class TestCharacter(TestCase):
# should call the provider one time only # should call the provider one time only
self.assertEqual(mock_provider_get_corp.call_count, 1) self.assertEqual(mock_provider_get_corp.call_count, 1)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance') @patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
@patch(MODULE_PATH + '.EveSwaggerProvider.get_corp') @patch(MODULE_PATH + '.EveSwaggerProvider.get_corp')
@@ -283,7 +285,6 @@ class TestCharacter(TestCase):
self.assertEqual(mock_provider_get_corp.call_count, 1) self.assertEqual(mock_provider_get_corp.call_count, 1)
self.assertEqual(mock_provider_get_alliance.call_count, 1) self.assertEqual(mock_provider_get_alliance.call_count, 1)
def test_alliance_has_none(self): def test_alliance_has_none(self):
self.my_character.alliance_id = None self.my_character.alliance_id = None
self.assertEqual(self.my_character.alliance, Entity(None, None)) self.assertEqual(self.my_character.alliance, Entity(None, None))
@@ -343,7 +344,6 @@ class TestEveSwaggerProvider(TestCase):
else: else:
raise HTTPNotFound(Mock()) raise HTTPNotFound(Mock())
@staticmethod @staticmethod
def esi_get_alliances_alliance_id_corporations(alliance_id): def esi_get_alliances_alliance_id_corporations(alliance_id):
alliances = { alliances = {
@@ -357,7 +357,6 @@ class TestEveSwaggerProvider(TestCase):
else: else:
raise HTTPNotFound(Mock()) raise HTTPNotFound(Mock())
@staticmethod @staticmethod
def esi_get_corporations_corporation_id(corporation_id): def esi_get_corporations_corporation_id(corporation_id):
corporations = { corporations = {
@@ -382,7 +381,6 @@ class TestEveSwaggerProvider(TestCase):
else: else:
raise HTTPNotFound(Mock()) raise HTTPNotFound(Mock())
@staticmethod @staticmethod
def esi_get_characters_character_id(character_id): def esi_get_characters_character_id(character_id):
characters = { characters = {
@@ -403,7 +401,6 @@ class TestEveSwaggerProvider(TestCase):
else: else:
raise HTTPNotFound(Mock()) raise HTTPNotFound(Mock())
@staticmethod @staticmethod
def esi_post_characters_affiliation(characters): def esi_post_characters_affiliation(characters):
character_data = { character_data = {
@@ -428,7 +425,6 @@ class TestEveSwaggerProvider(TestCase):
else: else:
raise TypeError() raise TypeError()
@staticmethod @staticmethod
def esi_get_universe_types_type_id(type_id): def esi_get_universe_types_type_id(type_id):
types = { types = {
@@ -446,13 +442,11 @@ class TestEveSwaggerProvider(TestCase):
else: else:
raise HTTPNotFound(Mock()) raise HTTPNotFound(Mock())
@patch(MODULE_PATH + '.esi_client_factory') @patch(MODULE_PATH + '.esi_client_factory')
def test_str(self, mock_esi_client_factory): def test_str(self, mock_esi_client_factory):
my_provider = EveSwaggerProvider() my_provider = EveSwaggerProvider()
self.assertEqual(str(my_provider), 'esi') self.assertEqual(str(my_provider), 'esi')
@patch(MODULE_PATH + '.esi_client_factory') @patch(MODULE_PATH + '.esi_client_factory')
def test_get_alliance(self, mock_esi_client_factory): def test_get_alliance(self, mock_esi_client_factory):
mock_esi_client_factory.return_value\ mock_esi_client_factory.return_value\
@@ -481,7 +475,6 @@ class TestEveSwaggerProvider(TestCase):
with self.assertRaises(ObjectNotFound): with self.assertRaises(ObjectNotFound):
my_provider.get_alliance(3999) my_provider.get_alliance(3999)
@patch(MODULE_PATH + '.esi_client_factory') @patch(MODULE_PATH + '.esi_client_factory')
def test_get_corp(self, mock_esi_client_factory): def test_get_corp(self, mock_esi_client_factory):
mock_esi_client_factory.return_value\ mock_esi_client_factory.return_value\
@@ -508,7 +501,6 @@ class TestEveSwaggerProvider(TestCase):
with self.assertRaises(ObjectNotFound): with self.assertRaises(ObjectNotFound):
my_provider.get_corp(2999) my_provider.get_corp(2999)
@patch(MODULE_PATH + '.esi_client_factory') @patch(MODULE_PATH + '.esi_client_factory')
def test_get_character(self, mock_esi_client_factory): def test_get_character(self, mock_esi_client_factory):
mock_esi_client_factory.return_value\ mock_esi_client_factory.return_value\
@@ -536,7 +528,6 @@ class TestEveSwaggerProvider(TestCase):
with self.assertRaises(ObjectNotFound): with self.assertRaises(ObjectNotFound):
my_provider.get_character(1999) my_provider.get_character(1999)
@patch(MODULE_PATH + '.esi_client_factory') @patch(MODULE_PATH + '.esi_client_factory')
def test_get_itemtype(self, mock_esi_client_factory): def test_get_itemtype(self, mock_esi_client_factory):
mock_esi_client_factory.return_value\ mock_esi_client_factory.return_value\
@@ -601,5 +592,3 @@ 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')

View File

@@ -3,8 +3,12 @@ from unittest.mock import patch, Mock
from django.test import TestCase from django.test import TestCase
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from ..tasks import update_alliance, update_corp, update_character, \ from ..tasks import (
update_alliance,
update_corp,
update_character,
run_model_update run_model_update
)
class TestTasks(TestCase): class TestTasks(TestCase):
@@ -13,98 +17,229 @@ class TestTasks(TestCase):
def test_update_corp(self, mock_EveCorporationInfo): def test_update_corp(self, mock_EveCorporationInfo):
update_corp(42) update_corp(42)
self.assertEqual( self.assertEqual(
mock_EveCorporationInfo.objects.update_corporation.call_count, mock_EveCorporationInfo.objects.update_corporation.call_count, 1
1
) )
self.assertEqual( self.assertEqual(
mock_EveCorporationInfo.objects.update_corporation.call_args[0][0], mock_EveCorporationInfo.objects.update_corporation.call_args[0][0], 42
42
) )
@patch('allianceauth.eveonline.tasks.EveAllianceInfo') @patch('allianceauth.eveonline.tasks.EveAllianceInfo')
def test_update_alliance(self, mock_EveAllianceInfo): def test_update_alliance(self, mock_EveAllianceInfo):
update_alliance(42) update_alliance(42)
self.assertEqual( self.assertEqual(
mock_EveAllianceInfo.objects.update_alliance.call_args[0][0], mock_EveAllianceInfo.objects.update_alliance.call_args[0][0], 42
42
) )
self.assertEqual( self.assertEqual(
mock_EveAllianceInfo.objects\ mock_EveAllianceInfo.objects
.update_alliance.return_value.populate_alliance.call_count, .update_alliance.return_value.populate_alliance.call_count, 1
1
) )
@patch('allianceauth.eveonline.tasks.EveCharacter') @patch('allianceauth.eveonline.tasks.EveCharacter')
def test_update_character(self, mock_EveCharacter): def test_update_character(self, mock_EveCharacter):
update_character(42) update_character(42)
self.assertEqual( self.assertEqual(
mock_EveCharacter.objects.update_character.call_count, mock_EveCharacter.objects.update_character.call_count, 1
1
) )
self.assertEqual( self.assertEqual(
mock_EveCharacter.objects.update_character.call_args[0][0], mock_EveCharacter.objects.update_character.call_args[0][0], 42
42
) )
@patch('allianceauth.eveonline.tasks.update_character') @patch('allianceauth.eveonline.tasks.update_character')
@patch('allianceauth.eveonline.tasks.update_alliance') @patch('allianceauth.eveonline.tasks.update_alliance')
@patch('allianceauth.eveonline.tasks.update_corp') @patch('allianceauth.eveonline.tasks.update_corp')
def test_run_model_update( @patch('allianceauth.eveonline.providers.provider')
self, @patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2)
mock_update_corp, class TestRunModelUpdate(TestCase):
mock_update_alliance,
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()
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='alliance.executor_corp_id', executor_corp_id=5,
)
EveCharacter.objects.create(
character_id=1,
character_name='character.name1',
corporation_id=2345,
corporation_name='character.corp.name',
corporation_ticker='c.c.t', # max 5 chars
alliance_id=None
) )
EveCharacter.objects.create( EveCharacter.objects.create(
character_id='1234', character_id=2,
character_name='character.name', character_name='character.name2',
corporation_id='character.corp.id', corporation_id=9876,
corporation_name='character.corp.name', corporation_name='character.corp.name',
corporation_ticker='character.corp.ticker', corporation_ticker='c.c.t', # max 5 chars
alliance_id='character.alliance.id', 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_update_corp.delay.call_count, 1)
self.assertEqual(
int(mock_update_corp.delay.call_args[0][0]),
2345
)
self.assertEqual(mock_update_alliance.delay.call_count, 1)
self.assertEqual( self.assertEqual(
int(mock_update_alliance.delay.call_args[0][0]), mock_provider.client.Character.post_characters_affiliation.call_count, 2
3456 )
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(
int(mock_update_corp.apply_async.call_args[1]['args'][0]), 2345
)
self.assertEqual(mock_update_alliance.apply_async.call_count, 1)
self.assertEqual(
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)
def test_ignore_character_not_in_affiliations(
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.affiliations[0]
mock_provider.client.Character.post_characters_affiliation.side_effect \
= get_affiliations
mock_provider.client.Universe.post_universe_names.side_effect = get_names
self.assertEqual(mock_update_character.delay.call_count, 1) run_model_update()
self.assertEqual( characters_updated = {
int(mock_update_character.delay.call_args[0][0]), x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list
1234 }
) 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

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

File diff suppressed because one or more lines are too long

View File

@@ -15,7 +15,13 @@
</div> </div>
{% endif %} {% endif %}
</h1> </h1>
<h2>{% blocktrans %}{{ user }} has collected {{ n_fats }} link{{ n_fats|pluralize }} this month.{% endblocktrans %}</h2> <h2>
{% blocktrans count links=n_fats trimmed %}
{{ user }} has collected one link this month.
{% plural %}
{{ user }} has collected {{ links }} links this month.
{% endblocktrans %}
</h2>
<table class="table table-responsive"> <table class="table table-responsive">
<tr> <tr>
<th class="col-md-2 text-center">{% trans "Ship" %}</th> <th class="col-md-2 text-center">{% trans "Ship" %}</th>
@@ -29,7 +35,13 @@
{% endfor %} {% endfor %}
</table> </table>
{% if created_fats %} {% if created_fats %}
<h2>{% blocktrans %}{{ user }} has created {{ n_created_fats }} link{{ n_created_fats|pluralize }} this month.{% endblocktrans %}</h2> <h2>
{% blocktrans count links=n_created_fats trimmed %}
{{ user }} has created one link this month.
{% plural %}
{{ user }} has created {{ links }} links this month.
{% endblocktrans %}
</h2>
{% if created_fats %} {% if created_fats %}
<table class="table"> <table class="table">
<tr> <tr>

View File

@@ -1,7 +1,7 @@
from django.conf import settings from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.models import Group as BaseGroup from django.contrib.auth.models import Group as BaseGroup, User
from django.db.models import Count from django.db.models import Count
from django.db.models.functions import Lower from django.db.models.functions import Lower
from django.db.models.signals import pre_save, post_save, pre_delete, \ from django.db.models.signals import pre_save, post_save, pre_delete, \
@@ -13,8 +13,7 @@ from .models import GroupRequest
from . import signals from . import signals
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS: if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True _has_auto_groups = True
from allianceauth.eveonline.autogroups.models import *
else: else:
_has_auto_groups = False _has_auto_groups = False
@@ -22,19 +21,24 @@ else:
class AuthGroupInlineAdmin(admin.StackedInline): class AuthGroupInlineAdmin(admin.StackedInline):
model = AuthGroup model = AuthGroup
filter_horizontal = ('group_leaders', 'group_leader_groups', 'states',) filter_horizontal = ('group_leaders', 'group_leader_groups', 'states',)
fields = ('description', 'group_leaders', 'group_leader_groups', 'states', 'internal', 'hidden', 'open', 'public') fields = (
'description',
'group_leaders',
'group_leader_groups',
'states', 'internal',
'hidden',
'open',
'public'
)
verbose_name_plural = 'Auth Settings' verbose_name_plural = 'Auth Settings'
verbose_name = '' verbose_name = ''
def formfield_for_manytomany(self, db_field, request, **kwargs): def formfield_for_manytomany(self, db_field, request, **kwargs):
"""overriding this formfield to have sorted lists in the form""" """overriding this formfield to have sorted lists in the form"""
if db_field.name == "group_leaders": if db_field.name == "group_leaders":
kwargs["queryset"] = User.objects\ kwargs["queryset"] = User.objects.order_by(Lower('username'))
.filter(profile__state__name='Member')\
.order_by(Lower('username'))
elif db_field.name == "group_leader_groups": elif db_field.name == "group_leader_groups":
kwargs["queryset"] = Group.objects\ kwargs["queryset"] = Group.objects.order_by(Lower('name'))
.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): def has_add_permission(self, request):
@@ -103,14 +107,16 @@ class GroupAdmin(admin.ModelAdmin):
'_member_count', '_member_count',
'has_leader' 'has_leader'
) )
list_filter = ( list_filter = [
'authgroup__internal', 'authgroup__internal',
'authgroup__hidden', 'authgroup__hidden',
'authgroup__open', 'authgroup__open',
'authgroup__public', 'authgroup__public',
IsAutoGroupFilter, ]
HasLeaderFilter if _has_auto_groups:
) list_filter.append(IsAutoGroupFilter)
list_filter.append(HasLeaderFilter)
search_fields = ('name', 'authgroup__description') search_fields = ('name', 'authgroup__description')
def get_queryset(self, request): def get_queryset(self, request):

View File

@@ -1,5 +0,0 @@
from allianceauth.groupmanagement.managers import GroupManager
def can_manage_groups(request):
return {'can_manage_groups': GroupManager.can_manage_groups(request.user)}

View File

@@ -1,27 +1,53 @@
from django.contrib.auth.models import Group import logging
from django.db.models import Q
from django.contrib.auth.models import Group, User
from django.db.models import Q, QuerySet
from allianceauth.authentication.models import State
logger = logging.getLogger(__name__)
class GroupManager: class GroupManager:
def __init__(self):
pass @classmethod
def get_joinable_groups_for_user(
cls, user: User, include_hidden = True
) -> QuerySet:
"""get groups a user could join incl. groups already joined"""
groups_qs = cls.get_joinable_groups(user.profile.state)
if not user.has_perm('groupmanagement.request_groups'):
groups_qs = groups_qs.filter(authgroup__public=True)
if not include_hidden:
groups_qs = groups_qs.filter(authgroup__hidden=False)
return groups_qs
@staticmethod @staticmethod
def get_joinable_groups(state): def get_joinable_groups(state: State) -> QuerySet:
return Group.objects.select_related('authgroup').exclude(authgroup__internal=True)\ """get groups that can be joined by user with given state"""
return Group.objects\
.select_related('authgroup')\
.exclude(authgroup__internal=True)\
.filter(Q(authgroup__states=state) | Q(authgroup__states=None)) .filter(Q(authgroup__states=state) | Q(authgroup__states=None))
@staticmethod @staticmethod
def get_all_non_internal_groups(): def get_all_non_internal_groups() -> QuerySet:
return Group.objects.select_related('authgroup').exclude(authgroup__internal=True) """get groups that are not internal"""
return Group.objects\
.select_related('authgroup')\
.exclude(authgroup__internal=True)
@staticmethod @staticmethod
def get_group_leaders_groups(user): def get_group_leaders_groups(user: User):
return Group.objects.select_related('authgroup').filter(authgroup__group_leaders__in=[user]) | \ return Group.objects.select_related('authgroup').filter(authgroup__group_leaders__in=[user]) | \
Group.objects.select_related('authgroup').filter(authgroup__group_leader_groups__in=user.groups.all()) Group.objects.select_related('authgroup').filter(authgroup__group_leader_groups__in=user.groups.all())
@staticmethod @staticmethod
def joinable_group(group, state): def joinable_group(group: Group, state: State) -> bool:
""" """
Check if a group is a user/state joinable group, i.e. Check if a group is a user/state joinable group, i.e.
not an internal group for Corp, Alliance, Members etc, not an internal group for Corp, Alliance, Members etc,
@@ -30,12 +56,15 @@ class GroupManager:
:param state: allianceauth.authentication.State object :param state: allianceauth.authentication.State object
:return: bool True if its joinable, False otherwise :return: bool True if its joinable, False otherwise
""" """
if len(group.authgroup.states.all()) != 0 and state not in group.authgroup.states.all(): if (len(group.authgroup.states.all()) != 0
and state not in group.authgroup.states.all()
):
return False return False
return not group.authgroup.internal else:
return not group.authgroup.internal
@staticmethod @staticmethod
def check_internal_group(group): def check_internal_group(group: Group) -> bool:
""" """
Check if a group is auditable, i.e not an internal group Check if a group is auditable, i.e not an internal group
:param group: django.contrib.auth.models.Group object :param group: django.contrib.auth.models.Group object
@@ -44,20 +73,11 @@ class GroupManager:
return not group.authgroup.internal return not group.authgroup.internal
@staticmethod @staticmethod
def check_internal_group(group): def has_management_permission(user: User) -> bool:
"""
Check if a group is auditable, i.e not an internal group
:param group: django.contrib.auth.models.Group object
:return: bool True if it is auditable, false otherwise
"""
return not group.authgroup.internal
@staticmethod
def has_management_permission(user):
return user.has_perm('auth.group_management') return user.has_perm('auth.group_management')
@classmethod @classmethod
def can_manage_groups(cls, user): def can_manage_groups(cls, user:User ) -> bool:
""" """
For use with user_passes_test decorator. For use with user_passes_test decorator.
Check if the user can manage groups. Either has the Check if the user can manage groups. Either has the
@@ -71,7 +91,7 @@ class GroupManager:
return False return False
@classmethod @classmethod
def can_manage_group(cls, user, group): def can_manage_group(cls, user: User, group: Group) -> bool:
""" """
Check user has permission to manage the given group Check user has permission to manage the given group
:param user: User object to test permission of :param user: User object to test permission of
@@ -79,5 +99,5 @@ class GroupManager:
:return: True if the user can manage the group :return: True if the user can manage the group
""" """
if user.is_authenticated: if user.is_authenticated:
return cls.has_management_permission(user) or user.leads_groups.filter(group=group).exists() return cls.has_management_permission(user) or cls.get_group_leaders_groups(user).filter(pk=group.pk).exists()
return False return False

View File

@@ -15,7 +15,7 @@
<div class="panel-body"> <div class="panel-body">
<p> <p>
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button"> <a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
Back {% trans "Back" %}
</a> </a>
</p> </p>
{% if entries %} {% if entries %}
@@ -33,7 +33,7 @@
<tbody> <tbody>
{% for entry in entries %} {% for entry in entries %}
<tr> <tr>
<td class="text-center">{{ entry.date }}</td> <td class="text-center">{{ entry.date|date:"Y-M-d, H:i" }}</td>
<td class="text-center">{{ entry.requestor }}</td> <td class="text-center">{{ entry.requestor }}</td>
<td class="text-center">{{ entry.req_char }}</td> <td class="text-center">{{ entry.req_char }}</td>
<td class="text-center">{{ entry.req_char.corporation_name }}</td> <td class="text-center">{{ entry.req_char.corporation_name }}</td>
@@ -66,6 +66,7 @@
{% block extra_javascript %} {% block extra_javascript %}
{% include 'bundles/datatables-js.html' %} {% include 'bundles/datatables-js.html' %}
{% include 'bundles/moment-js.html' %}
<script type="text/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script> <script type="text/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
{% endblock %} {% endblock %}
@@ -74,7 +75,26 @@
{% endblock %} {% endblock %}
{% block extra_script %} {% block extra_script %}
$.fn.dataTable.moment = function ( format, locale ) {
var types = $.fn.dataTable.ext.type;
// Add type detection
types.detect.unshift( function ( d ) {
return moment( d, format, locale, true ).isValid() ?
'moment-'+format :
null;
} );
// Add sorting method - use an integer for the sorting
types.order[ 'moment-'+format+'-pre' ] = function ( d ) {
return moment( d, format, locale, true ).unix();
};
};
$(document).ready(function(){ $(document).ready(function(){
$.fn.dataTable.moment( 'YYYY-MMM-D, HH:mm' );
$('#log-entries').DataTable({ $('#log-entries').DataTable({
order: [[ 0, 'desc' ], [ 1, 'asc' ] ], order: [[ 0, 'desc' ], [ 1, 'asc' ] ],
filterDropDown: filterDropDown:

View File

@@ -17,7 +17,7 @@
<div class="panel-body"> <div class="panel-body">
<p> <p>
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button"> <a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
Back {% trans "Back" %}
</a> </a>
</p> </p>
{% if group.user_set %} {% if group.user_set %}
@@ -36,7 +36,7 @@
<tr> <tr>
<td class="text-right"> <td class="text-right">
{% if member.is_leader %} {% if member.is_leader %}
<i class="fa fa-star"></i>&nbsp; <i class="fas fa-star"></i>&nbsp;
{% endif %} {% endif %}
<img src="{{ member.main_char|character_portrait_url:32 }}" class="img-circle"> <img src="{{ member.main_char|character_portrait_url:32 }}" class="img-circle">
</td> </td>
@@ -69,7 +69,7 @@
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<p class="text-muted"><i class="fa fa-star"></i>: Group leader</p> <p class="text-muted"><i class="fas fa-star"></i>: Group leader</p>
</div> </div>
{% else %} {% else %}
<div class="alert alert-warning text-center"> <div class="alert alert-warning text-center">

View File

@@ -11,7 +11,7 @@
{% include 'groupmanagement/menu.html' %} {% include 'groupmanagement/menu.html' %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
Groups {% trans "Groups" %}
</div> </div>
<div class="panel-body"> <div class="panel-body">
{% if groups %} {% if groups %}
@@ -53,6 +53,10 @@
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% trans "Audit Members" %}"> <a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% trans "Audit Members" %}">
<i class="glyphicon glyphicon-list-alt"></i> <i class="glyphicon glyphicon-list-alt"></i>
</a> </a>
<a id="clipboard-copy" data-clipboard-text="{{ request.scheme }}://{{request.get_host}}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% trans "Copy Direct Join Link" %}">
<i class="glyphicon glyphicon-copy"></i>
</a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@@ -68,3 +72,9 @@
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}
{% block extra_javascript %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.4/clipboard.min.js"></script>
<script>
new ClipboardJS('#clipboard-copy');
</script>
{% endblock %}

View File

@@ -20,8 +20,22 @@
{% include 'groupmanagement/menu.html' %} {% include 'groupmanagement/menu.html' %}
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="active"><a data-toggle="tab" href="#add">{% trans "Join Requests" %}</a></li> <li class="active">
<li><a data-toggle="tab" href="#leave">{% trans "Leave Requests" %}</a></li> <a data-toggle="tab" href="#add">
{% trans "Join Requests" %}
{% if acceptrequests %}
<span class="badge">{{ acceptrequests|length }}</span>
{% endif %}
</a>
</li>
<li>
<a data-toggle="tab" href="#leave">
{% trans "Leave Requests" %}
{% if leaverequests %}
<span class="badge">{{ leaverequests|length }}</span>
{% endif %}
</a>
</li>
</ul> </ul>
<div class="tab-content"> <div class="tab-content">

View File

@@ -0,0 +1,15 @@
from django import template
from django.contrib.auth.models import User
from allianceauth.groupmanagement.managers import GroupManager
register = template.Library()
@register.filter
def can_manage_groups(user: User) -> bool:
"""returns True if the given user can manage groups. Returns False otherwise."""
if not isinstance(user, User):
return False
return GroupManager.can_manage_groups(user)

View File

@@ -0,0 +1,11 @@
from django.urls import reverse
def get_admin_change_view_url(obj: object) -> str:
return reverse(
'admin:{}_{}_change'.format(
obj._meta.app_label,
type(obj).__name__.lower()
),
args=(obj.pk,)
)

View File

@@ -1,22 +1,25 @@
from unittest.mock import patch from unittest.mock import patch
from django.test import TestCase, RequestFactory from django.conf import settings
from django.contrib import admin 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 from django.contrib.auth.models import User
from django.test import TestCase, RequestFactory, Client
from allianceauth.authentication.models import CharacterOwnership, State from allianceauth.authentication.models import CharacterOwnership, State
from allianceauth.eveonline.autogroups.models import AutogroupsConfig
from allianceauth.eveonline.models import ( from allianceauth.eveonline.models import (
EveCharacter, EveCorporationInfo, EveAllianceInfo EveCharacter, EveCorporationInfo, EveAllianceInfo
) )
from ..admin import ( from ..admin import HasLeaderFilter, GroupAdmin, Group
IsAutoGroupFilter, from . import get_admin_change_view_url
HasLeaderFilter,
GroupAdmin, if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
Group _has_auto_groups = True
) from allianceauth.eveonline.autogroups.models import AutogroupsConfig
from ..admin import IsAutoGroupFilter
else:
_has_auto_groups = False
MODULE_PATH = 'allianceauth.groupmanagement.admin' MODULE_PATH = 'allianceauth.groupmanagement.admin'
@@ -81,33 +84,33 @@ class TestGroupAdmin(TestCase):
# user 1 - corp and alliance, normal user # user 1 - corp and alliance, normal user
cls.character_1 = EveCharacter.objects.create( cls.character_1 = EveCharacter.objects.create(
character_id='1001', character_id=1001,
character_name='Bruce Wayne', character_name='Bruce Wayne',
corporation_id='2001', corporation_id=2001,
corporation_name='Wayne Technologies', corporation_name='Wayne Technologies',
corporation_ticker='WT', corporation_ticker='WT',
alliance_id='3001', alliance_id=3001,
alliance_name='Wayne Enterprises', alliance_name='Wayne Enterprises',
alliance_ticker='WE', alliance_ticker='WE',
) )
cls.character_1a = EveCharacter.objects.create( cls.character_1a = EveCharacter.objects.create(
character_id='1002', character_id=1002,
character_name='Batman', character_name='Batman',
corporation_id='2001', corporation_id=2001,
corporation_name='Wayne Technologies', corporation_name='Wayne Technologies',
corporation_ticker='WT', corporation_ticker='WT',
alliance_id='3001', alliance_id=3001,
alliance_name='Wayne Enterprises', alliance_name='Wayne Enterprises',
alliance_ticker='WE', alliance_ticker='WE',
) )
alliance = EveAllianceInfo.objects.create( alliance = EveAllianceInfo.objects.create(
alliance_id='3001', alliance_id=3001,
alliance_name='Wayne Enterprises', alliance_name='Wayne Enterprises',
alliance_ticker='WE', alliance_ticker='WE',
executor_corp_id='2001' executor_corp_id=2001
) )
EveCorporationInfo.objects.create( EveCorporationInfo.objects.create(
corporation_id='2001', corporation_id=2001,
corporation_name='Wayne Technologies', corporation_name='Wayne Technologies',
corporation_ticker='WT', corporation_ticker='WT',
member_count=42, member_count=42,
@@ -182,10 +185,10 @@ class TestGroupAdmin(TestCase):
alliance=None alliance=None
) )
EveAllianceInfo.objects.create( EveAllianceInfo.objects.create(
alliance_id='3101', alliance_id=3101,
alliance_name='Lex World Domination', alliance_name='Lex World Domination',
alliance_ticker='LWD', alliance_ticker='LWD',
executor_corp_id='' executor_corp_id=2101
) )
cls.user_3 = User.objects.create_user( cls.user_3 = User.objects.create_user(
cls.character_3.character_name.replace(' ', '_'), cls.character_3.character_name.replace(' ', '_'),
@@ -210,14 +213,15 @@ class TestGroupAdmin(TestCase):
def _create_autogroups(self): def _create_autogroups(self):
"""create autogroups for corps and alliances""" """create autogroups for corps and alliances"""
autogroups_config = AutogroupsConfig( if _has_auto_groups:
corp_groups = True, autogroups_config = AutogroupsConfig(
alliance_groups = True corp_groups=True,
) alliance_groups=True
autogroups_config.save() )
for state in State.objects.all(): autogroups_config.save()
autogroups_config.states.add(state) for state in State.objects.all():
autogroups_config.update_corp_group_membership(self.user_1) autogroups_config.states.add(state)
autogroups_config.update_corp_group_membership(self.user_1)
# column rendering # column rendering
@@ -267,70 +271,72 @@ class TestGroupAdmin(TestCase):
result = self.modeladmin._properties(self.group_6) result = self.modeladmin._properties(self.group_6)
self.assertListEqual(result, expected) self.assertListEqual(result, expected)
@patch(MODULE_PATH + '._has_auto_groups', True) if _has_auto_groups:
def test_properties_6(self): @patch(MODULE_PATH + '._has_auto_groups', True)
self._create_autogroups() def test_properties_7(self):
expected = ['Auto Group'] self._create_autogroups()
my_group = Group.objects\ expected = ['Auto Group']
.filter(managedcorpgroup__isnull=False)\ my_group = Group.objects\
.first() .filter(managedcorpgroup__isnull=False)\
result = self.modeladmin._properties(my_group) .first()
self.assertListEqual(result, expected) result = self.modeladmin._properties(my_group)
self.assertListEqual(result, expected)
# actions # actions
# filters # filters
@patch(MODULE_PATH + '._has_auto_groups', True) if _has_auto_groups:
def test_filter_is_auto_group(self): @patch(MODULE_PATH + '._has_auto_groups', True)
def test_filter_is_auto_group(self):
class GroupAdminTest(admin.ModelAdmin):
list_filter = (IsAutoGroupFilter,) class GroupAdminTest(admin.ModelAdmin):
list_filter = (IsAutoGroupFilter,)
self._create_autogroups()
my_modeladmin = GroupAdminTest(Group, AdminSite()) self._create_autogroups()
my_modeladmin = GroupAdminTest(Group, AdminSite())
# Make sure the lookups are correct # Make sure the lookups are correct
request = self.factory.get('/') request = self.factory.get('/')
request.user = self.user_1 request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request) changelist = my_modeladmin.get_changelist_instance(request)
filters = changelist.get_filters(request) filters = changelist.get_filters(request)
filterspec = filters[0][0] filterspec = filters[0][0]
expected = [ expected = [
('yes', 'Yes'), ('yes', 'Yes'),
('no', 'No'), ('no', 'No'),
] ]
self.assertEqual(filterspec.lookup_choices, expected) self.assertEqual(filterspec.lookup_choices, expected)
# Make sure the correct queryset is returned - no # Make sure the correct queryset is returned - no
request = self.factory.get( request = self.factory.get(
'/', {'is_auto_group__exact': 'no'} '/', {'is_auto_group__exact': 'no'}
) )
request.user = self.user_1 request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request) changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request) queryset = changelist.get_queryset(request)
expected = [ expected = [
self.group_1, self.group_1,
self.group_2, self.group_2,
self.group_3, self.group_3,
self.group_4, self.group_4,
self.group_5, self.group_5,
self.group_6 self.group_6
] ]
self.assertSetEqual(set(queryset), set(expected)) self.assertSetEqual(set(queryset), set(expected))
# Make sure the correct queryset is returned - yes # Make sure the correct queryset is returned - yes
request = self.factory.get( request = self.factory.get(
'/', {'is_auto_group__exact': 'yes'} '/', {'is_auto_group__exact': 'yes'}
) )
request.user = self.user_1 request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request) changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request) queryset = changelist.get_queryset(request)
expected = Group.objects.exclude( expected = Group.objects.exclude(
managedalliancegroup__isnull=True, managedalliancegroup__isnull=True,
managedcorpgroup__isnull=True managedcorpgroup__isnull=True
) )
self.assertSetEqual(set(queryset), set(expected)) self.assertSetEqual(set(queryset), set(expected))
def test_filter_has_leader(self): def test_filter_has_leader(self):
@@ -376,4 +382,12 @@ class TestGroupAdmin(TestCase):
self.group_3 self.group_3
] ]
self.assertSetEqual(set(queryset), set(expected)) self.assertSetEqual(set(queryset), set(expected))
def test_change_view_loads_normally(self):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
response = c.get(get_admin_change_view_url(self.group_1))
self.assertEqual(response.status_code, 200)

View File

@@ -1,92 +0,0 @@
from unittest import mock
from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo, EveCharacter
from django.contrib.auth.models import User, Group
from allianceauth.groupmanagement.managers import GroupManager
from allianceauth.groupmanagement.signals import check_groups_on_state_change
class GroupManagementVisibilityTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('test')
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST')
cls.user.profile.refresh_from_db()
cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2')
cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1)
cls.group1 = Group.objects.create(name='group1')
cls.group2 = Group.objects.create(name='group2')
cls.group3 = Group.objects.create(name='group3')
def setUp(self):
self.user.refresh_from_db()
def _refresh_user(self):
self.user = User.objects.get(pk=self.user.pk)
def test_can_manage_group(self):
self.group1.authgroup.group_leaders.add(self.user)
self.group2.authgroup.group_leader_groups.add(self.group1)
self._refresh_user()
groups = GroupManager.get_group_leaders_groups(self.user)
self.assertIn(self.group1, groups) #avail due to user
self.assertNotIn(self.group2, groups) #not avail due to group
self.assertNotIn(self.group3, groups) #not avail at all
self.user.groups.add(self.group1)
self._refresh_user()
groups = GroupManager.get_group_leaders_groups(self.user)
self.assertIn(self.group1, groups) #avail due to user
self.assertIn(self.group2, groups) #avail due to group1
self.assertNotIn(self.group3, groups) #not avail at all
class GroupManagementStateTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('test')
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST')
cls.user.profile.refresh_from_db()
cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2')
cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1)
cls.state_group = Group.objects.create(name='state_group')
cls.open_group = Group.objects.create(name='open_group')
cls.state = AuthUtils.create_state('test state', 500)
cls.state_group.authgroup.states.add(cls.state)
cls.state_group.authgroup.internal = False
cls.state_group.save()
def setUp(self):
self.user.refresh_from_db()
self.state.refresh_from_db()
def _refresh_user(self):
self.user = User.objects.get(pk=self.user.pk)
def _refresh_test_group(self):
self.state_group = Group.objects.get(pk=self.state_group.pk)
def test_drop_state_group(self):
self.user.groups.add(self.open_group)
self.user.groups.add(self.state_group)
self.assertEqual(self.user.profile.state.name, "Guest")
self.state.member_corporations.add(self.corp)
self._refresh_user()
self.assertEqual(self.user.profile.state, self.state)
groups = self.user.groups.all()
self.assertIn(self.state_group, groups) #keeps group
self.assertIn(self.open_group, groups) #public group unafected
self.state.member_corporations.clear()
self._refresh_user()
self.assertEqual(self.user.profile.state.name, "Guest")
groups = self.user.groups.all()
self.assertNotIn(self.state_group, groups) #looses group
self.assertIn(self.open_group, groups) #public group unafected

View File

@@ -0,0 +1,337 @@
from unittest.mock import Mock, patch
from django.contrib.auth.models import Group, User
from django.test import TestCase
from django.urls import reverse
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
from allianceauth.tests.auth_utils import AuthUtils
from ..models import AuthGroup
from ..managers import GroupManager
class MockUserNotAuthenticated():
def __init__(self):
self.is_authenticated = False
class GroupManagementVisibilityTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('test')
AuthUtils.add_main_character(
cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST'
)
cls.user.profile.refresh_from_db()
cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2')
cls.corp = EveCorporationInfo.objects.create(
corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1
)
cls.group1 = Group.objects.create(name='group1')
cls.group2 = Group.objects.create(name='group2')
cls.group3 = Group.objects.create(name='group3')
def setUp(self):
self.user.refresh_from_db()
def _refresh_user(self):
self.user = User.objects.get(pk=self.user.pk)
def test_get_group_leaders_groups(self):
self.group1.authgroup.group_leaders.add(self.user)
self.group2.authgroup.group_leader_groups.add(self.group1)
self._refresh_user()
groups = GroupManager.get_group_leaders_groups(self.user)
self.assertIn(self.group1, groups) #avail due to user
self.assertNotIn(self.group2, groups) #not avail due to group
self.assertNotIn(self.group3, groups) #not avail at all
self.user.groups.add(self.group1)
self._refresh_user()
groups = GroupManager.get_group_leaders_groups(self.user)
def test_can_manage_group(self):
self.group1.authgroup.group_leaders.add(self.user)
self.user.groups.add(self.group1)
self._refresh_user()
self.assertTrue(GroupManager.can_manage_group(self.user, self.group1))
self.assertFalse(GroupManager.can_manage_group(self.user, self.group2))
self.assertFalse(GroupManager.can_manage_group(self.user, self.group3))
self.group2.authgroup.group_leader_groups.add(self.group1)
self.group1.authgroup.group_leaders.remove(self.user)
self._refresh_user()
self.assertFalse(GroupManager.can_manage_group(self.user, self.group1))
self.assertTrue(GroupManager.can_manage_group(self.user, self.group2))
self.assertFalse(GroupManager.can_manage_group(self.user, self.group3))
class TestGroupManager(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
# group 1
cls.group_default = Group.objects.create(name='default')
cls.group_default.authgroup.description = 'Default Group'
cls.group_default.authgroup.internal = False
cls.group_default.authgroup.hidden = False
cls.group_default.authgroup.save()
# group 2
cls.group_internal = Group.objects.create(name='internal')
cls.group_internal.authgroup.description = 'Internal Group'
cls.group_internal.authgroup.internal = True
cls.group_internal.authgroup.save()
# group 3
cls.group_hidden = Group.objects.create(name='hidden')
cls.group_hidden.authgroup.description = 'Hidden Group'
cls.group_hidden.authgroup.internal = False
cls.group_hidden.authgroup.hidden = True
cls.group_hidden.authgroup.save()
# group 4
cls.group_open = Group.objects.create(name='open')
cls.group_open.authgroup.description = 'Open Group'
cls.group_open.authgroup.internal = False
cls.group_open.authgroup.hidden = False
cls.group_open.authgroup.open = True
cls.group_open.authgroup.save()
# group 5
cls.group_public_1 = Group.objects.create(name='public 1')
cls.group_public_1.authgroup.description = 'Public Group 1'
cls.group_public_1.authgroup.internal = False
cls.group_public_1.authgroup.hidden = False
cls.group_public_1.authgroup.public = True
cls.group_public_1.authgroup.save()
# group 6
cls.group_public_2 = Group.objects.create(name='public 2')
cls.group_public_2.authgroup.description = 'Public Group 2'
cls.group_public_2.authgroup.internal = False
cls.group_public_2.authgroup.hidden = True
cls.group_public_2.authgroup.open = True
cls.group_public_2.authgroup.public = True
cls.group_public_2.authgroup.save()
# group 7
cls.group_default_member = Group.objects.create(name='default members')
cls.group_default_member.authgroup.description = \
'Default Group for members only'
cls.group_default_member.authgroup.internal = False
cls.group_default_member.authgroup.hidden = False
cls.group_default_member.authgroup.open = False
cls.group_default_member.authgroup.public = False
cls.group_default_member.authgroup.states.add(
AuthUtils.get_member_state()
)
cls.group_default_member.authgroup.save()
def setUp(self):
self.user = AuthUtils.create_user('Bruce Wayne')
def test_get_joinable_group_member(self):
result = GroupManager.get_joinable_groups(
AuthUtils.get_member_state()
)
expected = {
self.group_default,
self.group_hidden,
self.group_open,
self.group_public_1,
self.group_public_2,
self.group_default_member
}
self.assertSetEqual(set(result), expected)
def test_get_joinable_group_guest(self):
result = GroupManager.get_joinable_groups(
AuthUtils.get_guest_state()
)
expected = {
self.group_default,
self.group_hidden,
self.group_open,
self.group_public_1,
self.group_public_2
}
self.assertSetEqual(set(result), expected)
def test_joinable_group_member(self):
member_state = AuthUtils.get_member_state()
for x in [
self.group_default,
self.group_hidden,
self.group_open,
self.group_public_1,
self.group_public_2,
self.group_default_member
]:
self.assertTrue(GroupManager.joinable_group(x, member_state))
for x in [
self.group_internal,
]:
self.assertFalse(GroupManager.joinable_group(x, member_state))
def test_joinable_group_guest(self):
guest_state = AuthUtils.get_guest_state()
for x in [
self.group_default,
self.group_hidden,
self.group_open,
self.group_public_1,
self.group_public_2
]:
self.assertTrue(GroupManager.joinable_group(x, guest_state))
for x in [
self.group_internal,
self.group_default_member
]:
self.assertFalse(GroupManager.joinable_group(x, guest_state))
def test_get_all_non_internal_groups(self):
result = GroupManager.get_all_non_internal_groups()
expected = {
self.group_default,
self.group_hidden,
self.group_open,
self.group_public_1,
self.group_public_2,
self.group_default_member
}
self.assertSetEqual(set(result), expected)
def test_check_internal_group(self):
self.assertTrue(
GroupManager.check_internal_group(self.group_default)
)
self.assertFalse(
GroupManager.check_internal_group(self.group_internal)
)
def test_get_joinable_groups_for_user_no_permission(self):
AuthUtils.assign_state(self.user, AuthUtils.get_guest_state())
result = GroupManager.get_joinable_groups_for_user(self.user)
expected= {self.group_public_1, self.group_public_2}
self.assertSetEqual(set(result), expected)
def test_get_joinable_groups_for_user_guest_w_permission_(self):
AuthUtils.assign_state(self.user, AuthUtils.get_guest_state())
AuthUtils.add_permission_to_user_by_name(
'groupmanagement.request_groups', self.user
)
result = GroupManager.get_joinable_groups_for_user(self.user)
expected = {
self.group_default,
self.group_hidden,
self.group_open,
self.group_public_1,
self.group_public_2
}
self.assertSetEqual(set(result), expected)
def test_get_joinable_groups_for_user_member_w_permission(self):
AuthUtils.assign_state(self.user, AuthUtils.get_member_state(), True)
AuthUtils.add_permission_to_user_by_name(
'groupmanagement.request_groups', self.user
)
result = GroupManager.get_joinable_groups_for_user(self.user)
expected = {
self.group_default,
self.group_hidden,
self.group_open,
self.group_public_1,
self.group_public_2,
self.group_default_member
}
self.assertSetEqual(set(result), expected)
def test_get_joinable_groups_for_user_member_w_permission_no_hidden(self):
AuthUtils.assign_state(self.user, AuthUtils.get_member_state(), True)
AuthUtils.add_permission_to_user_by_name(
'groupmanagement.request_groups', self.user
)
result = GroupManager.get_joinable_groups_for_user(
self.user, include_hidden=False
)
expected = {
self.group_default,
self.group_open,
self.group_public_1,
self.group_default_member
}
self.assertSetEqual(set(result), expected)
def test_has_management_permission(self):
user = AuthUtils.create_user('Clark Kent')
AuthUtils.add_permission_to_user_by_name(
'auth.group_management', user
)
self.assertTrue(GroupManager.has_management_permission(user))
def test_can_manage_groups_no_perm_no_group(self):
user = AuthUtils.create_user('Clark Kent')
self.assertFalse(GroupManager.can_manage_groups(user))
def test_can_manage_groups_user_not_authenticated(self):
user = MockUserNotAuthenticated()
self.assertFalse(GroupManager.can_manage_groups(user))
def test_can_manage_groups_has_perm(self):
user = AuthUtils.create_user('Clark Kent')
AuthUtils.add_permission_to_user_by_name(
'auth.group_management', user
)
self.assertTrue(GroupManager.can_manage_groups(user))
def test_can_manage_groups_no_perm_leads_group(self):
user = AuthUtils.create_user('Clark Kent')
self.group_default.authgroup.group_leaders.add(user)
self.assertTrue(GroupManager.can_manage_groups(user))
def test_can_manage_group_no_perm_no_group(self):
user = AuthUtils.create_user('Clark Kent')
self.assertFalse(
GroupManager.can_manage_group(user, self.group_default)
)
def test_can_manage_group_has_perm(self):
user = AuthUtils.create_user('Clark Kent')
AuthUtils.add_permission_to_user_by_name(
'auth.group_management', user
)
self.assertTrue(
GroupManager.can_manage_group(user, self.group_default)
)
def test_can_manage_group_no_perm_leads_correct_group(self):
user = AuthUtils.create_user('Clark Kent')
self.group_default.authgroup.group_leaders.add(user)
self.assertTrue(
GroupManager.can_manage_group(user, self.group_default)
)
def test_can_manage_group_no_perm_leads_other_group(self):
user = AuthUtils.create_user('Clark Kent')
self.group_hidden.authgroup.group_leaders.add(user)
self.assertFalse(
GroupManager.can_manage_group(user, self.group_default)
)
def test_can_manage_group_user_not_authenticated(self):
user = MockUserNotAuthenticated()
self.assertFalse(
GroupManager.can_manage_group(user, self.group_default)
)

View File

@@ -0,0 +1,167 @@
from unittest import mock
from django.contrib.auth.models import User, Group
from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils
from allianceauth.eveonline.models import (
EveCorporationInfo, EveAllianceInfo, EveCharacter
)
from ..models import GroupRequest, RequestLog
def create_testdata():
# clear DB
User.objects.all().delete()
Group.objects.all().delete()
EveCharacter.objects.all().delete()
EveCorporationInfo.objects.all().delete()
EveAllianceInfo.objects.all().delete()
# group 1
group = Group.objects.create(name='Superheros')
group.authgroup.description = 'Default Group'
group.authgroup.internal = False
group.authgroup.hidden = False
group.authgroup.save()
# user 1
user_1 = AuthUtils.create_user('Bruce Wayne')
AuthUtils.add_main_character_2(
user_1,
name='Bruce Wayne',
character_id=1001,
corp_id=2001,
corp_name='Wayne Technologies'
)
user_1.groups.add(group)
group.authgroup.group_leaders.add(user_1)
# user 2
user_2 = AuthUtils.create_user('Clark Kent')
AuthUtils.add_main_character_2(
user_2,
name='Clark Kent',
character_id=1002,
corp_id=2002,
corp_name='Wayne Technologies'
)
return group, user_1, user_2
class TestGroupRequest(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.group, cls.user_1, _ = create_testdata()
def test_main_char(self):
group_request = GroupRequest.objects.create(
status='Pending',
user=self.user_1,
group=self.group
)
expected = self.user_1.profile.main_character
self.assertEqual(group_request.main_char, expected)
def test_str(self):
group_request = GroupRequest.objects.create(
status='Pending',
user=self.user_1,
group=self.group
)
expected = 'Bruce Wayne:Superheros'
self.assertEqual(str(group_request), expected)
class TestRequestLog(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.group, cls.user_1, cls.user_2 = create_testdata()
def test_requestor(self):
request_log = RequestLog.objects.create(
group=self.group,
request_info='Clark Kent:Superheros',
request_actor=self.user_1
)
expected = 'Clark Kent'
self.assertEqual(request_log.requestor(), expected)
def test_type_to_str_removed(self):
request_log = RequestLog.objects.create(
request_type=None,
group=self.group,
request_info='Clark Kent:Superheros',
request_actor=self.user_1
)
expected = 'Removed'
self.assertEqual(request_log.type_to_str(), expected)
def test_type_to_str_leave(self):
request_log = RequestLog.objects.create(
request_type=True,
group=self.group,
request_info='Clark Kent:Superheros',
request_actor=self.user_1
)
expected = 'Leave'
self.assertEqual(request_log.type_to_str(), expected)
def test_type_to_str_join(self):
request_log = RequestLog.objects.create(
request_type=False,
group=self.group,
request_info='Clark Kent:Superheros',
request_actor=self.user_1
)
expected = 'Join'
self.assertEqual(request_log.type_to_str(), expected)
def test_action_to_str_accept(self):
request_log = RequestLog.objects.create(
group=self.group,
request_info='Clark Kent:Superheros',
request_actor=self.user_1,
action = True
)
expected = 'Accept'
self.assertEqual(request_log.action_to_str(), expected)
def test_action_to_str_reject(self):
request_log = RequestLog.objects.create(
group=self.group,
request_info='Clark Kent:Superheros',
request_actor=self.user_1,
action = False
)
expected = 'Reject'
self.assertEqual(request_log.action_to_str(), expected)
def test_req_char(self):
request_log = RequestLog.objects.create(
group=self.group,
request_info='Clark Kent:Superheros',
request_actor=self.user_1,
action = False
)
expected = self.user_2.profile.main_character
self.assertEqual(request_log.req_char(), expected)
class TestAuthGroup(TestCase):
def test_str(self):
group = Group.objects.create(name='Superheros')
group.authgroup.description = 'Default Group'
group.authgroup.internal = False
group.authgroup.hidden = False
group.authgroup.save()
expected = 'Superheros'
self.assertEqual(str(group.authgroup), expected)

View File

@@ -0,0 +1,61 @@
from unittest import mock
from django.test import TestCase
from django.contrib.auth.models import User, Group
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
from allianceauth.tests.auth_utils import AuthUtils
from ..signals import check_groups_on_state_change
class GroupManagementStateTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('test')
AuthUtils.add_main_character(
cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST'
)
cls.user.profile.refresh_from_db()
cls.alliance = EveAllianceInfo.objects.create(
alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2'
)
cls.corp = EveCorporationInfo.objects.create(
corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1
)
cls.state_group = Group.objects.create(name='state_group')
cls.open_group = Group.objects.create(name='open_group')
cls.state = AuthUtils.create_state('test state', 500)
cls.state_group.authgroup.states.add(cls.state)
cls.state_group.authgroup.internal = False
cls.state_group.save()
def setUp(self):
self.user.refresh_from_db()
self.state.refresh_from_db()
def _refresh_user(self):
self.user = User.objects.get(pk=self.user.pk)
def _refresh_test_group(self):
self.state_group = Group.objects.get(pk=self.state_group.pk)
def test_drop_state_group(self):
self.user.groups.add(self.open_group)
self.user.groups.add(self.state_group)
self.assertEqual(self.user.profile.state.name, "Guest")
self.state.member_corporations.add(self.corp)
self._refresh_user()
self.assertEqual(self.user.profile.state, self.state)
groups = self.user.groups.all()
self.assertIn(self.state_group, groups) #keeps group
self.assertIn(self.open_group, groups) #public group unafected
self.state.member_corporations.clear()
self._refresh_user()
self.assertEqual(self.user.profile.state.name, "Guest")
groups = self.user.groups.all()
self.assertNotIn(self.state_group, groups) #looses group
self.assertIn(self.open_group, groups) #public group unafected

View File

@@ -0,0 +1,27 @@
from unittest.mock import patch
from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils
from ..templatetags.groupmanagement import can_manage_groups
MODULE_PATH = 'allianceauth.groupmanagement.templatetags.groupmanagement'
@patch(MODULE_PATH + '.GroupManager.can_manage_groups')
class TestCanManageGroups(TestCase):
def setUp(self):
self.user = AuthUtils.create_user('Bruce Wayne')
def test_return_normal_result(self, mock_can_manage_groups):
mock_can_manage_groups.return_value = True
self.assertTrue(can_manage_groups(self.user))
self.assertTrue(mock_can_manage_groups.called)
def test_return_false_if_not_user(self, mock_can_manage_groups):
mock_can_manage_groups.return_value = True
self.assertFalse(can_manage_groups('invalid'))
self.assertFalse(mock_can_manage_groups.called)

View File

@@ -0,0 +1,22 @@
from unittest.mock import Mock, patch
from django.test import RequestFactory, TestCase
from django.urls import reverse
from allianceauth.tests.auth_utils import AuthUtils
from esi.models import Token
from .. import views
class TestViews(TestCase):
def setUp(self):
self.factory = RequestFactory()
self.user = AuthUtils.create_user('Bruce Wayne')
def test_groups_view_can_load(self):
request = self.factory.get(reverse('groupmanagement:groups'))
request.user = self.user
response = views.groups_view(request)
self.assertEqual(response.status_code, 200)

View File

@@ -1,21 +1,21 @@
import logging import logging
from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import user_passes_test from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.core.paginator import Paginator, EmptyPage
from django.db.models import Count from django.db.models import Count
from django.http import Http404 from django.http import Http404
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .managers import GroupManager
from .models import GroupRequest, RequestLog
from allianceauth.notifications import notify from allianceauth.notifications import notify
from django.conf import settings from .managers import GroupManager
from .models import GroupRequest, RequestLog
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -27,13 +27,14 @@ def group_management(request):
acceptrequests = [] acceptrequests = []
leaverequests = [] leaverequests = []
base_group_query = GroupRequest.objects.select_related('user', 'group') base_group_query = GroupRequest.objects.select_related('user', 'group', 'user__profile__main_character')
if GroupManager.has_management_permission(request.user): if GroupManager.has_management_permission(request.user):
# Full access # Full access
group_requests = base_group_query.all() group_requests = base_group_query.all()
else: else:
# Group specific leader # Group specific leader
group_requests = base_group_query.filter(group__authgroup__group_leaders__in=[request.user]) users__groups = GroupManager.get_group_leaders_groups(request.user)
group_requests = base_group_query.filter(group__in=users__groups)
for grouprequest in group_requests: for grouprequest in group_requests:
if grouprequest.leave_request: if grouprequest.leave_request:
@@ -74,7 +75,6 @@ def group_membership_audit(request, group_id):
logger.debug("group_management_audit called by user %s" % request.user) logger.debug("group_management_audit called by user %s" % request.user)
group = get_object_or_404(Group, id=group_id) group = get_object_or_404(Group, id=group_id)
try: try:
# Check its a joinable group i.e. not corp or internal # Check its a joinable group i.e. not corp or internal
# And the user has permission to manage it # And the user has permission to manage it
if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group): if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
@@ -91,8 +91,6 @@ def group_membership_audit(request, group_id):
return render(request, 'groupmanagement/audit.html', context=render_items) return render(request, 'groupmanagement/audit.html', context=render_items)
@login_required @login_required
@user_passes_test(GroupManager.can_manage_groups) @user_passes_test(GroupManager.can_manage_groups)
def group_membership_list(request, group_id): def group_membership_list(request, group_id):
@@ -122,7 +120,7 @@ def group_membership_list(request, group_id):
for member in \ for member in \
group.user_set\ group.user_set\
.all()\ .all()\
.select_related('profile')\ .select_related('profile', 'profile__main_character')\
.order_by('profile__main_character__character_name'): .order_by('profile__main_character__character_name'):
members.append({ members.append({
@@ -234,7 +232,7 @@ def group_reject_request(request, group_request_id):
raise p raise p
except: except:
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group}) messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
logger.exception("Unhandled exception occured while user %s attempting to reject group request id %s" % ( logger.exception("Unhandled exception occurred while user %s attempting to reject group request id %s" % (
request.user, group_request_id)) request.user, group_request_id))
pass pass
@@ -268,9 +266,9 @@ def group_leave_accept_request(request, group_request_id):
(request.user, group_request_id)) (request.user, group_request_id))
raise p raise p
except: except:
messages.error(request, _('An unhandled error occured while processing the application from %(mainchar)s to leave %(group)s.') % { messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to leave %(group)s.') % {
"mainchar": group_request.main_char, "group": group_request.group}) "mainchar": group_request.main_char, "group": group_request.group})
logger.exception("Unhandled exception occured while user %s attempting to accept group leave request id %s" % ( logger.exception("Unhandled exception occurred while user %s attempting to accept group leave request id %s" % (
request.user, group_request_id)) request.user, group_request_id))
pass pass
@@ -302,9 +300,9 @@ def group_leave_reject_request(request, group_request_id):
(request.user, group_request_id)) (request.user, group_request_id))
raise p raise p
except: except:
messages.error(request, _('An unhandled error occured while processing the application from %(mainchar)s to leave %(group)s.') % { messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to leave %(group)s.') % {
"mainchar": group_request.main_char, "group": group_request.group}) "mainchar": group_request.main_char, "group": group_request.group})
logger.exception("Unhandled exception occured while user %s attempting to reject group leave request id %s" % ( logger.exception("Unhandled exception occurred while user %s attempting to reject group leave request id %s" % (
request.user, group_request_id)) request.user, group_request_id))
pass pass
@@ -314,24 +312,23 @@ def group_leave_reject_request(request, group_request_id):
@login_required @login_required
def groups_view(request): def groups_view(request):
logger.debug("groups_view called by user %s" % request.user) logger.debug("groups_view called by user %s" % request.user)
groups_qs = GroupManager.get_joinable_groups_for_user(
request.user, include_hidden=False
)
groups_qs = groups_qs.order_by('name')
groups = [] groups = []
for group in groups_qs:
group_request = GroupRequest.objects\
.filter(user=request.user)\
.filter(group=group)
groups.append({
'group': group,
'request': group_request[0] if group_request else None
})
group_query = GroupManager.get_joinable_groups(request.user.profile.state) context = {'groups': groups}
return render(request, 'groupmanagement/groups.html', context=context)
if not request.user.has_perm('groupmanagement.request_groups'):
# Filter down to public groups only for non-members
group_query = group_query.filter(authgroup__public=True)
logger.debug("Not a member, only public groups will be available")
for group in group_query:
# Exclude hidden
if not group.authgroup.hidden:
group_request = GroupRequest.objects.filter(user=request.user).filter(group=group)
groups.append({'group': group, 'request': group_request[0] if group_request else None})
render_items = {'groups': groups}
return render(request, 'groupmanagement/groups.html', context=render_items)
@login_required @login_required
@@ -348,13 +345,13 @@ def group_request_add(request, group_id):
# User is already a member of this group. # User is already a member of this group.
logger.warning("User %s attempted to join group id %s but they are already a member." % logger.warning("User %s attempted to join group id %s but they are already a member." %
(request.user, group_id)) (request.user, group_id))
messages.warning(request, "You are already a member of that group.") messages.warning(request, _("You are already a member of that group."))
return redirect('groupmanagement:groups') return redirect('groupmanagement:groups')
if not request.user.has_perm('groupmanagement.request_groups') and not group.authgroup.public: if not request.user.has_perm('groupmanagement.request_groups') and not group.authgroup.public:
# Does not have the required permission, trying to join a non-public group # Does not have the required permission, trying to join a non-public group
logger.warning("User %s attempted to join group id %s but it is not a public group" % logger.warning("User %s attempted to join group id %s but it is not a public group" %
(request.user, group_id)) (request.user, group_id))
messages.warning(request, "You cannot join that group") messages.warning(request, _("You cannot join that group"))
return redirect('groupmanagement:groups') return redirect('groupmanagement:groups')
if group.authgroup.open: if group.authgroup.open:
logger.info("%s joining %s as is an open group" % (request.user, group)) logger.info("%s joining %s as is an open group" % (request.user, group))
@@ -363,7 +360,7 @@ def group_request_add(request, group_id):
req = GroupRequest.objects.filter(user=request.user, group=group) req = GroupRequest.objects.filter(user=request.user, group=group)
if len(req) > 0: if len(req) > 0:
logger.info("%s attempted to join %s but already has an open application" % (request.user, group)) logger.info("%s attempted to join %s but already has an open application" % (request.user, group))
messages.warning(request, "You already have a pending application for that group.") messages.warning(request, _("You already have a pending application for that group."))
return redirect("groupmanagement:groups") return redirect("groupmanagement:groups")
grouprequest = GroupRequest() grouprequest = GroupRequest()
grouprequest.status = _('Pending') grouprequest.status = _('Pending')
@@ -397,7 +394,7 @@ def group_request_leave(request, group_id):
req = GroupRequest.objects.filter(user=request.user, group=group) req = GroupRequest.objects.filter(user=request.user, group=group)
if len(req) > 0: if len(req) > 0:
logger.info("%s attempted to leave %s but already has an pending leave request." % (request.user, group)) logger.info("%s attempted to leave %s but already has an pending leave request." % (request.user, group))
messages.warning(request, "You already have a pending leave request for that group.") messages.warning(request, _("You already have a pending leave request for that group."))
return redirect("groupmanagement:groups") return redirect("groupmanagement:groups")
if getattr(settings, 'AUTO_LEAVE', False): if getattr(settings, 'AUTO_LEAVE', False):
logger.info("%s leaving joinable group %s due to auto_leave" % (request.user, group)) logger.info("%s leaving joinable group %s due to auto_leave" % (request.user, group))

View File

@@ -8,7 +8,7 @@ class ApplicationsMenu(MenuItemHook):
def __init__(self): def __init__(self):
MenuItemHook.__init__(self, MenuItemHook.__init__(self,
_('Applications'), _('Applications'),
'fa fa-file-o fa-fw', 'far fa-file fa-fw',
'hrapplications:index', 'hrapplications:index',
navactive=['hrapplications:']) navactive=['hrapplications:'])

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,11 @@
from .models import Notification from .models import Notification
from django.core.cache import cache
def user_notification_count(request): def user_notification_count(request):
return {'notifications': len(Notification.objects.filter(user__id=request.user.id).filter(viewed=False))} user_id = request.user.id
notification_count = cache.get("u-note:{}".format(user_id), -1)
if notification_count<0:
notification_count = Notification.objects.filter(user__id=user_id).filter(viewed=False).count()
cache.set("u-note:{}".format(user_id),notification_count,5)
return {'notifications': notification_count}

View File

@@ -1 +0,0 @@
# Create your tests here.

View File

@@ -0,0 +1,76 @@
from unittest import mock
from django.test import TestCase
from allianceauth.notifications.context_processors import user_notification_count
from allianceauth.tests.auth_utils import AuthUtils
from django.core.cache import cache
from allianceauth.notifications.models import Notification
class TestNotificationCount(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('magic_mike')
AuthUtils.add_main_character(cls.user, 'Magic Mike', '1', corp_id='2', corp_name='Pole Riders', corp_ticker='PRIDE', alliance_id='3', alliance_name='RIDERS')
cls.user.profile.refresh_from_db()
### test notifications for mike
Notification.objects.all().delete()
Notification.objects.create(user=cls.user,
level="INFO",
title="Job 1 Failed",
message="Because it was broken",
viewed=True)
Notification.objects.create(user=cls.user,
level="INFO",
title="Job 2 Failed",
message="Because it was broken")
Notification.objects.create(user=cls.user,
level="INFO",
title="Job 3 Failed",
message="Because it was broken")
Notification.objects.create(user=cls.user,
level="INFO",
title="Job 4 Failed",
message="Because it was broken")
Notification.objects.create(user=cls.user,
level="INFO",
title="Job 5 Failed",
message="Because it was broken")
Notification.objects.create(user=cls.user,
level="INFO",
title="Job 6 Failed",
message="Because it was broken")
cls.user2 = AuthUtils.create_user('teh_kid')
AuthUtils.add_main_character(cls.user, 'The Kid', '2', corp_id='2', corp_name='Pole Riders', corp_ticker='PRIDE', alliance_id='3', alliance_name='RIDERS')
cls.user2.profile.refresh_from_db()
# Noitification for kid
Notification.objects.create(user=cls.user2,
level="INFO",
title="Job 6 Failed",
message="Because it was broken")
def test_no_cache(self):
mock_req = mock.MagicMock()
mock_req.user.id = self.user.id
cache.delete("u-note:{}".format(self.user.id)) # force the db to be hit
context_dict = user_notification_count(mock_req)
self.assertIsInstance(context_dict, dict)
self.assertEqual(context_dict.get('notifications'), 5) # 5 only
@mock.patch('allianceauth.notifications.models.Notification.objects')
def test_cache(self, mock_foo):
mock_foo.filter.return_value = mock_foo
mock_foo.count.return_value = 5
mock_req = mock.MagicMock()
mock_req.user.id = self.user.id
cache.set("u-note:{}".format(self.user.id),10,5)
context_dict = user_notification_count(mock_req)
self.assertIsInstance(context_dict, dict)
self.assertEqual(context_dict.get('notifications'), 10) # cached value
self.assertEqual(mock_foo.called, 0) # ensure the DB was not hit

View File

@@ -7,7 +7,7 @@ from . import urls
class OpTimerboardMenu(MenuItemHook): class OpTimerboardMenu(MenuItemHook):
def __init__(self): def __init__(self):
MenuItemHook.__init__(self, _('Fleet Operations'), MenuItemHook.__init__(self, _('Fleet Operations'),
'fa fa-exclamation fa-fw', 'fas fa-exclamation fa-fw',
'optimer:view', 'optimer:view',
navactive=['optimer:']) navactive=['optimer:'])

View File

@@ -8,7 +8,7 @@ class PermissionsTool(MenuItemHook):
def __init__(self): def __init__(self):
MenuItemHook.__init__(self, MenuItemHook.__init__(self,
'Permissions Audit', 'Permissions Audit',
'fa fa-key fa-id-card', 'fas fa-id-card fa-fw',
'permissions_tool:overview', 'permissions_tool:overview',
order=400, order=400,
navactive=['permissions_tool:']) navactive=['permissions_tool:'])

View File

@@ -41,7 +41,7 @@ class PermissionsToolViewsTestCase(WebTest):
response_content = response.content.decode('utf-8') response_content = response.content.decode('utf-8')
self.assertInHTML('<li><a class="active" href="/permissions/overview/">' self.assertInHTML('<li><a class="active" href="/permissions/overview/">'
'<i class="fa fa-key fa-id-card"></i> Permissions Audit</a></li>', response_content) '<i class="fas fa-id-card fa-fw"></i> Permissions Audit</a></li>', response_content)
def test_permissions_overview(self): def test_permissions_overview(self):
self.app.set_user(self.member) self.app.set_user(self.member)

View File

@@ -11,6 +11,15 @@ app = Celery('{{ project_name }}')
# Using a string here means the worker don't have to serialize # Using a string here means the worker don't have to serialize
# the configuration object to child processes. # the configuration object to child processes.
app.config_from_object('django.conf:settings') app.config_from_object('django.conf:settings')
# setup priorities ( 0 Highest, 9 Lowest )
app.conf.broker_transport_options = {
'priority_steps': list(range(10)), # setup que to have 10 steps
'queue_order_strategy': 'priority', # setup que to use prio sorting
}
app.conf.task_default_priority = 5 # anything called with the task.delay() will be given normal priority (5)
app.conf.worker_prefetch_multiplier = 1 # only prefetch single tasks at a time on the workers so that prio tasks happen
app.conf.ONCE = { app.conf.ONCE = {
'backend': 'allianceauth.services.tasks.DjangoBackend', 'backend': 'allianceauth.services.tasks.DjangoBackend',
'settings': {} 'settings': {}

View File

@@ -83,6 +83,9 @@ LANGUAGES = (
('en', ugettext('English')), ('en', ugettext('English')),
('de', ugettext('German')), ('de', ugettext('German')),
('es', ugettext('Spanish')), ('es', ugettext('Spanish')),
('zh-hans', ugettext('Chinese Simplified')),
('ru', ugettext('Russian')),
('ko', ugettext('Korean')),
) )
TEMPLATES = [ TEMPLATES = [
@@ -100,8 +103,7 @@ TEMPLATES = [
'django.template.context_processors.media', 'django.template.context_processors.media',
'django.template.context_processors.static', 'django.template.context_processors.static',
'django.template.context_processors.tz', 'django.template.context_processors.tz',
'allianceauth.notifications.context_processors.user_notification_count', 'allianceauth.notifications.context_processors.user_notification_count',
'allianceauth.groupmanagement.context_processors.can_manage_groups',
'allianceauth.context_processors.auth_settings', 'allianceauth.context_processors.auth_settings',
], ],
}, },
@@ -217,6 +219,14 @@ LOGGING = {
'maxBytes': 1024 * 1024 * 5, # edit this line to change max log file size 'maxBytes': 1024 * 1024 * 5, # edit this line to change max log file size
'backupCount': 5, # edit this line to change number of log backups 'backupCount': 5, # edit this line to change number of log backups
}, },
'extension_file': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(BASE_DIR, 'log/extensions.log'),
'formatter': 'verbose',
'maxBytes': 1024 * 1024 * 5, # edit this line to change max log file size
'backupCount': 5, # edit this line to change number of log backups
},
'console': { 'console': {
'level': 'DEBUG', # edit this line to change logging level to console 'level': 'DEBUG', # edit this line to change logging level to console
'class': 'logging.StreamHandler', 'class': 'logging.StreamHandler',
@@ -233,6 +243,10 @@ LOGGING = {
'handlers': ['log_file', 'console', 'notifications'], 'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG', 'level': 'DEBUG',
}, },
'extensions': {
'handlers': ['extension_file', 'console'],
'level': 'DEBUG',
},
'django': { 'django': {
'handlers': ['log_file', 'console'], 'handlers': ['log_file', 'console'],
'level': 'ERROR', 'level': 'ERROR',

View File

@@ -22,6 +22,10 @@ INSTALLED_APPS += [
] ]
# To change the logging level for extensions, uncomment the following line.
# LOGGING['handlers']['extension_file']['level'] = 'DEBUG'
# Enter credentials to use MySQL/MariaDB. Comment out to use sqlite3 # Enter credentials to use MySQL/MariaDB. Comment out to use sqlite3
DATABASES['default'] = { DATABASES['default'] = {
'ENGINE': 'django.db.backends.mysql', 'ENGINE': 'django.db.backends.mysql',

View File

@@ -4,3 +4,8 @@ from allianceauth import urls
urlpatterns = [ urlpatterns = [
url(r'', include(urls)), url(r'', include(urls)),
] ]
handler500 = 'allianceauth.views.Generic500Redirect'
handler404 = 'allianceauth.views.Generic404Redirect'
handler403 = 'allianceauth.views.Generic403Redirect'
handler400 = 'allianceauth.views.Generic400Redirect'

View File

@@ -17,6 +17,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMix
from django.db import models, IntegrityError from django.db import models, IntegrityError
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.shortcuts import render, Http404, redirect from django.shortcuts import render, Http404, redirect
from django.utils.translation import gettext_lazy as _
from .forms import ServicePasswordModelForm from .forms import ServicePasswordModelForm
@@ -68,7 +69,7 @@ class BaseCreatePasswordServiceAccountView(BaseServiceView, ServiceCredentialsVi
try: try:
svc_obj = self.model.objects.create(user=request.user) svc_obj = self.model.objects.create(user=request.user)
except IntegrityError: except IntegrityError:
messages.error(request, "That service account already exists") messages.error(request, _("That service account already exists"))
return redirect(self.index_redirect) return redirect(self.index_redirect)
return render(request, self.template_name, return render(request, self.template_name,
@@ -100,7 +101,7 @@ class BaseSetPasswordServiceAccountView(ServicesCRUDMixin, BaseServiceView, Upda
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
result = super(BaseSetPasswordServiceAccountView, self).post(request, *args, **kwargs) result = super(BaseSetPasswordServiceAccountView, self).post(request, *args, **kwargs)
if self.get_form().is_valid(): if self.get_form().is_valid():
messages.success(request, "Successfully set your {} password".format(self.service_name)) messages.success(request, _("Successfully set your {} password".format(self.service_name)))
return result return result

View File

@@ -1,14 +1,14 @@
from django import forms from django import forms
from django.contrib import admin from django.contrib import admin
from django.db.models.functions import Lower
from django.urls import reverse
from django.utils.html import format_html
from allianceauth import hooks from allianceauth import hooks
from allianceauth.eveonline.models import EveCharacter from allianceauth.authentication.admin import (
from allianceauth.authentication.admin import user_profile_pic, \ user_profile_pic,
user_username, user_main_organization, MainCorporationsFilter,\ user_username,
user_main_organization,
MainCorporationsFilter,
MainAllianceFilter MainAllianceFilter
)
from .models import NameFormatConfig from .models import NameFormatConfig
@@ -20,24 +20,29 @@ class ServicesUserAdmin(admin.ModelAdmin):
"all": ("services/admin.css",) "all": ("services/admin.css",)
} }
search_fields = ( search_fields = ('user__username',)
'user__username', ordering = ('user__username',)
'uid'
)
ordering = ('user__username', )
list_select_related = True list_select_related = True
list_display = ( list_display = (
user_profile_pic, user_profile_pic,
user_username, user_username,
'_state',
user_main_organization, user_main_organization,
'_date_joined' '_date_joined'
) )
list_filter = ( list_filter = (
'user__profile__state',
MainCorporationsFilter, MainCorporationsFilter,
MainAllianceFilter, MainAllianceFilter,
'user__date_joined' 'user__date_joined',
) )
def _state(self, obj):
return obj.user.profile.state.name
_state.short_description = 'state'
_state.admin_order_field = 'user__profile__state__name'
def _date_joined(self, obj): def _date_joined(self, obj):
return obj.user.date_joined return obj.user.date_joined
@@ -48,7 +53,8 @@ class ServicesUserAdmin(admin.ModelAdmin):
class NameFormatConfigForm(forms.ModelForm): class NameFormatConfigForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(NameFormatConfigForm, self).__init__(*args, **kwargs) super(NameFormatConfigForm, self).__init__(*args, **kwargs)
SERVICE_CHOICES = [(s.name, s.name) for h in hooks.get_hooks('services_hook') for s in [h()]] SERVICE_CHOICES = \
[(s.name, s.name) for h in hooks.get_hooks('services_hook') for s in [h()]]
if self.instance.id: if self.instance.id:
current_choice = (self.instance.service_name, self.instance.service_name) current_choice = (self.instance.service_name, self.instance.service_name)
if current_choice not in SERVICE_CHOICES: if current_choice not in SERVICE_CHOICES:

View File

@@ -1,4 +1,6 @@
from django.utils.translation import gettext_lazy as _
from allianceauth import hooks from allianceauth import hooks
from .hooks import MenuItemHook from .hooks import MenuItemHook
from .hooks import ServicesHook from .hooks import ServicesHook
@@ -6,8 +8,8 @@ from .hooks import ServicesHook
class Services(MenuItemHook): class Services(MenuItemHook):
def __init__(self): def __init__(self):
MenuItemHook.__init__(self, MenuItemHook.__init__(self,
'Services', _('Services'),
'fa fa-cogs fa-fw', 'fas fa-cogs fa-fw',
'services:services', 100) 'services:services', 100)
def render(self, request): def render(self, request):

View File

@@ -9,6 +9,31 @@ from allianceauth.hooks import get_hooks
from .models import NameFormatConfig from .models import NameFormatConfig
def get_extension_logger(name):
"""
Takes the name of a plugin/extension and generates a child logger of the extensions logger
to be used by the extension to log events to the extensions logger.
The logging level is determined by the level defined for the parent logger.
:param: name: the name of the extension doing the logging
:return: an extensions child logger
"""
if not isinstance(name, str):
raise TypeError(f"get_extension_logger takes an argument of type string."
f"Instead received argument of type {type(name).__name__}.")
import logging
parent_logger = logging.getLogger('extensions')
logger = logging.getLogger('extensions.' + name)
logger.name = name
logger.level = parent_logger.level
return logger
class ServicesHook: class ServicesHook:
""" """
Abstract base class for creating a compatible services Abstract base class for creating a compatible services

View File

@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-05 21:40
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0008_alter_user_username_max_length'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='DiscordAuthToken',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.CharField(max_length=254, unique=True)),
('token', models.CharField(max_length=254)),
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='GroupCache',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created', models.DateTimeField(auto_now_add=True)),
('groups', models.TextField(default={})),
('service', models.CharField(choices=[(b'discourse', b'discourse'), (b'discord', b'discord')], max_length=254, unique=True)),
],
),
]

View File

@@ -1,22 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-10-16 01:35
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('services', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='discordauthtoken',
name='user',
),
migrations.DeleteModel(
name='DiscordAuthToken',
),
]

View File

@@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-09-02 06:07
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('services', '0002_auto_20161016_0135'),
]
operations = [
migrations.DeleteModel(
name='GroupCache',
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 2.2.10 on 2020-03-21 13:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('services', '0002_nameformatter'),
]
operations = [
migrations.AlterField(
model_name='nameformatconfig',
name='format',
field=models.CharField(help_text='For information on constructing name formats please see the official documentation, topic "Services Name Formats".', max_length=100),
),
]

View File

@@ -4,14 +4,30 @@ from allianceauth.authentication.models import State
class NameFormatConfig(models.Model): class NameFormatConfig(models.Model):
service_name = models.CharField(max_length=100, blank=False, null=False) service_name = models.CharField(max_length=100, blank=False)
default_to_username = models.BooleanField(default=True, help_text="If a user has no main_character, " default_to_username = models.BooleanField(
"default to using their Auth username instead.") default=True,
format = models.CharField(max_length=100, blank=False, null=False, help_text=
help_text='For information on constructing name formats, please see the ' 'If a user has no main_character, '
'<a href="https://allianceauth.readthedocs.io/en/latest/features/nameformats">' 'default to using their Auth username instead.'
'name format documentation</a>') )
states = models.ManyToManyField(State, help_text="States to apply this format to. You should only have one " format = models.CharField(
"formatter for each state for each service.") max_length=100,
blank=False,
help_text=
'For information on constructing name formats '
'please see the official documentation, '
'topic "Services Name Formats".'
)
states = models.ManyToManyField(
State,
help_text=
"States to apply this format to. You should only have one "
"formatter for each state for each service."
)
def __str__(self):
return '%s: %s' % (
self.service_name, ', '.join([str(x) for x in self.states.all()])
)

View File

@@ -1 +1,3 @@
default_app_config = 'allianceauth.services.modules.discord.apps.DiscordServiceConfig' default_app_config = 'allianceauth.services.modules.discord.apps.DiscordServiceConfig' # noqa
__title__ = 'Discord Service'

View File

@@ -1,14 +1,22 @@
import logging
from django.contrib import admin from django.contrib import admin
from .models import DiscordUser from . import __title__
from ...admin import ServicesUserAdmin from ...admin import ServicesUserAdmin
from .models import DiscordUser
from .utils import LoggerAddTag
logger = LoggerAddTag(logging.getLogger(__name__), __title__)
@admin.register(DiscordUser) @admin.register(DiscordUser)
class DiscordUserAdmin(ServicesUserAdmin): class DiscordUserAdmin(ServicesUserAdmin):
list_display = ServicesUserAdmin.list_display + ( search_fields = ServicesUserAdmin.search_fields + ('uid', 'username')
'_uid', list_display = ServicesUserAdmin.list_display + ('activated', '_username', '_uid')
) list_filter = ServicesUserAdmin.list_filter + ('activated',)
ordering = ('-activated',)
def _uid(self, obj): def _uid(self, obj):
return obj.uid return obj.uid
@@ -16,3 +24,11 @@ class DiscordUserAdmin(ServicesUserAdmin):
_uid.short_description = 'Discord ID (UID)' _uid.short_description = 'Discord ID (UID)'
_uid.admin_order_field = 'uid' _uid.admin_order_field = 'uid'
def _username(self, obj):
if obj.username and obj.discriminator:
return f'{obj.username}#{obj.discriminator}'
else:
return ''
_username.short_description = 'Discord Username'
_username.admin_order_field = 'username'

View File

@@ -0,0 +1,17 @@
from .utils import clean_setting
DISCORD_APP_ID = clean_setting('DISCORD_APP_ID', '')
DISCORD_APP_SECRET = clean_setting('DISCORD_APP_SECRET', '')
DISCORD_BOT_TOKEN = clean_setting('DISCORD_BOT_TOKEN', '')
DISCORD_CALLBACK_URL = clean_setting('DISCORD_CALLBACK_URL', '')
DISCORD_GUILD_ID = clean_setting('DISCORD_GUILD_ID', '')
# max retries of tasks after an error occurred
DISCORD_TASKS_MAX_RETRIES = clean_setting('DISCORD_TASKS_MAX_RETRIES', 3)
# Pause in seconds until next retry for tasks after the API returned an error
DISCORD_TASKS_RETRY_PAUSE = clean_setting('DISCORD_TASKS_RETRY_PAUSE', 60)
# automatically sync Discord users names to user's main character name when created
DISCORD_SYNC_NAMES = clean_setting('DISCORD_SYNC_NAMES', False)

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