Compare commits

...

143 Commits

Author SHA1 Message Date
Ariel Rin
bb15de6d1a Version Bump v2.8.7 2021-09-14 04:32:37 +00:00
Ariel Rin
0f0c0441a9 Merge branch 'fix_logging_notifications' into 'master'
Fix logging notifications

See merge request allianceauth/allianceauth!1332
2021-08-20 04:03:48 +00:00
Erik Kalkoken
a0db8e8e2c Fix logging notifications 2021-08-20 04:03:48 +00:00
Ariel Rin
a7f468efd1 Merge branch 'fix_group_creation' into 'master'
Fix Group Creation

Closes #1161

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

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

See merge request allianceauth/allianceauth!1328
2021-08-11 06:06:13 +00:00
colcrunch
2e0ddf2e7a has_leader should return true when a group has a group_leader_group 2021-07-13 18:00:21 -04:00
colcrunch
e24bc2a05d Revert "Update tests."
This reverts commit 7c1d1074f9.
2021-07-13 11:35:19 -04:00
colcrunch
a8c0db3fd7 Revert "Update autogroups."
This reverts commit eaa1cde01a.
2021-07-13 11:35:06 -04:00
colcrunch
7b77a6cd40 Revert "Add authutil for creating groups with authgroups."
This reverts commit 15db817382.
2021-07-13 11:34:55 -04:00
colcrunch
b8b8e470f2 Revert "Update tests to use the create_group util."
This reverts commit 0897383e41.
2021-07-13 11:34:46 -04:00
colcrunch
ad92ea243d Revert "Fix missed test."
This reverts commit 37005b1c68.
2021-07-13 11:34:35 -04:00
colcrunch
489a8456f7 Revert "More test fixes."
This reverts commit 6c3650d9f2.
2021-07-13 11:34:19 -04:00
colcrunch
122e389c38 Add signals back. 2021-07-13 11:33:51 -04:00
colcrunch
8318add6d5 Update test_admin.py 2021-07-13 10:18:39 -04:00
colcrunch
6c3650d9f2 More test fixes. 2021-07-13 09:28:31 -04:00
colcrunch
37005b1c68 Fix missed test. 2021-07-13 09:18:53 -04:00
colcrunch
0897383e41 Update tests to use the create_group util. 2021-07-13 09:13:47 -04:00
colcrunch
15db817382 Add authutil for creating groups with authgroups. 2021-07-13 09:13:17 -04:00
colcrunch
eaa1cde01a Update autogroups. 2021-07-13 08:41:36 -04:00
colcrunch
7c1d1074f9 Update tests. 2021-07-13 08:41:25 -04:00
colcrunch
0f0f9b6062 Fix group creation ignoring AuthGroup settings. (Fixes #1161) 2021-07-13 08:09:40 -04:00
Ariel Rin
6f2807cba2 Version Bump v2.8.6 2021-07-02 16:47:06 +00:00
Ariel Rin
39a40a8c43 Limit Django-Celery-Beat to 2.2.0 for Celery 4.x 2021-07-02 16:42:07 +00:00
Ariel Rin
5f98b5350e Version Bump v2.8.5 2021-06-29 03:27:30 +00:00
Ariel Rin
9de4d557e3 Merge branch 'discord-existing' into 'master'
Update member when they are already in Discord server.

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

See merge request allianceauth/allianceauth!1323
2021-06-29 03:10:01 +00:00
Peter Pfeufer
47793e6198 [FIX] icons in alert messages
This way they don't interfere with possible HTML in the message itself.
2021-06-28 21:20:59 +02:00
Ariel Rin
5fcb56a087 Merge branch 'notification_with_icons' into 'master'
Improve messages

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

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

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

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

See merge request allianceauth/allianceauth!1317
2021-06-01 11:35:20 +00:00
Peter Pfeufer
3df6643513 this is a shell command ... 2021-05-17 17:41:28 +02:00
Ariel Rin
6ea0ebc9f9 Merge branch 'db-install-docs-update' into 'master'
Added a quick hint that it's ok to leave the SQL shell, and some commas

See merge request allianceauth/allianceauth!1315
2021-05-16 02:38:43 +00:00
Peter Pfeufer
26236f5886 Added a quick hint that it's ok to leave the SQL shell, and some commas 2021-05-15 15:27:50 +02:00
ErikKalkoken
1420c71ec5 Improve get_character_by_id() 2021-05-15 13:58:23 +02:00
Ariel Rin
5a2c9243c4 Version Bump v2.8.4 2021-05-09 05:52:20 +00:00
Ariel Rin
fecd748198 Merge branch 'mumble-version' into 'master'
Model updates for Mumble Authenticator 1.1

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

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

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

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

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

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

See merge request allianceauth/allianceauth!1309
2021-05-05 09:04:43 +00:00
Erik Kalkoken
937d656227 Remove redis ping and consolidate before steps 2021-05-05 09:04:43 +00:00
Ariel Rin
75a3adb2c9 Merge branch 'cron-fix' into 'master'
[Docs] Fix Discord Task Schedule

See merge request allianceauth/allianceauth!1304
2021-04-27 13:53:37 +00:00
colcrunch
148e208476 Update cron schedule for discord.update_all_usernames task in docs. 2021-04-18 22:14:52 -04:00
Ariel Rin
0036e8b280 Version Bump v2.8.3 2021-04-07 15:18:08 +00:00
Ariel Rin
ea2b5bfecf Merge branch 'srp-killmail-link-fix-issue-1282' into 'master'
Proper error message on non kill mail zkb links

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

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

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

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

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

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

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

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

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

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

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

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

Closes #1262

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

Closes #1253

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

Closes #1264

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

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

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

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

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

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

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

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

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

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

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

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

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

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

See merge request allianceauth/allianceauth!1262
2020-09-29 05:10:30 +00:00
Peter Pfeufer
b47cd197ce Fixing FA icon 2020-09-28 21:07:08 +00:00
125 changed files with 8040 additions and 1890 deletions

View File

@@ -1,53 +1,93 @@
stages: stages:
- gitlab
- test - test
- deploy - deploy
include:
- template: Dependency-Scanning.gitlab-ci.yml
- template: Security/SAST.gitlab-ci.yml
before_script: before_script:
- apt-get update && apt-get install redis-server -y - apt-get update && apt-get install redis-server -y
- redis-server --daemonize yes - redis-server --daemonize yes
- redis-cli ping - python -V
- python -V - pip install wheel tox
- pip install wheel tox
sast:
stage: gitlab
before_script: []
dependency_scanning:
stage: gitlab
before_script:
- apt-get update && apt-get install redis-server libmariadbclient-dev -y
- redis-server --daemonize yes
- python -V
- pip install wheel tox
test-3.6-core: test-3.6-core:
image: python:3.6-buster image: python:3.6-buster
script: script:
- tox -e py36-core - tox -e py36-core
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.7-core: test-3.7-core:
image: python:3.7-buster image: python:3.7-buster
script: script:
- tox -e py37-core - tox -e py37-core
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.8-core: test-3.8-core:
image: python:3.8-buster image: python:3.8-buster
script: script:
- tox -e py38-core - tox -e py38-core
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.6-all: test-3.6-all:
image: python:3.6-buster image: python:3.6-buster
script: script:
- tox -e py36-all - tox -e py36-all
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.7-all: test-3.7-all:
image: python:3.7-buster image: python:3.7-buster
script: script:
- tox -e py37-all - tox -e py37-all
artifacts:
when: always
reports:
cobertura: coverage.xml
test-3.8-all: test-3.8-all:
image: python:3.8-buster image: python:3.8-buster
script: script:
- tox -e py38-all - tox -e py38-all
artifacts:
when: always
reports:
cobertura: coverage.xml
deploy_production: deploy_production:
stage: deploy stage: deploy
image: python:3.8-buster image: python:3.8-buster
before_script: before_script:
- pip install twine - pip install twine wheel
script: script:
- python setup.py sdist - python setup.py sdist bdist_wheel
- twine upload dist/* - twine upload dist/*
rules: rules:

View File

@@ -18,10 +18,6 @@ formats: all
# Optionally set the version of Python and requirements required to build your docs # Optionally set the version of Python and requirements required to build your docs
python: python:
version: 3.7 version: 3.7
install: install:
- method: pip - requirements: docs/requirements.txt
path: .
extra_requirements:
- testing
system_packages: true

View File

@@ -1,7 +1,7 @@
# This will make sure the app is always imported when # This will make sure the app is always imported when
# Django starts so that shared_task will use this app. # Django starts so that shared_task will use this app.
__version__ = '2.8.0a2' __version__ = '2.8.7'
__title__ = 'Alliance Auth' __title__ = 'Alliance Auth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth' __url__ = 'https://gitlab.com/allianceauth/allianceauth'
NAME = '%s v%s' % (__title__, __version__) NAME = '%s v%s' % (__title__, __version__)

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,8 +6,9 @@ from django.contrib.auth import login, authenticate
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core import signing from django.core import signing
from django.urls import reverse, reverse_lazy from django.http import JsonResponse
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from allianceauth.eveonline.models import EveCharacter from allianceauth.eveonline.models import EveCharacter
@@ -139,6 +140,11 @@ class RegistrationView(BaseRegistrationView):
email_subject_template = "registration/activation_email_subject.txt" email_subject_template = "registration/activation_email_subject.txt"
success_url = reverse_lazy('registration_complete') success_url = reverse_lazy('registration_complete')
def get_success_url(self, user):
if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
return reverse_lazy('authentication:dashboard')
return super().get_success_url(user)
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
# We're storing a key in the session to pass user information from OAuth response. Make sure it's there. # We're storing a key in the session to pass user information from OAuth response. Make sure it's there.
if not self.request.session.get('registration_uid', None) or not User.objects.filter( if not self.request.session.get('registration_uid', None) or not User.objects.filter(
@@ -208,5 +214,5 @@ def activation_complete(request):
def registration_closed(request): def registration_closed(request):
messages.error(request, _('Registraion of new accounts it not allowed at this time.')) messages.error(request, _('Registration of new accounts is not allowed at this time.'))
return redirect('authentication:login') return redirect('authentication:login')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,15 +9,20 @@ from .managers import GroupManager
class GroupManagementMenuItem(MenuItemHook): class GroupManagementMenuItem(MenuItemHook):
""" This class ensures only authorized users will see the menu entry """ """ This class ensures only authorized users will see the menu entry """
def __init__(self): def __init__(self):
# setup menu entry for sidebar # setup menu entry for sidebar
MenuItemHook.__init__( MenuItemHook.__init__(
self, self,
text=_('Group Management'), text=_("Group Management"),
classes='fas fa-users-cog fa-fw', classes="fas fa-users-cog fa-fw",
url_name='groupmanagement:management', url_name="groupmanagement:management",
order=50, order=50,
navactive=['groupmanagement:management'] navactive=[
"groupmanagement:management", # group requests view
"groupmanagement:membership", # group membership view
"groupmanagement:audit_log", # group audit log view
],
) )
def render(self, request): def render(self, request):
@@ -25,14 +30,14 @@ class GroupManagementMenuItem(MenuItemHook):
app_count = GroupManager.pending_requests_count_for_user(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 self.count = app_count if app_count and app_count > 0 else None
return MenuItemHook.render(self, request) return MenuItemHook.render(self, request)
return '' return ""
@hooks.register('menu_item_hook') @hooks.register("menu_item_hook")
def register_menu(): def register_menu():
return GroupManagementMenuItem() return GroupManagementMenuItem()
@hooks.register('url_hook') @hooks.register("url_hook")
def register_urls(): def register_urls():
return UrlHook(urls, 'group', r'^group/') return UrlHook(urls, "group", r"^groups/")

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

@@ -55,7 +55,6 @@ class RequestLog(models.Model):
return user.profile.main_character return user.profile.main_character
class AuthGroup(models.Model): class AuthGroup(models.Model):
""" """
Extends Django Group model with a one-to-one field Extends Django Group model with a one-to-one field
@@ -107,7 +106,8 @@ class AuthGroup(models.Model):
help_text="States listed here will have the ability to join this group provided " help_text="States listed here will have the ability to join this group provided "
"they have the proper permissions.") "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): def __str__(self):
return self.group.name 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 import logging
from django.dispatch import receiver
from allianceauth.authentication.signals import state_changed
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@receiver(state_changed) @receiver(state_changed)
def check_groups_on_state_change(sender, user, state, **kwargs): def check_groups_on_state_change(sender, user, state, **kwargs):
logger.debug("Updating auth groups for {}".format(user)) logger.debug(
visible_groups = GroupManager.get_joinable_groups(state) "Checking group memberships for %s based on new state %s" % (user, state)
visible_groups = visible_groups | Group.objects.select_related('authgroup').filter(authgroup__internal=True) )
groups = user.groups.all() state_groups = (
for g in groups: user.groups.select_related("authgroup").exclude(authgroup__states=None)
if g not in visible_groups: )
user.groups.remove(g) 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

@@ -8,48 +8,55 @@
<div class="col-lg-12"> <div class="col-lg-12">
<br> <br>
{% include 'groupmanagement/menu.html' %} {% include 'groupmanagement/menu.html' %}
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
{{ group }} - {% trans 'Audit Log' %} {{ group }} - {% trans "Audit Log" %}
</div> </div>
<div class="panel-body"> <div class="panel-body">
<p> <p>
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button"> <a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
{% trans "Back" %} {% trans "Back" %}
</a> </a>
</p> </p>
{% if entries %} {% if entries %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped" id="log-entries"> <table class="table table-striped" id="log-entries">
<thead> <thead>
<th class="text-center" scope="col">{% trans "Date/Time" %}</th> <th scope="col">{% trans "Date/Time" %}</th>
<th class="text-center" scope="col">{% trans "Requestor" %}</th> <th scope="col">{% trans "Requestor" %}</th>
<th class="text-center" scope="col">{% trans "Character" %}</th> <th scope="col">{% trans "Character" %}</th>
<th class="text-center" scope="col">{% trans "Corporation" %}</th> <th scope="col">{% trans "Corporation" %}</th>
<th class="text-center" scope="col">{% trans "Type" %}</th> <th scope="col">{% trans "Type" %}</th>
<th class="text-center" scope="col">{% trans "Action" %}</th> <th scope="col">{% trans "Action" %}</th>
<th class="text-center" scope="col">{% trans "Actor" %}</th> <th scope="col">{% trans "Actor" %}</th>
</thead> </thead>
<tbody> <tbody>
{% for entry in entries %} {% for entry in entries %}
<tr> <tr>
<td class="text-center">{{ entry.date|date:"Y-M-d, H:i" }}</td> <td>{{ entry.date|date:"Y-M-d, H:i" }}</td>
<td class="text-center">{{ entry.requestor }}</td> <td>{{ entry.requestor }}</td>
<td class="text-center">{{ entry.req_char }}</td> <td>{{ entry.req_char }}</td>
<td class="text-center">{{ entry.req_char.corporation_name }}</td> <td>{{ entry.req_char.corporation_name }}</td>
<td class="text-center">{{ entry.type_to_str }}</td> <td>{{ entry.type_to_str }}</td>
{% if entry.request_type is None %} {% if entry.request_type is None %}
<td class="text-center"> Removed</td> <td>{% trans "Removed" %}</td>
{% else %} {% else %}
<td class="text-center">{{ entry.action_to_str }}</td> <td>{{ entry.action_to_str }}</td>
{% endif %} {% endif %}
<td class="text-center">{{ entry.request_actor }}</td>
<td>{{ entry.request_actor }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<p class="text-muted"> <p class="text-muted">
All times displayed are EVE/UTC. {% trans "All times displayed are EVE/UTC." %}
</p> </p>
</div> </div>
{% else %} {% else %}
@@ -75,28 +82,27 @@
{% endblock %} {% endblock %}
{% block extra_script %} {% block extra_script %}
$.fn.dataTable.moment = function(format, locale) {
$.fn.dataTable.moment = function ( format, locale ) {
var types = $.fn.dataTable.ext.type; var types = $.fn.dataTable.ext.type;
// Add type detection // Add type detection
types.detect.unshift( function ( d ) { types.detect.unshift(function(d) {
return moment( d, format, locale, true ).isValid() ? return moment(d, format, locale, true).isValid() ?
'moment-'+format : 'moment-'+format :
null; null;
} ); });
// Add sorting method - use an integer for the sorting // Add sorting method - use an integer for the sorting
types.order[ 'moment-'+format+'-pre' ] = function ( d ) { types.order[ 'moment-'+format+'-pre' ] = function(d) {
return moment( d, format, locale, true ).unix(); return moment(d, format, locale, true).unix();
}; };
}; };
$(document).ready(function(){ $(document).ready(function(){
$.fn.dataTable.moment( 'YYYY-MMM-D, HH:mm' ); $.fn.dataTable.moment('YYYY-MMM-D, HH:mm');
$('#log-entries').DataTable({ $('#log-entries').DataTable({
order: [[ 0, 'desc' ], [ 1, 'asc' ] ], order: [[0, 'desc'], [1, 'asc']],
filterDropDown: filterDropDown:
{ {
columns: [ columns: [
@@ -124,4 +130,3 @@
}); });
}); });
{% endblock %} {% endblock %}

View File

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

View File

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

View File

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

View File

@@ -4,14 +4,14 @@
{% load evelinks %} {% load evelinks %}
{% block page_title %}{% trans "Groups Management" %}{% endblock page_title %} {% 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 %} {% endblock extra_css %}
{% block content %} {% block content %}
@@ -23,6 +23,7 @@
<li class="active"> <li class="active">
<a data-toggle="tab" href="#add"> <a data-toggle="tab" href="#add">
{% trans "Join Requests" %} {% trans "Join Requests" %}
{% if acceptrequests %} {% if acceptrequests %}
<span class="badge">{{ acceptrequests|length }}</span> <span class="badge">{{ acceptrequests|length }}</span>
{% endif %} {% endif %}
@@ -31,6 +32,7 @@
<li> <li>
<a data-toggle="tab" href="#leave"> <a data-toggle="tab" href="#leave">
{% trans "Leave Requests" %} {% trans "Leave Requests" %}
{% if leaverequests %} {% if leaverequests %}
<span class="badge">{{ leaverequests|length }}</span> <span class="badge">{{ leaverequests|length }}</span>
{% endif %} {% endif %}
@@ -38,126 +40,123 @@
</li> </li>
</ul> </ul>
<div class="tab-content"> <div class="panel panel-default panel-tabs-aa">
<div class="panel-body">
<div class="tab-content">
<div id="add" class="tab-pane fade in active panel panel-default"> <div id="add" class="tab-pane active">
<div class="panel-body"> {% if acceptrequests %}
{% if acceptrequests %} <div class="table-responsive">
<div class="table-responsive"> <table class="table table-aa">
<table class="table table-aa"> <thead>
<thead> <tr>
<tr> <th>{% trans "Character" %}</th>
<th class="text-center"></th> <th>{% trans "Organization" %}</th>
<th class="text-center">{% trans "Character" %}</th> <th>{% trans "Group" %}</th>
<th class="text-center">{% trans "Organization" %}</th> <th></th>
<th class="text-center">{% trans "Group" %}</th> </tr>
<th class="text-center"></th> </thead>
</tr>
</thead> <tbody>
<tbody> {% for acceptrequest in acceptrequests %}
{% for acceptrequest in acceptrequests %} <tr>
<tr> <td>
<td class="text-right"> <img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="img-circle" style="margin-right: 1rem;">
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="img-circle"> {% if acceptrequest.main_char %}
</td> <a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank">
<td class="text-center"> {{ acceptrequest.main_char.character_name }}
{% if acceptrequest.main_char %} </a>
<a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank"> {% else %}
{{ acceptrequest.main_char.character_name }} {{ acceptrequest.user.username }}
</a> {% endif %}
{% else %} </td>
{{ acceptrequest.user.username }} <td>
{% endif %} {% if acceptrequest.main_char %}
</td> <a href="{{ acceptrequest.main_char|dotlan_corporation_url }}" target="_blank">
<td class="text-center"> {{ acceptrequest.main_char.corporation_name }}
{% if acceptrequest.main_char %} </a><br>
<a href="{{ acceptrequest.main_char|dotlan_corporation_url }}" target="_blank"> {{ acceptrequest.main_char.alliance_name|default_if_none:"" }}
{{ acceptrequest.main_char.corporation_name }} {% else %}
</a><br> {% trans "(unknown)" %}
{{ acceptrequest.main_char.alliance_name|default_if_none:"" }} {% endif %}
{% else %} </td>
(unknown) <td>{{ acceptrequest.group.name }}</td>
{% endif %} <td class="text-right">
</td> <a href="{% url 'groupmanagement:accept_request' acceptrequest.id %}" class="btn btn-success">
<td class="text-center">{{ acceptrequest.group.name }}</td> {% trans "Accept" %}
<td class="text-center"> </a>
<a href="{% url 'groupmanagement:accept_request' acceptrequest.id %}" class="btn btn-success">
{% trans "Accept" %} <a href="{% url 'groupmanagement:reject_request' acceptrequest.id %}" class="btn btn-danger">
</a> {% trans "Reject" %}
<a href="{% url 'groupmanagement:reject_request' acceptrequest.id %}" class="btn btn-danger"> </a>
{% trans "Reject" %} </td>
</a> </tr>
</td> {% endfor %}
</tr> </tbody>
{% endfor %} </table>
</tbody> </div>
</table> {% else %}
<div class="alert alert-warning text-center">{% trans "No group add requests." %}</div>
{% endif %}
</div>
<div id="leave" class="tab-pane">
{% 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>
</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>
{% else %}
<div class="alert alert-warning text-center">{% trans "No group add requests." %}</div>
{% endif %}
</div> </div>
</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>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="alert alert-warning text-center">{% trans "No group leave requests." %}</div>
{% endif %}
</div>
</div>
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

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

View File

@@ -48,6 +48,7 @@ class TestGroupAdmin(TestCase):
cls.group_2 = Group.objects.create(name='Group 2') cls.group_2 = Group.objects.create(name='Group 2')
cls.group_2.authgroup.description = 'Internal Group' cls.group_2.authgroup.description = 'Internal Group'
cls.group_2.authgroup.internal = True cls.group_2.authgroup.internal = True
cls.group_2.authgroup.group_leader_groups.add(cls.group_1)
cls.group_2.authgroup.save() cls.group_2.authgroup.save()
# group 3 - has leader # group 3 - has leader
@@ -237,10 +238,14 @@ class TestGroupAdmin(TestCase):
result = self.modeladmin._member_count(obj) result = self.modeladmin._member_count(obj)
self.assertEqual(result, expected) self.assertEqual(result, expected)
def test_has_leader(self): def test_has_leader_user(self):
result = self.modeladmin.has_leader(self.group_1) result = self.modeladmin.has_leader(self.group_1)
self.assertTrue(result) self.assertTrue(result)
def test_has_leader_group(self):
result = self.modeladmin.has_leader(self.group_2)
self.assertTrue(result)
def test_properties_1(self): def test_properties_1(self):
expected = ['Default'] expected = ['Default']
result = self.modeladmin._properties(self.group_1) result = self.modeladmin._properties(self.group_1)

View File

@@ -1,61 +1,100 @@
from unittest import mock
from django.test import TestCase from django.test import TestCase
from django.contrib.auth.models import User, Group 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 allianceauth.tests.auth_utils import AuthUtils
from ..signals import check_groups_on_state_change
class TestCheckGroupsOnStateChange(TestCase):
class GroupManagementStateTestCase(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.user = AuthUtils.create_user('test') cls.user = AuthUtils.create_user('test')
AuthUtils.add_main_character( cls.character = AuthUtils.add_main_character_2(
cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST' cls.user, 'test character', 1001, corp_id=2001, corp_name='test corp 1', corp_ticker='TEST'
) )
cls.user.profile.refresh_from_db() cls.user.profile.refresh_from_db()
cls.alliance = EveAllianceInfo.objects.create( cls.corp_1 = EveCorporationInfo.objects.create(
alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2' corporation_id=2001, corporation_name='test corp 1', corporation_ticker='C1', member_count=1
) )
cls.corp = EveCorporationInfo.objects.create( cls.corp_2 = EveCorporationInfo.objects.create(
corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1 corporation_id=2002, corporation_name='test corp 2', corporation_ticker='C2', member_count=1
) )
cls.state_group = Group.objects.create(name='state_group') cls.guest_state = AuthUtils.get_guest_state()
cls.open_group = Group.objects.create(name='open_group') cls.test_state_1 = AuthUtils.create_state('test_state_1', 500)
cls.state = AuthUtils.create_state('test state', 500) cls.test_state_2 = AuthUtils.create_state('test_state_2', 600)
cls.state_group.authgroup.states.add(cls.state)
cls.state_group.authgroup.internal = False
cls.state_group.save()
def setUp(self): def setUp(self):
self.user.refresh_from_db() self.user.refresh_from_db()
self.state.refresh_from_db()
def _refresh_user(self): def _refresh_user(self):
self.user = User.objects.get(pk=self.user.pk) 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): def test_drop_state_group(self):
self.user.groups.add(self.open_group) """
self.user.groups.add(self.state_group) given user is member of: state group, normal group and auto group
self.assertEqual(self.user.profile.state.name, "Guest") 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)
self.state.member_corporations.add(self.corp) # user changes state back to guest
self._refresh_user() self.test_state_1.member_corporations.clear()
self.assertEqual(self.user.profile.state, self.state)
groups = self.user.groups.all()
self.assertIn(self.state_group, groups) #keeps group
self.assertIn(self.open_group, groups) #public group unafected
self.state.member_corporations.clear() # assert
self._refresh_user() self._refresh_user()
self.assertEqual(self.user.profile.state.name, "Guest") self.assertEqual(self.user.profile.state, self.guest_state)
groups = self.user.groups.all() groups = self.user.groups.all()
self.assertNotIn(self.state_group, groups) #looses group self.assertNotIn(state_group, groups) # looses state group
self.assertIn(self.open_group, groups) #public group unafected 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
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, self.test_state_2)
groups = self.user.groups.all()
self.assertIn(state_group, groups)

View File

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

@@ -166,7 +166,7 @@ def group_membership_remove(request, group_id, user_id):
except ObjectDoesNotExist: except ObjectDoesNotExist:
messages.warning(request, _("Group does not exist")) messages.warning(request, _("Group does not exist"))
return redirect('groupmanagement:membership_list', group_id) return redirect('groupmanagement:membership', group_id)
@login_required @login_required
@@ -356,6 +356,9 @@ def group_request_add(request, group_id):
if group.authgroup.open: if group.authgroup.open:
logger.info("%s joining %s as is an open group" % (request.user, group)) logger.info("%s joining %s as is an open group" % (request.user, group))
request.user.groups.add(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") return redirect("groupmanagement:groups")
req = GroupRequest.objects.filter(user=request.user, group=group) req = GroupRequest.objects.filter(user=request.user, group=group)
if len(req) > 0: if len(req) > 0:
@@ -389,6 +392,9 @@ def group_request_leave(request, group_id):
return redirect('groupmanagement:groups') return redirect('groupmanagement:groups')
if group.authgroup.open: if group.authgroup.open:
logger.info("%s leaving %s as is an open group" % (request.user, group)) 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) request.user.groups.remove(group)
return redirect("groupmanagement:groups") return redirect("groupmanagement:groups")
req = GroupRequest.objects.filter(user=request.user, group=group) req = GroupRequest.objects.filter(user=request.user, group=group)
@@ -398,6 +404,9 @@ def group_request_leave(request, group_id):
return redirect("groupmanagement:groups") return redirect("groupmanagement:groups")
if getattr(settings, 'AUTO_LEAVE', False): if getattr(settings, 'AUTO_LEAVE', False):
logger.info("%s leaving joinable group %s due to auto_leave" % (request.user, group)) logger.info("%s leaving joinable group %s due to auto_leave" % (request.user, group))
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) request.user.groups.remove(group)
return redirect('groupmanagement:groups') return redirect('groupmanagement:groups')
grouprequest = GroupRequest() grouprequest = GroupRequest()

View File

@@ -6,16 +6,16 @@
# Translators: # Translators:
# Erik Kalkoken <erik.kalkoken@gmail.com>, 2020 # Erik Kalkoken <erik.kalkoken@gmail.com>, 2020
# Joel Falknau <ozirascal@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 #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-21 01:35+0000\n" "POT-Creation-Date: 2020-11-20 05:33+0000\n"
"PO-Revision-Date: 2020-02-18 03:14+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" "Language-Team: German (https://www.transifex.com/alliance-auth/teams/107430/de/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@@ -28,7 +28,7 @@ msgid "A main character is required to perform that action. Add one below."
msgstr "" msgstr ""
"Für diese Aktion wird ein Hauptcharacter benötigt. Bitte füge einen hinzu." "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" msgid "Email"
msgstr "E-Mail" msgstr "E-Mail"
@@ -82,7 +82,7 @@ msgstr "Charaktere"
#: allianceauth/authentication/templates/authentication/dashboard.html:129 #: allianceauth/authentication/templates/authentication/dashboard.html:129
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73 #: 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/groupmanagement/templates/groupmanagement/groups.html:15
#: allianceauth/hrapplications/templates/hrapplications/view.html:45 #: allianceauth/hrapplications/templates/hrapplications/view.html:45
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34 #: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34
@@ -168,11 +168,11 @@ msgstr ""
msgid "Unable to authenticate as the selected character." msgid "Unable to authenticate as the selected character."
msgstr "Authentifizierung mit dem ausgewählten Charakter nicht möglich." 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." msgid "Registration token has expired."
msgstr "Token zur Registrierung ist abgelaufen." msgstr "Token zur Registrierung ist abgelaufen."
#: allianceauth/authentication/views.py:200 #: allianceauth/authentication/views.py:206
msgid "" msgid ""
"Sent confirmation email. Please follow the link to confirm your email " "Sent confirmation email. Please follow the link to confirm your email "
"address." "address."
@@ -180,13 +180,13 @@ msgstr ""
"Bestätigungsmail gesendet. Bitte folge dem Link in der E-Mail zur " "Bestätigungsmail gesendet. Bitte folge dem Link in der E-Mail zur "
"Bestätigung." "Bestätigung."
#: allianceauth/authentication/views.py:205 #: allianceauth/authentication/views.py:211
msgid "Confirmed your email address. Please login to continue." msgid "Confirmed your email address. Please login to continue."
msgstr "" msgstr ""
"Deine E-Mail Adresse wurde bestätigt. Bitte log Dich ein um fortzufahren." "Deine E-Mail Adresse wurde bestätigt. Bitte log Dich ein um fortzufahren."
#: allianceauth/authentication/views.py:210 #: allianceauth/authentication/views.py:216
msgid "Registraion of new accounts it not allowed at this time." msgid "Registration of new accounts is not allowed at this time."
msgstr "Registrierung von neuen Konten ist zur Zeit nicht erlaubt." msgstr "Registrierung von neuen Konten ist zur Zeit nicht erlaubt."
#: allianceauth/corputils/auth_hooks.py:10 #: allianceauth/corputils/auth_hooks.py:10
@@ -234,16 +234,16 @@ msgstr "Letzes Update:"
#: allianceauth/corputils/templates/corputils/search.html:13 #: allianceauth/corputils/templates/corputils/search.html:13
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:51 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:51
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:110 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:109
msgid "Character" msgid "Character"
msgstr "Charakter" msgstr "Charakter"
#: allianceauth/corputils/templates/corputils/corpstats.html:76 #: allianceauth/corputils/templates/corputils/corpstats.html:76
#: allianceauth/corputils/templates/corputils/search.html:14 #: 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:27
#: allianceauth/hrapplications/templates/hrapplications/management.html:84 #: allianceauth/hrapplications/templates/hrapplications/management.html:84
#: allianceauth/hrapplications/templates/hrapplications/management.html:129 #: allianceauth/hrapplications/templates/hrapplications/management.html:129
@@ -552,36 +552,35 @@ msgstr "Flottenteilnahme registriert."
msgid "FAT link has expired." msgid "FAT link has expired."
msgstr "FAT-Link ist abgelaufen." msgstr "FAT-Link ist abgelaufen."
#: allianceauth/groupmanagement/auth_hooks.py:16 #: allianceauth/groupmanagement/auth_hooks.py:17
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:14
msgid "Group Management" msgid "Group Management"
msgstr "Gruppenverwaltung" msgstr "Gruppenverwaltung"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:13 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:14
msgid "Audit Log" msgid "Audit Log"
msgstr "Protokoll" msgstr "Protokoll"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:18 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:20
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:20 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:21
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
msgid "Back" msgid "Back"
msgstr "Zurück" msgstr "Zurück"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:28
msgid "Date/Time" msgid "Date/Time"
msgstr "Datum/Uhrzeit" msgstr "Datum/Uhrzeit"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:26 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
msgid "Requestor" msgid "Requestor"
msgstr "Antragsteller" msgstr "Antragsteller"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:32
msgid "Type" msgid "Type"
msgstr "Typ" msgstr "Typ"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:33
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:17
#: allianceauth/notifications/templates/notifications/list.html:37 #: allianceauth/notifications/templates/notifications/list.html:37
#: allianceauth/notifications/templates/notifications/list.html:69 #: allianceauth/notifications/templates/notifications/list.html:69
#: allianceauth/optimer/templates/optimer/fleetoptable.html:18 #: allianceauth/optimer/templates/optimer/fleetoptable.html:18
@@ -592,11 +591,19 @@ msgstr "Typ"
msgid "Action" msgid "Action"
msgstr "Aktion" msgstr "Aktion"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:34
msgid "Actor" msgid "Actor"
msgstr "Ausführender" msgstr "Ausführender"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:47
msgid "Removed"
msgstr "Entfernt"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:59 #: 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." msgid "No entries found for this group."
msgstr "Keine Einträge für diese Gruppe gefunden." msgstr "Keine Einträge für diese Gruppe gefunden."
@@ -604,22 +611,24 @@ msgstr "Keine Einträge für diese Gruppe gefunden."
msgid "Group Members" msgid "Group Members"
msgstr "Gruppenmitglieder" msgstr "Gruppenmitglieder"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:28 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:31
msgid "Portrait"
msgstr "Portrait"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:52 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:52
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:111 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:110
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
msgid "Organization" msgid "Organization"
msgstr "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" msgid "Remove from group"
msgstr "Aus Gruppe entfernen" msgstr "Aus Gruppe entfernen"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:76 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:81
msgid "No group members to list." msgid "No group members to list."
msgstr "Keine Gruppenmitglieder vorhanden." msgstr "Keine Gruppenmitglieder vorhanden."
@@ -627,18 +636,18 @@ msgstr "Keine Gruppenmitglieder vorhanden."
msgid "Groups Membership" msgid "Groups Membership"
msgstr "Gruppenmitgliedschaft" 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/permissions_tool/templates/permissions_tool/overview.html:40
#: allianceauth/templates/allianceauth/side-menu.html:16 #: allianceauth/templates/allianceauth/side-menu.html:16
msgid "Groups" msgid "Groups"
msgstr "Gruppen" msgstr "Gruppen"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
msgid "Description" msgid "Description"
msgstr "Beschreibung" 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:28
#: allianceauth/hrapplications/templates/hrapplications/management.html:85 #: allianceauth/hrapplications/templates/hrapplications/management.html:85
#: allianceauth/hrapplications/templates/hrapplications/management.html:130 #: allianceauth/hrapplications/templates/hrapplications/management.html:130
@@ -647,36 +656,36 @@ msgstr "Beschreibung"
msgid "Status" msgid "Status"
msgstr "Status" msgstr "Status"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:27
msgid "Member Count" msgid "Member Count"
msgstr "Mitgliederzahl" msgstr "Mitgliederzahl"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:38 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:43
msgid "Hidden" msgid "Hidden"
msgstr "Verborgen" msgstr "Verborgen"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:40 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
#: allianceauth/templates/allianceauth/admin-status/overview.html:12 #: allianceauth/templates/allianceauth/admin-status/overview.html:12
msgid "Open" msgid "Open"
msgstr "Öffnen" msgstr "Öffnen"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:42 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:47
msgid "Requestable" msgid "Requestable"
msgstr "Anfragbar" msgstr "Anfragbar"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:50 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
msgid "View Members" msgid "View Members"
msgstr "Mitglieder ansehen" msgstr "Mitglieder ansehen"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:53 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:60
msgid "Audit Members" msgid "Audit Members"
msgstr "Mitglieder Protokoll" msgstr "Mitglieder Protokoll"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
msgid "Copy Direct Join Link" msgid "Copy Direct Join Link"
msgstr "Direktlink kopieren" msgstr "Direktlink kopieren"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:75
msgid "No groups to list." msgid "No groups to list."
msgstr "Keine Gruppen vorhanden." msgstr "Keine Gruppen vorhanden."
@@ -685,19 +694,19 @@ msgstr "Keine Gruppen vorhanden."
msgid "Available Groups" msgid "Available Groups"
msgstr "Verfügbare Gruppen" msgstr "Verfügbare Gruppen"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:30
msgid "Leave" msgid "Leave"
msgstr "Verlassen" msgstr "Verlassen"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:39 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:40
msgid "Join" msgid "Join"
msgstr "Beitreten" msgstr "Beitreten"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:43 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:44
msgid "Request" msgid "Request"
msgstr "Anfrage" msgstr "Anfrage"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:58 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:59
msgid "No groups available." msgid "No groups available."
msgstr "Keine Gruppen verfügbar" msgstr "Keine Gruppen verfügbar"
@@ -709,24 +718,24 @@ msgstr "Gruppenverwaltung"
msgid "Join Requests" msgid "Join Requests"
msgstr "Beitrittsgesuche" msgstr "Beitrittsgesuche"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:33 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:34
msgid "Leave Requests" msgid "Leave Requests"
msgstr "Austrittsgesuche" msgstr "Austrittsgesuche"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:53 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:53
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:112 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:111
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
#: allianceauth/services/modules/openfire/forms.py:6 #: allianceauth/services/modules/openfire/forms.py:6
msgid "Group" msgid "Group"
msgstr "Gruppen" msgstr "Gruppen"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:85 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:84
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:144 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
msgid "Accept" msgid "Accept"
msgstr "Akzeptieren" msgstr "Akzeptieren"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:147 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:146
#: allianceauth/hrapplications/templates/hrapplications/view.html:85 #: allianceauth/hrapplications/templates/hrapplications/view.html:85
msgid "Reject" msgid "Reject"
msgstr "Ablehnen" msgstr "Ablehnen"
@@ -735,19 +744,19 @@ msgstr "Ablehnen"
msgid "No group add requests." msgid "No group add requests."
msgstr "Keine Gruppenbeitrittsanfragen." msgstr "Keine Gruppenbeitrittsanfragen."
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:156 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:155
msgid "No group leave requests." msgid "No group leave requests."
msgstr "Keine Gruppenaustrittsanfragen" msgstr "Keine Gruppenaustrittsanfragen"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:10 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:9
msgid "Toggle navigation" msgid "Toggle navigation"
msgstr "Toggle Navigation" msgstr "Toggle Navigation"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:21 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:20
msgid "Group Requests" msgid "Group Requests"
msgstr "Gruppenanfragen" msgstr "Gruppenanfragen"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:24 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:23
msgid "Group Membership" msgid "Group Membership"
msgstr "Gruppenmitgliedschaft" msgstr "Gruppenmitgliedschaft"
@@ -825,7 +834,7 @@ msgstr "Du hast Dich bereits für diese Gruppe beworben."
#: allianceauth/hrapplications/templates/hrapplications/management.html:144 #: allianceauth/hrapplications/templates/hrapplications/management.html:144
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:38 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:38
#: allianceauth/hrapplications/templates/hrapplications/view.html:20 #: allianceauth/hrapplications/templates/hrapplications/view.html:20
#: allianceauth/srp/templates/srp/data.html:128 #: allianceauth/srp/templates/srp/data.html:134
#: allianceauth/srp/templates/srp/management.html:81 #: allianceauth/srp/templates/srp/management.html:81
msgid "Pending" msgid "Pending"
msgstr "Beantragt" msgstr "Beantragt"
@@ -919,7 +928,7 @@ msgstr "Aktionen"
#: allianceauth/hrapplications/templates/hrapplications/management.html:147 #: allianceauth/hrapplications/templates/hrapplications/management.html:147
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:40 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:40
#: allianceauth/hrapplications/templates/hrapplications/view.html:16 #: allianceauth/hrapplications/templates/hrapplications/view.html:16
#: allianceauth/srp/templates/srp/data.html:120 #: allianceauth/srp/templates/srp/data.html:126
msgid "Approved" msgid "Approved"
msgstr "Akzeptiert" msgstr "Akzeptiert"
@@ -927,7 +936,7 @@ msgstr "Akzeptiert"
#: allianceauth/hrapplications/templates/hrapplications/management.html:104 #: allianceauth/hrapplications/templates/hrapplications/management.html:104
#: allianceauth/hrapplications/templates/hrapplications/management.html:149 #: allianceauth/hrapplications/templates/hrapplications/management.html:149
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:42 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:42
#: allianceauth/srp/templates/srp/data.html:124 #: allianceauth/srp/templates/srp/data.html:130
msgid "Rejected" msgid "Rejected"
msgstr "Abgelehnt" msgstr "Abgelehnt"
@@ -1264,7 +1273,7 @@ msgstr "Flotten Comms:"
#: allianceauth/services/forms.py:9 #: allianceauth/services/forms.py:9
msgid "Fleet Type:" msgid "Fleet Type:"
msgstr "Flottenzeit:" msgstr "Flottenart:"
#: allianceauth/services/forms.py:10 #: allianceauth/services/forms.py:10
msgid "Ship Priorities:" msgid "Ship Priorities:"
@@ -1310,13 +1319,13 @@ msgstr "Passwort"
msgid "Password must be at least 8 characters long." msgid "Password must be at least 8 characters long."
msgstr "Passwort muss mindestens 8 Zeichen lang sein" msgstr "Passwort muss mindestens 8 Zeichen lang sein"
#: allianceauth/services/modules/discord/models.py:224 #: allianceauth/services/modules/discord/models.py:225
msgid "Discord Account Disabled" msgid "Discord Account Disabled"
msgstr "Discord Konto deaktiviert" msgstr "Discord Konto deaktiviert"
#: allianceauth/services/modules/discord/models.py:226 #: allianceauth/services/modules/discord/models.py:227
msgid "" msgid ""
"Your Discord account was disabeled automatically by Auth. If you think this " "Your Discord account was disabled automatically by Auth. If you think this "
"was a mistake, please contact an admin." "was a mistake, please contact an admin."
msgstr "" msgstr ""
"Dein Discord Konto wurde automatisch durch Auth deaktiviert. Wenn Du glaubst" "Dein Discord Konto wurde automatisch durch Auth deaktiviert. Wenn Du glaubst"
@@ -1684,18 +1693,18 @@ msgid "Mark Completed"
msgstr "Als vollständig markieren" msgstr "Als vollständig markieren"
#: allianceauth/srp/templates/srp/data.html:70 #: allianceauth/srp/templates/srp/data.html:70
#: allianceauth/srp/templates/srp/data.html:150 #: allianceauth/srp/templates/srp/data.html:156
msgid "Total Losses:" msgid "Total Losses:"
msgstr "Verluste insgesamt:" msgstr "Verluste insgesamt:"
#: allianceauth/srp/templates/srp/data.html:71 #: allianceauth/srp/templates/srp/data.html:71
#: allianceauth/srp/templates/srp/data.html:151 #: allianceauth/srp/templates/srp/data.html:157
#: allianceauth/srp/templates/srp/management.html:30 #: allianceauth/srp/templates/srp/management.html:30
msgid "Total ISK Cost:" msgid "Total ISK Cost:"
msgstr "ISK-Kosten insgesamt:" msgstr "ISK-Kosten insgesamt:"
#: allianceauth/srp/templates/srp/data.html:79 #: allianceauth/srp/templates/srp/data.html:79
#: allianceauth/srp/templates/srp/data.html:159 #: allianceauth/srp/templates/srp/data.html:165
msgid "Are you sure you want to delete SRP requests?" msgid "Are you sure you want to delete SRP requests?"
msgstr "Bist Du sicher das Du SRP Anfragen löschen willst?" msgstr "Bist Du sicher das Du SRP Anfragen löschen willst?"
@@ -1729,7 +1738,7 @@ msgstr ""
msgid "Post Time" msgid "Post Time"
msgstr "Veröffentlichungszeit" msgstr "Veröffentlichungszeit"
#: allianceauth/srp/templates/srp/data.html:168 #: allianceauth/srp/templates/srp/data.html:174
msgid "No SRP requests for this fleet." msgid "No SRP requests for this fleet."
msgstr "Keine SRP Anfragen für diese Flotte." msgstr "Keine SRP Anfragen für diese Flotte."
@@ -2089,3 +2098,6 @@ msgstr "Neuen Timer hinzugefügt in %(system)s um %(time)s."
#: allianceauth/timerboard/views.py:83 #: allianceauth/timerboard/views.py:83
msgid "Saved changes to the timer." msgid "Saved changes to the timer."
msgstr "Änderungen am Timer gespeichert" 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 "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-21 01:35+0000\n" "POT-Creation-Date: 2020-11-20 05:33+0000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n" "PO-Revision-Date: 2020-02-18 03:14+0000\n"
"Last-Translator: frank1210 <francolopez_16@hotmail.com>, 2020\n" "Last-Translator: frank1210 <francolopez_16@hotmail.com>, 2020\n"
"Language-Team: Spanish (https://www.transifex.com/alliance-auth/teams/107430/es/)\n" "Language-Team: Spanish (https://www.transifex.com/alliance-auth/teams/107430/es/)\n"
@@ -27,7 +27,7 @@ msgid "A main character is required to perform that action. Add one below."
msgstr "" msgstr ""
"Un personaje principal es requerido para completar esta accion. Agregue uno" "Un personaje principal es requerido para completar esta accion. Agregue uno"
#: allianceauth/authentication/forms.py:6 #: allianceauth/authentication/forms.py:5
msgid "Email" msgid "Email"
msgstr "E-mail" msgstr "E-mail"
@@ -79,7 +79,7 @@ msgstr "Personajes"
#: allianceauth/authentication/templates/authentication/dashboard.html:129 #: allianceauth/authentication/templates/authentication/dashboard.html:129
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73 #: 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/groupmanagement/templates/groupmanagement/groups.html:15
#: allianceauth/hrapplications/templates/hrapplications/view.html:45 #: allianceauth/hrapplications/templates/hrapplications/view.html:45
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34 #: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34
@@ -161,26 +161,26 @@ msgstr ""
msgid "Unable to authenticate as the selected character." msgid "Unable to authenticate as the selected character."
msgstr "Imposible validar con el personaje seleccionado." msgstr "Imposible validar con el personaje seleccionado."
#: allianceauth/authentication/views.py:148 #: allianceauth/authentication/views.py:151
msgid "Registration token has expired." msgid "Registration token has expired."
msgstr "El token de registracion expiro." msgstr "El token de registracion expiro."
#: allianceauth/authentication/views.py:200 #: allianceauth/authentication/views.py:206
msgid "" msgid ""
"Sent confirmation email. Please follow the link to confirm your email " "Sent confirmation email. Please follow the link to confirm your email "
"address." "address."
msgstr "" msgstr ""
"Confirmacion de mail enviada. Por favor siga el enlace para confirmar " "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." msgid "Confirmed your email address. Please login to continue."
msgstr "" msgstr ""
"Se ha confirmado su direccion de mail. Por favor igrese su token para " "Se ha confirmado su direccion de mail. Por favor igrese su token para "
"continuar." "continuar."
#: allianceauth/authentication/views.py:210 #: allianceauth/authentication/views.py:216
msgid "Registraion of new accounts it not allowed at this time." msgid "Registration of new accounts is not allowed at this time."
msgstr "Registracion de nuevas cuentas no es permitido por el momento." msgstr ""
#: allianceauth/corputils/auth_hooks.py:10 #: allianceauth/corputils/auth_hooks.py:10
msgid "Corporation Stats" msgid "Corporation Stats"
@@ -227,16 +227,16 @@ msgstr "Ultima Actualizacion:"
#: allianceauth/corputils/templates/corputils/search.html:13 #: allianceauth/corputils/templates/corputils/search.html:13
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:51 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:51
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:110 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:109
msgid "Character" msgid "Character"
msgstr "Personaje" msgstr "Personaje"
#: allianceauth/corputils/templates/corputils/corpstats.html:76 #: allianceauth/corputils/templates/corputils/corpstats.html:76
#: allianceauth/corputils/templates/corputils/search.html:14 #: 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:27
#: allianceauth/hrapplications/templates/hrapplications/management.html:84 #: allianceauth/hrapplications/templates/hrapplications/management.html:84
#: allianceauth/hrapplications/templates/hrapplications/management.html:129 #: allianceauth/hrapplications/templates/hrapplications/management.html:129
@@ -546,36 +546,35 @@ msgstr "Participacion de flota registrada."
msgid "FAT link has expired." msgid "FAT link has expired."
msgstr "Enlace de participacion expirado." msgstr "Enlace de participacion expirado."
#: allianceauth/groupmanagement/auth_hooks.py:16 #: allianceauth/groupmanagement/auth_hooks.py:17
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:14
msgid "Group Management" msgid "Group Management"
msgstr "Manejo de Grupo" msgstr "Manejo de Grupo"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:13 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:14
msgid "Audit Log" msgid "Audit Log"
msgstr "Log de Auditoria" msgstr "Log de Auditoria"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:18 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:20
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:20 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:21
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
msgid "Back" msgid "Back"
msgstr "Volver" msgstr "Volver"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:28
msgid "Date/Time" msgid "Date/Time"
msgstr "Fecha/Hora" msgstr "Fecha/Hora"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:26 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
msgid "Requestor" msgid "Requestor"
msgstr "Solicitante" msgstr "Solicitante"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:32
msgid "Type" msgid "Type"
msgstr "Tipo" msgstr "Tipo"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:33
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:17
#: allianceauth/notifications/templates/notifications/list.html:37 #: allianceauth/notifications/templates/notifications/list.html:37
#: allianceauth/notifications/templates/notifications/list.html:69 #: allianceauth/notifications/templates/notifications/list.html:69
#: allianceauth/optimer/templates/optimer/fleetoptable.html:18 #: allianceauth/optimer/templates/optimer/fleetoptable.html:18
@@ -586,11 +585,19 @@ msgstr "Tipo"
msgid "Action" msgid "Action"
msgstr "Accion" msgstr "Accion"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:34
msgid "Actor" msgid "Actor"
msgstr "Actor" msgstr "Actor"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:47
msgid "Removed"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:59 #: 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." msgid "No entries found for this group."
msgstr "No se encontraron entradas para este grupo." msgstr "No se encontraron entradas para este grupo."
@@ -598,22 +605,24 @@ msgstr "No se encontraron entradas para este grupo."
msgid "Group Members" msgid "Group Members"
msgstr "Miembros del Grupo" msgstr "Miembros del Grupo"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:28 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:31
msgid "Portrait"
msgstr "Retrato"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:52 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:52
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:111 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:110
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
msgid "Organization" msgid "Organization"
msgstr "" 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" msgid "Remove from group"
msgstr "Remover del grupo" msgstr "Remover del grupo"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:76 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:81
msgid "No group members to list." msgid "No group members to list."
msgstr "no hay miembros para listar." msgstr "no hay miembros para listar."
@@ -621,18 +630,18 @@ msgstr "no hay miembros para listar."
msgid "Groups Membership" msgid "Groups Membership"
msgstr "Membresia de grupos" 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/permissions_tool/templates/permissions_tool/overview.html:40
#: allianceauth/templates/allianceauth/side-menu.html:16 #: allianceauth/templates/allianceauth/side-menu.html:16
msgid "Groups" msgid "Groups"
msgstr "Grupos" msgstr "Grupos"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
msgid "Description" msgid "Description"
msgstr "Descripcion" 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:28
#: allianceauth/hrapplications/templates/hrapplications/management.html:85 #: allianceauth/hrapplications/templates/hrapplications/management.html:85
#: allianceauth/hrapplications/templates/hrapplications/management.html:130 #: allianceauth/hrapplications/templates/hrapplications/management.html:130
@@ -641,36 +650,36 @@ msgstr "Descripcion"
msgid "Status" msgid "Status"
msgstr "Estado" msgstr "Estado"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:27
msgid "Member Count" msgid "Member Count"
msgstr "Contador de miembros" msgstr "Contador de miembros"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:38 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:43
msgid "Hidden" msgid "Hidden"
msgstr "Escondido" msgstr "Escondido"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:40 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
#: allianceauth/templates/allianceauth/admin-status/overview.html:12 #: allianceauth/templates/allianceauth/admin-status/overview.html:12
msgid "Open" msgid "Open"
msgstr "Abierto" msgstr "Abierto"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:42 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:47
msgid "Requestable" msgid "Requestable"
msgstr "Solicitable" msgstr "Solicitable"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:50 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
msgid "View Members" msgid "View Members"
msgstr "Ver Miembros" msgstr "Ver Miembros"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:53 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:60
msgid "Audit Members" msgid "Audit Members"
msgstr "Auditar Miembros" msgstr "Auditar Miembros"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
msgid "Copy Direct Join Link" msgid "Copy Direct Join Link"
msgstr "" msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:75
msgid "No groups to list." msgid "No groups to list."
msgstr "No hay grupos para listar" msgstr "No hay grupos para listar"
@@ -679,19 +688,19 @@ msgstr "No hay grupos para listar"
msgid "Available Groups" msgid "Available Groups"
msgstr "Grupos Disponibles" msgstr "Grupos Disponibles"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:30
msgid "Leave" msgid "Leave"
msgstr "Dejar" msgstr "Dejar"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:39 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:40
msgid "Join" msgid "Join"
msgstr "Unirse" msgstr "Unirse"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:43 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:44
msgid "Request" msgid "Request"
msgstr "Solicitar" msgstr "Solicitar"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:58 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:59
msgid "No groups available." msgid "No groups available."
msgstr "No hay grupos disponibles" msgstr "No hay grupos disponibles"
@@ -703,24 +712,24 @@ msgstr "Manejo de Grupos"
msgid "Join Requests" msgid "Join Requests"
msgstr "" msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:33 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:34
msgid "Leave Requests" msgid "Leave Requests"
msgstr "" msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:53 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:53
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:112 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:111
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
#: allianceauth/services/modules/openfire/forms.py:6 #: allianceauth/services/modules/openfire/forms.py:6
msgid "Group" msgid "Group"
msgstr "Grupo" msgstr "Grupo"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:85 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:84
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:144 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
msgid "Accept" msgid "Accept"
msgstr "Aceptar" msgstr "Aceptar"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:147 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:146
#: allianceauth/hrapplications/templates/hrapplications/view.html:85 #: allianceauth/hrapplications/templates/hrapplications/view.html:85
msgid "Reject" msgid "Reject"
msgstr "Rechazar" msgstr "Rechazar"
@@ -729,19 +738,19 @@ msgstr "Rechazar"
msgid "No group add requests." msgid "No group add requests."
msgstr "No hay solicitudes de ingreso." msgstr "No hay solicitudes de ingreso."
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:156 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:155
msgid "No group leave requests." msgid "No group leave requests."
msgstr "No hay solicitudes paradejar el grupo." msgstr "No hay solicitudes paradejar el grupo."
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:10 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:9
msgid "Toggle navigation" msgid "Toggle navigation"
msgstr "Navegacion" msgstr "Navegacion"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:21 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:20
msgid "Group Requests" msgid "Group Requests"
msgstr "Solicitudes de Grupo" msgstr "Solicitudes de Grupo"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:24 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:23
msgid "Group Membership" msgid "Group Membership"
msgstr "Membresia de Grupo" msgstr "Membresia de Grupo"
@@ -818,7 +827,7 @@ msgstr ""
#: allianceauth/hrapplications/templates/hrapplications/management.html:144 #: allianceauth/hrapplications/templates/hrapplications/management.html:144
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:38 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:38
#: allianceauth/hrapplications/templates/hrapplications/view.html:20 #: allianceauth/hrapplications/templates/hrapplications/view.html:20
#: allianceauth/srp/templates/srp/data.html:128 #: allianceauth/srp/templates/srp/data.html:134
#: allianceauth/srp/templates/srp/management.html:81 #: allianceauth/srp/templates/srp/management.html:81
msgid "Pending" msgid "Pending"
msgstr "Pendiente" msgstr "Pendiente"
@@ -912,7 +921,7 @@ msgstr "Acciones"
#: allianceauth/hrapplications/templates/hrapplications/management.html:147 #: allianceauth/hrapplications/templates/hrapplications/management.html:147
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:40 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:40
#: allianceauth/hrapplications/templates/hrapplications/view.html:16 #: allianceauth/hrapplications/templates/hrapplications/view.html:16
#: allianceauth/srp/templates/srp/data.html:120 #: allianceauth/srp/templates/srp/data.html:126
msgid "Approved" msgid "Approved"
msgstr "Aprovado" msgstr "Aprovado"
@@ -920,7 +929,7 @@ msgstr "Aprovado"
#: allianceauth/hrapplications/templates/hrapplications/management.html:104 #: allianceauth/hrapplications/templates/hrapplications/management.html:104
#: allianceauth/hrapplications/templates/hrapplications/management.html:149 #: allianceauth/hrapplications/templates/hrapplications/management.html:149
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:42 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:42
#: allianceauth/srp/templates/srp/data.html:124 #: allianceauth/srp/templates/srp/data.html:130
msgid "Rejected" msgid "Rejected"
msgstr "Rechazado" msgstr "Rechazado"
@@ -1303,13 +1312,13 @@ msgstr "Contraseña"
msgid "Password must be at least 8 characters long." msgid "Password must be at least 8 characters long."
msgstr "La contraseña tiene que tener 8 caracteres de largo minimo" msgstr "La contraseña tiene que tener 8 caracteres de largo minimo"
#: allianceauth/services/modules/discord/models.py:224 #: allianceauth/services/modules/discord/models.py:225
msgid "Discord Account Disabled" msgid "Discord Account Disabled"
msgstr "" msgstr ""
#: allianceauth/services/modules/discord/models.py:226 #: allianceauth/services/modules/discord/models.py:227
msgid "" msgid ""
"Your Discord account was disabeled automatically by Auth. If you think this " "Your Discord account was disabled automatically by Auth. If you think this "
"was a mistake, please contact an admin." "was a mistake, please contact an admin."
msgstr "" msgstr ""
@@ -1666,18 +1675,18 @@ msgid "Mark Completed"
msgstr "Marcar como Completo" msgstr "Marcar como Completo"
#: allianceauth/srp/templates/srp/data.html:70 #: allianceauth/srp/templates/srp/data.html:70
#: allianceauth/srp/templates/srp/data.html:150 #: allianceauth/srp/templates/srp/data.html:156
msgid "Total Losses:" msgid "Total Losses:"
msgstr "Perdidas Totales:" msgstr "Perdidas Totales:"
#: allianceauth/srp/templates/srp/data.html:71 #: allianceauth/srp/templates/srp/data.html:71
#: allianceauth/srp/templates/srp/data.html:151 #: allianceauth/srp/templates/srp/data.html:157
#: allianceauth/srp/templates/srp/management.html:30 #: allianceauth/srp/templates/srp/management.html:30
msgid "Total ISK Cost:" msgid "Total ISK Cost:"
msgstr "Costo Total:" msgstr "Costo Total:"
#: allianceauth/srp/templates/srp/data.html:79 #: allianceauth/srp/templates/srp/data.html:79
#: allianceauth/srp/templates/srp/data.html:159 #: allianceauth/srp/templates/srp/data.html:165
msgid "Are you sure you want to delete SRP requests?" msgid "Are you sure you want to delete SRP requests?"
msgstr "Estas seguro que quiere borrar las solicitudes de SRP" msgstr "Estas seguro que quiere borrar las solicitudes de SRP"
@@ -1709,7 +1718,7 @@ msgstr ""
msgid "Post Time" msgid "Post Time"
msgstr "Tiempo" msgstr "Tiempo"
#: allianceauth/srp/templates/srp/data.html:168 #: allianceauth/srp/templates/srp/data.html:174
msgid "No SRP requests for this fleet." msgid "No SRP requests for this fleet."
msgstr "No hay solicitudes de SRP para esta flota." msgstr "No hay solicitudes de SRP para esta flota."
@@ -2065,3 +2074,6 @@ msgstr "Se agrego un nuevo timer en %(system)s a las %(time)s."
#: allianceauth/timerboard/views.py:83 #: allianceauth/timerboard/views.py:83
msgid "Saved changes to the timer." msgid "Saved changes to the timer."
msgstr "Se guardaron los cambios en el 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

@@ -8,15 +8,16 @@
# Seowon Jung <seowon@hawaii.edu>, 2020 # Seowon Jung <seowon@hawaii.edu>, 2020
# Olgeda Choi <undead.choi@gmail.com>, 2020 # Olgeda Choi <undead.choi@gmail.com>, 2020
# Lahty <js03js70@gmail.com>, 2020 # Lahty <js03js70@gmail.com>, 2020
# Joel Falknau <ozirascal@gmail.com>, 2020
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-21 01:35+0000\n" "POT-Creation-Date: 2020-11-20 05:33+0000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n" "PO-Revision-Date: 2020-02-18 03:14+0000\n"
"Last-Translator: Lahty <js03js70@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" "Language-Team: Korean (Korea) (https://www.transifex.com/alliance-auth/teams/107430/ko_KR/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@@ -28,7 +29,7 @@ msgstr ""
msgid "A main character is required to perform that action. Add one below." msgid "A main character is required to perform that action. Add one below."
msgstr "해당 기능을 수행하려면 주 캐릭터가 요구됨. 아래에 하나를 추가하시오." msgstr "해당 기능을 수행하려면 주 캐릭터가 요구됨. 아래에 하나를 추가하시오."
#: allianceauth/authentication/forms.py:6 #: allianceauth/authentication/forms.py:5
msgid "Email" msgid "Email"
msgstr "이메일" msgstr "이메일"
@@ -83,7 +84,7 @@ msgstr "캐릭터"
#: allianceauth/authentication/templates/authentication/dashboard.html:129 #: allianceauth/authentication/templates/authentication/dashboard.html:129
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73 #: 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/groupmanagement/templates/groupmanagement/groups.html:15
#: allianceauth/hrapplications/templates/hrapplications/view.html:45 #: allianceauth/hrapplications/templates/hrapplications/view.html:45
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34 #: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34
@@ -163,22 +164,22 @@ msgstr "계정에 %(name)s를 추가하지 못했습니다. 이미 추가된 계
msgid "Unable to authenticate as the selected character." msgid "Unable to authenticate as the selected character."
msgstr "선택한 캐릭터로 인증을 수행할 수 없음" msgstr "선택한 캐릭터로 인증을 수행할 수 없음"
#: allianceauth/authentication/views.py:148 #: allianceauth/authentication/views.py:151
msgid "Registration token has expired." msgid "Registration token has expired."
msgstr "등록토큰 만료" msgstr "등록토큰 만료"
#: allianceauth/authentication/views.py:200 #: allianceauth/authentication/views.py:206
msgid "" msgid ""
"Sent confirmation email. Please follow the link to confirm your email " "Sent confirmation email. Please follow the link to confirm your email "
"address." "address."
msgstr "확인 메일 전송됨. 다음 링크를 눌러 이메일 주소를 확인하세요." msgstr "확인 메일 전송됨. 다음 링크를 눌러 이메일 주소를 확인하세요."
#: allianceauth/authentication/views.py:205 #: allianceauth/authentication/views.py:211
msgid "Confirmed your email address. Please login to continue." msgid "Confirmed your email address. Please login to continue."
msgstr "이메일 주소가 확인되었습니다. 로그인 해주세요." msgstr "이메일 주소가 확인되었습니다. 로그인 해주세요."
#: allianceauth/authentication/views.py:210 #: allianceauth/authentication/views.py:216
msgid "Registraion of new accounts it not allowed at this time." msgid "Registration of new accounts is not allowed at this time."
msgstr "현재 새로운 계정 등록은 받지않습니다." msgstr "현재 새로운 계정 등록은 받지않습니다."
#: allianceauth/corputils/auth_hooks.py:10 #: allianceauth/corputils/auth_hooks.py:10
@@ -226,16 +227,16 @@ msgstr "마지막 업데이트"
#: allianceauth/corputils/templates/corputils/search.html:13 #: allianceauth/corputils/templates/corputils/search.html:13
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:51 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:51
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:110 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:109
msgid "Character" msgid "Character"
msgstr "캐릭터" msgstr "캐릭터"
#: allianceauth/corputils/templates/corputils/corpstats.html:76 #: allianceauth/corputils/templates/corputils/corpstats.html:76
#: allianceauth/corputils/templates/corputils/search.html:14 #: 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:27
#: allianceauth/hrapplications/templates/hrapplications/management.html:84 #: allianceauth/hrapplications/templates/hrapplications/management.html:84
#: allianceauth/hrapplications/templates/hrapplications/management.html:129 #: allianceauth/hrapplications/templates/hrapplications/management.html:129
@@ -542,36 +543,35 @@ msgstr "플릿 참여 등록됨"
msgid "FAT link has expired." msgid "FAT link has expired."
msgstr "플릿활동추적 링크 기한만료" msgstr "플릿활동추적 링크 기한만료"
#: allianceauth/groupmanagement/auth_hooks.py:16 #: allianceauth/groupmanagement/auth_hooks.py:17
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:14
msgid "Group Management" msgid "Group Management"
msgstr "그룹 관리" msgstr "그룹 관리"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:13 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:14
msgid "Audit Log" msgid "Audit Log"
msgstr "감사 기록" msgstr "감사 기록"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:18 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:20
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:20 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:21
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
msgid "Back" msgid "Back"
msgstr "돌아가기" msgstr "돌아가기"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:28
msgid "Date/Time" msgid "Date/Time"
msgstr "날짜/시간" msgstr "날짜/시간"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:26 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
msgid "Requestor" msgid "Requestor"
msgstr "요청인" msgstr "요청인"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:32
msgid "Type" msgid "Type"
msgstr "타입" msgstr "타입"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:33
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:17
#: allianceauth/notifications/templates/notifications/list.html:37 #: allianceauth/notifications/templates/notifications/list.html:37
#: allianceauth/notifications/templates/notifications/list.html:69 #: allianceauth/notifications/templates/notifications/list.html:69
#: allianceauth/optimer/templates/optimer/fleetoptable.html:18 #: allianceauth/optimer/templates/optimer/fleetoptable.html:18
@@ -582,11 +582,19 @@ msgstr "타입"
msgid "Action" msgid "Action"
msgstr "활동" msgstr "활동"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:34
msgid "Actor" msgid "Actor"
msgstr "활동한 사람" msgstr "활동한 사람"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:47
msgid "Removed"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:59 #: 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." msgid "No entries found for this group."
msgstr "이 그룹에는 항목 없음" msgstr "이 그룹에는 항목 없음"
@@ -594,22 +602,24 @@ msgstr "이 그룹에는 항목 없음"
msgid "Group Members" msgid "Group Members"
msgstr "그룹 멤버" msgstr "그룹 멤버"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:28 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:31
msgid "Portrait"
msgstr "포트레잇"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:52 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:52
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:111 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:110
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
msgid "Organization" msgid "Organization"
msgstr "조직" 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" msgid "Remove from group"
msgstr "그룹에서 제거" msgstr "그룹에서 제거"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:76 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:81
msgid "No group members to list." msgid "No group members to list."
msgstr "목록에 그룹 멤버 없음" msgstr "목록에 그룹 멤버 없음"
@@ -617,18 +627,18 @@ msgstr "목록에 그룹 멤버 없음"
msgid "Groups Membership" msgid "Groups Membership"
msgstr "그룹 멤버쉽" 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/permissions_tool/templates/permissions_tool/overview.html:40
#: allianceauth/templates/allianceauth/side-menu.html:16 #: allianceauth/templates/allianceauth/side-menu.html:16
msgid "Groups" msgid "Groups"
msgstr "그룹" msgstr "그룹"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
msgid "Description" msgid "Description"
msgstr "설명" 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:28
#: allianceauth/hrapplications/templates/hrapplications/management.html:85 #: allianceauth/hrapplications/templates/hrapplications/management.html:85
#: allianceauth/hrapplications/templates/hrapplications/management.html:130 #: allianceauth/hrapplications/templates/hrapplications/management.html:130
@@ -637,36 +647,36 @@ msgstr "설명"
msgid "Status" msgid "Status"
msgstr "상태" msgstr "상태"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:27
msgid "Member Count" msgid "Member Count"
msgstr "멤버 수" msgstr "멤버 수"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:38 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:43
msgid "Hidden" msgid "Hidden"
msgstr "숨김" msgstr "숨김"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:40 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
#: allianceauth/templates/allianceauth/admin-status/overview.html:12 #: allianceauth/templates/allianceauth/admin-status/overview.html:12
msgid "Open" msgid "Open"
msgstr "열기" msgstr "열기"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:42 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:47
msgid "Requestable" msgid "Requestable"
msgstr "요청 가능" msgstr "요청 가능"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:50 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
msgid "View Members" msgid "View Members"
msgstr "멤버 보기" msgstr "멤버 보기"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:53 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:60
msgid "Audit Members" msgid "Audit Members"
msgstr "멤버 검사" msgstr "멤버 검사"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
msgid "Copy Direct Join Link" msgid "Copy Direct Join Link"
msgstr "직접 참여 링크 복사" msgstr "직접 참여 링크 복사"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:75
msgid "No groups to list." msgid "No groups to list."
msgstr "목록에 그룹 없음" msgstr "목록에 그룹 없음"
@@ -675,19 +685,19 @@ msgstr "목록에 그룹 없음"
msgid "Available Groups" msgid "Available Groups"
msgstr "사용 가능한 그룹" msgstr "사용 가능한 그룹"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:30
msgid "Leave" msgid "Leave"
msgstr "떠나기" msgstr "떠나기"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:39 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:40
msgid "Join" msgid "Join"
msgstr "참여하기" msgstr "참여하기"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:43 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:44
msgid "Request" msgid "Request"
msgstr "요청하기" msgstr "요청하기"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:58 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:59
msgid "No groups available." msgid "No groups available."
msgstr "사용 가능한 그룹 없음." msgstr "사용 가능한 그룹 없음."
@@ -699,24 +709,24 @@ msgstr "그룹 관리"
msgid "Join Requests" msgid "Join Requests"
msgstr "가입 요청" msgstr "가입 요청"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:33 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:34
msgid "Leave Requests" msgid "Leave Requests"
msgstr "탈퇴 요청" msgstr "탈퇴 요청"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:53 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:53
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:112 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:111
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
#: allianceauth/services/modules/openfire/forms.py:6 #: allianceauth/services/modules/openfire/forms.py:6
msgid "Group" msgid "Group"
msgstr "그룹" msgstr "그룹"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:85 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:84
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:144 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
msgid "Accept" msgid "Accept"
msgstr "수락" msgstr "수락"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:147 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:146
#: allianceauth/hrapplications/templates/hrapplications/view.html:85 #: allianceauth/hrapplications/templates/hrapplications/view.html:85
msgid "Reject" msgid "Reject"
msgstr "거절" msgstr "거절"
@@ -725,19 +735,19 @@ msgstr "거절"
msgid "No group add requests." msgid "No group add requests."
msgstr "가입 요청 없음" msgstr "가입 요청 없음"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:156 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:155
msgid "No group leave requests." msgid "No group leave requests."
msgstr "탈퇴 요청 없음" msgstr "탈퇴 요청 없음"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:10 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:9
msgid "Toggle navigation" msgid "Toggle navigation"
msgstr "네비게이션 전환" msgstr "네비게이션 전환"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:21 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:20
msgid "Group Requests" msgid "Group Requests"
msgstr "그룹 요청" msgstr "그룹 요청"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:24 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:23
msgid "Group Membership" msgid "Group Membership"
msgstr "그룹 멤버쉽" msgstr "그룹 멤버쉽"
@@ -811,7 +821,7 @@ msgstr "해당 그룹에 대한 참여신청이 보류되었습니다."
#: allianceauth/hrapplications/templates/hrapplications/management.html:144 #: allianceauth/hrapplications/templates/hrapplications/management.html:144
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:38 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:38
#: allianceauth/hrapplications/templates/hrapplications/view.html:20 #: allianceauth/hrapplications/templates/hrapplications/view.html:20
#: allianceauth/srp/templates/srp/data.html:128 #: allianceauth/srp/templates/srp/data.html:134
#: allianceauth/srp/templates/srp/management.html:81 #: allianceauth/srp/templates/srp/management.html:81
msgid "Pending" msgid "Pending"
msgstr "보류 중" msgstr "보류 중"
@@ -905,7 +915,7 @@ msgstr "활동"
#: allianceauth/hrapplications/templates/hrapplications/management.html:147 #: allianceauth/hrapplications/templates/hrapplications/management.html:147
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:40 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:40
#: allianceauth/hrapplications/templates/hrapplications/view.html:16 #: allianceauth/hrapplications/templates/hrapplications/view.html:16
#: allianceauth/srp/templates/srp/data.html:120 #: allianceauth/srp/templates/srp/data.html:126
msgid "Approved" msgid "Approved"
msgstr "승인" msgstr "승인"
@@ -913,7 +923,7 @@ msgstr "승인"
#: allianceauth/hrapplications/templates/hrapplications/management.html:104 #: allianceauth/hrapplications/templates/hrapplications/management.html:104
#: allianceauth/hrapplications/templates/hrapplications/management.html:149 #: allianceauth/hrapplications/templates/hrapplications/management.html:149
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:42 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:42
#: allianceauth/srp/templates/srp/data.html:124 #: allianceauth/srp/templates/srp/data.html:130
msgid "Rejected" msgid "Rejected"
msgstr "거절" msgstr "거절"
@@ -1296,13 +1306,13 @@ msgstr "비밀번호"
msgid "Password must be at least 8 characters long." msgid "Password must be at least 8 characters long."
msgstr "비밀번호는 8글자 이상이어야 합니다." msgstr "비밀번호는 8글자 이상이어야 합니다."
#: allianceauth/services/modules/discord/models.py:224 #: allianceauth/services/modules/discord/models.py:225
msgid "Discord Account Disabled" msgid "Discord Account Disabled"
msgstr "디스코드 계정 비활성화" msgstr "디스코드 계정 비활성화"
#: allianceauth/services/modules/discord/models.py:226 #: allianceauth/services/modules/discord/models.py:227
msgid "" msgid ""
"Your Discord account was disabeled automatically by Auth. If you think this " "Your Discord account was disabled automatically by Auth. If you think this "
"was a mistake, please contact an admin." "was a mistake, please contact an admin."
msgstr "Auth에 의해 자동으로 디스코드 계정이 비활성화됐습니다. 원치 않는 사항일 경우, 관리자에게 문의해 주세요." msgstr "Auth에 의해 자동으로 디스코드 계정이 비활성화됐습니다. 원치 않는 사항일 경우, 관리자에게 문의해 주세요."
@@ -1659,18 +1669,18 @@ msgid "Mark Completed"
msgstr "표시 완료" msgstr "표시 완료"
#: allianceauth/srp/templates/srp/data.html:70 #: allianceauth/srp/templates/srp/data.html:70
#: allianceauth/srp/templates/srp/data.html:150 #: allianceauth/srp/templates/srp/data.html:156
msgid "Total Losses:" msgid "Total Losses:"
msgstr "전체 손실:" msgstr "전체 손실:"
#: allianceauth/srp/templates/srp/data.html:71 #: allianceauth/srp/templates/srp/data.html:71
#: allianceauth/srp/templates/srp/data.html:151 #: allianceauth/srp/templates/srp/data.html:157
#: allianceauth/srp/templates/srp/management.html:30 #: allianceauth/srp/templates/srp/management.html:30
msgid "Total ISK Cost:" msgid "Total ISK Cost:"
msgstr "전체 ISK 비용:" msgstr "전체 ISK 비용:"
#: allianceauth/srp/templates/srp/data.html:79 #: allianceauth/srp/templates/srp/data.html:79
#: allianceauth/srp/templates/srp/data.html:159 #: allianceauth/srp/templates/srp/data.html:165
msgid "Are you sure you want to delete SRP requests?" msgid "Are you sure you want to delete SRP requests?"
msgstr "SRP 보상 요청을 삭제하시겠습니까?" msgstr "SRP 보상 요청을 삭제하시겠습니까?"
@@ -1702,7 +1712,7 @@ msgstr "금액을 수정하려면 클릭, 저장을 하고 다음으로 가려
msgid "Post Time" msgid "Post Time"
msgstr "작성 시간" msgstr "작성 시간"
#: allianceauth/srp/templates/srp/data.html:168 #: allianceauth/srp/templates/srp/data.html:174
msgid "No SRP requests for this fleet." msgid "No SRP requests for this fleet."
msgstr "이 플릿에는 SRP 보상 요청이 없습니다." msgstr "이 플릿에는 SRP 보상 요청이 없습니다."
@@ -2057,3 +2067,6 @@ msgstr "%(time)s 에 있을 %(system)s 타이머를 추가했습니다."
#: allianceauth/timerboard/views.py:83 #: allianceauth/timerboard/views.py:83
msgid "Saved changes to the timer." msgid "Saved changes to the timer."
msgstr "타이머 변경사항이 저장되었습니다." msgstr "타이머 변경사항이 저장되었습니다."
#~ msgid "Portrait"
#~ msgstr "포트레잇"

View File

@@ -5,15 +5,17 @@
# #
# Translators: # Translators:
# Alexander Gess <de.alex.gess@gmail.com>, 2020 # Alexander Gess <de.alex.gess@gmail.com>, 2020
# Yuriy K <thedjcooltv@gmail.com>, 2020
# Андрей Зубков <and.vareba81@gmail.com>, 2020
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-21 01:35+0000\n" "POT-Creation-Date: 2020-11-20 05:33+0000\n"
"PO-Revision-Date: 2020-02-18 03:14+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" "Language-Team: Russian (https://www.transifex.com/alliance-auth/teams/107430/ru/)\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@@ -25,7 +27,7 @@ msgstr ""
msgid "A main character is required to perform that action. Add one below." msgid "A main character is required to perform that action. Add one below."
msgstr "Необходимо указать основного персонажа. Добавим?" msgstr "Необходимо указать основного персонажа. Добавим?"
#: allianceauth/authentication/forms.py:6 #: allianceauth/authentication/forms.py:5
msgid "Email" msgid "Email"
msgstr "Email" msgstr "Email"
@@ -70,7 +72,7 @@ msgstr "Сменить основного персонажа"
#: allianceauth/authentication/templates/authentication/dashboard.html:101 #: allianceauth/authentication/templates/authentication/dashboard.html:101
msgid "Group Memberships" msgid "Group Memberships"
msgstr "Групповое участие" msgstr "Роли"
#: allianceauth/authentication/templates/authentication/dashboard.html:121 #: allianceauth/authentication/templates/authentication/dashboard.html:121
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkstatisticscorpview.html:23
@@ -80,7 +82,7 @@ msgstr "Персонажи"
#: allianceauth/authentication/templates/authentication/dashboard.html:129 #: allianceauth/authentication/templates/authentication/dashboard.html:129
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73 #: 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/groupmanagement/templates/groupmanagement/groups.html:15
#: allianceauth/hrapplications/templates/hrapplications/view.html:45 #: allianceauth/hrapplications/templates/hrapplications/view.html:45
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34 #: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34
@@ -163,23 +165,23 @@ msgstr "Персонаж %(name)s уже добавлен."
msgid "Unable to authenticate as the selected character." msgid "Unable to authenticate as the selected character."
msgstr "Невозможно авторизировать этого персонажа. " msgstr "Невозможно авторизировать этого персонажа. "
#: allianceauth/authentication/views.py:148 #: allianceauth/authentication/views.py:151
msgid "Registration token has expired." msgid "Registration token has expired."
msgstr "Регистрационный токен просрочен." msgstr "Регистрационный токен просрочен."
#: allianceauth/authentication/views.py:200 #: allianceauth/authentication/views.py:206
msgid "" msgid ""
"Sent confirmation email. Please follow the link to confirm your email " "Sent confirmation email. Please follow the link to confirm your email "
"address." "address."
msgstr "Отправить подтверждающее письмо. Пожалуйста, подтвердите почту. " msgstr "Отправить подтверждающее письмо. Пожалуйста, подтвердите почту. "
#: allianceauth/authentication/views.py:205 #: allianceauth/authentication/views.py:211
msgid "Confirmed your email address. Please login to continue." msgid "Confirmed your email address. Please login to continue."
msgstr "Подтвердите Ваш email адрес. Зайти для подтверждения. " msgstr "Подтвердите Ваш email адрес. Зайти для подтверждения. "
#: allianceauth/authentication/views.py:210 #: allianceauth/authentication/views.py:216
msgid "Registraion of new accounts it not allowed at this time." msgid "Registration of new accounts is not allowed at this time."
msgstr "Регистрация нового аккаунта сейчас невозможна." msgstr "Регистрация новых аккаунтов в настоящее время невозможна."
#: allianceauth/corputils/auth_hooks.py:10 #: allianceauth/corputils/auth_hooks.py:10
msgid "Corporation Stats" msgid "Corporation Stats"
@@ -226,16 +228,16 @@ msgstr "Последнее обновление: "
#: allianceauth/corputils/templates/corputils/search.html:13 #: allianceauth/corputils/templates/corputils/search.html:13
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:51 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:51
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:110 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:109
msgid "Character" msgid "Character"
msgstr "Персонаж" msgstr "Персонаж"
#: allianceauth/corputils/templates/corputils/corpstats.html:76 #: allianceauth/corputils/templates/corputils/corpstats.html:76
#: allianceauth/corputils/templates/corputils/search.html:14 #: 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:27
#: allianceauth/hrapplications/templates/hrapplications/management.html:84 #: allianceauth/hrapplications/templates/hrapplications/management.html:84
#: allianceauth/hrapplications/templates/hrapplications/management.html:129 #: allianceauth/hrapplications/templates/hrapplications/management.html:129
@@ -326,7 +328,7 @@ msgstr "Добавить сюда"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:19 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/characternotexisting.html:19
msgid "before attempting to click fleet attendance links." msgid "before attempting to click fleet attendance links."
msgstr "перед вступлением проверте содержимое" msgstr "перед вступлением проверьте содержимое"
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkformatter.html:6 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkformatter.html:6
msgid "Create Fatlink" msgid "Create Fatlink"
@@ -548,36 +550,35 @@ msgstr "Флотовое участие зарегистрированно."
msgid "FAT link has expired." msgid "FAT link has expired."
msgstr "ФлАк ссылка устарела" msgstr "ФлАк ссылка устарела"
#: allianceauth/groupmanagement/auth_hooks.py:16 #: allianceauth/groupmanagement/auth_hooks.py:17
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:14
msgid "Group Management" msgid "Group Management"
msgstr "Управление Группой" msgstr "Управление Группой"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:13 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:14
msgid "Audit Log" msgid "Audit Log"
msgstr "Записи безопасности" msgstr "Записи безопасности"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:18 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:20
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:20 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:21
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
msgid "Back" msgid "Back"
msgstr "Назад" msgstr "Назад"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:28
msgid "Date/Time" msgid "Date/Time"
msgstr "Дата / Время" msgstr "Дата / Время"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:26 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
msgid "Requestor" msgid "Requestor"
msgstr "Запрос от" msgstr "Запрос от"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:32
msgid "Type" msgid "Type"
msgstr "Тип" msgstr "Тип"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:33
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:17
#: allianceauth/notifications/templates/notifications/list.html:37 #: allianceauth/notifications/templates/notifications/list.html:37
#: allianceauth/notifications/templates/notifications/list.html:69 #: allianceauth/notifications/templates/notifications/list.html:69
#: allianceauth/optimer/templates/optimer/fleetoptable.html:18 #: allianceauth/optimer/templates/optimer/fleetoptable.html:18
@@ -588,11 +589,19 @@ msgstr "Тип"
msgid "Action" msgid "Action"
msgstr "Действие" msgstr "Действие"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:34
msgid "Actor" msgid "Actor"
msgstr "Исполнитель" msgstr "Исполнитель"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:47
msgid "Removed"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:59 #: 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." msgid "No entries found for this group."
msgstr "Нет вхождений в эту группу" msgstr "Нет вхождений в эту группу"
@@ -600,22 +609,24 @@ msgstr "Нет вхождений в эту группу"
msgid "Group Members" msgid "Group Members"
msgstr "Групповые Участники" msgstr "Групповые Участники"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:28 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:31
msgid "Portrait"
msgstr "Портрет"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:52 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:52
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:111 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:110
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
msgid "Organization" msgid "Organization"
msgstr "Корпорация" 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" msgid "Remove from group"
msgstr "Исключить из группы" msgstr "Исключить из группы"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:76 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:81
msgid "No group members to list." msgid "No group members to list."
msgstr "Нет участников в группе" msgstr "Нет участников в группе"
@@ -623,18 +634,18 @@ msgstr "Нет участников в группе"
msgid "Groups Membership" msgid "Groups Membership"
msgstr "Участники группы" 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/permissions_tool/templates/permissions_tool/overview.html:40
#: allianceauth/templates/allianceauth/side-menu.html:16 #: allianceauth/templates/allianceauth/side-menu.html:16
msgid "Groups" msgid "Groups"
msgstr "Группы" msgstr "Группы"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
msgid "Description" msgid "Description"
msgstr "Описание" 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:28
#: allianceauth/hrapplications/templates/hrapplications/management.html:85 #: allianceauth/hrapplications/templates/hrapplications/management.html:85
#: allianceauth/hrapplications/templates/hrapplications/management.html:130 #: allianceauth/hrapplications/templates/hrapplications/management.html:130
@@ -643,36 +654,36 @@ msgstr "Описание"
msgid "Status" msgid "Status"
msgstr "Статус" msgstr "Статус"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:27
msgid "Member Count" msgid "Member Count"
msgstr "Число участников" msgstr "Число участников"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:38 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:43
msgid "Hidden" msgid "Hidden"
msgstr "Скрытые" msgstr "Скрытые"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:40 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
#: allianceauth/templates/allianceauth/admin-status/overview.html:12 #: allianceauth/templates/allianceauth/admin-status/overview.html:12
msgid "Open" msgid "Open"
msgstr "Открыть" msgstr "Открыть"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:42 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:47
msgid "Requestable" msgid "Requestable"
msgstr "Запрошено" msgstr "Запрошено"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:50 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
msgid "View Members" msgid "View Members"
msgstr "Посмотреть участников" msgstr "Посмотреть участников"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:53 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:60
msgid "Audit Members" msgid "Audit Members"
msgstr "Проверить участников" msgstr "Проверить участников"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
msgid "Copy Direct Join Link" msgid "Copy Direct Join Link"
msgstr "Скопировать ссылку подключения" msgstr "Скопировать ссылку подключения"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:75
msgid "No groups to list." msgid "No groups to list."
msgstr "Нет групп в списке" msgstr "Нет групп в списке"
@@ -681,19 +692,19 @@ msgstr "Нет групп в списке"
msgid "Available Groups" msgid "Available Groups"
msgstr "Доступные группы" msgstr "Доступные группы"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:30
msgid "Leave" msgid "Leave"
msgstr "Покинуть" msgstr "Покинуть"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:39 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:40
msgid "Join" msgid "Join"
msgstr "Присоединиться" msgstr "Присоединиться"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:43 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:44
msgid "Request" msgid "Request"
msgstr "Запрос" msgstr "Запрос"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:58 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:59
msgid "No groups available." msgid "No groups available."
msgstr "Нет доступных групп." msgstr "Нет доступных групп."
@@ -705,24 +716,24 @@ msgstr "Управление Группами"
msgid "Join Requests" msgid "Join Requests"
msgstr "Запрос на присоединение" msgstr "Запрос на присоединение"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:33 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:34
msgid "Leave Requests" msgid "Leave Requests"
msgstr "Запрос на Выход" msgstr "Запрос на Выход"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:53 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:53
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:112 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:111
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
#: allianceauth/services/modules/openfire/forms.py:6 #: allianceauth/services/modules/openfire/forms.py:6
msgid "Group" msgid "Group"
msgstr "Группа" msgstr "Группа"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:85 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:84
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:144 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
msgid "Accept" msgid "Accept"
msgstr "Принять" msgstr "Принять"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:147 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:146
#: allianceauth/hrapplications/templates/hrapplications/view.html:85 #: allianceauth/hrapplications/templates/hrapplications/view.html:85
msgid "Reject" msgid "Reject"
msgstr "Сбросить" msgstr "Сбросить"
@@ -731,19 +742,19 @@ msgstr "Сбросить"
msgid "No group add requests." msgid "No group add requests."
msgstr "Нет групповых запросов на вступление" msgstr "Нет групповых запросов на вступление"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:156 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:155
msgid "No group leave requests." msgid "No group leave requests."
msgstr "Нет групповых запросов на выход" msgstr "Нет групповых запросов на выход"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:10 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:9
msgid "Toggle navigation" msgid "Toggle navigation"
msgstr "Проложить маршрут" msgstr "Проложить маршрут"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:21 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:20
msgid "Group Requests" msgid "Group Requests"
msgstr "Групповой запрос" msgstr "Групповой запрос"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:24 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:23
msgid "Group Membership" msgid "Group Membership"
msgstr "Групповое участие" msgstr "Групповое участие"
@@ -821,7 +832,7 @@ msgstr "Вы уже подали заявку на вступление этой
#: allianceauth/hrapplications/templates/hrapplications/management.html:144 #: allianceauth/hrapplications/templates/hrapplications/management.html:144
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:38 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:38
#: allianceauth/hrapplications/templates/hrapplications/view.html:20 #: allianceauth/hrapplications/templates/hrapplications/view.html:20
#: allianceauth/srp/templates/srp/data.html:128 #: allianceauth/srp/templates/srp/data.html:134
#: allianceauth/srp/templates/srp/management.html:81 #: allianceauth/srp/templates/srp/management.html:81
msgid "Pending" msgid "Pending"
msgstr "Ожидание" msgstr "Ожидание"
@@ -915,7 +926,7 @@ msgstr "Действия"
#: allianceauth/hrapplications/templates/hrapplications/management.html:147 #: allianceauth/hrapplications/templates/hrapplications/management.html:147
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:40 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:40
#: allianceauth/hrapplications/templates/hrapplications/view.html:16 #: allianceauth/hrapplications/templates/hrapplications/view.html:16
#: allianceauth/srp/templates/srp/data.html:120 #: allianceauth/srp/templates/srp/data.html:126
msgid "Approved" msgid "Approved"
msgstr "Проверено" msgstr "Проверено"
@@ -923,7 +934,7 @@ msgstr "Проверено"
#: allianceauth/hrapplications/templates/hrapplications/management.html:104 #: allianceauth/hrapplications/templates/hrapplications/management.html:104
#: allianceauth/hrapplications/templates/hrapplications/management.html:149 #: allianceauth/hrapplications/templates/hrapplications/management.html:149
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:42 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:42
#: allianceauth/srp/templates/srp/data.html:124 #: allianceauth/srp/templates/srp/data.html:130
msgid "Rejected" msgid "Rejected"
msgstr "Отменено " msgstr "Отменено "
@@ -1029,7 +1040,7 @@ msgstr "Не прочитанно"
#: allianceauth/notifications/templates/notifications/list.html:18 #: allianceauth/notifications/templates/notifications/list.html:18
msgid "Read" msgid "Read"
msgstr "Прочитать" msgstr "Прочитано"
#: allianceauth/notifications/templates/notifications/list.html:22 #: allianceauth/notifications/templates/notifications/list.html:22
msgid "Mark All Read" msgid "Mark All Read"
@@ -1137,7 +1148,7 @@ msgstr "Таймера Флотовых операций"
#: allianceauth/optimer/templates/optimer/management.html:21 #: allianceauth/optimer/templates/optimer/management.html:21
#: allianceauth/timerboard/templates/timerboard/view.html:23 #: allianceauth/timerboard/templates/timerboard/view.html:23
msgid "Current Eve Time:" msgid "Current Eve Time:"
msgstr "ET" msgstr "Текущий EVE Time:"
#: allianceauth/optimer/templates/optimer/management.html:27 #: allianceauth/optimer/templates/optimer/management.html:27
#: allianceauth/timerboard/templates/timerboard/view.html:189 #: allianceauth/timerboard/templates/timerboard/view.html:189
@@ -1244,7 +1255,7 @@ msgstr "{} Пароль успешно обновлен."
#: allianceauth/services/auth_hooks.py:11 #: allianceauth/services/auth_hooks.py:11
msgid "Services" msgid "Services"
msgstr "Сервисные услуги" msgstr "Подключение сервисов"
#: allianceauth/services/forms.py:6 #: allianceauth/services/forms.py:6
msgid "Name of Fleet:" msgid "Name of Fleet:"
@@ -1306,17 +1317,15 @@ msgstr "Пароль"
msgid "Password must be at least 8 characters long." msgid "Password must be at least 8 characters long."
msgstr "Пароль должен быть не менее 8 символов." msgstr "Пароль должен быть не менее 8 символов."
#: allianceauth/services/modules/discord/models.py:224 #: allianceauth/services/modules/discord/models.py:225
msgid "Discord Account Disabled" msgid "Discord Account Disabled"
msgstr "Discord персонаж отключен" msgstr "Discord персонаж отключен"
#: allianceauth/services/modules/discord/models.py:226 #: allianceauth/services/modules/discord/models.py:227
msgid "" msgid ""
"Your Discord account was disabeled automatically by Auth. If you think this " "Your Discord account was disabled automatically by Auth. If you think this "
"was a mistake, please contact an admin." "was a mistake, please contact an admin."
msgstr "" msgstr ""
"Ваш доступ на сервер Discord был отменен. Если Вы считаете что по ошибке, "
"пожалуйста, свяжитесь с СЕО."
#: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:18 #: allianceauth/services/modules/discord/templates/services/discord/discord_service_ctrl.html:18
msgid "Join the Discord server" msgid "Join the Discord server"
@@ -1625,7 +1634,7 @@ msgstr "Домен"
#: allianceauth/srp/auth_hooks.py:12 #: allianceauth/srp/auth_hooks.py:12
msgid "Ship Replacement" msgid "Ship Replacement"
msgstr "Замена корабля" msgstr "Компенсация корабля"
#: allianceauth/srp/form.py:7 #: allianceauth/srp/form.py:7
#: allianceauth/srp/templates/srp/management.html:38 #: allianceauth/srp/templates/srp/management.html:38
@@ -1679,18 +1688,18 @@ msgid "Mark Completed"
msgstr "Пометить законченным" msgstr "Пометить законченным"
#: allianceauth/srp/templates/srp/data.html:70 #: allianceauth/srp/templates/srp/data.html:70
#: allianceauth/srp/templates/srp/data.html:150 #: allianceauth/srp/templates/srp/data.html:156
msgid "Total Losses:" msgid "Total Losses:"
msgstr "Суммарные потери:" msgstr "Суммарные потери:"
#: allianceauth/srp/templates/srp/data.html:71 #: allianceauth/srp/templates/srp/data.html:71
#: allianceauth/srp/templates/srp/data.html:151 #: allianceauth/srp/templates/srp/data.html:157
#: allianceauth/srp/templates/srp/management.html:30 #: allianceauth/srp/templates/srp/management.html:30
msgid "Total ISK Cost:" msgid "Total ISK Cost:"
msgstr "Оценочная стоимость (ISK):" msgstr "Оценочная стоимость (ISK):"
#: allianceauth/srp/templates/srp/data.html:79 #: allianceauth/srp/templates/srp/data.html:79
#: allianceauth/srp/templates/srp/data.html:159 #: allianceauth/srp/templates/srp/data.html:165
msgid "Are you sure you want to delete SRP requests?" msgid "Are you sure you want to delete SRP requests?"
msgstr "Вы уверенны что хотите удалить запрос на SRP?" msgstr "Вы уверенны что хотите удалить запрос на SRP?"
@@ -1722,7 +1731,7 @@ msgstr "Нажмите на значение для редактирования
msgid "Post Time" msgid "Post Time"
msgstr "Опубликованно" msgstr "Опубликованно"
#: allianceauth/srp/templates/srp/data.html:168 #: allianceauth/srp/templates/srp/data.html:174
msgid "No SRP requests for this fleet." msgid "No SRP requests for this fleet."
msgstr "SRP запросы отсутствуют" msgstr "SRP запросы отсутствуют"
@@ -1974,14 +1983,14 @@ msgstr "Прочие"
#: allianceauth/timerboard/templates/timerboard/view.html:220 #: allianceauth/timerboard/templates/timerboard/view.html:220
#: allianceauth/timerboard/templates/timerboard/view.html:388 #: allianceauth/timerboard/templates/timerboard/view.html:388
msgid "Friendly" msgid "Friendly"
msgstr "Дружествен" msgstr "Дружественный"
#: allianceauth/timerboard/form.py:55 #: allianceauth/timerboard/form.py:55
#: allianceauth/timerboard/templates/timerboard/view.html:53 #: allianceauth/timerboard/templates/timerboard/view.html:53
#: allianceauth/timerboard/templates/timerboard/view.html:215 #: allianceauth/timerboard/templates/timerboard/view.html:215
#: allianceauth/timerboard/templates/timerboard/view.html:383 #: allianceauth/timerboard/templates/timerboard/view.html:383
msgid "Hostile" msgid "Hostile"
msgstr "Заложник" msgstr "Вражеский"
#: allianceauth/timerboard/form.py:56 #: allianceauth/timerboard/form.py:56
#: allianceauth/timerboard/templates/timerboard/view.html:63 #: allianceauth/timerboard/templates/timerboard/view.html:63
@@ -2083,3 +2092,6 @@ msgstr "Добавлен таймер в %(system)s на %(time)s."
#: allianceauth/timerboard/views.py:83 #: allianceauth/timerboard/views.py:83
msgid "Saved changes to the timer." msgid "Saved changes to the timer."
msgstr "Изменения сохранены" msgstr "Изменения сохранены"
#~ msgid "Portrait"
#~ msgstr "Портрет"

View File

@@ -13,7 +13,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-21 01:35+0000\n" "POT-Creation-Date: 2020-11-20 05:33+0000\n"
"PO-Revision-Date: 2020-02-18 03:14+0000\n" "PO-Revision-Date: 2020-02-18 03:14+0000\n"
"Last-Translator: Aaron BuBu <351793078@qq.com>, 2020\n" "Last-Translator: Aaron BuBu <351793078@qq.com>, 2020\n"
"Language-Team: Chinese Simplified (https://www.transifex.com/alliance-auth/teams/107430/zh-Hans/)\n" "Language-Team: Chinese Simplified (https://www.transifex.com/alliance-auth/teams/107430/zh-Hans/)\n"
@@ -27,7 +27,7 @@ msgstr ""
msgid "A main character is required to perform that action. Add one below." msgid "A main character is required to perform that action. Add one below."
msgstr "只有主要角色才能执行这个操作。在下面添加一个" msgstr "只有主要角色才能执行这个操作。在下面添加一个"
#: allianceauth/authentication/forms.py:6 #: allianceauth/authentication/forms.py:5
msgid "Email" msgid "Email"
msgstr "电子邮箱" msgstr "电子邮箱"
@@ -79,7 +79,7 @@ msgstr "角色"
#: allianceauth/authentication/templates/authentication/dashboard.html:129 #: allianceauth/authentication/templates/authentication/dashboard.html:129
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:73 #: 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/groupmanagement/templates/groupmanagement/groups.html:15
#: allianceauth/hrapplications/templates/hrapplications/view.html:45 #: allianceauth/hrapplications/templates/hrapplications/view.html:45
#: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34 #: allianceauth/permissions_tool/templates/permissions_tool/overview.html:34
@@ -159,23 +159,23 @@ msgstr "添加%(name)s到您的账户失败他们已经在一个账户中了"
msgid "Unable to authenticate as the selected character." msgid "Unable to authenticate as the selected character."
msgstr "无法作为选定的角色进行身份验证" msgstr "无法作为选定的角色进行身份验证"
#: allianceauth/authentication/views.py:148 #: allianceauth/authentication/views.py:151
msgid "Registration token has expired." msgid "Registration token has expired."
msgstr "注册令牌过期。" msgstr "注册令牌过期。"
#: allianceauth/authentication/views.py:200 #: allianceauth/authentication/views.py:206
msgid "" msgid ""
"Sent confirmation email. Please follow the link to confirm your email " "Sent confirmation email. Please follow the link to confirm your email "
"address." "address."
msgstr "已经发送了确认邮件。请按照链接确定您的电邮地址" msgstr "已经发送了确认邮件。请按照链接确定您的电邮地址"
#: allianceauth/authentication/views.py:205 #: allianceauth/authentication/views.py:211
msgid "Confirmed your email address. Please login to continue." msgid "Confirmed your email address. Please login to continue."
msgstr "已确认您的电邮地址。请登录以继续" msgstr "已确认您的电邮地址。请登录以继续"
#: allianceauth/authentication/views.py:210 #: allianceauth/authentication/views.py:216
msgid "Registraion of new accounts it not allowed at this time." msgid "Registration of new accounts is not allowed at this time."
msgstr "现在不允许注册新账户。" msgstr ""
#: allianceauth/corputils/auth_hooks.py:10 #: allianceauth/corputils/auth_hooks.py:10
msgid "Corporation Stats" msgid "Corporation Stats"
@@ -222,16 +222,16 @@ msgstr "最后一次更新"
#: allianceauth/corputils/templates/corputils/search.html:13 #: allianceauth/corputils/templates/corputils/search.html:13
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkmodify.html:24
#: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28 #: allianceauth/fleetactivitytracking/templates/fleetactivitytracking/fatlinkview.html:28
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:27 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:51 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:51
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:110 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:109
msgid "Character" msgid "Character"
msgstr "角色" msgstr "角色"
#: allianceauth/corputils/templates/corputils/corpstats.html:76 #: allianceauth/corputils/templates/corputils/corpstats.html:76
#: allianceauth/corputils/templates/corputils/search.html:14 #: 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:27
#: allianceauth/hrapplications/templates/hrapplications/management.html:84 #: allianceauth/hrapplications/templates/hrapplications/management.html:84
#: allianceauth/hrapplications/templates/hrapplications/management.html:129 #: allianceauth/hrapplications/templates/hrapplications/management.html:129
@@ -538,36 +538,35 @@ msgstr "成功注册舰队PAP"
msgid "FAT link has expired." msgid "FAT link has expired."
msgstr "PAP链接已过期" msgstr "PAP链接已过期"
#: allianceauth/groupmanagement/auth_hooks.py:16 #: allianceauth/groupmanagement/auth_hooks.py:17
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:15 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:14
msgid "Group Management" msgid "Group Management"
msgstr "用户组管理" msgstr "用户组管理"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:5
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:13 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:14
msgid "Audit Log" msgid "Audit Log"
msgstr "审计日志" msgstr "审计日志"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:18 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:20
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:20 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:21
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:13
msgid "Back" msgid "Back"
msgstr "返回" msgstr "返回"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:25 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:28
msgid "Date/Time" msgid "Date/Time"
msgstr "日期/时间" msgstr "日期/时间"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:26 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29
msgid "Requestor" msgid "Requestor"
msgstr "申请人" msgstr "申请人"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:32
msgid "Type" msgid "Type"
msgstr "类型" msgstr "类型"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:30 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:33
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:17
#: allianceauth/notifications/templates/notifications/list.html:37 #: allianceauth/notifications/templates/notifications/list.html:37
#: allianceauth/notifications/templates/notifications/list.html:69 #: allianceauth/notifications/templates/notifications/list.html:69
#: allianceauth/optimer/templates/optimer/fleetoptable.html:18 #: allianceauth/optimer/templates/optimer/fleetoptable.html:18
@@ -578,11 +577,19 @@ msgstr "类型"
msgid "Action" msgid "Action"
msgstr "操作" msgstr "操作"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:31 #: allianceauth/groupmanagement/templates/groupmanagement/audit.html:34
msgid "Actor" msgid "Actor"
msgstr "操作者" msgstr "操作者"
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:47
msgid "Removed"
msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/audit.html:59 #: 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." msgid "No entries found for this group."
msgstr "这一用户组下面没有任何条目呀" msgstr "这一用户组下面没有任何条目呀"
@@ -590,22 +597,24 @@ msgstr "这一用户组下面没有任何条目呀"
msgid "Group Members" msgid "Group Members"
msgstr "群组成员" msgstr "群组成员"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:28 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:31
msgid "Portrait"
msgstr "人物头像"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:30
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:52 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:52
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:111 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:110
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:23
msgid "Organization" msgid "Organization"
msgstr "组织" 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" msgid "Remove from group"
msgstr "从用户组中移除" msgstr "从用户组中移除"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:76 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembers.html:81
msgid "No group members to list." msgid "No group members to list."
msgstr "用户组里没人呀,你叫我怎么列" msgstr "用户组里没人呀,你叫我怎么列"
@@ -613,18 +622,18 @@ msgstr "用户组里没人呀,你叫我怎么列"
msgid "Groups Membership" msgid "Groups Membership"
msgstr "用户组成员" 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/permissions_tool/templates/permissions_tool/overview.html:40
#: allianceauth/templates/allianceauth/side-menu.html:16 #: allianceauth/templates/allianceauth/side-menu.html:16
msgid "Groups" msgid "Groups"
msgstr "群组" msgstr "群组"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:23 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:16
msgid "Description" msgid "Description"
msgstr "描述" 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:28
#: allianceauth/hrapplications/templates/hrapplications/management.html:85 #: allianceauth/hrapplications/templates/hrapplications/management.html:85
#: allianceauth/hrapplications/templates/hrapplications/management.html:130 #: allianceauth/hrapplications/templates/hrapplications/management.html:130
@@ -633,36 +642,36 @@ msgstr "描述"
msgid "Status" msgid "Status"
msgstr "状态" msgstr "状态"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:25 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:27
msgid "Member Count" msgid "Member Count"
msgstr "成员数量" msgstr "成员数量"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:38 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:43
msgid "Hidden" msgid "Hidden"
msgstr "已隐藏" msgstr "已隐藏"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:40 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:45
#: allianceauth/templates/allianceauth/admin-status/overview.html:12 #: allianceauth/templates/allianceauth/admin-status/overview.html:12
msgid "Open" msgid "Open"
msgstr "公开" msgstr "公开"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:42 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:47
msgid "Requestable" msgid "Requestable"
msgstr "可申请" msgstr "可申请"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:50 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56
msgid "View Members" msgid "View Members"
msgstr "查看成员" msgstr "查看成员"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:53 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:60
msgid "Audit Members" msgid "Audit Members"
msgstr "编辑成员" msgstr "编辑成员"
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:56 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:64
msgid "Copy Direct Join Link" msgid "Copy Direct Join Link"
msgstr "" msgstr ""
#: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:68 #: allianceauth/groupmanagement/templates/groupmanagement/groupmembership.html:75
msgid "No groups to list." msgid "No groups to list."
msgstr "无可用组" msgstr "无可用组"
@@ -671,19 +680,19 @@ msgstr "无可用组"
msgid "Available Groups" msgid "Available Groups"
msgstr "可用组" msgstr "可用组"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:29 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:30
msgid "Leave" msgid "Leave"
msgstr "离开" msgstr "离开"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:39 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:40
msgid "Join" msgid "Join"
msgstr "加入" msgstr "加入"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:43 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:44
msgid "Request" msgid "Request"
msgstr "申请" msgstr "申请"
#: allianceauth/groupmanagement/templates/groupmanagement/groups.html:58 #: allianceauth/groupmanagement/templates/groupmanagement/groups.html:59
msgid "No groups available." msgid "No groups available."
msgstr "没有可用用户组" msgstr "没有可用用户组"
@@ -695,24 +704,24 @@ msgstr "用户组管理"
msgid "Join Requests" msgid "Join Requests"
msgstr "入组的请求" msgstr "入组的请求"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:33 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:34
msgid "Leave Requests" msgid "Leave Requests"
msgstr "离组的请求" msgstr "离组的请求"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:53 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:53
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:112 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:111
#: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20 #: allianceauth/permissions_tool/templates/permissions_tool/audit.html:20
#: allianceauth/services/modules/openfire/forms.py:6 #: allianceauth/services/modules/openfire/forms.py:6
msgid "Group" msgid "Group"
msgstr "用户组" msgstr "用户组"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:85 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:84
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:144 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:142
msgid "Accept" msgid "Accept"
msgstr "接受" msgstr "接受"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:88 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:88
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:147 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:146
#: allianceauth/hrapplications/templates/hrapplications/view.html:85 #: allianceauth/hrapplications/templates/hrapplications/view.html:85
msgid "Reject" msgid "Reject"
msgstr "拒绝" msgstr "拒绝"
@@ -721,19 +730,19 @@ msgstr "拒绝"
msgid "No group add requests." msgid "No group add requests."
msgstr "没有加入用户组的请求,小老弟你是不是摇不到人" msgstr "没有加入用户组的请求,小老弟你是不是摇不到人"
#: allianceauth/groupmanagement/templates/groupmanagement/index.html:156 #: allianceauth/groupmanagement/templates/groupmanagement/index.html:155
msgid "No group leave requests." msgid "No group leave requests."
msgstr "没有离开用户组的请求,小老弟你人缘可以啊?" msgstr "没有离开用户组的请求,小老弟你人缘可以啊?"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:10 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:9
msgid "Toggle navigation" msgid "Toggle navigation"
msgstr "打开导航栏" msgstr "打开导航栏"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:21 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:20
msgid "Group Requests" msgid "Group Requests"
msgstr "用户组请求" msgstr "用户组请求"
#: allianceauth/groupmanagement/templates/groupmanagement/menu.html:24 #: allianceauth/groupmanagement/templates/groupmanagement/menu.html:23
msgid "Group Membership" msgid "Group Membership"
msgstr "用户组成员" msgstr "用户组成员"
@@ -807,7 +816,7 @@ msgstr "你已经有了该组的未决申请"
#: allianceauth/hrapplications/templates/hrapplications/management.html:144 #: allianceauth/hrapplications/templates/hrapplications/management.html:144
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:38 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:38
#: allianceauth/hrapplications/templates/hrapplications/view.html:20 #: allianceauth/hrapplications/templates/hrapplications/view.html:20
#: allianceauth/srp/templates/srp/data.html:128 #: allianceauth/srp/templates/srp/data.html:134
#: allianceauth/srp/templates/srp/management.html:81 #: allianceauth/srp/templates/srp/management.html:81
msgid "Pending" msgid "Pending"
msgstr "待定" msgstr "待定"
@@ -901,7 +910,7 @@ msgstr "操作"
#: allianceauth/hrapplications/templates/hrapplications/management.html:147 #: allianceauth/hrapplications/templates/hrapplications/management.html:147
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:40 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:40
#: allianceauth/hrapplications/templates/hrapplications/view.html:16 #: allianceauth/hrapplications/templates/hrapplications/view.html:16
#: allianceauth/srp/templates/srp/data.html:120 #: allianceauth/srp/templates/srp/data.html:126
msgid "Approved" msgid "Approved"
msgstr "通过" msgstr "通过"
@@ -909,7 +918,7 @@ msgstr "通过"
#: allianceauth/hrapplications/templates/hrapplications/management.html:104 #: allianceauth/hrapplications/templates/hrapplications/management.html:104
#: allianceauth/hrapplications/templates/hrapplications/management.html:149 #: allianceauth/hrapplications/templates/hrapplications/management.html:149
#: allianceauth/hrapplications/templates/hrapplications/searchview.html:42 #: allianceauth/hrapplications/templates/hrapplications/searchview.html:42
#: allianceauth/srp/templates/srp/data.html:124 #: allianceauth/srp/templates/srp/data.html:130
msgid "Rejected" msgid "Rejected"
msgstr "拒绝" msgstr "拒绝"
@@ -1292,13 +1301,13 @@ msgstr "密码"
msgid "Password must be at least 8 characters long." msgid "Password must be at least 8 characters long."
msgstr "密码至少要有8个字符啊你也太不注重安全啦" msgstr "密码至少要有8个字符啊你也太不注重安全啦"
#: allianceauth/services/modules/discord/models.py:224 #: allianceauth/services/modules/discord/models.py:225
msgid "Discord Account Disabled" msgid "Discord Account Disabled"
msgstr "" msgstr ""
#: allianceauth/services/modules/discord/models.py:226 #: allianceauth/services/modules/discord/models.py:227
msgid "" msgid ""
"Your Discord account was disabeled automatically by Auth. If you think this " "Your Discord account was disabled automatically by Auth. If you think this "
"was a mistake, please contact an admin." "was a mistake, please contact an admin."
msgstr "" msgstr ""
@@ -1655,18 +1664,18 @@ msgid "Mark Completed"
msgstr "标记为已完成" msgstr "标记为已完成"
#: allianceauth/srp/templates/srp/data.html:70 #: allianceauth/srp/templates/srp/data.html:70
#: allianceauth/srp/templates/srp/data.html:150 #: allianceauth/srp/templates/srp/data.html:156
msgid "Total Losses:" msgid "Total Losses:"
msgstr "损失总额:" msgstr "损失总额:"
#: allianceauth/srp/templates/srp/data.html:71 #: allianceauth/srp/templates/srp/data.html:71
#: allianceauth/srp/templates/srp/data.html:151 #: allianceauth/srp/templates/srp/data.html:157
#: allianceauth/srp/templates/srp/management.html:30 #: allianceauth/srp/templates/srp/management.html:30
msgid "Total ISK Cost:" msgid "Total ISK Cost:"
msgstr "ISK花费总额" msgstr "ISK花费总额"
#: allianceauth/srp/templates/srp/data.html:79 #: allianceauth/srp/templates/srp/data.html:79
#: allianceauth/srp/templates/srp/data.html:159 #: allianceauth/srp/templates/srp/data.html:165
msgid "Are you sure you want to delete SRP requests?" msgid "Are you sure you want to delete SRP requests?"
msgstr "老哥,你确定要删了补损请求么?" msgstr "老哥,你确定要删了补损请求么?"
@@ -1698,7 +1707,7 @@ msgstr "点击数值就可以编辑啦按回车确认按ESC取消"
msgid "Post Time" msgid "Post Time"
msgstr "发布时间" msgstr "发布时间"
#: allianceauth/srp/templates/srp/data.html:168 #: allianceauth/srp/templates/srp/data.html:174
msgid "No SRP requests for this fleet." msgid "No SRP requests for this fleet."
msgstr "这次起队没有补损请求!大捷" msgstr "这次起队没有补损请求!大捷"
@@ -2052,3 +2061,6 @@ msgstr "已经把%(system)s星系里%(time)s的时间节点设置好了CTA
#: allianceauth/timerboard/views.py:83 #: allianceauth/timerboard/views.py:83
msgid "Saved changes to the timer." msgid "Saved changes to the timer."
msgstr "保存至新的计划表" msgstr "保存至新的计划表"
#~ msgid "Portrait"
#~ msgstr "人物头像"

View File

@@ -1,22 +1,9 @@
default_app_config = 'allianceauth.notifications.apps.NotificationsConfig' 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 from .models import Notification
if Notification.objects.filter(user=user).count() > MAX_NOTIFICATIONS: Notification.objects.notify_user(user, title, message, level)
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)

View File

@@ -1,4 +1,35 @@
from django.contrib import admin from django.contrib import admin
from .models import Notification from .models import Notification
admin.site.register(Notification)
@admin.register(Notification)
class NotificationAdmin(admin.ModelAdmin):
list_display = ("timestamp", "_main", "_state", "title", "level", "viewed")
list_select_related = ("user", "user__profile__main_character", "user__profile__state")
list_filter = (
"level",
"timestamp",
"user__profile__state",
('user__profile__main_character', admin.RelatedOnlyFieldListFilter),
)
ordering = ("-timestamp", )
search_fields = ["user__username", "user__profile__main_character__character_name"]
def _main(self, obj):
try:
return obj.user.profile.main_character
except AttributeError:
return obj.user
_main.admin_order_field = "user__profile__main_character__character_name"
def _state(self, obj):
return obj.user.profile.state
_state.admin_order_field = "user__profile__state__name"
def has_change_permission(self, request, obj=None):
return False
def has_add_permission(self, request) -> bool:
return False

View File

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

View File

@@ -12,21 +12,20 @@ class NotificationHandler(logging.Handler):
try: try:
perm = Permission.objects.get(codename="logging_notifications") perm = Permission.objects.get(codename="logging_notifications")
message = record.getMessage()
if record.exc_text:
message += "\n\n"
message = message + record.exc_text
users = User.objects.filter(
Q(groups__permissions=perm) | Q(user_permissions=perm) | Q(is_superuser=True)).distinct()
for user in users:
notify(
user,
"%s [%s:%s]" % (record.levelname, record.funcName, record.lineno),
level=str([item[0] for item in Notification.LEVEL_CHOICES if item[1] == record.levelname][0]),
message=message
)
except Permission.DoesNotExist: except Permission.DoesNotExist:
pass return
message = record.getMessage()
if record.exc_text:
message += "\n\n"
message = message + record.exc_text
users = User.objects.filter(
Q(groups__permissions=perm) | Q(user_permissions=perm) | Q(is_superuser=True)).distinct()
for user in users:
notify(
user,
"%s [%s:%s]" % (record.levelname, record.funcName, record.lineno),
level=Notification.Level.from_old_name(record.levelname),
message=message
)

View File

@@ -0,0 +1,103 @@
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):
"""Override update to ensure cache is invalidated 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
if level not in self.model.Level:
level = self.model.Level.INFO
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

@@ -0,0 +1,18 @@
# Generated by Django 3.1.12 on 2021-07-01 21:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('notifications', '0004_performance_tuning'),
]
operations = [
migrations.AlterField(
model_name='notification',
name='level',
field=models.CharField(choices=[('danger', 'danger'), ('warning', 'warning'), ('info', 'info'), ('success', 'success')], default='info', max_length=10),
),
]

View File

@@ -1,36 +1,86 @@
import logging
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
import logging from django.utils.translation import gettext_lazy as _
from .managers import NotificationManager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Notification(models.Model): class Notification(models.Model):
LEVEL_CHOICES = ( """Notification to a user within Auth"""
('danger', 'CRITICAL'),
('danger', 'ERROR'), NOTIFICATIONS_MAX_PER_USER_DEFAULT = 50
('warning', 'WARN'), NOTIFICATIONS_REFRESH_TIME_DEFAULT = 30
('info', 'INFO'),
('success', 'DEBUG'), class Level(models.TextChoices):
) """A notification level."""
DANGER = 'danger', _('danger') #:
WARNING = 'warning', _('warning') #:
INFO = 'info', _('info') #:
SUCCESS = 'success', _('success') #:
@classmethod
def from_old_name(cls, name: str) -> object:
"""Map old name to enum.
Raises ValueError for invalid names.
"""
name_map = {
"CRITICAL": cls.DANGER,
"ERROR": cls.DANGER,
"WARN": cls.WARNING,
"INFO": cls.INFO,
"DEBUG": cls.SUCCESS,
}
try:
return name_map[name]
except KeyError:
raise ValueError(f"Unknown name: {name}") from None
# LEVEL_CHOICES = (
# ('danger', 'CRITICAL'),
# ('danger', 'ERROR'),
# ('warning', 'WARN'),
# ('info', 'INFO'),
# ('success', 'DEBUG'),
# )
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
level = models.CharField(choices=LEVEL_CHOICES, max_length=10) level = models.CharField(choices=Level.choices, max_length=10, default=Level.INFO)
title = models.CharField(max_length=254) title = models.CharField(max_length=254)
message = models.TextField() message = models.TextField()
timestamp = models.DateTimeField(auto_now_add=True) timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
viewed = models.BooleanField(default=False) 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) logger.info("Marking notification as viewed: %s" % self)
self.viewed = True self.viewed = True
self.save() self.save()
def __str__(self): def set_level(self, level_name: str) -> None:
return "%s: %s" % (self.user, self.title) """Set notification level according to old level name, e.g. 'CRITICAL'.
def set_level(self, level): Raises ValueError on invalid level names.
self.level = [item[0] for item in self.LEVEL_CHOICES if item[1] == level][0] """
self.level = self.Level.from_old_name(level_name)
class Meta: self.save()
ordering = ['-timestamp']

View File

@@ -0,0 +1,41 @@
"""Templatetags for notifications
These template tags are required to enable the notifications refresh functionality
in the browser.
"""
import logging
from django import template
from django.conf import settings
from django.contrib.auth.models import User
from allianceauth.notifications.models import Notification
logger = logging.getLogger(__name__)
register = template.Library()
@register.filter
def user_unread_notification_count(user: object) -> int:
"""returns the number of unread notifications for user
Will return -1 on error
"""
if not isinstance(user, User):
unread_count = -1
else:
unread_count = Notification.objects.user_unread_count(user.pk)
return unread_count
@register.simple_tag
def notifications_refresh_time() -> int:
refresh_time = getattr(settings, 'NOTIFICATIONS_REFRESH_TIME', Notification.NOTIFICATIONS_REFRESH_TIME_DEFAULT)
if (not isinstance(refresh_time, int) or refresh_time < 0):
logger.warning('NOTIFICATIONS_REFRESH_TIME setting is invalid. Using default.')
refresh_time = Notification.NOTIFICATIONS_REFRESH_TIME_DEFAULT
return refresh_time

View File

@@ -0,0 +1,69 @@
from logging import LogRecord, DEBUG
from django.contrib.auth.models import Permission, Group, User
from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils
from ..handlers import NotificationHandler
from ..models import Notification
MODULE_PATH = 'allianceauth.notifications.handlers'
class TestHandler(TestCase):
def test_do_nothing_if_permission_does_not_exist(self):
# given
Permission.objects.get(codename="logging_notifications").delete()
handler = NotificationHandler()
record = LogRecord(
name="name",
level=DEBUG,
pathname="pathname",
lineno=42,
msg="msg",
args=[],
exc_info=None,
func="func"
)
# when
handler.emit(record)
# then
self.assertEqual(Notification.objects.count(), 0)
def test_should_emit_message_to_users_with_permission_only(self):
# given
AuthUtils.create_user('Lex Luthor')
user_permission = AuthUtils.create_user('Bruce Wayne')
user_permission = AuthUtils.add_permission_to_user_by_name(
"auth.logging_notifications", user_permission
)
group = Group.objects.create(name="Dummy Group")
perm = Permission.objects.get(codename="logging_notifications")
group.permissions.add(perm)
user_group = AuthUtils.create_user('Peter Parker')
user_group.groups.add(group)
user_superuser = User.objects.create_superuser("Clark Kent")
handler = NotificationHandler()
record = LogRecord(
name="name",
level=DEBUG,
pathname="pathname",
lineno=42,
msg="msg",
args=[],
exc_info=None,
func="func"
)
# when
handler.emit(record)
# then
self.assertEqual(Notification.objects.count(), 3)
users = set(Notification.objects.values_list("user__pk", flat=True))
self.assertSetEqual(
users, {user_permission.pk, user_group.pk, user_superuser.pk}
)
notif = Notification.objects.first()
self.assertEqual(notif.user, user_permission)
self.assertEqual(notif.title, "DEBUG [func:42]")
self.assertEqual(notif.level, "success")
self.assertEqual(notif.message, "msg")

View File

@@ -0,0 +1,28 @@
from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils
from .. import notify
from ..models import Notification
MODULE_PATH = 'allianceauth.notifications'
class TestUserNotificationCount(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('magic_mike')
AuthUtils.add_main_character(
cls.user,
'Magic Mike',
'1',
corp_id='2',
corp_name='Pole Riders',
corp_ticker='PRIDE',
alliance_id='3',
alliance_name='RIDERS'
)
def test_can_notify(self):
notify(self.user, 'dummy')
self.assertEqual(Notification.objects.filter(user=self.user).count(), 1)

View File

@@ -0,0 +1,235 @@
from unittest.mock import patch
from django.contrib.auth.models import User
from django.test import TestCase, override_settings
from allianceauth.tests.auth_utils import AuthUtils
from ..models import Notification
MODULE_PATH = 'allianceauth.notifications.models'
NOTIFICATIONS_MAX_PER_USER_DEFAULT = 42
class TestQuerySet(TestCase):
@classmethod
def setUpTestData(cls):
cls.user_1 = AuthUtils.create_user('Peter Parker')
cls.user_2 = AuthUtils.create_user('Clark Kent')
@patch(MODULE_PATH + '.Notification.objects.invalidate_user_notification_cache')
def test_update_will_invalidate_cache(
self, mock_invalidate_user_notification_cache
):
Notification.objects.notify_user(self.user_1, 'dummy_1')
Notification.objects.notify_user(self.user_2, 'dummy_2')
Notification.objects.update(viewed=True)
self.assertEquals(mock_invalidate_user_notification_cache.call_count, 2)
class TestUserNotify(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('magic_mike')
AuthUtils.add_main_character(
cls.user,
'Magic Mike',
'1',
corp_id='2',
corp_name='Pole Riders',
corp_ticker='PRIDE',
alliance_id='3',
alliance_name='RIDERS'
)
def test_can_notify(self):
title = 'dummy_title'
message = 'dummy message'
level = 'danger'
Notification.objects.notify_user(self.user, title, message, level)
self.assertEqual(Notification.objects.filter(user=self.user).count(), 1)
obj = Notification.objects.first()
self.assertEqual(obj.user, self.user)
self.assertEqual(obj.title, title)
self.assertEqual(obj.message, message)
self.assertEqual(obj.level, level)
def test_use_message_as_title_if_missing(self):
title = 'dummy_title'
Notification.objects.notify_user(self.user, title)
self.assertEqual(Notification.objects.filter(user=self.user).count(), 1)
obj = Notification.objects.first()
self.assertEqual(obj.user, self.user)
self.assertEqual(obj.title, title)
self.assertEqual(obj.message, title)
def test_should_use_default_level_when_not_specified(self):
# given
title = 'dummy_title'
message = 'dummy message'
# when
Notification.objects.notify_user(self.user, title, message)
# then
self.assertEqual(Notification.objects.filter(user=self.user).count(), 1)
obj = Notification.objects.first()
self.assertEqual(obj.user, self.user)
self.assertEqual(obj.title, title)
self.assertEqual(obj.message, message)
self.assertEqual(obj.level, Notification.Level.INFO)
def test_should_use_default_level_when_invalid_level_given(self):
# given
title = 'dummy_title'
message = 'dummy message'
level = "invalid"
# when
Notification.objects.notify_user(self.user, title, message, level)
# then
self.assertEqual(Notification.objects.filter(user=self.user).count(), 1)
obj = Notification.objects.first()
self.assertEqual(obj.user, self.user)
self.assertEqual(obj.title, title)
self.assertEqual(obj.message, message)
self.assertEqual(obj.level, Notification.Level.INFO)
@override_settings(NOTIFICATIONS_MAX_PER_USER=3)
def test_remove_when_too_many_notifications(self):
Notification.objects.notify_user(self.user, 'dummy')
obj_2 = Notification.objects.notify_user(self.user, 'dummy')
obj_3 = Notification.objects.notify_user(self.user, 'dummy')
obj_4 = Notification.objects.notify_user(self.user, 'dummy')
expected = {obj_2.pk, obj_3.pk, obj_4.pk}
result = set(
Notification.objects.filter(user=self.user).values_list("pk", flat=True)
)
self.assertSetEqual(result, expected)
obj_5 = Notification.objects.notify_user(self.user, 'dummy')
expected = {obj_3.pk, obj_4.pk, obj_5.pk}
result = set(
Notification.objects.filter(user=self.user).values_list("pk", flat=True)
)
self.assertSetEqual(result, expected)
@patch(
MODULE_PATH + '.Notification.NOTIFICATIONS_MAX_PER_USER_DEFAULT',
NOTIFICATIONS_MAX_PER_USER_DEFAULT
)
class TestMaxNotificationsPerUser(TestCase):
@override_settings(NOTIFICATIONS_MAX_PER_USER=None)
def test_reset_to_default_if_not_defined(self):
result = Notification.objects._max_notifications_per_user()
expected = NOTIFICATIONS_MAX_PER_USER_DEFAULT
self.assertEquals(result, expected)
@override_settings(NOTIFICATIONS_MAX_PER_USER='11')
def test_reset_to_default_if_not_int(self):
result = Notification.objects._max_notifications_per_user()
expected = NOTIFICATIONS_MAX_PER_USER_DEFAULT
self.assertEquals(result, expected)
@override_settings(NOTIFICATIONS_MAX_PER_USER=-1)
def test_reset_to_default_if_lt_zero(self):
result = Notification.objects._max_notifications_per_user()
expected = NOTIFICATIONS_MAX_PER_USER_DEFAULT
self.assertEquals(result, expected)
@patch('allianceauth.notifications.managers.cache')
class TestUnreadCount(TestCase):
@classmethod
def setUpTestData(cls):
cls.user_1 = AuthUtils.create_user('magic_mike')
AuthUtils.add_main_character(
cls.user_1,
'Magic Mike',
'1',
corp_id='2',
corp_name='Pole Riders',
corp_ticker='PRIDE',
alliance_id='3',
alliance_name='RIDERS'
)
# test notifications for mike
Notification.objects.all().delete()
Notification.objects.create(
user=cls.user_1,
level="INFO",
title="Job 1 Failed",
message="Because it was broken",
viewed=True
)
Notification.objects.create(
user=cls.user_1,
level="INFO",
title="Job 2 Failed",
message="Because it was broken"
)
Notification.objects.create(
user=cls.user_1,
level="INFO",
title="Job 3 Failed",
message="Because it was broken"
)
cls.user_2 = AuthUtils.create_user('teh_kid')
AuthUtils.add_main_character(
cls.user_2,
'The Kid', '2',
corp_id='2',
corp_name='Pole Riders',
corp_ticker='PRIDE',
alliance_id='3',
alliance_name='RIDERS'
)
# Notifications for kid
Notification.objects.create(
user=cls.user_2,
level="INFO",
title="Job 6 Failed",
message="Because it was broken"
)
def test_update_cache_when_not_in_cache(self, mock_cache):
mock_cache.get.return_value = None
result = Notification.objects.user_unread_count(self.user_1.pk)
expected = 2
self.assertEqual(result, expected)
self.assertTrue(mock_cache.set.called)
args, kwargs = mock_cache.set.call_args
self.assertEqual(
kwargs['key'],
Notification.objects._user_notification_cache_key(self.user_1.pk)
)
self.assertEqual(kwargs['value'], expected)
def test_return_from_cache_when_in_cache(self, mock_cache):
mock_cache.get.return_value = 42
result = Notification.objects.user_unread_count(self.user_1.pk)
expected = 42
self.assertEqual(result, expected)
self.assertFalse(mock_cache.set.called)
def test_return_error_code_when_user_not_found(self, mock_cache):
mock_cache.get.return_value = None
invalid_user_id = max([user.pk for user in User.objects.all()]) + 1
result = Notification.objects.user_unread_count(invalid_user_id)
expected = -1
self.assertEqual(result, expected)
self.assertFalse(mock_cache.set.called)
def test_can_invalidate_cache(self, mock_cache):
Notification.objects.invalidate_user_notification_cache(self.user_1.pk)
self.assertTrue(mock_cache.delete)
args, kwargs = mock_cache.delete.call_args
self.assertEqual(
kwargs['key'],
Notification.objects._user_notification_cache_key(self.user_1.pk)
)

View File

@@ -0,0 +1,73 @@
from unittest.mock import patch
from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils
from ..models import Notification
MODULE_PATH = 'allianceauth.notifications.models'
class TestUserNotify(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('magic_mike')
AuthUtils.add_main_character(
cls.user,
'Magic Mike',
'1',
corp_id='2',
corp_name='Pole Riders',
corp_ticker='PRIDE',
alliance_id='3',
alliance_name='RIDERS'
)
@patch(MODULE_PATH + '.Notification.objects.invalidate_user_notification_cache')
def test_save_will_invalidate_cache(self, mock_invalidate_user_notification_cache):
obj = Notification.objects.notify_user(self.user, 'dummy')
self.assertTrue(Notification.objects.filter(pk=obj.pk).exists())
self.assertEquals(mock_invalidate_user_notification_cache.call_count, 1)
@patch(MODULE_PATH + '.Notification.objects.invalidate_user_notification_cache')
def test_delete_will_invalidate_cache(
self, mock_invalidate_user_notification_cache
):
obj = Notification.objects.notify_user(self.user, 'dummy')
obj.delete()
self.assertFalse(Notification.objects.filter(pk=obj.pk).exists())
self.assertEquals(mock_invalidate_user_notification_cache.call_count, 2)
def test_can_view(self):
obj = Notification.objects.notify_user(self.user, 'dummy')
self.assertFalse(obj.viewed)
obj.mark_viewed()
obj.refresh_from_db()
self.assertTrue(obj.viewed)
def test_can_set_level(self):
obj = Notification.objects.notify_user(self.user, 'dummy', level='info')
obj.set_level('ERROR')
obj.refresh_from_db()
self.assertEqual(obj.level, 'danger')
obj.set_level('CRITICAL')
obj.refresh_from_db()
self.assertEqual(obj.level, 'danger')
obj.set_level('WARN')
obj.refresh_from_db()
self.assertEqual(obj.level, 'warning')
obj.set_level('INFO')
obj.refresh_from_db()
self.assertEqual(obj.level, 'info')
obj.set_level('DEBUG')
obj.refresh_from_db()
self.assertEqual(obj.level, 'success')
with self.assertRaises(ValueError):
obj.set_level('XXX')

View File

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

View File

@@ -0,0 +1,86 @@
from unittest.mock import patch, Mock
from django.test import TestCase, override_settings
from allianceauth.tests.auth_utils import AuthUtils
from ..templatetags.auth_notifications import (
user_unread_notification_count, notifications_refresh_time
)
MODULE_PATH = 'allianceauth.notifications.templatetags.auth_notifications'
NOTIFICATIONS_REFRESH_TIME_DEFAULT = 66
MY_NOTIFICATIONS_REFRESH_TIME = 23
@patch(MODULE_PATH + '.Notification.objects.user_unread_count')
class TestUserNotificationCount(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('magic_mike')
AuthUtils.add_main_character(
cls.user,
'Magic Mike',
'1',
corp_id='2',
corp_name='Pole Riders',
corp_ticker='PRIDE',
alliance_id='3',
alliance_name='RIDERS'
)
def test_return_normal(self, mock_user_unread_count):
unread_count = 42
mock_user_unread_count.return_value = unread_count
result = user_unread_notification_count(self.user)
expected = unread_count
self.assertEqual(result, expected)
args, kwargs = mock_user_unread_count.call_args
self.assertEqual(args[0], self.user.pk)
def test_return_error_if_non_user(self, mock_user_unread_count):
unread_count = -1
mock_user_unread_count.return_value = unread_count
result = user_unread_notification_count('invalid')
expected = unread_count
self.assertEqual(result, expected)
@patch(
MODULE_PATH + '.Notification.NOTIFICATIONS_REFRESH_TIME_DEFAULT',
NOTIFICATIONS_REFRESH_TIME_DEFAULT
)
class TestNotificationsRefreshTime(TestCase):
@override_settings(NOTIFICATIONS_REFRESH_TIME=MY_NOTIFICATIONS_REFRESH_TIME)
def test_return_from_setting(self):
result = notifications_refresh_time()
expected = MY_NOTIFICATIONS_REFRESH_TIME
self.assertEqual(result, expected)
@override_settings(NOTIFICATIONS_REFRESH_TIME=0)
def test_refresh_time_can_be_zero(self):
result = notifications_refresh_time()
expected = 0
self.assertEqual(result, expected)
@override_settings(NOTIFICATIONS_REFRESH_TIME=None)
def test_return_default_refresh_time_if_not_exists(self):
result = notifications_refresh_time()
expected = NOTIFICATIONS_REFRESH_TIME_DEFAULT
self.assertEqual(result, expected)
@override_settings(NOTIFICATIONS_REFRESH_TIME='33')
def test_return_default_refresh_time_if_not_int(self):
result = notifications_refresh_time()
expected = NOTIFICATIONS_REFRESH_TIME_DEFAULT
self.assertEqual(result, expected)
@override_settings(NOTIFICATIONS_REFRESH_TIME=-1)
def test_return_default_refresh_time_if_lt_0(self):
result = notifications_refresh_time()
expected = NOTIFICATIONS_REFRESH_TIME_DEFAULT
self.assertEqual(result, expected)

View File

@@ -0,0 +1,49 @@
import json
from unittest.mock import patch, Mock
from django.test import TestCase, RequestFactory
from django.urls import reverse
from allianceauth.tests.auth_utils import AuthUtils
from ..views import user_notifications_count
MODULE_PATH = 'allianceauth.notifications.views'
class TestViews(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('magic_mike')
AuthUtils.add_main_character(
cls.user,
'Magic Mike',
'1',
corp_id='2',
corp_name='Pole Riders',
corp_ticker='PRIDE',
alliance_id='3',
alliance_name='RIDERS'
)
cls.factory = RequestFactory()
@patch(MODULE_PATH + '.Notification.objects.user_unread_count')
def test_user_notifications_count(self, mock_user_unread_count):
unread_count = 42
user_pk = 3
mock_user_unread_count.return_value = unread_count
request = self.factory.get(
reverse('notifications:user_notifications_count', args=[user_pk])
)
request.user = self.user
response = user_notifications_count(request, user_pk)
self.assertEqual(response.status_code, 200)
self.assertTrue(mock_user_unread_count.called)
expected = {'unread_count': unread_count}
result = json.loads(response.content.decode(response.charset))
self.assertDictEqual(result, expected)

View File

@@ -9,4 +9,9 @@ urlpatterns = [
url(r'^notifications/delete_all_read/$', views.delete_all_read, name='delete_all_read'), url(r'^notifications/delete_all_read/$', views.delete_all_read, name='delete_all_read'),
url(r'^notifications/$', views.notification_list, name='list'), url(r'^notifications/$', views.notification_list, name='list'),
url(r'^notifications/(\w+)/$', views.notification_view, name='view'), url(r'^notifications/(\w+)/$', views.notification_view, name='view'),
url(
r'^user_notifications_count/(?P<user_pk>\d+)/$',
views.user_notifications_count,
name='user_notifications_count'
),
] ]

View File

@@ -1,9 +1,12 @@
from django.shortcuts import render, get_object_or_404, redirect import logging
from .models import Notification
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib import messages from django.contrib import messages
from django.utils.translation import ugettext_lazy as _ from django.http import JsonResponse
import logging from django.shortcuts import render, get_object_or_404, redirect
from django.utils.translation import gettext_lazy as _
from .models import Notification
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -11,9 +14,15 @@ logger = logging.getLogger(__name__)
@login_required @login_required
def notification_list(request): def notification_list(request):
logger.debug("notification_list called by user %s" % request.user) logger.debug("notification_list called by user %s" % request.user)
new_notifs = Notification.objects.filter(user=request.user).filter(viewed=False) notifications_qs = Notification.objects.filter(user=request.user).order_by("-timestamp")
old_notifs = Notification.objects.filter(user=request.user).filter(viewed=True) new_notifs = notifications_qs.filter(viewed=False)
logger.debug("User %s has %s unread and %s read notifications" % (request.user, len(new_notifs), len(old_notifs))) old_notifs = notifications_qs.filter(viewed=True)
logger.debug(
"User %s has %s unread and %s read notifications",
request.user,
len(new_notifs),
len(old_notifs)
)
context = { context = {
'read': old_notifs, 'read': old_notifs,
'unread': new_notifs, 'unread': new_notifs,
@@ -23,39 +32,53 @@ def notification_list(request):
@login_required @login_required
def notification_view(request, notif_id): def notification_view(request, notif_id):
logger.debug("notification_view called by user %s for notif_id %s" % (request.user, notif_id)) logger.debug(
"notification_view called by user %s for notif_id %s",
request.user,
notif_id
)
notif = get_object_or_404(Notification, pk=notif_id) notif = get_object_or_404(Notification, pk=notif_id)
if notif.user == request.user: if notif.user == request.user:
logger.debug("Providing notification for user %s" % request.user) logger.debug("Providing notification for user %s", request.user)
context = {'notif': notif} context = {'notif': notif}
notif.view() notif.mark_viewed()
return render(request, 'notifications/view.html', context) return render(request, 'notifications/view.html', context)
else: else:
logger.warn( logger.warn(
"User %s not authorized to view notif_id %s belonging to user %s" % (request.user, notif_id, notif.user)) "User %s not authorized to view notif_id %s belonging to user %s",
request.user,
notif_id, notif.user
)
messages.error(request, _('You are not authorized to view that notification.')) messages.error(request, _('You are not authorized to view that notification.'))
return redirect('notifications:list') return redirect('notifications:list')
@login_required @login_required
def remove_notification(request, notif_id): def remove_notification(request, notif_id):
logger.debug("remove notification called by user %s for notif_id %s" % (request.user, notif_id)) logger.debug(
"remove notification called by user %s for notif_id %s",
request.user,
notif_id
)
notif = get_object_or_404(Notification, pk=notif_id) notif = get_object_or_404(Notification, pk=notif_id)
if notif.user == request.user: if notif.user == request.user:
if Notification.objects.filter(id=notif_id).exists(): if Notification.objects.filter(id=notif_id).exists():
notif.delete() notif.delete()
logger.info("Deleting notif id %s by user %s" % (notif_id, request.user)) logger.info("Deleting notif id %s by user %s", notif_id, request.user)
messages.success(request, _('Deleted notification.')) messages.success(request, _('Deleted notification.'))
else: else:
logger.error( logger.error(
"Unable to delete notif id %s for user %s - notif matching id not found." % (notif_id, request.user)) "Unable to delete notif id %s for user %s - notif matching id not found.",
notif_id,
request.user
)
messages.error(request, _('Failed to locate notification.')) messages.error(request, _('Failed to locate notification.'))
return redirect('notifications:list') return redirect('notifications:list')
@login_required @login_required
def mark_all_read(request): def mark_all_read(request):
logger.debug('mark all notifications read called by user %s' % request.user) logger.debug('mark all notifications read called by user %s', request.user)
Notification.objects.filter(user=request.user).update(viewed=True) Notification.objects.filter(user=request.user).update(viewed=True)
messages.success(request, _('Marked all notifications as read.')) messages.success(request, _('Marked all notifications as read.'))
return redirect('notifications:list') return redirect('notifications:list')
@@ -63,7 +86,17 @@ def mark_all_read(request):
@login_required @login_required
def delete_all_read(request): def delete_all_read(request):
logger.debug('delete all read notifications called by user %s' % request.user) logger.debug('delete all read notifications called by user %s', request.user)
Notification.objects.filter(user=request.user).filter(viewed=True).delete() Notification.objects.filter(user=request.user).filter(viewed=True).delete()
messages.success(request, _('Deleted all read notifications.')) messages.success(request, _('Deleted all read notifications.'))
return redirect('notifications:list') return redirect('notifications:list')
def user_notifications_count(request, user_pk: int):
"""returns to notifications count for the give user as JSON
This view is public and does not require login
"""
unread_count = Notification.objects.user_unread_count(user_pk)
data = {'unread_count': unread_count}
return JsonResponse(data, safe=False)

View File

@@ -1,5 +1,6 @@
import os import os
from celery import Celery from celery import Celery
from celery.app import trace
# set the default Django settings module for the 'celery' program. # set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{{ project_name }}.settings.local') os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{{ project_name }}.settings.local')
@@ -27,3 +28,6 @@ app.conf.ONCE = {
# Load task modules from all registered Django app configs. # Load task modules from all registered Django app configs.
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
# Remove result from default log message on task success
trace.LOG_SUCCESS = "Task %(name)s[%(id)s] succeeded in %(runtime)ss"

View File

@@ -64,12 +64,12 @@ BASE_DIR = os.path.dirname(PROJECT_DIR)
MIDDLEWARE = [ MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
] ]
ROOT_URLCONF = 'allianceauth.urls' ROOT_URLCONF = 'allianceauth.urls'
@@ -86,6 +86,8 @@ LANGUAGES = (
('zh-hans', ugettext('Chinese Simplified')), ('zh-hans', ugettext('Chinese Simplified')),
('ru', ugettext('Russian')), ('ru', ugettext('Russian')),
('ko', ugettext('Korean')), ('ko', ugettext('Korean')),
('fr', ugettext('French')),
('ja', ugettext('Japanese')),
) )
TEMPLATES = [ TEMPLATES = [
@@ -103,7 +105,6 @@ TEMPLATES = [
'django.template.context_processors.media', 'django.template.context_processors.media',
'django.template.context_processors.static', 'django.template.context_processors.static',
'django.template.context_processors.tz', 'django.template.context_processors.tz',
'allianceauth.notifications.context_processors.user_notification_count',
'allianceauth.context_processors.auth_settings', 'allianceauth.context_processors.auth_settings',
], ],
}, },
@@ -138,6 +139,8 @@ AUTHENTICATION_BACKENDS = ['allianceauth.authentication.backends.StateBackend',
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'en-us'
LANGUAGE_COOKIE_AGE = 1209600
TIME_ZONE = 'UTC' TIME_ZONE = 'UTC'
USE_I18N = True USE_I18N = True
@@ -169,6 +172,8 @@ CACHES = {
} }
} }
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
DEBUG = True DEBUG = True
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = ['*']
DATABASES = { DATABASES = {

View File

@@ -45,6 +45,7 @@ DATABASES['default'] = {
ESI_SSO_CLIENT_ID = '' ESI_SSO_CLIENT_ID = ''
ESI_SSO_CLIENT_SECRET = '' ESI_SSO_CLIENT_SECRET = ''
ESI_SSO_CALLBACK_URL = '' ESI_SSO_CALLBACK_URL = ''
ESI_USER_CONTACT_EMAIL = '' # A server maintainer that CCP can contact in case of issues.
# By default emails are validated before new users can log in. # By default emails are validated before new users can log in.
# It's recommended to use a free service like SparkPost or Elastic Email to send email. # It's recommended to use a free service like SparkPost or Elastic Email to send email.

View File

@@ -83,10 +83,31 @@ class DiscordUserManager(models.Manager):
if created is not False: if created is not False:
if created is None: if created is None:
logger.debug( logger.debug(
"User %s with Discord ID %s is already a member.", "User %s with Discord ID %s is already a member. Forcing a Refresh",
user, user,
user_id, user_id,
) )
# Force an update cause the discord API won't do it for us.
if role_ids:
role_ids = list(role_ids)
updated = bot_client.modify_guild_member(
guild_id=DISCORD_GUILD_ID,
user_id=user_id,
role_ids=role_ids,
nick=nickname
)
if not updated:
# Could not update the new user so fail.
logger.warning(
"Failed to add user %s with Discord ID %s to Discord server",
user,
user_id,
)
return False
self.update_or_create( self.update_or_create(
user=user, user=user,
defaults={ defaults={

View File

@@ -224,7 +224,7 @@ class DiscordUser(models.Model):
user=_user, user=_user,
title=gettext_lazy('Discord Account Disabled'), title=gettext_lazy('Discord Account Disabled'),
message=gettext_lazy( message=gettext_lazy(
'Your Discord account was disabeled automatically ' 'Your Discord account was disabled automatically '
'by Auth. If you think this was a mistake, ' 'by Auth. If you think this was a mistake, '
'please contact an admin.' 'please contact an admin.'
), ),

View File

@@ -111,6 +111,40 @@ class TestAddUser(TestCase):
self.assertSetEqual(set(kwargs['role_ids']), {1, 2, 3}) self.assertSetEqual(set(kwargs['role_ids']), {1, 2, 3})
self.assertIsNone(kwargs['nick']) self.assertIsNone(kwargs['nick'])
def test_can_activate_existing_user_with_roles_no_nick(
self,
mock_user_formatted_nick,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_DiscordClient
):
roles = [
create_matched_role(ROLE_ALPHA),
create_matched_role(ROLE_BRAVO),
create_matched_role(ROLE_CHARLIE)
]
mock_user_formatted_nick.return_value = None
mock_user_group_names.return_value = ['a', 'b', 'c']
mock_exchange_auth_code_for_token.return_value = self.access_token
mock_DiscordClient.return_value.current_user.return_value = self.user_info
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = roles
mock_DiscordClient.return_value.add_guild_member.return_value = None
mock_DiscordClient.return_value.modify_guild_member.return_value = True
result = DiscordUser.objects.add_user(self.user, authorization_code='abcdef')
self.assertTrue(result)
self.assertTrue(
DiscordUser.objects.filter(user=self.user, uid=TEST_USER_ID).exists()
)
self.assertTrue(mock_DiscordClient.return_value.add_guild_member.called)
self.assertTrue(mock_DiscordClient.return_value.modify_guild_member.called)
args, kwargs = mock_DiscordClient.return_value.modify_guild_member.call_args
self.assertEqual(kwargs['guild_id'], TEST_GUILD_ID)
self.assertEqual(kwargs['user_id'], TEST_USER_ID)
self.assertSetEqual(set(kwargs['role_ids']), {1, 2, 3})
self.assertIsNone(kwargs['nick'])
@patch(MODULE_PATH + '.managers.DISCORD_SYNC_NAMES', True) @patch(MODULE_PATH + '.managers.DISCORD_SYNC_NAMES', True)
def test_can_create_user_no_roles_with_nick( def test_can_create_user_no_roles_with_nick(
self, self,
@@ -140,6 +174,36 @@ class TestAddUser(TestCase):
self.assertIsNone(kwargs['role_ids']) self.assertIsNone(kwargs['role_ids'])
self.assertEqual(kwargs['nick'], TEST_MAIN_NAME) self.assertEqual(kwargs['nick'], TEST_MAIN_NAME)
@patch(MODULE_PATH + '.managers.DISCORD_SYNC_NAMES', True)
def test_can_activate_existing_user_no_roles_with_nick(
self,
mock_user_formatted_nick,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_DiscordClient
):
mock_user_formatted_nick.return_value = TEST_MAIN_NAME
mock_user_group_names.return_value = []
mock_exchange_auth_code_for_token.return_value = self.access_token
mock_DiscordClient.return_value.current_user.return_value = self.user_info
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = []
mock_DiscordClient.return_value.add_guild_member.return_value = None
mock_DiscordClient.return_value.modify_guild_member.return_value = True
result = DiscordUser.objects.add_user(self.user, authorization_code='abcdef')
self.assertTrue(result)
self.assertTrue(
DiscordUser.objects.filter(user=self.user, uid=TEST_USER_ID).exists()
)
self.assertTrue(mock_DiscordClient.return_value.add_guild_member.called)
self.assertTrue(mock_DiscordClient.return_value.modify_guild_member.called)
args, kwargs = mock_DiscordClient.return_value.modify_guild_member.call_args
self.assertEqual(kwargs['guild_id'], TEST_GUILD_ID)
self.assertEqual(kwargs['user_id'], TEST_USER_ID)
self.assertIsNone(kwargs['role_ids'])
self.assertEqual(kwargs['nick'], TEST_MAIN_NAME)
@patch(MODULE_PATH + '.managers.DISCORD_SYNC_NAMES', False) @patch(MODULE_PATH + '.managers.DISCORD_SYNC_NAMES', False)
def test_can_create_user_no_roles_and_without_nick_if_turned_off( def test_can_create_user_no_roles_and_without_nick_if_turned_off(
self, self,
@@ -183,6 +247,7 @@ class TestAddUser(TestCase):
mock_DiscordClient.return_value.match_or_create_roles_from_names\ mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = [] .return_value = []
mock_DiscordClient.return_value.add_guild_member.return_value = None mock_DiscordClient.return_value.add_guild_member.return_value = None
mock_DiscordClient.return_value.modify_guild_member.return_value = True
result = DiscordUser.objects.add_user(self.user, authorization_code='abcdef') result = DiscordUser.objects.add_user(self.user, authorization_code='abcdef')
self.assertTrue(result) self.assertTrue(result)
@@ -190,6 +255,31 @@ class TestAddUser(TestCase):
DiscordUser.objects.filter(user=self.user, uid=TEST_USER_ID).exists() DiscordUser.objects.filter(user=self.user, uid=TEST_USER_ID).exists()
) )
self.assertTrue(mock_DiscordClient.return_value.add_guild_member.called) self.assertTrue(mock_DiscordClient.return_value.add_guild_member.called)
self.assertTrue(mock_DiscordClient.return_value.modify_guild_member.called)
def test_can_activate_existing_guild_member_failure(
self,
mock_user_formatted_nick,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_DiscordClient
):
mock_user_formatted_nick.return_value = None
mock_user_group_names.return_value = []
mock_exchange_auth_code_for_token.return_value = self.access_token
mock_DiscordClient.return_value.current_user.return_value = self.user_info
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = []
mock_DiscordClient.return_value.add_guild_member.return_value = None
mock_DiscordClient.return_value.modify_guild_member.return_value = False
result = DiscordUser.objects.add_user(self.user, authorization_code='abcdef')
self.assertFalse(result)
self.assertFalse(
DiscordUser.objects.filter(user=self.user, uid=TEST_USER_ID).exists()
)
self.assertTrue(mock_DiscordClient.return_value.add_guild_member.called)
self.assertTrue(mock_DiscordClient.return_value.modify_guild_member.called)
def test_return_false_when_user_creation_fails( def test_return_false_when_user_creation_fails(
self, self,

View File

@@ -0,0 +1,70 @@
# Generated by Django 3.1.2 on 2021-01-08 13:54
from django.conf import settings
from django.db import migrations, models
import django.db.migrations.operations.special
import django.db.models.deletion
class Migration(migrations.Migration):
replaces = [('mumble', '0001_initial'), ('mumble', '0002_auto_20161212_0100'), ('mumble', '0003_mumbleuser_user'), ('mumble', '0004_auto_20161214_1024'), ('mumble', '0005_mumbleuser_hashfn'), ('mumble', '0006_service_permissions'), ('mumble', '0007_not_null_user'), ('mumble', '0008_mumbleuser_display_name'), ('mumble', '0009_set_mumble_dissplay_names'), ('mumble', '0010_mumbleuser_certhash'), ('mumble', '0011_auto_20201011_1009')]
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='MumbleUser',
fields=[
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='mumble', serialize=False, to=settings.AUTH_USER_MODEL)),
('username', models.CharField(max_length=254, unique=True)),
('pwhash', models.CharField(max_length=40)),
('groups', models.TextField(blank=True, null=True)),
],
options={
'db_table': 'services_mumbleuser',
},
),
migrations.AlterModelTable(
name='mumbleuser',
table=None,
),
migrations.AddField(
model_name='mumbleuser',
name='hashfn',
field=models.CharField(default='sha1', max_length=20),
),
migrations.AlterField(
model_name='mumbleuser',
name='pwhash',
field=models.CharField(max_length=80),
),
migrations.AlterModelOptions(
name='mumbleuser',
options={'permissions': (('access_mumble', 'Can access the Mumble service'),)},
),
migrations.AddField(
model_name='mumbleuser',
name='display_name',
field=models.CharField(max_length=254, null=True),
),
migrations.AlterField(
model_name='mumbleuser',
name='display_name',
field=models.CharField(max_length=254, unique=True),
preserve_default=False,
),
migrations.AddField(
model_name='mumbleuser',
name='certhash',
field=models.CharField(blank=True, editable=False, help_text='Hash of Mumble client certificate as presented when user authenticates', max_length=254, null=True, verbose_name='Certificate Hash'),
),
migrations.AlterField(
model_name='mumbleuser',
name='pwhash',
field=models.CharField(max_length=90),
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 3.1.2 on 2020-10-11 10:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mumble', '0010_mumbleuser_certhash'),
]
operations = [
migrations.AlterField(
model_name='mumbleuser',
name='pwhash',
field=models.CharField(max_length=90),
),
]

View File

@@ -0,0 +1,33 @@
# Generated by Django 3.1.6 on 2021-03-23 13:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mumble', '0001_squashed_0011_auto_20201011_1009'),
]
operations = [
migrations.AddField(
model_name='mumbleuser',
name='last_connect',
field=models.DateTimeField(blank=True, editable=False, help_text='Timestamp of the users Last Connection to Mumble', max_length=254, null=True, verbose_name='Last Connection'),
),
migrations.AddField(
model_name='mumbleuser',
name='last_disconnect',
field=models.DateTimeField(blank=True, editable=False, help_text='Timestamp of the users Last Disconnection to Mumble', max_length=254, null=True, verbose_name='Last Disconnection'),
),
migrations.AddField(
model_name='mumbleuser',
name='release',
field=models.TextField(blank=True, editable=False, help_text='The Mumble Release the user last authenticated with', max_length=254, null=True, verbose_name='Mumble Release'),
),
migrations.AddField(
model_name='mumbleuser',
name='version',
field=models.IntegerField(blank=True, editable=False, help_text='Client version. Major version in upper 16 bits, followed by 8 bits of minor version and 8 bits of patchlevel. Version 1.2.3 = 0x010203.', null=True, verbose_name='Mumble Version'),
),
]

View File

@@ -63,7 +63,7 @@ class MumbleManager(models.Manager):
class MumbleUser(AbstractServiceModel): class MumbleUser(AbstractServiceModel):
username = models.CharField(max_length=254, unique=True) username = models.CharField(max_length=254, unique=True)
pwhash = models.CharField(max_length=80) pwhash = models.CharField(max_length=90)
hashfn = models.CharField(max_length=20, default='sha1') hashfn = models.CharField(max_length=20, default='sha1')
groups = models.TextField(blank=True, null=True) groups = models.TextField(blank=True, null=True)
certhash = models.CharField( certhash = models.CharField(
@@ -74,7 +74,41 @@ class MumbleUser(AbstractServiceModel):
editable=False, editable=False,
help_text="Hash of Mumble client certificate as presented when user authenticates" help_text="Hash of Mumble client certificate as presented when user authenticates"
) )
display_name = models.CharField(max_length=254, unique=True) display_name = models.CharField(
max_length=254,
unique=True
)
release = models.TextField(
verbose_name="Mumble Release",
max_length=254,
blank=True,
null=True,
editable=False,
help_text="The Mumble Release the user last authenticated with"
)
version = models.IntegerField(
verbose_name="Mumble Version",
blank=True,
null=True,
editable=False,
help_text="Client version. Major version in upper 16 bits, followed by 8 bits of minor version and 8 bits of patchlevel. Version 1.2.3 = 0x010203."
)
last_connect = models.DateTimeField(
verbose_name="Last Connection",
max_length=254,
blank=True,
null=True,
editable=False,
help_text="Timestamp of the users Last Connection to Mumble"
)
last_disconnect = models.DateTimeField(
verbose_name="Last Disconnection",
max_length=254,
blank=True,
null=True,
editable=False,
help_text="Timestamp of the users Last Disconnection to Mumble"
)
objects = MumbleManager() objects = MumbleManager()

View File

@@ -211,4 +211,4 @@ class MumbleManagerTestCase(TestCase):
pwhash = self.manager.gen_pwhash('test') pwhash = self.manager.gen_pwhash('test')
self.assertEqual(pwhash[:15], '$bcrypt-sha256$') self.assertEqual(pwhash[:15], '$bcrypt-sha256$')
self.assertEqual(len(pwhash), 75) self.assertEqual(len(pwhash), 83)

View File

@@ -15,6 +15,7 @@ class Teamspeak3UserAdmin(ServicesUserAdmin):
@admin.register(AuthTS) @admin.register(AuthTS)
class AuthTSgroupAdmin(admin.ModelAdmin): class AuthTSgroupAdmin(admin.ModelAdmin):
change_list_template = 'admin/teamspeak3/authts/change_list.html'
ordering = ('auth_group__name', ) ordering = ('auth_group__name', )
list_select_related = True list_select_related = True
@@ -28,7 +29,7 @@ class AuthTSgroupAdmin(admin.ModelAdmin):
return [x for x in obj.ts_group.all().order_by('ts_group_id')] return [x for x in obj.ts_group.all().order_by('ts_group_id')]
_ts_group.short_description = 'ts groups' _ts_group.short_description = 'ts groups'
#_ts_group.admin_order_field = 'profile__state' # _ts_group.admin_order_field = 'profile__state'
@admin.register(StateGroup) @admin.register(StateGroup)

View File

@@ -0,0 +1,11 @@
{% extends "admin/change_list.html" %}
{% load i18n static %}
{% block object-tools-items %}
{{ block.super }}
<li>
<a href="{% url 'teamspeak3:admin_update_ts3_groups' %}" class="btn btn-high">
Update TS3 groups
</a>
</li>
{% endblock %}

View File

@@ -216,6 +216,24 @@ class Teamspeak3ViewsTestCase(TestCase):
self.assertEqual(ts3_user.perm_key, '123abc') self.assertEqual(ts3_user.perm_key, '123abc')
self.assertTrue(tasks_manager.return_value.__enter__.return_value.update_groups.called) self.assertTrue(tasks_manager.return_value.__enter__.return_value.update_groups.called)
@mock.patch(MODULE_PATH + '.views.Teamspeak3Tasks')
@mock.patch(MODULE_PATH + '.views.messages')
def test_should_update_ts_groups(self, messages, Teamspeak3Tasks):
# given
self.member.is_superuser = True
self.member.is_staff = True
self.member.save()
self.login()
# when
response = self.client.get(urls.reverse('teamspeak3:admin_update_ts3_groups'))
# then
self.assertRedirects(
response, urls.reverse('admin:teamspeak3_authts_changelist'),
target_status_code=200
)
self.assertTrue(messages.info.called)
self.assertTrue(Teamspeak3Tasks.run_ts3_group_update.delay.called)
class Teamspeak3SignalsTestCase(TestCase): class Teamspeak3SignalsTestCase(TestCase):
def setUp(self): def setUp(self):

View File

@@ -6,12 +6,14 @@ app_name = 'teamspeak3'
module_urls = [ module_urls = [
# Teamspeak3 service control # Teamspeak3 service control
url(r'^activate/$', views.activate_teamspeak3, url(r'^activate/$', views.activate_teamspeak3, name='activate'),
name='activate'), url(r'^deactivate/$', views.deactivate_teamspeak3, name='deactivate'),
url(r'^deactivate/$', views.deactivate_teamspeak3, url(r'^reset_perm/$', views.reset_teamspeak3_perm, name='reset_perm'),
name='deactivate'), url(
url(r'^reset_perm/$', views.reset_teamspeak3_perm, r'^admin_update_ts3_groups/$',
name='reset_perm'), views.admin_update_ts3_groups,
name='admin_update_ts3_groups'
),
# Teamspeak Urls # Teamspeak Urls
url(r'^verify/$', views.verify_teamspeak3, name='verify'), url(r'^verify/$', views.verify_teamspeak3, name='verify'),

View File

@@ -1,6 +1,7 @@
import logging import logging
from django.contrib import messages from django.contrib import messages
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.conf import settings from django.conf import settings
@@ -99,3 +100,11 @@ def reset_teamspeak3_perm(request):
logger.error("Unsuccessful attempt to reset TS3 permission key for user %s" % request.user) logger.error("Unsuccessful attempt to reset TS3 permission key for user %s" % request.user)
messages.error(request, _('An error occurred while processing your TeamSpeak3 account.')) messages.error(request, _('An error occurred while processing your TeamSpeak3 account.'))
return redirect("services:services") return redirect("services:services")
@login_required
@staff_member_required
def admin_update_ts3_groups(request):
Teamspeak3Tasks.run_ts3_group_update.delay()
messages.info(request, "Started updating TS3 server groups...")
return redirect("admin:teamspeak3_authts_changelist")

View File

@@ -1,3 +1,5 @@
import re
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@@ -21,6 +23,11 @@ class SrpFleetUserRequestForm(forms.Form):
data = self.cleaned_data['killboard_link'] data = self.cleaned_data['killboard_link']
if "zkillboard.com" not in data: if "zkillboard.com" not in data:
raise forms.ValidationError(_("Invalid Link. Please use zKillboard.com")) raise forms.ValidationError(_("Invalid Link. Please use zKillboard.com"))
if not re.match(r"http[s]?://zkillboard\.com/kill/\d+\/", data):
raise forms.ValidationError(
_("Invalid Link. Please post a direct link to a killmail.")
)
return data return data

View File

@@ -10,6 +10,9 @@
{% include 'bundles/x-editable.css.html' %} {% include 'bundles/x-editable.css.html' %}
<link href="{% static 'css/checkbox.css' %}" rel="stylesheet" type="text/css"> <link href="{% static 'css/checkbox.css' %}" rel="stylesheet" type="text/css">
<style> <style>
.copy-text-fa-icon:hover {
cursor: pointer;
}
.radio label, .checkbox label { .radio label, .checkbox label {
padding-left: 10px; padding-left: 10px;
} }
@@ -104,7 +107,14 @@ ESC to cancel{% endblocktrans %}"id="blah"></i></th>
<tbody> <tbody>
{% for srpfleetrequest in srpfleetrequests %} {% for srpfleetrequest in srpfleetrequests %}
<tr> <tr>
<td class="text-center">{{ srpfleetrequest.character.character_name }}</td> <td class="text-center">
{% if srpfleetrequest.character.alliance.alliance_ticker %}
{{ srpfleetrequest.character.alliance.alliance_ticker }}
{% endif %}
[{{ srpfleetrequest.character.corporation.corporation_ticker }}]
{{ srpfleetrequest.character.character_name }}&nbsp;<i class="copy-text-fa-icon far fa-copy" data-clipboard-text="{{ srpfleetrequest.character.character_name }}"></i>
</span>
</td>
<td class="text-center"> <td class="text-center">
<a href="{{ srpfleetrequest.killboard_link }}" <a href="{{ srpfleetrequest.killboard_link }}"
target="_blank" class="label label-warning">Link</a> target="_blank" class="label label-warning">Link</a>
@@ -176,7 +186,24 @@ ESC to cancel{% endblocktrans %}"id="blah"></i></th>
{% include 'bundles/datatables-js.html' %} {% include 'bundles/datatables-js.html' %}
{% include 'bundles/x-editable-js.html' %} {% include 'bundles/x-editable-js.html' %}
{% include 'bundles/moment-js.html' %} {% include 'bundles/moment-js.html' %}
{% endblock %} {% include 'bundles/clipboard-js.html' %}
<script>
var clipboard = new ClipboardJS('.copy-text-fa-icon');
clipboard.on('success', function (e) {
console.info('Action:', e.action);
console.info('Text:', e.text);
console.info('Trigger:', e.trigger);
e.clearSelection();
});
clipboard.on('error', function (e) {
console.error('Action:', e.action);
console.error('Trigger:', e.trigger);
});
</script>
{% endblock extra_javascript %}
{% block extra_script %} {% block extra_script %}
$(document).ready(function() { $(document).ready(function() {

View File

@@ -43,27 +43,63 @@ ul.list-group.list-group-horizontal > li.list-group-item {
justify-content: center; justify-content: center;
} }
/* style group headers within a table */ @media all {
.tr-group { /* style nav tabs in dark mode*/
font-weight: bold; .template-dark-mode .nav-tabs > li.active > a {
background-color: #e6e6e6 !important; background-color: rgb(70, 69, 69)!important;
color: rgb(255, 255, 255) !important;
}
.panel-tabs-aa {
border-top: none;
border-top-left-radius: 0%;
border-top-right-radius: 0%;
}
/* style group headers within a table */
.template-light-mode .tr-group {
font-weight: bold;
background-color: #e6e6e6 !important;
}
.template-dark-mode .tr-group {
font-weight: bold;
background-color: rgb(105, 105, 105) !important;
}
/* default style for tables */
.template-light-mode .table-aa > thead > tr > th{
border-bottom: 1px solid #f2f2f2;
}
.template-dark-mode .table-aa > thead > tr > th{
border-bottom: 1px solid rgb(70, 69, 69);
}
.table-aa > thead > tr > th{
vertical-align: middle;
}
.template-light-mode .table-aa > tbody > tr > td{
border-bottom: 1px solid #f2f2f2;
}
.template-dark-mode .table-aa > tbody > tr > td{
border-bottom: 1px solid rgb(70, 69, 69);
}
.table-aa > tbody > tr > td {
vertical-align: middle;
}
.table-aa > tbody > tr:last-child {
border-bottom: none;
}
} }
/* default style for tables */ /* highlight active menu items
.table-aa > thead > tr > th{ --------------------------------------------------------------------------------------------------------------------- */
border-bottom: 1px solid #f2f2f2; @media all {
} .template-light-mode .nav-pills > li > a.active {
.table-aa > thead > tr > th{ background-color: rgb(236, 240, 241);
vertical-align: middle; }
}
.table-aa > tbody > tr > td{ .template-dark-mode .nav-pills > li > a.active {
border-bottom: 1px solid #f2f2f2; background-color: rgb(48, 48, 48);
} }
.table-aa > tbody > tr > td {
vertical-align: middle;
}
.table-aa > tbody > tr:last-child {
border-bottom: none;
} }
/* Small devices (tablets, 768px and up) */ /* Small devices (tablets, 768px and up) */

