Compare commits

...

212 Commits

Author SHA1 Message Date
Ariel Rin
3452c3acd1 Version Bump v2.8.2 2021-02-05 12:19:25 +00:00
Ariel Rin
3c305fbf37 Merge branch 'wheel' into 'master'
Build wheel

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

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

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

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

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

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

Closes #1262

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

Closes #1253

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

Closes #1264

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Closes #1258

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

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

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

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

Closes #1217

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

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

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

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

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

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

Closes #1234

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

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

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

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

Closes #1248

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

Closes #1252

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

See merge request allianceauth/allianceauth!1235
2020-07-23 20:58:26 +00:00
Erik Kalkoken
fe36e57d72 Fix error 500 on service page for Discord and add feature "group_to_role" 2020-07-23 20:58:26 +00:00
Peter Pfeufer
31197812b6 Update Mumble documentation on setting a server password (#1252) 2020-07-22 12:18:52 +00:00
Ariel Rin
bd3fe01a12 correct task import for manual model population 2020-07-14 12:25:05 +00:00
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
AaronKable
543fa3cfa9 extra logging for tests 2020-05-08 10:03:05 +08:00
AaronKable
f70fbbdfee headers for Discourse 2020-04-27 14:37:01 +08: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
262 changed files with 18081 additions and 4671 deletions

2
.gitignore vendored
View File

@@ -75,3 +75,5 @@ celerybeat-schedule
#other
.flake8
.pylintrc
Makefile

View File

@@ -1,8 +1,11 @@
stages:
- "test"
- test
- deploy
before_script:
- apt-get update && apt-get install redis-server -y
- redis-server --daemonize yes
- redis-cli ping
- python -V
- pip install wheel tox
@@ -38,14 +41,14 @@ test-3.8-all:
deploy_production:
stage: deploy
image: python:3.6-stretch
image: python:3.8-buster
before_script:
- pip install twine
- pip install twine wheel
script:
- python setup.py sdist
- python setup.py sdist bdist_wheel
- twine upload dist/*
only:
- tags
rules:
- if: $CI_COMMIT_TAG

View File

@@ -18,10 +18,6 @@ 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
version: 3.7
install:
- requirements: docs/requirements.txt

View File

@@ -36,7 +36,7 @@ Main features:
- Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
- Chinese :cn:, English :us:, German :de: and Spanish :es: localization
- English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr: and Russian :flag_ru: localization
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io).

View File

@@ -1,6 +1,8 @@
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
__version__ = '2.6.5'
NAME = 'Alliance Auth v%s' % __version__
__version__ = '2.8.2'
__title__ = 'Alliance Auth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = '%s v%s' % (__title__, __version__)
default_app_config = 'allianceauth.apps.AllianceAuthConfig'

View File

@@ -37,8 +37,11 @@ def make_service_hooks_update_groups_action(service):
:return: fn to update services groups for the selected users
"""
def update_service_groups(modeladmin, request, queryset):
for user in queryset: # queryset filtering doesn't work here?
service.update_groups(user)
if hasattr(service, 'update_groups_bulk'):
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.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
"""
def sync_nickname(modeladmin, request, queryset):
for user in queryset: # queryset filtering doesn't work here?
service.sync_nickname(user)
if hasattr(service, 'sync_nicknames_bulk'):
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.short_description = "Sync nicknames for selected {} accounts".format(service.title)
@@ -94,7 +100,7 @@ class UserProfileInline(admin.StackedInline):
formset.get_form_kwargs = get_kwargs
return formset
def has_add_permission(self, request):
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
@@ -543,7 +549,7 @@ class PermissionAdmin(admin.ModelAdmin):
def admin_name(obj):
return str(obj)
def has_add_permission(self, request):
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):

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

View File

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

View File

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

View File

@@ -73,11 +73,17 @@ class UserProfile(models.Model):
if commit:
logger.info('Updating {} state to {}'.format(self.user, self.state))
self.save(update_fields=['state'])
notify(self.user, _('State Changed'),
_('Your user state has been changed to %(state)s') % ({'state': state}),
'info')
notify(
self.user,
_('State changed to: %s' % state),
_('Your user\'s state is now: %(state)s')
% ({'state': state}),
'info'
)
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):
return str(self.user)

View File

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

View File

@@ -1,5 +1,5 @@
{% extends "allianceauth/base.html" %}
{% load staticfiles %}
{% load static %}
{% load i18n %}
{% block page_title %}{% trans "Dashboard" %}{% endblock %}
@@ -14,7 +14,11 @@
<div class="col-sm-6 text-center">
<div class="panel panel-primary" style="height:100%">
<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 class="panel-body">
{% if request.user.profile.main_character %}
@@ -141,19 +145,17 @@
</table>
<table class="table table-aa visible-xs-block" style="width: 100%">
<tbody>
{% for ownership in request.user.character_ownerships.all %}
{% with ownership.character as char %}
<tr>
<td class="text-center" style="vertical-align: middle">
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
</td>
<td class="text-center" style="vertical-align: middle; width: 100%">
<strong>{{ char.character_name }}</strong><br>
{{ char.corporation_name }}<br>
{{ char.alliance_name|default:"" }}
</td>
</tr>
{% endwith %}
{% for char in characters %}
<tr>
<td class="text-center" style="vertical-align: middle">
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
</td>
<td class="text-center" style="vertical-align: middle; width: 100%">
<strong>{{ char.character_name }}</strong><br>
{{ char.corporation_name }}<br>
{{ char.alliance_name|default:"" }}
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

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

View File

@@ -1,5 +1,5 @@
from urllib.parse import quote
from unittest.mock import patch
from unittest.mock import patch, MagicMock
from django.conf import settings
from django.contrib import admin
@@ -13,6 +13,7 @@ from allianceauth.authentication.models import (
from allianceauth.eveonline.models import (
EveCharacter, EveCorporationInfo, EveAllianceInfo
)
from allianceauth.services.hooks import ServicesHook
from allianceauth.tests.auth_utils import AuthUtils
from ..admin import (
@@ -28,7 +29,9 @@ from ..admin import (
user_main_organization,
user_profile_pic,
user_username,
update_main_character_model
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
@@ -45,136 +48,144 @@ class MockRequest(object):
def __init__(self, user=None):
self.user = user
class TestCaseWithTestData(TestCase):
def create_test_data():
# groups
group_1 = Group.objects.create(
name='Group 1'
)
group_2 = Group.objects.create(
name='Group 2'
)
# user 1 - corp and alliance, normal user
character_1 = EveCharacter.objects.create(
character_id='1001',
character_name='Bruce Wayne',
corporation_id='2001',
corporation_name='Wayne Technologies',
corporation_ticker='WT',
alliance_id='3001',
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
)
character_1a = EveCharacter.objects.create(
character_id='1002',
character_name='Batman',
corporation_id='2001',
corporation_name='Wayne Technologies',
corporation_ticker='WT',
alliance_id='3001',
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
)
alliance = EveAllianceInfo.objects.create(
alliance_id='3001',
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
executor_corp_id='2001'
)
EveCorporationInfo.objects.create(
corporation_id='2001',
corporation_name='Wayne Technologies',
corporation_ticker='WT',
member_count=42,
alliance=alliance
)
user_1 = User.objects.create_user(
character_1.character_name.replace(' ', '_'),
'abc@example.com',
'password'
)
CharacterOwnership.objects.create(
character=character_1,
owner_hash='x1' + character_1.character_name,
user=user_1
)
CharacterOwnership.objects.create(
character=character_1a,
owner_hash='x1' + character_1a.character_name,
user=user_1
)
user_1.profile.main_character = character_1
user_1.profile.save()
user_1.groups.add(group_1)
@classmethod
def setUpClass(cls):
super().setUpClass()
# user 2 - corp only, staff
character_2 = EveCharacter.objects.create(
character_id=1003,
character_name='Clark Kent',
corporation_id=2002,
corporation_name='Daily Planet',
corporation_ticker='DP',
alliance_id=None
)
EveCorporationInfo.objects.create(
corporation_id=2002,
corporation_name='Daily Plane',
corporation_ticker='DP',
member_count=99,
alliance=None
)
user_2 = User.objects.create_user(
character_2.character_name.replace(' ', '_'),
'abc@example.com',
'password'
)
CharacterOwnership.objects.create(
character=character_2,
owner_hash='x1' + character_2.character_name,
user=user_2
)
user_2.profile.main_character = character_2
user_2.profile.save()
user_2.groups.add(group_2)
user_2.is_staff = True
user_2.save()
# user 3 - no main, no group, superuser
character_3 = EveCharacter.objects.create(
character_id=1101,
character_name='Lex Luthor',
corporation_id=2101,
corporation_name='Lex Corp',
corporation_ticker='LC',
alliance_id=None
)
EveCorporationInfo.objects.create(
corporation_id=2101,
corporation_name='Lex Corp',
corporation_ticker='LC',
member_count=666,
alliance=None
)
EveAllianceInfo.objects.create(
alliance_id='3101',
alliance_name='Lex World Domination',
alliance_ticker='LWD',
executor_corp_id=''
)
user_3 = User.objects.create_user(
character_3.character_name.replace(' ', '_'),
'abc@example.com',
'password'
)
CharacterOwnership.objects.create(
character=character_3,
owner_hash='x1' + character_3.character_name,
user=user_3
)
user_3.is_superuser = True
user_3.save()
return user_1, user_2, user_3, group_1, group_2
for MyModel in [
EveAllianceInfo, EveCorporationInfo, EveCharacter, Group, User
]:
MyModel.objects.all().delete()
# groups
cls.group_1 = Group.objects.create(
name='Group 1'
)
cls.group_2 = Group.objects.create(
name='Group 2'
)
# user 1 - corp and alliance, normal user
character_1 = EveCharacter.objects.create(
character_id=1001,
character_name='Bruce Wayne',
corporation_id=2001,
corporation_name='Wayne Technologies',
corporation_ticker='WT',
alliance_id=3001,
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
)
character_1a = EveCharacter.objects.create(
character_id=1002,
character_name='Batman',
corporation_id=2001,
corporation_name='Wayne Technologies',
corporation_ticker='WT',
alliance_id=3001,
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
)
alliance = EveAllianceInfo.objects.create(
alliance_id=3001,
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
executor_corp_id=2001
)
EveCorporationInfo.objects.create(
corporation_id=2001,
corporation_name='Wayne Technologies',
corporation_ticker='WT',
member_count=42,
alliance=alliance
)
cls.user_1 = User.objects.create_user(
character_1.character_name.replace(' ', '_'),
'abc@example.com',
'password'
)
CharacterOwnership.objects.create(
character=character_1,
owner_hash='x1' + character_1.character_name,
user=cls.user_1
)
CharacterOwnership.objects.create(
character=character_1a,
owner_hash='x1' + character_1a.character_name,
user=cls.user_1
)
cls.user_1.profile.main_character = character_1
cls.user_1.profile.save()
cls.user_1.groups.add(cls.group_1)
# user 2 - corp only, staff
character_2 = EveCharacter.objects.create(
character_id=1003,
character_name='Clark Kent',
corporation_id=2002,
corporation_name='Daily Planet',
corporation_ticker='DP',
alliance_id=None
)
EveCorporationInfo.objects.create(
corporation_id=2002,
corporation_name='Daily Plane',
corporation_ticker='DP',
member_count=99,
alliance=None
)
cls.user_2 = User.objects.create_user(
character_2.character_name.replace(' ', '_'),
'abc@example.com',
'password'
)
CharacterOwnership.objects.create(
character=character_2,
owner_hash='x1' + character_2.character_name,
user=cls.user_2
)
cls.user_2.profile.main_character = character_2
cls.user_2.profile.save()
cls.user_2.groups.add(cls.group_2)
cls.user_2.is_staff = True
cls.user_2.save()
# user 3 - no main, no group, superuser
character_3 = EveCharacter.objects.create(
character_id=1101,
character_name='Lex Luthor',
corporation_id=2101,
corporation_name='Lex Corp',
corporation_ticker='LC',
alliance_id=None
)
EveCorporationInfo.objects.create(
corporation_id=2101,
corporation_name='Lex Corp',
corporation_ticker='LC',
member_count=666,
alliance=None
)
EveAllianceInfo.objects.create(
alliance_id=3101,
alliance_name='Lex World Domination',
alliance_ticker='LWD',
executor_corp_id=2101
)
cls.user_3 = User.objects.create_user(
character_3.character_name.replace(' ', '_'),
'abc@example.com',
'password'
)
CharacterOwnership.objects.create(
character=character_3,
owner_hash='x1' + character_3.character_name,
user=cls.user_3
)
cls.user_3.is_superuser = True
cls.user_3.save()
def make_generic_search_request(ModelClass: type, search_term: str):
@@ -188,12 +199,7 @@ def make_generic_search_request(ModelClass: type, search_term: str):
)
class TestCharacterOwnershipAdmin(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.user_1, _, _, _, _ = create_test_data()
class TestCharacterOwnershipAdmin(TestCaseWithTestData):
def setUp(self):
self.modeladmin = CharacterOwnershipAdmin(
@@ -219,12 +225,7 @@ class TestCharacterOwnershipAdmin(TestCase):
self.assertEqual(response.status_code, expected)
class TestOwnershipRecordAdmin(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.user_1, _, _, _, _ = create_test_data()
class TestOwnershipRecordAdmin(TestCaseWithTestData):
def setUp(self):
self.modeladmin = OwnershipRecordAdmin(
@@ -250,13 +251,8 @@ class TestOwnershipRecordAdmin(TestCase):
self.assertEqual(response.status_code, expected)
class TestStateAdmin(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
create_test_data()
class TestStateAdmin(TestCaseWithTestData):
def setUp(self):
self.modeladmin = StateAdmin(
model=User, admin_site=AdminSite()
@@ -283,13 +279,7 @@ class TestStateAdmin(TestCase):
expected = 200
self.assertEqual(response.status_code, expected)
class TestUserAdmin(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.user_1, cls.user_2, cls.user_3, cls.group_1, cls.group_2 = \
create_test_data()
class TestUserAdmin(TestCaseWithTestData):
def setUp(self):
self.factory = RequestFactory()
@@ -520,8 +510,8 @@ class TestUserAdmin(TestCase):
filters = changelist.get_filters(request)
filterspec = filters[0][0]
expected = [
('2002', 'Daily Planet'),
('2001', 'Wayne Technologies'),
(2002, 'Daily Planet'),
(2001, 'Wayne Technologies'),
]
self.assertEqual(filterspec.lookup_choices, expected)
@@ -550,7 +540,7 @@ class TestUserAdmin(TestCase):
filters = changelist.get_filters(request)
filterspec = filters[0][0]
expected = [
('3001', 'Wayne Enterprises'),
(3001, 'Wayne Enterprises'),
]
self.assertEqual(filterspec.lookup_choices, expected)
@@ -578,4 +568,68 @@ class TestUserAdmin(TestCase):
obj = User.objects.first()
response = make_generic_search_request(type(obj), obj.username)
expected = 200
self.assertEqual(response.status_code, expected)
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

@@ -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 io import StringIO
from urllib import parse
from django.conf import settings
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.contrib.auth.models import User
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,\
EveAllianceInfo
from allianceauth.tests.auth_utils import AuthUtils
from esi.errors import IncompleteResponseError
from esi.models import Token
from ..backends import StateBackend
from ..models import CharacterOwnership, UserProfile, State, get_guest_state,\
OwnershipRecord
from ..models import CharacterOwnership, State, get_guest_state
from ..tasks import check_character_ownership
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):
@classmethod
def setUpTestData(cls):
@@ -343,10 +216,10 @@ class CharacterOwnershipCheckTestCase(TestCase):
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',
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(
user=cls.user,
character_id='1',
character_id=1,
character_name='Test',
character_owner_hash='1',
)
@@ -378,30 +251,3 @@ class CharacterOwnershipCheckTestCase(TestCase):
filter.return_value.exists.return_value = False
check_character_ownership(self.ownership)
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

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

View File

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

View File

@@ -17,9 +17,9 @@ class CorpStatsManagerTestCase(TestCase):
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.token = Token.objects.create(user=cls.user, access_token='a', character_id='1', character_name='test character', character_owner_hash='z')
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.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.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')
@@ -66,9 +66,9 @@ class CorpStatsUpdateTestCase(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.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)
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.corp = EveCorporationInfo.objects.create(corporation_id=2, corporation_name='test corp', corporation_ticker='TEST', member_count=1)
def setUp(self):
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.Universe.post_universe_names.return_value.result.return_value = [{'id': 1, 'name': 'test character'}]
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')
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.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'}]
@@ -130,15 +130,15 @@ class CorpStatsPropertiesTestCase(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')
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.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.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.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):
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)
member.delete()
self.assertEqual(self.corpstats.member_count, 0)
@@ -147,7 +147,7 @@ class CorpStatsPropertiesTestCase(TestCase):
AuthUtils.disconnect_signals()
co = CharacterOwnership.objects.create(character=self.character, user=self.user, owner_hash='a')
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)
co.delete()
self.assertEqual(self.corpstats.user_count, 0)
@@ -156,7 +156,8 @@ class CorpStatsPropertiesTestCase(TestCase):
AuthUtils.disconnect_signals()
co = CharacterOwnership.objects.create(character=self.character, user=self.user, owner_hash='a')
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.assertEqual(self.corpstats.registered_member_count, 1)
@@ -165,7 +166,7 @@ class CorpStatsPropertiesTestCase(TestCase):
self.assertEqual(self.corpstats.registered_member_count, 0)
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.assertIn(member, self.corpstats.unregistered_members)
self.assertEqual(self.corpstats.unregistered_member_count, 1)
@@ -178,13 +179,13 @@ class CorpStatsPropertiesTestCase(TestCase):
def test_mains(self):
# 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.assertEqual(self.corpstats.main_count, 1)
# test when is an alt
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()
co = CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
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.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.save()
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')
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.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.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.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):
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)
def test_main_character(self):
@@ -238,7 +239,7 @@ class CorpMemberTestCase(TestCase):
self.assertIsNone(self.member.main_character)
# 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')
self.member.refresh_from_db()
self.assertNotEqual(self.member.main_character, self.member.character)
@@ -260,14 +261,14 @@ class CorpMemberTestCase(TestCase):
def test_alts(self):
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')
self.assertIn(character, self.member.alts)
def test_registered(self):
self.assertFalse(self.member.registered)
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')
self.assertTrue(self.member.registered)
AuthUtils.connect_signals()

View File

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

View File

@@ -2,24 +2,30 @@
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:
"""return url to profile page for an eve entity"""
if category == ESI_CATEGORY_ALLIANCE:
if category == _ESI_CATEGORY_ALLIANCE:
partial = 'alliance'
elif category == ESI_CATEGORY_CORPORATION:
elif category == _ESI_CATEGORY_CORPORATION:
partial = 'corp'
elif category == ESI_CATEGORY_REGION:
elif category == _ESI_CATEGORY_REGION:
partial = 'map'
elif category == ESI_CATEGORY_SOLARSYSTEM:
elif category == _ESI_CATEGORY_SOLARSYSTEM:
partial = 'system'
else:
@@ -28,7 +34,7 @@ def _build_url(category: str, name: str) -> str:
)
url = urljoin(
BASE_URL,
_BASE_URL,
'{}/{}'.format(partial, quote(str(name).replace(" ", "_")))
)
@@ -37,16 +43,19 @@ def _build_url(category: str, name: str) -> str:
def alliance_url(name: str) -> str:
"""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:
"""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:
"""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:
"""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
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:
"""return url to profile page for an eve entity"""
if category == ESI_CATEGORY_ALLIANCE:
if category == _ESI_CATEGORY_ALLIANCE:
partial = 'alliance'
elif category == ESI_CATEGORY_CORPORATION:
elif category == _ESI_CATEGORY_CORPORATION:
partial = 'corporation'
elif category == ESI_CATEGORY_CHARACTER:
elif category == _ESI_CATEGORY_CHARACTER:
partial = 'character'
else:
@@ -25,7 +30,7 @@ def _build_url(category: str, eve_id: int) -> str:
)
url = urljoin(
BASE_URL,
_BASE_URL,
'{}/{}'.format(partial, int(eve_id))
)
return url
@@ -33,12 +38,14 @@ def _build_url(category: str, eve_id: int) -> str:
def alliance_url(eve_id: int) -> str:
"""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:
"""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:
"""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 ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from .. import dotlan, zkillboard, evewho
from .. import dotlan, zkillboard, evewho, eveimageserver
from ...templatetags import evelinks
@@ -90,3 +90,115 @@ class TestZkillboard(TestCase):
'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 ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from .. import dotlan, zkillboard, evewho
from .. import eveimageserver, evewho, dotlan, zkillboard
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
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:
"""return url to profile page for an eve entity"""
if category == ESI_CATEGORY_ALLIANCE:
if category == _ESI_CATEGORY_ALLIANCE:
partial = 'alliance'
elif category == ESI_CATEGORY_CORPORATION:
elif category == _ESI_CATEGORY_CORPORATION:
partial = 'corporation'
elif category == ESI_CATEGORY_CHARACTER:
elif category == _ESI_CATEGORY_CHARACTER:
partial = 'character'
elif category == ESI_CATEGORY_REGION:
elif category == _ESI_CATEGORY_REGION:
partial = 'region'
elif category == ESI_CATEGORY_SOLARSYSTEM:
elif category == _ESI_CATEGORY_SOLARSYSTEM:
partial = 'system'
else:
@@ -31,7 +38,7 @@ def _build_url(category: str, eve_id: int) -> str:
)
url = urljoin(
BASE_URL,
_BASE_URL,
'{}/{}/'.format(partial, int(eve_id))
)
return url
@@ -39,19 +46,23 @@ def _build_url(category: str, eve_id: int) -> str:
def alliance_url(eve_id: int) -> str:
"""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:
"""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:
"""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:
"""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:
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):
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

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

View File

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

View File

@@ -5,109 +5,35 @@ from .managers import EveCharacterManager, EveCharacterProviderManager
from .managers import EveCorporationManager, EveCorporationProviderManager
from .managers import EveAllianceManager, EveAllianceProviderManager
from . import providers
from .evelinks import eveimageserver
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
_DEFAULT_IMAGE_SIZE = 32
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_ticker = models.CharField(max_length=254)
executor_corp_id = models.CharField(max_length=254)
executor_corp_id = models.PositiveIntegerField()
objects = EveAllianceManager()
provider = EveAllianceProviderManager()
class Meta:
indexes = [models.Index(fields=['executor_corp_id',])]
def populate_alliance(self):
alliance = self.provider.get_alliance(self.alliance_id)
for corp_id in alliance.corp_ids:
if not EveCorporationInfo.objects.filter(corporation_id=corp_id).exists():
EveCorporationInfo.objects.create_corporation(corp_id)
EveCorporationInfo.objects.filter(corporation_id__in=alliance.corp_ids).update(alliance=self)
EveCorporationInfo.objects.filter(alliance=self).exclude(corporation_id__in=alliance.corp_ids).update(
alliance=None)
EveCorporationInfo.objects.filter(
corporation_id__in=alliance.corp_ids).update(alliance=self
)
EveCorporationInfo.objects\
.filter(alliance=self)\
.exclude(corporation_id__in=alliance.corp_ids)\
.update(alliance=None)
def update_alliance(self, alliance: providers.Alliance = None):
if alliance is None:
@@ -120,11 +46,13 @@ class EveAllianceInfo(models.Model):
return self.alliance_name
@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"""
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"""
return self.generic_logo_url(self.alliance_id, size)
@@ -150,19 +78,28 @@ class EveAllianceInfo(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_ticker = models.CharField(max_length=254)
member_count = models.IntegerField()
alliance = models.ForeignKey(EveAllianceInfo, blank=True, null=True, on_delete=models.SET_NULL)
ceo_id = models.PositiveIntegerField(blank=True, null=True, default=None)
alliance = models.ForeignKey(
EveAllianceInfo, blank=True, null=True, on_delete=models.SET_NULL
)
objects = EveCorporationManager()
provider = EveCorporationProviderManager()
class Meta:
indexes = [
models.Index(fields=['ceo_id',]),
]
def update_corporation(self, corp: providers.Corporation = None):
if corp is None:
corp = self.provider.get_corporation(self.corporation_id)
self.member_count = corp.members
self.ceo_id = corp.ceo_id
try:
self.alliance = EveAllianceInfo.objects.get(alliance_id=corp.alliance_id)
except EveAllianceInfo.DoesNotExist:
@@ -174,11 +111,13 @@ class EveCorporationInfo(models.Model):
return self.corporation_name
@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"""
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"""
return self.generic_logo_url(self.corporation_id, size)
@@ -204,18 +143,26 @@ class EveCorporationInfo(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)
corporation_id = models.CharField(max_length=254)
corporation_id = models.PositiveIntegerField()
corporation_name = models.CharField(max_length=254)
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_ticker = models.CharField(max_length=5, blank=True, null=True, default='')
objects = EveCharacterManager()
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
def alliance(self) -> Union[EveAllianceInfo, None]:
"""
@@ -253,11 +200,13 @@ class EveCharacter(models.Model):
return self.character_name
@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"""
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"""
return self.generic_portrait_url(self.character_id, size)
@@ -281,7 +230,7 @@ class EveCharacter(models.Model):
"""image URL for this character"""
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"""
return EveCorporationInfo.generic_logo_url(self.corporation_id, size)
@@ -305,7 +254,7 @@ class EveCharacter(models.Model):
"""image URL for corporation of this character"""
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"""
if self.alliance_id:
return EveAllianceInfo.generic_logo_url(self.alliance_id, size)

View File

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

View File

@@ -5,35 +5,96 @@ from .models import EveAllianceInfo
from .models import EveCharacter
from .models import EveCorporationInfo
from . import providers
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
def update_corp(corp_id):
"""Update given corporation from ESI"""
EveCorporationInfo.objects.update_corporation(corp_id)
@shared_task
def update_alliance(alliance_id):
"""Update given alliance from ESI"""
EveAllianceInfo.objects.update_alliance(alliance_id).populate_alliance()
@shared_task
def update_character(character_id):
"""Update given character from ESI"""
EveCharacter.objects.update_character(character_id)
@shared_task
def run_model_update():
"""Update all alliances, corporations and characters from ESI"""
# update existing corp models
for corp in EveCorporationInfo.objects.all().values('corporation_id'):
update_corp.apply_async(args=[corp['corporation_id']], priority=TASK_PRIORITY)
update_corp.apply_async(
args=[corp['corporation_id']], priority=TASK_PRIORITY
)
# update existing alliance models
for alliance in EveAllianceInfo.objects.all().values('alliance_id'):
update_alliance.apply_async(args=[alliance['alliance_id']], priority=TASK_PRIORITY)
update_alliance.apply_async(
args=[alliance['alliance_id']], priority=TASK_PRIORITY
)
#update existing character models
for character in EveCharacter.objects.all().values('character_id'):
update_character.apply_async(args=[character['character_id']], priority=TASK_PRIORITY)
# update existing character models
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 ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from ..evelinks import evewho, dotlan, zkillboard
from ..evelinks import eveimageserver, evewho, dotlan, zkillboard
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)
#zkillboard
# zkillboard
@register.filter
def zkillboard_character_url(eve_obj: EveCharacter) -> str:
@@ -212,7 +212,6 @@ def zkillboard_solar_system_url(eve_obj: object) -> str:
# image urls
@register.filter
def character_portrait_url(
eve_obj: object,
@@ -284,3 +283,30 @@ def alliance_logo_url(
except ValueError:
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()
provider.get_character.return_value = expected
result = EveCharacter.provider.get_character('1234')
result = EveCharacter.provider.get_character(1234)
self.assertEqual(expected, result)
@@ -22,30 +22,30 @@ class EveCharacterManagerTestCase(TestCase):
class TestCharacter(Character):
@property
def alliance(self):
return Alliance(id='3456', name='Test Alliance')
return Alliance(id=3456, name='Test Alliance')
@property
def corp(self):
return Corporation(
id='2345',
id=2345,
name='Test Corp',
alliance_id='3456',
ticker='0BUGS'
alliance_id=3456,
ticker='0BUGS' #lies, blatant lies!
)
@mock.patch('allianceauth.eveonline.managers.providers.provider')
def test_create_character(self, provider):
# Also covers create_character_obj
expected = self.TestCharacter(
id='1234',
id=1234,
name='Test Character',
corp_id='2345',
alliance_id='3456'
corp_id=2345,
alliance_id=3456
)
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_name, expected.name)
@@ -59,25 +59,24 @@ class EveCharacterManagerTestCase(TestCase):
def test_update_character(self, provider):
# Also covers Model.update_character
existing = EveCharacter.objects.create(
character_id='1234',
character_id=1234,
character_name='character.name',
corporation_id='character.corp.id',
corporation_id=23457,
corporation_name='character.corp.name',
corporation_ticker='character.corp.ticker',
alliance_id='character.alliance.id',
corporation_ticker='cc1',
alliance_id=34567,
alliance_name='character.alliance.name',
)
expected = self.TestCharacter(
id='1234',
id=1234,
name='Test Character',
corp_id='2345',
alliance_id='3456'
corp_id=2345,
alliance_id=3456
)
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_name, expected.name)
@@ -90,23 +89,23 @@ class EveCharacterManagerTestCase(TestCase):
def test_get_character_by_id(self):
EveCharacter.objects.all().delete()
EveCharacter.objects.create(
character_id='1234',
character_id=1234,
character_name='character.name',
corporation_id='character.corp.id',
corporation_id=2345,
corporation_name='character.corp.name',
corporation_ticker='character.corp.ticker',
alliance_id='character.alliance.id',
corporation_ticker='cc1',
alliance_id=3456,
alliance_name='character.alliance.name',
)
# 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')
# 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):
@@ -115,7 +114,7 @@ class EveAllianceProviderManagerTestCase(TestCase):
expected = Alliance()
provider.get_alliance.return_value = expected
result = EveAllianceInfo.provider.get_alliance('1234')
result = EveAllianceInfo.provider.get_alliance(1234)
self.assertEqual(expected, result)
@@ -131,16 +130,16 @@ class EveAllianceManagerTestCase(TestCase):
def test_create_alliance(self, provider, populate_alliance):
# Also covers create_alliance_obj
expected = self.TestAlliance(
id='3456',
id=3456,
name='Test Alliance',
ticker='TEST',
corp_ids=['2345'],
executor_corp_id='2345'
corp_ids=[2345],
executor_corp_id=2345
)
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_name, expected.name)
@@ -152,22 +151,22 @@ class EveAllianceManagerTestCase(TestCase):
def test_update_alliance(self, provider):
# Also covers Model.update_alliance
EveAllianceInfo.objects.create(
alliance_id='3456',
alliance_id=3456,
alliance_name='alliance.name',
alliance_ticker='alliance.ticker',
executor_corp_id='alliance.executor_corp_id',
alliance_ticker='at1',
executor_corp_id=2345,
)
expected = self.TestAlliance(
id='3456',
id=3456,
name='Test Alliance',
ticker='TEST',
corp_ids=['2345'],
executor_corp_id='2345'
corp_ids=[2345],
executor_corp_id=2345
)
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
self.assertEqual(result.executor_corp_id, expected.executor_corp_id)
@@ -179,7 +178,7 @@ class EveCorporationProviderManagerTestCase(TestCase):
expected = Corporation()
provider.get_corp.return_value = expected
result = EveCorporationInfo.provider.get_corporation('2345')
result = EveCorporationInfo.provider.get_corporation(2345)
self.assertEqual(expected, result)
@@ -190,39 +189,39 @@ class EveCorporationManagerTestCase(TestCase):
@property
def alliance(self):
return EveAllianceManagerTestCase.TestAlliance(
id='3456',
id=3456,
name='Test Alliance',
ticker='TEST',
corp_ids=['2345'],
executor_corp_id='2345'
corp_ids=[2345],
executor_corp_id=2345
)
@property
def ceo(self):
return EveCharacterManagerTestCase.TestCharacter(
id='1234',
id=1234,
name='Test Character',
corp_id='2345',
alliance_id='3456'
corp_id=2345,
alliance_id=3456
)
@mock.patch('allianceauth.eveonline.managers.providers.provider')
def test_create_corporation(self, provider):
# Also covers create_corp_obj
exp_alliance = EveAllianceInfo.objects.create(
alliance_id='3456',
alliance_id=3456,
alliance_name='alliance.name',
alliance_ticker='alliance.ticker',
executor_corp_id='alliance.executor_corp_id',
alliance_ticker='99bug',
executor_corp_id=2345,
)
expected = self.TestCorporation(
id='2345',
id=2345,
name='Test Corp',
ticker='0BUGS',
ceo_id='1234',
ceo_id=1234,
members=1,
alliance_id='3456'
alliance_id=3456
)
provider.get_corp.return_value = expected
@@ -240,17 +239,17 @@ class EveCorporationManagerTestCase(TestCase):
# variant to test no alliance case
# Also covers create_corp_obj
expected = self.TestCorporation(
id='2345',
id=2345,
name='Test Corp',
ticker='0BUGS',
ceo_id='1234',
ceo_id=1234,
members=1,
alliance_id='3456'
alliance_id=3456
)
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_name, expected.name)
@@ -262,27 +261,27 @@ class EveCorporationManagerTestCase(TestCase):
def test_update_corporation(self, provider):
# Also covers Model.update_corporation
exp_alliance = EveAllianceInfo.objects.create(
alliance_id='3456',
alliance_id=3456,
alliance_name='alliance.name',
alliance_ticker='alliance.ticker',
executor_corp_id='alliance.executor_corp_id',
alliance_ticker='at1',
executor_corp_id=2345,
)
EveCorporationInfo.objects.create(
corporation_id='2345',
corporation_id=2345,
corporation_name='corp.name',
corporation_ticker='corp.ticker',
corporation_ticker='cc1',
member_count=10,
alliance=None,
)
expected = self.TestCorporation(
id='2345',
id=2345,
name='Test Corp',
ticker='0BUGS',
ceo_id='1234',
ceo_id=1234,
members=1,
alliance_id='3456'
alliance_id=3456
)
provider.get_corp.return_value = expected

View File

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

View File

@@ -1,21 +1,28 @@
import os
from unittest.mock import Mock, patch
from bravado.exception import HTTPNotFound, HTTPUnprocessableEntity
from bravado.exception import HTTPNotFound
from jsonschema.exceptions import RefResolutionError
from django.test import TestCase
from . import set_logger
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from ..providers import ObjectNotFound, Entity, Character, Corporation, \
Alliance, ItemType, EveProvider, EveSwaggerProvider
from ..providers import (
ObjectNotFound,
Entity,
Character,
Corporation,
Alliance,
ItemType,
EveProvider,
EveSwaggerProvider
)
MODULE_PATH = 'allianceauth.eveonline.providers'
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__)
@@ -51,7 +58,6 @@ class TestEntity(TestCase):
x = Entity()
self.assertEqual(repr(x), '<Entity (None): None>')
def test_bool(self):
x = Entity(1001)
self.assertTrue(bool(x))
@@ -99,7 +105,6 @@ class TestCorporation(TestCase):
# should fetch alliance once only
self.assertEqual(mock_provider_get_alliance.call_count, 1)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
def test_alliance_not_defined(self, mock_provider_get_alliance):
mock_provider_get_alliance.return_value = None
@@ -110,7 +115,6 @@ class TestCorporation(TestCase):
Entity(None, None)
)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_character')
def test_ceo(self, mock_provider_get_character):
my_ceo = Character(
@@ -200,7 +204,6 @@ class TestAlliance(TestCase):
# should be called once by used corp only
self.assertEqual(mock_provider_get_corp.call_count, 2)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_corp')
def test_corps(self, mock_provider_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
self.assertEqual(mock_provider_get_corp.call_count, 1)
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
@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_alliance.call_count, 1)
def test_alliance_has_none(self):
self.my_character.alliance_id = None
self.assertEqual(self.my_character.alliance, Entity(None, None))
@@ -343,7 +344,6 @@ class TestEveSwaggerProvider(TestCase):
else:
raise HTTPNotFound(Mock())
@staticmethod
def esi_get_alliances_alliance_id_corporations(alliance_id):
alliances = {
@@ -357,7 +357,6 @@ class TestEveSwaggerProvider(TestCase):
else:
raise HTTPNotFound(Mock())
@staticmethod
def esi_get_corporations_corporation_id(corporation_id):
corporations = {
@@ -382,7 +381,6 @@ class TestEveSwaggerProvider(TestCase):
else:
raise HTTPNotFound(Mock())
@staticmethod
def esi_get_characters_character_id(character_id):
characters = {
@@ -403,7 +401,6 @@ class TestEveSwaggerProvider(TestCase):
else:
raise HTTPNotFound(Mock())
@staticmethod
def esi_post_characters_affiliation(characters):
character_data = {
@@ -428,7 +425,6 @@ class TestEveSwaggerProvider(TestCase):
else:
raise TypeError()
@staticmethod
def esi_get_universe_types_type_id(type_id):
types = {
@@ -446,13 +442,11 @@ class TestEveSwaggerProvider(TestCase):
else:
raise HTTPNotFound(Mock())
@patch(MODULE_PATH + '.esi_client_factory')
def test_str(self, mock_esi_client_factory):
my_provider = EveSwaggerProvider()
self.assertEqual(str(my_provider), 'esi')
@patch(MODULE_PATH + '.esi_client_factory')
def test_get_alliance(self, mock_esi_client_factory):
mock_esi_client_factory.return_value\
@@ -481,7 +475,6 @@ class TestEveSwaggerProvider(TestCase):
with self.assertRaises(ObjectNotFound):
my_provider.get_alliance(3999)
@patch(MODULE_PATH + '.esi_client_factory')
def test_get_corp(self, mock_esi_client_factory):
mock_esi_client_factory.return_value\
@@ -508,7 +501,6 @@ class TestEveSwaggerProvider(TestCase):
with self.assertRaises(ObjectNotFound):
my_provider.get_corp(2999)
@patch(MODULE_PATH + '.esi_client_factory')
def test_get_character(self, mock_esi_client_factory):
mock_esi_client_factory.return_value\
@@ -536,7 +528,6 @@ class TestEveSwaggerProvider(TestCase):
with self.assertRaises(ObjectNotFound):
my_provider.get_character(1999)
@patch(MODULE_PATH + '.esi_client_factory')
def test_get_itemtype(self, mock_esi_client_factory):
mock_esi_client_factory.return_value\
@@ -601,5 +592,12 @@ class TestEveSwaggerProvider(TestCase):
self.assertTrue(mock_esi_client_factory.called)
self.assertIsNotNone(my_provider._client)
self.assertEqual(my_client, 'my_client')
@patch(MODULE_PATH + '.__version__', '1.0.0')
def test_user_agent_header(self):
my_provider = EveSwaggerProvider()
my_client = my_provider.client
operation = my_client.Status.get_status()
self.assertEqual(
operation.future.request.headers['User-Agent'], 'allianceauth v1.0.0'
)

View File

@@ -3,8 +3,12 @@ from unittest.mock import patch, Mock
from django.test import TestCase
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
)
class TestTasks(TestCase):
@@ -13,98 +17,229 @@ class TestTasks(TestCase):
def test_update_corp(self, mock_EveCorporationInfo):
update_corp(42)
self.assertEqual(
mock_EveCorporationInfo.objects.update_corporation.call_count,
1
mock_EveCorporationInfo.objects.update_corporation.call_count, 1
)
self.assertEqual(
mock_EveCorporationInfo.objects.update_corporation.call_args[0][0],
42
mock_EveCorporationInfo.objects.update_corporation.call_args[0][0], 42
)
@patch('allianceauth.eveonline.tasks.EveAllianceInfo')
def test_update_alliance(self, mock_EveAllianceInfo):
update_alliance(42)
self.assertEqual(
mock_EveAllianceInfo.objects.update_alliance.call_args[0][0],
42
mock_EveAllianceInfo.objects.update_alliance.call_args[0][0], 42
)
self.assertEqual(
mock_EveAllianceInfo.objects\
.update_alliance.return_value.populate_alliance.call_count,
1
mock_EveAllianceInfo.objects
.update_alliance.return_value.populate_alliance.call_count, 1
)
@patch('allianceauth.eveonline.tasks.EveCharacter')
def test_update_character(self, mock_EveCharacter):
update_character(42)
self.assertEqual(
mock_EveCharacter.objects.update_character.call_count,
1
mock_EveCharacter.objects.update_character.call_count, 1
)
self.assertEqual(
mock_EveCharacter.objects.update_character.call_args[0][0],
42
mock_EveCharacter.objects.update_character.call_args[0][0], 42
)
@patch('allianceauth.eveonline.tasks.update_character')
@patch('allianceauth.eveonline.tasks.update_alliance')
@patch('allianceauth.eveonline.tasks.update_corp')
def test_run_model_update(
self,
mock_update_corp,
mock_update_alliance,
mock_update_character,
):
@patch('allianceauth.eveonline.tasks.update_character')
@patch('allianceauth.eveonline.tasks.update_alliance')
@patch('allianceauth.eveonline.tasks.update_corp')
@patch('allianceauth.eveonline.providers.provider')
@patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2)
class TestRunModelUpdate(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
EveCorporationInfo.objects.all().delete()
EveAllianceInfo.objects.all().delete()
EveCharacter.objects.all().delete()
EveCorporationInfo.objects.create(
corporation_id='2345',
corporation_id=2345,
corporation_name='corp.name',
corporation_ticker='corp.ticker',
corporation_ticker='c.c.t',
member_count=10,
alliance=None,
)
EveAllianceInfo.objects.create(
alliance_id='3456',
alliance_id=3456,
alliance_name='alliance.name',
alliance_ticker='alliance.ticker',
executor_corp_id='alliance.executor_corp_id',
)
alliance_ticker='a.t',
executor_corp_id=5,
)
EveCharacter.objects.create(
character_id='1234',
character_name='character.name',
corporation_id='character.corp.id',
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='character.alliance.id',
alliance_id=None
)
EveCharacter.objects.create(
character_id=2,
character_name='character.name2',
corporation_id=9876,
corporation_name='character.corp.name',
corporation_ticker='c.c.t', # max 5 chars
alliance_id=3456,
alliance_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()
self.assertEqual(
mock_provider.client.Character.post_characters_affiliation.call_count, 2
)
self.assertEqual(
mock_provider.client.Universe.post_universe_names.call_count, 2
)
# character 1 has changed corp
# character 2 no change
# character 3 has changed alliance
# character 4 has changed name
self.assertEqual(mock_update_corp.apply_async.call_count, 1)
self.assertEqual(
int(mock_update_corp.apply_async.call_args[1]['args'][0]),
2345
int(mock_update_corp.apply_async.call_args[1]['args'][0]), 2345
)
self.assertEqual(mock_update_alliance.apply_async.call_count, 1)
self.assertEqual(
int(mock_update_alliance.apply_async.call_args[1]['args'][0]),
3456
)
int(mock_update_alliance.apply_async.call_args[1]['args'][0]), 3456
)
characters_updated = {
x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list
}
excepted = {1, 3, 4}
self.assertSetEqual(characters_updated, excepted)
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.apply_async.call_count, 1)
self.assertEqual(
int(mock_update_character.apply_async.call_args[1]['args'][0]),
1234
)
run_model_update()
characters_updated = {
x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list
}
excepted = {3, 4}
self.assertSetEqual(characters_updated, excepted)
def test_ignore_character_not_in_names(
self,
mock_provider,
mock_update_corp,
mock_update_alliance,
mock_update_character,
):
def get_affiliations(characters: list):
response = [x for x in self.affiliations if x['character_id'] in characters]
mock_operator = Mock(**{'result.return_value': response})
return mock_operator
def get_names(ids: list):
response = [x for x in self.names if x['id'] in ids]
mock_operator = Mock(**{'result.return_value': response})
return mock_operator
del self.names[3]
mock_provider.client.Character.post_characters_affiliation.side_effect \
= get_affiliations
mock_provider.client.Universe.post_universe_names.side_effect = get_names
run_model_update()
characters_updated = {
x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list
}
excepted = {1, 3}
self.assertSetEqual(characters_updated, excepted)

View File

@@ -6,7 +6,7 @@ from allianceauth.services.hooks import MenuItemHook, UrlHook
@hooks.register('menu_item_hook')
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:'])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -41,7 +41,7 @@ class AuthGroupInlineAdmin(admin.StackedInline):
kwargs["queryset"] = Group.objects.order_by(Lower('name'))
return super().formfield_for_manytomany(db_field, request, **kwargs)
def has_add_permission(self, request):
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):

View File

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

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

@@ -4,6 +4,7 @@ from django.contrib.auth.models import Group, User
from django.db.models import Q, QuerySet
from allianceauth.authentication.models import State
from .models import GroupRequest
logger = logging.getLogger(__name__)
@@ -101,3 +102,18 @@ class GroupManager:
if user.is_authenticated:
return cls.has_management_permission(user) or cls.get_group_leaders_groups(user).filter(pk=group.pk).exists()
return False
@classmethod
def pending_requests_count_for_user(cls, user: User) -> int:
"""Returns the number of pending group requests for the given user"""
if cls.has_management_permission(user):
return GroupRequest.objects.filter(status="pending").count()
else:
return (
GroupRequest.objects
.filter(status="pending")
.filter(group__authgroup__group_leaders__exact=user)
.select_related("group__authgroup__group_leaders")
.count()
)

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.1.1 on 2020-09-18 14:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('groupmanagement', '0013_fix_requestlog_date_field'),
]
operations = [
migrations.AlterField(
model_name='requestlog',
name='request_type',
field=models.BooleanField(null=True),
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 3.1.2 on 2020-10-25 11:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("groupmanagement", "0014_auto_20200918_1412"),
]
operations = [
migrations.AlterField(
model_name="authgroup",
name="description",
field=models.TextField(
blank=True,
help_text="Short description <i>(max. 512 characters)</i> of the group shown to users.",
max_length=512,
),
),
]

View File

@@ -4,7 +4,6 @@ from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from allianceauth.authentication.models import State
from datetime import datetime
class GroupRequest(models.Model):
@@ -26,7 +25,7 @@ class GroupRequest(models.Model):
class RequestLog(models.Model):
request_type = models.NullBooleanField(default=0)
request_type = models.BooleanField(null=True)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
request_info = models.CharField(max_length=254)
action = models.BooleanField(default=0)
@@ -108,7 +107,7 @@ class AuthGroup(models.Model):
help_text="States listed here will have the ability to join this group provided "
"they have the proper permissions.")
description = models.CharField(max_length=512, blank=True, help_text="Description of the group shown to users.")
description = models.TextField(max_length=512, blank=True, help_text="Short description <i>(max. 512 characters)</i> of the group shown to users.")
def __str__(self):
return self.group.name

View File

@@ -1,16 +1,22 @@
from allianceauth.authentication.signals import state_changed
from .managers import GroupManager
from .models import Group
from django.dispatch import receiver
import logging
from django.dispatch import receiver
from allianceauth.authentication.signals import state_changed
logger = logging.getLogger(__name__)
@receiver(state_changed)
def check_groups_on_state_change(sender, user, state, **kwargs):
logger.debug("Updating auth groups for {}".format(user))
visible_groups = GroupManager.get_joinable_groups(state)
visible_groups = visible_groups | Group.objects.select_related('authgroup').filter(authgroup__internal=True)
groups = user.groups.all()
for g in groups:
if g not in visible_groups:
user.groups.remove(g)
logger.debug(
"Checking group memberships for %s based on new state %s" % (user, state)
)
state_groups = (
user.groups.select_related("authgroup").exclude(authgroup__states=None)
)
for group in state_groups:
if not group.authgroup.states.filter(id=state.id).exists():
logger.info(
"Removing user %s from group %s due to missing state" % (user, group)
)
user.groups.remove(group)

View File

@@ -1,5 +1,5 @@
{% extends "allianceauth/base.html" %}
{% load staticfiles %}
{% load static %}
{% load i18n %}
{% block page_title %}{{ group }} {% trans "Audit Log" %}{% endblock page_title %}
@@ -8,65 +8,73 @@
<div class="col-lg-12">
<br>
{% include 'groupmanagement/menu.html' %}
<div class="panel panel-default">
<div class="panel-heading">
{{ group }} - {% trans 'Audit Log' %}
{{ group }} - {% trans "Audit Log" %}
</div>
<div class="panel-body">
<p>
<div class="panel-body">
<p>
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
{% trans "Back" %}
</a>
</p>
</p>
{% if entries %}
<div class="table-responsive">
<table class="table table-striped" id="log-entries">
<thead>
<th class="text-center" scope="col">{% trans "Date/Time" %}</th>
<th class="text-center" scope="col">{% trans "Requestor" %}</th>
<th class="text-center" scope="col">{% trans "Character" %}</th>
<th class="text-center" scope="col">{% trans "Corporation" %}</th>
<th class="text-center" scope="col">{% trans "Type" %}</th>
<th class="text-center" scope="col">{% trans "Action" %}</th>
<th class="text-center" scope="col">{% trans "Actor" %}</th>
<th scope="col">{% trans "Date/Time" %}</th>
<th scope="col">{% trans "Requestor" %}</th>
<th scope="col">{% trans "Character" %}</th>
<th scope="col">{% trans "Corporation" %}</th>
<th scope="col">{% trans "Type" %}</th>
<th scope="col">{% trans "Action" %}</th>
<th scope="col">{% trans "Actor" %}</th>
</thead>
<tbody>
{% for entry in entries %}
<tr>
<td class="text-center">{{ entry.date }}</td>
<td class="text-center">{{ entry.requestor }}</td>
<td class="text-center">{{ entry.req_char }}</td>
<td class="text-center">{{ entry.req_char.corporation_name }}</td>
<td class="text-center">{{ entry.type_to_str }}</td>
<td>{{ entry.date|date:"Y-M-d, H:i" }}</td>
<td>{{ entry.requestor }}</td>
<td>{{ entry.req_char }}</td>
<td>{{ entry.req_char.corporation_name }}</td>
<td>{{ entry.type_to_str }}</td>
{% if entry.request_type is None %}
<td class="text-center"> Removed</td>
<td>{% trans "Removed" %}</td>
{% else %}
<td class="text-center">{{ entry.action_to_str }}</td>
<td>{{ entry.action_to_str }}</td>
{% endif %}
<td class="text-center">{{ entry.request_actor }}</td>
<td>{{ entry.request_actor }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<p class="text-muted">
All times displayed are EVE/UTC.
{% trans "All times displayed are EVE/UTC." %}
</p>
</div>
{% else %}
{% else %}
<div class="clearfix"></div>
<br>
<div class="alert alert-warning text-center">
<div class="alert alert-warning text-center">
{% trans "No entries found for this group." %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_javascript %}
{% include 'bundles/datatables-js.html' %}
<script type="text/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
{% include 'bundles/moment-js.html' with locale=True %}
<script type="application/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
{% endblock %}
{% block extra_css %}
@@ -74,12 +82,30 @@
{% endblock %}
{% 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(){
$.fn.dataTable.moment('YYYY-MMM-D, HH:mm');
$('#log-entries').DataTable({
order: [[ 0, 'desc' ], [ 1, 'asc' ] ],
order: [[0, 'desc'], [1, 'asc']],
filterDropDown:
{
columns: [
columns: [
{
idx: 1
},
@@ -104,4 +130,3 @@
});
});
{% endblock %}

View File

@@ -1,5 +1,5 @@
{% extends "allianceauth/base.html" %}
{% load staticfiles %}
{% load static %}
{% load i18n %}
{% load evelinks %}
@@ -9,38 +9,35 @@
<div class="col-lg-12">
<br>
{% include 'groupmanagement/menu.html' %}
<div class="panel panel-default">
<div class="panel-heading">
{{ group.name }} - {% trans 'Members' %}
</div>
<div class="panel-body">
<p>
<div class="panel-body">
<p>
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
{% trans "Back" %}
</a>
</p>
{% if group.user_set %}
</p>
{% if group.user_set %}
<div class="table-responsive">
<table class="table table-aa" id="tab_group_members">
<thead>
<tr>
<th class="text-right">{% trans "Portrait" %}</th>
<th class="text-center">{% trans "Character" %}</th>
<th class="text-center">{% trans "Organization" %}</th>
<th class="text-center"></th>
<tr>
<th>{% trans "Character" %}</th>
<th>{% trans "Organization" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for member in members %}
<tr>
<td class="text-right">
{% if member.is_leader %}
<i class="fa fa-star"></i>&nbsp;
{% endif %}
<img src="{{ member.main_char|character_portrait_url:32 }}" class="img-circle">
</td>
<td class="text-center">
<tr>
<td>
<img src="{{ member.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;">
{% if member.main_char %}
<a href="{{ member.main_char|evewho_character_url }}" target="_blank">
{{ member.main_char.character_name }}
@@ -48,28 +45,36 @@
{% else %}
{{ member.user.username }}
{% endif %}
{% if member.is_leader %}
<i class="fas fa-star" title="{% trans "Group leader" %}" style="margin-left: 1rem;"></i>&nbsp;
{% endif %}
</td>
<td class="text-center">
<td>
{% if member.main_char %}
<a href="{{ member.main_char|dotlan_corporation_url }}" target="_blank">
{{ member.main_char.corporation_name }}
</a><br>
{{ member.main_char.alliance_name|default_if_none:"" }}
{% else %}
(unknown)
{% trans "(unknown)" %}
{% endif %}
</td>
<td class="text-center">
<a href="{% url 'groupmanagement:membership_remove' group.id member.user.id %}" class="btn btn-danger"
title="{% trans "Remove from group" %}">
<i class="glyphicon glyphicon-remove"></i>
</td>
<td class="text-right">
<a href="{% url 'groupmanagement:membership_remove' group.id member.user.id %}" class="btn btn-danger" title="{% trans "Remove from group" %}">
<i class="glyphicon glyphicon-remove"></i>
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<p class="text-muted"><i class="fa fa-star"></i>: Group leader</p>
<p class="text-muted">
<i class="fas fa-star"></i>: {% trans "Group leader" %}
</p>
</div>
{% else %}
<div class="alert alert-warning text-center">
@@ -77,7 +82,7 @@
</div>
{% endif %}
</div>
</div>
</div>
{% endblock content %}
@@ -93,10 +98,13 @@
{% block extra_script %}
$(document).ready(function(){
$('#tab_group_members').DataTable({
order: [ [ 1, "asc" ] ],
order: [[0, "asc"]],
columnDefs: [
{ "sortable": false, "targets": [0, 3] },
{
"sortable": false,
"targets": [2]
},
]
});
});
{% endblock %}
{% endblock %}

View File

@@ -1,5 +1,5 @@
{% extends "allianceauth/base.html" %}
{% load staticfiles %}
{% load static %}
{% load i18n %}
{% block page_title %}{% trans "Groups Membership" %}{% endblock page_title %}
@@ -9,31 +9,36 @@
<div class="col-lg-12">
<br>
{% include 'groupmanagement/menu.html' %}
<div class="panel panel-default">
<div class="panel-heading">
{% trans "Groups" %}
</div>
<div class="panel-body">
{% if groups %}
{% if groups %}
<div class="table-responsive">
<table class="table table-aa">
<thead>
<tr>
<th class="text-center">{% trans "Name" %}</th>
<th class="text-center">{% trans "Description" %}</th>
<th class="text-center">{% trans "Status" %}</th>
<th class="text-center">{% trans "Member Count" %}</th>
<th class="text-center"></th>
<th>{% trans "Name" %}</th>
<th>{% trans "Description" %}</th>
<th>{% trans "Status" %}</th>
<th style="white-space: nowrap;">{% trans "Member Count" %}</th>
<th style="min-width: 170px;"></th>
</tr>
</thead>
<tbody>
{% for group in groups %}
<tr>
<td class="text-center">
<a href="{% url 'groupmanagement:membership_list' group.id %}">{{ group.name }}</a>
<td>
<a href="{% url 'groupmanagement:membership' group.id %}">{{ group.name }}</a>
</td>
<td class="text-center">{{ group.authgroup.description }}</td>
<td class="text-center">
<td>{{ group.authgroup.description|linebreaks|urlize }}</td>
<td>
{% if group.authgroup.hidden %}
<span class="label label-info">{% trans "Hidden" %}</span>
{% elif group.authgroup.open %}
@@ -42,21 +47,23 @@
<span class="label label-default">{% trans "Requestable" %}</span>
{% endif %}
</td>
<td class="text-center">
<td class="text-right">
{{ group.num_members }}
</td>
<td class="text-center">
<a href="{% url 'groupmanagement:membership_list' group.id %}" class="btn btn-primary"
title="{% trans "View Members" %}">
<i class="glyphicon glyphicon-eye-open"></i>
<td class="text-right">
<a href="{% url 'groupmanagement:membership' group.id %}" class="btn btn-primary" title="{% trans "View Members" %}">
<i class="glyphicon glyphicon-eye-open"></i>
</a>
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% trans "Audit Members" %}">
<i class="glyphicon glyphicon-list-alt"></i>
</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>
</tr>
{% endfor %}
@@ -72,9 +79,11 @@
</div>
</div>
{% 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 %}
{% block extra_javascript %}
{% include 'bundles/clipboard-js.html' %}
<script>
new ClipboardJS('#clipboard-copy');
</script>
{% endblock %}

View File

@@ -1,10 +1,10 @@
{% extends "allianceauth/base.html" %}
{% load staticfiles %}
{% load static %}
{% load i18n %}
{% block page_title %}{% trans "Available Groups" %}{% endblock page_title %}
{% block extra_css %}{% endblock extra_css %}
url
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Available Groups" %}</h1>
@@ -12,17 +12,18 @@ url
<table class="table table-aa">
<thead>
<tr>
<th class="text-center">{% trans "Name" %}</th>
<th class="text-center">{% trans "Description" %}</th>
<th class="text-center">{% trans "Action" %}</th>
<th>{% trans "Name" %}</th>
<th>{% trans "Description" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for g in groups %}
<tr>
<td class="text-center">{{ g.group.name }}</td>
<td class="text-center">{{ g.group.authgroup.description|urlize }}</td>
<td class="text-center">
<td>{{ g.group.name }}</td>
<td>{{ g.group.authgroup.description|linebreaks|urlize }}</td>
<td class="text-right">
{% if g.group in user.groups.all %}
{% if not g.request %}
<a href="{% url 'groupmanagement:request_leave' g.group.id %}" class="btn btn-danger">
@@ -59,5 +60,4 @@ url
</div>
{% endif %}
</div>
{% endblock content %}

View File

@@ -1,149 +1,161 @@
{% extends "allianceauth/base.html" %}
{% load staticfiles %}
{% load static %}
{% load i18n %}
{% load evelinks %}
{% block page_title %}{% trans "Groups Management" %}{% endblock page_title %}
{% block extra_css %}
<style>
.nav-tabs>li.active>a {
background-color: #ECF0F1 !important;
color: #2C3E50;
}
</style>
{% block extra_css %}
<style>
.nav-tabs > li.active > a {
background-color: rgb(236, 240, 241) !important;
color: rgb(44, 62, 80);
}
</style>
{% endblock extra_css %}
{% block content %}
<div class="col-lg-12">
<br>
{% include 'groupmanagement/menu.html' %}
<ul class="nav nav-tabs">
<li class="active"><a data-toggle="tab" href="#add">{% trans "Join Requests" %}</a></li>
<li><a data-toggle="tab" href="#leave">{% trans "Leave Requests" %}</a></li>
</ul>
<div class="tab-content">
<ul class="nav nav-tabs">
<li class="active">
<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>
<div class="tab-content">
<div id="add" class="tab-pane fade in active panel panel-default">
<div class="panel-body">
{% if acceptrequests %}
<div class="table-responsive">
<table class="table table-aa">
<thead>
<tr>
<th class="text-center"></th>
<th class="text-center">{% trans "Character" %}</th>
<th class="text-center">{% trans "Organization" %}</th>
<th class="text-center">{% trans "Group" %}</th>
<th class="text-center"></th>
</tr>
</thead>
<tbody>
{% for acceptrequest in acceptrequests %}
<tr>
<td class="text-right">
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="img-circle">
</td>
<td class="text-center">
{% if acceptrequest.main_char %}
<a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank">
{{ acceptrequest.main_char.character_name }}
</a>
{% else %}
{{ acceptrequest.user.username }}
{% endif %}
</td>
<td class="text-center">
{% if acceptrequest.main_char %}
<a href="{{ acceptrequest.main_char|dotlan_corporation_url }}" target="_blank">
{{ acceptrequest.main_char.corporation_name }}
</a><br>
{{ acceptrequest.main_char.alliance_name|default_if_none:"" }}
{% else %}
(unknown)
{% endif %}
</td>
<td class="text-center">{{ acceptrequest.group.name }}</td>
<td class="text-center">
<a href="{% url 'groupmanagement:accept_request' acceptrequest.id %}" class="btn btn-success">
{% trans "Accept" %}
</a>
<a href="{% url 'groupmanagement:reject_request' acceptrequest.id %}" class="btn btn-danger">
{% trans "Reject" %}
</a>
</td>
{% if acceptrequests %}
<div class="table-responsive">
<table class="table table-aa">
<thead>
<tr>
<th>{% trans "Character" %}</th>
<th>{% trans "Organization" %}</th>
<th>{% trans "Group" %}</th>
<th></th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-warning text-center">{% trans "No group add requests." %}</div>
{% endif %}
</thead>
<tbody>
{% for acceptrequest in acceptrequests %}
<tr>
<td>
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;">
{% if acceptrequest.main_char %}
<a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank">
{{ acceptrequest.main_char.character_name }}
</a>
{% else %}
{{ acceptrequest.user.username }}
{% endif %}
</td>
<td>
{% if acceptrequest.main_char %}
<a href="{{ acceptrequest.main_char|dotlan_corporation_url }}" target="_blank">
{{ acceptrequest.main_char.corporation_name }}
</a><br>
{{ acceptrequest.main_char.alliance_name|default_if_none:"" }}
{% else %}
{% trans "(unknown)" %}
{% endif %}
</td>
<td>{{ acceptrequest.group.name }}</td>
<td class="text-right">
<a href="{% url 'groupmanagement:accept_request' acceptrequest.id %}" class="btn btn-success">
{% trans "Accept" %}
</a>
<a href="{% url 'groupmanagement:reject_request' acceptrequest.id %}" class="btn btn-danger">
{% trans "Reject" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-warning text-center">{% trans "No group add requests." %}</div>
{% endif %}
</div>
</div>
<div id="leave" class="tab-pane fade panel panel-default">
<div class="panel-body">
{% if leaverequests %}
<div class="table-responsive">
<table class="table table-aa">
<thead>
<tr>
<th class="text-center"></th>
<th class="text-center">{% trans "Character" %}</th>
<th class="text-center">{% trans "Organization" %}</th>
<th class="text-center">{% trans "Group" %}</th>
<th class="text-center"></th>
</tr>
</thead>
<tbody>
{% for leaverequest in leaverequests %}
<tr>
<td class="text-right">
<img src="{{ leaverequest.main_char|character_portrait_url:32 }}" class="img-circle">
</td>
<td class="text-center">
{% if leaverequest.main_char %}
<a href="{{ leaverequest.main_char|evewho_character_url }}" target="_blank">
{{ leaverequest.main_char.character_name }}
</a>
{% else %}
{{ leaverequest.user.username }}
{% endif %}
</td>
<td class="text-center">
{% if leaverequest.main_char %}
<a href="{{ leaverequest.main_char|dotlan_corporation_url }}" target="_blank">
{{ leaverequest.main_char.corporation_name }}
</a><br>
{{ leaverequest.main_char.alliance_name|default_if_none:"" }}
{% else %}
(unknown)
{% endif %}
</td>
<td class="text-center">{{ leaverequest.group.name }}</td>
<td class="text-center">
<a href="{% url 'groupmanagement:leave_accept_request' leaverequest.id %}" class="btn btn-success">
{% trans "Accept" %}
</a>
<a href="{% url 'groupmanagement:leave_reject_request' leaverequest.id %}" class="btn btn-danger">
{% trans "Reject" %}
</a>
</td>
{% if leaverequests %}
<div class="table-responsive">
<table class="table table-aa">
<thead>
<tr>
<th>{% trans "Character" %}</th>
<th>{% trans "Organization" %}</th>
<th>{% trans "Group" %}</th>
<th></th>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-warning text-center">{% trans "No group leave requests." %}</div>
{% endif %}
</thead>
<tbody>
{% for leaverequest in leaverequests %}
<tr>
<td>
<img src="{{ leaverequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;">
{% if leaverequest.main_char %}
<a href="{{ leaverequest.main_char|evewho_character_url }}" target="_blank">
{{ leaverequest.main_char.character_name }}
</a>
{% else %}
{{ leaverequest.user.username }}
{% endif %}
</td>
<td>
{% if leaverequest.main_char %}
<a href="{{ leaverequest.main_char|dotlan_corporation_url }}" target="_blank">
{{ leaverequest.main_char.corporation_name }}
</a><br>
{{ leaverequest.main_char.alliance_name|default_if_none:"" }}
{% else %}
{% trans "(unknown)" %}
{% endif %}
</td>
<td>{{ leaverequest.group.name }}</td>
<td class="text-right">
<a href="{% url 'groupmanagement:leave_accept_request' leaverequest.id %}" class="btn btn-success">
{% trans "Accept" %}
</a>
<a href="{% url 'groupmanagement:leave_reject_request' leaverequest.id %}" class="btn btn-danger">
{% trans "Reject" %}
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-warning text-center">{% trans "No group leave requests." %}</div>
{% endif %}
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@@ -1,10 +1,9 @@
{% load staticfiles %}
{% load static %}
{% load i18n %}
{% load navactive %}
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">{% trans "Toggle navigation" %}</span>
@@ -12,7 +11,7 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="">{% trans "Group Management" %}</a>
<a class="navbar-brand" href="{% url 'groupmanagement:management' %}">{% trans "Group Management" %}</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
@@ -20,11 +19,10 @@
<li class="{% navactive request 'groupmanagement:management' %}">
<a href="{% url 'groupmanagement:management' %}">{% trans "Group Requests" %}</a>
</li>
<li class="{% renavactive request '^/group/membership/' %}">
<li class="{% navactive request 'groupmanagement:membership groupmanagement:audit_log' %}">
<a href="{% url 'groupmanagement:membership' %}">{% trans "Group Membership" %}</a>
</li>
</ul>
</div>
</div>
</nav>

View File

@@ -11,11 +11,7 @@ from allianceauth.eveonline.models import (
EveCharacter, EveCorporationInfo, EveAllianceInfo
)
from ..admin import (
HasLeaderFilter,
GroupAdmin,
Group
)
from ..admin import HasLeaderFilter, GroupAdmin, Group
from . import get_admin_change_view_url
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
@@ -88,33 +84,33 @@ class TestGroupAdmin(TestCase):
# user 1 - corp and alliance, normal user
cls.character_1 = EveCharacter.objects.create(
character_id='1001',
character_id=1001,
character_name='Bruce Wayne',
corporation_id='2001',
corporation_id=2001,
corporation_name='Wayne Technologies',
corporation_ticker='WT',
alliance_id='3001',
alliance_id=3001,
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
)
cls.character_1a = EveCharacter.objects.create(
character_id='1002',
character_id=1002,
character_name='Batman',
corporation_id='2001',
corporation_id=2001,
corporation_name='Wayne Technologies',
corporation_ticker='WT',
alliance_id='3001',
alliance_id=3001,
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
)
alliance = EveAllianceInfo.objects.create(
alliance_id='3001',
alliance_id=3001,
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
executor_corp_id='2001'
executor_corp_id=2001
)
EveCorporationInfo.objects.create(
corporation_id='2001',
corporation_id=2001,
corporation_name='Wayne Technologies',
corporation_ticker='WT',
member_count=42,
@@ -189,10 +185,10 @@ class TestGroupAdmin(TestCase):
alliance=None
)
EveAllianceInfo.objects.create(
alliance_id='3101',
alliance_id=3101,
alliance_name='Lex World Domination',
alliance_ticker='LWD',
executor_corp_id=''
executor_corp_id=2101
)
cls.user_3 = User.objects.create_user(
cls.character_3.character_name.replace(' ', '_'),
@@ -219,8 +215,8 @@ class TestGroupAdmin(TestCase):
"""create autogroups for corps and alliances"""
if _has_auto_groups:
autogroups_config = AutogroupsConfig(
corp_groups = True,
alliance_groups = True
corp_groups=True,
alliance_groups=True
)
autogroups_config.save()
for state in State.objects.all():
@@ -277,7 +273,7 @@ class TestGroupAdmin(TestCase):
if _has_auto_groups:
@patch(MODULE_PATH + '._has_auto_groups', True)
def test_properties_6(self):
def test_properties_7(self):
self._create_autogroups()
expected = ['Auto Group']
my_group = Group.objects\
@@ -337,8 +333,8 @@ class TestGroupAdmin(TestCase):
changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request)
expected = Group.objects.exclude(
managedalliancegroup__isnull=True,
managedcorpgroup__isnull=True
managedalliancegroup__isnull=True,
managedcorpgroup__isnull=True
)
self.assertSetEqual(set(queryset), set(expected))
@@ -394,4 +390,4 @@ class TestGroupAdmin(TestCase):
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)
self.assertEqual(response.status_code, 200)

View File

@@ -7,7 +7,7 @@ from django.urls import reverse
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
from allianceauth.tests.auth_utils import AuthUtils
from ..models import AuthGroup
from ..models import GroupRequest
from ..managers import GroupManager
@@ -15,6 +15,7 @@ class MockUserNotAuthenticated():
def __init__(self):
self.is_authenticated = False
class GroupManagementVisibilityTestCase(TestCase):
@classmethod
def setUpTestData(cls):
@@ -37,22 +38,20 @@ class GroupManagementVisibilityTestCase(TestCase):
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.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)
@@ -182,7 +181,6 @@ class TestGroupManager(TestCase):
]:
self.assertFalse(GroupManager.joinable_group(x, member_state))
def test_joinable_group_guest(self):
guest_state = AuthUtils.get_guest_state()
for x in [
@@ -200,7 +198,6 @@ class TestGroupManager(TestCase):
]:
self.assertFalse(GroupManager.joinable_group(x, guest_state))
def test_get_all_non_internal_groups(self):
result = GroupManager.get_all_non_internal_groups()
expected = {
@@ -224,7 +221,7 @@ class TestGroupManager(TestCase):
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}
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):
@@ -335,3 +332,96 @@ class TestGroupManager(TestCase):
self.assertFalse(
GroupManager.can_manage_group(user, self.group_default)
)
class TestPendingRequestsCountForUser(TestCase):
def setUp(self) -> None:
self.group_1 = Group.objects.create(name="Group 1")
self.group_2 = Group.objects.create(name="Group 2")
self.user_leader_1 = AuthUtils.create_member('Clark Kent')
self.group_1.authgroup.group_leaders.add(self.user_leader_1)
self.user_leader_2 = AuthUtils.create_member('Peter Parker')
self.group_2.authgroup.group_leaders.add(self.user_leader_2)
self.user_requestor = AuthUtils.create_member('Bruce Wayne')
def test_single_request_for_leader(self):
# given user_leader_1 is leader of group_1
# and user_leader_2 is leader of group_2
# when user_requestor is requesting access to group 1
# then return 1 for user_leader 1 and 0 for user_leader_2
GroupRequest.objects.create(
status="pending", user=self.user_requestor, group=self.group_1
)
self.assertEqual(
GroupManager.pending_requests_count_for_user(self.user_leader_1), 1
)
self.assertEqual(
GroupManager.pending_requests_count_for_user(self.user_leader_2), 0
)
def test_return_none_for_none_leader(self):
# given user_requestor is leader of no group
# when user_requestor is requesting access to group 1
# then return 0 for user_requestor
GroupRequest.objects.create(
status="pending", user=self.user_requestor, group=self.group_1
)
self.assertEqual(
GroupManager.pending_requests_count_for_user(self.user_requestor), 0
)
def test_single_leave_request(self):
# given user_leader_2 is leader of group_2
# and user_requestor is member of group 2
# when user_requestor is requesting to leave group 2
# then return 1 for user_leader_2
self.user_requestor.groups.add(self.group_2)
GroupRequest.objects.create(
status="pending",
user=self.user_requestor,
group=self.group_2,
leave_request=True
)
self.assertEqual(
GroupManager.pending_requests_count_for_user(self.user_leader_2), 1
)
def test_join_and_leave_request(self):
# given user_leader_2 is leader of group_2
# and user_requestor is member of group 2
# when user_requestor is requesting to leave group 2
# and user_requestor_2 is requesting to join group 2
# then return 2 for user_leader_2
self.user_requestor.groups.add(self.group_2)
user_requestor_2 = AuthUtils.create_member("Lex Luther")
GroupRequest.objects.create(
status="pending",
user=user_requestor_2,
group=self.group_2
)
GroupRequest.objects.create(
status="pending",
user=self.user_requestor,
group=self.group_2,
leave_request=True
)
self.assertEqual(
GroupManager.pending_requests_count_for_user(self.user_leader_2), 2
)
def test_single_request_for_user_with_management_perm(self):
# given user_leader_4 which is leafer of no group
# but has the management permissions
# when user_requestor is requesting access to group 1
# then return 1 for user_leader_4
user_leader_4 = AuthUtils.create_member("Lex Luther")
AuthUtils.add_permission_to_user_by_name("auth.group_management", user_leader_4)
user_leader_4 = User.objects.get(pk=user_leader_4.pk)
GroupRequest.objects.create(
status="pending", user=self.user_requestor, group=self.group_1
)
self.assertEqual(
GroupManager.pending_requests_count_for_user(self.user_leader_1), 1
)

View File

@@ -1,61 +1,100 @@
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.eveonline.models import EveCorporationInfo
from allianceauth.eveonline.autogroups.models import AutogroupsConfig
from allianceauth.tests.auth_utils import AuthUtils
from ..signals import check_groups_on_state_change
class GroupManagementStateTestCase(TestCase):
class TestCheckGroupsOnStateChange(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.character = AuthUtils.add_main_character_2(
cls.user, 'test character', 1001, corp_id=2001, corp_name='test corp 1', corp_ticker='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.user.profile.refresh_from_db()
cls.corp_1 = EveCorporationInfo.objects.create(
corporation_id=2001, corporation_name='test corp 1', corporation_ticker='C1', 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.corp_2 = EveCorporationInfo.objects.create(
corporation_id=2002, corporation_name='test corp 2', corporation_ticker='C2', 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()
cls.guest_state = AuthUtils.get_guest_state()
cls.test_state_1 = AuthUtils.create_state('test_state_1', 500)
cls.test_state_2 = AuthUtils.create_state('test_state_2', 600)
def setUp(self):
self.user.refresh_from_db()
self.state.refresh_from_db()
self.user.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)
"""
given user is member of: state group, normal group and auto group
when user looses state
then user is automatically kicked from state group
and remains member of normal group and auto group
"""
# setup
state_group = Group.objects.create(name='state_group')
state_group.authgroup.states.add(self.test_state_1)
state_group.authgroup.internal = False
state_group.save()
normal_group = Group.objects.create(name='normal_group')
normal_group.authgroup.internal = False
normal_group.save()
internal_group = Group.objects.create(name='internal_group')
autogroup_config = AutogroupsConfig.objects.create(corp_groups=True)
autogroup_config.states.add(self.test_state_1)
autogroup_config.states.add(self.guest_state)
auto_group = autogroup_config.corp_managed_groups.first()
internal_state_group = Group.objects.create(name='internal_state_group')
internal_state_group.authgroup.states.add(self.test_state_1)
self.test_state_1.member_corporations.add(self.corp_1)
self.user.groups.add(normal_group)
self.user.groups.add(internal_group)
self.user.groups.add(state_group)
self.user.groups.add(internal_state_group)
# user changes state back to guest
self.test_state_1.member_corporations.clear()
# assert
self._refresh_user()
self.assertEqual(self.user.profile.state, self.state)
self.assertEqual(self.user.profile.state, self.guest_state)
groups = self.user.groups.all()
self.assertIn(self.state_group, groups) #keeps group
self.assertIn(self.open_group, groups) #public group unafected
self.assertNotIn(state_group, groups) # looses state group
self.assertNotIn(internal_state_group, groups) # looses state group
self.assertIn(normal_group, groups) # normal group unafected
self.assertIn(internal_group, groups) # internal group unafected
self.assertIn(auto_group, groups) # auto group unafected
self.state.member_corporations.clear()
def test_change_to_other_state(self):
"""
given a state group with 2 allowed states
when user changes from one state to the other
then user remains member of that group
"""
# setup
state_group = Group.objects.create(name='state_group')
state_group.authgroup.states.add(self.test_state_1)
state_group.authgroup.states.add(self.test_state_2)
self.test_state_1.member_corporations.add(self.corp_1)
self.test_state_2.member_corporations.add(self.corp_2)
self.user.groups.add(state_group)
# user changes state back to guest
self.character.corporation_id = 2002
self.character.corporation_name = "test corp 2"
self.character.save()
# assert
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
self.assertEqual(self.user.profile.state, self.test_state_2)
groups = self.user.groups.all()
self.assertIn(state_group, groups)

View File

@@ -1,32 +1,52 @@
from . import views
from django.conf.urls import include, url
app_name = 'groupmanagement'
from django.conf.urls import url
app_name = "groupmanagement"
urlpatterns = [
url(r'^groups/', views.groups_view, name='groups'),
url(r'^group/', include([
url(r'^management/', views.group_management,
name='management'),
url(r'^membership/$', views.group_membership,
name='membership'),
url(r'^membership/(\w+)/$', views.group_membership_list,
name='membership_list'),
url(r'^membership/(\w+)/audit/$', views.group_membership_audit, name="audit_log"),
url(r'^membership/(\w+)/remove/(\w+)/$', views.group_membership_remove,
name='membership_remove'),
url(r'^request_add/(\w+)', views.group_request_add,
name='request_add'),
url(r'^request/accept/(\w+)', views.group_accept_request,
name='accept_request'),
url(r'^request/reject/(\w+)', views.group_reject_request,
name='reject_request'),
url(r'^request_leave/(\w+)', views.group_request_leave,
name='request_leave'),
url(r'leave_request/accept/(\w+)', views.group_leave_accept_request,
name='leave_accept_request'),
url(r'^leave_request/reject/(\w+)', views.group_leave_reject_request,
name='leave_reject_request'),
])),
# groups
url(r"^groups/$", views.groups_view, name="groups"),
url(r"^group/request/join/(\w+)/$", views.group_request_add, name="request_add"),
url(
r"^group/request/leave/(\w+)/$", views.group_request_leave, name="request_leave"
),
# group management
url(r"^groupmanagement/requests/$", views.group_management, name="management"),
url(r"^groupmanagement/membership/$", views.group_membership, name="membership"),
url(
r"^groupmanagement/membership/(\w+)/$",
views.group_membership_list,
name="membership",
),
url(
r"^groupmanagement/membership/(\w+)/audit-log/$",
views.group_membership_audit,
name="audit_log",
),
url(
r"^groupmanagement/membership/(\w+)/remove/(\w+)/$",
views.group_membership_remove,
name="membership_remove",
),
url(
r"^groupmanagement/request/join/accept/(\w+)/$",
views.group_accept_request,
name="accept_request",
),
url(
r"^groupmanagement/request/join/reject/(\w+)/$",
views.group_reject_request,
name="reject_request",
),
url(
r"^groupmanagement/request/leave/accept/(\w+)/$",
views.group_leave_accept_request,
name="leave_accept_request",
),
url(
r"^groupmanagement/request/leave/reject/(\w+)/$",
views.group_leave_reject_request,
name="leave_reject_request",
),
]

View File

@@ -6,7 +6,6 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.models import Group
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
from django.core.paginator import Paginator, EmptyPage
from django.db.models import Count
from django.http import Http404
from django.shortcuts import render, redirect, get_object_or_404
@@ -28,7 +27,7 @@ def group_management(request):
acceptrequests = []
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):
# Full access
group_requests = base_group_query.all()
@@ -76,7 +75,6 @@ def group_membership_audit(request, group_id):
logger.debug("group_management_audit called by user %s" % request.user)
group = get_object_or_404(Group, id=group_id)
try:
# Check its a joinable group i.e. not corp or internal
# And the user has permission to manage it
if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
@@ -93,8 +91,6 @@ def group_membership_audit(request, group_id):
return render(request, 'groupmanagement/audit.html', context=render_items)
@login_required
@user_passes_test(GroupManager.can_manage_groups)
def group_membership_list(request, group_id):
@@ -107,7 +103,7 @@ def group_membership_list(request, group_id):
# Check its a joinable group i.e. not corp or internal
# And the user has permission to manage it
if (not GroupManager.check_internal_group(group)
if (not GroupManager.check_internal_group(group)
or not GroupManager.can_manage_group(request.user, group)
):
logger.warning(
@@ -124,7 +120,7 @@ def group_membership_list(request, group_id):
for member in \
group.user_set\
.all()\
.select_related('profile')\
.select_related('profile', 'profile__main_character')\
.order_by('profile__main_character__character_name'):
members.append({
@@ -136,7 +132,7 @@ def group_membership_list(request, group_id):
render_items = {'group': group, 'members': members}
return render(
request, 'groupmanagement/groupmembers.html',
request, 'groupmanagement/groupmembers.html',
context=render_items
)
@@ -170,7 +166,7 @@ def group_membership_remove(request, group_id, user_id):
except ObjectDoesNotExist:
messages.warning(request, _("Group does not exist"))
return redirect('groupmanagement:membership_list', group_id)
return redirect('groupmanagement:membership', group_id)
@login_required
@@ -316,18 +312,18 @@ def group_leave_reject_request(request, group_request_id):
@login_required
def groups_view(request):
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 = []
for group in groups_qs:
for group in groups_qs:
group_request = GroupRequest.objects\
.filter(user=request.user)\
.filter(group=group)
groups.append({
'group': group,
'group': group,
'request': group_request[0] if group_request else None
})
@@ -360,6 +356,9 @@ def group_request_add(request, group_id):
if group.authgroup.open:
logger.info("%s joining %s as is an open group" % (request.user, group))
request.user.groups.add(group)
request_info = request.user.username + ":" + group.name
log = RequestLog(request_type=False, group=group, request_info=request_info, action=1, request_actor=request.user)
log.save()
return redirect("groupmanagement:groups")
req = GroupRequest.objects.filter(user=request.user, group=group)
if len(req) > 0:
@@ -393,6 +392,9 @@ def group_request_leave(request, group_id):
return redirect('groupmanagement:groups')
if group.authgroup.open:
logger.info("%s leaving %s as is an open group" % (request.user, group))
request_info = request.user.username + ":" + group.name
log = RequestLog(request_type=True, group=group, request_info=request_info, action=1, request_actor=request.user)
log.save()
request.user.groups.remove(group)
return redirect("groupmanagement:groups")
req = GroupRequest.objects.filter(user=request.user, group=group)
@@ -402,6 +404,9 @@ def group_request_leave(request, group_id):
return redirect("groupmanagement:groups")
if getattr(settings, 'AUTO_LEAVE', False):
logger.info("%s leaving joinable group %s due to auto_leave" % (request.user, group))
request_info = request.user.username + ":" + group.name
log = RequestLog(request_type=True, group=group, request_info=request_info, action=1, request_actor=request.user)
log.save()
request.user.groups.remove(group)
return redirect('groupmanagement:groups')
grouprequest = GroupRequest()

View File

@@ -1,17 +1,25 @@
from allianceauth.services.hooks import MenuItemHook, UrlHook
from django.utils.translation import ugettext_lazy as _
from allianceauth import hooks
from allianceauth.hrapplications import urls
from allianceauth.services.hooks import MenuItemHook, UrlHook
from . import urls
from .models import Application
class ApplicationsMenu(MenuItemHook):
def __init__(self):
MenuItemHook.__init__(self,
_('Applications'),
'fa fa-file-o fa-fw',
'far fa-file fa-fw',
'hrapplications:index',
navactive=['hrapplications:'])
def render(self, request):
app_count = Application.objects.pending_requests_count_for_user(request.user)
self.count = app_count if app_count and app_count > 0 else None
return MenuItemHook.render(self, request)
@hooks.register('menu_item_hook')
def register_menu():

View File

@@ -0,0 +1,25 @@
from django.contrib.auth.models import User
from django.db import models
from typing import Optional
class ApplicationManager(models.Manager):
def pending_requests_count_for_user(self, user: User) -> Optional[int]:
"""Returns the number of pending group requests for the given user"""
if user.is_superuser:
return self.filter(approved__isnull=True).count()
elif user.has_perm("auth.human_resources"):
main_character = user.profile.main_character
if main_character:
return (
self
.select_related("form__corp")
.filter(form__corp__corporation_id=main_character.corporation_id)
.filter(approved__isnull=True)
.count()
)
else:
return None
else:
return None

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.1.1 on 2020-09-18 14:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hrapplications', '0006_remove_legacy_models'),
]
operations = [
migrations.AlterField(
model_name='application',
name='approved',
field=models.BooleanField(blank=True, default=None, null=True),
),
]

View File

@@ -2,8 +2,9 @@ from django.contrib.auth.models import User
from django.db import models
from sortedm2m.fields import SortedManyToManyField
from allianceauth.eveonline.models import EveCharacter
from allianceauth.eveonline.models import EveCorporationInfo
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo
from .managers import ApplicationManager
class ApplicationQuestion(models.Model):
@@ -22,6 +23,7 @@ class ApplicationChoice(models.Model):
def __str__(self):
return self.choice_text
class ApplicationForm(models.Model):
questions = SortedManyToManyField(ApplicationQuestion)
corp = models.OneToOneField(EveCorporationInfo, on_delete=models.CASCADE)
@@ -33,11 +35,13 @@ class ApplicationForm(models.Model):
class Application(models.Model):
form = models.ForeignKey(ApplicationForm, on_delete=models.CASCADE, related_name='applications')
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='applications')
approved = models.NullBooleanField(blank=True, null=True, default=None)
approved = models.BooleanField(blank=True, null=True, default=None)
reviewer = models.ForeignKey(User, on_delete=models.SET_NULL, blank=True, null=True)
reviewer_character = models.ForeignKey(EveCharacter, on_delete=models.SET_NULL, blank=True, null=True)
created = models.DateTimeField(auto_now_add=True)
objects = ApplicationManager()
def __str__(self):
return str(self.user) + " Application To " + str(self.form)

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
{% extends "allianceauth/base.html" %}
{% load staticfiles %}
{% load static %}
{% load bootstrap %}
{% load i18n %}

View File

@@ -1 +1,103 @@
# Create your tests here.
from django.contrib.auth.models import User
from django.test import TestCase
from allianceauth.eveonline.models import EveCorporationInfo
from allianceauth.tests.auth_utils import AuthUtils
from .models import Application, ApplicationForm, ApplicationQuestion, ApplicationChoice
class TestApplicationManagersPendingRequestsCountForUser(TestCase):
def setUp(self) -> None:
self.corporation_1 = EveCorporationInfo.objects.create(
corporation_id=2001, corporation_name="Wayne Tech", member_count=42
)
self.corporation_2 = EveCorporationInfo.objects.create(
corporation_id=2011, corporation_name="Lex Corp", member_count=666
)
question = ApplicationQuestion.objects.create(title="Dummy Question")
ApplicationChoice.objects.create(question=question, choice_text="yes")
ApplicationChoice.objects.create(question=question, choice_text="no")
self.form_corporation_1 = ApplicationForm.objects.create(
corp=self.corporation_1
)
self.form_corporation_1.questions.add(question)
self.form_corporation_2 = ApplicationForm.objects.create(
corp=self.corporation_2
)
self.form_corporation_2.questions.add(question)
self.user_requestor = AuthUtils.create_member("Peter Parker")
self.user_manager = AuthUtils.create_member("Bruce Wayne")
AuthUtils.add_main_character_2(
self.user_manager,
self.user_manager.username,
1001,
self.corporation_1.corporation_id,
self.corporation_1.corporation_name,
)
AuthUtils.add_permission_to_user_by_name(
"auth.human_resources", self.user_manager
)
self.user_manager = User.objects.get(pk=self.user_manager.pk)
def test_no_pending_application(self):
# given manager of corporation 1 has permission
# when no application is pending for corporation 1
# return 0
self.assertEqual(
Application.objects.pending_requests_count_for_user(self.user_manager), 0
)
def test_single_pending_application(self):
# given manager of corporation 1 has permission
# when 1 application is pending for corporation 1
# return 1
Application.objects.create(
form=self.form_corporation_1, user=self.user_requestor
)
self.assertEqual(
Application.objects.pending_requests_count_for_user(self.user_manager), 1
)
def test_user_has_no_permission(self):
# given user has no permission
# when 1 application is pending
# return None
self.assertIsNone(
Application.objects.pending_requests_count_for_user(self.user_requestor)
)
def test_two_pending_applications_for_different_corporations_normal_manager(self):
# given manager of corporation 1 has permission
# when 1 application is pending for corporation 1
# and 1 application is pending for corporation 2
# return 1
Application.objects.create(
form=self.form_corporation_1, user=self.user_requestor
)
Application.objects.create(
form=self.form_corporation_2, user=self.user_requestor
)
self.assertEqual(
Application.objects.pending_requests_count_for_user(self.user_manager), 1
)
def test_two_pending_applications_for_different_corporations_manager_is_super(self):
# given manager of corporation 1 has permission
# when 1 application is pending for corporation 1
# and 1 application is pending for corporation 2
# return 1
Application.objects.create(
form=self.form_corporation_1, user=self.user_requestor
)
Application.objects.create(
form=self.form_corporation_2, user=self.user_requestor
)
superuser = User.objects.create_superuser(
"Superman", "superman@example.com", "password"
)
self.assertEqual(
Application.objects.pending_requests_count_for_user(superuser), 2
)

View File

@@ -6,16 +6,16 @@
# Translators:
# Erik Kalkoken <erik.kalkoken@gmail.com>, 2020
# Joel Falknau <ozirascal@gmail.com>, 2020
# Rounon Dax <rounon.dax@terra-nanotech.de>, 2020
# Peter Pfeufer <rounon.dax@terra-nanotech.de>, 2020
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-08 00:57+0000\n"
"POT-Creation-Date: 2020-11-20 05:33+0000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
"Last-Translator: Rounon Dax <rounon.dax@terra-nanotech.de>, 2020\n"
"Last-Translator: Peter Pfeufer <rounon.dax@terra-nanotech.de>, 2020\n"
"Language-Team: German (https://www.transifex.com/alliance-auth/teams/107430/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -28,74 +28,74 @@ msgid "A main character is required to perform that action. Add one below."
msgstr ""
"Für diese Aktion wird ein Hauptcharacter benötigt. Bitte füge einen hinzu."
#: allianceauth/authentication/forms.py:6
#: allianceauth/authentication/forms.py:5
msgid "Email"
msgstr "E-Mail"
#: allianceauth/authentication/models.py:76
msgid "State Changed"
msgstr "Status geändert"
#: allianceauth/authentication/models.py:77
#: allianceauth/authentication/models.py:78
#, python-format
msgid "Your user state has been changed to %(state)s"
msgstr "Dein Nutzerstatus hat sich geändert zu %(state)s"
msgid "State changed to: %s"
msgstr "Status geändert zu %s"
#: allianceauth/authentication/models.py:79
#, python-format
msgid "Your user's state is now: %(state)s"
msgstr "Dein Nutzerstatus ist nun %(state)s"
#: allianceauth/authentication/templates/authentication/dashboard.html:5
#: allianceauth/authentication/templates/authentication/dashboard.html:8
#: allianceauth/templates/allianceauth/side-menu.html:10
#: allianceauth/templates/allianceauth/side-menu.html:11
msgid "Dashboard"
msgstr "Dashboard"
#: allianceauth/authentication/templates/authentication/dashboard.html:17
#: allianceauth/corputils/templates/corputils/corpstats.html:116
#: allianceauth/corputils/templates/corputils/search.html:16
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
msgid "Main Character"
msgstr "Hauptcharakter"
#: allianceauth/authentication/templates/authentication/dashboard.html:18
#, python-format
msgid ""
"\n"
" Main Character (State: %(state)s)\n"
" "
msgstr ""
"\n"
"Hauptcharakter (Status: %(state)s)"
#: allianceauth/authentication/templates/authentication/dashboard.html:77
#: allianceauth/authentication/templates/authentication/dashboard.html:81
msgid "No main character set."
msgstr "Kein Hauptcharakter gesetzt."
#: allianceauth/authentication/templates/authentication/dashboard.html:84
#: allianceauth/authentication/templates/authentication/dashboard.html:88
msgid "Add Character"
msgstr "Charakter hinzufügen"
#: allianceauth/authentication/templates/authentication/dashboard.html:88
#: allianceauth/authentication/templates/authentication/dashboard.html:92
msgid "Change Main"
msgstr "Hauptcharakter ändern"
#: allianceauth/authentication/templates/authentication/dashboard.html:97
#: allianceauth/authentication/templates/authentication/dashboard.html:101
msgid "Group Memberships"
msgstr "Gruppen"
#: allianceauth/authentication/templates/authentication/dashboard.html:117
#: allianceauth/authentication/templates/authentication/dashboard.html:121
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
#: allianceauth/hrapplications/templates/hrapplications/view.html:41
msgid "Characters"
msgstr "Charaktere"
#: allianceauth/authentication/templates/authentication/dashboard.html:125
#: allianceauth/authentication/templates/authentication/dashboard.html:129
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:22
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:15
#: allianceauth/hrapplications/templates/hrapplications/view.html:45
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34
msgid "Name"
msgstr "Name"
#: allianceauth/authentication/templates/authentication/dashboard.html:126
#: allianceauth/authentication/templates/authentication/dashboard.html:130
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:23
#: allianceauth/hrapplications/templates/hrapplications/view.html:46
msgid "Corp"
msgstr "Corp"
#: allianceauth/authentication/templates/authentication/dashboard.html:127
#: allianceauth/authentication/templates/authentication/dashboard.html:131
#: allianceauth/corputils/templates/corputils/corpstats.html:77
#: allianceauth/hrapplications/templates/hrapplications/view.html:47
msgid "Alliance"
@@ -168,11 +168,11 @@ msgstr ""
msgid "Unable to authenticate as the selected character."
msgstr "Authentifizierung mit dem ausgewählten Charakter nicht möglich."
#: allianceauth/authentication/views.py:148
#: allianceauth/authentication/views.py:151
msgid "Registration token has expired."
msgstr "Token zur Registrierung ist abgelaufen."
#: allianceauth/authentication/views.py:200
#: allianceauth/authentication/views.py:206
msgid ""
"Sent confirmation email. Please follow the link to confirm your email "
"address."
@@ -180,13 +180,13 @@ msgstr ""
"Bestätigungsmail gesendet. Bitte folge dem Link in der E-Mail zur "
"Bestätigung."
#: allianceauth/authentication/views.py:205
#: allianceauth/authentication/views.py:211
msgid "Confirmed your email address. Please login to continue."
msgstr ""
"Deine E-Mail Adresse wurde bestätigt. Bitte log Dich ein um fortzufahren."
#: allianceauth/authentication/views.py:210
msgid "Registraion of new accounts it not allowed at this time."
#: allianceauth/authentication/views.py:216
msgid "Registration of new accounts is not allowed at this time."
msgstr "Registrierung von neuen Konten ist zur Zeit nicht erlaubt."
#: allianceauth/corputils/auth_hooks.py:10
@@ -234,16 +234,16 @@ msgstr "Letzes Update:"
#: allianceauth/corputils/templates/corputils/search.html:13
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:37
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:96
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:51
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:109
msgid "Character"
msgstr "Charakter"
#: allianceauth/corputils/templates/corputils/corpstats.html:76
#: allianceauth/corputils/templates/corputils/search.html:14
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:28
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
#: allianceauth/hrapplications/templates/hrapplications/management.html:27
#: allianceauth/hrapplications/templates/hrapplications/management.html:84
#: allianceauth/hrapplications/templates/hrapplications/management.html:129
@@ -259,6 +259,16 @@ msgstr "Corporation"
msgid "Killboard"
msgstr "Killboard"
#: allianceauth/corputils/templates/corputils/corpstats.html:116
#: allianceauth/corputils/templates/corputils/search.html:16
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
msgid "Main Character"
msgstr "Hauptcharakter"
#: allianceauth/corputils/templates/corputils/corpstats.html:117
#: allianceauth/corputils/templates/corputils/search.html:17
msgid "Main Corporation"
@@ -542,31 +552,35 @@ msgstr "Flottenteilnahme registriert."
msgid "FAT link has expired."
msgstr "FAT-Link ist abgelaufen."
#: allianceauth/groupmanagement/auth_hooks.py:17
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:14
msgid "Group Management"
msgstr "Gruppenverwaltung"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:13
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:14
msgid "Audit Log"
msgstr "Protokoll"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:18
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:20
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:20
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:21
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
msgid "Back"
msgstr "Zurück"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:28
msgid "Date/Time"
msgstr "Datum/Uhrzeit"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:26
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
msgid "Requestor"
msgstr "Antragsteller"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:32
msgid "Type"
msgstr "Typ"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:17
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:33
#: allianceauth/notifications/templates/notifications/list.html:37
#: allianceauth/notifications/templates/notifications/list.html:69
#: allianceauth/optimer/templates/optimer/fleetoptable.html:18
@@ -577,11 +591,19 @@ msgstr "Typ"
msgid "Action"
msgstr "Aktion"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:34
msgid "Actor"
msgstr "Ausführender"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:47
msgid "Removed"
msgstr "Entfernt"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:59
msgid "All times displayed are EVE/UTC."
msgstr "Alle angezeigten Zeiten sind EVE/UTC"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:66
msgid "No entries found for this group."
msgstr "Keine Einträge für diese Gruppe gefunden."
@@ -589,22 +611,24 @@ msgstr "Keine Einträge für diese Gruppe gefunden."
msgid "Group Members"
msgstr "Gruppenmitglieder"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:28
msgid "Portrait"
msgstr "Portrait"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:38
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:31
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:52
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:110
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
msgid "Organization"
msgstr "Organization"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:64
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:61
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:78
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:136
msgid "(unknown)"
msgstr "(unbekannt)"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:66
msgid "Remove from group"
msgstr "Aus Gruppe entfernen"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:76
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:81
msgid "No group members to list."
msgstr "Keine Gruppenmitglieder vorhanden."
@@ -612,56 +636,56 @@ msgstr "Keine Gruppenmitglieder vorhanden."
msgid "Groups Membership"
msgstr "Gruppenmitgliedschaft"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:15
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
#: allianceauth/templates/allianceauth/side-menu.html:15
#: allianceauth/templates/allianceauth/side-menu.html:16
msgid "Groups"
msgstr "Gruppen"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
msgid "Description"
msgstr "Beschreibung"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:26
#: allianceauth/hrapplications/templates/hrapplications/management.html:28
#: allianceauth/hrapplications/templates/hrapplications/management.html:85
#: allianceauth/hrapplications/templates/hrapplications/management.html:130
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:27
#: allianceauth/srp/templates/srp/data.html:97
#: allianceauth/srp/templates/srp/data.html:98
msgid "Status"
msgstr "Status"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:27
msgid "Member Count"
msgstr "Mitgliederzahl"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:38
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:43
msgid "Hidden"
msgstr "Verborgen"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:40
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
#: allianceauth/templates/allianceauth/admin-status/overview.html:12
msgid "Open"
msgstr "Öffnen"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:42
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:47
msgid "Requestable"
msgstr "Anfragbar"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:50
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
msgid "View Members"
msgstr "Mitglieder ansehen"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:53
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:60
msgid "Audit Members"
msgstr "Mitglieder Protokoll"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
msgid "Copy Direrct Join Link"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
msgid "Copy Direct Join Link"
msgstr "Direktlink kopieren"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:75
msgid "No groups to list."
msgstr "Keine Gruppen vorhanden."
@@ -670,19 +694,19 @@ msgstr "Keine Gruppen vorhanden."
msgid "Available Groups"
msgstr "Verfügbare Gruppen"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:29
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:30
msgid "Leave"
msgstr "Verlassen"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:39
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:40
msgid "Join"
msgstr "Beitreten"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:43
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:44
msgid "Request"
msgstr "Anfrage"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:58
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:59
msgid "No groups available."
msgstr "Keine Gruppen verfügbar"
@@ -690,77 +714,72 @@ msgstr "Keine Gruppen verfügbar"
msgid "Groups Management"
msgstr "Gruppenverwaltung"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:23
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:25
msgid "Join Requests"
msgstr "Beitrittsgesuche"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:34
msgid "Leave Requests"
msgstr "Austrittsgesuche"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:39
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:98
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:53
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:111
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
#: allianceauth/services/modules/openfire/forms.py:6
msgid "Group"
msgstr "Gruppen"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:71
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:130
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:84
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
msgid "Accept"
msgstr "Akzeptieren"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:74
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:133
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:146
#: allianceauth/hrapplications/templates/hrapplications/view.html:85
msgid "Reject"
msgstr "Ablehnen"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:83
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
msgid "No group add requests."
msgstr "Keine Gruppenbeitrittsanfragen."
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:155
msgid "No group leave requests."
msgstr "Keine Gruppenaustrittsanfragen"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:10
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:9
msgid "Toggle navigation"
msgstr "Toggle Navigation"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15
#: allianceauth/templates/allianceauth/side-menu.html:23
msgid "Group Management"
msgstr "Gruppenverwaltung"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:21
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:20
msgid "Group Requests"
msgstr "Gruppenanfragen"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:23
msgid "Group Membership"
msgstr "Gruppenmitgliedschaft"
#: allianceauth/groupmanagement/views.py:166
#: allianceauth/groupmanagement/views.py:162
#, python-format
msgid "Removed user %(user)s from group %(group)s."
msgstr "Benutzer %(user)s von Gruppe %(group)s entfernt."
#: allianceauth/groupmanagement/views.py:168
#: allianceauth/groupmanagement/views.py:164
msgid "User does not exist in that group"
msgstr "Benutzer existiert nicht in dieser Gruppe"
#: allianceauth/groupmanagement/views.py:171
#: allianceauth/groupmanagement/views.py:167
msgid "Group does not exist"
msgstr "Gruppe existiert nicht"
#: allianceauth/groupmanagement/views.py:198
#: allianceauth/groupmanagement/views.py:194
#, python-format
msgid "Accepted application from %(mainchar)s to %(group)s."
msgstr "Beitrittsgesuch von %(mainchar)s zur Gruppe %(group)s zugestimmt."
#: allianceauth/groupmanagement/views.py:205
#: allianceauth/groupmanagement/views.py:238
#: allianceauth/groupmanagement/views.py:201
#: allianceauth/groupmanagement/views.py:234
#, python-format
msgid ""
"An unhandled error occurred while processing the application from "
@@ -769,18 +788,18 @@ msgstr ""
"Bei der Bearbeitung des Beitrittsgesuchs von %(mainchar)s zur Gruppe "
"%(group)s ist ein unbehandelter Fehler aufgetreten."
#: allianceauth/groupmanagement/views.py:231
#: allianceauth/groupmanagement/views.py:227
#, python-format
msgid "Rejected application from %(mainchar)s to %(group)s."
msgstr "Beitrittsgesuch von %(mainchar)s zur Gruppe %(group)s abgelehnt."
#: allianceauth/groupmanagement/views.py:267
#: allianceauth/groupmanagement/views.py:263
#, python-format
msgid "Accepted application from %(mainchar)s to leave %(group)s."
msgstr "Austrittsgesuch von %(mainchar)s für Gruppe %(group)s akzeptiert."
#: allianceauth/groupmanagement/views.py:273
#: allianceauth/groupmanagement/views.py:307
#: allianceauth/groupmanagement/views.py:269
#: allianceauth/groupmanagement/views.py:303
#, python-format
msgid ""
"An unhandled error occurred while processing the application from "
@@ -789,60 +808,60 @@ msgstr ""
"Bei der Bearbeitung des Austrittsgesuchs von %(mainchar)s für Gruppe "
"%(group)s ist ein unbehandelter Fehler aufgetreten."
#: allianceauth/groupmanagement/views.py:300
#: allianceauth/groupmanagement/views.py:296
#, python-format
msgid "Rejected application from %(mainchar)s to leave %(group)s."
msgstr "Austrittsgesuch von %(mainchar)s für Gruppe %(group)s abgelehnt."
#: allianceauth/groupmanagement/views.py:346
#: allianceauth/groupmanagement/views.py:358
#: allianceauth/groupmanagement/views.py:342
#: allianceauth/groupmanagement/views.py:354
msgid "You cannot join that group"
msgstr "Du kannst dieser Gruppe nicht beitreten"
#: allianceauth/groupmanagement/views.py:352
#: allianceauth/groupmanagement/views.py:348
msgid "You are already a member of that group."
msgstr "Du bist bereits Mitglied dieser Gruppe."
#: allianceauth/groupmanagement/views.py:367
#: allianceauth/groupmanagement/views.py:363
msgid "You already have a pending application for that group."
msgstr "Du hast Dich bereits für diese Gruppe beworben."
#: allianceauth/groupmanagement/views.py:370
#: allianceauth/groupmanagement/views.py:408
#: allianceauth/groupmanagement/views.py:366
#: allianceauth/groupmanagement/views.py:404
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
#: allianceauth/hrapplications/templates/hrapplications/management.html:144
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:38
#: allianceauth/hrapplications/templates/hrapplications/view.html:20
#: allianceauth/srp/templates/srp/data.html:125
#: allianceauth/srp/templates/srp/data.html:134
#: allianceauth/srp/templates/srp/management.html:81
msgid "Pending"
msgstr "Beantragt"
#: allianceauth/groupmanagement/views.py:376
#: allianceauth/groupmanagement/views.py:372
#, python-format
msgid "Applied to group %(group)s."
msgstr "Beitritt zur Gruppe %(group)s beantragt."
#: allianceauth/groupmanagement/views.py:387
#: allianceauth/groupmanagement/views.py:383
msgid "You cannot leave that group"
msgstr "Du kannst diese Gruppe nicht verlassen"
#: allianceauth/groupmanagement/views.py:392
#: allianceauth/groupmanagement/views.py:388
msgid "You are not a member of that group"
msgstr "Du bist kein Mitglied dieser Gruppe"
#: allianceauth/groupmanagement/views.py:401
#: allianceauth/groupmanagement/views.py:397
msgid "You already have a pending leave request for that group."
msgstr "Du hast bereits ein ausstehendes Austrittsgesuch für diese Gruppe."
#: allianceauth/groupmanagement/views.py:414
#: allianceauth/groupmanagement/views.py:410
#, python-format
msgid "Applied to leave group %(group)s."
msgstr "Austrittsgesuch für Gruppe %(group)s gesendet."
#: allianceauth/hrapplications/auth_hooks.py:10
#: allianceauth/hrapplications/auth_hooks.py:13
msgid "Applications"
msgstr "Bewerbungen"
@@ -899,7 +918,7 @@ msgstr "Benutzername"
#: allianceauth/hrapplications/templates/hrapplications/management.html:131
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:28
#: allianceauth/hrapplications/templates/hrapplications/view.html:75
#: allianceauth/srp/templates/srp/data.html:99
#: allianceauth/srp/templates/srp/data.html:100
#: allianceauth/srp/templates/srp/management.html:46
msgid "Actions"
msgstr "Aktionen"
@@ -909,7 +928,7 @@ msgstr "Aktionen"
#: allianceauth/hrapplications/templates/hrapplications/management.html:147
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:40
#: allianceauth/hrapplications/templates/hrapplications/view.html:16
#: allianceauth/srp/templates/srp/data.html:117
#: allianceauth/srp/templates/srp/data.html:126
msgid "Approved"
msgstr "Akzeptiert"
@@ -917,7 +936,7 @@ msgstr "Akzeptiert"
#: allianceauth/hrapplications/templates/hrapplications/management.html:104
#: allianceauth/hrapplications/templates/hrapplications/management.html:149
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:42
#: allianceauth/srp/templates/srp/data.html:121
#: allianceauth/srp/templates/srp/data.html:130
msgid "Rejected"
msgstr "Abgelehnt"
@@ -1023,7 +1042,7 @@ msgstr "Ungelesen"
#: allianceauth/notifications/templates/notifications/list.html:18
msgid "Read"
msgstr "Lesen"
msgstr "Gelesen"
#: allianceauth/notifications/templates/notifications/list.html:22
msgid "Mark All Read"
@@ -1254,7 +1273,7 @@ msgstr "Flotten Comms:"
#: allianceauth/services/forms.py:9
msgid "Fleet Type:"
msgstr "Flottenzeit:"
msgstr "Flottenart:"
#: allianceauth/services/forms.py:10
msgid "Ship Priorities:"
@@ -1300,23 +1319,54 @@ msgstr "Passwort"
msgid "Password must be at least 8 characters long."
msgstr "Passwort muss mindestens 8 Zeichen lang sein"
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:23
#: allianceauth/services/modules/discord/models.py:225
msgid "Discord Account Disabled"
msgstr "Discord Konto deaktiviert"
#: allianceauth/services/modules/discord/models.py:227
msgid ""
"Your Discord account was disabled automatically by Auth. If you think this "
"was a mistake, please contact an admin."
msgstr ""
"Dein Discord Konto wurde automatisch durch Auth deaktiviert. Wenn Du glaubst"
" dies war ein Fehler, kontaktiere bitte einen Administrator."
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:18
msgid "Join the Discord server"
msgstr "Discord Server beitreten"
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:22
msgid "Leave- and rejoin the Discord Server (Reset)"
msgstr "Discord Server verlassen und wieder beitreten (Zurücksetzen)"
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:25
msgid "Leave the Discord server"
msgstr "Discord Server verlassen"
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32
msgid "Link Discord Server"
msgstr "Verbinde Discord Server"
#: allianceauth/services/modules/discord/views.py:26
#: allianceauth/services/modules/discord/views.py:30
msgid "Deactivated Discord account."
msgstr "Discord Konto deaktiviert."
#: allianceauth/services/modules/discord/views.py:29
#: allianceauth/services/modules/discord/views.py:41
#: allianceauth/services/modules/discord/views.py:65
#: allianceauth/services/modules/discord/views.py:36
#: allianceauth/services/modules/discord/views.py:59
msgid "An error occurred while processing your Discord account."
msgstr "Es gab einen Fehler bei der Verarbeitung Deines Discord Kontos."
#: allianceauth/services/modules/discord/views.py:62
msgid "Activated Discord account."
msgstr "Discord Konto aktiviert."
#: allianceauth/services/modules/discord/views.py:102
msgid "Your Discord account has been successfully activated."
msgstr "Dein Discord Konto wurde erfolgreich aktiviert."
#: allianceauth/services/modules/discord/views.py:108
msgid ""
"An error occurred while trying to activate your Discord account. Please try "
"again."
msgstr ""
"Es gab einen Fehler während der Aktivierung Deines Discord Kontos. Bitte "
"versuche es noch einmal."
#: allianceauth/services/modules/discourse/views.py:37
msgid "You are not authorized to access Discourse."
@@ -1587,7 +1637,7 @@ msgstr "Dienst"
msgid "Domain"
msgstr "Domain"
#: allianceauth/srp/auth_hooks.py:9
#: allianceauth/srp/auth_hooks.py:12
msgid "Ship Replacement"
msgstr "Schiffserstattung"
@@ -1601,7 +1651,7 @@ msgstr "Flottenzeit"
msgid "Fleet Doctrine"
msgstr "Flottendoktrin"
#: allianceauth/srp/form.py:12 allianceauth/srp/templates/srp/data.html:89
#: allianceauth/srp/form.py:12 allianceauth/srp/templates/srp/data.html:90
msgid "Additional Info"
msgstr "Zusätzliche Info"
@@ -1630,65 +1680,65 @@ msgstr "SRP-Flotte erstellen"
msgid "Give this link to the line members"
msgstr "Gib diesen Link an Deine Piloten weiter"
#: allianceauth/srp/templates/srp/data.html:48
#: allianceauth/srp/templates/srp/data.html:49
msgid "SRP Fleet Data"
msgstr "SRP-Flotte Daten"
#: allianceauth/srp/templates/srp/data.html:53
#: allianceauth/srp/templates/srp/data.html:54
msgid "Mark Incomplete"
msgstr "Als unvollständig markieren"
#: allianceauth/srp/templates/srp/data.html:57
#: allianceauth/srp/templates/srp/data.html:58
msgid "Mark Completed"
msgstr "Als vollständig markieren"
#: allianceauth/srp/templates/srp/data.html:69
#: allianceauth/srp/templates/srp/data.html:145
#: allianceauth/srp/templates/srp/data.html:70
#: allianceauth/srp/templates/srp/data.html:156
msgid "Total Losses:"
msgstr "Verluste insgesamt:"
#: allianceauth/srp/templates/srp/data.html:70
#: allianceauth/srp/templates/srp/data.html:146
#: allianceauth/srp/templates/srp/data.html:71
#: allianceauth/srp/templates/srp/data.html:157
#: allianceauth/srp/templates/srp/management.html:30
msgid "Total ISK Cost:"
msgstr "ISK-Kosten insgesamt:"
#: allianceauth/srp/templates/srp/data.html:78
#: allianceauth/srp/templates/srp/data.html:154
#: allianceauth/srp/templates/srp/data.html:79
#: allianceauth/srp/templates/srp/data.html:165
msgid "Are you sure you want to delete SRP requests?"
msgstr "Bist Du sicher das Du SRP Anfragen löschen willst?"
#: allianceauth/srp/templates/srp/data.html:87
#: allianceauth/srp/templates/srp/data.html:88
msgid "Pilot Name"
msgstr "Name des Piloten"
#: allianceauth/srp/templates/srp/data.html:88
#: allianceauth/srp/templates/srp/data.html:89
msgid "Killboard Link"
msgstr "Killboard Link"
#: allianceauth/srp/templates/srp/data.html:90
#: allianceauth/srp/templates/srp/data.html:91
msgid "Ship Type"
msgstr "Schiffstyp"
#: allianceauth/srp/templates/srp/data.html:91
#: allianceauth/srp/templates/srp/data.html:92
msgid "Killboard Loss Amt"
msgstr "Summe Killboard Verluste"
#: allianceauth/srp/templates/srp/data.html:92
#: allianceauth/srp/templates/srp/data.html:93
msgid "SRP ISK Cost"
msgstr "SRP ISK-Kosten"
#: allianceauth/srp/templates/srp/data.html:93
#: allianceauth/srp/templates/srp/data.html:94
msgid "Click value to edit Enter to save & next ESC to cancel"
msgstr ""
"Klicke auf den Wert um diesen zu bearbeiten, Enter zum Speichern und um zum "
"nächsten Wert zu springen, ESC zum Beenden."
#: allianceauth/srp/templates/srp/data.html:96
#: allianceauth/srp/templates/srp/data.html:97
msgid "Post Time"
msgstr "Veröffentlichungszeit"
#: allianceauth/srp/templates/srp/data.html:163
#: allianceauth/srp/templates/srp/data.html:174
msgid "No SRP requests for this fleet."
msgstr "Keine SRP Anfragen für diese Flotte."
@@ -1884,32 +1934,30 @@ msgid "Current"
msgstr "Aktuell"
#: allianceauth/templates/allianceauth/admin-status/overview.html:40
msgid "Latest Major"
msgstr "Aktuellste Hauptversion"
msgid "Latest Stable"
msgstr "Aktuellste stabile Version"
#: allianceauth/templates/allianceauth/admin-status/overview.html:46
#: allianceauth/templates/allianceauth/admin-status/overview.html:56
#: allianceauth/templates/allianceauth/admin-status/overview.html:66
msgid "Update available"
msgstr "Update verfügbar"
#: allianceauth/templates/allianceauth/admin-status/overview.html:50
msgid "Latest Minor"
msgstr "Aktuellste Unterversion"
#: allianceauth/templates/allianceauth/admin-status/overview.html:51
msgid "Latest Pre-Release"
msgstr "Aktuellste Testversion"
#: allianceauth/templates/allianceauth/admin-status/overview.html:60
msgid "Latest Patch"
msgstr "Aktuellste Patchversion"
#: allianceauth/templates/allianceauth/admin-status/overview.html:57
msgid "Pre-Release available"
msgstr "Testversion verfügbar"
#: allianceauth/templates/allianceauth/admin-status/overview.html:73
#: allianceauth/templates/allianceauth/admin-status/overview.html:65
msgid "Task Queue"
msgstr "Warteschlange"
#: allianceauth/templates/allianceauth/admin-status/overview.html:90
#: allianceauth/templates/allianceauth/admin-status/overview.html:82
msgid "Error retrieving task queue length"
msgstr "Fehler beim Ermitteln der Warteschlange."
#: allianceauth/templates/allianceauth/admin-status/overview.html:92
#: allianceauth/templates/allianceauth/admin-status/overview.html:84
#, python-format
msgid "%(tasks)s task"
msgid_plural "%(tasks)s tasks"
@@ -2050,3 +2098,6 @@ msgstr "Neuen Timer hinzugefügt in %(system)s um %(time)s."
#: allianceauth/timerboard/views.py:83
msgid "Saved changes to the timer."
msgstr "Änderungen am Timer gespeichert"
#~ msgid "Portrait"
#~ msgstr "Portrait"

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-08 00:57+0000\n"
"POT-Creation-Date: 2020-11-20 05:33+0000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
"Last-Translator: frank1210 <francolopez_16@hotmail.com>, 2020\n"
"Language-Team: Spanish (https://www.transifex.com/alliance-auth/teams/107430/es/)\n"
@@ -27,74 +27,72 @@ msgid "A main character is required to perform that action. Add one below."
msgstr ""
"Un personaje principal es requerido para completar esta accion. Agregue uno"
#: allianceauth/authentication/forms.py:6
#: allianceauth/authentication/forms.py:5
msgid "Email"
msgstr "E-mail"
#: allianceauth/authentication/models.py:76
msgid "State Changed"
msgstr "Estado Cambiado"
#: allianceauth/authentication/models.py:77
#: allianceauth/authentication/models.py:78
#, python-format
msgid "Your user state has been changed to %(state)s"
msgstr "Tu estado de usuario fue cambiado a %(state)s"
msgid "State changed to: %s"
msgstr ""
#: allianceauth/authentication/models.py:79
#, python-format
msgid "Your user's state is now: %(state)s"
msgstr ""
#: allianceauth/authentication/templates/authentication/dashboard.html:5
#: allianceauth/authentication/templates/authentication/dashboard.html:8
#: allianceauth/templates/allianceauth/side-menu.html:10
#: allianceauth/templates/allianceauth/side-menu.html:11
msgid "Dashboard"
msgstr "Pagina Principal"
#: allianceauth/authentication/templates/authentication/dashboard.html:17
#: allianceauth/corputils/templates/corputils/corpstats.html:116
#: allianceauth/corputils/templates/corputils/search.html:16
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
msgid "Main Character"
msgstr "Personaje Principal"
#: allianceauth/authentication/templates/authentication/dashboard.html:18
#, python-format
msgid ""
"\n"
" Main Character (State: %(state)s)\n"
" "
msgstr ""
#: allianceauth/authentication/templates/authentication/dashboard.html:77
#: allianceauth/authentication/templates/authentication/dashboard.html:81
msgid "No main character set."
msgstr "No se ha seleccionado un personaje principal."
#: allianceauth/authentication/templates/authentication/dashboard.html:84
#: allianceauth/authentication/templates/authentication/dashboard.html:88
msgid "Add Character"
msgstr "Agregar Personaje"
#: allianceauth/authentication/templates/authentication/dashboard.html:88
#: allianceauth/authentication/templates/authentication/dashboard.html:92
msgid "Change Main"
msgstr "Cambiar Personaje Principal"
#: allianceauth/authentication/templates/authentication/dashboard.html:97
#: allianceauth/authentication/templates/authentication/dashboard.html:101
msgid "Group Memberships"
msgstr ""
#: allianceauth/authentication/templates/authentication/dashboard.html:117
#: allianceauth/authentication/templates/authentication/dashboard.html:121
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
#: allianceauth/hrapplications/templates/hrapplications/view.html:41
msgid "Characters"
msgstr "Personajes"
#: allianceauth/authentication/templates/authentication/dashboard.html:125
#: allianceauth/authentication/templates/authentication/dashboard.html:129
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:22
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:15
#: allianceauth/hrapplications/templates/hrapplications/view.html:45
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34
msgid "Name"
msgstr "Nombre"
#: allianceauth/authentication/templates/authentication/dashboard.html:126
#: allianceauth/authentication/templates/authentication/dashboard.html:130
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:23
#: allianceauth/hrapplications/templates/hrapplications/view.html:46
msgid "Corp"
msgstr "Corporación"
#: allianceauth/authentication/templates/authentication/dashboard.html:127
#: allianceauth/authentication/templates/authentication/dashboard.html:131
#: allianceauth/corputils/templates/corputils/corpstats.html:77
#: allianceauth/hrapplications/templates/hrapplications/view.html:47
msgid "Alliance"
@@ -163,26 +161,26 @@ msgstr ""
msgid "Unable to authenticate as the selected character."
msgstr "Imposible validar con el personaje seleccionado."
#: allianceauth/authentication/views.py:148
#: allianceauth/authentication/views.py:151
msgid "Registration token has expired."
msgstr "El token de registracion expiro."
#: allianceauth/authentication/views.py:200
#: allianceauth/authentication/views.py:206
msgid ""
"Sent confirmation email. Please follow the link to confirm your email "
"address."
msgstr ""
"Confirmacion de mail enviada. Por favor siga el enlace para confirmar "
#: allianceauth/authentication/views.py:205
#: allianceauth/authentication/views.py:211
msgid "Confirmed your email address. Please login to continue."
msgstr ""
"Se ha confirmado su direccion de mail. Por favor igrese su token para "
"continuar."
#: allianceauth/authentication/views.py:210
msgid "Registraion of new accounts it not allowed at this time."
msgstr "Registracion de nuevas cuentas no es permitido por el momento."
#: allianceauth/authentication/views.py:216
msgid "Registration of new accounts is not allowed at this time."
msgstr ""
#: allianceauth/corputils/auth_hooks.py:10
msgid "Corporation Stats"
@@ -229,16 +227,16 @@ msgstr "Ultima Actualizacion:"
#: allianceauth/corputils/templates/corputils/search.html:13
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:37
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:96
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:51
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:109
msgid "Character"
msgstr "Personaje"
#: allianceauth/corputils/templates/corputils/corpstats.html:76
#: allianceauth/corputils/templates/corputils/search.html:14
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:28
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
#: allianceauth/hrapplications/templates/hrapplications/management.html:27
#: allianceauth/hrapplications/templates/hrapplications/management.html:84
#: allianceauth/hrapplications/templates/hrapplications/management.html:129
@@ -254,6 +252,16 @@ msgstr "Corporacion"
msgid "Killboard"
msgstr "Killboard"
#: allianceauth/corputils/templates/corputils/corpstats.html:116
#: allianceauth/corputils/templates/corputils/search.html:16
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
msgid "Main Character"
msgstr "Personaje Principal"
#: allianceauth/corputils/templates/corputils/corpstats.html:117
#: allianceauth/corputils/templates/corputils/search.html:17
msgid "Main Corporation"
@@ -538,31 +546,35 @@ msgstr "Participacion de flota registrada."
msgid "FAT link has expired."
msgstr "Enlace de participacion expirado."
#: allianceauth/groupmanagement/auth_hooks.py:17
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:14
msgid "Group Management"
msgstr "Manejo de Grupo"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:13
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:14
msgid "Audit Log"
msgstr "Log de Auditoria"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:18
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:20
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:20
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:21
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
msgid "Back"
msgstr "Volver"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:28
msgid "Date/Time"
msgstr "Fecha/Hora"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:26
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
msgid "Requestor"
msgstr "Solicitante"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:32
msgid "Type"
msgstr "Tipo"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:17
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:33
#: allianceauth/notifications/templates/notifications/list.html:37
#: allianceauth/notifications/templates/notifications/list.html:69
#: allianceauth/optimer/templates/optimer/fleetoptable.html:18
@@ -573,11 +585,19 @@ msgstr "Tipo"
msgid "Action"
msgstr "Accion"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:34
msgid "Actor"
msgstr "Actor"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:47
msgid "Removed"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:59
msgid "All times displayed are EVE/UTC."
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:66
msgid "No entries found for this group."
msgstr "No se encontraron entradas para este grupo."
@@ -585,22 +605,24 @@ msgstr "No se encontraron entradas para este grupo."
msgid "Group Members"
msgstr "Miembros del Grupo"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:28
msgid "Portrait"
msgstr "Retrato"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:38
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:31
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:52
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:110
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
msgid "Organization"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:64
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:61
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:78
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:136
msgid "(unknown)"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:66
msgid "Remove from group"
msgstr "Remover del grupo"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:76
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:81
msgid "No group members to list."
msgstr "no hay miembros para listar."
@@ -608,56 +630,56 @@ msgstr "no hay miembros para listar."
msgid "Groups Membership"
msgstr "Membresia de grupos"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:15
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
#: allianceauth/templates/allianceauth/side-menu.html:15
#: allianceauth/templates/allianceauth/side-menu.html:16
msgid "Groups"
msgstr "Grupos"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
msgid "Description"
msgstr "Descripcion"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:26
#: allianceauth/hrapplications/templates/hrapplications/management.html:28
#: allianceauth/hrapplications/templates/hrapplications/management.html:85
#: allianceauth/hrapplications/templates/hrapplications/management.html:130
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:27
#: allianceauth/srp/templates/srp/data.html:97
#: allianceauth/srp/templates/srp/data.html:98
msgid "Status"
msgstr "Estado"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:27
msgid "Member Count"
msgstr "Contador de miembros"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:38
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:43
msgid "Hidden"
msgstr "Escondido"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:40
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
#: allianceauth/templates/allianceauth/admin-status/overview.html:12
msgid "Open"
msgstr "Abierto"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:42
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:47
msgid "Requestable"
msgstr "Solicitable"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:50
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
msgid "View Members"
msgstr "Ver Miembros"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:53
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:60
msgid "Audit Members"
msgstr "Auditar Miembros"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
msgid "Copy Direrct Join Link"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
msgid "Copy Direct Join Link"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:75
msgid "No groups to list."
msgstr "No hay grupos para listar"
@@ -666,19 +688,19 @@ msgstr "No hay grupos para listar"
msgid "Available Groups"
msgstr "Grupos Disponibles"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:29
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:30
msgid "Leave"
msgstr "Dejar"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:39
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:40
msgid "Join"
msgstr "Unirse"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:43
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:44
msgid "Request"
msgstr "Solicitar"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:58
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:59
msgid "No groups available."
msgstr "No hay grupos disponibles"
@@ -686,77 +708,72 @@ msgstr "No hay grupos disponibles"
msgid "Groups Management"
msgstr "Manejo de Grupos"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:23
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:25
msgid "Join Requests"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:34
msgid "Leave Requests"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:39
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:98
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:53
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:111
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
#: allianceauth/services/modules/openfire/forms.py:6
msgid "Group"
msgstr "Grupo"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:71
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:130
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:84
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
msgid "Accept"
msgstr "Aceptar"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:74
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:133
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:146
#: allianceauth/hrapplications/templates/hrapplications/view.html:85
msgid "Reject"
msgstr "Rechazar"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:83
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
msgid "No group add requests."
msgstr "No hay solicitudes de ingreso."
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:155
msgid "No group leave requests."
msgstr "No hay solicitudes paradejar el grupo."
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:10
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:9
msgid "Toggle navigation"
msgstr "Navegacion"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15
#: allianceauth/templates/allianceauth/side-menu.html:23
msgid "Group Management"
msgstr "Manejo de Grupo"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:21
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:20
msgid "Group Requests"
msgstr "Solicitudes de Grupo"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:23
msgid "Group Membership"
msgstr "Membresia de Grupo"
#: allianceauth/groupmanagement/views.py:166
#: allianceauth/groupmanagement/views.py:162
#, python-format
msgid "Removed user %(user)s from group %(group)s."
msgstr "El usuario %(user)s fue removido del grupo %(group)s"
#: allianceauth/groupmanagement/views.py:168
#: allianceauth/groupmanagement/views.py:164
msgid "User does not exist in that group"
msgstr "El usuario no existe en ese grupos"
#: allianceauth/groupmanagement/views.py:171
#: allianceauth/groupmanagement/views.py:167
msgid "Group does not exist"
msgstr "El grupo no existe"
#: allianceauth/groupmanagement/views.py:198
#: allianceauth/groupmanagement/views.py:194
#, python-format
msgid "Accepted application from %(mainchar)s to %(group)s."
msgstr "Solicitud aceptada de %(mainchar)s a %(group)s"
#: allianceauth/groupmanagement/views.py:205
#: allianceauth/groupmanagement/views.py:238
#: allianceauth/groupmanagement/views.py:201
#: allianceauth/groupmanagement/views.py:234
#, python-format
msgid ""
"An unhandled error occurred while processing the application from "
@@ -765,79 +782,79 @@ msgstr ""
"Ocurrio un error cuando se intento procesar la informacion de %(mainchar)s "
"al grupo %(group)s."
#: allianceauth/groupmanagement/views.py:231
#: allianceauth/groupmanagement/views.py:227
#, python-format
msgid "Rejected application from %(mainchar)s to %(group)s."
msgstr "Se rechazo la solicitud de %(mainchar)s al grupo %(group)s."
#: allianceauth/groupmanagement/views.py:267
#: allianceauth/groupmanagement/views.py:263
#, python-format
msgid "Accepted application from %(mainchar)s to leave %(group)s."
msgstr "Se acepto la solicitud de %(mainchar)s para dejar el grupo %(group)s."
#: allianceauth/groupmanagement/views.py:273
#: allianceauth/groupmanagement/views.py:307
#: allianceauth/groupmanagement/views.py:269
#: allianceauth/groupmanagement/views.py:303
#, python-format
msgid ""
"An unhandled error occurred while processing the application from "
"%(mainchar)s to leave %(group)s."
msgstr ""
#: allianceauth/groupmanagement/views.py:300
#: allianceauth/groupmanagement/views.py:296
#, python-format
msgid "Rejected application from %(mainchar)s to leave %(group)s."
msgstr ""
"Se rechazo la solicitud de %(mainchar)s para dejar el grupo %(group)s."
#: allianceauth/groupmanagement/views.py:346
#: allianceauth/groupmanagement/views.py:358
#: allianceauth/groupmanagement/views.py:342
#: allianceauth/groupmanagement/views.py:354
msgid "You cannot join that group"
msgstr "No puedes unirte a ese grupo"
#: allianceauth/groupmanagement/views.py:352
#: allianceauth/groupmanagement/views.py:348
msgid "You are already a member of that group."
msgstr ""
#: allianceauth/groupmanagement/views.py:367
#: allianceauth/groupmanagement/views.py:363
msgid "You already have a pending application for that group."
msgstr ""
#: allianceauth/groupmanagement/views.py:370
#: allianceauth/groupmanagement/views.py:408
#: allianceauth/groupmanagement/views.py:366
#: allianceauth/groupmanagement/views.py:404
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
#: allianceauth/hrapplications/templates/hrapplications/management.html:144
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:38
#: allianceauth/hrapplications/templates/hrapplications/view.html:20
#: allianceauth/srp/templates/srp/data.html:125
#: allianceauth/srp/templates/srp/data.html:134
#: allianceauth/srp/templates/srp/management.html:81
msgid "Pending"
msgstr "Pendiente"
#: allianceauth/groupmanagement/views.py:376
#: allianceauth/groupmanagement/views.py:372
#, python-format
msgid "Applied to group %(group)s."
msgstr "Solicitud enviada al grupo %(group)s."
#: allianceauth/groupmanagement/views.py:387
#: allianceauth/groupmanagement/views.py:383
msgid "You cannot leave that group"
msgstr "No puedes dejar el grupos"
#: allianceauth/groupmanagement/views.py:392
#: allianceauth/groupmanagement/views.py:388
msgid "You are not a member of that group"
msgstr "No eres miembro de ese grupo"
#: allianceauth/groupmanagement/views.py:401
#: allianceauth/groupmanagement/views.py:397
msgid "You already have a pending leave request for that group."
msgstr ""
#: allianceauth/groupmanagement/views.py:414
#: allianceauth/groupmanagement/views.py:410
#, python-format
msgid "Applied to leave group %(group)s."
msgstr "Solicitaste dejar el grupo %(group)s."
#: allianceauth/hrapplications/auth_hooks.py:10
#: allianceauth/hrapplications/auth_hooks.py:13
msgid "Applications"
msgstr "Solicitudes"
@@ -894,7 +911,7 @@ msgstr "Usuario"
#: allianceauth/hrapplications/templates/hrapplications/management.html:131
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:28
#: allianceauth/hrapplications/templates/hrapplications/view.html:75
#: allianceauth/srp/templates/srp/data.html:99
#: allianceauth/srp/templates/srp/data.html:100
#: allianceauth/srp/templates/srp/management.html:46
msgid "Actions"
msgstr "Acciones"
@@ -904,7 +921,7 @@ msgstr "Acciones"
#: allianceauth/hrapplications/templates/hrapplications/management.html:147
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:40
#: allianceauth/hrapplications/templates/hrapplications/view.html:16
#: allianceauth/srp/templates/srp/data.html:117
#: allianceauth/srp/templates/srp/data.html:126
msgid "Approved"
msgstr "Aprovado"
@@ -912,7 +929,7 @@ msgstr "Aprovado"
#: allianceauth/hrapplications/templates/hrapplications/management.html:104
#: allianceauth/hrapplications/templates/hrapplications/management.html:149
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:42
#: allianceauth/srp/templates/srp/data.html:121
#: allianceauth/srp/templates/srp/data.html:130
msgid "Rejected"
msgstr "Rechazado"
@@ -1295,22 +1312,49 @@ msgstr "Contraseña"
msgid "Password must be at least 8 characters long."
msgstr "La contraseña tiene que tener 8 caracteres de largo minimo"
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:23
#: allianceauth/services/modules/discord/models.py:225
msgid "Discord Account Disabled"
msgstr ""
#: allianceauth/services/modules/discord/models.py:227
msgid ""
"Your Discord account was disabled automatically by Auth. If you think this "
"was a mistake, please contact an admin."
msgstr ""
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:18
msgid "Join the Discord server"
msgstr ""
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:22
msgid "Leave- and rejoin the Discord Server (Reset)"
msgstr ""
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:25
msgid "Leave the Discord server"
msgstr ""
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32
msgid "Link Discord Server"
msgstr "Enlace a servidor de Discord"
#: allianceauth/services/modules/discord/views.py:26
#: allianceauth/services/modules/discord/views.py:30
msgid "Deactivated Discord account."
msgstr ""
#: allianceauth/services/modules/discord/views.py:29
#: allianceauth/services/modules/discord/views.py:41
#: allianceauth/services/modules/discord/views.py:65
#: allianceauth/services/modules/discord/views.py:36
#: allianceauth/services/modules/discord/views.py:59
msgid "An error occurred while processing your Discord account."
msgstr ""
#: allianceauth/services/modules/discord/views.py:62
msgid "Activated Discord account."
#: allianceauth/services/modules/discord/views.py:102
msgid "Your Discord account has been successfully activated."
msgstr ""
#: allianceauth/services/modules/discord/views.py:108
msgid ""
"An error occurred while trying to activate your Discord account. Please try "
"again."
msgstr ""
#: allianceauth/services/modules/discourse/views.py:37
@@ -1575,7 +1619,7 @@ msgstr "Servicio"
msgid "Domain"
msgstr "Dominio"
#: allianceauth/srp/auth_hooks.py:9
#: allianceauth/srp/auth_hooks.py:12
msgid "Ship Replacement"
msgstr "Reemplazo de Nave"
@@ -1589,7 +1633,7 @@ msgstr "Hora de flota"
msgid "Fleet Doctrine"
msgstr "Doctrina"
#: allianceauth/srp/form.py:12 allianceauth/srp/templates/srp/data.html:89
#: allianceauth/srp/form.py:12 allianceauth/srp/templates/srp/data.html:90
msgid "Additional Info"
msgstr "Informacion Adicional"
@@ -1618,63 +1662,63 @@ msgstr "Crear SRP"
msgid "Give this link to the line members"
msgstr "Entregar este enlace a los miembros"
#: allianceauth/srp/templates/srp/data.html:48
#: allianceauth/srp/templates/srp/data.html:49
msgid "SRP Fleet Data"
msgstr "Informacion de SRP de la flota"
#: allianceauth/srp/templates/srp/data.html:53
#: allianceauth/srp/templates/srp/data.html:54
msgid "Mark Incomplete"
msgstr "Marcar como Incompleto"
#: allianceauth/srp/templates/srp/data.html:57
#: allianceauth/srp/templates/srp/data.html:58
msgid "Mark Completed"
msgstr "Marcar como Completo"
#: allianceauth/srp/templates/srp/data.html:69
#: allianceauth/srp/templates/srp/data.html:145
#: allianceauth/srp/templates/srp/data.html:70
#: allianceauth/srp/templates/srp/data.html:156
msgid "Total Losses:"
msgstr "Perdidas Totales:"
#: allianceauth/srp/templates/srp/data.html:70
#: allianceauth/srp/templates/srp/data.html:146
#: allianceauth/srp/templates/srp/data.html:71
#: allianceauth/srp/templates/srp/data.html:157
#: allianceauth/srp/templates/srp/management.html:30
msgid "Total ISK Cost:"
msgstr "Costo Total:"
#: allianceauth/srp/templates/srp/data.html:78
#: allianceauth/srp/templates/srp/data.html:154
#: allianceauth/srp/templates/srp/data.html:79
#: allianceauth/srp/templates/srp/data.html:165
msgid "Are you sure you want to delete SRP requests?"
msgstr "Estas seguro que quiere borrar las solicitudes de SRP"
#: allianceauth/srp/templates/srp/data.html:87
#: allianceauth/srp/templates/srp/data.html:88
msgid "Pilot Name"
msgstr "Nombre del Piloto"
#: allianceauth/srp/templates/srp/data.html:88
#: allianceauth/srp/templates/srp/data.html:89
msgid "Killboard Link"
msgstr "Enlace de la Muerte"
#: allianceauth/srp/templates/srp/data.html:90
#: allianceauth/srp/templates/srp/data.html:91
msgid "Ship Type"
msgstr "Tipo"
#: allianceauth/srp/templates/srp/data.html:91
#: allianceauth/srp/templates/srp/data.html:92
msgid "Killboard Loss Amt"
msgstr "Monto de la perdida en ZKB"
#: allianceauth/srp/templates/srp/data.html:92
#: allianceauth/srp/templates/srp/data.html:93
msgid "SRP ISK Cost"
msgstr "Costo del SRP"
#: allianceauth/srp/templates/srp/data.html:93
#: allianceauth/srp/templates/srp/data.html:94
msgid "Click value to edit Enter to save & next ESC to cancel"
msgstr ""
#: allianceauth/srp/templates/srp/data.html:96
#: allianceauth/srp/templates/srp/data.html:97
msgid "Post Time"
msgstr "Tiempo"
#: allianceauth/srp/templates/srp/data.html:163
#: allianceauth/srp/templates/srp/data.html:174
msgid "No SRP requests for this fleet."
msgstr "No hay solicitudes de SRP para esta flota."
@@ -1866,32 +1910,30 @@ msgid "Current"
msgstr "Actual"
#: allianceauth/templates/allianceauth/admin-status/overview.html:40
msgid "Latest Major"
msgstr "Ultimo Importante"
msgid "Latest Stable"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:46
#: allianceauth/templates/allianceauth/admin-status/overview.html:56
#: allianceauth/templates/allianceauth/admin-status/overview.html:66
msgid "Update available"
msgstr "Actualizacion Disponible"
#: allianceauth/templates/allianceauth/admin-status/overview.html:50
msgid "Latest Minor"
msgstr "Ultimo no importante"
#: allianceauth/templates/allianceauth/admin-status/overview.html:51
msgid "Latest Pre-Release"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:60
msgid "Latest Patch"
msgstr "Ultimo Parche"
#: allianceauth/templates/allianceauth/admin-status/overview.html:57
msgid "Pre-Release available"
msgstr ""
#: allianceauth/templates/allianceauth/admin-status/overview.html:73
#: allianceauth/templates/allianceauth/admin-status/overview.html:65
msgid "Task Queue"
msgstr "Cola de Tareas"
#: allianceauth/templates/allianceauth/admin-status/overview.html:90
#: allianceauth/templates/allianceauth/admin-status/overview.html:82
msgid "Error retrieving task queue length"
msgstr "Error al conseguir la cola de tareas"
#: allianceauth/templates/allianceauth/admin-status/overview.html:92
#: allianceauth/templates/allianceauth/admin-status/overview.html:84
#, python-format
msgid "%(tasks)s task"
msgid_plural "%(tasks)s tasks"
@@ -2032,3 +2074,6 @@ msgstr "Se agrego un nuevo timer en %(system)s a las %(time)s."
#: allianceauth/timerboard/views.py:83
msgid "Saved changes to the timer."
msgstr "Se guardaron los cambios en el timer."
#~ msgid "Portrait"
#~ msgstr "Retrato"

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

@@ -4,19 +4,20 @@
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
# Translators:
# Lahty <js03js70@gmail.com>, 2020
# Kim Hyundong <khd1226543@gmail.com>, 2020
# None None <khd1226543@gmail.com>, 2020
# Seowon Jung <seowon@hawaii.edu>, 2020
# Olgeda Choi <undead.choi@gmail.com>, 2020
# Lahty <js03js70@gmail.com>, 2020
# Joel Falknau <ozirascal@gmail.com>, 2020
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-08 00:57+0000\n"
"POT-Creation-Date: 2020-11-20 05:33+0000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
"Last-Translator: Olgeda Choi <undead.choi@gmail.com>, 2020\n"
"Last-Translator: Joel Falknau <ozirascal@gmail.com>, 2020\n"
"Language-Team: Korean (Korea) (https://www.transifex.com/alliance-auth/teams/107430/ko_KR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -28,74 +29,75 @@ msgstr ""
msgid "A main character is required to perform that action. Add one below."
msgstr "해당 기능을 수행하려면 주 캐릭터가 요구됨. 아래에 하나를 추가하시오."
#: allianceauth/authentication/forms.py:6
#: allianceauth/authentication/forms.py:5
msgid "Email"
msgstr "이메일"
#: allianceauth/authentication/models.py:76
msgid "State Changed"
msgstr "상태 변경됨"
#: allianceauth/authentication/models.py:77
#: allianceauth/authentication/models.py:78
#, python-format
msgid "Your user state has been changed to %(state)s"
msgstr "사용자의 상태가 %(state)s변경됨"
msgid "State changed to: %s"
msgstr "상태가 %s로 변경됐습니다."
#: allianceauth/authentication/models.py:79
#, python-format
msgid "Your user's state is now: %(state)s"
msgstr "사용자의 상태는 %(state)s입니다."
#: allianceauth/authentication/templates/authentication/dashboard.html:5
#: allianceauth/authentication/templates/authentication/dashboard.html:8
#: allianceauth/templates/allianceauth/side-menu.html:10
#: allianceauth/templates/allianceauth/side-menu.html:11
msgid "Dashboard"
msgstr "대시보드"
#: allianceauth/authentication/templates/authentication/dashboard.html:17
#: allianceauth/corputils/templates/corputils/corpstats.html:116
#: allianceauth/corputils/templates/corputils/search.html:16
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
msgid "Main Character"
msgstr "주 캐릭터"
#: allianceauth/authentication/templates/authentication/dashboard.html:18
#, python-format
msgid ""
"\n"
" Main Character (State: %(state)s)\n"
" "
msgstr ""
"\n"
" 메인 캐릭터 (상태: %(state)s)\n"
" "
#: allianceauth/authentication/templates/authentication/dashboard.html:77
#: allianceauth/authentication/templates/authentication/dashboard.html:81
msgid "No main character set."
msgstr "주 캐릭터가 지정되지 않음"
#: allianceauth/authentication/templates/authentication/dashboard.html:84
#: allianceauth/authentication/templates/authentication/dashboard.html:88
msgid "Add Character"
msgstr "캐릭터 추가"
#: allianceauth/authentication/templates/authentication/dashboard.html:88
#: allianceauth/authentication/templates/authentication/dashboard.html:92
msgid "Change Main"
msgstr "주 캐릭터 변경"
#: allianceauth/authentication/templates/authentication/dashboard.html:97
#: allianceauth/authentication/templates/authentication/dashboard.html:101
msgid "Group Memberships"
msgstr "그룹 멤버쉽"
#: allianceauth/authentication/templates/authentication/dashboard.html:117
#: allianceauth/authentication/templates/authentication/dashboard.html:121
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
#: allianceauth/hrapplications/templates/hrapplications/view.html:41
msgid "Characters"
msgstr "캐릭터"
#: allianceauth/authentication/templates/authentication/dashboard.html:125
#: allianceauth/authentication/templates/authentication/dashboard.html:129
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:22
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:15
#: allianceauth/hrapplications/templates/hrapplications/view.html:45
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34
msgid "Name"
msgstr "이름"
#: allianceauth/authentication/templates/authentication/dashboard.html:126
#: allianceauth/authentication/templates/authentication/dashboard.html:130
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:23
#: allianceauth/hrapplications/templates/hrapplications/view.html:46
msgid "Corp"
msgstr "콥"
#: allianceauth/authentication/templates/authentication/dashboard.html:127
#: allianceauth/authentication/templates/authentication/dashboard.html:131
#: allianceauth/corputils/templates/corputils/corpstats.html:77
#: allianceauth/hrapplications/templates/hrapplications/view.html:47
msgid "Alliance"
@@ -151,33 +153,33 @@ msgstr "주 캐릭터가 %(char)s로 변경됨"
#: allianceauth/authentication/views.py:89
#, python-format
msgid "Added %(name)s to your account."
msgstr "%(name)s을(를) 계정에 추가함"
msgstr "계정에 %(name)s를 추가했습니다."
#: allianceauth/authentication/views.py:91
#, python-format
msgid "Failed to add %(name)s to your account: they already have an account."
msgstr "%(name)s을(를) 계정에 추가하는데 실패함. 이미 추가되어있음."
msgstr "계정에 %(name)s를 추가하지 못했습니다. 이미 추가된 계정입니다."
#: allianceauth/authentication/views.py:130
msgid "Unable to authenticate as the selected character."
msgstr "선택한 캐릭터로 인증을 수행할 수 없음"
#: allianceauth/authentication/views.py:148
#: allianceauth/authentication/views.py:151
msgid "Registration token has expired."
msgstr "등록토큰 만료"
#: allianceauth/authentication/views.py:200
#: allianceauth/authentication/views.py:206
msgid ""
"Sent confirmation email. Please follow the link to confirm your email "
"address."
msgstr "확인 메일 전송됨. 다음 링크를 눌러 이메일 주소를 확인하세요."
#: allianceauth/authentication/views.py:205
#: allianceauth/authentication/views.py:211
msgid "Confirmed your email address. Please login to continue."
msgstr "이메일 주소가 확인되었습니다. 로그인 해주세요."
#: allianceauth/authentication/views.py:210
msgid "Registraion of new accounts it not allowed at this time."
#: allianceauth/authentication/views.py:216
msgid "Registration of new accounts is not allowed at this time."
msgstr "현재 새로운 계정 등록은 받지않습니다."
#: allianceauth/corputils/auth_hooks.py:10
@@ -225,16 +227,16 @@ msgstr "마지막 업데이트"
#: allianceauth/corputils/templates/corputils/search.html:13
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:37
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:96
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:51
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:109
msgid "Character"
msgstr "캐릭터"
#: allianceauth/corputils/templates/corputils/corpstats.html:76
#: allianceauth/corputils/templates/corputils/search.html:14
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:28
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
#: allianceauth/hrapplications/templates/hrapplications/management.html:27
#: allianceauth/hrapplications/templates/hrapplications/management.html:84
#: allianceauth/hrapplications/templates/hrapplications/management.html:129
@@ -250,6 +252,16 @@ msgstr "콥"
msgid "Killboard"
msgstr "킬보드"
#: allianceauth/corputils/templates/corputils/corpstats.html:116
#: allianceauth/corputils/templates/corputils/search.html:16
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
msgid "Main Character"
msgstr "주 캐릭터"
#: allianceauth/corputils/templates/corputils/corpstats.html:117
#: allianceauth/corputils/templates/corputils/search.html:17
msgid "Main Corporation"
@@ -531,31 +543,35 @@ msgstr "플릿 참여 등록됨"
msgid "FAT link has expired."
msgstr "플릿활동추적 링크 기한만료"
#: allianceauth/groupmanagement/auth_hooks.py:17
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:14
msgid "Group Management"
msgstr "그룹 관리"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:13
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:14
msgid "Audit Log"
msgstr "감사 기록"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:18
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:20
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:20
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:21
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
msgid "Back"
msgstr "돌아가기"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:28
msgid "Date/Time"
msgstr "날짜/시간"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:26
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
msgid "Requestor"
msgstr "요청인"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:32
msgid "Type"
msgstr "타입"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:17
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:33
#: allianceauth/notifications/templates/notifications/list.html:37
#: allianceauth/notifications/templates/notifications/list.html:69
#: allianceauth/optimer/templates/optimer/fleetoptable.html:18
@@ -566,11 +582,19 @@ msgstr "타입"
msgid "Action"
msgstr "활동"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:34
msgid "Actor"
msgstr "활동한 사람"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:47
msgid "Removed"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:59
msgid "All times displayed are EVE/UTC."
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:66
msgid "No entries found for this group."
msgstr "이 그룹에는 항목 없음"
@@ -578,22 +602,24 @@ msgstr "이 그룹에는 항목 없음"
msgid "Group Members"
msgstr "그룹 멤버"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:28
msgid "Portrait"
msgstr "포트레잇"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:38
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:31
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:52
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:110
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
msgid "Organization"
msgstr "조직"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:64
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:61
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:78
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:136
msgid "(unknown)"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:66
msgid "Remove from group"
msgstr "그룹에서 제거"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:76
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:81
msgid "No group members to list."
msgstr "목록에 그룹 멤버 없음"
@@ -601,56 +627,56 @@ msgstr "목록에 그룹 멤버 없음"
msgid "Groups Membership"
msgstr "그룹 멤버쉽"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:15
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
#: allianceauth/templates/allianceauth/side-menu.html:15
#: allianceauth/templates/allianceauth/side-menu.html:16
msgid "Groups"
msgstr "그룹"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
msgid "Description"
msgstr "설명"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:26
#: allianceauth/hrapplications/templates/hrapplications/management.html:28
#: allianceauth/hrapplications/templates/hrapplications/management.html:85
#: allianceauth/hrapplications/templates/hrapplications/management.html:130
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:27
#: allianceauth/srp/templates/srp/data.html:97
#: allianceauth/srp/templates/srp/data.html:98
msgid "Status"
msgstr "상태"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:27
msgid "Member Count"
msgstr "멤버 수"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:38
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:43
msgid "Hidden"
msgstr "숨김"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:40
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
#: allianceauth/templates/allianceauth/admin-status/overview.html:12
msgid "Open"
msgstr "열기"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:42
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:47
msgid "Requestable"
msgstr "요청 가능"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:50
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
msgid "View Members"
msgstr "멤버 보기"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:53
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:60
msgid "Audit Members"
msgstr "멤버 검사"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
msgid "Copy Direrct Join Link"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
msgid "Copy Direct Join Link"
msgstr "직접 참여 링크 복사"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:75
msgid "No groups to list."
msgstr "목록에 그룹 없음"
@@ -659,19 +685,19 @@ msgstr "목록에 그룹 없음"
msgid "Available Groups"
msgstr "사용 가능한 그룹"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:29
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:30
msgid "Leave"
msgstr "떠나기"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:39
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:40
msgid "Join"
msgstr "참여하기"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:43
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:44
msgid "Request"
msgstr "요청하기"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:58
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:59
msgid "No groups available."
msgstr "사용 가능한 그룹 없음."
@@ -679,155 +705,150 @@ msgstr "사용 가능한 그룹 없음."
msgid "Groups Management"
msgstr "그룹 관리"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:23
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:25
msgid "Join Requests"
msgstr "가입 요청"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:34
msgid "Leave Requests"
msgstr "탈퇴 요청"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:39
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:98
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:53
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:111
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
#: allianceauth/services/modules/openfire/forms.py:6
msgid "Group"
msgstr "그룹"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:71
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:130
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:84
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
msgid "Accept"
msgstr "수락"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:74
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:133
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:146
#: allianceauth/hrapplications/templates/hrapplications/view.html:85
msgid "Reject"
msgstr "거절"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:83
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
msgid "No group add requests."
msgstr "가입 요청 없음"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:155
msgid "No group leave requests."
msgstr "탈퇴 요청 없음"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:10
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:9
msgid "Toggle navigation"
msgstr "네비게이션 전환"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15
#: allianceauth/templates/allianceauth/side-menu.html:23
msgid "Group Management"
msgstr "그룹 관리"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:21
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:20
msgid "Group Requests"
msgstr "그룹 요청"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:23
msgid "Group Membership"
msgstr "그룹 멤버쉽"
#: allianceauth/groupmanagement/views.py:166
#: allianceauth/groupmanagement/views.py:162
#, python-format
msgid "Removed user %(user)s from group %(group)s."
msgstr "유저 %(user)s이(가) %(group)s에서 제거됨."
#: allianceauth/groupmanagement/views.py:168
#: allianceauth/groupmanagement/views.py:164
msgid "User does not exist in that group"
msgstr "유저가 해당 그룹에 존재하지 않음."
#: allianceauth/groupmanagement/views.py:171
#: allianceauth/groupmanagement/views.py:167
msgid "Group does not exist"
msgstr "그룹이 존재하지 않음."
#: allianceauth/groupmanagement/views.py:198
#: allianceauth/groupmanagement/views.py:194
#, python-format
msgid "Accepted application from %(mainchar)s to %(group)s."
msgstr "%(mainchar)s의 %(group)s 그룹 신청 수락"
#: allianceauth/groupmanagement/views.py:205
#: allianceauth/groupmanagement/views.py:238
#: allianceauth/groupmanagement/views.py:201
#: allianceauth/groupmanagement/views.py:234
#, python-format
msgid ""
"An unhandled error occurred while processing the application from "
"%(mainchar)s to %(group)s."
msgstr "%(mainchar)s의 %(group)s 그룹 신청을 처리하는 중 알 수 없는 에러 발생"
#: allianceauth/groupmanagement/views.py:231
#: allianceauth/groupmanagement/views.py:227
#, python-format
msgid "Rejected application from %(mainchar)s to %(group)s."
msgstr "%(mainchar)s의 %(group)s 그룹 신청 거절"
#: allianceauth/groupmanagement/views.py:267
#: allianceauth/groupmanagement/views.py:263
#, python-format
msgid "Accepted application from %(mainchar)s to leave %(group)s."
msgstr "%(mainchar)s의 %(group)s 그룹 탈퇴 수락"
#: allianceauth/groupmanagement/views.py:273
#: allianceauth/groupmanagement/views.py:307
#: allianceauth/groupmanagement/views.py:269
#: allianceauth/groupmanagement/views.py:303
#, python-format
msgid ""
"An unhandled error occurred while processing the application from "
"%(mainchar)s to leave %(group)s."
msgstr "%(mainchar)s의 %(group)s 그룹 탈퇴를 처리하는 중 알 수 없는 에러 발생"
#: allianceauth/groupmanagement/views.py:300
#: allianceauth/groupmanagement/views.py:296
#, python-format
msgid "Rejected application from %(mainchar)s to leave %(group)s."
msgstr "%(mainchar)s의 %(group)s 그룹 탈퇴 거절"
#: allianceauth/groupmanagement/views.py:346
#: allianceauth/groupmanagement/views.py:358
#: allianceauth/groupmanagement/views.py:342
#: allianceauth/groupmanagement/views.py:354
msgid "You cannot join that group"
msgstr "해당 그룹에 참여할 수 없습니다."
#: allianceauth/groupmanagement/views.py:352
#: allianceauth/groupmanagement/views.py:348
msgid "You are already a member of that group."
msgstr "이미 해당 그룹에 가입되어 있습니다."
#: allianceauth/groupmanagement/views.py:367
#: allianceauth/groupmanagement/views.py:363
msgid "You already have a pending application for that group."
msgstr "해당 그룹에 대한 참여신청이 보류되었습니다."
#: allianceauth/groupmanagement/views.py:370
#: allianceauth/groupmanagement/views.py:408
#: allianceauth/groupmanagement/views.py:366
#: allianceauth/groupmanagement/views.py:404
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
#: allianceauth/hrapplications/templates/hrapplications/management.html:144
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:38
#: allianceauth/hrapplications/templates/hrapplications/view.html:20
#: allianceauth/srp/templates/srp/data.html:125
#: allianceauth/srp/templates/srp/data.html:134
#: allianceauth/srp/templates/srp/management.html:81
msgid "Pending"
msgstr "보류 중"
#: allianceauth/groupmanagement/views.py:376
#: allianceauth/groupmanagement/views.py:372
#, python-format
msgid "Applied to group %(group)s."
msgstr "%(group)s그룹에 지원하였음."
#: allianceauth/groupmanagement/views.py:387
#: allianceauth/groupmanagement/views.py:383
msgid "You cannot leave that group"
msgstr "해당 그룹을 떠날 수 없습니다."
#: allianceauth/groupmanagement/views.py:392
#: allianceauth/groupmanagement/views.py:388
msgid "You are not a member of that group"
msgstr "해당그룹의 멤버가 아닙니다."
#: allianceauth/groupmanagement/views.py:401
#: allianceauth/groupmanagement/views.py:397
msgid "You already have a pending leave request for that group."
msgstr "해당 그룹의 탈퇴 신청이 접수된 상태입니다."
#: allianceauth/groupmanagement/views.py:414
#: allianceauth/groupmanagement/views.py:410
#, python-format
msgid "Applied to leave group %(group)s."
msgstr "%(group)s그룹의 탈퇴가 신청됨."
#: allianceauth/hrapplications/auth_hooks.py:10
#: allianceauth/hrapplications/auth_hooks.py:13
msgid "Applications"
msgstr "지원"
@@ -884,7 +905,7 @@ msgstr "사용자명"
#: allianceauth/hrapplications/templates/hrapplications/management.html:131
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:28
#: allianceauth/hrapplications/templates/hrapplications/view.html:75
#: allianceauth/srp/templates/srp/data.html:99
#: allianceauth/srp/templates/srp/data.html:100
#: allianceauth/srp/templates/srp/management.html:46
msgid "Actions"
msgstr "활동"
@@ -894,7 +915,7 @@ msgstr "활동"
#: allianceauth/hrapplications/templates/hrapplications/management.html:147
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:40
#: allianceauth/hrapplications/templates/hrapplications/view.html:16
#: allianceauth/srp/templates/srp/data.html:117
#: allianceauth/srp/templates/srp/data.html:126
msgid "Approved"
msgstr "승인"
@@ -902,7 +923,7 @@ msgstr "승인"
#: allianceauth/hrapplications/templates/hrapplications/management.html:104
#: allianceauth/hrapplications/templates/hrapplications/management.html:149
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:42
#: allianceauth/srp/templates/srp/data.html:121
#: allianceauth/srp/templates/srp/data.html:130
msgid "Rejected"
msgstr "거절"
@@ -1285,23 +1306,50 @@ msgstr "비밀번호"
msgid "Password must be at least 8 characters long."
msgstr "비밀번호는 8글자 이상이어야 합니다."
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:23
#: allianceauth/services/modules/discord/models.py:225
msgid "Discord Account Disabled"
msgstr "디스코드 계정 비활성화"
#: allianceauth/services/modules/discord/models.py:227
msgid ""
"Your Discord account was disabled automatically by Auth. If you think this "
"was a mistake, please contact an admin."
msgstr "Auth에 의해 자동으로 디스코드 계정이 비활성화됐습니다. 원치 않는 사항일 경우, 관리자에게 문의해 주세요."
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:18
msgid "Join the Discord server"
msgstr "디스코드 서버 입장"
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:22
msgid "Leave- and rejoin the Discord Server (Reset)"
msgstr "디스코드 서버를 나가고 다시 입장하기 (리셋)"
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:25
msgid "Leave the Discord server"
msgstr "디스코드 서버 나가기"
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32
msgid "Link Discord Server"
msgstr "디스코드 서버 링크"
#: allianceauth/services/modules/discord/views.py:26
#: allianceauth/services/modules/discord/views.py:30
msgid "Deactivated Discord account."
msgstr "디스코드 계정 해제 완료"
#: allianceauth/services/modules/discord/views.py:29
#: allianceauth/services/modules/discord/views.py:41
#: allianceauth/services/modules/discord/views.py:65
#: allianceauth/services/modules/discord/views.py:36
#: allianceauth/services/modules/discord/views.py:59
msgid "An error occurred while processing your Discord account."
msgstr "디스코드 계정 처리 중 오류가 발생했습니다."
#: allianceauth/services/modules/discord/views.py:62
msgid "Activated Discord account."
msgstr "디스코드 계정 활성화 완료"
#: allianceauth/services/modules/discord/views.py:102
msgid "Your Discord account has been successfully activated."
msgstr "디스코드 계정과 성공적으로 연동됐습니다."
#: allianceauth/services/modules/discord/views.py:108
msgid ""
"An error occurred while trying to activate your Discord account. Please try "
"again."
msgstr "디스코드 계정 연동 중 오류가 발생했습니다. 다시 시도해 주세요."
#: allianceauth/services/modules/discourse/views.py:37
msgid "You are not authorized to access Discourse."
@@ -1565,7 +1613,7 @@ msgstr "서드파티"
msgid "Domain"
msgstr "도메인"
#: allianceauth/srp/auth_hooks.py:9
#: allianceauth/srp/auth_hooks.py:12
msgid "Ship Replacement"
msgstr "SRP"
@@ -1579,7 +1627,7 @@ msgstr "플릿 시간"
msgid "Fleet Doctrine"
msgstr "플릿 독트린"
#: allianceauth/srp/form.py:12 allianceauth/srp/templates/srp/data.html:89
#: allianceauth/srp/form.py:12 allianceauth/srp/templates/srp/data.html:90
msgid "Additional Info"
msgstr "추가 기재 사항"
@@ -1608,63 +1656,63 @@ msgstr "SRP 보상 플릿 생성"
msgid "Give this link to the line members"
msgstr "이 링크를 직계 멤버들에게 전달"
#: allianceauth/srp/templates/srp/data.html:48
#: allianceauth/srp/templates/srp/data.html:49
msgid "SRP Fleet Data"
msgstr "SRP 보상 플릿 데이터"
#: allianceauth/srp/templates/srp/data.html:53
#: allianceauth/srp/templates/srp/data.html:54
msgid "Mark Incomplete"
msgstr "표시 미완료"
#: allianceauth/srp/templates/srp/data.html:57
#: allianceauth/srp/templates/srp/data.html:58
msgid "Mark Completed"
msgstr "표시 완료"
#: allianceauth/srp/templates/srp/data.html:69
#: allianceauth/srp/templates/srp/data.html:145
#: allianceauth/srp/templates/srp/data.html:70
#: allianceauth/srp/templates/srp/data.html:156
msgid "Total Losses:"
msgstr "전체 손실:"
#: allianceauth/srp/templates/srp/data.html:70
#: allianceauth/srp/templates/srp/data.html:146
#: allianceauth/srp/templates/srp/data.html:71
#: allianceauth/srp/templates/srp/data.html:157
#: allianceauth/srp/templates/srp/management.html:30
msgid "Total ISK Cost:"
msgstr "전체 ISK 비용:"
#: allianceauth/srp/templates/srp/data.html:78
#: allianceauth/srp/templates/srp/data.html:154
#: allianceauth/srp/templates/srp/data.html:79
#: allianceauth/srp/templates/srp/data.html:165
msgid "Are you sure you want to delete SRP requests?"
msgstr "SRP 보상 요청을 삭제하시겠습니까?"
#: allianceauth/srp/templates/srp/data.html:87
#: allianceauth/srp/templates/srp/data.html:88
msgid "Pilot Name"
msgstr "파일럿 이름"
#: allianceauth/srp/templates/srp/data.html:88
#: allianceauth/srp/templates/srp/data.html:89
msgid "Killboard Link"
msgstr "킬보드 링크"
#: allianceauth/srp/templates/srp/data.html:90
#: allianceauth/srp/templates/srp/data.html:91
msgid "Ship Type"
msgstr "함선 종류"
#: allianceauth/srp/templates/srp/data.html:91
#: allianceauth/srp/templates/srp/data.html:92
msgid "Killboard Loss Amt"
msgstr "킬보드상 손실 금액"
#: allianceauth/srp/templates/srp/data.html:92
#: allianceauth/srp/templates/srp/data.html:93
msgid "SRP ISK Cost"
msgstr "SRP 보상 비용"
#: allianceauth/srp/templates/srp/data.html:93
#: allianceauth/srp/templates/srp/data.html:94
msgid "Click value to edit Enter to save & next ESC to cancel"
msgstr "금액을 수정하려면 클릭, 저장을 하고 다음으로 가려면 엔터, 취소를 하려면 ESC를 누르세요. "
#: allianceauth/srp/templates/srp/data.html:96
#: allianceauth/srp/templates/srp/data.html:97
msgid "Post Time"
msgstr "작성 시간"
#: allianceauth/srp/templates/srp/data.html:163
#: allianceauth/srp/templates/srp/data.html:174
msgid "No SRP requests for this fleet."
msgstr "이 플릿에는 SRP 보상 요청이 없습니다."
@@ -1856,32 +1904,30 @@ msgid "Current"
msgstr "현재"
#: allianceauth/templates/allianceauth/admin-status/overview.html:40
msgid "Latest Major"
msgstr "최근 주요 사항"
msgid "Latest Stable"
msgstr "최신 안정화 버전"
#: allianceauth/templates/allianceauth/admin-status/overview.html:46
#: allianceauth/templates/allianceauth/admin-status/overview.html:56
#: allianceauth/templates/allianceauth/admin-status/overview.html:66
msgid "Update available"
msgstr "업데이트 가능"
#: allianceauth/templates/allianceauth/admin-status/overview.html:50
msgid "Latest Minor"
msgstr "최근 기타 사항"
#: allianceauth/templates/allianceauth/admin-status/overview.html:51
msgid "Latest Pre-Release"
msgstr "최신 사전 출시 버전"
#: allianceauth/templates/allianceauth/admin-status/overview.html:60
msgid "Latest Patch"
msgstr "최근 패치"
#: allianceauth/templates/allianceauth/admin-status/overview.html:57
msgid "Pre-Release available"
msgstr "사전 출시 사용 가능"
#: allianceauth/templates/allianceauth/admin-status/overview.html:73
#: allianceauth/templates/allianceauth/admin-status/overview.html:65
msgid "Task Queue"
msgstr "대기 중인 할 일"
#: allianceauth/templates/allianceauth/admin-status/overview.html:90
#: allianceauth/templates/allianceauth/admin-status/overview.html:82
msgid "Error retrieving task queue length"
msgstr "대기 중인 할 일 목록 회수 에러"
#: allianceauth/templates/allianceauth/admin-status/overview.html:92
#: allianceauth/templates/allianceauth/admin-status/overview.html:84
#, python-format
msgid "%(tasks)s task"
msgid_plural "%(tasks)s tasks"
@@ -2021,3 +2067,6 @@ msgstr "%(time)s 에 있을 %(system)s 타이머를 추가했습니다."
#: allianceauth/timerboard/views.py:83
msgid "Saved changes to the timer."
msgstr "타이머 변경사항이 저장되었습니다."
#~ msgid "Portrait"
#~ msgstr "포트레잇"

View File

@@ -5,15 +5,17 @@
#
# Translators:
# Alexander Gess <de.alex.gess@gmail.com>, 2020
# Yuriy K <thedjcooltv@gmail.com>, 2020
# Андрей Зубков <and.vareba81@gmail.com>, 2020
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-08 00:57+0000\n"
"POT-Creation-Date: 2020-11-20 05:33+0000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n"
"Last-Translator: Alexander Gess <de.alex.gess@gmail.com>, 2020\n"
"Last-Translator: Андрей Зубков <and.vareba81@gmail.com>, 2020\n"
"Language-Team: Russian (https://www.transifex.com/alliance-auth/teams/107430/ru/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -25,74 +27,75 @@ msgstr ""
msgid "A main character is required to perform that action. Add one below."
msgstr "Необходимо указать основного персонажа. Добавим?"
#: allianceauth/authentication/forms.py:6
#: allianceauth/authentication/forms.py:5
msgid "Email"
msgstr "Email"
#: allianceauth/authentication/models.py:76
msgid "State Changed"
msgstr "Состояние заменено. "
#: allianceauth/authentication/models.py:77
#: allianceauth/authentication/models.py:78
#, python-format
msgid "Your user state has been changed to %(state)s"
msgstr "Статус вашего пользователя сменен на %(state)s"
msgid "State changed to: %s"
msgstr "Статус изменен: %s"
#: allianceauth/authentication/models.py:79
#, python-format
msgid "Your user's state is now: %(state)s"
msgstr "Статус пилота: %(state)s"
#: allianceauth/authentication/templates/authentication/dashboard.html:5
#: allianceauth/authentication/templates/authentication/dashboard.html:8
#: allianceauth/templates/allianceauth/side-menu.html:10
#: allianceauth/templates/allianceauth/side-menu.html:11
msgid "Dashboard"
msgstr "Панель показателей"
#: allianceauth/authentication/templates/authentication/dashboard.html:17
#: allianceauth/corputils/templates/corputils/corpstats.html:116
#: allianceauth/corputils/templates/corputils/search.html:16
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
msgid "Main Character"
msgstr "Основной персонаж"
#: allianceauth/authentication/templates/authentication/dashboard.html:18
#, python-format
msgid ""
"\n"
" Main Character (State: %(state)s)\n"
" "
msgstr ""
"\n"
" Основной персонаж (статус: %(state)s)\n"
" "
#: allianceauth/authentication/templates/authentication/dashboard.html:77
#: allianceauth/authentication/templates/authentication/dashboard.html:81
msgid "No main character set."
msgstr "Основной персонаж не установлен."
#: allianceauth/authentication/templates/authentication/dashboard.html:84
#: allianceauth/authentication/templates/authentication/dashboard.html:88
msgid "Add Character"
msgstr "Добавить Персонажа"
#: allianceauth/authentication/templates/authentication/dashboard.html:88
#: allianceauth/authentication/templates/authentication/dashboard.html:92
msgid "Change Main"
msgstr "Сменить основного персонажа"
#: allianceauth/authentication/templates/authentication/dashboard.html:97
#: allianceauth/authentication/templates/authentication/dashboard.html:101
msgid "Group Memberships"
msgstr "Групповое участие"
msgstr "Роли"
#: allianceauth/authentication/templates/authentication/dashboard.html:117
#: allianceauth/authentication/templates/authentication/dashboard.html:121
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
#: allianceauth/hrapplications/templates/hrapplications/view.html:41
msgid "Characters"
msgstr "Персонажи"
#: allianceauth/authentication/templates/authentication/dashboard.html:125
#: allianceauth/authentication/templates/authentication/dashboard.html:129
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:22
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:15
#: allianceauth/hrapplications/templates/hrapplications/view.html:45
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34
msgid "Name"
msgstr "Имя"
#: allianceauth/authentication/templates/authentication/dashboard.html:126
#: allianceauth/authentication/templates/authentication/dashboard.html:130
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticsview.html:23
#: allianceauth/hrapplications/templates/hrapplications/view.html:46
msgid "Corp"
msgstr "Корпорация"
#: allianceauth/authentication/templates/authentication/dashboard.html:127
#: allianceauth/authentication/templates/authentication/dashboard.html:131
#: allianceauth/corputils/templates/corputils/corpstats.html:77
#: allianceauth/hrapplications/templates/hrapplications/view.html:47
msgid "Alliance"
@@ -141,6 +144,7 @@ msgid ""
"Cannot change main character to %(char)s: character owned by a different "
"account."
msgstr ""
"Нельзя сменить основного персонажа на %(char)s: похоже, что Владелец не Вы. "
#: allianceauth/authentication/views.py:80
#, python-format
@@ -161,23 +165,23 @@ msgstr "Персонаж %(name)s уже добавлен."
msgid "Unable to authenticate as the selected character."
msgstr "Невозможно авторизировать этого персонажа. "
#: allianceauth/authentication/views.py:148
#: allianceauth/authentication/views.py:151
msgid "Registration token has expired."
msgstr "Регистрационный токен просрочен."
#: allianceauth/authentication/views.py:200
#: allianceauth/authentication/views.py:206
msgid ""
"Sent confirmation email. Please follow the link to confirm your email "
"address."
msgstr "Отправить подтверждающее письмо. Пожалуйста, подтвердите почту. "
#: allianceauth/authentication/views.py:205
#: allianceauth/authentication/views.py:211
msgid "Confirmed your email address. Please login to continue."
msgstr "Подтвердите Ваш email адрес. Зайти для подтверждения. "
#: allianceauth/authentication/views.py:210
msgid "Registraion of new accounts it not allowed at this time."
msgstr "Регистрация нового аккаунта сейчас невозможна."
#: allianceauth/authentication/views.py:216
msgid "Registration of new accounts is not allowed at this time."
msgstr "Регистрация новых аккаунтов в настоящее время невозможна."
#: allianceauth/corputils/auth_hooks.py:10
msgid "Corporation Stats"
@@ -224,16 +228,16 @@ msgstr "Последнее обновление: "
#: allianceauth/corputils/templates/corputils/search.html:13
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:37
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:96
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:51
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:109
msgid "Character"
msgstr "Персонаж"
#: allianceauth/corputils/templates/corputils/corpstats.html:76
#: allianceauth/corputils/templates/corputils/search.html:14
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:28
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
#: allianceauth/hrapplications/templates/hrapplications/management.html:27
#: allianceauth/hrapplications/templates/hrapplications/management.html:84
#: allianceauth/hrapplications/templates/hrapplications/management.html:129
@@ -249,6 +253,16 @@ msgstr "Корпорация"
msgid "Killboard"
msgstr "zKillBoard"
#: allianceauth/corputils/templates/corputils/corpstats.html:116
#: allianceauth/corputils/templates/corputils/search.html:16
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:22
#: allianceauth/hrapplications/templates/hrapplications/management.html:83
#: allianceauth/hrapplications/templates/hrapplications/management.html:128
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:25
#: allianceauth/hrapplications/templates/hrapplications/view.html:32
msgid "Main Character"
msgstr "Основной персонаж"
#: allianceauth/corputils/templates/corputils/corpstats.html:117
#: allianceauth/corputils/templates/corputils/search.html:17
msgid "Main Corporation"
@@ -314,7 +328,7 @@ msgstr "Добавить сюда"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:19
msgid "before attempting to click fleet attendance links."
msgstr "перед вступлением проверте содержимое"
msgstr "перед вступлением проверьте содержимое"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkformatter.html:6
msgid "Create Fatlink"
@@ -536,31 +550,35 @@ msgstr "Флотовое участие зарегистрированно."
msgid "FAT link has expired."
msgstr "ФлАк ссылка устарела"
#: allianceauth/groupmanagement/auth_hooks.py:17
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:14
msgid "Group Management"
msgstr "Управление Группой"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:13
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:14
msgid "Audit Log"
msgstr "Записи безопасности"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:18
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:20
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:20
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:21
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
msgid "Back"
msgstr "Назад"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:28
msgid "Date/Time"
msgstr "Дата / Время"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:26
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
msgid "Requestor"
msgstr "Запрос от"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:32
msgid "Type"
msgstr "Тип"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:17
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:33
#: allianceauth/notifications/templates/notifications/list.html:37
#: allianceauth/notifications/templates/notifications/list.html:69
#: allianceauth/optimer/templates/optimer/fleetoptable.html:18
@@ -571,11 +589,19 @@ msgstr "Тип"
msgid "Action"
msgstr "Действие"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:34
msgid "Actor"
msgstr "Исполнитель"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:47
msgid "Removed"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:59
msgid "All times displayed are EVE/UTC."
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:66
msgid "No entries found for this group."
msgstr "Нет вхождений в эту группу"
@@ -583,22 +609,24 @@ msgstr "Нет вхождений в эту группу"
msgid "Group Members"
msgstr "Групповые Участники"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:28
msgid "Portrait"
msgstr "Портрет"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:38
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:31
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:52
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:110
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
msgid "Organization"
msgstr "Корпорация"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:64
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:61
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:78
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:136
msgid "(unknown)"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:66
msgid "Remove from group"
msgstr "Исключить из группы"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:76
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:81
msgid "No group members to list."
msgstr "Нет участников в группе"
@@ -606,56 +634,56 @@ msgstr "Нет участников в группе"
msgid "Groups Membership"
msgstr "Участники группы"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:14
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:15
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:40
#: allianceauth/templates/allianceauth/side-menu.html:15
#: allianceauth/templates/allianceauth/side-menu.html:16
msgid "Groups"
msgstr "Группы"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
msgid "Description"
msgstr "Описание"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:26
#: allianceauth/hrapplications/templates/hrapplications/management.html:28
#: allianceauth/hrapplications/templates/hrapplications/management.html:85
#: allianceauth/hrapplications/templates/hrapplications/management.html:130
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:27
#: allianceauth/srp/templates/srp/data.html:97
#: allianceauth/srp/templates/srp/data.html:98
msgid "Status"
msgstr "Статус"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:27
msgid "Member Count"
msgstr "Число участников"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:38
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:43
msgid "Hidden"
msgstr "Скрытые"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:40
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
#: allianceauth/templates/allianceauth/admin-status/overview.html:12
msgid "Open"
msgstr "Открыть"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:42
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:47
msgid "Requestable"
msgstr "Запрошено"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:50
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
msgid "View Members"
msgstr "Посмотреть участников"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:53
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:60
msgid "Audit Members"
msgstr "Проверить участников"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
msgid "Copy Direrct Join Link"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
msgid "Copy Direct Join Link"
msgstr "Скопировать ссылку подключения"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:75
msgid "No groups to list."
msgstr "Нет групп в списке"
@@ -664,19 +692,19 @@ msgstr "Нет групп в списке"
msgid "Available Groups"
msgstr "Доступные группы"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:29
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:30
msgid "Leave"
msgstr "Покинуть"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:39
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:40
msgid "Join"
msgstr "Присоединиться"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:43
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:44
msgid "Request"
msgstr "Запрос"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:58
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:59
msgid "No groups available."
msgstr "Нет доступных групп."
@@ -684,77 +712,72 @@ msgstr "Нет доступных групп."
msgid "Groups Management"
msgstr "Управление Группами"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:23
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:25
msgid "Join Requests"
msgstr "Запрос на присоединение"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:34
msgid "Leave Requests"
msgstr "Запрос на Выход"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:39
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:98
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:53
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:111
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
#: allianceauth/services/modules/openfire/forms.py:6
msgid "Group"
msgstr "Группа"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:71
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:130
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:84
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
msgid "Accept"
msgstr "Принять"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:74
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:133
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:146
#: allianceauth/hrapplications/templates/hrapplications/view.html:85
msgid "Reject"
msgstr "Сбросить"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:83
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:97
msgid "No group add requests."
msgstr "Нет групповых запросов на вступление"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:155
msgid "No group leave requests."
msgstr "Нет групповых запросов на выход"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:10
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:9
msgid "Toggle navigation"
msgstr "Проложить маршрут"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15
#: allianceauth/templates/allianceauth/side-menu.html:23
msgid "Group Management"
msgstr "Управление Группой"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:21
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:20
msgid "Group Requests"
msgstr "Групповой запрос"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:24
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:23
msgid "Group Membership"
msgstr "Групповое участие"
#: allianceauth/groupmanagement/views.py:166
#: allianceauth/groupmanagement/views.py:162
#, python-format
msgid "Removed user %(user)s from group %(group)s."
msgstr "Пользователь %(user)s исключен из %(group)s."
#: allianceauth/groupmanagement/views.py:168
#: allianceauth/groupmanagement/views.py:164
msgid "User does not exist in that group"
msgstr "Пользователь не существует в этой группе."
#: allianceauth/groupmanagement/views.py:171
#: allianceauth/groupmanagement/views.py:167
msgid "Group does not exist"
msgstr "Группа не существует."
#: allianceauth/groupmanagement/views.py:198
#: allianceauth/groupmanagement/views.py:194
#, python-format
msgid "Accepted application from %(mainchar)s to %(group)s."
msgstr "Запрос от %(mainchar)sв %(group)s принят."
#: allianceauth/groupmanagement/views.py:205
#: allianceauth/groupmanagement/views.py:238
#: allianceauth/groupmanagement/views.py:201
#: allianceauth/groupmanagement/views.py:234
#, python-format
msgid ""
"An unhandled error occurred while processing the application from "
@@ -763,78 +786,80 @@ msgstr ""
"Персонаж %(mainchar)s не может быть добавлен %(group)s, из-за непредвиденной"
" ошибки. "
#: allianceauth/groupmanagement/views.py:231
#: allianceauth/groupmanagement/views.py:227
#, python-format
msgid "Rejected application from %(mainchar)s to %(group)s."
msgstr "%(mainchar)s исключен из %(group)s."
#: allianceauth/groupmanagement/views.py:267
#: allianceauth/groupmanagement/views.py:263
#, python-format
msgid "Accepted application from %(mainchar)s to leave %(group)s."
msgstr "Утвержден выход %(mainchar)s из %(group)s. "
#: allianceauth/groupmanagement/views.py:273
#: allianceauth/groupmanagement/views.py:307
#: allianceauth/groupmanagement/views.py:269
#: allianceauth/groupmanagement/views.py:303
#, python-format
msgid ""
"An unhandled error occurred while processing the application from "
"%(mainchar)s to leave %(group)s."
msgstr ""
"Возникла ошибка во время обработки %(mainchar)s на выход из группы "
"%(group)s. Повторите позже."
#: allianceauth/groupmanagement/views.py:300
#: allianceauth/groupmanagement/views.py:296
#, python-format
msgid "Rejected application from %(mainchar)s to leave %(group)s."
msgstr "Прошение об исключении %(mainchar)s из %(group)s отклонено. "
#: allianceauth/groupmanagement/views.py:346
#: allianceauth/groupmanagement/views.py:358
#: allianceauth/groupmanagement/views.py:342
#: allianceauth/groupmanagement/views.py:354
msgid "You cannot join that group"
msgstr "Вы не можете вступить"
#: allianceauth/groupmanagement/views.py:352
#: allianceauth/groupmanagement/views.py:348
msgid "You are already a member of that group."
msgstr ""
msgstr "Вы уже участник этой группы."
#: allianceauth/groupmanagement/views.py:367
#: allianceauth/groupmanagement/views.py:363
msgid "You already have a pending application for that group."
msgstr ""
msgstr "Вы уже подали заявку на вступление этой группы."
#: allianceauth/groupmanagement/views.py:370
#: allianceauth/groupmanagement/views.py:408
#: allianceauth/groupmanagement/views.py:366
#: allianceauth/groupmanagement/views.py:404
#: allianceauth/hrapplications/templates/hrapplications/management.html:37
#: allianceauth/hrapplications/templates/hrapplications/management.html:72
#: allianceauth/hrapplications/templates/hrapplications/management.html:99
#: allianceauth/hrapplications/templates/hrapplications/management.html:144
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:38
#: allianceauth/hrapplications/templates/hrapplications/view.html:20
#: allianceauth/srp/templates/srp/data.html:125
#: allianceauth/srp/templates/srp/data.html:134
#: allianceauth/srp/templates/srp/management.html:81
msgid "Pending"
msgstr "Ожидание"
#: allianceauth/groupmanagement/views.py:376
#: allianceauth/groupmanagement/views.py:372
#, python-format
msgid "Applied to group %(group)s."
msgstr "Вступить в группу %(group)s."
#: allianceauth/groupmanagement/views.py:387
#: allianceauth/groupmanagement/views.py:383
msgid "You cannot leave that group"
msgstr "Вы не можете покинуть эту группу"
#: allianceauth/groupmanagement/views.py:392
#: allianceauth/groupmanagement/views.py:388
msgid "You are not a member of that group"
msgstr "Вы не участник группыы"
#: allianceauth/groupmanagement/views.py:401
#: allianceauth/groupmanagement/views.py:397
msgid "You already have a pending leave request for that group."
msgstr ""
msgstr "Ваш запрос находится на рассмотрении"
#: allianceauth/groupmanagement/views.py:414
#: allianceauth/groupmanagement/views.py:410
#, python-format
msgid "Applied to leave group %(group)s."
msgstr "Запрос на выход из группы %(group)s."
#: allianceauth/hrapplications/auth_hooks.py:10
#: allianceauth/hrapplications/auth_hooks.py:13
msgid "Applications"
msgstr "Запросы"
@@ -891,7 +916,7 @@ msgstr "Пользователь"
#: allianceauth/hrapplications/templates/hrapplications/management.html:131
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:28
#: allianceauth/hrapplications/templates/hrapplications/view.html:75
#: allianceauth/srp/templates/srp/data.html:99
#: allianceauth/srp/templates/srp/data.html:100
#: allianceauth/srp/templates/srp/management.html:46
msgid "Actions"
msgstr "Действия"
@@ -901,7 +926,7 @@ msgstr "Действия"
#: allianceauth/hrapplications/templates/hrapplications/management.html:147
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:40
#: allianceauth/hrapplications/templates/hrapplications/view.html:16
#: allianceauth/srp/templates/srp/data.html:117
#: allianceauth/srp/templates/srp/data.html:126
msgid "Approved"
msgstr "Проверено"
@@ -909,7 +934,7 @@ msgstr "Проверено"
#: allianceauth/hrapplications/templates/hrapplications/management.html:104
#: allianceauth/hrapplications/templates/hrapplications/management.html:149
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:42
#: allianceauth/srp/templates/srp/data.html:121
#: allianceauth/srp/templates/srp/data.html:130
msgid "Rejected"
msgstr "Отменено "
@@ -1015,7 +1040,7 @@ msgstr "Не прочитанно"
#: allianceauth/notifications/templates/notifications/list.html:18
msgid "Read"
msgstr "Прочитать"
msgstr "Прочитано"
#: allianceauth/notifications/templates/notifications/list.html:22
msgid "Mark All Read"
@@ -1123,7 +1148,7 @@ msgstr "Таймера Флотовых операций"
#: allianceauth/optimer/templates/optimer/management.html:21
#: allianceauth/timerboard/templates/timerboard/view.html:23
msgid "Current Eve Time:"
msgstr "ET"
msgstr "Текущий EVE Time:"
#: allianceauth/optimer/templates/optimer/management.html:27
#: allianceauth/timerboard/templates/timerboard/view.html:189
@@ -1222,15 +1247,15 @@ msgstr "Состояния"
#: allianceauth/services/abstract.py:72
msgid "That service account already exists"
msgstr ""
msgstr "Этот сервис уже активирован"
#: allianceauth/services/abstract.py:104
msgid "Successfully set your {} password"
msgstr ""
msgstr "{} Пароль успешно обновлен."
#: allianceauth/services/auth_hooks.py:11
msgid "Services"
msgstr ""
msgstr "Подключение сервисов"
#: allianceauth/services/forms.py:6
msgid "Name of Fleet:"
@@ -1292,37 +1317,72 @@ msgstr "Пароль"
msgid "Password must be at least 8 characters long."
msgstr "Пароль должен быть не менее 8 символов."
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:23
#: allianceauth/services/modules/discord/models.py:225
msgid "Discord Account Disabled"
msgstr "Discord персонаж отключен"
#: allianceauth/services/modules/discord/models.py:227
msgid ""
"Your Discord account was disabled automatically by Auth. If you think this "
"was a mistake, please contact an admin."
msgstr ""
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:18
msgid "Join the Discord server"
msgstr "Подключиться к серверу Discord"
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:22
msgid "Leave- and rejoin the Discord Server (Reset)"
msgstr "Переподключиться к серверу Discord. "
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:25
msgid "Leave the Discord server"
msgstr "Покинуть Discord сервер"
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:32
msgid "Link Discord Server"
msgstr "Ссылка на сервер Discord"
#: allianceauth/services/modules/discord/views.py:26
#: allianceauth/services/modules/discord/views.py:30
msgid "Deactivated Discord account."
msgstr ""
msgstr "Отменить доступ на Discord сервер."
#: allianceauth/services/modules/discord/views.py:29
#: allianceauth/services/modules/discord/views.py:41
#: allianceauth/services/modules/discord/views.py:65
#: allianceauth/services/modules/discord/views.py:36
#: allianceauth/services/modules/discord/views.py:59
msgid "An error occurred while processing your Discord account."
msgstr ""
"Во время обработки Discord аккаунта возникла ошибка. Попробуйте чуточку "
"позднее. "
#: allianceauth/services/modules/discord/views.py:62
msgid "Activated Discord account."
#: allianceauth/services/modules/discord/views.py:102
msgid "Your Discord account has been successfully activated."
msgstr "Доступ на сервер Discord успешно получен."
#: allianceauth/services/modules/discord/views.py:108
msgid ""
"An error occurred while trying to activate your Discord account. Please try "
"again."
msgstr ""
"Во время активации Discord аккаунта возникла ошибка. Попробуйте чуточку "
"позднее. "
#: allianceauth/services/modules/discourse/views.py:37
msgid "You are not authorized to access Discourse."
msgstr ""
msgstr "Вы не авторизованы в Discourse."
#: allianceauth/services/modules/discourse/views.py:42
msgid "You must have a main character set to access Discourse."
msgstr ""
"Для авторизации Discourse, необходимо получить авторизацию Вашим основным "
"аккаунтом."
#: allianceauth/services/modules/discourse/views.py:52
msgid ""
"No SSO payload or signature. Please contact support if this problem "
"persists."
msgstr ""
"Отсуствует связь SSO. Если ошибка повторяется - свяжитесь с тех. поддержкой."
" "
#: allianceauth/services/modules/discourse/views.py:62
#: allianceauth/services/modules/discourse/views.py:70
@@ -1354,7 +1414,7 @@ msgstr ""
#: allianceauth/services/modules/openfire/auth_hooks.py:26
msgid "Jabber"
msgstr ""
msgstr "Jabber"
#: allianceauth/services/modules/openfire/auth_hooks.py:78
#: allianceauth/services/modules/openfire/templates/services/openfire/broadcast.html:6
@@ -1380,50 +1440,50 @@ msgstr "Бродкаст"
#: allianceauth/services/modules/openfire/views.py:35
msgid "Activated jabber account."
msgstr ""
msgstr "Активировать доступ в jabber."
#: allianceauth/services/modules/openfire/views.py:44
#: allianceauth/services/modules/openfire/views.py:57
#: allianceauth/services/modules/openfire/views.py:78
#: allianceauth/services/modules/openfire/views.py:151
msgid "An error occurred while processing your jabber account."
msgstr ""
msgstr "Возникла ошибка во время активации jabber'а ."
#: allianceauth/services/modules/openfire/views.py:70
msgid "Reset jabber password."
msgstr ""
msgstr "Сбросить jabber пароль."
#: allianceauth/services/modules/openfire/views.py:119
#, python-format
msgid "Sent jabber broadcast to %s"
msgstr ""
msgstr "Отправить Бродкаст %s"
#: allianceauth/services/modules/openfire/views.py:148
msgid "Set jabber password."
msgstr ""
msgstr "Установить jabber пароль."
#: allianceauth/services/modules/phpbb3/views.py:34
msgid "Activated forum account."
msgstr ""
msgstr "Допустить на Форум."
#: allianceauth/services/modules/phpbb3/views.py:43
#: allianceauth/services/modules/phpbb3/views.py:57
#: allianceauth/services/modules/phpbb3/views.py:80
#: allianceauth/services/modules/phpbb3/views.py:103
msgid "An error occurred while processing your forum account."
msgstr ""
msgstr "Во время обработки Форумного аккаунта, возникла ошибка."
#: allianceauth/services/modules/phpbb3/views.py:54
msgid "Deactivated forum account."
msgstr ""
msgstr "Отменить доступ на Форум. "
#: allianceauth/services/modules/phpbb3/views.py:71
msgid "Reset forum password."
msgstr ""
msgstr "Сбросить пароль на Форум."
#: allianceauth/services/modules/phpbb3/views.py:100
msgid "Set forum password."
msgstr ""
msgstr "Установить пароль на Форум."
#: allianceauth/services/modules/smf/views.py:34
msgid "Activated SMF account."
@@ -1473,21 +1533,21 @@ msgstr "Продолжить"
#: allianceauth/services/modules/teamspeak3/views.py:34
msgid "Activated TeamSpeak3 account."
msgstr ""
msgstr "Активировать аккаунт TeamSpeak3."
#: allianceauth/services/modules/teamspeak3/views.py:37
#: allianceauth/services/modules/teamspeak3/views.py:74
#: allianceauth/services/modules/teamspeak3/views.py:100
msgid "An error occurred while processing your TeamSpeak3 account."
msgstr ""
msgstr "Во время активации TeamSpeak3 возникла ошибка, попробуйте позже."
#: allianceauth/services/modules/teamspeak3/views.py:71
msgid "Deactivated TeamSpeak3 account."
msgstr ""
msgstr "Отключить TeamSpeak3 аккаунт."
#: allianceauth/services/modules/teamspeak3/views.py:97
msgid "Reset TeamSpeak3 permission key."
msgstr ""
msgstr "Сбросить TeamSpeak3 ключ доступа."
#: allianceauth/services/modules/xenforo/views.py:30
msgid "Activated XenForo account."
@@ -1572,9 +1632,9 @@ msgstr "Сервис"
msgid "Domain"
msgstr "Домен"
#: allianceauth/srp/auth_hooks.py:9
#: allianceauth/srp/auth_hooks.py:12
msgid "Ship Replacement"
msgstr "Замена корабля"
msgstr "Компенсация корабля"
#: allianceauth/srp/form.py:7
#: allianceauth/srp/templates/srp/management.html:38
@@ -1586,7 +1646,7 @@ msgstr "Флотовое время"
msgid "Fleet Doctrine"
msgstr "Флотовая Доктрина"
#: allianceauth/srp/form.py:12 allianceauth/srp/templates/srp/data.html:89
#: allianceauth/srp/form.py:12 allianceauth/srp/templates/srp/data.html:90
msgid "Additional Info"
msgstr "Дополнительная информация"
@@ -1615,63 +1675,63 @@ msgstr "Создать SRP Флот"
msgid "Give this link to the line members"
msgstr "Поделиться ссылкой с рядовыми участниками"
#: allianceauth/srp/templates/srp/data.html:48
#: allianceauth/srp/templates/srp/data.html:49
msgid "SRP Fleet Data"
msgstr "SRP данные флота"
#: allianceauth/srp/templates/srp/data.html:53
#: allianceauth/srp/templates/srp/data.html:54
msgid "Mark Incomplete"
msgstr "Пометить незаконченным"
#: allianceauth/srp/templates/srp/data.html:57
#: allianceauth/srp/templates/srp/data.html:58
msgid "Mark Completed"
msgstr "Пометить законченным"
#: allianceauth/srp/templates/srp/data.html:69
#: allianceauth/srp/templates/srp/data.html:145
#: allianceauth/srp/templates/srp/data.html:70
#: allianceauth/srp/templates/srp/data.html:156
msgid "Total Losses:"
msgstr "Суммарные потери:"
#: allianceauth/srp/templates/srp/data.html:70
#: allianceauth/srp/templates/srp/data.html:146
#: allianceauth/srp/templates/srp/data.html:71
#: allianceauth/srp/templates/srp/data.html:157
#: allianceauth/srp/templates/srp/management.html:30
msgid "Total ISK Cost:"
msgstr "Оценочная стоимость (ISK):"
#: allianceauth/srp/templates/srp/data.html:78
#: allianceauth/srp/templates/srp/data.html:154
#: allianceauth/srp/templates/srp/data.html:79
#: allianceauth/srp/templates/srp/data.html:165
msgid "Are you sure you want to delete SRP requests?"
msgstr "Вы уверенны что хотите удалить запрос на SRP?"
#: allianceauth/srp/templates/srp/data.html:87
#: allianceauth/srp/templates/srp/data.html:88
msgid "Pilot Name"
msgstr "Имя Пилота"
#: allianceauth/srp/templates/srp/data.html:88
#: allianceauth/srp/templates/srp/data.html:89
msgid "Killboard Link"
msgstr "zKillBoard ссылка"
#: allianceauth/srp/templates/srp/data.html:90
#: allianceauth/srp/templates/srp/data.html:91
msgid "Ship Type"
msgstr "Тип корабля"
#: allianceauth/srp/templates/srp/data.html:91
#: allianceauth/srp/templates/srp/data.html:92
msgid "Killboard Loss Amt"
msgstr "потерь по zKillBoard на данный момент"
#: allianceauth/srp/templates/srp/data.html:92
#: allianceauth/srp/templates/srp/data.html:93
msgid "SRP ISK Cost"
msgstr "SRP ISK Стоимость"
#: allianceauth/srp/templates/srp/data.html:93
#: allianceauth/srp/templates/srp/data.html:94
msgid "Click value to edit Enter to save & next ESC to cancel"
msgstr "Нажмите на значение для редактирования и ESC для отмены"
#: allianceauth/srp/templates/srp/data.html:96
#: allianceauth/srp/templates/srp/data.html:97
msgid "Post Time"
msgstr "Опубликованно"
#: allianceauth/srp/templates/srp/data.html:163
#: allianceauth/srp/templates/srp/data.html:174
msgid "No SRP requests for this fleet."
msgstr "SRP запросы отсутствуют"
@@ -1866,32 +1926,30 @@ msgid "Current"
msgstr "Текущий"
#: allianceauth/templates/allianceauth/admin-status/overview.html:40
msgid "Latest Major"
msgstr "Последняя версия"
msgid "Latest Stable"
msgstr "Стабильная Версия"
#: allianceauth/templates/allianceauth/admin-status/overview.html:46
#: allianceauth/templates/allianceauth/admin-status/overview.html:56
#: allianceauth/templates/allianceauth/admin-status/overview.html:66
msgid "Update available"
msgstr "Доступно обновление"
#: allianceauth/templates/allianceauth/admin-status/overview.html:50
msgid "Latest Minor"
msgstr "Последняя версия"
#: allianceauth/templates/allianceauth/admin-status/overview.html:51
msgid "Latest Pre-Release"
msgstr "Предрелизная Версия"
#: allianceauth/templates/allianceauth/admin-status/overview.html:60
msgid "Latest Patch"
msgstr "Последние исправления"
#: allianceauth/templates/allianceauth/admin-status/overview.html:57
msgid "Pre-Release available"
msgstr "Предрелизная Версия"
#: allianceauth/templates/allianceauth/admin-status/overview.html:73
#: allianceauth/templates/allianceauth/admin-status/overview.html:65
msgid "Task Queue"
msgstr "Список задач"
#: allianceauth/templates/allianceauth/admin-status/overview.html:90
#: allianceauth/templates/allianceauth/admin-status/overview.html:82
msgid "Error retrieving task queue length"
msgstr "Ошибка при получении списка задач. "
#: allianceauth/templates/allianceauth/admin-status/overview.html:92
#: allianceauth/templates/allianceauth/admin-status/overview.html:84
#, python-format
msgid "%(tasks)s task"
msgid_plural "%(tasks)s tasks"
@@ -1925,14 +1983,14 @@ msgstr "Прочие"
#: allianceauth/timerboard/templates/timerboard/view.html:220
#: allianceauth/timerboard/templates/timerboard/view.html:388
msgid "Friendly"
msgstr "Дружествен"
msgstr "Дружественный"
#: allianceauth/timerboard/form.py:55
#: allianceauth/timerboard/templates/timerboard/view.html:53
#: allianceauth/timerboard/templates/timerboard/view.html:215
#: allianceauth/timerboard/templates/timerboard/view.html:383
msgid "Hostile"
msgstr "Заложник"
msgstr "Вражеский"
#: allianceauth/timerboard/form.py:56
#: allianceauth/timerboard/templates/timerboard/view.html:63
@@ -2034,3 +2092,6 @@ msgstr "Добавлен таймер в %(system)s на %(time)s."
#: allianceauth/timerboard/views.py:83
msgid "Saved changes to the timer."
msgstr "Изменения сохранены"
#~ msgid "Portrait"
#~ msgstr "Портрет"

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,9 @@
default_app_config = 'allianceauth.notifications.apps.NotificationsConfig'
import logging
logger = logging.getLogger(__name__)
MAX_NOTIFICATIONS = 50
def notify(user, title, message=None, level='info'):
def notify(
user: object, title: str, message: str = None, level: str = 'info'
) -> None:
"""Sends a new notification to user. Convenience function to manager pendant."""
from .models import Notification
if Notification.objects.filter(user=user).count() > MAX_NOTIFICATIONS:
for n in Notification.objects.filter(user=user)[MAX_NOTIFICATIONS-1:]:
n.delete()
notif = Notification()
notif.user = user
notif.title = title
if not message:
message = title
notif.message = message
notif.level = level
notif.save()
logger.info("Created notification %s" % notif)
Notification.objects.notify_user(user, title, message, level)

View File

@@ -1,5 +0,0 @@
from .models import Notification
def user_notification_count(request):
return {'notifications': len(Notification.objects.filter(user__id=request.user.id).filter(viewed=False))}

View File

@@ -0,0 +1,101 @@
import logging
from django.conf import settings
from django.core.cache import cache
from django.db import models
from django.contrib.auth.models import User
logger = logging.getLogger(__name__)
class NotificationQuerySet(models.QuerySet):
"""Custom QuerySet for Notification model"""
def update(self, *args, **kwargs):
# overriden update to ensure cache is invaidated on very call
super().update(*args, **kwargs)
user_pks = set(self.select_related("user").values_list('user__pk', flat=True))
for user_pk in user_pks:
NotificationManager.invalidate_user_notification_cache(user_pk)
class NotificationManager(models.Manager):
USER_NOTIFICATION_COUNT_PREFIX = 'USER_NOTIFICATION_COUNT'
USER_NOTIFICATION_COUNT_CACHE_DURATION = 86_400
def get_queryset(self):
return NotificationQuerySet(self.model, using=self._db)
def notify_user(
self, user: object, title: str, message: str = None, level: str = 'info'
) -> object:
"""Sends a new notification to user. Returns newly created notification object.
"""
max_notifications = self._max_notifications_per_user()
if self.filter(user=user).count() >= max_notifications:
to_be_deleted_qs = self.filter(user=user).order_by(
"-timestamp"
)[max_notifications - 1:]
for notification in to_be_deleted_qs:
notification.delete()
if not message:
message = title
obj = self.create(user=user, title=title, message=message, level=level)
logger.info("Created notification %s", obj)
return obj
def _max_notifications_per_user(self):
"""return the maximum number of notifications allowed per user"""
max_notifications = getattr(settings, 'NOTIFICATIONS_MAX_PER_USER', None)
if (
max_notifications is None
or not isinstance(max_notifications, int)
or max_notifications < 0
):
logger.warning(
'NOTIFICATIONS_MAX_PER_USER setting is invalid. Using default.'
)
max_notifications = self.model.NOTIFICATIONS_MAX_PER_USER_DEFAULT
return max_notifications
def user_unread_count(self, user_pk: int) -> int:
"""returns the cached unread count for a user given by user PK
Will return -1 if user can not be found
"""
cache_key = self._user_notification_cache_key(user_pk)
unread_count = cache.get(key=cache_key)
if not unread_count:
try:
user = User.objects.get(pk=user_pk)
except User.DoesNotExist:
unread_count = -1
else:
logger.debug(
'Updating notification cache for user with pk %s', user_pk
)
unread_count = user.notification_set.filter(viewed=False).count()
cache.set(
key=cache_key,
value=unread_count,
timeout=self.USER_NOTIFICATION_COUNT_CACHE_DURATION
)
else:
logger.debug(
'Returning notification count from cache for user with pk %s', user_pk
)
return unread_count
@classmethod
def invalidate_user_notification_cache(cls, user_pk: int) -> None:
cache.delete(key=cls._user_notification_cache_key(user_pk))
logger.debug('Invalided notification cache for user with pk %s', user_pk)
@classmethod
def _user_notification_cache_key(cls, user_pk: int) -> str:
return f'{cls.USER_NOTIFICATION_COUNT_PREFIX}_{user_pk}'

View File

@@ -0,0 +1,27 @@
# Generated by Django 3.1.5 on 2021-01-07 21:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('notifications', '0003_make_strings_more_stringy'),
]
operations = [
migrations.AlterModelOptions(
name='notification',
options={},
),
migrations.AlterField(
model_name='notification',
name='timestamp',
field=models.DateTimeField(auto_now_add=True, db_index=True),
),
migrations.AlterField(
model_name='notification',
name='viewed',
field=models.BooleanField(db_index=True, default=False),
),
]

View File

@@ -1,11 +1,19 @@
import logging
from django.db import models
from django.contrib.auth.models import User
import logging
from .managers import NotificationManager
logger = logging.getLogger(__name__)
class Notification(models.Model):
"""Notification to a user within Auth"""
NOTIFICATIONS_MAX_PER_USER_DEFAULT = 50
NOTIFICATIONS_REFRESH_TIME_DEFAULT = 30
LEVEL_CHOICES = (
('danger', 'CRITICAL'),
('danger', 'ERROR'),
@@ -18,19 +26,41 @@ class Notification(models.Model):
level = models.CharField(choices=LEVEL_CHOICES, max_length=10)
title = models.CharField(max_length=254)
message = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True)
viewed = models.BooleanField(default=False)
timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
viewed = models.BooleanField(default=False, db_index=True)
def view(self):
objects = NotificationManager()
def __str__(self) -> str:
return "%s: %s" % (self.user, self.title)
def save(self, *args, **kwargs):
# overriden save to ensure cache is invaidated on very call
super().save(*args, **kwargs)
Notification.objects.invalidate_user_notification_cache(self.user.pk)
def delete(self, *args, **kwargs):
# overriden delete to ensure cache is invaidated on very call
super().delete(*args, **kwargs)
Notification.objects.invalidate_user_notification_cache(self.user.pk)
def mark_viewed(self) -> None:
"""mark notification as viewed"""
logger.info("Marking notification as viewed: %s" % self)
self.viewed = True
self.save()
def __str__(self):
return "%s: %s" % (self.user, self.title)
def set_level(self, level_name: str) -> None:
"""set notification level according to level name, e.g. 'CRITICAL'
raised exception on invalid level names
"""
try:
new_level = [
item[0] for item in self.LEVEL_CHOICES if item[1] == level_name
][0]
except IndexError:
raise ValueError('Invalid level name: %s' % level_name)
def set_level(self, level):
self.level = [item[0] for item in self.LEVEL_CHOICES if item[1] == level][0]
class Meta:
ordering = ['-timestamp']
self.level = new_level
self.save()

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