View File

@@ -0,0 +1,75 @@
/*
This script refreshed the unread notification count in the top menu
on a regular basis so to keep the user apprised about newly arrived
notifications without having to reload the page.
The refresh rate can be changes via the Django setting NOTIFICATIONS_REFRESH_TIME.
See documentation for details.
*/
$(function () {
var elem = document.getElementById("dataExport");
var notificationsListViewUrl = elem.getAttribute("data-notificationsListViewUrl");
var notificationsRefreshTime = elem.getAttribute("data-notificationsRefreshTime");
var userNotificationsCountViewUrl = elem.getAttribute(
"data-userNotificationsCountViewUrl"
);
// update the notification unread count in the top menu
function update_notifications() {
$.getJSON(userNotificationsCountViewUrl, function (data, status) {
if (status == 'success') {
var innerHtml = "";
var unread_count = data.unread_count;
if (unread_count > 0) {
innerHtml = (
`Notifications <span class="badge">${unread_count}</span>`
)
}
else {
innerHtml = '<i class="far fa-bell"></i>'
}
$("#menu_item_notifications").html(
`<a href="${notificationsListViewUrl}">${innerHtml}</a>`
);
}
else {
console.error(
`Failed to load HTMl to render notifications item. Error: `
`${xhr.status}': '${xhr.statusText}`
);
}
});
}
var myInterval;
// activate automatic refreshing every x seconds
function activate_refreshing() {
if (notificationsRefreshTime > 0) {
myInterval = setInterval(
update_notifications, notificationsRefreshTime * 1000
);
}
}
// deactivate automatic refreshing
function deactivate_refreshing() {
if ((notificationsRefreshTime > 0) && (typeof myInterval !== 'undefined')) {
clearInterval(myInterval)
}
}
// refreshing only happens on active browser tab
$(document).on({
'show': function () {
activate_refreshing()
},
'hide': function () {
deactivate_refreshing()
}
});
// Initial start of refreshing on script loading
activate_refreshing()
});

View File

@@ -18,9 +18,20 @@
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>
<div class="text-right" style="position:absolute;bottom:5px;right:5px;">
<a href="https://gitlab.com/allianceauth/allianceauth/issues"><span class="label" style="background-color:#e65328;"> <div class="text-right" style="position: absolute; bottom: 5px; right: 5px;">
<i class="fab fa-gitlab" aria-hidden="true"></i> Powered by GitLab</span> <a href="https://gitlab.com/allianceauth/allianceauth/issues" target="_blank" style="margin-right: 0.5rem;">
<span class="label" style="background-color: #e65328;">
<i class="fab fa-gitlab" aria-hidden="true"></i>
{% translate 'Powered by GitLab' %}
</span>
</a>
<a href="https://discord.com/invite/fjnHAmk" target="_blank">
<span class="label" style="background-color: rgb(110,133,211);">
<i class="fab fa-discord" aria-hidden="true"></i>
{% translate 'Support Discord' %}
</span>
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,54 +1,60 @@
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% load navactive %} {% load navactive %}
{% load auth_notifications %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content=""> <meta name="description" content="">
<meta name="author" content=""> <meta name="author" content="">
{% include 'allianceauth/icons.html' %} {% include 'allianceauth/icons.html' %}
<title>{% block title %}{% block page_title %}{% endblock page_title %} - Alliance Auth{% endblock title %}</title> <title>{% block title %}{% block page_title %}{% endblock page_title %} - Alliance Auth{% endblock title %}</title>
{% include 'bundles/bootstrap-css.html' %} {% include 'bundles/bootstrap-css.html' %}
{% include 'bundles/fontawesome.html' %} {% include 'bundles/fontawesome.html' %}
<link href="{% static 'css/auth-base.css' %}" type="text/css" rel="stylesheet"> <link href="{% static 'css/auth-base.css' %}" type="text/css" rel="stylesheet">
{% block extra_css %}{% endblock extra_css %}
</head> {% block extra_css %}{% endblock extra_css %}
<body> </head>
{% if user.is_authenticated %}
<div id="wrapper" class="container"> <body class="{% if NIGHT_MODE %}template-dark-mode{% else %}template-light-mode{% endif %}">
<!-- Navigation --> {% if user.is_authenticated %}
{% include 'allianceauth/top-menu.html' %} <div id="wrapper" class="container">
<div class="row" id="site-body-wrapper"> <!-- Navigation -->
{% include 'allianceauth/side-menu.html' %} {% include 'allianceauth/top-menu.html' %}
<div class="col-sm-10"> <div class="row" id="site-body-wrapper">
{% if messages %} {% include 'allianceauth/side-menu.html' %}
<br> <div class="col-sm-10">
{% for message in messages %} {% include 'allianceauth/messages.html' %}
<div class="alert alert-{{ message.level_tag }}">{{ message }}</div> {% block content %}
{% if not forloop.last %} {% endblock content %}
<br> </div>
{% endif %} <div class="clearfix"></div>
{% endfor %} </div>
{% endif %}
{% block content %}
{% endblock content %}
</div> </div>
<div class="clearfix"></div> {% endif %}
<!-- share data with JS part -->
<div
id="dataExport"
data-notificationsListViewUrl="{% url 'notifications:list' %}"
data-notificationsRefreshTime="{% notifications_refresh_time %}"
data-userNotificationsCountViewUrl="{% url 'notifications:user_notifications_count' request.user.pk %}"
>
</div> </div>
</div> {% include 'bundles/bootstrap-js.html' %}
{% endif %} {% include 'bundles/jquery-visibility-js.html' %}
{% include 'bundles/bootstrap-js.html' %} <script src="{% static 'js/refresh_notifications.js' %}"></script>
{% block extra_javascript %}
{% endblock extra_javascript %} {% block extra_javascript %}
<script> {% endblock extra_javascript %}
{% block extra_script %} <script>
{% endblock extra_script %} {% block extra_script %}
</script> {% endblock extra_script %}
</body> </script>
</body>
</html> </html>

View File

@@ -0,0 +1,26 @@
{% if messages %}
<br>
{% for message in messages %}
<div class="alert alert-{{ message.level_tag }} alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<div class="message-icon pull-left" style="margin-right: 0.5rem;">
{% if message.level_tag == "info" %}
<i class="fas fa-info-circle"></i>
{% elif message.level_tag == "success" %}
<i class="fas fa-check-circle"></i>
{% elif message.level_tag == "warning" %}
<i class="fas fa-exclamation-circle"></i>
{% elif message.level_tag == "danger" %}
<i class="fas fa-exclamation-triangle"></i>
{% endif %}
</div>
<div class="message-text" style="margin-left: 2.5rem;">
{{ message }}
</div>
</div>
{% endfor %}
{% endif %}

View File

@@ -1,5 +1,5 @@
{% load i18n %} {% load i18n %}
<a href="{% url 'nightmode' %}?next={{ request.path|urlencode }}"> <a href="{% url 'nightmode' %}?next={{ request.path|urlencode }}">
{% trans "Night" %} {% trans "Night" %}
<i class="fa {% if NIGHT_MODE %}fa-toggle-on{% else %}fa-toggle-off{% endif %}" aria-hidden="true"></i> <i class="fas {% if NIGHT_MODE %}fa-toggle-on{% else %}fa-toggle-off{% endif %}" aria-hidden="true"></i>
</a> </a>

View File

@@ -0,0 +1,13 @@
{% load auth_notifications %}
{% with unread_count=request.user|user_unread_notification_count %}
{% if unread_count > 0 %}
<a href="{% url 'notifications:list' %}">Notifications
<span class="badge">{{ unread_count }}</span>
</a>
{% else %}
<a href="{% url 'notifications:list' %}">
<i class="far fa-bell"></i>
</a>
{% endif %}
{% endwith %}

View File

@@ -20,17 +20,11 @@
<li> <li>
{% include 'allianceauth/night-toggle.html' %} {% include 'allianceauth/night-toggle.html' %}
</li> </li>
{% if notifications %} <li
<li class="{% navactive request 'notifications:' %}"> class="{% navactive request 'notifications:' %}" id="menu_item_notifications"
<a href="{% url 'notifications:list' %}">Notifications >
<span class="badge">{{ notifications }}</span> {% include 'allianceauth/notifications_menu_item.html' %}
</a>
</li> </li>
{% else %}
<li><a href="{% url 'notifications:list' %}">
<i class="far fa-bell"></i></a>
</li>
{% endif %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
{% if user.is_staff %} {% if user.is_staff %}
<li><a href="{% url 'admin:index' %}">{% trans "Admin" %}</a></li> <li><a href="{% url 'admin:index' %}">{% trans "Admin" %}</a></li>
@@ -64,3 +58,4 @@
</div> </div>
</div> </div>
</nav> </nav>

View File

@@ -1,3 +1,3 @@
<!-- Start Clipboard.js js from cdnjs --> <!-- Start Clipboard.js js from cdnjs -->
<script type="application/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.6/clipboard.min.js"></script> <script type="application/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.8/clipboard.min.js" integrity="sha512-sIqUEnRn31BgngPmHt2JenzleDDsXwYO+iyvQ46Mw6RL+udAUZj2n/u/PGY80NxRxynO7R9xIGx5LEzw4INWJQ==" crossorigin="anonymous"></script>
<!-- End Clipboard.js js from cdnjs --> <!-- End Clipboard.js js from cdnjs -->

View File

@@ -0,0 +1,4 @@
{% load static %}
<!-- Start jQuery visibility js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-visibility/1.0.11/jquery-visibility.min.js"></script>
<!-- End jQuery visibility js -->

View File

@@ -27,7 +27,7 @@ GITLAB_AUTH_REPOSITORY_TAGS_URL = (
) )
GITLAB_AUTH_ANNOUNCEMENT_ISSUES_URL = ( GITLAB_AUTH_ANNOUNCEMENT_ISSUES_URL = (
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues' 'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues'
'?labels=announcement' '?labels=announcement&state=opened'
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -70,8 +70,8 @@ def _current_notifications() -> dict:
_fetch_notification_issues_from_gitlab, _fetch_notification_issues_from_gitlab,
NOTIFICATION_CACHE_TIME NOTIFICATION_CACHE_TIME
) )
except requests.RequestException: except requests.HTTPError:
logger.exception('Error while getting gitlab notifications') logger.warning('Error while getting gitlab notifications', exc_info=True)
top_notifications = [] top_notifications = []
else: else:
if notifications: if notifications:
@@ -95,8 +95,8 @@ def _current_version_summary() -> dict:
tags = cache.get_or_set( tags = cache.get_or_set(
'git_release_tags', _fetch_tags_from_gitlab, TAG_CACHE_TIME 'git_release_tags', _fetch_tags_from_gitlab, TAG_CACHE_TIME
) )
except requests.RequestException: except requests.HTTPError:
logger.exception('Error while getting gitlab release tags') logger.warning('Error while getting gitlab release tags', exc_info=True)
return {} return {}
if not tags: if not tags:

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -28,8 +28,6 @@ on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
# Support for recommonmark module # Support for recommonmark module
import recommonmark import recommonmark
from recommonmark.parser import CommonMarkParser
from recommonmark.transform import AutoStructify from recommonmark.transform import AutoStructify
# -- General configuration ------------------------------------------------ # -- General configuration ------------------------------------------------
@@ -41,8 +39,11 @@ from recommonmark.transform import AutoStructify
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = [ extensions = [
'sphinx_rtd_theme',
'sphinx.ext.autodoc', 'sphinx.ext.autodoc',
'recommonmark',
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
@@ -76,7 +77,7 @@ version = u'2.0'
# #
# This is also used if you do content translation via gettext catalogs. # This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases. # Usually you set "language" from the command line for these cases.
language = None language = 'en'
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
@@ -171,12 +172,6 @@ texinfo_documents = [
'Miscellaneous'), 'Miscellaneous'),
] ]
# Markdown support
source_parsers = {
'.md': CommonMarkParser,
}
def setup(app): def setup(app):
app.add_config_value('recommonmark_config', { app.add_config_value('recommonmark_config', {
'auto_toc_tree_section': 'Contents', 'auto_toc_tree_section': 'Contents',

View File

@@ -23,7 +23,7 @@ Within your auth project exists two folders named `static` and `templates`. Thes
You can add extra static or templates by putting files in these folders. Note that changes to static requires running the `python manage.py collectstatic` command to copy to the web server directory. You can add extra static or templates by putting files in these folders. Note that changes to static requires running the `python manage.py collectstatic` command to copy to the web server directory.
It is possible to overload static and templates shipped with Django or Alliance Auth by including a file with the exact path of the one you wish to overload. For instance if you wish to add extra links to the menu bar by editing the template, you would make a copy of the `allianceauth/templates/allianceauth/base.html` file to `myauth/templates/allinceauth/base.html` and edit it there. Notice the paths are identical after the `templates/` directory - this is critical for it to be recognized. Your custom template would be used instead of the one included with Alliance Auth when Django renders the web page. Similar idea for static: put CSS or images at an identical path after the `static/` directory and they will be copied to the web server directory instead of the ones included. It is possible to overload static and templates shipped with Django or Alliance Auth by including a file with the exact path of the one you wish to overload. For instance if you wish to add extra links to the menu bar by editing the template, you would make a copy of the `allianceauth/templates/allianceauth/base.html` file to `myauth/templates/allianceauth/base.html` and edit it there. Notice the paths are identical after the `templates/` directory - this is critical for it to be recognized. Your custom template would be used instead of the one included with Alliance Auth when Django renders the web page. Similar idea for static: put CSS or images at an identical path after the `static/` directory and they will be copied to the web server directory instead of the ones included.
## Custom URLs and Views ## Custom URLs and Views

View File

@@ -25,6 +25,11 @@ The development environment consists of the following components:
We will use the build-in Django development webserver, so we don't need to setup a WSGI server or a web server. We will use the build-in Django development webserver, so we don't need to setup a WSGI server or a web server.
```eval_rst
.. note::
This setup works with both WSL 1 and WSL 2. However, due to the significantly better performance we recommend WSL 2.
```
## Requirement ## Requirement
The only requirement is a PC with Windows 10 and Internet connection in order to download the additional software components. The only requirement is a PC with Windows 10 and Internet connection in order to download the additional software components.
@@ -362,7 +367,6 @@ Here is an example debug config for Celery:
"cwd": "${workspaceFolder}/myauth", "cwd": "${workspaceFolder}/myauth",
"console": "integratedTerminal", "console": "integratedTerminal",
"args": [ "args": [
"-E",
"-A", "-A",
"myauth", "myauth",
"worker", "worker",
@@ -371,7 +375,8 @@ Here is an example debug config for Celery:
"-P", "-P",
"solo", "solo",
], ],
"django": true "django": true,
"justMyCode": true,
}, },
``` ```
@@ -387,14 +392,13 @@ Finally it makes sense to have a dedicated debug config for running unit tests.
"program": "${workspaceFolder}/myauth/manage.py", "program": "${workspaceFolder}/myauth/manage.py",
"args": [ "args": [
"test", "test",
"-v 2",
"--keepdb", "--keepdb",
"--debug-mode", "--debug-mode",
"--failfast", "--failfast",
"example", "example",
], ],
"django": true,
"django": true "justMyCode": true
}, },
``` ```
@@ -416,13 +420,31 @@ Finally you may also want to have a debug config to debug a non-Django Python sc
## Additional tools ## Additional tools
The following additional tools are very helpful when developing for AA. The following additional tools are very helpful when developing for AA with VS Code:
### Pylance
Pylance is an extension that works alongside Python in Visual Studio Code to provide performant language support: [Pylance](https://marketplace.visualstudio.com/items?itemName=ms-python.vscode-pylance)
### Code Spell Checker ### Code Spell Checker
Typos in your user facing comments can be quite embarrassing. This spell checker helps you avoid them. Typos in your user facing comments can be quite embarrassing. This spell checker helps you avoid them: [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker)
Install from here: [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker) ### markdownlint
Extension for Visual Studio Code - Markdown linting and style checking for Visual Studio Code: [markdownlint](https://marketplace.visualstudio.com/items?itemName=DavidAnson.vscode-markdownlint)
### GitLens
Extension for Visual Studio Code - Supercharge the Git capabilities built into Visual Studio Code: [GitLens](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)
### RST preview
A VS Code extension to preview restructured text and provide syntax highlighting: [RST Preview](https://marketplace.visualstudio.com/items?itemName=tht13.rst-vscode)
### Django Template
This extension adds language colorization support and user snippets for the Django template language to VS Code: [Django Template](https://marketplace.visualstudio.com/items?itemName=bibhasdn.django-html)
### DBeaver ### DBeaver

View File

@@ -9,5 +9,6 @@ To reduce redundancy and help speed up development we encourage developers to ut
esi esi
evelinks evelinks
eveonline eveonline
notifications
testutils testutils
``` ```

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