mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2026-02-04 14:16:21 +01:00
Compare commits
211 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4befc5e59 | ||
|
|
1ee8065592 | ||
|
|
e4e3bd44fc | ||
|
|
4f802e82a9 | ||
|
|
0c90bd462e | ||
|
|
f6e6ba775c | ||
|
|
06646be907 | ||
|
|
1b4c1a4b9e | ||
|
|
ae3f5a0f62 | ||
|
|
3a984e8a4d | ||
|
|
7d711a54bc | ||
|
|
d92d629c25 | ||
|
|
21e630209a | ||
|
|
e3933998ef | ||
|
|
667afe9051 | ||
|
|
26dc2881eb | ||
|
|
250cb33285 | ||
|
|
db51abec1f | ||
|
|
530716d458 | ||
|
|
f3065d79b3 | ||
|
|
bca5f0472e | ||
|
|
8e54c43917 | ||
|
|
946df1d7a0 | ||
|
|
55f00f742c | ||
|
|
97b2cb71b7 | ||
|
|
ba3a5ba53c | ||
|
|
953c09c999 | ||
|
|
b4cc325b07 | ||
|
|
28c1343f3e | ||
|
|
c16fd94c4a | ||
|
|
bb2cc20838 | ||
|
|
7b815fd010 | ||
|
|
18584974df | ||
|
|
15823b7785 | ||
|
|
6c275d4cd2 | ||
|
|
2d64ee5e2a | ||
|
|
3ae5ffa3f6 | ||
|
|
57d9ddc2c6 | ||
|
|
8b84def494 | ||
|
|
546f01ceb2 | ||
|
|
be720d0e0f | ||
|
|
72bed03244 | ||
|
|
38083ed284 | ||
|
|
53f1b94475 | ||
|
|
ed4270a0e3 | ||
|
|
f1d5cc8903 | ||
|
|
80efdec5d9 | ||
|
|
d49687400a | ||
|
|
e6e03b50da | ||
|
|
543fa3cfa9 | ||
|
|
899988c7c2 | ||
|
|
2f48dd449b | ||
|
|
f70fbbdfee | ||
|
|
2b09ca240d | ||
|
|
0626ff84ad | ||
|
|
62ec746ee3 | ||
|
|
d0f12d7d56 | ||
|
|
b806a69604 | ||
|
|
a609d6360b | ||
|
|
dafbfc8644 | ||
|
|
55413eea19 | ||
|
|
5247c181af | ||
|
|
321af5ec87 | ||
|
|
9ccf340b3d | ||
|
|
d7dcacb899 | ||
|
|
8addd483c2 | ||
|
|
4d27e5ac9b | ||
|
|
31290f6e80 | ||
|
|
c31cc4dbee | ||
|
|
cc1f94cf61 | ||
|
|
a9132b8d50 | ||
|
|
7b4a9891aa | ||
|
|
dcaaf38ecc | ||
|
|
653a8aa850 | ||
|
|
274af11385 | ||
|
|
170b246901 | ||
|
|
5250432ce3 | ||
|
|
53d6e973eb | ||
|
|
c9bdd62d53 | ||
|
|
7eb98af528 | ||
|
|
385e3e21b3 | ||
|
|
127ec63d76 | ||
|
|
4988b5f260 | ||
|
|
f28a50f92c | ||
|
|
e8efe8e609 | ||
|
|
d7e7457bc5 | ||
|
|
daff927811 | ||
|
|
8861ec0a61 | ||
|
|
bd4321f61a | ||
|
|
d831482fe0 | ||
|
|
9ea79ea389 | ||
|
|
b6fdf840ef | ||
|
|
73f262ce4b | ||
|
|
f63434adc3 | ||
|
|
42948386ec | ||
|
|
1ce0dbde0e | ||
|
|
32e0621b0a | ||
|
|
78e05b84e9 | ||
|
|
76ebd21163 | ||
|
|
38aaf545c6 | ||
|
|
527d7ef671 | ||
|
|
e54b80e061 | ||
|
|
27f95a8b2c | ||
|
|
a1e8903128 | ||
|
|
b00ac2aef4 | ||
|
|
8865d15ed9 | ||
|
|
fc3d4b7f33 | ||
|
|
934cc44540 | ||
|
|
106de3dd4c | ||
|
|
9b55cfcbe3 | ||
|
|
8137f1023a | ||
|
|
d670e33b6f | ||
|
|
3d3bb8fc94 | ||
|
|
9c880eae8a | ||
|
|
54a71630f1 | ||
|
|
923a8453cc | ||
|
|
00447ca819 | ||
|
|
ad4ee9d822 | ||
|
|
40e9dbfda2 | ||
|
|
b9da6911e6 | ||
|
|
81f9211098 | ||
|
|
8290081365 | ||
|
|
81af610c11 | ||
|
|
cfa2cf58f3 | ||
|
|
01c17d28f6 | ||
|
|
efd2a5e8c5 | ||
|
|
c437b00727 | ||
|
|
5df0d1ddc6 | ||
|
|
c3521b0d87 | ||
|
|
148916d35e | ||
|
|
06c7da944c | ||
|
|
f2ba741499 | ||
|
|
0f9927686b | ||
|
|
59855a71ef | ||
|
|
fffb21bb4f | ||
|
|
30bb6cdfab | ||
|
|
8771477884 | ||
|
|
55a5070691 | ||
|
|
1182b51e4b | ||
|
|
9976ecc2aa | ||
|
|
3bd8107fcf | ||
|
|
a48c67de5c | ||
|
|
bb0a7c014e | ||
|
|
80729b6b06 | ||
|
|
ff168d1c9e | ||
|
|
331100370c | ||
|
|
47babf2ed7 | ||
|
|
c1388bf23f | ||
|
|
3f4dfe9b0b | ||
|
|
0caac20d77 | ||
|
|
9d0a65a516 | ||
|
|
ab061ba7a6 | ||
|
|
2d24d064d5 | ||
|
|
458685026b | ||
|
|
b0448a4565 | ||
|
|
f902f59b31 | ||
|
|
2b8bfbe544 | ||
|
|
564a25e578 | ||
|
|
f50b08d301 | ||
|
|
26566d9ce0 | ||
|
|
f42d438be2 | ||
|
|
1fbc39b614 | ||
|
|
36af471c3c | ||
|
|
a5e86c9a36 | ||
|
|
4b27dd40b9 | ||
|
|
ff0aac9d8a | ||
|
|
bf24c8253e | ||
|
|
fd92352302 | ||
|
|
fcb66a11a3 | ||
|
|
63d2021a73 | ||
|
|
d110d9c74e | ||
|
|
157bf81dcb | ||
|
|
1beb1b1b4f | ||
|
|
13f523679c | ||
|
|
ed816d9aea | ||
|
|
ebfb51eed5 | ||
|
|
0127b1ea9e | ||
|
|
61f41a1459 | ||
|
|
d3fbc133a2 | ||
|
|
ce7a8e7a3d | ||
|
|
2b45610080 | ||
|
|
5b4dd6731c | ||
|
|
80157a032a | ||
|
|
7b1bf9a4e2 | ||
|
|
337c4d9ce5 | ||
|
|
9afc36a009 | ||
|
|
ebff1387c1 | ||
|
|
801502ec77 | ||
|
|
c07f59201e | ||
|
|
98b799d821 | ||
|
|
02714956d8 | ||
|
|
4d435d58c5 | ||
|
|
1c2fd3be50 | ||
|
|
6222439e21 | ||
|
|
46d46ac90b | ||
|
|
a5fe61eb15 | ||
|
|
0bfec36983 | ||
|
|
11607ecf24 | ||
|
|
9970e5535b | ||
|
|
99492e9c34 | ||
|
|
1d6ecffb3b | ||
|
|
8c33349dcb | ||
|
|
cfb2c55a4b | ||
|
|
e24d29f1d3 | ||
|
|
debd6ef2b9 | ||
|
|
58e9c21e4f | ||
|
|
c7c3083e3e | ||
|
|
63d061e9f2 | ||
|
|
1887bdb90a | ||
|
|
69addb068a | ||
|
|
a62c3ce0f9 |
@@ -8,6 +8,8 @@ omit =
|
||||
*/example/*
|
||||
*/project_template/*
|
||||
*/bin/*
|
||||
*/tests/*
|
||||
*/tests.py
|
||||
|
||||
[report]
|
||||
exclude_lines =
|
||||
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -64,5 +64,16 @@ celerybeat-schedule
|
||||
.idea/*
|
||||
/nbproject/
|
||||
|
||||
#VSCode
|
||||
.vscode/
|
||||
|
||||
#gitlab configs
|
||||
.gitlab/
|
||||
|
||||
#transifex
|
||||
.tx/
|
||||
|
||||
#other
|
||||
.flake8
|
||||
.pylintrc
|
||||
Makefile
|
||||
|
||||
@@ -1,33 +1,54 @@
|
||||
# Official language image. Look for the different tagged releases at:
|
||||
# https://hub.docker.com/r/library/python/tags/
|
||||
stages:
|
||||
- test
|
||||
- deploy
|
||||
|
||||
.job_template: &job_definition
|
||||
# Change pip's cache directory to be inside the project directory since we can
|
||||
# only cache local items.
|
||||
variables:
|
||||
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache"
|
||||
|
||||
# Pip's cache doesn't store the python packages
|
||||
# https://pip.pypa.io/en/stable/reference/pip_install/#caching
|
||||
#
|
||||
# If you want to also cache the installed packages, you have to install
|
||||
# them in a virtualenv and cache it as well.
|
||||
cache:
|
||||
paths:
|
||||
- .cache/pip
|
||||
- venv/
|
||||
|
||||
before_script:
|
||||
- python -V # Print out python version for debugging
|
||||
- pip install virtualenv tox
|
||||
- virtualenv venv
|
||||
- source venv/bin/activate
|
||||
|
||||
coverage: '/TOTAL.+ ([0-9]{1,3}%)/'
|
||||
before_script:
|
||||
- apt-get update && apt-get install redis-server -y
|
||||
- redis-server --daemonize yes
|
||||
- redis-cli ping
|
||||
- python -V
|
||||
- pip install wheel tox
|
||||
|
||||
py36-dj20:
|
||||
<<: *job_definition
|
||||
test-3.6-core:
|
||||
image: python:3.6-buster
|
||||
script:
|
||||
- tox -e py36-core
|
||||
|
||||
test-3.7-core:
|
||||
image: python:3.7-buster
|
||||
script:
|
||||
- tox -e py37-core
|
||||
|
||||
test-3.8-core:
|
||||
image: python:3.8-buster
|
||||
script:
|
||||
- tox -e py38-core
|
||||
|
||||
test-3.6-all:
|
||||
image: python:3.6-buster
|
||||
script:
|
||||
- tox -e py36-all
|
||||
|
||||
test-3.7-all:
|
||||
image: python:3.7-buster
|
||||
script:
|
||||
- tox -e py37-all
|
||||
|
||||
test-3.8-all:
|
||||
image: python:3.8-buster
|
||||
script:
|
||||
- tox -e py38-all
|
||||
|
||||
deploy_production:
|
||||
stage: deploy
|
||||
image: python:3.6-stretch
|
||||
|
||||
before_script:
|
||||
- pip install twine
|
||||
|
||||
script:
|
||||
- export TOXENV=py36-dj20
|
||||
- tox
|
||||
- python setup.py sdist
|
||||
- twine upload dist/*
|
||||
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
|
||||
27
.readthedocs.yml
Normal file
27
.readthedocs.yml
Normal file
@@ -0,0 +1,27 @@
|
||||
# .readthedocs.yml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
# Build documentation with MkDocs
|
||||
#mkdocs:
|
||||
# configuration: mkdocs.yml
|
||||
|
||||
# Optionally build your docs in additional formats such as PDF and ePub
|
||||
formats: all
|
||||
|
||||
# Optionally set the version of Python and requirements required to build your docs
|
||||
python:
|
||||
version: 3.7
|
||||
install:
|
||||
- method: pip
|
||||
path: .
|
||||
extra_requirements:
|
||||
- testing
|
||||
system_packages: true
|
||||
25
.travis.yml
25
.travis.yml
@@ -1,25 +0,0 @@
|
||||
language: python
|
||||
sudo: false
|
||||
cache: pip
|
||||
dist: trusty
|
||||
install:
|
||||
- pip install coveralls>=1.1 tox
|
||||
# command to run tests
|
||||
script:
|
||||
- tox
|
||||
after_success:
|
||||
coveralls
|
||||
matrix:
|
||||
include:
|
||||
- env: TOXENV=py35-dj111
|
||||
python: '3.5'
|
||||
- env: TOXENV=py36-dj111
|
||||
python: '3.6'
|
||||
- env: TOXENV=py35-dj20
|
||||
python: '3.5'
|
||||
- env: TOXENV=py36-dj20
|
||||
python: '3.6'
|
||||
- env: TOXENV=py37-dj20
|
||||
python: '3.7-dev'
|
||||
allow_failures:
|
||||
- env: TOXENV=py37-dj20
|
||||
93
README.md
93
README.md
@@ -1,37 +1,86 @@
|
||||
Alliance Auth
|
||||
============
|
||||
# Alliance Auth
|
||||
|
||||
[](https://discord.gg/fjnHAmk)
|
||||
[](http://allianceauth.readthedocs.io/?badge=latest)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://pypi.org/project/allianceauth/)
|
||||
[](https://gitlab.com/allianceauth/allianceauth/commits/master)
|
||||
[](http://allianceauth.readthedocs.io/?badge=latest)
|
||||
[](https://gitlab.com/allianceauth/allianceauth/commits/master)
|
||||
|
||||
|
||||
[](https://discord.gg/fjnHAmk)
|
||||
|
||||
An auth system for EVE Online to help in-game organizations manage online service access.
|
||||
|
||||
[Read the docs here.](http://allianceauth.rtfd.io)
|
||||
## Content
|
||||
|
||||
[Get help on Discord](https://discord.gg/fjnHAmk) or submit an Issue.
|
||||
- [Overview](#overview)
|
||||
- [Documentation](http://allianceauth.rtfd.io)
|
||||
- [Support](#support)
|
||||
- [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases)
|
||||
- [Developer Team](#developer-team)
|
||||
- [Contributing](#contributing)
|
||||
|
||||
## Overview
|
||||
|
||||
Active Developers:
|
||||
Alliance Auth (AA) is a web site that helps Eve Online organizations efficiently manage access to applications and services.
|
||||
|
||||
- [Adarnof](https://gitlab.com/adarnof/)
|
||||
- [Basraah](https://gitlab.com/basraah/)
|
||||
- [Ariel Rin](https://gitlab.com/soratidus999/)
|
||||
- [Col Crunch](https://gitlab.com/colcrunch/)
|
||||
Main features:
|
||||
|
||||
Beta Testers / Bug Fixers:
|
||||
- Automatically grants or revokes user access to external services (e.g. Discord, Mumble) and web apps (e.g. SRP requests) based on the user's current membership to [in-game organizations](https://allianceauth.readthedocs.io/en/latest/features/core/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/core/groups/)
|
||||
|
||||
- [ghoti](https://gitlab.com/ChainsawMcGinny/)
|
||||
- [mmolitor87](https://gitlab.com/mmolitor87/)
|
||||
- [TargetZ3R0](https://github.com/TargetZ3R0)
|
||||
- [kaezon](https://github.com/kaezon/)
|
||||
- [orbitroom](https://github.com/orbitroom/)
|
||||
- [tehfiend](https://github.com/tehfiend/)
|
||||
- Provides a central web site where users can directly access web apps (e.g. SRP requests, Fleet Schedule) and manage their access to external services and groups.
|
||||
|
||||
- Includes a set of connectors (called ["services"](https://allianceauth.readthedocs.io/en/latest/features/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others
|
||||
|
||||
- Includes a set of web [apps](https://allianceauth.readthedocs.io/en/latest/features/apps/) which add many useful functions, e.g.: fleet schedule, timer board, SRP request management, fleet activity tracker
|
||||
|
||||
- Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
|
||||
|
||||
- Chinese :cn:, English :us:, German :de: and Spanish :es: localization
|
||||
|
||||
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io).
|
||||
|
||||
## Screenshot
|
||||
|
||||
Here is an example of the Alliance Auth web site with some plug-ins apps and services enabled:
|
||||
|
||||

|
||||
|
||||
## Support
|
||||
|
||||
[Get help on Discord](https://discord.gg/fjnHAmk) or submit an [issue](https://gitlab.com/allianceauth/allianceauth/issues).
|
||||
|
||||
## Development Team
|
||||
|
||||
### Active Developers
|
||||
|
||||
- [Aaron Kable](https://gitlab.com/aaronkable/)
|
||||
- [Ariel Rin](https://gitlab.com/soratidus999/)
|
||||
- [Basraah](https://gitlab.com/basraah/)
|
||||
- [Col Crunch](https://gitlab.com/colcrunch/)
|
||||
- [Erik Kalkoken](https://gitlab.com/ErikKalkoken/)
|
||||
|
||||
### Former Developers
|
||||
|
||||
- [Adarnof](https://gitlab.com/adarnof/)
|
||||
|
||||
### Beta Testers / Bug Fixers
|
||||
|
||||
- [ghoti](https://gitlab.com/ChainsawMcGinny/)
|
||||
- [kaezon](https://github.com/kaezon/)
|
||||
- [mmolitor87](https://gitlab.com/mmolitor87/)
|
||||
- [orbitroom](https://github.com/orbitroom/)
|
||||
- [TargetZ3R0](https://github.com/TargetZ3R0)
|
||||
- [tehfiend](https://github.com/tehfiend/)
|
||||
|
||||
Special thanks to [Nikdoof](https://github.com/nikdoof/), as his [auth](https://github.com/nikdoof/test-auth) was the foundation for the original work on this project.
|
||||
|
||||
### Contributing
|
||||
Make sure you have signed the [License Agreement](https://developers.eveonline.com/resource/license-agreement) by logging in at [https://developers.eveonline.com](https://developers.eveonline.com) before submitting any pull requests.
|
||||
## Contributing
|
||||
|
||||
Alliance Auth is maintained and developed by the community and we welcome every contribution!
|
||||
|
||||
To see what needs to be worked on please review our issue list or chat with our active developers on Discord.
|
||||
|
||||
Also, please make sure you have signed the [License Agreement](https://developers.eveonline.com/resource/license-agreement) by logging in at [https://developers.eveonline.com](https://developers.eveonline.com) before submitting any pull requests.
|
||||
|
||||
In addition to the core AA system we also very much welcome contributions to our growing list of 3rd party services and plugin apps. Please see [AA Community Creations](https://gitlab.com/allianceauth/community-creations) for details.
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
|
||||
__version__ = '2.2.1'
|
||||
NAME = 'Alliance Auth v%s' % __version__
|
||||
__version__ = '2.7.1'
|
||||
__title__ = 'Alliance Auth'
|
||||
__url__ = 'https://gitlab.com/allianceauth/allianceauth'
|
||||
NAME = '%s v%s' % (__title__, __version__)
|
||||
default_app_config = 'allianceauth.apps.AllianceAuthConfig'
|
||||
|
||||
@@ -1,15 +1,33 @@
|
||||
from django.conf import settings
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib.auth.models import User as BaseUser, Permission as BasePermission
|
||||
from django.utils.text import slugify
|
||||
from django.db.models import Q
|
||||
from django.contrib.auth.models import User as BaseUser, \
|
||||
Permission as BasePermission, Group
|
||||
from django.db.models import Q, F
|
||||
from allianceauth.services.hooks import ServicesHook
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, \
|
||||
post_delete, m2m_changed
|
||||
from django.db.models.functions import Lower
|
||||
from django.dispatch import receiver
|
||||
from allianceauth.authentication.models import State, get_guest_state, CharacterOwnership, UserProfile, OwnershipRecord
|
||||
from allianceauth.hooks import get_hooks
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
from django.forms import ModelForm
|
||||
from django.utils.html import format_html
|
||||
from django.urls import reverse
|
||||
from django.utils.text import slugify
|
||||
|
||||
from allianceauth.authentication.models import State, get_guest_state,\
|
||||
CharacterOwnership, UserProfile, OwnershipRecord
|
||||
from allianceauth.hooks import get_hooks
|
||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\
|
||||
EveAllianceInfo
|
||||
from allianceauth.eveonline.tasks import update_character
|
||||
from .app_settings import AUTHENTICATION_ADMIN_USERS_MAX_GROUPS, \
|
||||
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):
|
||||
@@ -19,8 +37,11 @@ def make_service_hooks_update_groups_action(service):
|
||||
:return: fn to update services groups for the selected users
|
||||
"""
|
||||
def update_service_groups(modeladmin, request, queryset):
|
||||
for user in queryset: # queryset filtering doesn't work here?
|
||||
service.update_groups(user)
|
||||
if hasattr(service, 'update_groups_bulk'):
|
||||
service.update_groups_bulk(queryset)
|
||||
else:
|
||||
for user in queryset: # queryset filtering doesn't work here?
|
||||
service.update_groups(user)
|
||||
|
||||
update_service_groups.__name__ = str('update_{}_groups'.format(slugify(service.name)))
|
||||
update_service_groups.short_description = "Sync groups for selected {} accounts".format(service.title)
|
||||
@@ -34,8 +55,11 @@ def make_service_hooks_sync_nickname_action(service):
|
||||
:return: fn to sync nickname for the selected users
|
||||
"""
|
||||
def sync_nickname(modeladmin, request, queryset):
|
||||
for user in queryset: # queryset filtering doesn't work here?
|
||||
service.sync_nickname(user)
|
||||
if hasattr(service, 'sync_nicknames_bulk'):
|
||||
service.sync_nicknames_bulk(queryset)
|
||||
else:
|
||||
for user in queryset: # queryset filtering doesn't work here?
|
||||
service.sync_nickname(user)
|
||||
|
||||
sync_nickname.__name__ = str('sync_{}_nickname'.format(slugify(service.name)))
|
||||
sync_nickname.short_description = "Sync nicknames for selected {} accounts".format(service.title)
|
||||
@@ -83,41 +107,324 @@ class UserProfileInline(admin.StackedInline):
|
||||
return False
|
||||
|
||||
|
||||
def user_profile_pic(obj):
|
||||
"""profile pic column data for user objects
|
||||
|
||||
works for both User objects and objects with `user` as FK to User
|
||||
To be used for all user based admin lists (requires CSS)
|
||||
"""
|
||||
user_obj = obj.user if hasattr(obj, 'user') else obj
|
||||
if user_obj.profile.main_character:
|
||||
return format_html(
|
||||
'<img src="{}" class="img-circle">',
|
||||
user_obj.profile.main_character.portrait_url(size=32)
|
||||
)
|
||||
else:
|
||||
return None
|
||||
user_profile_pic.short_description = ''
|
||||
|
||||
|
||||
def user_username(obj):
|
||||
"""user column data for user objects
|
||||
|
||||
works for both User objects and objects with `user` as FK to User
|
||||
To be used for all user based admin lists
|
||||
"""
|
||||
link = reverse(
|
||||
'admin:{}_{}_change'.format(
|
||||
obj._meta.app_label,
|
||||
type(obj).__name__.lower()
|
||||
),
|
||||
args=(obj.pk,)
|
||||
)
|
||||
user_obj = obj.user if hasattr(obj, 'user') else obj
|
||||
if user_obj.profile.main_character:
|
||||
return format_html(
|
||||
'<strong><a href="{}">{}</a></strong><br>{}',
|
||||
link,
|
||||
user_obj.username,
|
||||
user_obj.profile.main_character.character_name
|
||||
)
|
||||
else:
|
||||
return format_html(
|
||||
'<strong><a href="{}">{}</a></strong>',
|
||||
link,
|
||||
user_obj.username,
|
||||
)
|
||||
|
||||
user_username.short_description = 'user / main'
|
||||
user_username.admin_order_field = 'username'
|
||||
|
||||
|
||||
def user_main_organization(obj):
|
||||
"""main organization column data for user objects
|
||||
|
||||
works for both User objects and objects with `user` as FK to User
|
||||
To be used for all user based admin lists
|
||||
"""
|
||||
user_obj = obj.user if hasattr(obj, 'user') else obj
|
||||
if not user_obj.profile.main_character:
|
||||
result = None
|
||||
else:
|
||||
corporation = user_obj.profile.main_character.corporation_name
|
||||
if user_obj.profile.main_character.alliance_id:
|
||||
result = format_html('{}<br>{}',
|
||||
corporation,
|
||||
user_obj.profile.main_character.alliance_name
|
||||
)
|
||||
else:
|
||||
result = corporation
|
||||
return result
|
||||
|
||||
user_main_organization.short_description = 'Corporation / Alliance (Main)'
|
||||
user_main_organization.admin_order_field = \
|
||||
'profile__main_character__corporation_name'
|
||||
|
||||
|
||||
class MainCorporationsFilter(admin.SimpleListFilter):
|
||||
"""Custom filter to filter on corporations from mains only
|
||||
|
||||
works for both User objects and objects with `user` as FK to User
|
||||
To be used for all user based admin lists
|
||||
"""
|
||||
title = 'corporation'
|
||||
parameter_name = 'main_corporation_id__exact'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
qs = EveCharacter.objects\
|
||||
.exclude(userprofile=None)\
|
||||
.values('corporation_id', 'corporation_name')\
|
||||
.distinct()\
|
||||
.order_by(Lower('corporation_name'))
|
||||
return tuple(
|
||||
[(x['corporation_id'], x['corporation_name']) for x in qs]
|
||||
)
|
||||
|
||||
def queryset(self, request, qs):
|
||||
if self.value() is None:
|
||||
return qs.all()
|
||||
else:
|
||||
if qs.model == User:
|
||||
return qs\
|
||||
.filter(profile__main_character__corporation_id=\
|
||||
self.value())
|
||||
else:
|
||||
return qs\
|
||||
.filter(user__profile__main_character__corporation_id=\
|
||||
self.value())
|
||||
|
||||
|
||||
class MainAllianceFilter(admin.SimpleListFilter):
|
||||
"""Custom filter to filter on alliances from mains only
|
||||
|
||||
works for both User objects and objects with `user` as FK to User
|
||||
To be used for all user based admin lists
|
||||
"""
|
||||
title = 'alliance'
|
||||
parameter_name = 'main_alliance_id__exact'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
qs = EveCharacter.objects\
|
||||
.exclude(alliance_id=None)\
|
||||
.exclude(userprofile=None)\
|
||||
.values('alliance_id', 'alliance_name')\
|
||||
.distinct()\
|
||||
.order_by(Lower('alliance_name'))
|
||||
return tuple(
|
||||
[(x['alliance_id'], x['alliance_name']) for x in qs]
|
||||
)
|
||||
|
||||
def queryset(self, request, qs):
|
||||
if self.value() is None:
|
||||
return qs.all()
|
||||
else:
|
||||
if qs.model == User:
|
||||
return qs\
|
||||
.filter(profile__main_character__alliance_id=self.value())
|
||||
else:
|
||||
return qs\
|
||||
.filter(user__profile__main_character__alliance_id=\
|
||||
self.value())
|
||||
|
||||
|
||||
def update_main_character_model(modeladmin, request, queryset):
|
||||
tasks_count = 0
|
||||
for obj in queryset:
|
||||
if obj.profile.main_character:
|
||||
update_character.delay(obj.profile.main_character.character_id)
|
||||
tasks_count += 1
|
||||
|
||||
modeladmin.message_user(
|
||||
request,
|
||||
'Update from ESI started for {} characters'.format(tasks_count)
|
||||
)
|
||||
|
||||
update_main_character_model.short_description = \
|
||||
'Update main character model from ESI'
|
||||
|
||||
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
"""Extending Django's UserAdmin model
|
||||
|
||||
Behavior of groups and characters columns can be configured via settings
|
||||
|
||||
"""
|
||||
Extending Django's UserAdmin model
|
||||
"""
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
"all": ("authentication/css/admin.css",)
|
||||
}
|
||||
|
||||
class RealGroupsFilter(admin.SimpleListFilter):
|
||||
"""Custom filter to get groups w/o Autogroups"""
|
||||
title = 'group'
|
||||
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):
|
||||
actions = super(BaseUserAdmin, self).get_actions(request)
|
||||
|
||||
actions[update_main_character_model.__name__] = (
|
||||
update_main_character_model,
|
||||
update_main_character_model.__name__,
|
||||
update_main_character_model.short_description
|
||||
)
|
||||
|
||||
for hook in get_hooks('services_hook'):
|
||||
svc = hook()
|
||||
# Check update_groups is redefined/overloaded
|
||||
if svc.update_groups.__module__ != ServicesHook.update_groups.__module__:
|
||||
action = make_service_hooks_update_groups_action(svc)
|
||||
actions[action.__name__] = (action,
|
||||
action.__name__,
|
||||
action.short_description)
|
||||
actions[action.__name__] = (
|
||||
action,
|
||||
action.__name__,
|
||||
action.short_description
|
||||
)
|
||||
|
||||
# Create sync nickname action if service implements it
|
||||
if svc.sync_nickname.__module__ != ServicesHook.sync_nickname.__module__:
|
||||
action = make_service_hooks_sync_nickname_action(svc)
|
||||
actions[action.__name__] = (action,
|
||||
action.__name__,
|
||||
action.short_description)
|
||||
|
||||
actions[action.__name__] = (
|
||||
action, action.__name__,
|
||||
action.short_description
|
||||
)
|
||||
return actions
|
||||
list_filter = BaseUserAdmin.list_filter + ('profile__state',)
|
||||
|
||||
def _list_2_html_w_tooltips(self, my_items: list, max_items: int) -> str:
|
||||
"""converts list of strings into HTML with cutoff and tooltip"""
|
||||
items_truncated_str = ', '.join(my_items[:max_items])
|
||||
if not my_items:
|
||||
result = None
|
||||
elif len(my_items) <= max_items:
|
||||
result = items_truncated_str
|
||||
else:
|
||||
items_truncated_str += ', (...)'
|
||||
items_all_str = ', '.join(my_items)
|
||||
result = format_html(
|
||||
'<span data-tooltip="{}" class="tooltip">{}</span>',
|
||||
items_all_str,
|
||||
items_truncated_str
|
||||
)
|
||||
return result
|
||||
|
||||
inlines = BaseUserAdmin.inlines + [UserProfileInline]
|
||||
list_display = ('username', 'email', 'get_main_character', 'get_state', 'is_active')
|
||||
|
||||
def get_main_character(self, obj):
|
||||
return obj.profile.main_character
|
||||
get_main_character.short_description = "Main Character"
|
||||
ordering = ('username', )
|
||||
list_select_related = True
|
||||
show_full_result_count = True
|
||||
|
||||
list_display = (
|
||||
user_profile_pic,
|
||||
user_username,
|
||||
'_state',
|
||||
'_groups',
|
||||
user_main_organization,
|
||||
'_characters',
|
||||
'is_active',
|
||||
'date_joined',
|
||||
'_role'
|
||||
)
|
||||
list_display_links = None
|
||||
|
||||
def get_state(self, obj):
|
||||
return obj.profile.state
|
||||
get_state.short_description = "State"
|
||||
list_filter = (
|
||||
'profile__state',
|
||||
RealGroupsFilter,
|
||||
MainCorporationsFilter,
|
||||
MainAllianceFilter,
|
||||
'is_active',
|
||||
'date_joined',
|
||||
'is_staff',
|
||||
'is_superuser'
|
||||
)
|
||||
search_fields = (
|
||||
'username',
|
||||
'character_ownerships__character__character_name'
|
||||
)
|
||||
|
||||
def _characters(self, obj):
|
||||
my_characters = [
|
||||
x.character.character_name
|
||||
for x in CharacterOwnership.objects\
|
||||
.filter(user=obj)\
|
||||
.order_by('character__character_name')\
|
||||
.select_related()
|
||||
]
|
||||
return self._list_2_html_w_tooltips(
|
||||
my_characters,
|
||||
AUTHENTICATION_ADMIN_USERS_MAX_CHARS
|
||||
)
|
||||
|
||||
_characters.short_description = 'characters'
|
||||
|
||||
|
||||
def _state(self, obj):
|
||||
return obj.profile.state.name
|
||||
|
||||
_state.short_description = 'state'
|
||||
_state.admin_order_field = 'profile__state'
|
||||
|
||||
def _groups(self, obj):
|
||||
if not _has_auto_groups:
|
||||
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(
|
||||
my_groups,
|
||||
AUTHENTICATION_ADMIN_USERS_MAX_GROUPS
|
||||
)
|
||||
|
||||
_groups.short_description = 'groups'
|
||||
|
||||
def _role(self, obj):
|
||||
if obj.is_superuser:
|
||||
role = 'Superuser'
|
||||
elif obj.is_staff:
|
||||
role = 'Staff'
|
||||
else:
|
||||
role = 'User'
|
||||
return role
|
||||
|
||||
_role.short_description = 'role'
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return request.user.has_perm('auth.change_user')
|
||||
|
||||
@@ -127,19 +434,54 @@ class UserAdmin(BaseUserAdmin):
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return request.user.has_perm('auth.delete_user')
|
||||
|
||||
def formfield_for_manytomany(self, db_field, request, **kwargs):
|
||||
"""overriding this formfield to have sorted lists in the form"""
|
||||
if db_field.name == "groups":
|
||||
kwargs["queryset"] = Group.objects.all().order_by(Lower('name'))
|
||||
return super().formfield_for_manytomany(db_field, request, **kwargs)
|
||||
|
||||
|
||||
@admin.register(State)
|
||||
class StateAdmin(admin.ModelAdmin):
|
||||
class StateAdmin(admin.ModelAdmin):
|
||||
list_select_related = True
|
||||
list_display = ('name', 'priority', '_user_count')
|
||||
|
||||
def _user_count(self, obj):
|
||||
return obj.userprofile_set.all().count()
|
||||
_user_count.short_description = 'Users'
|
||||
|
||||
fieldsets = (
|
||||
(None, {
|
||||
'fields': ('name', 'permissions', 'priority'),
|
||||
}),
|
||||
('Membership', {
|
||||
'fields': ('public', 'member_characters', 'member_corporations', 'member_alliances'),
|
||||
'fields': (
|
||||
'public',
|
||||
'member_characters',
|
||||
'member_corporations',
|
||||
'member_alliances'
|
||||
),
|
||||
})
|
||||
)
|
||||
filter_horizontal = ['member_characters', 'member_corporations', 'member_alliances', 'permissions']
|
||||
list_display = ('name', 'priority', 'user_count')
|
||||
filter_horizontal = [
|
||||
'member_characters',
|
||||
'member_corporations',
|
||||
'member_alliances',
|
||||
'permissions'
|
||||
]
|
||||
|
||||
def formfield_for_manytomany(self, db_field, request, **kwargs):
|
||||
"""overriding this formfield to have sorted lists in the form"""
|
||||
if db_field.name == "member_characters":
|
||||
kwargs["queryset"] = EveCharacter.objects.all()\
|
||||
.order_by(Lower('character_name'))
|
||||
elif db_field.name == "member_corporations":
|
||||
kwargs["queryset"] = EveCorporationInfo.objects.all()\
|
||||
.order_by(Lower('corporation_name'))
|
||||
elif db_field.name == "member_alliances":
|
||||
kwargs["queryset"] = EveAllianceInfo.objects.all()\
|
||||
.order_by(Lower('alliance_name'))
|
||||
return super().formfield_for_manytomany(db_field, request, **kwargs)
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
if obj == get_guest_state():
|
||||
@@ -154,15 +496,31 @@ class StateAdmin(admin.ModelAdmin):
|
||||
}),
|
||||
)
|
||||
return super(StateAdmin, self).get_fieldsets(request, obj=obj)
|
||||
|
||||
@staticmethod
|
||||
def user_count(obj):
|
||||
return obj.userprofile_set.all().count()
|
||||
|
||||
|
||||
|
||||
class BaseOwnershipAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'character')
|
||||
search_fields = ('user__username', 'character__character_name', 'character__corporation_name', 'character__alliance_name')
|
||||
class Media:
|
||||
css = {
|
||||
"all": ("authentication/css/admin.css",)
|
||||
}
|
||||
|
||||
list_select_related = True
|
||||
list_display = (
|
||||
user_profile_pic,
|
||||
user_username,
|
||||
user_main_organization,
|
||||
'character',
|
||||
)
|
||||
search_fields = (
|
||||
'user__username',
|
||||
'character__character_name',
|
||||
'character__corporation_name',
|
||||
'character__alliance_name'
|
||||
)
|
||||
list_filter = (
|
||||
MainCorporationsFilter,
|
||||
MainAllianceFilter,
|
||||
)
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
if obj and obj.pk:
|
||||
|
||||
46
allianceauth/authentication/app_settings.py
Normal file
46
allianceauth/authentication/app_settings.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
def _clean_setting(
|
||||
name: str,
|
||||
default_value: object,
|
||||
min_value: int = None,
|
||||
max_value: int = None,
|
||||
required_type: type = None
|
||||
):
|
||||
"""cleans the input for a custom setting
|
||||
|
||||
Will use `default_value` if settings does not exit or has the wrong type
|
||||
or is outside define boundaries (for int only)
|
||||
|
||||
Need to define `required_type` if `default_value` is `None`
|
||||
|
||||
Will assume `min_value` of 0 for int (can be overriden)
|
||||
|
||||
Returns cleaned value for setting
|
||||
"""
|
||||
if default_value is None and not required_type:
|
||||
raise ValueError('You must specify a required_type for None defaults')
|
||||
|
||||
if not required_type:
|
||||
required_type = type(default_value)
|
||||
|
||||
if min_value is None and required_type == int:
|
||||
min_value = 0
|
||||
|
||||
if (hasattr(settings, name)
|
||||
and isinstance(getattr(settings, name), required_type)
|
||||
and (min_value is None or getattr(settings, name) >= min_value)
|
||||
and (max_value is None or getattr(settings, name) <= max_value)
|
||||
):
|
||||
return getattr(settings, name)
|
||||
else:
|
||||
return default_value
|
||||
|
||||
|
||||
AUTHENTICATION_ADMIN_USERS_MAX_GROUPS = \
|
||||
_clean_setting('AUTHENTICATION_ADMIN_USERS_MAX_GROUPS', 10)
|
||||
|
||||
AUTHENTICATION_ADMIN_USERS_MAX_CHARS = \
|
||||
_clean_setting('AUTHENTICATION_ADMIN_USERS_MAX_CHARS', 5)
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def remove_permission(apps, schema_editor):
|
||||
User = apps.get_model('auth', 'User')
|
||||
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||
Permission = apps.get_model('auth', 'Permission')
|
||||
ct = ContentType.objects.get_for_model(User)
|
||||
Permission.objects.filter(codename="view_fleetup", content_type=ct, name="view_fleetup").delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('authentication', '0016_ownershiprecord'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(remove_permission, migrations.RunPython.noop)
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
CSS for allianceauth admin site
|
||||
*/
|
||||
|
||||
/* styling for profile pic */
|
||||
.img-circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.column-user_profile_pic {
|
||||
width: 1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* tooltip */
|
||||
.tooltip {
|
||||
position: relative ;
|
||||
}
|
||||
.tooltip:hover::after {
|
||||
content: attr(data-tooltip) ;
|
||||
position: absolute ;
|
||||
top: 1.1em ;
|
||||
left: 1em ;
|
||||
min-width: 200px ;
|
||||
border: 1px #808080 solid ;
|
||||
padding: 8px ;
|
||||
color: black ;
|
||||
background-color: rgb(255, 255, 204) ;
|
||||
z-index: 1 ;
|
||||
}
|
||||
@@ -10,77 +10,99 @@
|
||||
{% include 'allianceauth/admin-status/include.html' %}
|
||||
{% endif %}
|
||||
<div class="col-sm-12">
|
||||
<div class="row vertical-flexbox-row">
|
||||
<div class="row vertical-flexbox-row2">
|
||||
<div class="col-sm-6 text-center">
|
||||
<div class="panel panel-primary" style="height:100%">
|
||||
<div class="panel-heading"><h3 class="panel-title">{% trans "Main Character" %}</h3></div>
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans "Main Character" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if request.user.profile.main_character %}
|
||||
{% with request.user.profile.main_character as main %}
|
||||
<div class="col-lg-4 col-sm-2">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center"><img class="ra-avatar"
|
||||
src="{{ main.portrait_url_128 }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center">{{ main.character_name }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-2">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center"><img class="ra-avatar"
|
||||
src="https://image.eveonline.com/Corporation/{{ main.corporation_id }}_128.png">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center">{{ main.corporation_name }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-2">
|
||||
{% if main.alliance_id %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center"><img class="ra-avatar"
|
||||
src="https://image.eveonline.com/Alliance/{{ main.alliance_id }}_128.png">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center">{{ main.alliance_name }}</td>
|
||||
<tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% with request.user.profile.main_character as main %}
|
||||
<div class="hidden-xs">
|
||||
<div class="col-lg-4 col-sm-2">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar"src="{{ main.portrait_url_128 }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center">{{ main.character_name }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-2">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar"src="{{ main.corporation_logo_url_128 }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center">{{ main.corporation_name }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-lg-4 col-sm-2">
|
||||
{% if main.alliance_id %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar"src="{{ main.alliance_logo_url_128 }}">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-center">{{ main.alliance_name }}</td>
|
||||
<tr>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="table visible-xs-block">
|
||||
<p>
|
||||
<img class="ra-avatar" src="{{ main.portrait_url_64 }}">
|
||||
<img class="ra-avatar" src="{{ main.corporation_logo_url_64 }}">
|
||||
<img class="ra-avatar" src="{{ main.alliance_logo_url_64 }}">
|
||||
</p>
|
||||
<p>
|
||||
<strong>{{ main.character_name }}</strong><br>
|
||||
{{ main.corporation_name }}<br>
|
||||
{{ main.alliance_name }}
|
||||
</p>
|
||||
</div>
|
||||
{% endwith %}
|
||||
{% else %}
|
||||
<div class="alert alert-danger" role="alert">{% trans "No main character set." %}</div>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{% trans "No main character set." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
<div class="col-xs-6">
|
||||
<a href="{% url 'authentication:add_character' %}" class="btn btn-block btn-info"
|
||||
title="Add Character">{% trans 'Add Character' %}</a>
|
||||
</div>
|
||||
<div class="col-xs-6">
|
||||
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-block btn-info"
|
||||
title="Change Main Character">{% trans "Change Main" %}</a>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 button-wrapper">
|
||||
<a href="{% url 'authentication:add_character' %}" class="btn btn-block btn-info"
|
||||
title="Add Character">{% trans 'Add Character' %}</a>
|
||||
</div>
|
||||
<div class="col-sm-6 button-wrapper">
|
||||
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-block btn-info"
|
||||
title="Change Main Character">{% trans "Change Main" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 text-center">
|
||||
<div class="panel panel-success" style="height:100%">
|
||||
<div class="panel-heading"><h3 class="panel-title">{% trans "Groups" %}</h3></div>
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans "Group Memberships" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div style="height: 240px;overflow:-moz-scrollbars-vertical;overflow-y:auto;">
|
||||
<table class="table table-striped">
|
||||
{% for group in user.groups.all %}
|
||||
<tr>
|
||||
<td>{{ group.name }}</td>
|
||||
</tr>
|
||||
<table class="table table-aa">
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td>{{ group.name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
@@ -90,26 +112,48 @@
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" style="display:flex;"><h3 class="panel-title">{% trans 'Characters' %}</h3></div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-hover">
|
||||
<tr>
|
||||
<th class="text-center"></th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Corp' %}</th>
|
||||
<th class="text-center">{% trans 'Alliance' %}</th>
|
||||
</tr>
|
||||
{% for ownership in request.user.character_ownerships.all %}
|
||||
{% with ownership.character as char %}
|
||||
<tr>
|
||||
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
||||
</td>
|
||||
<td class="text-center">{{ char.character_name }}</td>
|
||||
<td class="text-center">{{ char.corporation_name }}</td>
|
||||
<td class="text-center">{{ char.alliance_name }}</td>
|
||||
</tr>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title text-center" style="text-align: center">
|
||||
{% trans 'Characters' %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-aa hidden-xs">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"></th>
|
||||
<th class="text-center">{% trans 'Name' %}</th>
|
||||
<th class="text-center">{% trans 'Corp' %}</th>
|
||||
<th class="text-center">{% trans 'Alliance' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for char in characters %}
|
||||
<tr>
|
||||
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
||||
</td>
|
||||
<td class="text-center">{{ char.character_name }}</td>
|
||||
<td class="text-center">{{ char.corporation_name }}</td>
|
||||
<td class="text-center">{{ char.alliance_name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="table table-aa visible-xs-block" style="width: 100%">
|
||||
<tbody>
|
||||
{% for char in characters %}
|
||||
<tr>
|
||||
<td class="text-center" style="vertical-align: middle">
|
||||
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
|
||||
</td>
|
||||
<td class="text-center" style="vertical-align: middle; width: 100%">
|
||||
<strong>{{ char.character_name }}</strong><br>
|
||||
{{ char.corporation_name }}<br>
|
||||
{{ char.alliance_name|default:"" }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<meta property="og:title" content="{{ SITE_NAME }}">
|
||||
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'icons/apple-touch-icon.png' %}">
|
||||
<meta property="og:description" content="Alliance Auth - An auth system for EVE Online to help in-game organizations manage online service access.">
|
||||
|
||||
{% include 'allianceauth/icons.html' %}
|
||||
|
||||
<title>{% block title %}{{ SITE_NAME }}{% endblock %}</title>
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
{% extends 'public/middle_box.html' %}
|
||||
{% load static %}
|
||||
{% block page_title %}Login{% endblock %}
|
||||
{% block middle_box_content %}
|
||||
<p style="text-align:center">
|
||||
<a href="{% url 'auth_sso_login' %}">
|
||||
<img src="{% static 'img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" border=0>
|
||||
</a>
|
||||
</p>
|
||||
{% block middle_box_content %}
|
||||
<a href="{% url 'auth_sso_login' %}{% if request.GET.next %}?next={{request.GET.next}}{%endif%}">
|
||||
<img class="img-responsive center-block" src="{% static 'img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" border=0>
|
||||
</a>
|
||||
{% endblock %}
|
||||
@@ -1,6 +1,10 @@
|
||||
You're receiving this email because someone has entered this email address while registering for an account on {{ site.domain }}
|
||||
|
||||
If this was you, please go to the following URL to confirm your email address:
|
||||
If this was you, please click on the link below to confirm your email address:
|
||||
|
||||
<a href="{{ scheme }}://{{ url }}">Confirm email address</a>
|
||||
|
||||
Link not working? Try copy/pasting this URL into your browser:
|
||||
|
||||
{{ scheme }}://{{ url }}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{% load i18n %}{% autoescape off %}
|
||||
{% blocktrans %}You're receiving this email because you requested a password reset for your
|
||||
{% blocktrans trimmed %}You're receiving this email because you requested a password reset for your
|
||||
user account.{% endblocktrans %}
|
||||
|
||||
{% trans "Please go to the following page and choose a new password:" %}
|
||||
|
||||
18
allianceauth/authentication/tests/__init__.py
Normal file
18
allianceauth/authentication/tests/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
def get_admin_change_view_url(obj: object) -> str:
|
||||
"""returns URL to admin change view for given object"""
|
||||
return reverse(
|
||||
'admin:{}_{}_change'.format(
|
||||
obj._meta.app_label, type(obj).__name__.lower()
|
||||
),
|
||||
args=(obj.pk,)
|
||||
)
|
||||
|
||||
def get_admin_search_url(ModelClass: type) -> str:
|
||||
"""returns URL to search URL for model of given object"""
|
||||
return '{}{}/'.format(
|
||||
reverse('admin:app_list', args=(ModelClass._meta.app_label,)),
|
||||
ModelClass.__name__.lower()
|
||||
)
|
||||
635
allianceauth/authentication/tests/test_admin.py
Normal file
635
allianceauth/authentication/tests/test_admin.py
Normal file
@@ -0,0 +1,635 @@
|
||||
from urllib.parse import quote
|
||||
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.auth.models import User as BaseUser, Group
|
||||
from django.test import TestCase, RequestFactory, Client
|
||||
|
||||
from allianceauth.authentication.models import (
|
||||
CharacterOwnership, State, OwnershipRecord
|
||||
)
|
||||
from allianceauth.eveonline.models import (
|
||||
EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
)
|
||||
from allianceauth.services.hooks import ServicesHook
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..admin import (
|
||||
BaseUserAdmin,
|
||||
CharacterOwnershipAdmin,
|
||||
PermissionAdmin,
|
||||
StateAdmin,
|
||||
MainCorporationsFilter,
|
||||
MainAllianceFilter,
|
||||
OwnershipRecordAdmin,
|
||||
User,
|
||||
UserAdmin,
|
||||
user_main_organization,
|
||||
user_profile_pic,
|
||||
user_username,
|
||||
update_main_character_model,
|
||||
make_service_hooks_update_groups_action,
|
||||
make_service_hooks_sync_nickname_action
|
||||
)
|
||||
from . import get_admin_change_view_url, get_admin_search_url
|
||||
|
||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||
_has_auto_groups = True
|
||||
from allianceauth.eveonline.autogroups.models import AutogroupsConfig
|
||||
else:
|
||||
_has_auto_groups = False
|
||||
|
||||
MODULE_PATH = 'allianceauth.authentication.admin'
|
||||
|
||||
|
||||
class MockRequest(object):
|
||||
def __init__(self, user=None):
|
||||
self.user = user
|
||||
|
||||
class TestCaseWithTestData(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
for MyModel in [
|
||||
EveAllianceInfo, EveCorporationInfo, EveCharacter, Group, User
|
||||
]:
|
||||
MyModel.objects.all().delete()
|
||||
|
||||
# groups
|
||||
cls.group_1 = Group.objects.create(
|
||||
name='Group 1'
|
||||
)
|
||||
cls.group_2 = Group.objects.create(
|
||||
name='Group 2'
|
||||
)
|
||||
|
||||
# user 1 - corp and alliance, normal user
|
||||
character_1 = EveCharacter.objects.create(
|
||||
character_id='1001',
|
||||
character_name='Bruce Wayne',
|
||||
corporation_id='2001',
|
||||
corporation_name='Wayne Technologies',
|
||||
corporation_ticker='WT',
|
||||
alliance_id='3001',
|
||||
alliance_name='Wayne Enterprises',
|
||||
alliance_ticker='WE',
|
||||
)
|
||||
character_1a = EveCharacter.objects.create(
|
||||
character_id='1002',
|
||||
character_name='Batman',
|
||||
corporation_id='2001',
|
||||
corporation_name='Wayne Technologies',
|
||||
corporation_ticker='WT',
|
||||
alliance_id='3001',
|
||||
alliance_name='Wayne Enterprises',
|
||||
alliance_ticker='WE',
|
||||
)
|
||||
alliance = EveAllianceInfo.objects.create(
|
||||
alliance_id='3001',
|
||||
alliance_name='Wayne Enterprises',
|
||||
alliance_ticker='WE',
|
||||
executor_corp_id='2001'
|
||||
)
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id='2001',
|
||||
corporation_name='Wayne Technologies',
|
||||
corporation_ticker='WT',
|
||||
member_count=42,
|
||||
alliance=alliance
|
||||
)
|
||||
cls.user_1 = User.objects.create_user(
|
||||
character_1.character_name.replace(' ', '_'),
|
||||
'abc@example.com',
|
||||
'password'
|
||||
)
|
||||
CharacterOwnership.objects.create(
|
||||
character=character_1,
|
||||
owner_hash='x1' + character_1.character_name,
|
||||
user=cls.user_1
|
||||
)
|
||||
CharacterOwnership.objects.create(
|
||||
character=character_1a,
|
||||
owner_hash='x1' + character_1a.character_name,
|
||||
user=cls.user_1
|
||||
)
|
||||
cls.user_1.profile.main_character = character_1
|
||||
cls.user_1.profile.save()
|
||||
cls.user_1.groups.add(cls.group_1)
|
||||
|
||||
# user 2 - corp only, staff
|
||||
character_2 = EveCharacter.objects.create(
|
||||
character_id=1003,
|
||||
character_name='Clark Kent',
|
||||
corporation_id=2002,
|
||||
corporation_name='Daily Planet',
|
||||
corporation_ticker='DP',
|
||||
alliance_id=None
|
||||
)
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id=2002,
|
||||
corporation_name='Daily Plane',
|
||||
corporation_ticker='DP',
|
||||
member_count=99,
|
||||
alliance=None
|
||||
)
|
||||
cls.user_2 = User.objects.create_user(
|
||||
character_2.character_name.replace(' ', '_'),
|
||||
'abc@example.com',
|
||||
'password'
|
||||
)
|
||||
CharacterOwnership.objects.create(
|
||||
character=character_2,
|
||||
owner_hash='x1' + character_2.character_name,
|
||||
user=cls.user_2
|
||||
)
|
||||
cls.user_2.profile.main_character = character_2
|
||||
cls.user_2.profile.save()
|
||||
cls.user_2.groups.add(cls.group_2)
|
||||
cls.user_2.is_staff = True
|
||||
cls.user_2.save()
|
||||
|
||||
# user 3 - no main, no group, superuser
|
||||
character_3 = EveCharacter.objects.create(
|
||||
character_id=1101,
|
||||
character_name='Lex Luthor',
|
||||
corporation_id=2101,
|
||||
corporation_name='Lex Corp',
|
||||
corporation_ticker='LC',
|
||||
alliance_id=None
|
||||
)
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id=2101,
|
||||
corporation_name='Lex Corp',
|
||||
corporation_ticker='LC',
|
||||
member_count=666,
|
||||
alliance=None
|
||||
)
|
||||
EveAllianceInfo.objects.create(
|
||||
alliance_id='3101',
|
||||
alliance_name='Lex World Domination',
|
||||
alliance_ticker='LWD',
|
||||
executor_corp_id=''
|
||||
)
|
||||
cls.user_3 = User.objects.create_user(
|
||||
character_3.character_name.replace(' ', '_'),
|
||||
'abc@example.com',
|
||||
'password'
|
||||
)
|
||||
CharacterOwnership.objects.create(
|
||||
character=character_3,
|
||||
owner_hash='x1' + character_3.character_name,
|
||||
user=cls.user_3
|
||||
)
|
||||
cls.user_3.is_superuser = True
|
||||
cls.user_3.save()
|
||||
|
||||
|
||||
def make_generic_search_request(ModelClass: type, search_term: str):
|
||||
User.objects.create_superuser(
|
||||
username='superuser', password='secret', email='admin@example.com'
|
||||
)
|
||||
c = Client()
|
||||
c.login(username='superuser', password='secret')
|
||||
return c.get(
|
||||
'%s?q=%s' % (get_admin_search_url(ModelClass), quote(search_term))
|
||||
)
|
||||
|
||||
|
||||
class TestCharacterOwnershipAdmin(TestCaseWithTestData):
|
||||
|
||||
def setUp(self):
|
||||
self.modeladmin = CharacterOwnershipAdmin(
|
||||
model=User, admin_site=AdminSite()
|
||||
)
|
||||
|
||||
def test_change_view_loads_normally(self):
|
||||
User.objects.create_superuser(
|
||||
username='superuser', password='secret', email='admin@example.com'
|
||||
)
|
||||
c = Client()
|
||||
c.login(username='superuser', password='secret')
|
||||
ownership = self.user_1.character_ownerships.first()
|
||||
response = c.get(get_admin_change_view_url(ownership))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = CharacterOwnership.objects\
|
||||
.filter(user=self.user_1)\
|
||||
.first()
|
||||
response = make_generic_search_request(type(obj), obj.user.username)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
|
||||
|
||||
class TestOwnershipRecordAdmin(TestCaseWithTestData):
|
||||
|
||||
def setUp(self):
|
||||
self.modeladmin = OwnershipRecordAdmin(
|
||||
model=User, admin_site=AdminSite()
|
||||
)
|
||||
|
||||
def test_change_view_loads_normally(self):
|
||||
User.objects.create_superuser(
|
||||
username='superuser', password='secret', email='admin@example.com'
|
||||
)
|
||||
c = Client()
|
||||
c.login(username='superuser', password='secret')
|
||||
ownership_record = OwnershipRecord.objects\
|
||||
.filter(user=self.user_1)\
|
||||
.first()
|
||||
response = c.get(get_admin_change_view_url(ownership_record))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = OwnershipRecord.objects.first()
|
||||
response = make_generic_search_request(type(obj), obj.user.username)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
|
||||
|
||||
class TestStateAdmin(TestCaseWithTestData):
|
||||
|
||||
def setUp(self):
|
||||
self.modeladmin = StateAdmin(
|
||||
model=User, admin_site=AdminSite()
|
||||
)
|
||||
|
||||
def test_change_view_loads_normally(self):
|
||||
User.objects.create_superuser(
|
||||
username='superuser', password='secret', email='admin@example.com'
|
||||
)
|
||||
c = Client()
|
||||
c.login(username='superuser', password='secret')
|
||||
|
||||
guest_state = AuthUtils.get_guest_state()
|
||||
response = c.get(get_admin_change_view_url(guest_state))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
member_state = AuthUtils.get_member_state()
|
||||
response = c.get(get_admin_change_view_url(member_state))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = State.objects.first()
|
||||
response = make_generic_search_request(type(obj), obj.name)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
|
||||
class TestUserAdmin(TestCaseWithTestData):
|
||||
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
self.modeladmin = UserAdmin(
|
||||
model=User, admin_site=AdminSite()
|
||||
)
|
||||
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):
|
||||
expected = ('<img src="https://images.evetech.net/characters/1001/'
|
||||
'portrait?size=32" class="img-circle">')
|
||||
self.assertEqual(user_profile_pic(self.user_1), expected)
|
||||
|
||||
def test_user_profile_pic_u3(self):
|
||||
self.assertIsNone(user_profile_pic(self.user_3))
|
||||
|
||||
def test_user_username_u1(self):
|
||||
expected = (
|
||||
'<strong><a href="/admin/authentication/user/{}/change/">'
|
||||
'Bruce_Wayne</a></strong><br>Bruce Wayne'.format(self.user_1.pk)
|
||||
)
|
||||
self.assertEqual(user_username(self.user_1), expected)
|
||||
|
||||
def test_user_username_u3(self):
|
||||
expected = (
|
||||
'<strong><a href="/admin/authentication/user/{}/change/">'
|
||||
'Lex_Luthor</a></strong>'.format(self.user_3.pk)
|
||||
)
|
||||
self.assertEqual(user_username(self.user_3), expected)
|
||||
|
||||
def test_user_main_organization_u1(self):
|
||||
expected = 'Wayne Technologies<br>Wayne Enterprises'
|
||||
self.assertEqual(user_main_organization(self.user_1), expected)
|
||||
|
||||
def test_user_main_organization_u2(self):
|
||||
expected = 'Daily Planet'
|
||||
self.assertEqual(user_main_organization(self.user_2), expected)
|
||||
|
||||
def test_user_main_organization_u3(self):
|
||||
expected = None
|
||||
self.assertEqual(user_main_organization(self.user_3), expected)
|
||||
|
||||
def test_characters_u1(self):
|
||||
expected = 'Batman, Bruce Wayne'
|
||||
result = self.modeladmin._characters(self.user_1)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_characters_u2(self):
|
||||
expected = 'Clark Kent'
|
||||
result = self.modeladmin._characters(self.user_2)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_characters_u3(self):
|
||||
expected = 'Lex Luthor'
|
||||
result = self.modeladmin._characters(self.user_3)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_groups_u1(self):
|
||||
self._create_autogroups()
|
||||
expected = 'Group 1'
|
||||
result = self.modeladmin._groups(self.user_1)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_groups_u2(self):
|
||||
self._create_autogroups()
|
||||
expected = 'Group 2'
|
||||
result = self.modeladmin._groups(self.user_2)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
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)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_state(self):
|
||||
expected = 'Guest'
|
||||
result = self.modeladmin._state(self.user_1)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_role_u1(self):
|
||||
expected = 'User'
|
||||
result = self.modeladmin._role(self.user_1)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_role_u2(self):
|
||||
expected = 'Staff'
|
||||
result = self.modeladmin._role(self.user_2)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_role_u3(self):
|
||||
expected = 'Superuser'
|
||||
result = self.modeladmin._role(self.user_3)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_list_2_html_w_tooltips_no_cutoff(self):
|
||||
items = ['one', 'two', 'three']
|
||||
expected = 'one, two, three'
|
||||
result = self.modeladmin._list_2_html_w_tooltips(items, 5)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_list_2_html_w_tooltips_w_cutoff(self):
|
||||
items = ['one', 'two', 'three']
|
||||
expected = ('<span data-tooltip="one, two, three" '
|
||||
'class="tooltip">one, two, (...)</span>')
|
||||
result = self.modeladmin._list_2_html_w_tooltips(items, 2)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_list_2_html_w_tooltips_empty_list(self):
|
||||
items = []
|
||||
expected = None
|
||||
result = self.modeladmin._list_2_html_w_tooltips(items, 5)
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
# actions
|
||||
|
||||
@patch(MODULE_PATH + '.UserAdmin.message_user', auto_spec=True)
|
||||
@patch(MODULE_PATH + '.update_character')
|
||||
def test_action_update_main_character_model(
|
||||
self, mock_task, mock_message_user
|
||||
):
|
||||
users_qs = User.objects.filter(pk__in=[self.user_1.pk, self.user_2.pk])
|
||||
update_main_character_model(
|
||||
self.modeladmin, MockRequest(self.user_1), users_qs
|
||||
)
|
||||
self.assertEqual(mock_task.delay.call_count, 2)
|
||||
self.assertTrue(mock_message_user.called)
|
||||
|
||||
# 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):
|
||||
|
||||
class UserAdminTest(BaseUserAdmin):
|
||||
list_filter = (MainCorporationsFilter,)
|
||||
|
||||
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 = [
|
||||
('2002', 'Daily Planet'),
|
||||
('2001', 'Wayne Technologies'),
|
||||
]
|
||||
self.assertEqual(filterspec.lookup_choices, expected)
|
||||
|
||||
# Make sure the correct queryset is returned
|
||||
request = self.factory.get(
|
||||
'/',
|
||||
{'main_corporation_id__exact': self.character_1.corporation_id}
|
||||
)
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
queryset = changelist.get_queryset(request)
|
||||
expected = [self.user_1]
|
||||
self.assertSetEqual(set(queryset), set(expected))
|
||||
|
||||
def test_filter_main_alliances(self):
|
||||
|
||||
class UserAdminTest(BaseUserAdmin):
|
||||
list_filter = (MainAllianceFilter,)
|
||||
|
||||
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 = [
|
||||
('3001', 'Wayne Enterprises'),
|
||||
]
|
||||
self.assertEqual(filterspec.lookup_choices, expected)
|
||||
|
||||
# Make sure the correct queryset is returned
|
||||
request = self.factory.get(
|
||||
'/',
|
||||
{'main_alliance_id__exact': self.character_1.alliance_id}
|
||||
)
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
queryset = changelist.get_queryset(request)
|
||||
expected = [self.user_1]
|
||||
self.assertSetEqual(set(queryset), set(expected))
|
||||
|
||||
def test_change_view_loads_normally(self):
|
||||
User.objects.create_superuser(
|
||||
username='superuser', password='secret', email='admin@example.com'
|
||||
)
|
||||
c = Client()
|
||||
c.login(username='superuser', password='secret')
|
||||
response = c.get(get_admin_change_view_url(self.user_1))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_search_works(self):
|
||||
obj = User.objects.first()
|
||||
response = make_generic_search_request(type(obj), obj.username)
|
||||
expected = 200
|
||||
self.assertEqual(response.status_code, expected)
|
||||
|
||||
|
||||
class TestMakeServicesHooksActions(TestCaseWithTestData):
|
||||
|
||||
class MyServicesHookTypeA(ServicesHook):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.name = 'My Service A'
|
||||
|
||||
def update_groups(self, user):
|
||||
pass
|
||||
|
||||
def sync_nicknames(self, user):
|
||||
pass
|
||||
|
||||
class MyServicesHookTypeB(ServicesHook):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.name = 'My Service B'
|
||||
|
||||
def update_groups(self, user):
|
||||
pass
|
||||
|
||||
def update_groups_bulk(self, user):
|
||||
pass
|
||||
|
||||
def sync_nicknames(self, user):
|
||||
pass
|
||||
|
||||
def sync_nicknames_bulk(self, user):
|
||||
pass
|
||||
|
||||
|
||||
def test_service_has_update_groups_only(self):
|
||||
service = self.MyServicesHookTypeA()
|
||||
mock_service = MagicMock(spec=service)
|
||||
action = make_service_hooks_update_groups_action(mock_service)
|
||||
action(MagicMock(), MagicMock(), [self.user_1])
|
||||
self.assertTrue(mock_service.update_groups.called)
|
||||
|
||||
def test_service_has_update_groups_bulk(self):
|
||||
service = self.MyServicesHookTypeB()
|
||||
mock_service = MagicMock(spec=service)
|
||||
action = make_service_hooks_update_groups_action(mock_service)
|
||||
action(MagicMock(), MagicMock(), [self.user_1])
|
||||
self.assertFalse(mock_service.update_groups.called)
|
||||
self.assertTrue(mock_service.update_groups_bulk.called)
|
||||
|
||||
def test_service_has_sync_nickname_only(self):
|
||||
service = self.MyServicesHookTypeA()
|
||||
mock_service = MagicMock(spec=service)
|
||||
action = make_service_hooks_sync_nickname_action(mock_service)
|
||||
action(MagicMock(), MagicMock(), [self.user_1])
|
||||
self.assertTrue(mock_service.sync_nickname.called)
|
||||
|
||||
def test_service_has_sync_nicknames_bulk(self):
|
||||
service = self.MyServicesHookTypeB()
|
||||
mock_service = MagicMock(spec=service)
|
||||
action = make_service_hooks_sync_nickname_action(mock_service)
|
||||
action(MagicMock(), MagicMock(), [self.user_1])
|
||||
self.assertFalse(mock_service.sync_nickname.called)
|
||||
self.assertTrue(mock_service.sync_nicknames_bulk.called)
|
||||
@@ -1,23 +1,28 @@
|
||||
from unittest import mock
|
||||
from io import StringIO
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from .models import CharacterOwnership, UserProfile, State, get_guest_state, OwnershipRecord
|
||||
from .backends import StateBackend
|
||||
from .tasks import check_character_ownership
|
||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
from esi.models import Token
|
||||
from esi.errors import IncompleteResponseError
|
||||
from allianceauth.authentication.decorators import main_character_required
|
||||
from django.test.client import RequestFactory
|
||||
from django.http.response import HttpResponse
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.conf import settings
|
||||
from django.shortcuts import reverse
|
||||
from django.core.management import call_command
|
||||
from urllib import parse
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser, User
|
||||
from django.core.management import call_command
|
||||
from django.http.response import HttpResponse
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
|
||||
|
||||
from allianceauth.authentication.decorators import main_character_required
|
||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\
|
||||
EveAllianceInfo
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from esi.errors import IncompleteResponseError
|
||||
from esi.models import Token
|
||||
|
||||
from ..backends import StateBackend
|
||||
from ..models import CharacterOwnership, UserProfile, State, get_guest_state,\
|
||||
OwnershipRecord
|
||||
from ..tasks import check_character_ownership
|
||||
|
||||
MODULE_PATH = 'allianceauth.authentication'
|
||||
|
||||
|
||||
102
allianceauth/authentication/tests/test_app_settings.py
Normal file
102
allianceauth/authentication/tests/test_app_settings.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from unittest.mock import Mock, patch
|
||||
from django.test import TestCase
|
||||
|
||||
from .. import app_settings
|
||||
|
||||
MODULE_PATH = 'allianceauth.authentication'
|
||||
|
||||
|
||||
class TestSetAppSetting(TestCase):
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_if_not_set(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = Mock(spec=None)
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
False,
|
||||
)
|
||||
self.assertEqual(result, False)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_if_not_set_for_none(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = Mock(spec=None)
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
None,
|
||||
required_type=int
|
||||
)
|
||||
self.assertEqual(result, None)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_true_stays_true(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = True
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
False,
|
||||
)
|
||||
self.assertEqual(result, True)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_false_stays_false(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = False
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
False
|
||||
)
|
||||
self.assertEqual(result, False)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_for_invalid_type_bool(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
False
|
||||
)
|
||||
self.assertEqual(result, False)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_for_invalid_type_int(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
50
|
||||
)
|
||||
self.assertEqual(result, 50)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_if_below_minimum_1(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = -5
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
default_value=50
|
||||
)
|
||||
self.assertEqual(result, 50)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_if_below_minimum_2(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = -50
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
default_value=50,
|
||||
min_value=-10
|
||||
)
|
||||
self.assertEqual(result, 50)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_for_invalid_type_int(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = 1000
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
default_value=50,
|
||||
max_value=100
|
||||
)
|
||||
self.assertEqual(result, 50)
|
||||
|
||||
@patch(MODULE_PATH + '.app_settings.settings')
|
||||
def test_default_is_none_needs_required_type(self, mock_settings):
|
||||
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
|
||||
with self.assertRaises(ValueError):
|
||||
result = app_settings._clean_setting(
|
||||
'TEST_SETTING_DUMMY',
|
||||
default_value=None
|
||||
)
|
||||
268
allianceauth/authentication/tests/test_templatetags.py
Normal file
268
allianceauth/authentication/tests/test_templatetags.py
Normal file
@@ -0,0 +1,268 @@
|
||||
from math import ceil
|
||||
from unittest.mock import patch
|
||||
|
||||
from requests import RequestException
|
||||
import requests_mock
|
||||
from packaging.version import Version as Pep440Version
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth.templatetags.admin_status import (
|
||||
status_overview,
|
||||
_fetch_list_from_gitlab,
|
||||
_current_notifications,
|
||||
_current_version_summary,
|
||||
_fetch_notification_issues_from_gitlab,
|
||||
_fetch_tags_from_gitlab,
|
||||
_latests_versions
|
||||
)
|
||||
|
||||
MODULE_PATH = 'allianceauth.templatetags'
|
||||
|
||||
|
||||
def create_tags_list(tag_names: list):
|
||||
return [{'name': str(tag_name)} for tag_name in tag_names]
|
||||
|
||||
|
||||
GITHUB_TAGS = create_tags_list(['v2.4.6a1', 'v2.4.5', 'v2.4.0', 'v2.0.0', 'v1.1.1'])
|
||||
GITHUB_NOTIFICATION_ISSUES = [
|
||||
{
|
||||
'id': 1,
|
||||
'title': 'first issue'
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'title': 'second issue'
|
||||
},
|
||||
{
|
||||
'id': 3,
|
||||
'title': 'third issue'
|
||||
},
|
||||
{
|
||||
'id': 4,
|
||||
'title': 'forth issue'
|
||||
},
|
||||
{
|
||||
'id': 5,
|
||||
'title': 'fifth issue'
|
||||
},
|
||||
{
|
||||
'id': 6,
|
||||
'title': 'sixth issue'
|
||||
},
|
||||
]
|
||||
TEST_VERSION = '2.6.5'
|
||||
|
||||
|
||||
class TestStatusOverviewTag(TestCase):
|
||||
|
||||
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
|
||||
@patch(MODULE_PATH + '.admin_status._fetch_celery_queue_length')
|
||||
@patch(MODULE_PATH + '.admin_status._current_version_summary')
|
||||
@patch(MODULE_PATH + '.admin_status._current_notifications')
|
||||
def test_status_overview(
|
||||
self,
|
||||
mock_current_notifications,
|
||||
mock_current_version_info,
|
||||
mock_fetch_celery_queue_length
|
||||
):
|
||||
notifications = {
|
||||
'notifications': GITHUB_NOTIFICATION_ISSUES[:5]
|
||||
}
|
||||
mock_current_notifications.return_value = notifications
|
||||
|
||||
version_info = {
|
||||
'latest_major': True,
|
||||
'latest_minor': True,
|
||||
'latest_patch': True,
|
||||
'current_version': TEST_VERSION,
|
||||
'latest_major_version': '2.0.0',
|
||||
'latest_minor_version': '2.4.0',
|
||||
'latest_patch_version': '2.4.5',
|
||||
}
|
||||
mock_current_version_info.return_value = version_info
|
||||
mock_fetch_celery_queue_length.return_value = 3
|
||||
|
||||
context = {}
|
||||
result = status_overview(context)
|
||||
expected = {
|
||||
'notifications': GITHUB_NOTIFICATION_ISSUES[:5],
|
||||
'latest_major': True,
|
||||
'latest_minor': True,
|
||||
'latest_patch': True,
|
||||
'current_version': TEST_VERSION,
|
||||
'latest_major_version': '2.0.0',
|
||||
'latest_minor_version': '2.4.0',
|
||||
'latest_patch_version': '2.4.5',
|
||||
'task_queue_length': 3,
|
||||
}
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
|
||||
class TestNotifications(TestCase):
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_fetch_notification_issues_from_gitlab(self, requests_mocker):
|
||||
url = (
|
||||
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth/issues'
|
||||
'?labels=announcement'
|
||||
)
|
||||
requests_mocker.get(url, json=GITHUB_NOTIFICATION_ISSUES)
|
||||
result = _fetch_notification_issues_from_gitlab()
|
||||
self.assertEqual(result, GITHUB_NOTIFICATION_ISSUES)
|
||||
|
||||
@patch(MODULE_PATH + '.admin_status.cache')
|
||||
def test_current_notifications_normal(self, mock_cache):
|
||||
mock_cache.get_or_set.return_value = GITHUB_NOTIFICATION_ISSUES
|
||||
|
||||
result = _current_notifications()
|
||||
self.assertEqual(result['notifications'], GITHUB_NOTIFICATION_ISSUES[:5])
|
||||
|
||||
@patch(MODULE_PATH + '.admin_status.cache')
|
||||
def test_current_notifications_failed(self, mock_cache):
|
||||
mock_cache.get_or_set.side_effect = RequestException
|
||||
|
||||
result = _current_notifications()
|
||||
self.assertEqual(result['notifications'], list())
|
||||
|
||||
|
||||
class TestCeleryQueueLength(TestCase):
|
||||
|
||||
def test_get_celery_queue_length(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestVersionTags(TestCase):
|
||||
|
||||
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
|
||||
@patch(MODULE_PATH + '.admin_status.cache')
|
||||
def test_current_version_info_normal(self, mock_cache):
|
||||
mock_cache.get_or_set.return_value = GITHUB_TAGS
|
||||
|
||||
result = _current_version_summary()
|
||||
self.assertTrue(result['latest_major'])
|
||||
self.assertTrue(result['latest_minor'])
|
||||
self.assertTrue(result['latest_patch'])
|
||||
self.assertEqual(result['latest_major_version'], '2.0.0')
|
||||
self.assertEqual(result['latest_minor_version'], '2.4.0')
|
||||
self.assertEqual(result['latest_patch_version'], '2.4.5')
|
||||
|
||||
@patch(MODULE_PATH + '.admin_status.__version__', TEST_VERSION)
|
||||
@patch(MODULE_PATH + '.admin_status.cache')
|
||||
def test_current_version_info_failed(self, mock_cache):
|
||||
mock_cache.get_or_set.side_effect = RequestException
|
||||
|
||||
expected = {}
|
||||
result = _current_version_summary()
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_fetch_tags_from_gitlab(self, requests_mocker):
|
||||
url = (
|
||||
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth'
|
||||
'/repository/tags'
|
||||
)
|
||||
requests_mocker.get(url, json=GITHUB_TAGS)
|
||||
result = _fetch_tags_from_gitlab()
|
||||
self.assertEqual(result, GITHUB_TAGS)
|
||||
|
||||
|
||||
class TestLatestsVersion(TestCase):
|
||||
|
||||
def test_all_version_types_defined(self):
|
||||
|
||||
tags = create_tags_list(
|
||||
['2.1.1', '2.1.0', '2.0.0', '2.1.1a1', '1.1.1', '1.1.0', '1.0.0']
|
||||
)
|
||||
major, minor, patch = _latests_versions(tags)
|
||||
self.assertEqual(major, Pep440Version('2.0.0'))
|
||||
self.assertEqual(minor, Pep440Version('2.1.0'))
|
||||
self.assertEqual(patch, Pep440Version('2.1.1'))
|
||||
|
||||
def test_major_and_minor_not_defined_with_zero(self):
|
||||
|
||||
tags = create_tags_list(
|
||||
['2.1.2', '2.1.1', '2.0.1', '2.1.1a1', '1.1.1', '1.1.0', '1.0.0']
|
||||
)
|
||||
major, minor, patch = _latests_versions(tags)
|
||||
self.assertEqual(major, Pep440Version('2.0.1'))
|
||||
self.assertEqual(minor, Pep440Version('2.1.1'))
|
||||
self.assertEqual(patch, Pep440Version('2.1.2'))
|
||||
|
||||
def test_can_ignore_invalid_versions(self):
|
||||
|
||||
tags = create_tags_list(
|
||||
['2.1.1', '2.1.0', '2.0.0', '2.1.1a1', 'invalid']
|
||||
)
|
||||
major, minor, patch = _latests_versions(tags)
|
||||
self.assertEqual(major, Pep440Version('2.0.0'))
|
||||
self.assertEqual(minor, Pep440Version('2.1.0'))
|
||||
self.assertEqual(patch, Pep440Version('2.1.1'))
|
||||
|
||||
|
||||
class TestFetchListFromGitlab(TestCase):
|
||||
|
||||
page_size = 2
|
||||
|
||||
def setUp(self):
|
||||
self.url = (
|
||||
'https://gitlab.com/api/v4/projects/allianceauth%2Fallianceauth'
|
||||
'/repository/tags'
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def my_callback(cls, request, context):
|
||||
page = int(request.qs['page'][0])
|
||||
start = (page - 1) * cls.page_size
|
||||
end = start + cls.page_size
|
||||
return GITHUB_TAGS[start:end]
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_can_fetch_one_page_with_header(self, requests_mocker):
|
||||
headers = {
|
||||
'x-total-pages': '1'
|
||||
}
|
||||
requests_mocker.get(self.url, json=GITHUB_TAGS, headers=headers)
|
||||
result = _fetch_list_from_gitlab(self.url)
|
||||
self.assertEqual(result, GITHUB_TAGS)
|
||||
self.assertEqual(requests_mocker.call_count, 1)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_can_fetch_one_page_wo_header(self, requests_mocker):
|
||||
requests_mocker.get(self.url, json=GITHUB_TAGS)
|
||||
result = _fetch_list_from_gitlab(self.url)
|
||||
self.assertEqual(result, GITHUB_TAGS)
|
||||
self.assertEqual(requests_mocker.call_count, 1)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_can_fetch_one_page_and_ignore_invalid_header(self, requests_mocker):
|
||||
headers = {
|
||||
'x-total-pages': 'invalid'
|
||||
}
|
||||
requests_mocker.get(self.url, json=GITHUB_TAGS, headers=headers)
|
||||
result = _fetch_list_from_gitlab(self.url)
|
||||
self.assertEqual(result, GITHUB_TAGS)
|
||||
self.assertEqual(requests_mocker.call_count, 1)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_can_fetch_multiple_pages(self, requests_mocker):
|
||||
total_pages = ceil(len(GITHUB_TAGS) / self.page_size)
|
||||
headers = {
|
||||
'x-total-pages': str(total_pages)
|
||||
}
|
||||
requests_mocker.get(self.url, json=self.my_callback, headers=headers)
|
||||
result = _fetch_list_from_gitlab(self.url)
|
||||
self.assertEqual(result, GITHUB_TAGS)
|
||||
self.assertEqual(requests_mocker.call_count, total_pages)
|
||||
|
||||
@requests_mock.mock()
|
||||
def test_can_fetch_given_number_of_pages_only(self, requests_mocker):
|
||||
total_pages = ceil(len(GITHUB_TAGS) / self.page_size)
|
||||
headers = {
|
||||
'x-total-pages': str(total_pages)
|
||||
}
|
||||
requests_mocker.get(self.url, json=self.my_callback, headers=headers)
|
||||
max_pages = 2
|
||||
result = _fetch_list_from_gitlab(self.url, max_pages=max_pages)
|
||||
self.assertEqual(result, GITHUB_TAGS[:4])
|
||||
self.assertEqual(requests_mocker.call_count, max_pages)
|
||||
@@ -7,11 +7,21 @@ from . import views
|
||||
app_name = 'authentication'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', login_required(TemplateView.as_view(template_name='authentication/dashboard.html')),),
|
||||
url(r'^account/login/$', TemplateView.as_view(template_name='public/login.html'), name='login'),
|
||||
url(r'^account/characters/main/$', views.main_character_change, name='change_main_character'),
|
||||
url(r'^account/characters/add/$', views.add_character, name='add_character'),
|
||||
url(r'^help/$', login_required(TemplateView.as_view(template_name='allianceauth/help.html')), name='help'),
|
||||
url(r'^dashboard/$',
|
||||
login_required(TemplateView.as_view(template_name='authentication/dashboard.html')), name='dashboard'),
|
||||
url(r'^$', views.index, name='index'),
|
||||
url(
|
||||
r'^account/login/$',
|
||||
TemplateView.as_view(template_name='public/login.html'),
|
||||
name='login'
|
||||
),
|
||||
url(
|
||||
r'^account/characters/main/$',
|
||||
views.main_character_change,
|
||||
name='change_main_character'
|
||||
),
|
||||
url(
|
||||
r'^account/characters/add/$',
|
||||
views.add_character,
|
||||
name='add_character'
|
||||
),
|
||||
url(r'^dashboard/$', views.dashboard, name='dashboard'),
|
||||
]
|
||||
|
||||
@@ -7,20 +7,58 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import signing
|
||||
from django.urls import reverse
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.shortcuts import redirect, render
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from allianceauth.eveonline.models import EveCharacter
|
||||
from esi.decorators import token_required
|
||||
from esi.models import Token
|
||||
from registration.backends.hmac.views import RegistrationView as BaseRegistrationView, \
|
||||
ActivationView as BaseActivationView, REGISTRATION_SALT
|
||||
|
||||
from registration.backends.hmac.views import (
|
||||
RegistrationView as BaseRegistrationView,
|
||||
ActivationView as BaseActivationView,
|
||||
REGISTRATION_SALT
|
||||
)
|
||||
from registration.signals import user_registered
|
||||
|
||||
from .models import CharacterOwnership
|
||||
from .forms import RegistrationForm
|
||||
|
||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||
_has_auto_groups = True
|
||||
from allianceauth.eveonline.autogroups.models import *
|
||||
else:
|
||||
_has_auto_groups = False
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@login_required
|
||||
def index(request):
|
||||
return redirect('authentication:dashboard')
|
||||
|
||||
|
||||
@login_required
|
||||
def dashboard(request):
|
||||
groups = request.user.groups.all()
|
||||
if _has_auto_groups:
|
||||
groups = groups\
|
||||
.filter(managedalliancegroup__isnull=True)\
|
||||
.filter(managedcorpgroup__isnull=True)
|
||||
groups = groups.order_by('name')
|
||||
characters = EveCharacter.objects\
|
||||
.filter(character_ownership__user=request.user)\
|
||||
.select_related()\
|
||||
.order_by('character_name')
|
||||
|
||||
context = {
|
||||
'groups': groups,
|
||||
'characters': characters
|
||||
}
|
||||
return render(request, 'authentication/dashboard.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
|
||||
def main_character_change(request, token):
|
||||
@@ -31,7 +69,10 @@ def main_character_change(request, token):
|
||||
if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists():
|
||||
co = CharacterOwnership.objects.create_by_token(token)
|
||||
else:
|
||||
messages.error(request, 'Cannot change main character to %(char)s: character owned by a different account.' % ({'char': token.character_name}))
|
||||
messages.error(
|
||||
request,
|
||||
_('Cannot change main character to %(char)s: character owned by a different account.') % ({'char': token.character_name})
|
||||
)
|
||||
co = None
|
||||
if co:
|
||||
request.user.profile.main_character = co.character
|
||||
|
||||
@@ -6,7 +6,8 @@ from bravado.exception import HTTPForbidden
|
||||
from django.db import models
|
||||
from esi.errors import TokenError
|
||||
from esi.models import Token
|
||||
from allianceauth.eveonline.models import EveCorporationInfo, EveCharacter
|
||||
from allianceauth.eveonline.models import EveCorporationInfo, EveCharacter,\
|
||||
EveAllianceInfo
|
||||
from allianceauth.notifications import notify
|
||||
|
||||
from allianceauth.corputils.managers import CorpStatsManager
|
||||
@@ -137,13 +138,13 @@ class CorpStats(models.Model):
|
||||
return self.token.user == user or self.visible_to(user)
|
||||
|
||||
def corp_logo(self, size=128):
|
||||
return "https://image.eveonline.com/Corporation/%s_%s.png" % (self.corp.corporation_id, size)
|
||||
return self.corp.logo_url(size)
|
||||
|
||||
def alliance_logo(self, size=128):
|
||||
if self.corp.alliance:
|
||||
return "https://image.eveonline.com/Alliance/%s_%s.png" % (self.corp.alliance.alliance_id, size)
|
||||
return self.corp.alliance.logo_url(size)
|
||||
else:
|
||||
return "https://image.eveonline.com/Alliance/1_%s.png" % size
|
||||
return EveAllianceInfo.generic_logo_url(1, size)
|
||||
|
||||
|
||||
class CorpMember(models.Model):
|
||||
@@ -185,10 +186,16 @@ class CorpMember(models.Model):
|
||||
return CharacterOwnership.objects.filter(character__character_id=self.character_id).exists()
|
||||
|
||||
def portrait_url(self, size=32):
|
||||
return "https://image.eveonline.com/Character/%s_%s.jpg" % (self.character_id, size)
|
||||
return EveCharacter.generic_portrait_url(self.character_id, size)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.startswith('portrait_url_'):
|
||||
size = item.strip('portrait_url_')
|
||||
return self.portrait_url(size)
|
||||
return self.__getattribute__(item)
|
||||
@property
|
||||
def portrait_url_32(self):
|
||||
return self.portrait_url(32)
|
||||
|
||||
@property
|
||||
def portrait_url_64(self):
|
||||
return self.portrait_url(64)
|
||||
|
||||
@property
|
||||
def portrait_url_128(self):
|
||||
return self.portrait_url(128)
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,11 +7,12 @@
|
||||
<div class="col-lg-12 text-center">
|
||||
<table class="table">
|
||||
<tr>
|
||||
<td class="text-center col-lg-6
|
||||
{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}"><img
|
||||
class="ra-avatar" src="{{ corpstats.corp.logo_url_128 }}"></td>
|
||||
<td class="text-center col-lg-6{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}">
|
||||
<img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}">
|
||||
</td>
|
||||
{% if corpstats.corp.alliance %}
|
||||
<td class="text-center col-lg-6"><img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_128 }}">
|
||||
<td class="text-center col-lg-6">
|
||||
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}">
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
@@ -29,15 +30,15 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<ul class="nav nav-pills pull-left">
|
||||
<li class="active"><a href="#mains" data-toggle="pill">{% trans 'Mains' %} ({{ corpstats.main_count }})</a></li>
|
||||
<li class="active"><a href="#mains" data-toggle="pill">{% trans 'Mains' %} ({{ total_mains }})</a></li>
|
||||
<li><a href="#members" data-toggle="pill">{% trans 'Members' %} ({{ corpstats.member_count }})</a></li>
|
||||
<li><a href="#unregistered" data-toggle="pill">{% trans 'Unregistered' %} ({{ corpstats.unregistered_member_count }})</a></li>
|
||||
<li><a href="#unregistered" data-toggle="pill">{% trans 'Unregistered' %} ({{ unregistered.count }})</a></li>
|
||||
</ul>
|
||||
<div class="pull-right">
|
||||
{% trans "Last update:" %} {{ corpstats.last_update|naturaltime }}
|
||||
<a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now">
|
||||
<span class="glyphicon glyphicon-refresh"></span>
|
||||
</a>
|
||||
<div class="pull-right hidden-xs">
|
||||
{% trans "Last update:" %} {{ corpstats.last_update|naturaltime }}
|
||||
<a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now">
|
||||
<span class="glyphicon glyphicon-refresh"></span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
@@ -54,14 +55,14 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for main in mains %}
|
||||
{% for id, main in mains.items %}
|
||||
<tr>
|
||||
<td class="text-center" style="vertical-align:middle">
|
||||
<div class="thumbnail"
|
||||
style="border: 0 none; box-shadow: none; background: transparent;">
|
||||
<img src="{{ main.portrait_url_64 }}" class="img-circle">
|
||||
<img src="{{ main.main.portrait_url_64 }}" class="img-circle">
|
||||
<div class="caption text-center">
|
||||
{{ main }}
|
||||
{{ main.main }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -80,7 +81,7 @@
|
||||
<tr>
|
||||
<td class="text-center" style="width:5%">
|
||||
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
|
||||
<img src="https://image.eveonline.com/Character/{{ alt.character_id }}_32.jpg" class="img-circle">
|
||||
<img src="{{ alt.portrait_url_32 }}" class="img-circle">
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center" style="width:30%">{{ alt.character_name }}</td>
|
||||
@@ -119,16 +120,29 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for member in members %}
|
||||
<tr {% if not member.registered %}class="danger"{% endif %}>
|
||||
<tr>
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
|
||||
<td class="text-center">{{ member }}</td>
|
||||
<td class="text-center"><a
|
||||
href="https://zkillboard.com/character/{{ member.character_id }}/"
|
||||
class="label label-danger"
|
||||
target="_blank">{% trans "Killboard" %}</a></td>
|
||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.character_name }}</td>
|
||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.corporation_name }}</td>
|
||||
<td class="text-center">{{ member.character_ownership.user.profile.main_character.alliance_name }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% for member in unregistered %}
|
||||
<tr class="danger">
|
||||
<td><img src="{{ member.portrait_url }}" class="img-circle"></td>
|
||||
<td class="text-center">{{ member.character_name }}</td>
|
||||
<td class="text-center"><a
|
||||
href="https://zkillboard.com/character/{{ member.character_id }}/"
|
||||
class="label label-danger"
|
||||
target="_blank">{% trans "Killboard" %}</a></td>
|
||||
<td class="text-center">{{ member.main_character.character_name }}</td>
|
||||
<td class="text-center">{{ member.main_character.corporation_name }}</td>
|
||||
<td class="text-center">{{ member.main_character.alliance_name }}</td>
|
||||
<td class="text-center"></td>
|
||||
<td class="text-center"></td>
|
||||
<td class="text-center"></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -205,13 +205,13 @@ class CorpStatsPropertiesTestCase(TestCase):
|
||||
AuthUtils.connect_signals()
|
||||
|
||||
def test_logos(self):
|
||||
self.assertEqual(self.corpstats.corp_logo(size=128), 'https://image.eveonline.com/Corporation/2_128.png')
|
||||
self.assertEqual(self.corpstats.alliance_logo(size=128), 'https://image.eveonline.com/Alliance/1_128.png')
|
||||
self.assertEqual(self.corpstats.corp_logo(size=128), 'https://images.evetech.net/corporations/2/logo?size=128')
|
||||
self.assertEqual(self.corpstats.alliance_logo(size=128), 'https://images.evetech.net/alliances/1/logo?size=128')
|
||||
|
||||
alliance = EveAllianceInfo.objects.create(alliance_name='test alliance', alliance_id='3', alliance_ticker='TEST', executor_corp_id='2')
|
||||
self.corp.alliance = alliance
|
||||
self.corp.save()
|
||||
self.assertEqual(self.corpstats.alliance_logo(size=128), 'https://image.eveonline.com/Alliance/3_128.png')
|
||||
self.assertEqual(self.corpstats.alliance_logo(size=128), 'https://images.evetech.net/alliances/3/logo?size=128')
|
||||
alliance.delete()
|
||||
|
||||
|
||||
@@ -273,5 +273,7 @@ class CorpMemberTestCase(TestCase):
|
||||
AuthUtils.connect_signals()
|
||||
|
||||
def test_portrait_url(self):
|
||||
self.assertEquals(self.member.portrait_url(size=32), 'https://image.eveonline.com/Character/2_32.jpg')
|
||||
self.assertEquals(self.member.portrait_url(size=32), 'https://images.evetech.net/characters/2/portrait?size=32')
|
||||
self.assertEquals(self.member.portrait_url(size=32), self.member.portrait_url_32)
|
||||
self.assertEquals(self.member.portrait_url(size=64), self.member.portrait_url_64)
|
||||
self.assertEquals(self.member.portrait_url(size=128), self.member.portrait_url_128)
|
||||
|
||||
@@ -3,14 +3,14 @@ import os
|
||||
from bravado.exception import HTTPError
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required, permission_required, user_passes_test
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist
|
||||
from django.db import IntegrityError
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from esi.decorators import token_required
|
||||
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo
|
||||
|
||||
from .models import CorpStats
|
||||
from .models import CorpStats, CorpMember
|
||||
|
||||
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
||||
"""
|
||||
@@ -68,7 +68,7 @@ def corpstats_view(request, corp_id=None):
|
||||
corpstats = get_object_or_404(CorpStats, corp=corp)
|
||||
|
||||
# get available models
|
||||
available = CorpStats.objects.visible_to(request.user).order_by('corp__corporation_name')
|
||||
available = CorpStats.objects.visible_to(request.user).order_by('corp__corporation_name').select_related('corp')
|
||||
|
||||
# ensure we can see the requested model
|
||||
if corpstats and corpstats not in available:
|
||||
@@ -89,13 +89,50 @@ def corpstats_view(request, corp_id=None):
|
||||
}
|
||||
|
||||
if corpstats:
|
||||
members = corpstats.members.all()
|
||||
mains = corpstats.mains.all()
|
||||
unregistered = corpstats.unregistered_members.all()
|
||||
character_list = CorpMember.objects.filter(corpstats=corpstats)
|
||||
linked_chars = EveCharacter.objects.filter(
|
||||
character_id__in=character_list.values_list('character_id', flat=True))
|
||||
linked_chars = linked_chars | EveCharacter.objects.filter(
|
||||
character_ownership__user__profile__main_character__corporation_id=corpstats.corp.corporation_id)
|
||||
|
||||
linked_chars = linked_chars.select_related('character_ownership',
|
||||
'character_ownership__user__profile__main_character') \
|
||||
.prefetch_related('character_ownership__user__character_ownerships') \
|
||||
.prefetch_related('character_ownership__user__character_ownerships__character')
|
||||
|
||||
members = []
|
||||
mains = {}
|
||||
|
||||
temp_ids = []
|
||||
for char in linked_chars:
|
||||
try:
|
||||
main = char.character_ownership.user.profile.main_character
|
||||
if main is not None:
|
||||
if main.corporation_id == corpstats.corp.corporation_id:
|
||||
if main.character_id not in mains:
|
||||
mains[main.character_id] = {'main':main, 'alts':[]}
|
||||
|
||||
mains[main.character_id]['alts'].append(char)
|
||||
|
||||
if char.corporation_id == corpstats.corp.corporation_id:
|
||||
members.append(char)
|
||||
|
||||
temp_ids.append(char.character_id)
|
||||
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
|
||||
unregistered = character_list.exclude(character_id__in=temp_ids)
|
||||
|
||||
members = members
|
||||
mains = mains
|
||||
total_mains = len(mains)
|
||||
unregistered = unregistered
|
||||
context.update({
|
||||
'corpstats': corpstats,
|
||||
'members': members,
|
||||
'mains': mains,
|
||||
'total_mains': total_mains,
|
||||
'unregistered': unregistered,
|
||||
})
|
||||
|
||||
|
||||
@@ -25,14 +25,35 @@ class AutogroupsConfigManagerTestCase(TestCase):
|
||||
obj = AutogroupsConfig.objects.create()
|
||||
obj.states.add(member.profile.state)
|
||||
|
||||
with patch('.models.AutogroupsConfig.update_group_membership_for_user') as update_group_membership_for_user:
|
||||
AutogroupsConfig.objects.update_groups_for_user(member)
|
||||
with patch('.models.AutogroupsConfig.update_group_membership_for_user') \
|
||||
as update_group_membership_for_user:
|
||||
AutogroupsConfig.objects.update_groups_for_user(
|
||||
user=member
|
||||
)
|
||||
|
||||
self.assertTrue(update_group_membership_for_user.called)
|
||||
self.assertEqual(update_group_membership_for_user.call_count, 1)
|
||||
args, kwargs = update_group_membership_for_user.call_args
|
||||
self.assertEqual(args[0], member)
|
||||
|
||||
def test_update_groups_for_user_no_state(self):
|
||||
member = AuthUtils.create_member('test member')
|
||||
obj = AutogroupsConfig.objects.create()
|
||||
obj.states.add(member.profile.state)
|
||||
|
||||
with patch('.models.AutogroupsConfig.update_group_membership_for_user') \
|
||||
as update_group_membership_for_user:
|
||||
AutogroupsConfig.objects.update_groups_for_user(
|
||||
user=member,
|
||||
state=member.profile.state
|
||||
)
|
||||
|
||||
self.assertTrue(update_group_membership_for_user.called)
|
||||
self.assertEqual(update_group_membership_for_user.call_count, 1)
|
||||
args, kwargs = update_group_membership_for_user.call_args
|
||||
self.assertEqual(args[0], member)
|
||||
|
||||
|
||||
@patch('.models.AutogroupsConfig.update_group_membership_for_user')
|
||||
@patch('.models.AutogroupsConfig.remove_user_from_alliance_groups')
|
||||
@patch('.models.AutogroupsConfig.remove_user_from_corp_groups')
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import Group
|
||||
from django.db import transaction
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
@@ -50,7 +51,11 @@ class AutogroupsConfigTestCase(TestCase):
|
||||
|
||||
@patch('.models.AutogroupsConfig.update_alliance_group_membership')
|
||||
@patch('.models.AutogroupsConfig.update_corp_group_membership')
|
||||
def test_update_group_membership(self, update_corp, update_alliance):
|
||||
def test_update_group_membership_for_user(
|
||||
self,
|
||||
update_corp,
|
||||
update_alliance
|
||||
):
|
||||
agc = AutogroupsConfig.objects.create()
|
||||
agc.update_group_membership_for_user(self.member)
|
||||
|
||||
@@ -101,8 +106,27 @@ class AutogroupsConfigTestCase(TestCase):
|
||||
|
||||
self.assertNotIn(group, self.member.groups.all())
|
||||
|
||||
def test_update_alliance_group_membership_no_alliance_model(self):
|
||||
obj = AutogroupsConfig.objects.create()
|
||||
# todo: this test case currently does not work, because it forces
|
||||
# an exception during a transaction, which is not easily testable
|
||||
# the production code itself should be fine though
|
||||
# I therefore commented out the test case for now
|
||||
"""
|
||||
@patch('.models.EveAllianceInfo.objects.create_alliance')
|
||||
def test_update_alliance_group_membership_no_alliance_model(
|
||||
self,
|
||||
mock_create_alliance
|
||||
):
|
||||
def mock_create_alliance_side_effect(*args, **kwargs):
|
||||
return EveAllianceInfo.objects.create(
|
||||
alliance_id='3459',
|
||||
alliance_name='alliance name',
|
||||
alliance_ticker='alliance_ticker',
|
||||
executor_corp_id='2345'
|
||||
)
|
||||
|
||||
mock_create_alliance.side_effect = mock_create_alliance_side_effect
|
||||
|
||||
obj = AutogroupsConfig.objects.create(alliance_groups=True)
|
||||
obj.states.add(AuthUtils.get_member_state())
|
||||
char = EveCharacter.objects.create(
|
||||
character_id='1234',
|
||||
@@ -116,12 +140,13 @@ class AutogroupsConfigTestCase(TestCase):
|
||||
self.member.profile.main_character = char
|
||||
self.member.profile.save()
|
||||
|
||||
# Act
|
||||
# Act
|
||||
obj.update_alliance_group_membership(self.member)
|
||||
|
||||
group = obj.get_alliance_group(self.alliance)
|
||||
|
||||
self.assertNotIn(group, self.member.groups.all())
|
||||
"""
|
||||
|
||||
def test_update_corp_group_membership(self):
|
||||
obj = AutogroupsConfig.objects.create(corp_groups=True)
|
||||
|
||||
17
allianceauth/eveonline/evelinks/__init__.py
Normal file
17
allianceauth/eveonline/evelinks/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# this package generates profile URL for eve entities
|
||||
# on 3rd party websites like evewho and zKillboard
|
||||
#
|
||||
# It contains of modules for views and templatetags for templates
|
||||
|
||||
# list of all eve entity categories as defined in ESI
|
||||
_ESI_CATEGORY_AGENT = "agent"
|
||||
_ESI_CATEGORY_ALLIANCE = "alliance"
|
||||
_ESI_CATEGORY_CHARACTER = "character"
|
||||
_ESI_CATEGORY_CONSTELLATION = "constellation"
|
||||
_ESI_CATEGORY_CORPORATION = "corporation"
|
||||
_ESI_CATEGORY_FACTION = "faction"
|
||||
_ESI_CATEGORY_INVENTORYTYPE = "inventory_type"
|
||||
_ESI_CATEGORY_REGION = "region"
|
||||
_ESI_CATEGORY_SOLARSYSTEM = "solar_system"
|
||||
_ESI_CATEGORY_STATION = "station"
|
||||
_ESI_CATEGORY_WORMHOLE = "wormhole"
|
||||
61
allianceauth/eveonline/evelinks/dotlan.py
Normal file
61
allianceauth/eveonline/evelinks/dotlan.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# this module generates profile URLs for dotlan
|
||||
|
||||
from urllib.parse import urljoin, quote
|
||||
|
||||
from . import (
|
||||
_ESI_CATEGORY_ALLIANCE,
|
||||
_ESI_CATEGORY_CORPORATION,
|
||||
_ESI_CATEGORY_REGION,
|
||||
_ESI_CATEGORY_SOLARSYSTEM
|
||||
)
|
||||
|
||||
|
||||
_BASE_URL = 'http://evemaps.dotlan.net'
|
||||
|
||||
|
||||
def _build_url(category: str, name: str) -> str:
|
||||
"""return url to profile page for an eve entity"""
|
||||
|
||||
if category == _ESI_CATEGORY_ALLIANCE:
|
||||
partial = 'alliance'
|
||||
|
||||
elif category == _ESI_CATEGORY_CORPORATION:
|
||||
partial = 'corp'
|
||||
|
||||
elif category == _ESI_CATEGORY_REGION:
|
||||
partial = 'map'
|
||||
|
||||
elif category == _ESI_CATEGORY_SOLARSYSTEM:
|
||||
partial = 'system'
|
||||
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Not implemented yet for category:" + category
|
||||
)
|
||||
|
||||
url = urljoin(
|
||||
_BASE_URL,
|
||||
'{}/{}'.format(partial, quote(str(name).replace(" ", "_")))
|
||||
|
||||
)
|
||||
return url
|
||||
|
||||
|
||||
def alliance_url(name: str) -> str:
|
||||
"""url for page about given alliance on dotlan"""
|
||||
return _build_url(_ESI_CATEGORY_ALLIANCE, name)
|
||||
|
||||
|
||||
def corporation_url(name: str) -> str:
|
||||
"""url for page about given corporation on dotlan"""
|
||||
return _build_url(_ESI_CATEGORY_CORPORATION, name)
|
||||
|
||||
|
||||
def region_url(name: str) -> str:
|
||||
"""url for page about given region on dotlan"""
|
||||
return _build_url(_ESI_CATEGORY_REGION, name)
|
||||
|
||||
|
||||
def solar_system_url(name: str) -> str:
|
||||
"""url for page about given solar system on dotlan"""
|
||||
return _build_url(_ESI_CATEGORY_SOLARSYSTEM, name)
|
||||
129
allianceauth/eveonline/evelinks/eveimageserver.py
Normal file
129
allianceauth/eveonline/evelinks/eveimageserver.py
Normal file
@@ -0,0 +1,129 @@
|
||||
from . import (
|
||||
_ESI_CATEGORY_ALLIANCE,
|
||||
_ESI_CATEGORY_CHARACTER,
|
||||
_ESI_CATEGORY_CORPORATION,
|
||||
_ESI_CATEGORY_INVENTORYTYPE
|
||||
)
|
||||
|
||||
|
||||
_EVE_IMAGE_SERVER_URL = 'https://images.evetech.net'
|
||||
_DEFAULT_IMAGE_SIZE = 32
|
||||
|
||||
|
||||
def _eve_entity_image_url(
|
||||
category: str,
|
||||
entity_id: int,
|
||||
size: int = 32,
|
||||
variant: str = None,
|
||||
tenant: str = None,
|
||||
) -> str:
|
||||
"""returns image URL for an Eve Online ID.
|
||||
Supported categories: alliance, corporation, character, inventory_type
|
||||
|
||||
Arguments:
|
||||
- category: category of the ID, see ESI category constants
|
||||
- entity_id: Eve ID of the entity
|
||||
- size: (optional) render size of the image.must be between 32 (default) and 1024
|
||||
- variant: (optional) image variant for category. currently not relevant.
|
||||
- tenant: (optional) Eve Server, either `tranquility`(default) or `singularity`
|
||||
|
||||
Returns:
|
||||
- URL string for the requested image on the Eve image server
|
||||
|
||||
Exceptions:
|
||||
- Throws ValueError on invalid input
|
||||
"""
|
||||
|
||||
# input validations
|
||||
categories = {
|
||||
_ESI_CATEGORY_ALLIANCE: {
|
||||
'endpoint': 'alliances',
|
||||
'variants': ['logo']
|
||||
},
|
||||
_ESI_CATEGORY_CORPORATION: {
|
||||
'endpoint': 'corporations',
|
||||
'variants': ['logo']
|
||||
},
|
||||
_ESI_CATEGORY_CHARACTER: {
|
||||
'endpoint': 'characters',
|
||||
'variants': ['portrait']
|
||||
},
|
||||
_ESI_CATEGORY_INVENTORYTYPE: {
|
||||
'endpoint': 'types',
|
||||
'variants': ['icon', 'render']
|
||||
}
|
||||
}
|
||||
tenants = ['tranquility', 'singularity']
|
||||
|
||||
if not entity_id:
|
||||
raise ValueError('Invalid entity_id: {}'.format(entity_id))
|
||||
else:
|
||||
entity_id = int(entity_id)
|
||||
|
||||
if not size or size < 32 or size > 1024 or (size & (size - 1) != 0):
|
||||
raise ValueError('Invalid size: {}'.format(size))
|
||||
|
||||
if category not in categories:
|
||||
raise ValueError('Invalid category {}'.format(category))
|
||||
else:
|
||||
endpoint = categories[category]['endpoint']
|
||||
|
||||
if variant:
|
||||
if variant not in categories[category]['variants']:
|
||||
raise ValueError('Invalid variant {} for category {}'.format(
|
||||
variant,
|
||||
category
|
||||
))
|
||||
else:
|
||||
variant = categories[category]['variants'][0]
|
||||
|
||||
if tenant and tenant not in tenants:
|
||||
raise ValueError('Invalid tenant {}'.format(tenant))
|
||||
|
||||
# compose result URL
|
||||
result = '{}/{}/{}/{}?size={}'.format(
|
||||
_EVE_IMAGE_SERVER_URL,
|
||||
endpoint,
|
||||
entity_id,
|
||||
variant,
|
||||
size
|
||||
)
|
||||
if tenant:
|
||||
result += '&tenant={}'.format(tenant)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def alliance_logo_url(alliance_id: int, size: int = _DEFAULT_IMAGE_SIZE) -> str:
|
||||
"""image URL for the given alliance ID"""
|
||||
return _eve_entity_image_url(_ESI_CATEGORY_ALLIANCE, alliance_id, size)
|
||||
|
||||
|
||||
def corporation_logo_url(
|
||||
corporation_id: int, size: int = _DEFAULT_IMAGE_SIZE
|
||||
) -> str:
|
||||
"""image URL for the given corporation ID"""
|
||||
return _eve_entity_image_url(
|
||||
_ESI_CATEGORY_CORPORATION, corporation_id, size
|
||||
)
|
||||
|
||||
|
||||
def character_portrait_url(
|
||||
character_id: int, size: int = _DEFAULT_IMAGE_SIZE
|
||||
) -> str:
|
||||
"""image URL for the given character ID"""
|
||||
return _eve_entity_image_url(_ESI_CATEGORY_CHARACTER, character_id, size)
|
||||
|
||||
|
||||
def type_icon_url(type_id: int, size: int = _DEFAULT_IMAGE_SIZE) -> str:
|
||||
"""icon image URL for the given type ID"""
|
||||
return _eve_entity_image_url(
|
||||
_ESI_CATEGORY_INVENTORYTYPE, type_id, size, variant='icon'
|
||||
)
|
||||
|
||||
|
||||
def type_render_url(type_id: int, size: int = _DEFAULT_IMAGE_SIZE) -> str:
|
||||
"""render image URL for the given type ID"""
|
||||
return _eve_entity_image_url(
|
||||
_ESI_CATEGORY_INVENTORYTYPE, type_id, size, variant='render'
|
||||
)
|
||||
51
allianceauth/eveonline/evelinks/evewho.py
Normal file
51
allianceauth/eveonline/evelinks/evewho.py
Normal file
@@ -0,0 +1,51 @@
|
||||
# this module generates profile URLs for evewho
|
||||
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from . import (
|
||||
_ESI_CATEGORY_ALLIANCE,
|
||||
_ESI_CATEGORY_CORPORATION,
|
||||
_ESI_CATEGORY_CHARACTER,
|
||||
)
|
||||
|
||||
|
||||
_BASE_URL = 'https://evewho.com'
|
||||
|
||||
|
||||
def _build_url(category: str, eve_id: int) -> str:
|
||||
"""return url to profile page for an eve entity"""
|
||||
|
||||
if category == _ESI_CATEGORY_ALLIANCE:
|
||||
partial = 'alliance'
|
||||
|
||||
elif category == _ESI_CATEGORY_CORPORATION:
|
||||
partial = 'corporation'
|
||||
|
||||
elif category == _ESI_CATEGORY_CHARACTER:
|
||||
partial = 'character'
|
||||
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Not implemented yet for category:" + category
|
||||
)
|
||||
|
||||
url = urljoin(
|
||||
_BASE_URL,
|
||||
'{}/{}'.format(partial, int(eve_id))
|
||||
)
|
||||
return url
|
||||
|
||||
|
||||
def alliance_url(eve_id: int) -> str:
|
||||
"""url for page about given alliance on evewho"""
|
||||
return _build_url(_ESI_CATEGORY_ALLIANCE, eve_id)
|
||||
|
||||
|
||||
def character_url(eve_id: int) -> str:
|
||||
"""url for page about given character on evewho"""
|
||||
return _build_url(_ESI_CATEGORY_CHARACTER, eve_id)
|
||||
|
||||
|
||||
def corporation_url(eve_id: int) -> str:
|
||||
"""url for page about given corporation on evewho"""
|
||||
return _build_url(_ESI_CATEGORY_CORPORATION, eve_id)
|
||||
204
allianceauth/eveonline/evelinks/tests/test_evelinks.py
Normal file
204
allianceauth/eveonline/evelinks/tests/test_evelinks.py
Normal file
@@ -0,0 +1,204 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
from .. import dotlan, zkillboard, evewho, eveimageserver
|
||||
from ...templatetags import evelinks
|
||||
|
||||
|
||||
class TestEveWho(TestCase):
|
||||
|
||||
def test_alliance_url(self):
|
||||
self.assertEqual(
|
||||
evewho.alliance_url(12345678),
|
||||
'https://evewho.com/alliance/12345678'
|
||||
)
|
||||
|
||||
def test_corporation_url(self):
|
||||
self.assertEqual(
|
||||
evewho.corporation_url(12345678),
|
||||
'https://evewho.com/corporation/12345678'
|
||||
)
|
||||
|
||||
def test_character_url(self):
|
||||
self.assertEqual(
|
||||
evewho.character_url(12345678),
|
||||
'https://evewho.com/character/12345678'
|
||||
)
|
||||
|
||||
|
||||
class TestDotlan(TestCase):
|
||||
|
||||
def test_alliance_url(self):
|
||||
self.assertEqual(
|
||||
dotlan.alliance_url('Wayne Enterprices'),
|
||||
'http://evemaps.dotlan.net/alliance/Wayne_Enterprices'
|
||||
)
|
||||
|
||||
def test_corporation_url(self):
|
||||
self.assertEqual(
|
||||
dotlan.corporation_url('Wayne Technology'),
|
||||
'http://evemaps.dotlan.net/corp/Wayne_Technology'
|
||||
)
|
||||
self.assertEqual(
|
||||
dotlan.corporation_url('Crédit Agricole'),
|
||||
'http://evemaps.dotlan.net/corp/Cr%C3%A9dit_Agricole'
|
||||
)
|
||||
|
||||
def test_region_url(self):
|
||||
self.assertEqual(
|
||||
dotlan.region_url('Black Rise'),
|
||||
'http://evemaps.dotlan.net/map/Black_Rise'
|
||||
)
|
||||
|
||||
def test_solar_system_url(self):
|
||||
self.assertEqual(
|
||||
dotlan.solar_system_url('Jita'),
|
||||
'http://evemaps.dotlan.net/system/Jita'
|
||||
)
|
||||
|
||||
|
||||
class TestZkillboard(TestCase):
|
||||
|
||||
def test_alliance_url(self):
|
||||
self.assertEqual(
|
||||
zkillboard.alliance_url(12345678),
|
||||
'https://zkillboard.com/alliance/12345678/'
|
||||
)
|
||||
|
||||
def test_corporation_url(self):
|
||||
self.assertEqual(
|
||||
zkillboard.corporation_url(12345678),
|
||||
'https://zkillboard.com/corporation/12345678/'
|
||||
)
|
||||
|
||||
def test_character_url(self):
|
||||
self.assertEqual(
|
||||
zkillboard.character_url(12345678),
|
||||
'https://zkillboard.com/character/12345678/'
|
||||
)
|
||||
|
||||
|
||||
def test_region_url(self):
|
||||
self.assertEqual(
|
||||
zkillboard.region_url(12345678),
|
||||
'https://zkillboard.com/region/12345678/'
|
||||
)
|
||||
|
||||
def test_solar_system_url(self):
|
||||
self.assertEqual(
|
||||
zkillboard.solar_system_url(12345678),
|
||||
'https://zkillboard.com/system/12345678/'
|
||||
)
|
||||
|
||||
|
||||
class TestEveImageServer(TestCase):
|
||||
"""unit test for eveimageserver"""
|
||||
|
||||
def test_sizes(self):
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('character', 42),
|
||||
'https://images.evetech.net/characters/42/portrait?size=32'
|
||||
)
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('character', 42, size=32),
|
||||
'https://images.evetech.net/characters/42/portrait?size=32'
|
||||
)
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('character', 42, size=64),
|
||||
'https://images.evetech.net/characters/42/portrait?size=64'
|
||||
)
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('character', 42, size=128),
|
||||
'https://images.evetech.net/characters/42/portrait?size=128'
|
||||
)
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('character', 42, size=256),
|
||||
'https://images.evetech.net/characters/42/portrait?size=256'
|
||||
)
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('character', 42, size=512),
|
||||
'https://images.evetech.net/characters/42/portrait?size=512'
|
||||
)
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('character', 42, size=1024),
|
||||
'https://images.evetech.net/characters/42/portrait?size=1024'
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
eveimageserver._eve_entity_image_url('corporation', 42, size=-5)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
eveimageserver._eve_entity_image_url('corporation', 42, size=0)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
eveimageserver._eve_entity_image_url('corporation', 42, size=31)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
eveimageserver._eve_entity_image_url('corporation', 42, size=1025)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
eveimageserver._eve_entity_image_url('corporation', 42, size=2048)
|
||||
|
||||
|
||||
def test_variant(self):
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('character', 42, variant='portrait'),
|
||||
'https://images.evetech.net/characters/42/portrait?size=32'
|
||||
)
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('alliance', 42, variant='logo'),
|
||||
'https://images.evetech.net/alliances/42/logo?size=32'
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
eveimageserver._eve_entity_image_url('character', 42, variant='logo')
|
||||
|
||||
|
||||
def test_alliance(self):
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('alliance', 42),
|
||||
'https://images.evetech.net/alliances/42/logo?size=32'
|
||||
)
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('corporation', 42),
|
||||
'https://images.evetech.net/corporations/42/logo?size=32'
|
||||
)
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('character', 42),
|
||||
'https://images.evetech.net/characters/42/portrait?size=32'
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
eveimageserver._eve_entity_image_url('station', 42)
|
||||
|
||||
|
||||
def test_tenants(self):
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('character', 42, tenant='tranquility'),
|
||||
'https://images.evetech.net/characters/42/portrait?size=32&tenant=tranquility'
|
||||
)
|
||||
self.assertEqual(
|
||||
eveimageserver._eve_entity_image_url('character', 42, tenant='singularity'),
|
||||
'https://images.evetech.net/characters/42/portrait?size=32&tenant=singularity'
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
eveimageserver._eve_entity_image_url('character', 42, tenant='xxx')
|
||||
|
||||
def test_alliance_logo_url(self):
|
||||
expected = 'https://images.evetech.net/alliances/42/logo?size=128'
|
||||
self.assertEqual(eveimageserver.alliance_logo_url(42, 128), expected)
|
||||
|
||||
def test_corporation_logo_url(self):
|
||||
expected = 'https://images.evetech.net/corporations/42/logo?size=128'
|
||||
self.assertEqual(eveimageserver.corporation_logo_url(42, 128), expected)
|
||||
|
||||
def test_character_portrait_url(self):
|
||||
expected = 'https://images.evetech.net/characters/42/portrait?size=128'
|
||||
self.assertEqual(
|
||||
eveimageserver.character_portrait_url(42, 128), expected
|
||||
)
|
||||
|
||||
def test_type_icon_url(self):
|
||||
expected = 'https://images.evetech.net/types/42/icon?size=128'
|
||||
self.assertEqual(eveimageserver.type_icon_url(42, 128), expected)
|
||||
|
||||
def test_type_render_url(self):
|
||||
expected = 'https://images.evetech.net/types/42/render?size=128'
|
||||
self.assertEqual(eveimageserver.type_render_url(42, 128), expected)
|
||||
359
allianceauth/eveonline/evelinks/tests/test_templatetags.py
Normal file
359
allianceauth/eveonline/evelinks/tests/test_templatetags.py
Normal file
@@ -0,0 +1,359 @@
|
||||
from django.test import TestCase
|
||||
|
||||
from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
from .. import eveimageserver, evewho, dotlan, zkillboard
|
||||
from ...templatetags import evelinks
|
||||
|
||||
|
||||
class TestTemplateTags(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.my_character = EveCharacter.objects.create(
|
||||
character_id=1001,
|
||||
character_name='Bruce Wayne',
|
||||
corporation_id=2001,
|
||||
corporation_name='Dummy Corporation 1',
|
||||
corporation_ticker='DC1',
|
||||
alliance_id=3001,
|
||||
alliance_name='Dummy Alliance 1',
|
||||
alliance_ticker='DA1',
|
||||
)
|
||||
self.my_character_2 = EveCharacter.objects.create(
|
||||
character_id=1002,
|
||||
character_name='Peter Parker',
|
||||
corporation_id=2002,
|
||||
corporation_name='Dummy Corporation 2',
|
||||
corporation_ticker='DC2',
|
||||
)
|
||||
self.my_alliance = EveAllianceInfo.objects.create(
|
||||
alliance_id=3001,
|
||||
alliance_name='Dummy Alliance 1',
|
||||
alliance_ticker='DA1',
|
||||
executor_corp_id=2001
|
||||
)
|
||||
self.my_corporation = EveCorporationInfo(
|
||||
corporation_id=2001,
|
||||
corporation_name='Dummy Corporation 1',
|
||||
corporation_ticker='DC1',
|
||||
member_count=42,
|
||||
alliance=self.my_alliance
|
||||
)
|
||||
|
||||
self.my_region_id = 8001
|
||||
self.my_region_name = 'Southpark'
|
||||
|
||||
self.my_solar_system_id = 9001
|
||||
self.my_solar_system_name = 'Gotham'
|
||||
|
||||
|
||||
def test_evewho_character_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.evewho_character_url(self.my_character),
|
||||
evewho.character_url(self.my_character.character_id),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.evewho_character_url(None),
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.evewho_character_url(self.my_character.character_id),
|
||||
evewho.character_url(self.my_character.character_id),
|
||||
)
|
||||
|
||||
|
||||
def test_evewho_corporation_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.evewho_corporation_url(self.my_character),
|
||||
evewho.corporation_url(self.my_character.corporation_id),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.evewho_corporation_url(self.my_corporation),
|
||||
evewho.corporation_url(self.my_corporation.corporation_id),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.evewho_corporation_url(None),
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.evewho_corporation_url(self.my_character.corporation_id),
|
||||
evewho.corporation_url(self.my_character.corporation_id),
|
||||
)
|
||||
|
||||
|
||||
def test_evewho_alliance_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.evewho_alliance_url(self.my_character),
|
||||
evewho.alliance_url(self.my_character.alliance_id),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.evewho_alliance_url(self.my_character_2),
|
||||
'',
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.evewho_alliance_url(self.my_alliance),
|
||||
evewho.alliance_url(self.my_alliance.alliance_id),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.evewho_alliance_url(None),
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.evewho_alliance_url(self.my_character.alliance_id),
|
||||
evewho.alliance_url(self.my_character.alliance_id),
|
||||
)
|
||||
|
||||
|
||||
# dotlan
|
||||
|
||||
def test_dotlan_corporation_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.dotlan_corporation_url(self.my_character),
|
||||
dotlan.corporation_url(self.my_character.corporation_name),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.dotlan_corporation_url(self.my_corporation),
|
||||
dotlan.corporation_url(self.my_corporation.corporation_name),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.dotlan_corporation_url(None),
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.dotlan_corporation_url(self.my_character.corporation_name),
|
||||
dotlan.corporation_url(self.my_character.corporation_name),
|
||||
)
|
||||
|
||||
|
||||
def test_dotlan_alliance_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.dotlan_alliance_url(self.my_character),
|
||||
dotlan.alliance_url(self.my_character.alliance_name),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.dotlan_alliance_url(self.my_character_2),
|
||||
'',
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.dotlan_alliance_url(self.my_alliance),
|
||||
dotlan.alliance_url(self.my_alliance.alliance_name),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.dotlan_alliance_url(None),
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.dotlan_alliance_url(self.my_character.alliance_name),
|
||||
dotlan.alliance_url(self.my_character.alliance_name),
|
||||
)
|
||||
|
||||
def test_dotlan_region_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.dotlan_region_url(self.my_region_name),
|
||||
dotlan.region_url(self.my_region_name),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.dotlan_region_url(None),
|
||||
''
|
||||
)
|
||||
|
||||
def test_dotlan_solar_system_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.dotlan_solar_system_url(self.my_solar_system_name),
|
||||
dotlan.solar_system_url(self.my_solar_system_name),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.dotlan_solar_system_url(None),
|
||||
''
|
||||
)
|
||||
|
||||
|
||||
# zkillboard
|
||||
|
||||
def test_zkillboard_character_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_character_url(self.my_character),
|
||||
zkillboard.character_url(self.my_character.character_id),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_character_url(None),
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_character_url(self.my_character.character_id),
|
||||
zkillboard.character_url(self.my_character.character_id),
|
||||
)
|
||||
|
||||
|
||||
def test_zkillboard_corporation_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_corporation_url(self.my_character),
|
||||
zkillboard.corporation_url(self.my_character.corporation_id),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_corporation_url(self.my_corporation),
|
||||
zkillboard.corporation_url(self.my_corporation.corporation_id),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_corporation_url(None),
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_corporation_url(self.my_character.corporation_id),
|
||||
zkillboard.corporation_url(self.my_character.corporation_id),
|
||||
)
|
||||
|
||||
|
||||
def test_zkillboard_alliance_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_alliance_url(self.my_character),
|
||||
zkillboard.alliance_url(self.my_character.alliance_id),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_alliance_url(self.my_character_2),
|
||||
'',
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_alliance_url(self.my_alliance),
|
||||
zkillboard.alliance_url(self.my_alliance.alliance_id),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_alliance_url(None),
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_alliance_url(self.my_character.alliance_id),
|
||||
zkillboard.alliance_url(self.my_character.alliance_id),
|
||||
)
|
||||
|
||||
|
||||
def test_zkillboard_region_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_region_url(self.my_region_id),
|
||||
zkillboard.region_url(self.my_region_id),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_region_url(None),
|
||||
''
|
||||
)
|
||||
|
||||
|
||||
def test_zkillboard_solar_system_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_solar_system_url(self.my_solar_system_id),
|
||||
zkillboard.solar_system_url(self.my_solar_system_id),
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.zkillboard_solar_system_url(None),
|
||||
''
|
||||
)
|
||||
|
||||
|
||||
# image URLs
|
||||
|
||||
def test_character_portrait_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.character_portrait_url(123),
|
||||
EveCharacter.generic_portrait_url(123)
|
||||
|
||||
),
|
||||
self.assertEqual(
|
||||
evelinks.character_portrait_url(123, 128),
|
||||
EveCharacter.generic_portrait_url(123, 128)
|
||||
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.character_portrait_url(123, 99),
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.character_portrait_url(self.my_character),
|
||||
self.my_character.portrait_url()
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.character_portrait_url(None),
|
||||
''
|
||||
)
|
||||
|
||||
|
||||
def test_corporation_logo_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.corporation_logo_url(123),
|
||||
EveCorporationInfo.generic_logo_url(123)
|
||||
),
|
||||
self.assertEqual(
|
||||
evelinks.corporation_logo_url(123, 128),
|
||||
EveCorporationInfo.generic_logo_url(123, 128)
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.corporation_logo_url(123, 99),
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.corporation_logo_url(self.my_corporation),
|
||||
self.my_corporation.logo_url()
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.corporation_logo_url(self.my_character),
|
||||
self.my_character.corporation_logo_url()
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.corporation_logo_url(None),
|
||||
''
|
||||
)
|
||||
|
||||
|
||||
def test_alliance_logo_url(self):
|
||||
self.assertEqual(
|
||||
evelinks.alliance_logo_url(123),
|
||||
EveAllianceInfo.generic_logo_url(123)
|
||||
),
|
||||
self.assertEqual(
|
||||
evelinks.alliance_logo_url(123, 128),
|
||||
EveAllianceInfo.generic_logo_url(123, 128)
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.alliance_logo_url(123, 99),
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.alliance_logo_url(self.my_alliance),
|
||||
self.my_alliance.logo_url()
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.alliance_logo_url(self.my_character),
|
||||
self.my_character.alliance_logo_url()
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.alliance_logo_url(None),
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
evelinks.alliance_logo_url(self.my_character_2),
|
||||
''
|
||||
)
|
||||
|
||||
def test_type_icon_url(self):
|
||||
expected = eveimageserver.type_icon_url(123)
|
||||
self.assertEqual(evelinks.type_icon_url(123), expected)
|
||||
|
||||
expected = eveimageserver.type_icon_url(123, 128)
|
||||
self.assertEqual(evelinks.type_icon_url(123, 128), expected)
|
||||
|
||||
expected = ''
|
||||
self.assertEqual(evelinks.type_icon_url(123, 99), expected)
|
||||
|
||||
expected = ''
|
||||
self.assertEqual(evelinks.type_icon_url(None), expected)
|
||||
|
||||
def test_type_render_url(self):
|
||||
expected = eveimageserver.type_render_url(123)
|
||||
self.assertEqual(evelinks.type_render_url(123), expected)
|
||||
|
||||
expected = eveimageserver.type_render_url(123, 128)
|
||||
self.assertEqual(evelinks.type_render_url(123, 128), expected)
|
||||
|
||||
expected = ''
|
||||
self.assertEqual(evelinks.type_render_url(123, 99), expected)
|
||||
|
||||
expected = ''
|
||||
self.assertEqual(evelinks.type_render_url(None), expected)
|
||||
68
allianceauth/eveonline/evelinks/zkillboard.py
Normal file
68
allianceauth/eveonline/evelinks/zkillboard.py
Normal file
@@ -0,0 +1,68 @@
|
||||
# this module generates profile URLs for zKillboard
|
||||
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from . import (
|
||||
_ESI_CATEGORY_ALLIANCE,
|
||||
_ESI_CATEGORY_CORPORATION,
|
||||
_ESI_CATEGORY_CHARACTER,
|
||||
_ESI_CATEGORY_REGION,
|
||||
_ESI_CATEGORY_SOLARSYSTEM
|
||||
)
|
||||
|
||||
|
||||
_BASE_URL = 'https://zkillboard.com'
|
||||
|
||||
|
||||
def _build_url(category: str, eve_id: int) -> str:
|
||||
"""return url to profile page for an eve entity"""
|
||||
|
||||
if category == _ESI_CATEGORY_ALLIANCE:
|
||||
partial = 'alliance'
|
||||
|
||||
elif category == _ESI_CATEGORY_CORPORATION:
|
||||
partial = 'corporation'
|
||||
|
||||
elif category == _ESI_CATEGORY_CHARACTER:
|
||||
partial = 'character'
|
||||
|
||||
elif category == _ESI_CATEGORY_REGION:
|
||||
partial = 'region'
|
||||
|
||||
elif category == _ESI_CATEGORY_SOLARSYSTEM:
|
||||
partial = 'system'
|
||||
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
"Not implemented yet for category:" + category
|
||||
)
|
||||
|
||||
url = urljoin(
|
||||
_BASE_URL,
|
||||
'{}/{}/'.format(partial, int(eve_id))
|
||||
)
|
||||
return url
|
||||
|
||||
|
||||
def alliance_url(eve_id: int) -> str:
|
||||
"""url for page about given alliance on zKillboard"""
|
||||
return _build_url(_ESI_CATEGORY_ALLIANCE, eve_id)
|
||||
|
||||
|
||||
def character_url(eve_id: int) -> str:
|
||||
"""url for page about given character on zKillboard"""
|
||||
return _build_url(_ESI_CATEGORY_CHARACTER, eve_id)
|
||||
|
||||
|
||||
def corporation_url(eve_id: int) -> str:
|
||||
"""url for page about given corporation on zKillboard"""
|
||||
return _build_url(_ESI_CATEGORY_CORPORATION, eve_id)
|
||||
|
||||
|
||||
def region_url(eve_id: int) -> str:
|
||||
"""url for page about given region on zKillboard"""
|
||||
return _build_url(_ESI_CATEGORY_REGION, eve_id)
|
||||
|
||||
|
||||
def solar_system_url(eve_id: int) -> str:
|
||||
return _build_url(_ESI_CATEGORY_SOLARSYSTEM, eve_id)
|
||||
@@ -89,4 +89,6 @@ class EveCorporationManager(models.Manager):
|
||||
)
|
||||
|
||||
def update_corporation(self, corp_id):
|
||||
return self.get(corporation_id=corp_id).update_corporation(self.provider.get_corporation(corp_id))
|
||||
return self\
|
||||
.get(corporation_id=corp_id)\
|
||||
.update_corporation(self.provider.get_corporation(corp_id))
|
||||
|
||||
@@ -5,6 +5,9 @@ from .managers import EveCharacterManager, EveCharacterProviderManager
|
||||
from .managers import EveCorporationManager, EveCorporationProviderManager
|
||||
from .managers import EveAllianceManager, EveAllianceProviderManager
|
||||
from . import providers
|
||||
from .evelinks import eveimageserver
|
||||
|
||||
_DEFAULT_IMAGE_SIZE = 32
|
||||
|
||||
|
||||
class EveAllianceInfo(models.Model):
|
||||
@@ -21,9 +24,13 @@ class EveAllianceInfo(models.Model):
|
||||
for corp_id in alliance.corp_ids:
|
||||
if not EveCorporationInfo.objects.filter(corporation_id=corp_id).exists():
|
||||
EveCorporationInfo.objects.create_corporation(corp_id)
|
||||
EveCorporationInfo.objects.filter(corporation_id__in=alliance.corp_ids).update(alliance=self)
|
||||
EveCorporationInfo.objects.filter(alliance=self).exclude(corporation_id__in=alliance.corp_ids).update(
|
||||
alliance=None)
|
||||
EveCorporationInfo.objects.filter(
|
||||
corporation_id__in=alliance.corp_ids).update(alliance=self
|
||||
)
|
||||
EveCorporationInfo.objects\
|
||||
.filter(alliance=self)\
|
||||
.exclude(corporation_id__in=alliance.corp_ids)\
|
||||
.update(alliance=None)
|
||||
|
||||
def update_alliance(self, alliance: providers.Alliance = None):
|
||||
if alliance is None:
|
||||
@@ -35,14 +42,36 @@ class EveAllianceInfo(models.Model):
|
||||
def __str__(self):
|
||||
return self.alliance_name
|
||||
|
||||
def logo_url(self, size=32):
|
||||
return "https://image.eveonline.com/Alliance/%s_%s.png" % (self.alliance_id, size)
|
||||
@staticmethod
|
||||
def generic_logo_url(
|
||||
alliance_id: int, size: int = _DEFAULT_IMAGE_SIZE
|
||||
) -> str:
|
||||
"""image URL for the given alliance ID"""
|
||||
return eveimageserver.alliance_logo_url(alliance_id, size)
|
||||
|
||||
def logo_url(self, size: int = _DEFAULT_IMAGE_SIZE) -> str:
|
||||
"""image URL of this alliance"""
|
||||
return self.generic_logo_url(self.alliance_id, size)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.startswith('logo_url_'):
|
||||
size = item.strip('logo_url_')
|
||||
return self.logo_url(size)
|
||||
return self.__getattribute__(item)
|
||||
@property
|
||||
def logo_url_32(self) -> str:
|
||||
"""image URL for this alliance"""
|
||||
return self.logo_url(32)
|
||||
|
||||
@property
|
||||
def logo_url_64(self) -> str:
|
||||
"""image URL for this alliance"""
|
||||
return self.logo_url(64)
|
||||
|
||||
@property
|
||||
def logo_url_128(self) -> str:
|
||||
"""image URL for this alliance"""
|
||||
return self.logo_url(128)
|
||||
|
||||
@property
|
||||
def logo_url_256(self) -> str:
|
||||
"""image URL for this alliance"""
|
||||
return self.logo_url(256)
|
||||
|
||||
|
||||
class EveCorporationInfo(models.Model):
|
||||
@@ -50,7 +79,9 @@ class EveCorporationInfo(models.Model):
|
||||
corporation_name = models.CharField(max_length=254, unique=True)
|
||||
corporation_ticker = models.CharField(max_length=254)
|
||||
member_count = models.IntegerField()
|
||||
alliance = models.ForeignKey(EveAllianceInfo, blank=True, null=True, on_delete=models.SET_NULL)
|
||||
alliance = models.ForeignKey(
|
||||
EveAllianceInfo, blank=True, null=True, on_delete=models.SET_NULL
|
||||
)
|
||||
|
||||
objects = EveCorporationManager()
|
||||
provider = EveCorporationProviderManager()
|
||||
@@ -69,14 +100,36 @@ class EveCorporationInfo(models.Model):
|
||||
def __str__(self):
|
||||
return self.corporation_name
|
||||
|
||||
def logo_url(self, size=32):
|
||||
return "https://image.eveonline.com/Corporation/%s_%s.png" % (self.corporation_id, size)
|
||||
@staticmethod
|
||||
def generic_logo_url(
|
||||
corporation_id: int, size: int = _DEFAULT_IMAGE_SIZE
|
||||
) -> str:
|
||||
"""image URL for the given corporation ID"""
|
||||
return eveimageserver.corporation_logo_url(corporation_id, size)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.startswith('logo_url_'):
|
||||
size = item.strip('logo_url_')
|
||||
return self.logo_url(size)
|
||||
return self.__getattribute__(item)
|
||||
def logo_url(self, size: int = _DEFAULT_IMAGE_SIZE) -> str:
|
||||
"""image URL for this corporation"""
|
||||
return self.generic_logo_url(self.corporation_id, size)
|
||||
|
||||
@property
|
||||
def logo_url_32(self) -> str:
|
||||
"""image URL for this corporation"""
|
||||
return self.logo_url(32)
|
||||
|
||||
@property
|
||||
def logo_url_64(self) -> str:
|
||||
"""image URL for this corporation"""
|
||||
return self.logo_url(64)
|
||||
|
||||
@property
|
||||
def logo_url_128(self) -> str:
|
||||
"""image URL for this corporation"""
|
||||
return self.logo_url(128)
|
||||
|
||||
@property
|
||||
def logo_url_256(self) -> str:
|
||||
"""image URL for this corporation"""
|
||||
return self.logo_url(256)
|
||||
|
||||
|
||||
class EveCharacter(models.Model):
|
||||
@@ -128,11 +181,84 @@ class EveCharacter(models.Model):
|
||||
def __str__(self):
|
||||
return self.character_name
|
||||
|
||||
def portrait_url(self, size=32):
|
||||
return "https://image.eveonline.com/Character/%s_%s.jpg" % (self.character_id, size)
|
||||
@staticmethod
|
||||
def generic_portrait_url(
|
||||
character_id: int, size: int = _DEFAULT_IMAGE_SIZE
|
||||
) -> str:
|
||||
"""image URL for the given character ID"""
|
||||
return eveimageserver.character_portrait_url(character_id, size)
|
||||
|
||||
def __getattr__(self, item):
|
||||
if item.startswith('portrait_url_'):
|
||||
size = item.strip('portrait_url_')
|
||||
return self.portrait_url(size)
|
||||
return self.__getattribute__(item)
|
||||
def portrait_url(self, size=_DEFAULT_IMAGE_SIZE) -> str:
|
||||
"""image URL for this character"""
|
||||
return self.generic_portrait_url(self.character_id, size)
|
||||
|
||||
@property
|
||||
def portrait_url_32(self) -> str:
|
||||
"""image URL for this character"""
|
||||
return self.portrait_url(32)
|
||||
|
||||
@property
|
||||
def portrait_url_64(self) -> str:
|
||||
"""image URL for this character"""
|
||||
return self.portrait_url(64)
|
||||
|
||||
@property
|
||||
def portrait_url_128(self) -> str:
|
||||
"""image URL for this character"""
|
||||
return self.portrait_url(128)
|
||||
|
||||
@property
|
||||
def portrait_url_256(self) -> str:
|
||||
"""image URL for this character"""
|
||||
return self.portrait_url(256)
|
||||
|
||||
def corporation_logo_url(self, size=_DEFAULT_IMAGE_SIZE) -> str:
|
||||
"""image URL for corporation of this character"""
|
||||
return EveCorporationInfo.generic_logo_url(self.corporation_id, size)
|
||||
|
||||
@property
|
||||
def corporation_logo_url_32(self) -> str:
|
||||
"""image URL for corporation of this character"""
|
||||
return self.corporation_logo_url(32)
|
||||
|
||||
@property
|
||||
def corporation_logo_url_64(self) -> str:
|
||||
"""image URL for corporation of this character"""
|
||||
return self.corporation_logo_url(64)
|
||||
|
||||
@property
|
||||
def corporation_logo_url_128(self) -> str:
|
||||
"""image URL for corporation of this character"""
|
||||
return self.corporation_logo_url(128)
|
||||
|
||||
@property
|
||||
def corporation_logo_url_256(self) -> str:
|
||||
"""image URL for corporation of this character"""
|
||||
return self.corporation_logo_url(256)
|
||||
|
||||
def alliance_logo_url(self, size=_DEFAULT_IMAGE_SIZE) -> str:
|
||||
"""image URL for alliance of this character or empty string"""
|
||||
if self.alliance_id:
|
||||
return EveAllianceInfo.generic_logo_url(self.alliance_id, size)
|
||||
else:
|
||||
return ''
|
||||
|
||||
@property
|
||||
def alliance_logo_url_32(self) -> str:
|
||||
"""image URL for alliance of this character or empty string"""
|
||||
return self.alliance_logo_url(32)
|
||||
|
||||
@property
|
||||
def alliance_logo_url_64(self) -> str:
|
||||
"""image URL for alliance of this character or empty string"""
|
||||
return self.alliance_logo_url(64)
|
||||
|
||||
@property
|
||||
def alliance_logo_url_128(self) -> str:
|
||||
"""image URL for alliance of this character or empty string"""
|
||||
return self.alliance_logo_url(128)
|
||||
|
||||
@property
|
||||
def alliance_logo_url_256(self) -> str:
|
||||
"""image URL for alliance of this character or empty string"""
|
||||
return self.alliance_logo_url(256)
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
from esi.clients import esi_client_factory
|
||||
from bravado.exception import HTTPNotFound, HTTPUnprocessableEntity
|
||||
import logging
|
||||
import os
|
||||
|
||||
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
||||
from bravado.exception import HTTPNotFound, HTTPUnprocessableEntity, HTTPError
|
||||
from jsonschema.exceptions import RefResolutionError
|
||||
|
||||
from django.conf import settings
|
||||
from esi.clients import esi_client_factory
|
||||
|
||||
|
||||
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(
|
||||
os.path.abspath(__file__)), 'swagger.json'
|
||||
)
|
||||
"""
|
||||
Swagger spec operations:
|
||||
|
||||
@@ -12,6 +19,7 @@ get_alliances_alliance_id_corporations
|
||||
get_corporations_corporation_id
|
||||
get_characters_character_id
|
||||
get_universe_types_type_id
|
||||
post_character_affiliation
|
||||
"""
|
||||
|
||||
|
||||
@@ -149,10 +157,35 @@ class EveProvider(object):
|
||||
|
||||
|
||||
class EveSwaggerProvider(EveProvider):
|
||||
def __init__(self, token=None, adapter=None):
|
||||
self.client = esi_client_factory(token=token, spec_file=SWAGGER_SPEC_PATH)
|
||||
def __init__(self, token=None, adapter=None):
|
||||
if settings.DEBUG:
|
||||
self._client = None
|
||||
logger.info(
|
||||
'DEBUG mode detected: ESI client will be loaded on-demand.'
|
||||
)
|
||||
else:
|
||||
try:
|
||||
self._client = esi_client_factory(
|
||||
token=token, spec_file=SWAGGER_SPEC_PATH
|
||||
)
|
||||
except (HTTPError, RefResolutionError):
|
||||
logger.exception(
|
||||
'Failed to load ESI client on startup. '
|
||||
'Switching to on-demand loading for ESI client.'
|
||||
)
|
||||
self._client = None
|
||||
|
||||
self._token = token
|
||||
self.adapter = adapter or self
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
if self._client is None:
|
||||
self._client = esi_client_factory(
|
||||
token=self._token, spec_file=SWAGGER_SPEC_PATH
|
||||
)
|
||||
return self._client
|
||||
|
||||
def __str__(self):
|
||||
return 'esi'
|
||||
|
||||
@@ -189,11 +222,13 @@ class EveSwaggerProvider(EveProvider):
|
||||
def get_character(self, character_id):
|
||||
try:
|
||||
data = self.client.Character.get_characters_character_id(character_id=character_id).result()
|
||||
affiliation = self.client.Character.post_characters_affiliation(characters=[character_id]).result()[0]
|
||||
|
||||
model = Character(
|
||||
id=character_id,
|
||||
name=data['name'],
|
||||
corp_id=data['corporation_id'],
|
||||
alliance_id=data['alliance_id'] if 'alliance_id' in data else None,
|
||||
corp_id=affiliation['corporation_id'],
|
||||
alliance_id=affiliation['alliance_id'] if 'alliance_id' in affiliation else None,
|
||||
)
|
||||
return model
|
||||
except (HTTPNotFound, HTTPUnprocessableEntity):
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -7,6 +7,7 @@ from .models import EveCorporationInfo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TASK_PRIORITY = 7
|
||||
|
||||
@shared_task
|
||||
def update_corp(corp_id):
|
||||
@@ -27,11 +28,12 @@ def update_character(character_id):
|
||||
def run_model_update():
|
||||
# update existing corp models
|
||||
for corp in EveCorporationInfo.objects.all().values('corporation_id'):
|
||||
update_corp.delay(corp['corporation_id'])
|
||||
update_corp.apply_async(args=[corp['corporation_id']], priority=TASK_PRIORITY)
|
||||
|
||||
# update existing alliance models
|
||||
for alliance in EveAllianceInfo.objects.all().values('alliance_id'):
|
||||
update_alliance.delay(alliance['alliance_id'])
|
||||
update_alliance.apply_async(args=[alliance['alliance_id']], priority=TASK_PRIORITY)
|
||||
|
||||
#update existing character models
|
||||
for character in EveCharacter.objects.all().values('character_id'):
|
||||
update_character.delay(character['character_id'])
|
||||
update_character.apply_async(args=[character['character_id']], priority=TASK_PRIORITY)
|
||||
|
||||
312
allianceauth/eveonline/templatetags/evelinks.py
Normal file
312
allianceauth/eveonline/templatetags/evelinks.py
Normal file
@@ -0,0 +1,312 @@
|
||||
# This module defines template tags for evelinks URLs and eve image URLs
|
||||
#
|
||||
# Many tags will work both with their respective eveonline object
|
||||
# and their respective eve entity ID
|
||||
#
|
||||
# Example:
|
||||
# character URL on evewho: {{ my_character|evewho_character_url}}
|
||||
# character URL on evewho: {{ 1456384556|evewho_character_url}}
|
||||
#
|
||||
# For more examples see examples.html
|
||||
#
|
||||
# To add templatetags for additional providers just add the respective
|
||||
# template functions and let them call the generic functions
|
||||
|
||||
from django import template
|
||||
|
||||
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
from ..evelinks import eveimageserver, evewho, dotlan, zkillboard
|
||||
|
||||
register = template.Library()
|
||||
|
||||
_DEFAULT_IMAGE_SIZE = 32
|
||||
|
||||
|
||||
# generic functions
|
||||
|
||||
def _generic_character_url(
|
||||
provider: object,
|
||||
obj_prop: str,
|
||||
eve_obj: EveCharacter
|
||||
) -> str:
|
||||
"""returns character URL for given provider and object"""
|
||||
my_func = getattr(provider, 'character_url')
|
||||
if isinstance(eve_obj, EveCharacter):
|
||||
return my_func(getattr(eve_obj, obj_prop))
|
||||
|
||||
elif eve_obj is None:
|
||||
return ''
|
||||
|
||||
else:
|
||||
return my_func(eve_obj)
|
||||
|
||||
|
||||
def _generic_corporation_url(
|
||||
provider: object,
|
||||
obj_prop: str,
|
||||
eve_obj: object
|
||||
) -> str:
|
||||
"""returns corporation URL for given provider and object"""
|
||||
my_func = getattr(provider, 'corporation_url')
|
||||
if isinstance(eve_obj, (EveCharacter, EveCorporationInfo)):
|
||||
return my_func(getattr(eve_obj, obj_prop))
|
||||
|
||||
elif eve_obj is None:
|
||||
return ''
|
||||
|
||||
else:
|
||||
return my_func(eve_obj)
|
||||
|
||||
|
||||
def _generic_alliance_url(
|
||||
provider: object,
|
||||
obj_prop: str,
|
||||
eve_obj: object
|
||||
) -> str:
|
||||
"""returns alliance URL for given provider and object"""
|
||||
my_func = getattr(provider, 'alliance_url')
|
||||
|
||||
if isinstance(eve_obj, EveCharacter):
|
||||
if eve_obj.alliance_id:
|
||||
return my_func(getattr(eve_obj, obj_prop))
|
||||
else:
|
||||
return ''
|
||||
|
||||
elif isinstance(eve_obj, EveAllianceInfo):
|
||||
return my_func(getattr(eve_obj, obj_prop))
|
||||
|
||||
elif eve_obj is None:
|
||||
return ''
|
||||
|
||||
else:
|
||||
return my_func(eve_obj)
|
||||
|
||||
|
||||
def _generic_evelinks_url(
|
||||
provider: object,
|
||||
provider_func: str,
|
||||
eve_obj: object
|
||||
) -> str:
|
||||
"""returns evelinks URL for given provider, function and object"""
|
||||
my_func = getattr(provider, provider_func)
|
||||
if eve_obj is None:
|
||||
return ''
|
||||
|
||||
else:
|
||||
return my_func(eve_obj)
|
||||
|
||||
|
||||
# evewho
|
||||
|
||||
@register.filter
|
||||
def evewho_character_url(eve_obj: EveCharacter) -> str:
|
||||
"""generates an evewho URL for the given object
|
||||
Works with allianceauth.eveonline objects and eve entity IDs
|
||||
Returns URL or empty string
|
||||
"""
|
||||
return _generic_character_url(evewho, 'character_id', eve_obj)
|
||||
|
||||
|
||||
@register.filter
|
||||
def evewho_corporation_url(eve_obj: object) -> str:
|
||||
"""generates an evewho URL for the given object
|
||||
Works with allianceauth.eveonline objects and eve entity IDs
|
||||
Returns URL or empty string
|
||||
"""
|
||||
return _generic_corporation_url(evewho, 'corporation_id', eve_obj)
|
||||
|
||||
|
||||
@register.filter
|
||||
def evewho_alliance_url(eve_obj: object) -> str:
|
||||
"""generates an evewho URL for the given object
|
||||
Works with allianceauth.eveonline objects and eve entity IDs
|
||||
Returns URL or empty string
|
||||
"""
|
||||
return _generic_alliance_url(evewho, 'alliance_id', eve_obj)
|
||||
|
||||
|
||||
# dotlan
|
||||
|
||||
@register.filter
|
||||
def dotlan_corporation_url(eve_obj: object) -> str:
|
||||
"""generates a dotlan URL for the given object
|
||||
Works with allianceauth.eveonline objects and eve entity names
|
||||
Returns URL or empty string
|
||||
"""
|
||||
return _generic_corporation_url(dotlan, 'corporation_name', eve_obj)
|
||||
|
||||
|
||||
@register.filter
|
||||
def dotlan_alliance_url(eve_obj: object) -> str:
|
||||
"""generates a dotlan URL for the given object
|
||||
Works with allianceauth.eveonline objects and eve entity names
|
||||
Returns URL or empty string
|
||||
"""
|
||||
return _generic_alliance_url(dotlan, 'alliance_name', eve_obj)
|
||||
|
||||
|
||||
@register.filter
|
||||
def dotlan_region_url(eve_obj: object) -> str:
|
||||
"""generates a dotlan URL for the given object
|
||||
Works with eve entity names
|
||||
Returns URL or empty string
|
||||
"""
|
||||
return _generic_evelinks_url(dotlan, 'region_url', eve_obj)
|
||||
|
||||
|
||||
@register.filter
|
||||
def dotlan_solar_system_url(eve_obj: object) -> str:
|
||||
"""generates a dotlan URL for the given object
|
||||
Works with eve entity names
|
||||
Returns URL or empty string
|
||||
"""
|
||||
return _generic_evelinks_url(dotlan, 'solar_system_url', eve_obj)
|
||||
|
||||
|
||||
# zkillboard
|
||||
|
||||
@register.filter
|
||||
def zkillboard_character_url(eve_obj: EveCharacter) -> str:
|
||||
"""generates a zkillboard URL for the given object
|
||||
Works with allianceauth.eveonline objects and eve entity IDs
|
||||
Returns URL or empty string
|
||||
"""
|
||||
return _generic_character_url(zkillboard, 'character_id', eve_obj)
|
||||
|
||||
|
||||
@register.filter
|
||||
def zkillboard_corporation_url(eve_obj: object) -> str:
|
||||
"""generates a zkillboard URL for the given object
|
||||
Works with allianceauth.eveonline objects and eve entity IDs
|
||||
Returns URL or empty string
|
||||
"""
|
||||
return _generic_corporation_url(zkillboard, 'corporation_id', eve_obj)
|
||||
|
||||
|
||||
@register.filter
|
||||
def zkillboard_alliance_url(eve_obj: object) -> str:
|
||||
"""generates a zkillboard URL for the given object
|
||||
Works with allianceauth.eveonline objects and eve entity IDs
|
||||
Returns URL or empty string
|
||||
"""
|
||||
return _generic_alliance_url(zkillboard, 'alliance_id', eve_obj)
|
||||
|
||||
|
||||
@register.filter
|
||||
def zkillboard_region_url(eve_obj: object) -> str:
|
||||
"""generates a zkillboard URL for the given object
|
||||
Works with eve entity IDs
|
||||
Returns URL or empty string
|
||||
"""
|
||||
return _generic_evelinks_url(zkillboard, 'region_url', eve_obj)
|
||||
|
||||
|
||||
@register.filter
|
||||
def zkillboard_solar_system_url(eve_obj: object) -> str:
|
||||
"""generates zkillboard URL for the given object
|
||||
Works with eve entity IDs
|
||||
Returns URL or empty string
|
||||
"""
|
||||
return _generic_evelinks_url(zkillboard, 'solar_system_url', eve_obj)
|
||||
|
||||
|
||||
# image urls
|
||||
|
||||
@register.filter
|
||||
def character_portrait_url(
|
||||
eve_obj: object,
|
||||
size: int = _DEFAULT_IMAGE_SIZE
|
||||
) -> str:
|
||||
"""generates an image URL for the given object
|
||||
Works with EveCharacter objects or character IDs
|
||||
Returns URL or empty string
|
||||
"""
|
||||
if isinstance(eve_obj, EveCharacter):
|
||||
return eve_obj.portrait_url(size)
|
||||
|
||||
elif eve_obj is None:
|
||||
return ''
|
||||
|
||||
else:
|
||||
try:
|
||||
return EveCharacter.generic_portrait_url(eve_obj, size)
|
||||
except ValueError:
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter
|
||||
def corporation_logo_url(
|
||||
eve_obj: object,
|
||||
size: int = _DEFAULT_IMAGE_SIZE
|
||||
) -> str:
|
||||
"""generates image URL for the given object
|
||||
Works with EveCharacter, EveCorporationInfo objects or corporation IDs
|
||||
Returns URL or empty string
|
||||
"""
|
||||
if isinstance(eve_obj, EveCorporationInfo):
|
||||
return eve_obj.logo_url(size)
|
||||
|
||||
elif isinstance(eve_obj, EveCharacter):
|
||||
return eve_obj.corporation_logo_url(size)
|
||||
|
||||
elif eve_obj is None:
|
||||
return ''
|
||||
|
||||
else:
|
||||
try:
|
||||
return EveCorporationInfo.generic_logo_url(eve_obj, size)
|
||||
except ValueError:
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter
|
||||
def alliance_logo_url(
|
||||
eve_obj: object,
|
||||
size: int = _DEFAULT_IMAGE_SIZE
|
||||
) -> str:
|
||||
"""generates image URL for the given object
|
||||
Works with EveCharacter, EveAllianceInfo objects or alliance IDs
|
||||
Returns URL or empty string
|
||||
"""
|
||||
if isinstance(eve_obj, EveAllianceInfo):
|
||||
return eve_obj.logo_url(size)
|
||||
|
||||
elif isinstance(eve_obj, EveCharacter):
|
||||
return eve_obj.alliance_logo_url(size)
|
||||
|
||||
elif eve_obj is None:
|
||||
return ''
|
||||
|
||||
else:
|
||||
try:
|
||||
return EveAllianceInfo.generic_logo_url(eve_obj, size)
|
||||
except ValueError:
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter
|
||||
def type_icon_url(
|
||||
type_id: int,
|
||||
size: int = _DEFAULT_IMAGE_SIZE
|
||||
) -> str:
|
||||
"""generates a icon image URL for the given type ID
|
||||
Returns URL or empty string
|
||||
"""
|
||||
try:
|
||||
return eveimageserver.type_icon_url(type_id, size)
|
||||
except ValueError:
|
||||
return ''
|
||||
|
||||
|
||||
@register.filter
|
||||
def type_render_url(
|
||||
type_id: int,
|
||||
size: int = _DEFAULT_IMAGE_SIZE
|
||||
) -> str:
|
||||
"""generates a render image URL for the given type ID
|
||||
Returns URL or empty string
|
||||
"""
|
||||
try:
|
||||
return eveimageserver.type_render_url(type_id, size)
|
||||
except ValueError:
|
||||
return ''
|
||||
84
allianceauth/eveonline/templatetags/examples.html
Normal file
84
allianceauth/eveonline/templatetags/examples.html
Normal file
@@ -0,0 +1,84 @@
|
||||
<!-- This is an example template for the evelinks template tags
|
||||
|
||||
Needs to be called with a context containing three objects:
|
||||
|
||||
- EveCharacter: my_character
|
||||
- EveCorporationInfo: my_corporation
|
||||
- EveAllianceInfo: my_alliance
|
||||
|
||||
-->
|
||||
|
||||
{% extends 'allianceauth/base.html' %}
|
||||
{% load evelinks %}
|
||||
|
||||
{% block page_title %}Evelinks examples{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">Evelinks templatetags examples</h1>
|
||||
<div class="col-lg-12 container">
|
||||
|
||||
<h2>profile URLs</h2>
|
||||
|
||||
<div class="rows">
|
||||
|
||||
<div class="col-md-4">
|
||||
<h3>evewho</h3>
|
||||
<p><a href="{{ my_character|evewho_character_url}}">character from character object</a></p>
|
||||
<p><a href="{{ my_corporation|evewho_corporation_url}}">corporation form corporation object</a></p>
|
||||
<p><a href="{{ my_character|evewho_corporation_url}}">corporation from charachter object</a></p>
|
||||
<p><a href="{{ my_alliance|evewho_alliance_url}}">alliance from alliance object</a></p>
|
||||
<p><a href="{{ my_character|evewho_alliance_url}}">alliance from character object</a></p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<h3>dotlan</h3>
|
||||
<p><a href="{{ my_character|dotlan_corporation_url}}">corporation form character object</a></p>
|
||||
<p><a href="{{ my_corporation|dotlan_corporation_url}}">corporation form corporation object</a></p>
|
||||
<p><a href="{{ my_character|dotlan_alliance_url}}">alliance from character object</a></p>
|
||||
<p><a href="{{ my_alliance|dotlan_alliance_url}}">alliance from alliance object</a></p>
|
||||
<p><a href="{{ 'Black Rise'|dotlan_region_url}}">region from name string</a></p>
|
||||
<p><a href="{{ 'Tama'|dotlan_solar_system_url}}">solar system from name string</a></p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<h3>zkillboard</h3>
|
||||
<p><a href="{{ my_character|zkillboard_character_url}}">character from character object</a></p>
|
||||
<p><a href="{{ my_character|zkillboard_corporation_url}}">corporation from character object</a></p>
|
||||
<p><a href="{{ my_corporation|zkillboard_corporation_url}}">corporation form corporation object</a></p>
|
||||
<p><a href="{{ my_character|zkillboard_alliance_url}}">alliance from character object</a></p>
|
||||
<p><a href="{{ my_alliance|zkillboard_alliance_url}}">alliance from alliance object</a></p>
|
||||
<p><a href="{{ 10000069|zkillboard_region_url}}">region from ID</a></p>
|
||||
<p><a href="{{ 30002813|zkillboard_solar_system_url}}">solar sytem from ID</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>image URLs</h2>
|
||||
|
||||
<div class="rows">
|
||||
|
||||
<div class="col-md-4">
|
||||
<p>character from ID: <img src="{{ my_character.character_id|character_portrait_url:128}}"></p>
|
||||
<p>character from character object: <img src="{{ my_character|character_portrait_url:128}}"></p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<p>corporation from ID: <img src="{{ my_character.corporation_id|corporation_logo_url:128}}"></p>
|
||||
<p>corporation from character object: <img src="{{ my_character|corporation_logo_url:128}}"></p>
|
||||
<p>corporation from corporation object: <img src="{{ my_corporation|corporation_logo_url:128}}"></p>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<p>alliance from ID: <img src="{{ my_character.alliance_id|alliance_logo_url:128}}"></p>
|
||||
<p>alliance from character object: <img src="{{ my_character|alliance_logo_url:128}}"></p>
|
||||
<p>alliance from alliance object: <img src="{{ my_alliance|alliance_logo_url:128}}"></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,29 @@
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
||||
def set_logger(logger_name: str, name: str) -> object:
|
||||
"""set logger for current test module
|
||||
|
||||
Args:
|
||||
- logger: current logger object
|
||||
- name: name of current module, e.g. __file__
|
||||
|
||||
Returns:
|
||||
- amended logger
|
||||
"""
|
||||
|
||||
# reconfigure logger so we get logging from tested module
|
||||
f_format = logging.Formatter(
|
||||
'%(asctime)s - %(levelname)s - %(module)s:%(funcName)s - %(message)s'
|
||||
)
|
||||
f_handler = logging.FileHandler(
|
||||
'{}.log'.format(os.path.splitext(name)[0]),
|
||||
'w+'
|
||||
)
|
||||
f_handler.setFormatter(f_format)
|
||||
logger = logging.getLogger(logger_name)
|
||||
logger.level = logging.DEBUG
|
||||
logger.addHandler(f_handler)
|
||||
logger.propagate = False
|
||||
return logger
|
||||
|
||||
1
allianceauth/eveonline/tests/swagger_old.json
Normal file
1
allianceauth/eveonline/tests/swagger_old.json
Normal file
File diff suppressed because one or more lines are too long
@@ -26,12 +26,22 @@ class EveCharacterManagerTestCase(TestCase):
|
||||
|
||||
@property
|
||||
def corp(self):
|
||||
return Corporation(id='2345', name='Test Corp', alliance_id='3456', ticker='0BUGS')
|
||||
return Corporation(
|
||||
id='2345',
|
||||
name='Test Corp',
|
||||
alliance_id='3456',
|
||||
ticker='0BUGS'
|
||||
)
|
||||
|
||||
@mock.patch('allianceauth.eveonline.managers.providers.provider')
|
||||
def test_create_character(self, provider):
|
||||
# Also covers create_character_obj
|
||||
expected = self.TestCharacter(id='1234', name='Test Character', corp_id='2345', alliance_id='3456')
|
||||
expected = self.TestCharacter(
|
||||
id='1234',
|
||||
name='Test Character',
|
||||
corp_id='2345',
|
||||
alliance_id='3456'
|
||||
)
|
||||
|
||||
provider.get_character.return_value = expected
|
||||
|
||||
@@ -48,18 +58,19 @@ class EveCharacterManagerTestCase(TestCase):
|
||||
@mock.patch('allianceauth.eveonline.managers.providers.provider')
|
||||
def test_update_character(self, provider):
|
||||
# Also covers Model.update_character
|
||||
existing = EveCharacter.objects.create(
|
||||
EveCharacter.objects.create(
|
||||
character_id='1234',
|
||||
character_name='character.name',
|
||||
corporation_id='character.corp.id',
|
||||
corporation_name='character.corp.name',
|
||||
corporation_ticker='character.corp.ticker',
|
||||
corporation_ticker='abc',
|
||||
alliance_id='character.alliance.id',
|
||||
alliance_name='character.alliance.name',
|
||||
)
|
||||
|
||||
expected = self.TestCharacter(id='1234', name='Test Character', corp_id='2345', alliance_id='3456')
|
||||
|
||||
expected = self.TestCharacter(
|
||||
id='1234', name='Test Character', corp_id='2345', alliance_id='3456'
|
||||
)
|
||||
provider.get_character.return_value = expected
|
||||
|
||||
result = EveCharacter.objects.update_character('1234')
|
||||
@@ -73,21 +84,26 @@ class EveCharacterManagerTestCase(TestCase):
|
||||
self.assertEqual(result.alliance_name, expected.alliance.name)
|
||||
|
||||
def test_get_character_by_id(self):
|
||||
EveCharacter.objects.all().delete()
|
||||
EveCharacter.objects.create(
|
||||
character_id='1234',
|
||||
character_name='character.name',
|
||||
corporation_id='character.corp.id',
|
||||
corporation_name='character.corp.name',
|
||||
corporation_ticker='character.corp.ticker',
|
||||
corporation_ticker='abc',
|
||||
alliance_id='character.alliance.id',
|
||||
alliance_name='character.alliance.name',
|
||||
)
|
||||
|
||||
# try to get existing character
|
||||
result = EveCharacter.objects.get_character_by_id('1234')
|
||||
|
||||
self.assertEqual(result.character_id, '1234')
|
||||
self.assertEqual(result.character_name, 'character.name')
|
||||
|
||||
# try to get non existing character
|
||||
self.assertIsNone(EveCharacter.objects.get_character_by_id('9999'))
|
||||
|
||||
|
||||
class EveAllianceProviderManagerTestCase(TestCase):
|
||||
@mock.patch('allianceauth.eveonline.managers.providers.provider')
|
||||
@@ -110,8 +126,13 @@ class EveAllianceManagerTestCase(TestCase):
|
||||
@mock.patch('allianceauth.eveonline.managers.providers.provider')
|
||||
def test_create_alliance(self, provider, populate_alliance):
|
||||
# Also covers create_alliance_obj
|
||||
expected = self.TestAlliance(id='3456', name='Test Alliance', ticker='TEST',
|
||||
corp_ids=['2345'], executor_corp_id='2345')
|
||||
expected = self.TestAlliance(
|
||||
id='3456',
|
||||
name='Test Alliance',
|
||||
ticker='TEST',
|
||||
corp_ids=['2345'],
|
||||
executor_corp_id='2345'
|
||||
)
|
||||
|
||||
provider.get_alliance.return_value = expected
|
||||
|
||||
@@ -132,8 +153,13 @@ class EveAllianceManagerTestCase(TestCase):
|
||||
alliance_ticker='alliance.ticker',
|
||||
executor_corp_id='alliance.executor_corp_id',
|
||||
)
|
||||
expected = self.TestAlliance(id='3456', name='Test Alliance', ticker='TEST',
|
||||
corp_ids=['2345'], executor_corp_id='2345')
|
||||
expected = self.TestAlliance(
|
||||
id='3456',
|
||||
name='Test Alliance',
|
||||
ticker='TEST',
|
||||
corp_ids=['2345'],
|
||||
executor_corp_id='2345'
|
||||
)
|
||||
|
||||
provider.get_alliance.return_value = expected
|
||||
|
||||
@@ -159,13 +185,22 @@ class EveCorporationManagerTestCase(TestCase):
|
||||
class TestCorporation(Corporation):
|
||||
@property
|
||||
def alliance(self):
|
||||
return EveAllianceManagerTestCase.TestAlliance(id='3456', name='Test Alliance', ticker='TEST',
|
||||
corp_ids=['2345'], executor_corp_id='2345')
|
||||
return EveAllianceManagerTestCase.TestAlliance(
|
||||
id='3456',
|
||||
name='Test Alliance',
|
||||
ticker='TEST',
|
||||
corp_ids=['2345'],
|
||||
executor_corp_id='2345'
|
||||
)
|
||||
|
||||
@property
|
||||
def ceo(self):
|
||||
return EveCharacterManagerTestCase.TestCharacter(id='1234', name='Test Character',
|
||||
corp_id='2345', alliance_id='3456')
|
||||
return EveCharacterManagerTestCase.TestCharacter(
|
||||
id='1234',
|
||||
name='Test Character',
|
||||
corp_id='2345',
|
||||
alliance_id='3456'
|
||||
)
|
||||
|
||||
@mock.patch('allianceauth.eveonline.managers.providers.provider')
|
||||
def test_create_corporation(self, provider):
|
||||
@@ -177,8 +212,14 @@ class EveCorporationManagerTestCase(TestCase):
|
||||
executor_corp_id='alliance.executor_corp_id',
|
||||
)
|
||||
|
||||
expected = self.TestCorporation(id='2345', name='Test Corp', ticker='0BUGS',
|
||||
ceo_id='1234', members=1, alliance_id='3456')
|
||||
expected = self.TestCorporation(
|
||||
id='2345',
|
||||
name='Test Corp',
|
||||
ticker='0BUGS',
|
||||
ceo_id='1234',
|
||||
members=1,
|
||||
alliance_id='3456'
|
||||
)
|
||||
|
||||
provider.get_corp.return_value = expected
|
||||
|
||||
@@ -191,7 +232,30 @@ class EveCorporationManagerTestCase(TestCase):
|
||||
self.assertEqual(result.alliance, exp_alliance)
|
||||
|
||||
@mock.patch('allianceauth.eveonline.managers.providers.provider')
|
||||
def test_create_corporation(self, provider):
|
||||
def test_create_corporation_no_alliance(self, provider):
|
||||
# variant to test no alliance case
|
||||
# Also covers create_corp_obj
|
||||
expected = self.TestCorporation(
|
||||
id='2345',
|
||||
name='Test Corp',
|
||||
ticker='0BUGS',
|
||||
ceo_id='1234',
|
||||
members=1,
|
||||
alliance_id='3456'
|
||||
)
|
||||
|
||||
provider.get_corp.return_value = expected
|
||||
|
||||
result = EveCorporationInfo.objects.create_corporation('2345')
|
||||
|
||||
self.assertEqual(result.corporation_id, expected.id)
|
||||
self.assertEqual(result.corporation_name, expected.name)
|
||||
self.assertEqual(result.corporation_ticker, expected.ticker)
|
||||
self.assertEqual(result.member_count, expected.members)
|
||||
self.assertIsNone(result.alliance)
|
||||
|
||||
@mock.patch('allianceauth.eveonline.managers.providers.provider')
|
||||
def test_update_corporation(self, provider):
|
||||
# Also covers Model.update_corporation
|
||||
exp_alliance = EveAllianceInfo.objects.create(
|
||||
alliance_id='3456',
|
||||
@@ -203,13 +267,19 @@ class EveCorporationManagerTestCase(TestCase):
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id='2345',
|
||||
corporation_name='corp.name',
|
||||
corporation_ticker='corp.ticker',
|
||||
corporation_ticker='abc',
|
||||
member_count=10,
|
||||
alliance=None,
|
||||
)
|
||||
|
||||
expected = self.TestCorporation(id='2345', name='Test Corp', ticker='0BUGS',
|
||||
ceo_id='1234', members=1, alliance_id='3456')
|
||||
expected = self.TestCorporation(
|
||||
id='2345',
|
||||
name='Test Corp',
|
||||
ticker='0BUGS',
|
||||
ceo_id='1234',
|
||||
members=1,
|
||||
alliance_id='3456'
|
||||
)
|
||||
|
||||
provider.get_corp.return_value = expected
|
||||
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
from ..models import (
|
||||
EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
)
|
||||
from ..providers import Alliance, Corporation, Character
|
||||
from ..evelinks import eveimageserver
|
||||
|
||||
|
||||
class EveCharacterTestCase(TestCase):
|
||||
@@ -13,7 +19,7 @@ class EveCharacterTestCase(TestCase):
|
||||
character_name='character.name',
|
||||
corporation_id='2345',
|
||||
corporation_name='character.corp.name',
|
||||
corporation_ticker='character.corp.ticker',
|
||||
corporation_ticker='abc',
|
||||
alliance_id='character.alliance.id',
|
||||
alliance_name='character.alliance.name',
|
||||
)
|
||||
@@ -21,7 +27,7 @@ class EveCharacterTestCase(TestCase):
|
||||
expected = EveCorporationInfo.objects.create(
|
||||
corporation_id='2345',
|
||||
corporation_name='corp.name',
|
||||
corporation_ticker='corp.ticker',
|
||||
corporation_ticker='abc',
|
||||
member_count=10,
|
||||
alliance=None,
|
||||
)
|
||||
@@ -29,7 +35,7 @@ class EveCharacterTestCase(TestCase):
|
||||
incorrect = EveCorporationInfo.objects.create(
|
||||
corporation_id='9999',
|
||||
corporation_name='corp.name1',
|
||||
corporation_ticker='corp.ticker1',
|
||||
corporation_ticker='abc1',
|
||||
member_count=10,
|
||||
alliance=None,
|
||||
)
|
||||
@@ -47,13 +53,13 @@ class EveCharacterTestCase(TestCase):
|
||||
character_name='character.name',
|
||||
corporation_id='2345',
|
||||
corporation_name='character.corp.name',
|
||||
corporation_ticker='character.corp.ticker',
|
||||
corporation_ticker='abc',
|
||||
alliance_id='character.alliance.id',
|
||||
alliance_name='character.alliance.name',
|
||||
)
|
||||
|
||||
with self.assertRaises(EveCorporationInfo.DoesNotExist):
|
||||
result = character.corporation
|
||||
character.corporation
|
||||
|
||||
def test_alliance_prop(self):
|
||||
"""
|
||||
@@ -64,7 +70,7 @@ class EveCharacterTestCase(TestCase):
|
||||
character_name='character.name',
|
||||
corporation_id='2345',
|
||||
corporation_name='character.corp.name',
|
||||
corporation_ticker='character.corp.ticker',
|
||||
corporation_ticker='abc',
|
||||
alliance_id='3456',
|
||||
alliance_name='character.alliance.name',
|
||||
)
|
||||
@@ -96,13 +102,13 @@ class EveCharacterTestCase(TestCase):
|
||||
character_name='character.name',
|
||||
corporation_id='2345',
|
||||
corporation_name='character.corp.name',
|
||||
corporation_ticker='character.corp.ticker',
|
||||
corporation_ticker='abc',
|
||||
alliance_id='3456',
|
||||
alliance_name='character.alliance.name',
|
||||
)
|
||||
|
||||
with self.assertRaises(EveAllianceInfo.DoesNotExist):
|
||||
result = character.alliance
|
||||
character.alliance
|
||||
|
||||
def test_alliance_prop_none(self):
|
||||
"""
|
||||
@@ -113,9 +119,407 @@ class EveCharacterTestCase(TestCase):
|
||||
character_name='character.name',
|
||||
corporation_id='2345',
|
||||
corporation_name='character.corp.name',
|
||||
corporation_ticker='character.corp.ticker',
|
||||
corporation_ticker='abc',
|
||||
alliance_id=None,
|
||||
alliance_name=None,
|
||||
)
|
||||
|
||||
self.assertIsNone(character.alliance)
|
||||
|
||||
@patch('allianceauth.eveonline.providers.provider')
|
||||
def test_update_character(self, mock_provider):
|
||||
mock_provider.get_corp.return_value = Corporation(
|
||||
id=2002,
|
||||
name='Dummy Corp 2',
|
||||
ticker='DC2',
|
||||
ceo_id=1001,
|
||||
members=34,
|
||||
)
|
||||
|
||||
my_character = EveCharacter.objects.create(
|
||||
character_id='1001',
|
||||
character_name='Bruce Wayne',
|
||||
corporation_id='2001',
|
||||
corporation_name='Dummy Corp 1',
|
||||
corporation_ticker='DC1',
|
||||
alliance_id='3001',
|
||||
alliance_name='Dummy Alliance 1',
|
||||
)
|
||||
my_updated_character = Character(
|
||||
name='Bruce X. Wayne',
|
||||
corp_id=2002
|
||||
)
|
||||
my_character.update_character(my_updated_character)
|
||||
self.assertEqual(my_character.character_name, 'Bruce X. Wayne')
|
||||
|
||||
# todo: add test cases not yet covered, e.g. with alliance
|
||||
|
||||
def test_image_url(self):
|
||||
self.assertEqual(
|
||||
EveCharacter.generic_portrait_url(42),
|
||||
eveimageserver._eve_entity_image_url('character', 42)
|
||||
)
|
||||
self.assertEqual(
|
||||
EveCharacter.generic_portrait_url(42, 256),
|
||||
eveimageserver._eve_entity_image_url('character', 42, 256)
|
||||
)
|
||||
|
||||
def test_portrait_urls(self):
|
||||
x = EveCharacter(
|
||||
character_id='42',
|
||||
character_name='character.name',
|
||||
corporation_id='123',
|
||||
corporation_name='corporation.name',
|
||||
corporation_ticker='ABC',
|
||||
)
|
||||
self.assertEqual(
|
||||
x.portrait_url(),
|
||||
eveimageserver._eve_entity_image_url('character', 42)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.portrait_url(64),
|
||||
eveimageserver._eve_entity_image_url('character', 42, size=64)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.portrait_url_32,
|
||||
eveimageserver._eve_entity_image_url('character', 42, size=32)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.portrait_url_64,
|
||||
eveimageserver._eve_entity_image_url('character', 42, size=64)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.portrait_url_128,
|
||||
eveimageserver._eve_entity_image_url('character', 42, size=128)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.portrait_url_256,
|
||||
eveimageserver._eve_entity_image_url('character', 42, size=256)
|
||||
)
|
||||
|
||||
def test_corporation_logo_urls(self):
|
||||
x = EveCharacter(
|
||||
character_id='42',
|
||||
character_name='character.name',
|
||||
corporation_id='123',
|
||||
corporation_name='corporation.name',
|
||||
corporation_ticker='ABC',
|
||||
)
|
||||
self.assertEqual(
|
||||
x.corporation_logo_url(),
|
||||
eveimageserver._eve_entity_image_url('corporation', 123)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.corporation_logo_url(256),
|
||||
eveimageserver._eve_entity_image_url('corporation', 123, size=256)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.corporation_logo_url_32,
|
||||
eveimageserver._eve_entity_image_url('corporation', 123, size=32)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.corporation_logo_url_64,
|
||||
eveimageserver._eve_entity_image_url('corporation', 123, size=64)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.corporation_logo_url_128,
|
||||
eveimageserver._eve_entity_image_url('corporation', 123, size=128)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.corporation_logo_url_256,
|
||||
eveimageserver._eve_entity_image_url('corporation', 123, size=256)
|
||||
)
|
||||
|
||||
def test_alliance_logo_urls(self):
|
||||
x = EveCharacter(
|
||||
character_id='42',
|
||||
character_name='character.name',
|
||||
corporation_id='123',
|
||||
corporation_name='corporation.name',
|
||||
corporation_ticker='ABC',
|
||||
)
|
||||
self.assertEqual(
|
||||
x.alliance_logo_url(),
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
x.alliance_logo_url_32,
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
x.alliance_logo_url_64,
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
x.alliance_logo_url_128,
|
||||
''
|
||||
)
|
||||
self.assertEqual(
|
||||
x.alliance_logo_url_256,
|
||||
''
|
||||
)
|
||||
x.alliance_id = 987
|
||||
self.assertEqual(
|
||||
x.alliance_logo_url(),
|
||||
eveimageserver._eve_entity_image_url('alliance', 987)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.alliance_logo_url(128),
|
||||
eveimageserver._eve_entity_image_url('alliance', 987, size=128)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.alliance_logo_url_32,
|
||||
eveimageserver._eve_entity_image_url('alliance', 987, size=32)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.alliance_logo_url_64,
|
||||
eveimageserver._eve_entity_image_url('alliance', 987, size=64)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.alliance_logo_url_128,
|
||||
eveimageserver._eve_entity_image_url('alliance', 987, size=128)
|
||||
)
|
||||
self.assertEqual(
|
||||
x.alliance_logo_url_256,
|
||||
eveimageserver._eve_entity_image_url('alliance', 987, size=256)
|
||||
)
|
||||
|
||||
|
||||
class EveAllianceTestCase(TestCase):
|
||||
|
||||
def test_str(self):
|
||||
my_alliance = EveAllianceInfo(
|
||||
alliance_id=3001,
|
||||
alliance_name='Dummy Alliance 1',
|
||||
alliance_ticker='DA1',
|
||||
executor_corp_id=2001
|
||||
)
|
||||
self.assertEqual(str(my_alliance), 'Dummy Alliance 1')
|
||||
|
||||
@patch(
|
||||
'allianceauth.eveonline.models.EveCorporationInfo.objects.create_corporation'
|
||||
)
|
||||
def test_populate_alliance(self, mock_create_corporation):
|
||||
|
||||
def create_corp(corp_id):
|
||||
if corp_id == 2002:
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id=2002,
|
||||
corporation_name='Dummy Corporation 2',
|
||||
corporation_ticker='DC2',
|
||||
member_count=87,
|
||||
)
|
||||
else:
|
||||
raise ValueError()
|
||||
|
||||
mock_EveAllianceProviderManager = Mock()
|
||||
mock_EveAllianceProviderManager.get_alliance.return_value = \
|
||||
Alliance(
|
||||
id=3001,
|
||||
name='Dummy Alliance 1',
|
||||
corp_ids=[2001, 2002]
|
||||
)
|
||||
mock_create_corporation.side_effect = create_corp
|
||||
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id=2001,
|
||||
corporation_name='Dummy Corporation 1',
|
||||
corporation_ticker='DC1',
|
||||
member_count=42,
|
||||
)
|
||||
|
||||
my_alliance = EveAllianceInfo(
|
||||
alliance_id=3001,
|
||||
alliance_name='Dummy Alliance 1',
|
||||
alliance_ticker='DA1',
|
||||
executor_corp_id=2001
|
||||
)
|
||||
my_alliance.provider = mock_EveAllianceProviderManager
|
||||
my_alliance.save()
|
||||
my_alliance.populate_alliance()
|
||||
|
||||
for corporation in EveCorporationInfo.objects\
|
||||
.filter(corporation_id__in=[2001, 2002]
|
||||
):
|
||||
self.assertEqual(corporation.alliance, my_alliance)
|
||||
|
||||
def test_update_alliance_with_object(self):
|
||||
my_alliance = EveAllianceInfo.objects.create(
|
||||
alliance_id=3001,
|
||||
alliance_name='Dummy Alliance 1',
|
||||
alliance_ticker='DA1',
|
||||
executor_corp_id=2001
|
||||
)
|
||||
updated_alliance = Alliance(
|
||||
id=3002,
|
||||
name='Dummy Alliance 2',
|
||||
corp_ids=[2004],
|
||||
executor_corp_id=2004
|
||||
)
|
||||
my_alliance.update_alliance(updated_alliance)
|
||||
my_alliance.refresh_from_db()
|
||||
self.assertEqual(int(my_alliance.executor_corp_id), 2004)
|
||||
|
||||
# potential bug
|
||||
# update_alliance() is only updateting executor_corp_id when object is given
|
||||
|
||||
def test_update_alliance_wo_object(self):
|
||||
mock_EveAllianceProviderManager = Mock()
|
||||
mock_EveAllianceProviderManager.get_alliance.return_value = \
|
||||
Alliance(
|
||||
id=3002,
|
||||
name='Dummy Alliance 2',
|
||||
corp_ids=[2004],
|
||||
executor_corp_id=2004
|
||||
)
|
||||
|
||||
my_alliance = EveAllianceInfo.objects.create(
|
||||
alliance_id=3001,
|
||||
alliance_name='Dummy Alliance 1',
|
||||
alliance_ticker='DA1',
|
||||
executor_corp_id=2001
|
||||
)
|
||||
my_alliance.provider = mock_EveAllianceProviderManager
|
||||
my_alliance.save()
|
||||
Alliance(
|
||||
name='Dummy Alliance 2',
|
||||
corp_ids=[2004],
|
||||
executor_corp_id=2004
|
||||
)
|
||||
my_alliance.update_alliance()
|
||||
my_alliance.refresh_from_db()
|
||||
self.assertEqual(int(my_alliance.executor_corp_id), 2004)
|
||||
|
||||
# potential bug
|
||||
# update_alliance() is only updateting executor_corp_id nothing else ???
|
||||
|
||||
def test_image_url(self):
|
||||
self.assertEqual(
|
||||
EveAllianceInfo.generic_logo_url(42),
|
||||
eveimageserver._eve_entity_image_url('alliance', 42)
|
||||
)
|
||||
self.assertEqual(
|
||||
EveAllianceInfo.generic_logo_url(42, 256),
|
||||
eveimageserver._eve_entity_image_url('alliance', 42, 256)
|
||||
)
|
||||
|
||||
def test_logo_url(self):
|
||||
x = EveAllianceInfo(
|
||||
alliance_id='42',
|
||||
alliance_name='alliance.name',
|
||||
alliance_ticker='ABC',
|
||||
executor_corp_id='123'
|
||||
)
|
||||
self.assertEqual(
|
||||
x.logo_url(),
|
||||
'https://images.evetech.net/alliances/42/logo?size=32'
|
||||
)
|
||||
self.assertEqual(
|
||||
x.logo_url(64),
|
||||
'https://images.evetech.net/alliances/42/logo?size=64'
|
||||
)
|
||||
self.assertEqual(
|
||||
x.logo_url_32,
|
||||
'https://images.evetech.net/alliances/42/logo?size=32'
|
||||
)
|
||||
self.assertEqual(
|
||||
x.logo_url_64,
|
||||
'https://images.evetech.net/alliances/42/logo?size=64'
|
||||
)
|
||||
self.assertEqual(
|
||||
x.logo_url_128,
|
||||
'https://images.evetech.net/alliances/42/logo?size=128'
|
||||
)
|
||||
self.assertEqual(
|
||||
x.logo_url_256,
|
||||
'https://images.evetech.net/alliances/42/logo?size=256'
|
||||
)
|
||||
|
||||
|
||||
class EveCorporationTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
my_alliance = EveAllianceInfo.objects.create(
|
||||
alliance_id=3001,
|
||||
alliance_name='Dummy Alliance 1',
|
||||
alliance_ticker='DA1',
|
||||
executor_corp_id=2001
|
||||
)
|
||||
self.my_corp = EveCorporationInfo(
|
||||
corporation_id=2001,
|
||||
corporation_name='Dummy Corporation 1',
|
||||
corporation_ticker='DC1',
|
||||
member_count=42,
|
||||
alliance=my_alliance
|
||||
)
|
||||
|
||||
def test_str(self):
|
||||
self.assertEqual(str(self.my_corp), 'Dummy Corporation 1')
|
||||
|
||||
def test_update_corporation_from_object_w_alliance(self):
|
||||
updated_corp = Corporation(
|
||||
members=87
|
||||
)
|
||||
self.my_corp.update_corporation(updated_corp)
|
||||
self.assertEqual(self.my_corp.member_count, 87)
|
||||
|
||||
# potential bug
|
||||
# update_corporation updates member_count only
|
||||
|
||||
def test_update_corporation_no_object_w_alliance(self):
|
||||
mock_provider = Mock()
|
||||
mock_provider.get_corporation.return_value = Corporation(members=87)
|
||||
self.my_corp.provider = mock_provider
|
||||
|
||||
self.my_corp.update_corporation()
|
||||
self.assertEqual(self.my_corp.member_count, 87)
|
||||
|
||||
def test_update_corporation_from_object_wo_alliance(self):
|
||||
my_corp2 = EveCorporationInfo(
|
||||
corporation_id=2011,
|
||||
corporation_name='Dummy Corporation 11',
|
||||
corporation_ticker='DC11',
|
||||
member_count=6
|
||||
)
|
||||
updated_corp = Corporation(
|
||||
members=8
|
||||
)
|
||||
my_corp2.update_corporation(updated_corp)
|
||||
self.assertEqual(my_corp2.member_count, 8)
|
||||
self.assertIsNone(my_corp2.alliance)
|
||||
|
||||
def test_image_url(self):
|
||||
self.assertEqual(
|
||||
EveCorporationInfo.generic_logo_url(42),
|
||||
eveimageserver._eve_entity_image_url('corporation', 42)
|
||||
)
|
||||
self.assertEqual(
|
||||
EveCorporationInfo.generic_logo_url(42, 256),
|
||||
eveimageserver._eve_entity_image_url('corporation', 42, 256)
|
||||
)
|
||||
|
||||
def test_logo_url(self):
|
||||
self.assertEqual(
|
||||
self.my_corp.logo_url(),
|
||||
'https://images.evetech.net/corporations/2001/logo?size=32'
|
||||
)
|
||||
self.assertEqual(
|
||||
self.my_corp.logo_url(64),
|
||||
'https://images.evetech.net/corporations/2001/logo?size=64'
|
||||
)
|
||||
self.assertEqual(
|
||||
self.my_corp.logo_url_32,
|
||||
'https://images.evetech.net/corporations/2001/logo?size=32'
|
||||
)
|
||||
self.assertEqual(
|
||||
self.my_corp.logo_url_64,
|
||||
'https://images.evetech.net/corporations/2001/logo?size=64'
|
||||
)
|
||||
self.assertEqual(
|
||||
self.my_corp.logo_url_128,
|
||||
'https://images.evetech.net/corporations/2001/logo?size=128'
|
||||
)
|
||||
self.assertEqual(
|
||||
self.my_corp.logo_url_256,
|
||||
'https://images.evetech.net/corporations/2001/logo?size=256'
|
||||
)
|
||||
|
||||
594
allianceauth/eveonline/tests/test_providers.py
Normal file
594
allianceauth/eveonline/tests/test_providers.py
Normal file
@@ -0,0 +1,594 @@
|
||||
import os
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from bravado.exception import HTTPNotFound
|
||||
from jsonschema.exceptions import RefResolutionError
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from . import set_logger
|
||||
from ..providers import (
|
||||
ObjectNotFound,
|
||||
Entity,
|
||||
Character,
|
||||
Corporation,
|
||||
Alliance,
|
||||
ItemType,
|
||||
EveProvider,
|
||||
EveSwaggerProvider
|
||||
)
|
||||
|
||||
|
||||
MODULE_PATH = 'allianceauth.eveonline.providers'
|
||||
SWAGGER_OLD_SPEC_PATH = os.path.join(os.path.dirname(
|
||||
os.path.abspath(__file__)), 'swagger_old.json'
|
||||
)
|
||||
set_logger(MODULE_PATH, __file__)
|
||||
|
||||
|
||||
class TestObjectNotFound(TestCase):
|
||||
|
||||
def test_str(self):
|
||||
x = ObjectNotFound(1001, 'Character')
|
||||
self.assertEqual(str(x), 'Character with ID 1001 not found.')
|
||||
|
||||
|
||||
class TestEntity(TestCase):
|
||||
|
||||
def test_str(self):
|
||||
x = Entity(1001, 'Bruce Wayne')
|
||||
self.assertEqual(str(x), 'Bruce Wayne')
|
||||
|
||||
# bug - does not return a string
|
||||
"""
|
||||
x = Entity(1001)
|
||||
self.assertEqual(str(x), '')
|
||||
|
||||
x = Entity()
|
||||
self.assertEqual(str(x), '')
|
||||
"""
|
||||
|
||||
def test_repr(self):
|
||||
x = Entity(1001, 'Bruce Wayne')
|
||||
self.assertEqual(repr(x), '<Entity (1001): Bruce Wayne>')
|
||||
|
||||
x = Entity(1001)
|
||||
self.assertEqual(repr(x), '<Entity (1001): None>')
|
||||
|
||||
x = Entity()
|
||||
self.assertEqual(repr(x), '<Entity (None): None>')
|
||||
|
||||
def test_bool(self):
|
||||
x = Entity(1001)
|
||||
self.assertTrue(bool(x))
|
||||
|
||||
x = Entity()
|
||||
self.assertFalse(bool(x))
|
||||
|
||||
def test_eq(self):
|
||||
x1 = Entity(1001)
|
||||
x2 = Entity(1001)
|
||||
y = Entity(1002)
|
||||
z1 = Entity()
|
||||
z2 = Entity()
|
||||
|
||||
self.assertEqual(x1, x2)
|
||||
self.assertNotEqual(x1, y)
|
||||
self.assertNotEqual(x1, z1)
|
||||
self.assertEqual(z1, z2)
|
||||
|
||||
# bug: missing _neq_ in Equity to compliment _eq_
|
||||
|
||||
|
||||
class TestCorporation(TestCase):
|
||||
|
||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
|
||||
def test_alliance_defined(self, mock_provider_get_alliance):
|
||||
my_alliance = Alliance(
|
||||
id=3001,
|
||||
name='Dummy Alliance',
|
||||
ticker='Dummy',
|
||||
corp_ids=[2001, 2002, 2003],
|
||||
executor_corp_id=2001
|
||||
)
|
||||
mock_provider_get_alliance.return_value = my_alliance
|
||||
|
||||
x = Corporation(alliance_id=3001)
|
||||
self.assertEqual(
|
||||
x.alliance,
|
||||
my_alliance
|
||||
)
|
||||
self.assertEqual(
|
||||
x.alliance,
|
||||
my_alliance
|
||||
)
|
||||
# should fetch alliance once only
|
||||
self.assertEqual(mock_provider_get_alliance.call_count, 1)
|
||||
|
||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
|
||||
def test_alliance_not_defined(self, mock_provider_get_alliance):
|
||||
mock_provider_get_alliance.return_value = None
|
||||
|
||||
x = Corporation()
|
||||
self.assertEqual(
|
||||
x.alliance,
|
||||
Entity(None, None)
|
||||
)
|
||||
|
||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_character')
|
||||
def test_ceo(self, mock_provider_get_character):
|
||||
my_ceo = Character(
|
||||
id=1001,
|
||||
name='Bruce Wayne',
|
||||
corp_id=2001,
|
||||
alliance_id=3001
|
||||
)
|
||||
mock_provider_get_character.return_value = my_ceo
|
||||
|
||||
# fetch from provider if not defined
|
||||
x = Corporation()
|
||||
self.assertEqual(
|
||||
x.ceo,
|
||||
my_ceo
|
||||
)
|
||||
|
||||
# return existing if defined
|
||||
mock_provider_get_character.return_value = None
|
||||
self.assertEqual(
|
||||
x.ceo,
|
||||
my_ceo
|
||||
)
|
||||
self.assertEqual(mock_provider_get_character.call_count, 1)
|
||||
|
||||
# bug in ceo(): will try to fetch character even if ceo_id is None
|
||||
|
||||
|
||||
class TestAlliance(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.my_alliance = Alliance(
|
||||
id=3001,
|
||||
name='Dummy Alliance',
|
||||
ticker='Dummy',
|
||||
corp_ids=[2001, 2002, 2003],
|
||||
executor_corp_id=2001
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _get_corp(corp_id):
|
||||
corps = {
|
||||
2001: Corporation(
|
||||
id=2001,
|
||||
name='Dummy Corp 1',
|
||||
alliance_id=3001
|
||||
),
|
||||
2002: Corporation(
|
||||
id=2002,
|
||||
name='Dummy Corp 2',
|
||||
alliance_id=3001
|
||||
),
|
||||
2003: Corporation(
|
||||
id=2003,
|
||||
name='Dummy Corp 3',
|
||||
alliance_id=3001
|
||||
),
|
||||
}
|
||||
|
||||
if corp_id:
|
||||
return corps[int(corp_id)]
|
||||
|
||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_corp')
|
||||
def test_corp(self, mock_provider_get_corp):
|
||||
mock_provider_get_corp.side_effect = TestAlliance._get_corp
|
||||
|
||||
# should fetch corp if not in the object
|
||||
self.assertEqual(
|
||||
self.my_alliance.corp(2001),
|
||||
TestAlliance._get_corp(2001)
|
||||
)
|
||||
# should fetch corp if not in the object
|
||||
self.assertEqual(
|
||||
self.my_alliance.corp(2002),
|
||||
TestAlliance._get_corp(2002)
|
||||
)
|
||||
# should return from the object if its there
|
||||
self.assertEqual(
|
||||
self.my_alliance.corp(2001),
|
||||
TestAlliance._get_corp(2001)
|
||||
)
|
||||
# should return from the object if its there
|
||||
self.assertEqual(
|
||||
self.my_alliance.corp(2002),
|
||||
TestAlliance._get_corp(2002)
|
||||
)
|
||||
# should be called once by used corp only
|
||||
self.assertEqual(mock_provider_get_corp.call_count, 2)
|
||||
|
||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_corp')
|
||||
def test_corps(self, mock_provider_get_corp):
|
||||
mock_provider_get_corp.side_effect = TestAlliance._get_corp
|
||||
|
||||
self.assertEqual(
|
||||
self.my_alliance.corps,
|
||||
[
|
||||
TestAlliance._get_corp(2001),
|
||||
TestAlliance._get_corp(2002),
|
||||
TestAlliance._get_corp(2003),
|
||||
]
|
||||
)
|
||||
|
||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_corp')
|
||||
def test_executor_corp(self, mock_provider_get_corp):
|
||||
mock_provider_get_corp.side_effect = TestAlliance._get_corp
|
||||
|
||||
self.assertEqual(
|
||||
self.my_alliance.executor_corp,
|
||||
TestAlliance._get_corp(2001),
|
||||
)
|
||||
|
||||
x = Alliance()
|
||||
self.assertEqual(
|
||||
x.executor_corp,
|
||||
Entity(None, None),
|
||||
)
|
||||
|
||||
|
||||
class TestCharacter(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.my_character = Character(
|
||||
id=1001,
|
||||
name='Bruce Wayne',
|
||||
corp_id=2001,
|
||||
alliance_id=3001
|
||||
)
|
||||
|
||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_corp')
|
||||
def test_corp(self, mock_provider_get_corp):
|
||||
my_corp = Corporation(
|
||||
id=2001,
|
||||
name='Dummy Corp 1'
|
||||
)
|
||||
mock_provider_get_corp.return_value = my_corp
|
||||
|
||||
self.assertEqual(self.my_character.corp, my_corp)
|
||||
self.assertEqual(self.my_character.corp, my_corp)
|
||||
|
||||
# should call the provider one time only
|
||||
self.assertEqual(mock_provider_get_corp.call_count, 1)
|
||||
|
||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_alliance')
|
||||
@patch(MODULE_PATH + '.EveSwaggerProvider.get_corp')
|
||||
def test_alliance_has_one(
|
||||
self,
|
||||
mock_provider_get_corp,
|
||||
mock_provider_get_alliance,
|
||||
):
|
||||
my_corp = Corporation(
|
||||
id=2001,
|
||||
name='Dummy Corp 1',
|
||||
alliance_id=3001
|
||||
)
|
||||
mock_provider_get_corp.return_value = my_corp
|
||||
my_alliance = Alliance(
|
||||
id=3001,
|
||||
name='Dummy Alliance 1',
|
||||
executor_corp_id=2001,
|
||||
corp_ids=[2001, 2002]
|
||||
)
|
||||
mock_provider_get_alliance.return_value = my_alliance
|
||||
|
||||
self.assertEqual(self.my_character.alliance, my_alliance)
|
||||
self.assertEqual(self.my_character.alliance, my_alliance)
|
||||
|
||||
# should call the provider one time only
|
||||
self.assertEqual(mock_provider_get_corp.call_count, 1)
|
||||
self.assertEqual(mock_provider_get_alliance.call_count, 1)
|
||||
|
||||
def test_alliance_has_none(self):
|
||||
self.my_character.alliance_id = None
|
||||
self.assertEqual(self.my_character.alliance, Entity(None, None))
|
||||
|
||||
|
||||
class TestItemType(TestCase):
|
||||
|
||||
def test_init(self):
|
||||
x = ItemType(id=99, name='Dummy Item')
|
||||
self.assertIsInstance(x, ItemType)
|
||||
|
||||
|
||||
class TestEveProvider(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.my_provider = EveProvider()
|
||||
|
||||
def test_get_alliance(self):
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.my_provider.get_alliance(3001)
|
||||
|
||||
def test_get_corp(self):
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.my_provider.get_corp(2001)
|
||||
|
||||
def test_get_character(self):
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.my_provider.get_character(1001)
|
||||
|
||||
# bug: should be calling NotImplementedError() not NotImplemented
|
||||
"""
|
||||
def test_get_itemtype(self):
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.my_provider.get_itemtype(4001)
|
||||
"""
|
||||
|
||||
|
||||
class TestEveSwaggerProvider(TestCase):
|
||||
|
||||
@staticmethod
|
||||
def esi_get_alliances_alliance_id(alliance_id):
|
||||
alliances = {
|
||||
3001: {
|
||||
'name': 'Dummy Alliance 1',
|
||||
'ticker': 'DA1',
|
||||
'executor_corporation_id': 2001
|
||||
},
|
||||
3002: {
|
||||
'name': 'Dummy Alliance 2',
|
||||
'ticker': 'DA2'
|
||||
}
|
||||
}
|
||||
mock_result = Mock()
|
||||
if alliance_id in alliances:
|
||||
mock_result.result.return_value = alliances[alliance_id]
|
||||
return mock_result
|
||||
else:
|
||||
raise HTTPNotFound(Mock())
|
||||
|
||||
@staticmethod
|
||||
def esi_get_alliances_alliance_id_corporations(alliance_id):
|
||||
alliances = {
|
||||
3001: [2001, 2002, 2003],
|
||||
3002: [2004, 2005]
|
||||
}
|
||||
mock_result = Mock()
|
||||
if alliance_id in alliances:
|
||||
mock_result.result.return_value = alliances[alliance_id]
|
||||
return mock_result
|
||||
else:
|
||||
raise HTTPNotFound(Mock())
|
||||
|
||||
@staticmethod
|
||||
def esi_get_corporations_corporation_id(corporation_id):
|
||||
corporations = {
|
||||
2001: {
|
||||
'name': 'Dummy Corp 1',
|
||||
'ticker': 'DC1',
|
||||
'ceo_id': 1001,
|
||||
'member_count': 42,
|
||||
'alliance_id': 3001
|
||||
},
|
||||
2002: {
|
||||
'name': 'Dummy Corp 2',
|
||||
'ticker': 'DC2',
|
||||
'ceo_id': 1011,
|
||||
'member_count': 5
|
||||
}
|
||||
}
|
||||
mock_result = Mock()
|
||||
if corporation_id in corporations:
|
||||
mock_result.result.return_value = corporations[corporation_id]
|
||||
return mock_result
|
||||
else:
|
||||
raise HTTPNotFound(Mock())
|
||||
|
||||
@staticmethod
|
||||
def esi_get_characters_character_id(character_id):
|
||||
characters = {
|
||||
1001: {
|
||||
'name': 'Bruce Wayne',
|
||||
'corporation_id': 2001,
|
||||
'alliance_id': 3001
|
||||
},
|
||||
1002: {
|
||||
'name': 'Peter Parker',
|
||||
'corporation_id': 2101
|
||||
}
|
||||
}
|
||||
mock_result = Mock()
|
||||
if character_id in characters:
|
||||
mock_result.result.return_value = characters[character_id]
|
||||
return mock_result
|
||||
else:
|
||||
raise HTTPNotFound(Mock())
|
||||
|
||||
@staticmethod
|
||||
def esi_post_characters_affiliation(characters):
|
||||
character_data = {
|
||||
1001: {
|
||||
'corporation_id': 2001,
|
||||
'alliance_id': 3001
|
||||
},
|
||||
1002: {
|
||||
'corporation_id': 2101
|
||||
}
|
||||
}
|
||||
mock_result = Mock()
|
||||
if isinstance(characters, list):
|
||||
characters_result = list()
|
||||
for character_id in characters:
|
||||
if character_id in character_data:
|
||||
characters_result.append(character_data[character_id])
|
||||
else:
|
||||
raise HTTPNotFound(Mock())
|
||||
mock_result.result.return_value = characters_result
|
||||
return mock_result
|
||||
else:
|
||||
raise TypeError()
|
||||
|
||||
@staticmethod
|
||||
def esi_get_universe_types_type_id(type_id):
|
||||
types = {
|
||||
4001: {
|
||||
'name': 'Dummy Type 1'
|
||||
},
|
||||
4002: {
|
||||
'name': 'Dummy Type 2'
|
||||
}
|
||||
}
|
||||
mock_result = Mock()
|
||||
if type_id in types:
|
||||
mock_result.result.return_value = types[type_id]
|
||||
return mock_result
|
||||
else:
|
||||
raise HTTPNotFound(Mock())
|
||||
|
||||
@patch(MODULE_PATH + '.esi_client_factory')
|
||||
def test_str(self, mock_esi_client_factory):
|
||||
my_provider = EveSwaggerProvider()
|
||||
self.assertEqual(str(my_provider), 'esi')
|
||||
|
||||
@patch(MODULE_PATH + '.esi_client_factory')
|
||||
def test_get_alliance(self, mock_esi_client_factory):
|
||||
mock_esi_client_factory.return_value\
|
||||
.Alliance.get_alliances_alliance_id \
|
||||
= TestEveSwaggerProvider.esi_get_alliances_alliance_id
|
||||
mock_esi_client_factory.return_value\
|
||||
.Alliance.get_alliances_alliance_id_corporations \
|
||||
= TestEveSwaggerProvider.esi_get_alliances_alliance_id_corporations
|
||||
|
||||
my_provider = EveSwaggerProvider()
|
||||
|
||||
# fully defined alliance
|
||||
my_alliance = my_provider.get_alliance(3001)
|
||||
self.assertEqual(my_alliance.id, 3001)
|
||||
self.assertEqual(my_alliance.name, 'Dummy Alliance 1')
|
||||
self.assertEqual(my_alliance.ticker, 'DA1')
|
||||
self.assertListEqual(my_alliance.corp_ids, [2001, 2002, 2003])
|
||||
self.assertEqual(my_alliance.executor_corp_id, 2001)
|
||||
|
||||
# alliance missing executor_corporation_id
|
||||
my_alliance = my_provider.get_alliance(3002)
|
||||
self.assertEqual(my_alliance.id, 3002)
|
||||
self.assertEqual(my_alliance.executor_corp_id, None)
|
||||
|
||||
# alliance not found
|
||||
with self.assertRaises(ObjectNotFound):
|
||||
my_provider.get_alliance(3999)
|
||||
|
||||
@patch(MODULE_PATH + '.esi_client_factory')
|
||||
def test_get_corp(self, mock_esi_client_factory):
|
||||
mock_esi_client_factory.return_value\
|
||||
.Corporation.get_corporations_corporation_id \
|
||||
= TestEveSwaggerProvider.esi_get_corporations_corporation_id
|
||||
|
||||
my_provider = EveSwaggerProvider()
|
||||
|
||||
# corporation with alliance
|
||||
my_corp = my_provider.get_corp(2001)
|
||||
self.assertEqual(my_corp.id, 2001)
|
||||
self.assertEqual(my_corp.name, 'Dummy Corp 1')
|
||||
self.assertEqual(my_corp.ticker, 'DC1')
|
||||
self.assertEqual(my_corp.ceo_id, 1001)
|
||||
self.assertEqual(my_corp.members, 42)
|
||||
self.assertEqual(my_corp.alliance_id, 3001)
|
||||
|
||||
# corporation wo/ alliance
|
||||
my_corp = my_provider.get_corp(2002)
|
||||
self.assertEqual(my_corp.id, 2002)
|
||||
self.assertEqual(my_corp.alliance_id, None)
|
||||
|
||||
# corporation not found
|
||||
with self.assertRaises(ObjectNotFound):
|
||||
my_provider.get_corp(2999)
|
||||
|
||||
@patch(MODULE_PATH + '.esi_client_factory')
|
||||
def test_get_character(self, mock_esi_client_factory):
|
||||
mock_esi_client_factory.return_value\
|
||||
.Character.get_characters_character_id \
|
||||
= TestEveSwaggerProvider.esi_get_characters_character_id
|
||||
mock_esi_client_factory.return_value\
|
||||
.Character.post_characters_affiliation \
|
||||
= TestEveSwaggerProvider.esi_post_characters_affiliation
|
||||
|
||||
my_provider = EveSwaggerProvider()
|
||||
|
||||
# character with alliance
|
||||
my_character = my_provider.get_character(1001)
|
||||
self.assertEqual(my_character.id, 1001)
|
||||
self.assertEqual(my_character.name, 'Bruce Wayne')
|
||||
self.assertEqual(my_character.corp_id, 2001)
|
||||
self.assertEqual(my_character.alliance_id, 3001)
|
||||
|
||||
# character wo/ alliance
|
||||
my_character = my_provider.get_character(1002)
|
||||
self.assertEqual(my_character.id, 1002)
|
||||
self.assertEqual(my_character.alliance_id, None)
|
||||
|
||||
# character not found
|
||||
with self.assertRaises(ObjectNotFound):
|
||||
my_provider.get_character(1999)
|
||||
|
||||
@patch(MODULE_PATH + '.esi_client_factory')
|
||||
def test_get_itemtype(self, mock_esi_client_factory):
|
||||
mock_esi_client_factory.return_value\
|
||||
.Universe.get_universe_types_type_id \
|
||||
= TestEveSwaggerProvider.esi_get_universe_types_type_id
|
||||
|
||||
my_provider = EveSwaggerProvider()
|
||||
|
||||
# type exists
|
||||
my_type = my_provider.get_itemtype(4001)
|
||||
self.assertEqual(my_type.id, 4001)
|
||||
self.assertEqual(my_type.name, 'Dummy Type 1')
|
||||
|
||||
# type not found
|
||||
with self.assertRaises(ObjectNotFound):
|
||||
my_provider.get_itemtype(4999)
|
||||
|
||||
@patch(MODULE_PATH + '.settings.DEBUG', False)
|
||||
@patch(MODULE_PATH + '.esi_client_factory')
|
||||
def test_create_client_on_normal_startup(self, mock_esi_client_factory):
|
||||
my_provider = EveSwaggerProvider()
|
||||
self.assertTrue(mock_esi_client_factory.called)
|
||||
self.assertIsNotNone(my_provider._client)
|
||||
|
||||
@patch(MODULE_PATH + '.SWAGGER_SPEC_PATH', SWAGGER_OLD_SPEC_PATH)
|
||||
@patch(MODULE_PATH + '.settings.DEBUG', False)
|
||||
@patch('socket.socket')
|
||||
def test_create_client_on_normal_startup_w_old_swagger_spec(
|
||||
self, mock_socket
|
||||
):
|
||||
mock_socket.side_effect = Exception('Network blocked for testing')
|
||||
my_provider = EveSwaggerProvider()
|
||||
self.assertIsNone(my_provider._client)
|
||||
|
||||
@patch(MODULE_PATH + '.settings.DEBUG', True)
|
||||
@patch(MODULE_PATH + '.esi_client_factory')
|
||||
def test_dont_create_client_on_debug_startup(self, mock_esi_client_factory):
|
||||
my_provider = EveSwaggerProvider()
|
||||
self.assertFalse(mock_esi_client_factory.called)
|
||||
self.assertIsNone(my_provider._client)
|
||||
|
||||
@patch(MODULE_PATH + '.settings.DEBUG', False)
|
||||
@patch(MODULE_PATH + '.esi_client_factory')
|
||||
def test_dont_create_client_if_client_creation_fails_on_normal_startup(
|
||||
self, mock_esi_client_factory
|
||||
):
|
||||
mock_esi_client_factory.side_effect = RefResolutionError(cause='Test')
|
||||
my_provider = EveSwaggerProvider()
|
||||
self.assertTrue(mock_esi_client_factory.called)
|
||||
self.assertIsNone(my_provider._client)
|
||||
|
||||
@patch(MODULE_PATH + '.settings.DEBUG', True)
|
||||
@patch(MODULE_PATH + '.esi_client_factory')
|
||||
def test_client_loads_on_demand(
|
||||
self, mock_esi_client_factory
|
||||
):
|
||||
mock_esi_client_factory.return_value = 'my_client'
|
||||
my_provider = EveSwaggerProvider()
|
||||
self.assertFalse(mock_esi_client_factory.called)
|
||||
self.assertIsNone(my_provider._client)
|
||||
my_client = my_provider.client
|
||||
self.assertTrue(mock_esi_client_factory.called)
|
||||
self.assertIsNotNone(my_provider._client)
|
||||
self.assertEqual(my_client, 'my_client')
|
||||
98
allianceauth/eveonline/tests/test_tasks.py
Normal file
98
allianceauth/eveonline/tests/test_tasks.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
from ..tasks import (
|
||||
update_alliance,
|
||||
update_corp,
|
||||
update_character,
|
||||
run_model_update
|
||||
)
|
||||
|
||||
|
||||
class TestTasks(TestCase):
|
||||
|
||||
@patch('allianceauth.eveonline.tasks.EveCorporationInfo')
|
||||
def test_update_corp(self, mock_EveCorporationInfo):
|
||||
update_corp(42)
|
||||
self.assertEqual(
|
||||
mock_EveCorporationInfo.objects.update_corporation.call_count, 1
|
||||
)
|
||||
self.assertEqual(
|
||||
mock_EveCorporationInfo.objects.update_corporation.call_args[0][0], 42
|
||||
)
|
||||
|
||||
@patch('allianceauth.eveonline.tasks.EveAllianceInfo')
|
||||
def test_update_alliance(self, mock_EveAllianceInfo):
|
||||
update_alliance(42)
|
||||
self.assertEqual(
|
||||
mock_EveAllianceInfo.objects.update_alliance.call_args[0][0], 42
|
||||
)
|
||||
self.assertEqual(
|
||||
mock_EveAllianceInfo.objects
|
||||
.update_alliance.return_value.populate_alliance.call_count, 1
|
||||
)
|
||||
|
||||
@patch('allianceauth.eveonline.tasks.EveCharacter')
|
||||
def test_update_character(self, mock_EveCharacter):
|
||||
update_character(42)
|
||||
self.assertEqual(
|
||||
mock_EveCharacter.objects.update_character.call_count, 1
|
||||
)
|
||||
self.assertEqual(
|
||||
mock_EveCharacter.objects.update_character.call_args[0][0], 42
|
||||
)
|
||||
|
||||
@patch('allianceauth.eveonline.tasks.update_character')
|
||||
@patch('allianceauth.eveonline.tasks.update_alliance')
|
||||
@patch('allianceauth.eveonline.tasks.update_corp')
|
||||
def test_run_model_update(
|
||||
self,
|
||||
mock_update_corp,
|
||||
mock_update_alliance,
|
||||
mock_update_character,
|
||||
):
|
||||
EveCorporationInfo.objects.all().delete()
|
||||
EveAllianceInfo.objects.all().delete()
|
||||
EveCharacter.objects.all().delete()
|
||||
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id='2345',
|
||||
corporation_name='corp.name',
|
||||
corporation_ticker='corp.ticker',
|
||||
member_count=10,
|
||||
alliance=None,
|
||||
)
|
||||
EveAllianceInfo.objects.create(
|
||||
alliance_id='3456',
|
||||
alliance_name='alliance.name',
|
||||
alliance_ticker='alliance.ticker',
|
||||
executor_corp_id='alliance.executor_corp_id',
|
||||
)
|
||||
EveCharacter.objects.create(
|
||||
character_id='1234',
|
||||
character_name='character.name',
|
||||
corporation_id='character.corp.id',
|
||||
corporation_name='character.corp.name',
|
||||
corporation_ticker='c.c.t', # max 5 chars
|
||||
alliance_id='character.alliance.id',
|
||||
alliance_name='character.alliance.name',
|
||||
)
|
||||
|
||||
run_model_update()
|
||||
|
||||
self.assertEqual(mock_update_corp.apply_async.call_count, 1)
|
||||
self.assertEqual(
|
||||
int(mock_update_corp.apply_async.call_args[1]['args'][0]), 2345
|
||||
)
|
||||
|
||||
self.assertEqual(mock_update_alliance.apply_async.call_count, 1)
|
||||
self.assertEqual(
|
||||
int(mock_update_alliance.apply_async.call_args[1]['args'][0]), 3456
|
||||
)
|
||||
|
||||
self.assertEqual(mock_update_character.apply_async.call_count, 1)
|
||||
self.assertEqual(
|
||||
int(mock_update_character.apply_async.call_args[1]['args'][0]), 1234
|
||||
)
|
||||
File diff suppressed because one or more lines are too long
@@ -12,7 +12,7 @@
|
||||
<div class="panel-heading">{{ character_name }}</div>
|
||||
<div class="panel-body">
|
||||
<div class="col-lg-2 col-sm-2">
|
||||
<img class="ra-avatar img-responsive" src="https://image.eveonline.com/Character/{{ character_id }}_128.jpg">
|
||||
<img class="ra-avatar img-responsive" src="{{ character_portrait_url }}">
|
||||
</div>
|
||||
<div class="col-lg-10 col-sm-2">
|
||||
<div class="alert alert-danger" role="alert">{% trans "Character not registered!" %}</div>
|
||||
|
||||
@@ -15,7 +15,13 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
</h1>
|
||||
<h2>{% blocktrans %}{{ user }} has collected {{ n_fats }} link{{ n_fats|pluralize }} this month.{% endblocktrans %}</h2>
|
||||
<h2>
|
||||
{% blocktrans count links=n_fats trimmed %}
|
||||
{{ user }} has collected one link this month.
|
||||
{% plural %}
|
||||
{{ user }} has collected {{ links }} links this month.
|
||||
{% endblocktrans %}
|
||||
</h2>
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
<th class="col-md-2 text-center">{% trans "Ship" %}</th>
|
||||
@@ -29,7 +35,13 @@
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% if created_fats %}
|
||||
<h2>{% blocktrans %}{{ user }} has created {{ n_created_fats }} link{{ n_created_fats|pluralize }} this month.{% endblocktrans %}</h2>
|
||||
<h2>
|
||||
{% blocktrans count links=n_created_fats trimmed %}
|
||||
{{ user }} has created one link this month.
|
||||
{% plural %}
|
||||
{{ user }} has created {{ links }} links this month.
|
||||
{% endblocktrans %}
|
||||
</h2>
|
||||
{% if created_fats %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
{% for memberStat in fatStats %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="https://image.eveonline.com/Character/{{ memberStat.mainchid }}_32.jpg" class="ra-avatar img-responsive">
|
||||
<img src="{{ memberStat.mainchar.portrait_url_32 }}" class="ra-avatar img-responsive">
|
||||
</td>
|
||||
<td class="text-center">{{ memberStat.mainchar.character_name }}</td>
|
||||
<td class="text-center">{{ memberStat.n_chars }}</td>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
{% for corpStat in fatStats %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="https://image.eveonline.com/Corporation/{{ corpStat.corp.corporation_id }}_32.png" class="ra-avatar img-responsive">
|
||||
<img src="{{ corpStat.corp.logo_url_32 }}" class="ra-avatar img-responsive">
|
||||
</td>
|
||||
<td class="text-center"><a href="{% url 'fatlink:statistics_corp' corpStat.corp.corporation_id %}">[{{ corpStat.corp.corporation_ticker }}]</td>
|
||||
<td class="text-center">{{ corpStat.corp.corporation_name }}</td>
|
||||
|
||||
@@ -287,8 +287,13 @@ def click_fatlink_view(request, token, fat_hash=None):
|
||||
err_messages.append(message[0])
|
||||
messages.error(request, ' '.join(err_messages))
|
||||
else:
|
||||
context = {'character_id': token.character_id,
|
||||
'character_name': token.character_name}
|
||||
context = {
|
||||
'character_id': token.character_id,
|
||||
'character_name': token.character_name,
|
||||
'character_portrait_url': EveCharacter.generic_portrait_url(
|
||||
token.character_id, 128
|
||||
),
|
||||
}
|
||||
return render(request, 'fleetactivitytracking/characternotexisting.html', context=context)
|
||||
else:
|
||||
messages.error(request, _('FAT link has expired.'))
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
default_app_config = 'allianceauth.fleetup.apps.FleetupConfig'
|
||||
@@ -1,6 +0,0 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FleetupConfig(AppConfig):
|
||||
name = 'allianceauth.fleetup'
|
||||
label = 'fleetup'
|
||||
@@ -1,27 +0,0 @@
|
||||
from allianceauth.services.hooks import MenuItemHook, UrlHook
|
||||
|
||||
from allianceauth import hooks
|
||||
from . import urls
|
||||
|
||||
|
||||
class FleetUpMenu(MenuItemHook):
|
||||
def __init__(self):
|
||||
MenuItemHook.__init__(self, 'Fleet-Up',
|
||||
'fa fa-arrow-up fa-fw',
|
||||
'fleetup:view',
|
||||
navactive=['fleetup:'])
|
||||
|
||||
def render(self, request):
|
||||
if request.user.has_perm('auth.view_fleetup'):
|
||||
return MenuItemHook.render(self, request)
|
||||
return ''
|
||||
|
||||
|
||||
@hooks.register('menu_item_hook')
|
||||
def register_menu():
|
||||
return FleetUpMenu()
|
||||
|
||||
|
||||
@hooks.register('url_hook')
|
||||
def register_url():
|
||||
return UrlHook(urls, 'fleetup', r'^fleetup/')
|
||||
@@ -1,189 +0,0 @@
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.utils import timezone
|
||||
from datetime import datetime
|
||||
|
||||
import logging
|
||||
import requests
|
||||
import hashlib
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FleetUpManager:
|
||||
APP_KEY = settings.FLEETUP_APP_KEY
|
||||
USER_ID = settings.FLEETUP_USER_ID
|
||||
API_ID = settings.FLEETUP_API_ID
|
||||
GROUP_ID = settings.FLEETUP_GROUP_ID
|
||||
BASE_URL = "http://api.fleet-up.com/Api.svc/{}/{}/{}".format(APP_KEY, USER_ID, API_ID)
|
||||
|
||||
TZ = timezone.utc
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _request_cache_key(cls, url):
|
||||
h = hashlib.sha1()
|
||||
h.update(url.encode('utf-8'))
|
||||
return 'FLEETUP_ENDPOINT_' + h.hexdigest()
|
||||
|
||||
@classmethod
|
||||
def _cache_until_seconds(cls, cache_until_json):
|
||||
# Format comes in like "/Date(1493896236163)/"
|
||||
try:
|
||||
epoch_ms = int(cache_until_json[6:-2])
|
||||
cache_delta = datetime.fromtimestamp(epoch_ms/1000) - datetime.now()
|
||||
cache_delta_seconds = cache_delta.total_seconds()
|
||||
if cache_delta_seconds < 0:
|
||||
return 0
|
||||
elif cache_delta_seconds > 3600:
|
||||
return 3600
|
||||
else:
|
||||
return cache_delta_seconds
|
||||
except TypeError:
|
||||
logger.debug("Couldn't convert CachedUntil time, defaulting to 600 seconds")
|
||||
return 600
|
||||
|
||||
@classmethod
|
||||
def get_endpoint(cls, url):
|
||||
try:
|
||||
cache_key = cls._request_cache_key(url)
|
||||
cached = cache.get(cache_key)
|
||||
if cached:
|
||||
return cached
|
||||
|
||||
r = requests.get(url)
|
||||
r.raise_for_status()
|
||||
|
||||
json = r.json()
|
||||
|
||||
if json['Success']:
|
||||
cache.set(cache_key, json, cls._cache_until_seconds(json['CachedUntilUTC']))
|
||||
return json
|
||||
except requests.exceptions.ConnectionError:
|
||||
logger.warning("Can't connect to Fleet-Up API, is it offline?!")
|
||||
except requests.HTTPError:
|
||||
logger.exception("Error accessing Fleetup API")
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_members(cls):
|
||||
url = "{}/GroupCharacters/{}".format(cls.BASE_URL, cls.GROUP_ID)
|
||||
try:
|
||||
fmembers = cls.get_endpoint(url)
|
||||
if not fmembers:
|
||||
return None
|
||||
return {row["UserId"]: {"user_id": row["UserId"],
|
||||
"char_name": row["EveCharName"],
|
||||
"char_id": row["EveCharId"],
|
||||
"corporation": row["Corporation"]} for row in fmembers["Data"]}
|
||||
except (ValueError, UnicodeDecodeError, TypeError):
|
||||
logger.debug("No fleetup members retrieved.")
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_operations(cls):
|
||||
url = "{}/Operations/{}".format(cls.BASE_URL, cls.GROUP_ID)
|
||||
foperations = cls.get_endpoint(url)
|
||||
if foperations is None:
|
||||
return None
|
||||
return {row["StartString"]: {"subject": row["Subject"],
|
||||
"start": timezone.make_aware(
|
||||
datetime.strptime(row["StartString"], "%Y-%m-%d %H:%M:%S"), cls.TZ),
|
||||
"end": timezone.make_aware(
|
||||
datetime.strptime(row["EndString"], "%Y-%m-%d %H:%M:%S"), cls.TZ),
|
||||
"operation_id": row["OperationId"],
|
||||
"location": row["Location"],
|
||||
"location_info": row["LocationInfo"],
|
||||
"details": row["Details"],
|
||||
"url": row["Url"],
|
||||
"doctrine": row["Doctrines"],
|
||||
"organizer": row["Organizer"]} for row in foperations["Data"]}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_timers(cls):
|
||||
url = "{}/Timers/{}".format(cls.BASE_URL, cls.GROUP_ID)
|
||||
ftimers = cls.get_endpoint(url)
|
||||
if not ftimers:
|
||||
return None
|
||||
return {row["ExpiresString"]: {"solarsystem": row["SolarSystem"],
|
||||
"planet": row["Planet"],
|
||||
"moon": row["Moon"],
|
||||
"owner": row["Owner"],
|
||||
"type": row["Type"],
|
||||
"timer_type": row["TimerType"],
|
||||
"expires": timezone.make_aware(
|
||||
datetime.strptime(row["ExpiresString"], "%Y-%m-%d %H:%M:%S"), cls.TZ),
|
||||
"notes": row["Notes"]} for row in ftimers["Data"]}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_doctrines(cls):
|
||||
url = "{}/Doctrines/{}".format(cls.BASE_URL, cls.GROUP_ID)
|
||||
fdoctrines = cls.get_endpoint(url)
|
||||
if not fdoctrines:
|
||||
return None
|
||||
return {"fleetup_doctrines": fdoctrines["Data"]}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_doctrine(cls, doctrinenumber):
|
||||
url = "{}/DoctrineFittings/{}".format(cls.BASE_URL, doctrinenumber)
|
||||
fdoctrine = cls.get_endpoint(url)
|
||||
if not fdoctrine:
|
||||
return None
|
||||
return {"fitting_doctrine": fdoctrine}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_fittings(cls):
|
||||
url = "{}/Fittings/{}".format(cls.BASE_URL, cls.GROUP_ID)
|
||||
ffittings = cls.get_endpoint(url)
|
||||
if not ffittings:
|
||||
return None
|
||||
return {row["FittingId"]: {"fitting_id": row["FittingId"],
|
||||
"name": row["Name"],
|
||||
"icon_id": row["EveTypeId"],
|
||||
"hull": row["HullType"],
|
||||
"shiptype": row["ShipType"],
|
||||
"estimated": row["EstPrice"],
|
||||
"faction": row["Faction"],
|
||||
"categories": row["Categories"],
|
||||
"last_update":
|
||||
timezone.make_aware(
|
||||
datetime.strptime(row["LastUpdatedString"], "%Y-%m-%d %H:%M:%S"), cls.TZ)}
|
||||
for row in ffittings["Data"]}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_fitting(cls, fittingnumber):
|
||||
url = "{}/Fitting/{}".format(cls.BASE_URL, fittingnumber)
|
||||
try:
|
||||
ffitting = cls.get_endpoint(url)
|
||||
if not ffitting:
|
||||
return None
|
||||
return {"fitting_data": ffitting["Data"]}
|
||||
except KeyError:
|
||||
logger.warning("Failed to retrieve fleetup fitting number %s" % fittingnumber)
|
||||
return {"fitting_data": {}}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_doctrineid(cls, fittingnumber):
|
||||
url = "{}/Fitting/{}".format(cls.BASE_URL, fittingnumber)
|
||||
try:
|
||||
fdoctrineid = cls.get_endpoint(url)
|
||||
if not fdoctrineid:
|
||||
return None
|
||||
return fdoctrineid['Data']['Doctrines'][0]['DoctrineId']
|
||||
except (KeyError, IndexError):
|
||||
logger.debug("Fleetup fitting number %s not in a doctrine." % fittingnumber)
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_fitting_eft(cls, fittingnumber):
|
||||
url = "{}/Fitting/{}/eft".format(cls.BASE_URL, fittingnumber)
|
||||
try:
|
||||
ffittingeft = cls.get_endpoint(url)
|
||||
if not ffittingeft:
|
||||
return None
|
||||
return {"fitting_eft": ffittingeft["Data"]["FittingData"]}
|
||||
except KeyError:
|
||||
logger.warning("Fleetup fitting eft not found for fitting number %s" % fittingnumber)
|
||||
return {"fitting_eft": {}}
|
||||
@@ -1,48 +0,0 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}Characters - FleetUp{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
{% if perms.auth.corp_stats %}
|
||||
{% include "fleetup/menu.html" %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans "Characters registered on Fleet-Up.com" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="col-lg-6">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<tr>
|
||||
<th class="col-md-1"></th>
|
||||
<th class="col-md-1">{% trans "Character" %}</th>
|
||||
<th class="col-md-1">{% trans "Corporation" %}</th>
|
||||
<th class="col-md-1">Fleet-Up(id)</th>
|
||||
</tr>
|
||||
{% for char_name, user_id in member_list %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="https://imageserver.eveonline.com/Character/{{ user_id.char_id }}_32.jpg" class="img-circle">
|
||||
</td>
|
||||
<td>
|
||||
<p>{{ user_id.char_name }}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p>{{ user_id.corporation }}</p>
|
||||
</td>
|
||||
<td>
|
||||
<p>{{ user_id.user_id }}</p>
|
||||
</td>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
@@ -1,67 +0,0 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}Doctrine - FleetUp{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
{% include "fleetup/menu.html" %}
|
||||
<div>
|
||||
{% for a, j in doctrine.items %}
|
||||
{% regroup j.Data|dictsort:"Role" by Role as role_list %}
|
||||
|
||||
{% for Role in role_list %}
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><b>{{ Role.grouper }}</b></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<tr>
|
||||
<th class="col-md-1"></th>
|
||||
<th class="col-md-1">{% trans "Name" %}</th>
|
||||
<th class="col-md-1">{% trans "Role" %}</th>
|
||||
<th class="col-md-1">{% trans "Hull type" %}</th>
|
||||
<th class="col-md-1">{% trans "Ship type" %}</th>
|
||||
<th class="col-md-1">{% trans "Estimated ISK" %}</th>
|
||||
<th class="col-md-2">{% trans "Categories" %}</th>
|
||||
</tr>
|
||||
{% for item in Role.list %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'fleetup:fitting' item.FittingId %}"><img src="https://image.eveonline.com/InventoryType/{{ item.EveTypeId }}_32.png"></a>
|
||||
</td>
|
||||
<td>
|
||||
{{ item.Name }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.Role }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.HullType }}
|
||||
</td>
|
||||
<td>
|
||||
{{ item.ShipType }}
|
||||
</td>
|
||||
<td>
|
||||
{% load humanize %}{{ item.EstPrice|intword }}
|
||||
</td>
|
||||
<td>
|
||||
{% for categories in item.Categories %}
|
||||
{{ categories }},
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
@@ -1,62 +0,0 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}Doctrines - FleetUp{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
{% include "fleetup/menu.html" %}
|
||||
<div>
|
||||
{% if doctrines_list %}
|
||||
{% for a, j in doctrines_list.items %}
|
||||
{% regroup j|dictsort:"FolderName" by FolderName as folder_list %}
|
||||
{% for FolderName in folder_list %}
|
||||
<div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><b>{{ FolderName.grouper }}</b></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<tr>
|
||||
<th class="col-lg-1"></th>
|
||||
<th class="col-lg-4">{% trans "Name" %}</th>
|
||||
<th class="col-lg-3">{% trans "Doctrine" %}</th>
|
||||
<th class="col-lg-4">{% trans "Last updated" %}</th>
|
||||
<!--<th class="col-lg-1">Owner</th>
|
||||
<th class="col-lg-2">Note</th>-->
|
||||
</tr>
|
||||
{% for item in FolderName.list %}
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'fleetup:doctrine' item.DoctrineId %}"><img src="https://image.eveonline.com/InventoryType/{{ item.IconId }}_32.png"></a>
|
||||
</td>
|
||||
<td>
|
||||
{{ item.Name }}
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'fleetup:doctrine' item.DoctrineId %}" class="btn btn-info btn-sm">{{ item.FolderName }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ item.LastUpdatedString }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
<h3>{% trans "There seems to be no Doctrines in here at the moment!" %}</h3>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
@@ -1,131 +0,0 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}{% trans "Doctrine - FleetUp" %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
{% include "fleetup/menu.html" %}
|
||||
<div class="tab-content row">
|
||||
<div id="fit" class="tab-pane fade in active">
|
||||
<div class="col-lg-4">
|
||||
{% for x, y in fitting_data.items %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans "This fit is part of a doctrine" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% for doctrin in y.Doctrines %}
|
||||
<div class="clearfix">
|
||||
<h4>{{ doctrin.Name }}</h4>
|
||||
<div class="col-lg-12">
|
||||
<p>{% trans "Role in doctrine:" %} {{ doctrin.Role }}</p>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<p>{% trans "Priority:" %}</p>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-striped" role="progressbar" aria-valuenow="{{ doctrin.Priority }}" aria-valuemin="0" aria-valuemax="5" style="width: {% widthratio doctrin.Priority 5 100 %}%;">
|
||||
{{ doctrin.Priority }}/5
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<a class="btn btn-primary" href="{% url 'fleetup:doctrine' doctrin.DoctrineId %}">{% trans "See doctrine" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans "Fit categories" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% for category in y.Categories %}
|
||||
<span class="label label-success">{{ category }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans "All fits in this Doctrine" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="list-group">
|
||||
{% for arbit, orbit in doctrines_list.items %}
|
||||
|
||||
{% for fitting in orbit.Data %}
|
||||
<a href="{% url 'fleetup:fitting' fitting.FittingId %}" class="list-group-item">
|
||||
|
||||
<h4 class="list-group-item-heading">{{ fitting.Name }}<span class="pull-right"><img src="https://image.eveonline.com/InventoryType/{{ fitting.EveTypeId }}_32.png" class="img-circle"></span></h4>
|
||||
<p class="list-group-item-heading">{{ fitting.Role }} - {{ fitting.ShipType }}</p>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
{% for a, j in fitting_data.items %}
|
||||
<h3 class="panel-title">{{ j.Name }}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="col-lg-3">
|
||||
<img src="https://image.eveonline.com/InventoryType/{{ j.EveTypeId }}_64.png" class="img-responsive">
|
||||
</div>
|
||||
<div class="col-lg-9">
|
||||
<p>{% trans "Hull:" %} <b>{{ j.HullType }}</b></p>
|
||||
<p>{% trans "Ship:" %} <b>{{ j.ShipType }}</b></p>
|
||||
{% load humanize %}
|
||||
<p>{% trans "Estimated price:" %} <b>{{ j.EstPrice|intword }} ISK</b></p>
|
||||
</div>
|
||||
{% regroup j.FittingData by Slot as fitting_list %}
|
||||
<table class="table table-condensed table-hover">
|
||||
<tr>
|
||||
<th class="col-lg-1"></th>
|
||||
<th class="col-lg-11"></th>
|
||||
</tr>
|
||||
{% for Slot in fitting_list %}
|
||||
<tr class="info">
|
||||
<td></td><td><b>{{ Slot.grouper }}</b></td>
|
||||
</tr>
|
||||
{% for item in Slot.list %}
|
||||
<tr>
|
||||
<td><img src="https://image.eveonline.com/InventoryType/{{ item.TypeId }}_32.png" class="img-responsive"></td>
|
||||
<td> {{ item.Quantity }}x {{ item.TypeName }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans "EFT/Export" %}</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% for data in fitting_eft.items %}
|
||||
{% autoescape off %}
|
||||
<textarea class="form-control" rows="25" spellcheck="false" onclick="this.focus();this.select()" readonly>{{ fitting_eft.fitting_eft }}</textarea>
|
||||
{% endautoescape %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
@@ -1,55 +0,0 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}Fittings - FleetUp{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
{% include "fleetup/menu.html" %}
|
||||
<div class="panel">
|
||||
{% if fitting_list %}
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<tr>
|
||||
<th class="col-md-1"></th>
|
||||
<th class="col-md-1">{% trans "Name" %}</th>
|
||||
<th class="col-md-1">{% trans "Hull" %}</th>
|
||||
<th class="col-md-1">{% trans "Ship type" %}</th>
|
||||
<th class="col-md-1">{% trans "Estimated ISK" %}</th>
|
||||
<th class="col-md-2">{% trans "Categories" %}</th>
|
||||
</tr>
|
||||
{% for id, fittings in fitting_list %}
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'fleetup:fitting' fittings.fitting_id %}"><img src="https://image.eveonline.com/InventoryType/{{ fittings.icon_id }}_32.png"></a>
|
||||
</td>
|
||||
<td>
|
||||
{{ fittings.name }}
|
||||
</td>
|
||||
<td>
|
||||
{{ fittings.hull }}
|
||||
</td>
|
||||
<td>
|
||||
{{ fittings.shiptype }}
|
||||
</td>
|
||||
<td>
|
||||
{% load humanize %}{{ fittings.estimated|intword }}
|
||||
</td>
|
||||
<td>
|
||||
{% for categories in fittings.categories %}
|
||||
{{ categories }},
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<h3>{% trans "There seems to be no Fittings in here at the moment!" %}</h3>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
@@ -1,254 +0,0 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block page_title %}FleetUp{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
{% include "fleetup/menu.html" %}
|
||||
<div>
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a data-toggle="tab" href="#operations">{% trans "Operations" %}</a></li>
|
||||
<li><a data-toggle="tab" href="#timers">{% trans "Timers" %}</a></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content row">
|
||||
<div id="operations" class="tab-pane fade in active">
|
||||
<div class="col-lg-8">
|
||||
{% if operations_list %}
|
||||
{% for subject, start in operations_list %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><b>{{ start.subject }}</b></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-condensed">
|
||||
<tr>
|
||||
<th class="col-md-6">{% trans "Start" %}</th>
|
||||
<th class="col-md-6">{% trans "End" %}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-md-6">{{ start.start|date:"l d M H:i" }} <span class="label label-success">{% trans "Eve Time" %}</span></td>
|
||||
|
||||
<td class="col-md-6">{{ start.end|date:"l d M H:i" }} <span class="label label-success">{% trans "Eve Time" %}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="col-md-6">
|
||||
<span id="localtime{{ start.operation_id }}"></span> <span class='label label-success'>Local time</span><br>
|
||||
<div id="countdown{{ start.operation_id }}"></div>
|
||||
</td>
|
||||
|
||||
<td class="col-md-6"></td>
|
||||
</tr>
|
||||
</table>
|
||||
{{ start.details|linebreaks }}
|
||||
|
||||
<table class="table table-condensed table-striped">
|
||||
<tr>
|
||||
<th class="col-md-4">{% trans "Location" %}</th>
|
||||
<th class="col-md-4">{% trans "Doctrine" %}</th>
|
||||
<th class="col-md-2">{% trans "Organizer" %}</th>
|
||||
<th class="col-md-2">{% trans "URL" %}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
{{ start.location }} - {{ start.location_info }} <a href="http://evemaps.dotlan.net/system/{{ start.location }}" target="_blank" class="label label-success">Dotlan</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if start.doctrine %}
|
||||
{% for doctrine in start.doctrine %}
|
||||
|
||||
<a href="{% url 'fleetup:doctrine' doctrine.Id %}" class="label label-success">{{ doctrine.Name }}</a>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
<span class="label label-danger">{% trans "TBA" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ start.organizer }}
|
||||
</td>
|
||||
<td>
|
||||
{% ifequal start.url "" %}
|
||||
<div class="label label-danger">{% trans "No link" %}</div>
|
||||
{% else %}
|
||||
<a href="{{ start.url }}" target="_blank" class="label label-success">{% trans "External link" %}</a>
|
||||
{% endifequal %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<h3>{% trans "There seems to be no Operations in the near future." %}</h3>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-lg-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">{% trans "Current Eve Time:" %}</h2>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div id="current-time"></div>
|
||||
</div>
|
||||
</div>
|
||||
{% if timers_list %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">{% trans "Timers" %}</h2>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
{% for notes, type in timers_list %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ type.solarsystem }}
|
||||
</td>
|
||||
<td>
|
||||
{{ type.expires|date:"l d M H:i" }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div id="timers" class="tab-pane fade in">
|
||||
<div class="col-lg-12">
|
||||
{% if timers_list %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">{% trans "Timers" %}</h2>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="col-lg-12">
|
||||
<table class="table table-condensed table-hover table-striped">
|
||||
<tr>
|
||||
<th class="col-lg-1">{% trans "Type" %}</th>
|
||||
<th class="col-lg-1">{% trans "Structure" %}</th>
|
||||
<th class="col-lg-2">{% trans "Location" %}</th>
|
||||
<th class="col-lg-2">{% trans "Expires(EVE-time)" %}</th>
|
||||
<th class="col-lg-1">{% trans "Owner" %}</th>
|
||||
<th class="col-lg-2">{% trans "Note" %}</th>
|
||||
</tr>
|
||||
{% for notes, type in timers_list %}
|
||||
<tr>
|
||||
<td>
|
||||
{% ifequal type.type "Final" %}
|
||||
<span class="label label-danger">
|
||||
{{ type.type }}</span>{% else %}{{ type.type }}{% endifequal %}
|
||||
</td>
|
||||
<td>
|
||||
{{ type.timer_type }}
|
||||
</td>
|
||||
<td>
|
||||
{{ type.solarsystem }} - Planet:{{ type.planet }} Moon:{{ type.moon }}
|
||||
</td>
|
||||
<td>
|
||||
{{ type.expires|date:"l d M H:i" }}
|
||||
</td>
|
||||
<td>
|
||||
{{ type.owner }}
|
||||
</td>
|
||||
<td>
|
||||
{{ type.notes }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<h3>{% trans "There seems to be no Timers in the near future." %}</h3>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% include 'bundles/moment-js.html' with locale=True %}
|
||||
<script src="{% static 'js/timers.js' %}"></script>
|
||||
<script type="text/javascript">
|
||||
// Data
|
||||
var timers = [
|
||||
{% for start, op in operations_list %}
|
||||
{
|
||||
'id': {{ op.operation_id }},
|
||||
'start': moment("{{ op.start | date:"c" }}"),
|
||||
'end': moment("{{ op.end | date:"c" }}"),
|
||||
'expired': false
|
||||
},
|
||||
{% endfor %}
|
||||
]
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
|
||||
timedUpdate();
|
||||
setAllLocalTimes();
|
||||
|
||||
// Start timed updates
|
||||
setInterval(timedUpdate, 1000);
|
||||
|
||||
function timedUpdate() {
|
||||
updateClock();
|
||||
updateAllTimers();
|
||||
}
|
||||
|
||||
function updateAllTimers () {
|
||||
var l = timers.length;
|
||||
for (var i=0; i < l; ++i) {
|
||||
if (timers[i].expired) continue;
|
||||
updateTimer(timers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a timer
|
||||
* @param timer Timer information
|
||||
* @param timer.start Date of the timer
|
||||
* @param timer.id Id number of the timer
|
||||
* @param timer.expired
|
||||
*/
|
||||
function updateTimer(timer) {
|
||||
if (timer.start.isAfter(Date.now())) {
|
||||
var duration = moment.duration(timer.start - moment(), 'milliseconds');
|
||||
document.getElementById("countdown" + timer.id).innerHTML = getDurationString(duration);
|
||||
} else {
|
||||
timer.expired = true;
|
||||
document.getElementById("countdown" + timer.id).innerHTML = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all local time fields
|
||||
*/
|
||||
function setAllLocalTimes() {
|
||||
var l = timers.length;
|
||||
for (var i=0; i < l; ++i) {
|
||||
setLocalTime(timers[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the local time info for the timer
|
||||
* @param timer Timer information
|
||||
* @param timer.start Date of the timer
|
||||
* @param timer.id Id number of the timer
|
||||
*/
|
||||
function setLocalTime(timer) {
|
||||
document.getElementById("localtime" + timer.id).innerHTML = timer.start.format("ddd @ LT");
|
||||
}
|
||||
|
||||
function updateClock() {
|
||||
document.getElementById("current-time").innerHTML = "<b>" + moment.utc().format('ddd, ll HH:mm:ss z') + "</b>";
|
||||
}
|
||||
</script>
|
||||
{% endblock content %}
|
||||
@@ -1,26 +0,0 @@
|
||||
{% load i18n %}
|
||||
{% load navactive %}
|
||||
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
|
||||
<span class="sr-only">{% trans "Toggle navigation" %}</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#">Fleet-Up</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="{% navactive request 'fleetup:view' %}"><a href="{% url 'fleetup:view' %}">{% trans "Ops and Timers" %}</a></li>
|
||||
<li class="{% navactive request 'fleetup:doctrines fleetup:doctrine' %}"><a href="{% url 'fleetup:doctrines' %}">{% trans "Doctrines" %}</a></li>
|
||||
<li class="{% navactive request 'fleetup:fittings fleetup:fitting' %}"><a href="{% url 'fleetup:fittings' %}">{% trans "Fittings" %}</a></li>
|
||||
{% if perms.auth.corp_stats %}
|
||||
<li class="{% navactive request 'fleetup:characters' %}"><a href="{% url 'fleetup:characters' %}">{% trans "Characters" %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -1,503 +0,0 @@
|
||||
from unittest import mock
|
||||
|
||||
import requests_mock
|
||||
import json
|
||||
import datetime
|
||||
|
||||
from django.test import TestCase
|
||||
from django.utils.timezone import make_aware, utc
|
||||
|
||||
from allianceauth.fleetup.managers import FleetUpManager
|
||||
|
||||
|
||||
class FleetupManagerTestCase(TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def test__request_cache_key(self):
|
||||
|
||||
cache_key = FleetUpManager._request_cache_key('testurl')
|
||||
|
||||
self.assertEqual('FLEETUP_ENDPOINT_a39562b6ef5b858220be13d2adb61d3f10cf8d61',
|
||||
cache_key)
|
||||
|
||||
@mock.patch('allianceauth.fleetup.managers.cache')
|
||||
@requests_mock.Mocker()
|
||||
def test_get_endpoint(self, cache, m):
|
||||
url = "http://example.com/test/endpoint/"
|
||||
json_data = {'data': "123456", 'CachedUntilUTC': '/Date(1493896236163)/', 'Success': True}
|
||||
m.register_uri('GET', url,
|
||||
text=json.dumps(json_data))
|
||||
|
||||
cache.get.return_value = None # No cached value
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_endpoint(url)
|
||||
|
||||
# Assert
|
||||
self.assertTrue(cache.get.called)
|
||||
self.assertTrue(cache.set.called)
|
||||
args, kwargs = cache.set.call_args
|
||||
self.assertDictEqual(json_data, args[1])
|
||||
|
||||
self.assertDictEqual(json_data, result)
|
||||
|
||||
@mock.patch('allianceauth.fleetup.managers.cache')
|
||||
@requests_mock.Mocker()
|
||||
def test_get_endpoint_error(self, cache, m):
|
||||
url = "http://example.com/test/endpoint/"
|
||||
json_data = {'data': [], 'Success': False}
|
||||
m.register_uri('GET', url,
|
||||
text=json.dumps(json_data),
|
||||
status_code=400)
|
||||
|
||||
cache.get.return_value = None # No cached value
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_endpoint(url)
|
||||
|
||||
# Assert
|
||||
self.assertTrue(cache.get.called)
|
||||
self.assertFalse(cache.set.called)
|
||||
self.assertIsNone(result)
|
||||
|
||||
@mock.patch('allianceauth.fleetup.managers.FleetUpManager.get_endpoint')
|
||||
def test_get_fleetup_members(self, get_endpoint):
|
||||
|
||||
get_endpoint.return_value = {"Data": [
|
||||
{
|
||||
'UserId': 1234,
|
||||
'EveCharName': 'test_name',
|
||||
'EveCharId': 5678,
|
||||
'Corporation': 'test_corporation',
|
||||
}
|
||||
]}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_members()
|
||||
|
||||
# Asset
|
||||
self.assertTrue(get_endpoint.called)
|
||||
args, kwargs = get_endpoint.call_args
|
||||
self.assertEqual(args[0],
|
||||
FleetUpManager.BASE_URL + '/GroupCharacters/' +
|
||||
FleetUpManager.GROUP_ID)
|
||||
expected_result = {
|
||||
1234: {
|
||||
'user_id': 1234,
|
||||
'char_name': 'test_name',
|
||||
'char_id': 5678,
|
||||
'corporation': 'test_corporation',
|
||||
}
|
||||
}
|
||||
self.assertDictEqual(expected_result, result)
|
||||
|
||||
# Test None response
|
||||
# Arrange
|
||||
get_endpoint.return_value = None
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_members()
|
||||
|
||||
# Assert
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Test Empty response
|
||||
# Arrange
|
||||
get_endpoint.return_value = {'Data': []}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_members()
|
||||
|
||||
# Assert
|
||||
self.assertDictEqual({}, result)
|
||||
|
||||
@mock.patch('allianceauth.fleetup.managers.FleetUpManager.get_endpoint')
|
||||
def test_get_fleetup_operations(self, get_endpoint):
|
||||
|
||||
get_endpoint.return_value = {"Data": [
|
||||
{
|
||||
'Subject': 'test_operation',
|
||||
'StartString': '2017-05-06 11:11:11',
|
||||
'EndString': '2017-05-06 12:12:12',
|
||||
'OperationId': 1234,
|
||||
'Location': 'Jita',
|
||||
'LocationInfo': '4-4',
|
||||
'Details': 'This is a test operation',
|
||||
'Url': 'http://example.com/1234',
|
||||
'Doctrines': 'Foxcats',
|
||||
'Organizer': 'Example FC'
|
||||
}
|
||||
]}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_operations()
|
||||
self.maxDiff = None
|
||||
# Asset
|
||||
self.assertTrue(get_endpoint.called)
|
||||
args, kwargs = get_endpoint.call_args
|
||||
self.assertEqual(args[0],
|
||||
FleetUpManager.BASE_URL + '/Operations/' +
|
||||
FleetUpManager.GROUP_ID)
|
||||
expected_result = {
|
||||
'2017-05-06 11:11:11': {
|
||||
'subject': 'test_operation',
|
||||
'start': make_aware(datetime.datetime(2017, 5, 6, 11, 11, 11), utc),
|
||||
'end': make_aware(datetime.datetime(2017, 5, 6, 12, 12, 12), utc),
|
||||
'operation_id': 1234,
|
||||
'location': 'Jita',
|
||||
'location_info': '4-4',
|
||||
'details': 'This is a test operation',
|
||||
'url': 'http://example.com/1234',
|
||||
'doctrine': 'Foxcats',
|
||||
'organizer': 'Example FC'
|
||||
}
|
||||
}
|
||||
self.assertDictEqual(expected_result, result)
|
||||
|
||||
# Test None response
|
||||
# Arrange
|
||||
get_endpoint.return_value = None
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_operations()
|
||||
|
||||
# Assert
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Test Empty response
|
||||
# Arrange
|
||||
get_endpoint.return_value = {'Data': []}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_operations()
|
||||
|
||||
# Assert
|
||||
self.assertDictEqual({}, result)
|
||||
|
||||
@mock.patch('allianceauth.fleetup.managers.FleetUpManager.get_endpoint')
|
||||
def test_get_fleetup_timers(self, get_endpoint):
|
||||
|
||||
get_endpoint.return_value = {"Data": [
|
||||
{
|
||||
'ExpiresString': '2017-05-06 11:11:11',
|
||||
'SolarSystem': 'Jita',
|
||||
'Planet': '4',
|
||||
'Moon': '4',
|
||||
'Owner': 'Caldari Navy',
|
||||
'Type': 'Caldari Station',
|
||||
'TimerType': 'Armor',
|
||||
'Notes': 'Burn Jita?'
|
||||
}
|
||||
]}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_timers()
|
||||
|
||||
# Asset
|
||||
self.assertTrue(get_endpoint.called)
|
||||
args, kwargs = get_endpoint.call_args
|
||||
self.assertEqual(args[0],
|
||||
FleetUpManager.BASE_URL + '/Timers/' +
|
||||
FleetUpManager.GROUP_ID)
|
||||
expected_result = {
|
||||
'2017-05-06 11:11:11': {
|
||||
'expires': make_aware(datetime.datetime(2017, 5, 6, 11, 11, 11), utc),
|
||||
'solarsystem': 'Jita',
|
||||
'planet': '4',
|
||||
'moon': '4',
|
||||
'owner': 'Caldari Navy',
|
||||
'type': 'Caldari Station',
|
||||
'timer_type': 'Armor',
|
||||
'notes': 'Burn Jita?'
|
||||
}
|
||||
}
|
||||
self.assertDictEqual(expected_result, result)
|
||||
|
||||
# Test None response
|
||||
# Arrange
|
||||
get_endpoint.return_value = None
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_timers()
|
||||
|
||||
# Assert
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Test Empty response
|
||||
# Arrange
|
||||
get_endpoint.return_value = {'Data': []}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_timers()
|
||||
|
||||
# Assert
|
||||
self.assertDictEqual({}, result)
|
||||
|
||||
@mock.patch('allianceauth.fleetup.managers.FleetUpManager.get_endpoint')
|
||||
def test_get_fleetup_doctrines(self, get_endpoint):
|
||||
|
||||
get_endpoint.return_value = {"Data": [
|
||||
{
|
||||
'TestData': True
|
||||
}
|
||||
]}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_doctrines()
|
||||
|
||||
# Asset
|
||||
self.assertTrue(get_endpoint.called)
|
||||
args, kwargs = get_endpoint.call_args
|
||||
self.assertEqual(args[0],
|
||||
FleetUpManager.BASE_URL + '/Doctrines/' +
|
||||
FleetUpManager.GROUP_ID)
|
||||
expected_result = {
|
||||
'fleetup_doctrines': [{
|
||||
'TestData': True
|
||||
}]
|
||||
}
|
||||
self.assertDictEqual(expected_result, result)
|
||||
|
||||
# Test None response
|
||||
# Arrange
|
||||
get_endpoint.return_value = None
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_doctrines()
|
||||
|
||||
# Assert
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Test Empty response
|
||||
# Arrange
|
||||
get_endpoint.return_value = {'Data': []}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_doctrines()
|
||||
|
||||
# Assert
|
||||
self.assertDictEqual({"fleetup_doctrines": []}, result)
|
||||
|
||||
@mock.patch('allianceauth.fleetup.managers.FleetUpManager.get_endpoint')
|
||||
def test_get_fleetup_doctrine(self, get_endpoint):
|
||||
|
||||
get_endpoint.return_value = {"Data": [
|
||||
{
|
||||
'TestData': True
|
||||
}
|
||||
]}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_doctrine(1234)
|
||||
|
||||
# Asset
|
||||
self.assertTrue(get_endpoint.called)
|
||||
args, kwargs = get_endpoint.call_args
|
||||
self.assertEqual(args[0],
|
||||
FleetUpManager.BASE_URL + '/DoctrineFittings/1234')
|
||||
expected_result = {
|
||||
'fitting_doctrine': {'Data': [{
|
||||
'TestData': True
|
||||
}]}
|
||||
}
|
||||
self.assertDictEqual(expected_result, result)
|
||||
|
||||
# Test None response
|
||||
# Arrange
|
||||
get_endpoint.return_value = None
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_doctrine(1234)
|
||||
|
||||
# Assert
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Test Empty response
|
||||
# Arrange
|
||||
get_endpoint.return_value = {'Data': []}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_doctrine(1234)
|
||||
|
||||
# Assert
|
||||
self.assertDictEqual({"fitting_doctrine": {'Data': []}}, result)
|
||||
|
||||
@mock.patch('allianceauth.fleetup.managers.FleetUpManager.get_endpoint')
|
||||
def test_get_fleetup_fittings(self, get_endpoint):
|
||||
|
||||
get_endpoint.return_value = {"Data": [
|
||||
{
|
||||
'FittingId': 1234,
|
||||
'Name': 'Foxcat',
|
||||
'EveTypeId': 17726,
|
||||
'HullType': 'Battleship',
|
||||
'ShipType': 'Apocalypse Navy Issue',
|
||||
'EstPrice': 500000000,
|
||||
'Faction': 'Amarr',
|
||||
'Categories': ["Armor", "Laser"],
|
||||
'LastUpdatedString': '2017-05-06 11:11:11',
|
||||
}
|
||||
]}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_fittings()
|
||||
|
||||
# Asset
|
||||
self.assertTrue(get_endpoint.called)
|
||||
expected_result = {
|
||||
1234: {
|
||||
'fitting_id': 1234,
|
||||
'name': 'Foxcat',
|
||||
'icon_id': 17726,
|
||||
'hull': 'Battleship',
|
||||
'shiptype': 'Apocalypse Navy Issue',
|
||||
'estimated': 500000000,
|
||||
'faction': 'Amarr',
|
||||
'categories': ["Armor", "Laser"],
|
||||
'last_update': make_aware(datetime.datetime(2017, 5, 6, 11, 11, 11), utc)
|
||||
}
|
||||
}
|
||||
self.assertDictEqual(expected_result, result)
|
||||
|
||||
# Test None response
|
||||
# Arrange
|
||||
get_endpoint.return_value = None
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_fittings()
|
||||
|
||||
# Assert
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Test Empty response
|
||||
# Arrange
|
||||
get_endpoint.return_value = {'Data': []}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_fittings()
|
||||
|
||||
# Assert
|
||||
self.assertDictEqual({}, result)
|
||||
|
||||
@mock.patch('allianceauth.fleetup.managers.FleetUpManager.get_endpoint')
|
||||
def test_get_fleetup_fitting(self, get_endpoint):
|
||||
|
||||
get_endpoint.return_value = {"Data":
|
||||
{
|
||||
'FittingData': [{}]
|
||||
}
|
||||
}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_fitting(1234)
|
||||
|
||||
# Asset
|
||||
self.assertTrue(get_endpoint.called)
|
||||
args, kwargs = get_endpoint.call_args
|
||||
self.assertEqual(args[0], FleetUpManager.BASE_URL + '/Fitting/1234')
|
||||
expected_result = {
|
||||
'fitting_data': {
|
||||
'FittingData': [{}]
|
||||
}
|
||||
}
|
||||
self.assertDictEqual(expected_result, result)
|
||||
|
||||
# Test None response
|
||||
# Arrange
|
||||
get_endpoint.return_value = None
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_fitting(1234)
|
||||
|
||||
# Assert
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Test Empty response
|
||||
# Arrange
|
||||
get_endpoint.return_value = {'Data': {}}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_fitting(1234)
|
||||
|
||||
# Assert
|
||||
self.assertDictEqual({"fitting_data": {}}, result)
|
||||
|
||||
@mock.patch('allianceauth.fleetup.managers.FleetUpManager.get_endpoint')
|
||||
def test_get_fleetup_doctrineid(self, get_endpoint):
|
||||
|
||||
get_endpoint.return_value = {
|
||||
"Data": {
|
||||
'Doctrines': [{'DoctrineId': 4567}]
|
||||
}
|
||||
}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_doctrineid(1234)
|
||||
|
||||
# Asset
|
||||
self.assertTrue(get_endpoint.called)
|
||||
args, kwargs = get_endpoint.call_args
|
||||
self.assertEqual(args[0], FleetUpManager.BASE_URL + '/Fitting/1234')
|
||||
|
||||
self.assertEqual(4567, result)
|
||||
|
||||
# Test None response
|
||||
# Arrange
|
||||
get_endpoint.return_value = None
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_doctrineid(1234)
|
||||
|
||||
# Assert
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Test Empty response
|
||||
# Arrange
|
||||
get_endpoint.return_value = {'Data': {}}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_doctrineid(1234)
|
||||
|
||||
# Assert
|
||||
self.assertDictEqual({}, result)
|
||||
|
||||
@mock.patch('allianceauth.fleetup.managers.FleetUpManager.get_endpoint')
|
||||
def test_get_fleetup_fitting_eft(self, get_endpoint):
|
||||
|
||||
get_endpoint.return_value = {
|
||||
"Data": {
|
||||
'FittingData': '[Apocalypse Navy Issue, Foxcat]'
|
||||
}
|
||||
}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_fitting_eft(1234)
|
||||
|
||||
# Asset
|
||||
self.assertTrue(get_endpoint.called)
|
||||
args, kwargs = get_endpoint.call_args
|
||||
self.assertEqual(args[0], FleetUpManager.BASE_URL + '/Fitting/1234/eft')
|
||||
|
||||
self.assertDictEqual({"fitting_eft": '[Apocalypse Navy Issue, Foxcat]'},
|
||||
result)
|
||||
|
||||
# Test None response
|
||||
# Arrange
|
||||
get_endpoint.return_value = None
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_fitting_eft(1234)
|
||||
|
||||
# Assert
|
||||
self.assertIsNone(result)
|
||||
|
||||
# Test Empty response
|
||||
# Arrange
|
||||
get_endpoint.return_value = {'Data': {}}
|
||||
|
||||
# Act
|
||||
result = FleetUpManager.get_fleetup_fitting_eft(1234)
|
||||
|
||||
# Assert
|
||||
self.assertDictEqual({"fitting_eft": {}}, result)
|
||||
@@ -1,14 +0,0 @@
|
||||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'fleetup'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$', views.fleetup_view, name='view'),
|
||||
url(r'^fittings/$', views.fleetup_fittings, name='fittings'),
|
||||
url(r'^fittings/(?P<fittingnumber>[0-9]+)/$', views.fleetup_fitting, name='fitting'),
|
||||
url(r'^doctrines/$', views.fleetup_doctrines, name='doctrines'),
|
||||
url(r'^characters/$', views.fleetup_characters, name='characters'),
|
||||
url(r'^doctrines/(?P<doctrinenumber>[0-9]+)/$', views.fleetup_doctrine, name='doctrine'),
|
||||
]
|
||||
@@ -1,112 +0,0 @@
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.shortcuts import render
|
||||
from django.template.defaulttags import register
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from .managers import FleetUpManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@register.filter
|
||||
def get_item(dictionary, key):
|
||||
return dictionary.get(key)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.view_fleetup')
|
||||
def fleetup_view(request):
|
||||
logger.debug("fleetup_view called by user %s" % request.user)
|
||||
|
||||
operations_list = FleetUpManager.get_fleetup_operations()
|
||||
if operations_list is None:
|
||||
messages.add_message(request, messages.ERROR, _("Failed to get operations list, contact your administrator"))
|
||||
operations_list = {}
|
||||
timers_list = FleetUpManager.get_fleetup_timers()
|
||||
if timers_list is None:
|
||||
messages.add_message(request, messages.ERROR, _("Failed to get timers list, contact your administrator"))
|
||||
timers_list = {}
|
||||
now = datetime.datetime.now().strftime('%H:%M:%S')
|
||||
|
||||
context = {"timers_list": sorted(timers_list.items()),
|
||||
"operations_list": sorted(operations_list.items()),
|
||||
"now": now}
|
||||
|
||||
return render(request, 'fleetup/index.html', context=context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.human_resources')
|
||||
@permission_required('auth.view_fleetup')
|
||||
def fleetup_characters(request):
|
||||
logger.debug("fleetup_characters called by user %s" % request.user)
|
||||
|
||||
member_list = FleetUpManager.get_fleetup_members()
|
||||
if member_list is None:
|
||||
messages.add_message(request, messages.ERROR, _("Failed to get member list, contact your administrator"))
|
||||
member_list = {}
|
||||
|
||||
context = {"member_list": sorted(member_list.items())}
|
||||
|
||||
return render(request, 'fleetup/characters.html', context=context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.view_fleetup')
|
||||
def fleetup_fittings(request):
|
||||
logger.debug("fleetup_fittings called by user %s" % request.user)
|
||||
fitting_list = FleetUpManager.get_fleetup_fittings()
|
||||
|
||||
if fitting_list is None:
|
||||
messages.add_message(request, messages.ERROR, _("Failed to get fitting list, contact your administrator"))
|
||||
fitting_list = {}
|
||||
|
||||
context = {"fitting_list": sorted(fitting_list.items())}
|
||||
return render(request, 'fleetup/fittingsview.html', context=context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.view_fleetup')
|
||||
def fleetup_fitting(request, fittingnumber):
|
||||
logger.debug("fleetup_fitting called by user %s" % request.user)
|
||||
fitting_eft = FleetUpManager.get_fleetup_fitting_eft(fittingnumber)
|
||||
fitting_data = FleetUpManager.get_fleetup_fitting(fittingnumber)
|
||||
doctrinenumber = FleetUpManager.get_fleetup_doctrineid(fittingnumber)
|
||||
doctrines_list = FleetUpManager.get_fleetup_doctrine(doctrinenumber)
|
||||
|
||||
if fitting_eft is None or fitting_data is None or doctrinenumber is None:
|
||||
messages.add_message(request, messages.ERROR, _("There was an error getting some of the data for this fitting. "
|
||||
"Contact your administrator"))
|
||||
|
||||
context = {"fitting_eft": fitting_eft,
|
||||
"fitting_data": fitting_data,
|
||||
"doctrines_list": doctrines_list}
|
||||
return render(request, 'fleetup/fitting.html', context=context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.view_fleetup')
|
||||
def fleetup_doctrines(request):
|
||||
logger.debug("fleetup_doctrines called by user %s" % request.user)
|
||||
doctrines_list = FleetUpManager.get_fleetup_doctrines()
|
||||
if doctrines_list is None:
|
||||
messages.add_message(request, messages.ERROR, _("Failed to get doctrines list, contact your administrator"))
|
||||
|
||||
context = {"doctrines_list": doctrines_list}
|
||||
return render(request, 'fleetup/doctrinesview.html', context=context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.view_fleetup')
|
||||
def fleetup_doctrine(request, doctrinenumber):
|
||||
logger.debug("fleetup_doctrine called by user %s" % request.user)
|
||||
doctrine = FleetUpManager.get_fleetup_doctrine(doctrinenumber)
|
||||
if doctrine is None:
|
||||
messages.add_message(request, messages.ERROR, _("Failed to get doctine, contact your administrator"))
|
||||
context = {"doctrine": doctrine}
|
||||
return render(request, 'fleetup/doctrine.html', context=context)
|
||||
@@ -1,18 +1,46 @@
|
||||
from django.conf import settings
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import Group as BaseGroup
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
|
||||
from django.contrib.auth.models import Group as BaseGroup, User
|
||||
from django.db.models import Count
|
||||
from django.db.models.functions import Lower
|
||||
from django.db.models.signals import pre_save, post_save, pre_delete, \
|
||||
post_delete, m2m_changed
|
||||
from django.dispatch import receiver
|
||||
|
||||
from .models import AuthGroup
|
||||
from .models import GroupRequest
|
||||
from . import signals
|
||||
|
||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||
_has_auto_groups = True
|
||||
else:
|
||||
_has_auto_groups = False
|
||||
|
||||
|
||||
class AuthGroupInlineAdmin(admin.StackedInline):
|
||||
model = AuthGroup
|
||||
filter_horizontal = ('group_leaders', 'states',)
|
||||
fields = ('description', 'group_leaders', 'states', 'internal', 'hidden', 'open', 'public')
|
||||
filter_horizontal = ('group_leaders', 'group_leader_groups', 'states',)
|
||||
fields = (
|
||||
'description',
|
||||
'group_leaders',
|
||||
'group_leader_groups',
|
||||
'states', 'internal',
|
||||
'hidden',
|
||||
'open',
|
||||
'public'
|
||||
)
|
||||
verbose_name_plural = 'Auth Settings'
|
||||
verbose_name = ''
|
||||
|
||||
def formfield_for_manytomany(self, db_field, request, **kwargs):
|
||||
"""overriding this formfield to have sorted lists in the form"""
|
||||
if db_field.name == "group_leaders":
|
||||
kwargs["queryset"] = User.objects.order_by(Lower('username'))
|
||||
elif db_field.name == "group_leader_groups":
|
||||
kwargs["queryset"] = Group.objects.order_by(Lower('name'))
|
||||
return super().formfield_for_manytomany(db_field, request, **kwargs)
|
||||
|
||||
def has_add_permission(self, request):
|
||||
return False
|
||||
|
||||
@@ -23,7 +51,118 @@ class AuthGroupInlineAdmin(admin.StackedInline):
|
||||
return request.user.has_perm('auth.change_group')
|
||||
|
||||
|
||||
class GroupAdmin(admin.ModelAdmin):
|
||||
if _has_auto_groups:
|
||||
class IsAutoGroupFilter(admin.SimpleListFilter):
|
||||
title = 'auto group'
|
||||
parameter_name = 'is_auto_group__exact'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return (
|
||||
('yes', 'Yes'),
|
||||
('no', 'No'),
|
||||
)
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
value = self.value()
|
||||
if value == 'yes':
|
||||
return queryset.exclude(
|
||||
managedalliancegroup__isnull=True,
|
||||
managedcorpgroup__isnull=True
|
||||
)
|
||||
elif value == 'no':
|
||||
return queryset.filter(
|
||||
managedalliancegroup__isnull=True,
|
||||
managedcorpgroup__isnull=True
|
||||
)
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
class HasLeaderFilter(admin.SimpleListFilter):
|
||||
title = 'has leader'
|
||||
parameter_name = 'has_leader__exact'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return (
|
||||
('yes', 'Yes'),
|
||||
('no', 'No'),
|
||||
)
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
value = self.value()
|
||||
if value == 'yes':
|
||||
return queryset.filter(authgroup__group_leaders__isnull=False)
|
||||
elif value == 'no':
|
||||
return queryset.filter(authgroup__group_leaders__isnull=True)
|
||||
else:
|
||||
return queryset
|
||||
|
||||
class GroupAdmin(admin.ModelAdmin):
|
||||
list_select_related = True
|
||||
ordering = ('name', )
|
||||
list_display = (
|
||||
'name',
|
||||
'_description',
|
||||
'_properties',
|
||||
'_member_count',
|
||||
'has_leader'
|
||||
)
|
||||
list_filter = [
|
||||
'authgroup__internal',
|
||||
'authgroup__hidden',
|
||||
'authgroup__open',
|
||||
'authgroup__public',
|
||||
]
|
||||
if _has_auto_groups:
|
||||
list_filter.append(IsAutoGroupFilter)
|
||||
list_filter.append(HasLeaderFilter)
|
||||
|
||||
search_fields = ('name', 'authgroup__description')
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super().get_queryset(request)
|
||||
qs = qs.annotate(
|
||||
member_count=Count('user', distinct=True),
|
||||
)
|
||||
return qs
|
||||
|
||||
def _description(self, obj):
|
||||
return obj.authgroup.description
|
||||
|
||||
def _member_count(self, obj):
|
||||
return obj.member_count
|
||||
|
||||
_member_count.short_description = 'Members'
|
||||
_member_count.admin_order_field = 'member_count'
|
||||
|
||||
def has_leader(self, obj):
|
||||
return obj.authgroup.group_leaders.exists()
|
||||
|
||||
has_leader.boolean = True
|
||||
|
||||
def _properties(self, obj):
|
||||
properties = list()
|
||||
if _has_auto_groups and (
|
||||
obj.managedalliancegroup_set.exists()
|
||||
or obj.managedcorpgroup_set.exists()
|
||||
):
|
||||
properties.append('Auto Group')
|
||||
elif obj.authgroup.internal:
|
||||
properties.append('Internal')
|
||||
else:
|
||||
if obj.authgroup.hidden:
|
||||
properties.append('Hidden')
|
||||
if obj.authgroup.open:
|
||||
properties.append('Open')
|
||||
if obj.authgroup.public:
|
||||
properties.append('Public')
|
||||
if not properties:
|
||||
properties.append('Default')
|
||||
|
||||
return properties
|
||||
|
||||
_properties.short_description = "properties"
|
||||
|
||||
filter_horizontal = ('permissions',)
|
||||
inlines = (AuthGroupInlineAdmin,)
|
||||
|
||||
@@ -65,4 +204,4 @@ def redirect_post_delete(sender, signal=None, *args, **kwargs):
|
||||
|
||||
@receiver(m2m_changed, sender=Group.permissions.through)
|
||||
def redirect_m2m_changed_permissions(sender, signal=None, *args, **kwargs):
|
||||
m2m_changed.send(BaseGroup, *args, **kwargs)
|
||||
m2m_changed.send(BaseGroup, *args, **kwargs)
|
||||
|
||||
@@ -1,26 +1,53 @@
|
||||
from django.contrib.auth.models import Group
|
||||
from django.db.models import Q
|
||||
import logging
|
||||
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.db.models import Q, QuerySet
|
||||
|
||||
from allianceauth.authentication.models import State
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GroupManager:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_joinable_groups_for_user(
|
||||
cls, user: User, include_hidden = True
|
||||
) -> QuerySet:
|
||||
"""get groups a user could join incl. groups already joined"""
|
||||
groups_qs = cls.get_joinable_groups(user.profile.state)
|
||||
|
||||
if not user.has_perm('groupmanagement.request_groups'):
|
||||
groups_qs = groups_qs.filter(authgroup__public=True)
|
||||
|
||||
if not include_hidden:
|
||||
groups_qs = groups_qs.filter(authgroup__hidden=False)
|
||||
|
||||
return groups_qs
|
||||
|
||||
@staticmethod
|
||||
def get_joinable_groups(state):
|
||||
return Group.objects.select_related('authgroup').exclude(authgroup__internal=True)\
|
||||
def get_joinable_groups(state: State) -> QuerySet:
|
||||
"""get groups that can be joined by user with given state"""
|
||||
return Group.objects\
|
||||
.select_related('authgroup')\
|
||||
.exclude(authgroup__internal=True)\
|
||||
.filter(Q(authgroup__states=state) | Q(authgroup__states=None))
|
||||
|
||||
@staticmethod
|
||||
def get_all_non_internal_groups():
|
||||
return Group.objects.select_related('authgroup').exclude(authgroup__internal=True)
|
||||
def get_all_non_internal_groups() -> QuerySet:
|
||||
"""get groups that are not internal"""
|
||||
return Group.objects\
|
||||
.select_related('authgroup')\
|
||||
.exclude(authgroup__internal=True)
|
||||
|
||||
@staticmethod
|
||||
def get_group_leaders_groups(user):
|
||||
return Group.objects.select_related('authgroup').filter(authgroup__group_leaders__in=[user])
|
||||
def get_group_leaders_groups(user: User):
|
||||
return Group.objects.select_related('authgroup').filter(authgroup__group_leaders__in=[user]) | \
|
||||
Group.objects.select_related('authgroup').filter(authgroup__group_leader_groups__in=user.groups.all())
|
||||
|
||||
@staticmethod
|
||||
def joinable_group(group, state):
|
||||
def joinable_group(group: Group, state: State) -> bool:
|
||||
"""
|
||||
Check if a group is a user/state joinable group, i.e.
|
||||
not an internal group for Corp, Alliance, Members etc,
|
||||
@@ -29,12 +56,15 @@ class GroupManager:
|
||||
:param state: allianceauth.authentication.State object
|
||||
:return: bool True if its joinable, False otherwise
|
||||
"""
|
||||
if len(group.authgroup.states.all()) != 0 and state not in group.authgroup.states.all():
|
||||
if (len(group.authgroup.states.all()) != 0
|
||||
and state not in group.authgroup.states.all()
|
||||
):
|
||||
return False
|
||||
return not group.authgroup.internal
|
||||
else:
|
||||
return not group.authgroup.internal
|
||||
|
||||
@staticmethod
|
||||
def check_internal_group(group):
|
||||
def check_internal_group(group: Group) -> bool:
|
||||
"""
|
||||
Check if a group is auditable, i.e not an internal group
|
||||
:param group: django.contrib.auth.models.Group object
|
||||
@@ -43,20 +73,11 @@ class GroupManager:
|
||||
return not group.authgroup.internal
|
||||
|
||||
@staticmethod
|
||||
def check_internal_group(group):
|
||||
"""
|
||||
Check if a group is auditable, i.e not an internal group
|
||||
:param group: django.contrib.auth.models.Group object
|
||||
:return: bool True if it is auditable, false otherwise
|
||||
"""
|
||||
return not group.authgroup.internal
|
||||
|
||||
@staticmethod
|
||||
def has_management_permission(user):
|
||||
def has_management_permission(user: User) -> bool:
|
||||
return user.has_perm('auth.group_management')
|
||||
|
||||
@classmethod
|
||||
def can_manage_groups(cls, user):
|
||||
def can_manage_groups(cls, user:User ) -> bool:
|
||||
"""
|
||||
For use with user_passes_test decorator.
|
||||
Check if the user can manage groups. Either has the
|
||||
@@ -66,11 +87,11 @@ class GroupManager:
|
||||
:return: bool True if user can manage groups, False otherwise
|
||||
"""
|
||||
if user.is_authenticated:
|
||||
return cls.has_management_permission(user) or user.leads_groups.all()
|
||||
return cls.has_management_permission(user) or cls.get_group_leaders_groups(user)
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def can_manage_group(cls, user, group):
|
||||
def can_manage_group(cls, user: User, group: Group) -> bool:
|
||||
"""
|
||||
Check user has permission to manage the given group
|
||||
:param user: User object to test permission of
|
||||
@@ -78,5 +99,5 @@ class GroupManager:
|
||||
:return: True if the user can manage the group
|
||||
"""
|
||||
if user.is_authenticated:
|
||||
return cls.has_management_permission(user) or user.leads_groups.filter(group=group).exists()
|
||||
return cls.has_management_permission(user) or cls.get_group_leaders_groups(user).filter(pk=group.pk).exists()
|
||||
return False
|
||||
|
||||
19
allianceauth/groupmanagement/migrations/0012_group_leads.py
Normal file
19
allianceauth/groupmanagement/migrations/0012_group_leads.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.8 on 2020-01-06 11:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0011_update_proxy_permissions'),
|
||||
('groupmanagement', '0011_requestlog_date'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='authgroup',
|
||||
name='group_leader_groups',
|
||||
field=models.ManyToManyField(blank=True, help_text='Group leaders can process group requests for this group specifically. Use the auth.group_management permission to allow a user to manage all groups.', related_name='leads_group_groups', to='auth.Group'),
|
||||
)
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.9 on 2020-01-30 13:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('groupmanagement', '0012_group_leads'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='requestlog',
|
||||
name='date',
|
||||
field=models.DateTimeField(auto_now_add=True),
|
||||
),
|
||||
]
|
||||
@@ -31,7 +31,7 @@ class RequestLog(models.Model):
|
||||
request_info = models.CharField(max_length=254)
|
||||
action = models.BooleanField(default=0)
|
||||
request_actor = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
date = models.DateTimeField(auto_now_add=datetime.utcnow())
|
||||
date = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def requestor(self):
|
||||
return self.request_info.split(":")[0]
|
||||
@@ -98,6 +98,11 @@ class AuthGroup(models.Model):
|
||||
help_text="Group leaders can process group requests for this group "
|
||||
"specifically. Use the auth.group_management permission to allow "
|
||||
"a user to manage all groups.")
|
||||
# allow groups to be *group leads*
|
||||
group_leader_groups = models.ManyToManyField(Group, related_name='leads_group_groups', blank=True,
|
||||
help_text="Group leaders can process group requests for this group "
|
||||
"specifically. Use the auth.group_management permission to allow "
|
||||
"a user to manage all groups.")
|
||||
|
||||
states = models.ManyToManyField(State, related_name='valid_states', blank=True,
|
||||
help_text="States listed here will have the ability to join this group provided "
|
||||
|
||||
16
allianceauth/groupmanagement/signals.py
Normal file
16
allianceauth/groupmanagement/signals.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from allianceauth.authentication.signals import state_changed
|
||||
from .managers import GroupManager
|
||||
from .models import Group
|
||||
from django.dispatch import receiver
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@receiver(state_changed)
|
||||
def check_groups_on_state_change(sender, user, state, **kwargs):
|
||||
logger.debug("Updating auth groups for {}".format(user))
|
||||
visible_groups = GroupManager.get_joinable_groups(state)
|
||||
visible_groups = visible_groups | Group.objects.select_related('authgroup').filter(authgroup__internal=True)
|
||||
groups = user.groups.all()
|
||||
for g in groups:
|
||||
if g not in visible_groups:
|
||||
user.groups.remove(g)
|
||||
@@ -8,55 +8,100 @@
|
||||
<div class="col-lg-12">
|
||||
<br>
|
||||
{% include 'groupmanagement/menu.html' %}
|
||||
<div>
|
||||
<h3>{{ group }} Audit Log</h3>
|
||||
<p> All times displayed are EVE/UTC.</p>
|
||||
{% if entries %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" id="log-entries">
|
||||
<thead>
|
||||
<th class="text-center" scope="col">{% trans "Date/Time" %}</th>
|
||||
<th class="text-center" scope="col">{% trans "Requestor" %}</th>
|
||||
<th class="text-center" scope="col">{% trans "Main Character" %}</th>
|
||||
<th class="text-center" scope="col">{% trans "Group" %}</th>
|
||||
<th class="text-center" scope="col">{% trans "Type" %}</th>
|
||||
<th class="text-center" scope="col">{% trans "Action" %}</th>
|
||||
<th class="text-center" scope="col">{% trans "Actor" %}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entry in entries %}
|
||||
<tr>
|
||||
<td class="text-center">{{ entry.date }}</td>
|
||||
<td class="text-center">{{ entry.requestor }}</td>
|
||||
<td class="text-center">{{ entry.req_char }}</td>
|
||||
<td class="text-center">{{ entry.group }}</td>
|
||||
<td class="text-center">{{ entry.type_to_str }}</td>
|
||||
{% if entry.request_type is None %}
|
||||
<td class="text-center"> Removed</td>
|
||||
{% else %}
|
||||
<td class="text-center">{{ entry.action_to_str }}</td>
|
||||
{% endif %}
|
||||
<td class="text-center">{{ entry.request_actor }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">{% trans "No entries found for this group." %}</div>
|
||||
{% endif %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
{{ group }} - {% trans 'Audit Log' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
|
||||
{% trans "Back" %}
|
||||
</a>
|
||||
</p>
|
||||
{% if entries %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped" id="log-entries">
|
||||
<thead>
|
||||
<th class="text-center" scope="col">{% trans "Date/Time" %}</th>
|
||||
<th class="text-center" scope="col">{% trans "Requestor" %}</th>
|
||||
<th class="text-center" scope="col">{% trans "Character" %}</th>
|
||||
<th class="text-center" scope="col">{% trans "Corporation" %}</th>
|
||||
<th class="text-center" scope="col">{% trans "Type" %}</th>
|
||||
<th class="text-center" scope="col">{% trans "Action" %}</th>
|
||||
<th class="text-center" scope="col">{% trans "Actor" %}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for entry in entries %}
|
||||
<tr>
|
||||
<td class="text-center">{{ entry.date|date:"Y-M-d H:i" }}</td>
|
||||
<td class="text-center">{{ entry.requestor }}</td>
|
||||
<td class="text-center">{{ entry.req_char }}</td>
|
||||
<td class="text-center">{{ entry.req_char.corporation_name }}</td>
|
||||
<td class="text-center">{{ entry.type_to_str }}</td>
|
||||
{% if entry.request_type is None %}
|
||||
<td class="text-center"> Removed</td>
|
||||
{% else %}
|
||||
<td class="text-center">{{ entry.action_to_str }}</td>
|
||||
{% endif %}
|
||||
<td class="text-center">{{ entry.request_actor }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-muted">
|
||||
All times displayed are EVE/UTC.
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="clearfix"></div>
|
||||
<br>
|
||||
<div class="alert alert-warning text-center">
|
||||
{% trans "No entries found for this group." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js.html' %}
|
||||
<script type="text/javascript" src="{% static 'js/filterDropDown/filterDropDown.min.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{% include 'bundles/datatables-css.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
$(document).ready(function(){
|
||||
$('#log-entries').DataTable();
|
||||
$('#log-entries').DataTable({
|
||||
order: [[ 0, 'desc' ], [ 1, 'asc' ] ],
|
||||
filterDropDown:
|
||||
{
|
||||
columns: [
|
||||
{
|
||||
idx: 1
|
||||
},
|
||||
{
|
||||
idx: 2
|
||||
},
|
||||
{
|
||||
idx: 3
|
||||
},
|
||||
{
|
||||
idx: 4
|
||||
},
|
||||
{
|
||||
idx: 5
|
||||
},
|
||||
{
|
||||
idx: 6
|
||||
}
|
||||
],
|
||||
bootstrap: true
|
||||
},
|
||||
});
|
||||
});
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,43 +1,102 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
{% load evelinks %}
|
||||
|
||||
{% block page_title %}{% trans "Group Members" %}{% endblock page_title %}
|
||||
{% block extra_css %}{% endblock extra_css %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<br>
|
||||
{% include 'groupmanagement/menu.html' %}
|
||||
<h3>{{ group.name }} {% trans 'Members' %}</h3>
|
||||
<div id="list" class="">
|
||||
{% if group.user_set %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th class="text-center">{% trans "User" %}</th>
|
||||
<th class="text-center">{% trans "Character" %}</th>
|
||||
<th class="text-center">{% trans "Corp" %}</th>
|
||||
<th class="text-center">{% trans "Alliance" %}</th>
|
||||
<th class="text-center">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
{% for member in members %}
|
||||
<tr>
|
||||
<td class="text-center">{{ member.user.username }}</td>
|
||||
<td class="text-center">{{ member.main_char.character_name }}</td>
|
||||
<td class="text-center">{{ member.main_char.corporation_name }}</td>
|
||||
<td class="text-center">{{ member.main_char.alliance_name }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'groupmanagement:membership_remove' group.id member.user.id %}" class="btn btn-danger"
|
||||
title="{% trans "Remove from group" %}">
|
||||
<i class="glyphicon glyphicon-remove"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">{% trans "No group members to list." %}</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
{{ group.name }} - {% trans 'Members' %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
<a class="btn btn-default" href="{% url 'groupmanagement:membership' %}" role="button">
|
||||
{% trans "Back" %}
|
||||
</a>
|
||||
</p>
|
||||
{% if group.user_set %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-aa" id="tab_group_members">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-right">{% trans "Portrait" %}</th>
|
||||
<th class="text-center">{% trans "Character" %}</th>
|
||||
<th class="text-center">{% trans "Organization" %}</th>
|
||||
<th class="text-center"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for member in members %}
|
||||
<tr>
|
||||
<td class="text-right">
|
||||
{% if member.is_leader %}
|
||||
<i class="fa fa-star"></i>
|
||||
{% endif %}
|
||||
<img src="{{ member.main_char|character_portrait_url:32 }}" class="img-circle">
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if member.main_char %}
|
||||
<a href="{{ member.main_char|evewho_character_url }}" target="_blank">
|
||||
{{ member.main_char.character_name }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ member.user.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if member.main_char %}
|
||||
<a href="{{ member.main_char|dotlan_corporation_url }}" target="_blank">
|
||||
{{ member.main_char.corporation_name }}
|
||||
</a><br>
|
||||
{{ member.main_char.alliance_name|default_if_none:"" }}
|
||||
{% else %}
|
||||
(unknown)
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'groupmanagement:membership_remove' group.id member.user.id %}" class="btn btn-danger"
|
||||
title="{% trans "Remove from group" %}">
|
||||
<i class="glyphicon glyphicon-remove"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="text-muted"><i class="fa fa-star"></i>: Group leader</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">
|
||||
{% trans "No group members to list." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
{% block extra_javascript %}
|
||||
{% include 'bundles/datatables-js.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
{% include 'bundles/datatables-css.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
$(document).ready(function(){
|
||||
$('#tab_group_members').DataTable({
|
||||
order: [ [ 1, "asc" ] ],
|
||||
columnDefs: [
|
||||
{ "sortable": false, "targets": [0, 3] },
|
||||
]
|
||||
});
|
||||
});
|
||||
{% endblock %}
|
||||
@@ -9,48 +9,72 @@
|
||||
<div class="col-lg-12">
|
||||
<br>
|
||||
{% include 'groupmanagement/menu.html' %}
|
||||
<div>
|
||||
{% if groups %}
|
||||
<h3>Groups</h3>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th class="text-center">{% trans "Name" %}</th>
|
||||
<th class="text-center">{% trans "Description" %}</th>
|
||||
<th class="text-center">{% trans "Status" %}</th>
|
||||
<th class="text-center">{% trans "Member Count" %}</th>
|
||||
<th class="text-center">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td class="text-center">{{ group.name }}</td>
|
||||
<td class="text-center">{{ group.authgroup.description }}</td>
|
||||
<td class="text-center">
|
||||
{% if group.authgroup.hidden %}
|
||||
<span class="label label-info">{% trans "Hidden" %}</span>
|
||||
{% elif group.authgroup.open %}
|
||||
<span class="label label-success">{% trans "Open" %}</span>
|
||||
{% else %}
|
||||
<span class="label label-default">{% trans "Requestable" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ group.num_members }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'groupmanagement:membership_list' group.id %}" class="btn btn-primary"
|
||||
title="{% trans "View Members" %}">
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
</a>
|
||||
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% trans "Audit Members" %}">
|
||||
<i class="glyphicon glyphicon-list-alt"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">{% trans "No groups to list." %}</div>
|
||||
{% endif %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
{% trans "Groups" %}
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if groups %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-aa">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">{% trans "Name" %}</th>
|
||||
<th class="text-center">{% trans "Description" %}</th>
|
||||
<th class="text-center">{% trans "Status" %}</th>
|
||||
<th class="text-center">{% trans "Member Count" %}</th>
|
||||
<th class="text-center"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'groupmanagement:membership_list' group.id %}">{{ group.name }}</a>
|
||||
</td>
|
||||
<td class="text-center">{{ group.authgroup.description }}</td>
|
||||
<td class="text-center">
|
||||
{% if group.authgroup.hidden %}
|
||||
<span class="label label-info">{% trans "Hidden" %}</span>
|
||||
{% elif group.authgroup.open %}
|
||||
<span class="label label-success">{% trans "Open" %}</span>
|
||||
{% else %}
|
||||
<span class="label label-default">{% trans "Requestable" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{{ group.num_members }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'groupmanagement:membership_list' group.id %}" class="btn btn-primary"
|
||||
title="{% trans "View Members" %}">
|
||||
<i class="glyphicon glyphicon-eye-open"></i>
|
||||
</a>
|
||||
<a href="{% url "groupmanagement:audit_log" group.id %}" class="btn btn-info" title="{% trans "Audit Members" %}">
|
||||
<i class="glyphicon glyphicon-list-alt"></i>
|
||||
</a>
|
||||
<a id="clipboard-copy" data-clipboard-text="{{ request.scheme }}://{{request.get_host}}{% url 'groupmanagement:request_add' group.id %}" class="btn btn-warning" title="{% trans "Copy Direct Join Link" %}">
|
||||
<i class="glyphicon glyphicon-copy"></i>
|
||||
</a>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">
|
||||
{% trans "No groups to list." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
{% block extra_javascript %}
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.4/clipboard.min.js"></script>
|
||||
<script>
|
||||
new ClipboardJS('#clipboard-copy');
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -9,49 +9,54 @@ url
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% trans "Available Groups" %}</h1>
|
||||
{% if groups %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th class="text-center">{% trans "Name" %}</th>
|
||||
<th class="text-center">{% trans "Description" %}</th>
|
||||
<th class="text-center">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
|
||||
{% for g in groups %}
|
||||
<tr>
|
||||
<td class="text-center">{{ g.group.name }}</td>
|
||||
<td class="text-center">{{ g.group.authgroup.description|urlize }}</td>
|
||||
<td class="text-center">
|
||||
{% if g.group in user.groups.all %}
|
||||
{% if not g.request %}
|
||||
<a href="{% url 'groupmanagement:request_leave' g.group.id %}" class="btn btn-danger">
|
||||
{% trans "Leave" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary" disabled>
|
||||
{{ g.request.status }}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% elif not g.request %}
|
||||
{% if g.group.authgroup.open %}
|
||||
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-success">
|
||||
{% trans "Join" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-primary">
|
||||
{% trans "Request" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary" disabled>
|
||||
{{ g.request.status }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<table class="table table-aa">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center">{% trans "Name" %}</th>
|
||||
<th class="text-center">{% trans "Description" %}</th>
|
||||
<th class="text-center">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for g in groups %}
|
||||
<tr>
|
||||
<td class="text-center">{{ g.group.name }}</td>
|
||||
<td class="text-center">{{ g.group.authgroup.description|urlize }}</td>
|
||||
<td class="text-center">
|
||||
{% if g.group in user.groups.all %}
|
||||
{% if not g.request %}
|
||||
<a href="{% url 'groupmanagement:request_leave' g.group.id %}" class="btn btn-danger">
|
||||
{% trans "Leave" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary" disabled>
|
||||
{{ g.request.status }}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% elif not g.request %}
|
||||
{% if g.group.authgroup.open %}
|
||||
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-success">
|
||||
{% trans "Join" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-primary">
|
||||
{% trans "Request" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary" disabled>
|
||||
{{ g.request.status }}
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">{% trans "No groups available." %}</div>
|
||||
<div class="alert alert-warning text-center">
|
||||
{% trans "No groups available." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,81 +1,163 @@
|
||||
{% extends "allianceauth/base.html" %}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
{% load evelinks %}
|
||||
|
||||
{% block page_title %}{% trans "Groups Management" %}{% endblock page_title %}
|
||||
{% block extra_css %}{% endblock extra_css %}
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.nav-tabs>li.active>a {
|
||||
background-color: #ECF0F1 !important;
|
||||
color: #2C3E50;
|
||||
}
|
||||
</style>
|
||||
|
||||
{% endblock extra_css %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<br>
|
||||
{% include 'groupmanagement/menu.html' %}
|
||||
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="active"><a data-toggle="tab" href="#add">{% trans "Group Add Requests" %}</a></li>
|
||||
<li><a data-toggle="tab" href="#leave">{% trans "Group Leave Requests" %}</a></li>
|
||||
<li class="active">
|
||||
<a data-toggle="tab" href="#add">
|
||||
{% trans "Join Requests" %}
|
||||
{% if acceptrequests %}
|
||||
<span class="badge">{{ acceptrequests|length }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="tab" href="#leave">
|
||||
{% trans "Leave Requests" %}
|
||||
{% if leaverequests %}
|
||||
<span class="badge">{{ leaverequests|length }}</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
|
||||
<div id="add" class="tab-pane fade in active panel panel-default">
|
||||
<div class="panel-body">
|
||||
{% if acceptrequests %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th class="text-center">{% trans "RequestID" %}</th>
|
||||
<th class="text-center">{% trans "CharacterName" %}</th>
|
||||
<th class="text-center">{% trans "GroupName" %}</th>
|
||||
<th class="text-center">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
{% for acceptrequest in acceptrequests %}
|
||||
<tr>
|
||||
<td class="text-center">{{ acceptrequest.id }}</td>
|
||||
<td class="text-center">{{ acceptrequest.main_char.character_name }}</td>
|
||||
<td class="text-center">{{ acceptrequest.group.name }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'groupmanagement:accept_request' acceptrequest.id %}" class="btn btn-success">
|
||||
{% trans "Accept" %}
|
||||
</a>
|
||||
<a href="{% url 'groupmanagement:reject_request' acceptrequest.id %}" class="btn btn-danger">
|
||||
{% trans "Reject" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-aa">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-center"></th>
|
||||
<th class="text-center">{% trans "Character" %}</th>
|
||||
<th class="text-center">{% trans "Organization" %}</th>
|
||||
<th class="text-center">{% trans "Group" %}</th>
|
||||
<th class="text-center"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for acceptrequest in acceptrequests %}
|
||||
<tr>
|
||||
<td class="text-right">
|
||||
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="img-circle">
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if acceptrequest.main_char %}
|
||||
<a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank">
|
||||
{{ acceptrequest.main_char.character_name }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ acceptrequest.user.username }}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if acceptrequest.main_char %}
|
||||
<a href="{{ acceptrequest.main_char|dotlan_corporation_url }}" target="_blank">
|
||||
{{ acceptrequest.main_char.corporation_name }}
|
||||
</a><br>
|
||||
{{ acceptrequest.main_char.alliance_name|default_if_none:"" }}
|
||||
{% else %}
|
||||
(unknown)
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">{{ acceptrequest.group.name }}</td>
|
||||
<td class="text-center">
|
||||
<a href="{% url 'groupmanagement:accept_request' acceptrequest.id %}" class="btn btn-success">
|
||||
{% trans "Accept" %}
|
||||
</a>
|
||||
<a href="{% url 'groupmanagement:reject_request' acceptrequest.id %}" class="btn btn-danger">
|
||||
{% trans "Reject" %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">{% trans "No group add requests." %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="leave" class="tab-pane fade panel panel-default">
|
||||
<div class="panel-body">
|
||||
{% if leaverequests %}
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th class="text-center">{% trans "RequestID" %}</th>
|
||||
<th class="text-center">{% trans "CharacterName" %}</th>
|
||||
<th class="text-center">{% trans "GroupName" %}</th>
|
||||
<th class="text-center">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
{% for leaverequest in leaverequests %}
|
||||
<tr>
|
||||
<td class="text-center">{{ leaverequest.id }}</td>
|
||||
<td class="text-center">{{ leaverequest.main_char.character_name }}</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 %}
|
||||
</table>
|
||||
<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>
|
||||
{% endblock content %}
|
||||
|
||||
@@ -3,25 +3,28 @@
|
||||
{% load navactive %}
|
||||
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
|
||||
<span class="sr-only">{% trans "Toggle navigation" %}</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#">{% trans "Group Management" %}</a>
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
|
||||
<span class="sr-only">{% trans "Toggle navigation" %}</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="">{% trans "Group Management" %}</a>
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="{% navactive request 'groupmanagement:management' %}">
|
||||
<a href="{% url 'groupmanagement:management' %}">{% trans "Group Requests" %}</a>
|
||||
</li>
|
||||
<li class="{% renavactive request '^/group/membership/' %}">
|
||||
<a href="{% url 'groupmanagement:membership' %}">{% trans "Group Membership" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="{% navactive request 'auth_group_management' %}">
|
||||
<a href="{% url 'groupmanagement:management' %}">{% trans "Group Requests" %}</a>
|
||||
</li>
|
||||
<li class="{% navactive request 'auth_group_membership auth_group_membership_list' %}">
|
||||
<a href="{% url 'groupmanagement:membership' %}">{% trans "Group Membership" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
11
allianceauth/groupmanagement/tests/__init__.py
Normal file
11
allianceauth/groupmanagement/tests/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from django.urls import reverse
|
||||
|
||||
|
||||
def get_admin_change_view_url(obj: object) -> str:
|
||||
return reverse(
|
||||
'admin:{}_{}_change'.format(
|
||||
obj._meta.app_label,
|
||||
type(obj).__name__.lower()
|
||||
),
|
||||
args=(obj.pk,)
|
||||
)
|
||||
397
allianceauth/groupmanagement/tests/test_admin.py
Normal file
397
allianceauth/groupmanagement/tests/test_admin.py
Normal file
@@ -0,0 +1,397 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.sites import AdminSite
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase, RequestFactory, Client
|
||||
|
||||
from allianceauth.authentication.models import CharacterOwnership, State
|
||||
from allianceauth.eveonline.models import (
|
||||
EveCharacter, EveCorporationInfo, EveAllianceInfo
|
||||
)
|
||||
|
||||
from ..admin import (
|
||||
HasLeaderFilter,
|
||||
GroupAdmin,
|
||||
Group
|
||||
)
|
||||
from . import get_admin_change_view_url
|
||||
|
||||
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
|
||||
_has_auto_groups = True
|
||||
from allianceauth.eveonline.autogroups.models import AutogroupsConfig
|
||||
from ..admin import IsAutoGroupFilter
|
||||
else:
|
||||
_has_auto_groups = False
|
||||
|
||||
|
||||
MODULE_PATH = 'allianceauth.groupmanagement.admin'
|
||||
|
||||
|
||||
class MockRequest(object):
|
||||
|
||||
def __init__(self, user=None):
|
||||
self.user = user
|
||||
|
||||
|
||||
class TestGroupAdmin(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# group 1 - has leader
|
||||
cls.group_1 = Group.objects.create(name='Group 1')
|
||||
cls.group_1.authgroup.description = 'Default Group'
|
||||
cls.group_1.authgroup.internal = False
|
||||
cls.group_1.authgroup.hidden = False
|
||||
cls.group_1.authgroup.save()
|
||||
|
||||
# group 2 - no leader
|
||||
cls.group_2 = Group.objects.create(name='Group 2')
|
||||
cls.group_2.authgroup.description = 'Internal Group'
|
||||
cls.group_2.authgroup.internal = True
|
||||
cls.group_2.authgroup.save()
|
||||
|
||||
# group 3 - has leader
|
||||
cls.group_3 = Group.objects.create(name='Group 3')
|
||||
cls.group_3.authgroup.description = 'Hidden Group'
|
||||
cls.group_3.authgroup.internal = False
|
||||
cls.group_3.authgroup.hidden = True
|
||||
cls.group_3.authgroup.save()
|
||||
|
||||
# group 4 - no leader
|
||||
cls.group_4 = Group.objects.create(name='Group 4')
|
||||
cls.group_4.authgroup.description = 'Open Group'
|
||||
cls.group_4.authgroup.internal = False
|
||||
cls.group_4.authgroup.hidden = False
|
||||
cls.group_4.authgroup.open = True
|
||||
cls.group_4.authgroup.save()
|
||||
|
||||
# group 5 - no leader
|
||||
cls.group_5 = Group.objects.create(name='Group 5')
|
||||
cls.group_5.authgroup.description = 'Public Group'
|
||||
cls.group_5.authgroup.internal = False
|
||||
cls.group_5.authgroup.hidden = False
|
||||
cls.group_5.authgroup.public = True
|
||||
cls.group_5.authgroup.save()
|
||||
|
||||
# group 6 - no leader
|
||||
cls.group_6 = Group.objects.create(name='Group 6')
|
||||
cls.group_6.authgroup.description = 'Mixed Group'
|
||||
cls.group_6.authgroup.internal = False
|
||||
cls.group_6.authgroup.hidden = True
|
||||
cls.group_6.authgroup.open = True
|
||||
cls.group_6.authgroup.public = True
|
||||
cls.group_6.authgroup.save()
|
||||
|
||||
# user 1 - corp and alliance, normal user
|
||||
cls.character_1 = EveCharacter.objects.create(
|
||||
character_id='1001',
|
||||
character_name='Bruce Wayne',
|
||||
corporation_id='2001',
|
||||
corporation_name='Wayne Technologies',
|
||||
corporation_ticker='WT',
|
||||
alliance_id='3001',
|
||||
alliance_name='Wayne Enterprises',
|
||||
alliance_ticker='WE',
|
||||
)
|
||||
cls.character_1a = EveCharacter.objects.create(
|
||||
character_id='1002',
|
||||
character_name='Batman',
|
||||
corporation_id='2001',
|
||||
corporation_name='Wayne Technologies',
|
||||
corporation_ticker='WT',
|
||||
alliance_id='3001',
|
||||
alliance_name='Wayne Enterprises',
|
||||
alliance_ticker='WE',
|
||||
)
|
||||
alliance = EveAllianceInfo.objects.create(
|
||||
alliance_id='3001',
|
||||
alliance_name='Wayne Enterprises',
|
||||
alliance_ticker='WE',
|
||||
executor_corp_id='2001'
|
||||
)
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id='2001',
|
||||
corporation_name='Wayne Technologies',
|
||||
corporation_ticker='WT',
|
||||
member_count=42,
|
||||
alliance=alliance
|
||||
)
|
||||
cls.user_1 = User.objects.create_user(
|
||||
cls.character_1.character_name.replace(' ', '_'),
|
||||
'abc@example.com',
|
||||
'password'
|
||||
)
|
||||
CharacterOwnership.objects.create(
|
||||
character=cls.character_1,
|
||||
owner_hash='x1' + cls.character_1.character_name,
|
||||
user=cls.user_1
|
||||
)
|
||||
CharacterOwnership.objects.create(
|
||||
character=cls.character_1a,
|
||||
owner_hash='x1' + cls.character_1a.character_name,
|
||||
user=cls.user_1
|
||||
)
|
||||
cls.user_1.profile.main_character = cls.character_1
|
||||
cls.user_1.profile.save()
|
||||
cls.user_1.groups.add(cls.group_1)
|
||||
cls.group_1.authgroup.group_leaders.add(cls.user_1)
|
||||
|
||||
# user 2 - corp only, staff
|
||||
cls.character_2 = EveCharacter.objects.create(
|
||||
character_id=1003,
|
||||
character_name='Clark Kent',
|
||||
corporation_id=2002,
|
||||
corporation_name='Daily Planet',
|
||||
corporation_ticker='DP',
|
||||
alliance_id=None
|
||||
)
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id=2002,
|
||||
corporation_name='Daily Plane',
|
||||
corporation_ticker='DP',
|
||||
member_count=99,
|
||||
alliance=None
|
||||
)
|
||||
cls.user_2 = User.objects.create_user(
|
||||
cls.character_2.character_name.replace(' ', '_'),
|
||||
'abc@example.com',
|
||||
'password'
|
||||
)
|
||||
CharacterOwnership.objects.create(
|
||||
character=cls.character_2,
|
||||
owner_hash='x1' + cls.character_2.character_name,
|
||||
user=cls.user_2
|
||||
)
|
||||
cls.user_2.profile.main_character = cls.character_2
|
||||
cls.user_2.profile.save()
|
||||
cls.user_2.groups.add(cls.group_2)
|
||||
cls.user_2.is_staff = True
|
||||
cls.user_2.save()
|
||||
|
||||
# user 3 - no main, no group, superuser
|
||||
cls.character_3 = EveCharacter.objects.create(
|
||||
character_id=1101,
|
||||
character_name='Lex Luthor',
|
||||
corporation_id=2101,
|
||||
corporation_name='Lex Corp',
|
||||
corporation_ticker='LC',
|
||||
alliance_id=None
|
||||
)
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id=2101,
|
||||
corporation_name='Lex Corp',
|
||||
corporation_ticker='LC',
|
||||
member_count=666,
|
||||
alliance=None
|
||||
)
|
||||
EveAllianceInfo.objects.create(
|
||||
alliance_id='3101',
|
||||
alliance_name='Lex World Domination',
|
||||
alliance_ticker='LWD',
|
||||
executor_corp_id=''
|
||||
)
|
||||
cls.user_3 = User.objects.create_user(
|
||||
cls.character_3.character_name.replace(' ', '_'),
|
||||
'abc@example.com',
|
||||
'password'
|
||||
)
|
||||
CharacterOwnership.objects.create(
|
||||
character=cls.character_3,
|
||||
owner_hash='x1' + cls.character_3.character_name,
|
||||
user=cls.user_3
|
||||
)
|
||||
cls.user_3.is_superuser = True
|
||||
cls.user_3.save()
|
||||
cls.user_3.groups.add(cls.group_3)
|
||||
cls.group_3.authgroup.group_leaders.add(cls.user_3)
|
||||
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
self.modeladmin = GroupAdmin(
|
||||
model=Group, admin_site=AdminSite()
|
||||
)
|
||||
|
||||
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_description(self):
|
||||
expected = 'Default Group'
|
||||
result = self.modeladmin._description(self.group_1)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_member_count(self):
|
||||
expected = 1
|
||||
obj = self.modeladmin.get_queryset(MockRequest(user=self.user_1))\
|
||||
.get(pk=self.group_1.pk)
|
||||
result = self.modeladmin._member_count(obj)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_has_leader(self):
|
||||
result = self.modeladmin.has_leader(self.group_1)
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_properties_1(self):
|
||||
expected = ['Default']
|
||||
result = self.modeladmin._properties(self.group_1)
|
||||
self.assertListEqual(result, expected)
|
||||
|
||||
def test_properties_2(self):
|
||||
expected = ['Internal']
|
||||
result = self.modeladmin._properties(self.group_2)
|
||||
self.assertListEqual(result, expected)
|
||||
|
||||
def test_properties_3(self):
|
||||
expected = ['Hidden']
|
||||
result = self.modeladmin._properties(self.group_3)
|
||||
self.assertListEqual(result, expected)
|
||||
|
||||
def test_properties_4(self):
|
||||
expected = ['Open']
|
||||
result = self.modeladmin._properties(self.group_4)
|
||||
self.assertListEqual(result, expected)
|
||||
|
||||
def test_properties_5(self):
|
||||
expected = ['Public']
|
||||
result = self.modeladmin._properties(self.group_5)
|
||||
self.assertListEqual(result, expected)
|
||||
|
||||
def test_properties_6(self):
|
||||
expected = ['Hidden', 'Open', 'Public']
|
||||
result = self.modeladmin._properties(self.group_6)
|
||||
self.assertListEqual(result, expected)
|
||||
|
||||
if _has_auto_groups:
|
||||
@patch(MODULE_PATH + '._has_auto_groups', True)
|
||||
def test_properties_6(self):
|
||||
self._create_autogroups()
|
||||
expected = ['Auto Group']
|
||||
my_group = Group.objects\
|
||||
.filter(managedcorpgroup__isnull=False)\
|
||||
.first()
|
||||
result = self.modeladmin._properties(my_group)
|
||||
self.assertListEqual(result, expected)
|
||||
|
||||
# actions
|
||||
|
||||
# filters
|
||||
|
||||
if _has_auto_groups:
|
||||
@patch(MODULE_PATH + '._has_auto_groups', True)
|
||||
def test_filter_is_auto_group(self):
|
||||
|
||||
class GroupAdminTest(admin.ModelAdmin):
|
||||
list_filter = (IsAutoGroupFilter,)
|
||||
|
||||
self._create_autogroups()
|
||||
my_modeladmin = GroupAdminTest(Group, 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 = [
|
||||
('yes', 'Yes'),
|
||||
('no', 'No'),
|
||||
]
|
||||
self.assertEqual(filterspec.lookup_choices, expected)
|
||||
|
||||
# Make sure the correct queryset is returned - no
|
||||
request = self.factory.get(
|
||||
'/', {'is_auto_group__exact': 'no'}
|
||||
)
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
queryset = changelist.get_queryset(request)
|
||||
expected = [
|
||||
self.group_1,
|
||||
self.group_2,
|
||||
self.group_3,
|
||||
self.group_4,
|
||||
self.group_5,
|
||||
self.group_6
|
||||
]
|
||||
self.assertSetEqual(set(queryset), set(expected))
|
||||
|
||||
# Make sure the correct queryset is returned - yes
|
||||
request = self.factory.get(
|
||||
'/', {'is_auto_group__exact': 'yes'}
|
||||
)
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
queryset = changelist.get_queryset(request)
|
||||
expected = Group.objects.exclude(
|
||||
managedalliancegroup__isnull=True,
|
||||
managedcorpgroup__isnull=True
|
||||
)
|
||||
self.assertSetEqual(set(queryset), set(expected))
|
||||
|
||||
def test_filter_has_leader(self):
|
||||
|
||||
class GroupAdminTest(admin.ModelAdmin):
|
||||
list_filter = (HasLeaderFilter,)
|
||||
|
||||
self._create_autogroups()
|
||||
my_modeladmin = GroupAdminTest(Group, 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 = [
|
||||
('yes', 'Yes'),
|
||||
('no', 'No'),
|
||||
]
|
||||
self.assertEqual(filterspec.lookup_choices, expected)
|
||||
|
||||
# Make sure the correct queryset is returned - no
|
||||
request = self.factory.get(
|
||||
'/', {'has_leader__exact': 'no'}
|
||||
)
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
queryset = changelist.get_queryset(request)
|
||||
expected = Group.objects.exclude(pk__in=[
|
||||
self.group_1.pk, self.group_3.pk
|
||||
])
|
||||
self.assertSetEqual(set(queryset), set(expected))
|
||||
|
||||
# Make sure the correct queryset is returned - yes
|
||||
request = self.factory.get(
|
||||
'/', {'has_leader__exact': 'yes'}
|
||||
)
|
||||
request.user = self.user_1
|
||||
changelist = my_modeladmin.get_changelist_instance(request)
|
||||
queryset = changelist.get_queryset(request)
|
||||
expected = [
|
||||
self.group_1,
|
||||
self.group_3
|
||||
]
|
||||
self.assertSetEqual(set(queryset), set(expected))
|
||||
|
||||
def test_change_view_loads_normally(self):
|
||||
User.objects.create_superuser(
|
||||
username='superuser', password='secret', email='admin@example.com'
|
||||
)
|
||||
c = Client()
|
||||
c.login(username='superuser', password='secret')
|
||||
response = c.get(get_admin_change_view_url(self.group_1))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
337
allianceauth/groupmanagement/tests/test_managers.py
Normal file
337
allianceauth/groupmanagement/tests/test_managers.py
Normal file
@@ -0,0 +1,337 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..models import AuthGroup
|
||||
from ..managers import GroupManager
|
||||
|
||||
|
||||
class MockUserNotAuthenticated():
|
||||
def __init__(self):
|
||||
self.is_authenticated = False
|
||||
|
||||
class GroupManagementVisibilityTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = AuthUtils.create_user('test')
|
||||
AuthUtils.add_main_character(
|
||||
cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST'
|
||||
)
|
||||
cls.user.profile.refresh_from_db()
|
||||
cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2')
|
||||
cls.corp = EveCorporationInfo.objects.create(
|
||||
corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1
|
||||
)
|
||||
cls.group1 = Group.objects.create(name='group1')
|
||||
cls.group2 = Group.objects.create(name='group2')
|
||||
cls.group3 = Group.objects.create(name='group3')
|
||||
|
||||
def setUp(self):
|
||||
self.user.refresh_from_db()
|
||||
|
||||
def _refresh_user(self):
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
|
||||
|
||||
def test_get_group_leaders_groups(self):
|
||||
self.group1.authgroup.group_leaders.add(self.user)
|
||||
self.group2.authgroup.group_leader_groups.add(self.group1)
|
||||
self._refresh_user()
|
||||
groups = GroupManager.get_group_leaders_groups(self.user)
|
||||
|
||||
self.assertIn(self.group1, groups) #avail due to user
|
||||
self.assertNotIn(self.group2, groups) #not avail due to group
|
||||
self.assertNotIn(self.group3, groups) #not avail at all
|
||||
|
||||
self.user.groups.add(self.group1)
|
||||
self._refresh_user()
|
||||
groups = GroupManager.get_group_leaders_groups(self.user)
|
||||
|
||||
|
||||
def test_can_manage_group(self):
|
||||
self.group1.authgroup.group_leaders.add(self.user)
|
||||
self.user.groups.add(self.group1)
|
||||
self._refresh_user()
|
||||
|
||||
self.assertTrue(GroupManager.can_manage_group(self.user, self.group1))
|
||||
self.assertFalse(GroupManager.can_manage_group(self.user, self.group2))
|
||||
self.assertFalse(GroupManager.can_manage_group(self.user, self.group3))
|
||||
|
||||
self.group2.authgroup.group_leader_groups.add(self.group1)
|
||||
self.group1.authgroup.group_leaders.remove(self.user)
|
||||
self._refresh_user()
|
||||
|
||||
self.assertFalse(GroupManager.can_manage_group(self.user, self.group1))
|
||||
self.assertTrue(GroupManager.can_manage_group(self.user, self.group2))
|
||||
self.assertFalse(GroupManager.can_manage_group(self.user, self.group3))
|
||||
|
||||
|
||||
class TestGroupManager(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
# group 1
|
||||
cls.group_default = Group.objects.create(name='default')
|
||||
cls.group_default.authgroup.description = 'Default Group'
|
||||
cls.group_default.authgroup.internal = False
|
||||
cls.group_default.authgroup.hidden = False
|
||||
cls.group_default.authgroup.save()
|
||||
|
||||
# group 2
|
||||
cls.group_internal = Group.objects.create(name='internal')
|
||||
cls.group_internal.authgroup.description = 'Internal Group'
|
||||
cls.group_internal.authgroup.internal = True
|
||||
cls.group_internal.authgroup.save()
|
||||
|
||||
# group 3
|
||||
cls.group_hidden = Group.objects.create(name='hidden')
|
||||
cls.group_hidden.authgroup.description = 'Hidden Group'
|
||||
cls.group_hidden.authgroup.internal = False
|
||||
cls.group_hidden.authgroup.hidden = True
|
||||
cls.group_hidden.authgroup.save()
|
||||
|
||||
# group 4
|
||||
cls.group_open = Group.objects.create(name='open')
|
||||
cls.group_open.authgroup.description = 'Open Group'
|
||||
cls.group_open.authgroup.internal = False
|
||||
cls.group_open.authgroup.hidden = False
|
||||
cls.group_open.authgroup.open = True
|
||||
cls.group_open.authgroup.save()
|
||||
|
||||
# group 5
|
||||
cls.group_public_1 = Group.objects.create(name='public 1')
|
||||
cls.group_public_1.authgroup.description = 'Public Group 1'
|
||||
cls.group_public_1.authgroup.internal = False
|
||||
cls.group_public_1.authgroup.hidden = False
|
||||
cls.group_public_1.authgroup.public = True
|
||||
cls.group_public_1.authgroup.save()
|
||||
|
||||
# group 6
|
||||
cls.group_public_2 = Group.objects.create(name='public 2')
|
||||
cls.group_public_2.authgroup.description = 'Public Group 2'
|
||||
cls.group_public_2.authgroup.internal = False
|
||||
cls.group_public_2.authgroup.hidden = True
|
||||
cls.group_public_2.authgroup.open = True
|
||||
cls.group_public_2.authgroup.public = True
|
||||
cls.group_public_2.authgroup.save()
|
||||
|
||||
# group 7
|
||||
cls.group_default_member = Group.objects.create(name='default members')
|
||||
cls.group_default_member.authgroup.description = \
|
||||
'Default Group for members only'
|
||||
cls.group_default_member.authgroup.internal = False
|
||||
cls.group_default_member.authgroup.hidden = False
|
||||
cls.group_default_member.authgroup.open = False
|
||||
cls.group_default_member.authgroup.public = False
|
||||
cls.group_default_member.authgroup.states.add(
|
||||
AuthUtils.get_member_state()
|
||||
)
|
||||
cls.group_default_member.authgroup.save()
|
||||
|
||||
def setUp(self):
|
||||
self.user = AuthUtils.create_user('Bruce Wayne')
|
||||
|
||||
def test_get_joinable_group_member(self):
|
||||
result = GroupManager.get_joinable_groups(
|
||||
AuthUtils.get_member_state()
|
||||
)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2,
|
||||
self.group_default_member
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_get_joinable_group_guest(self):
|
||||
result = GroupManager.get_joinable_groups(
|
||||
AuthUtils.get_guest_state()
|
||||
)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_joinable_group_member(self):
|
||||
member_state = AuthUtils.get_member_state()
|
||||
for x in [
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2,
|
||||
self.group_default_member
|
||||
]:
|
||||
self.assertTrue(GroupManager.joinable_group(x, member_state))
|
||||
|
||||
for x in [
|
||||
self.group_internal,
|
||||
]:
|
||||
self.assertFalse(GroupManager.joinable_group(x, member_state))
|
||||
|
||||
|
||||
def test_joinable_group_guest(self):
|
||||
guest_state = AuthUtils.get_guest_state()
|
||||
for x in [
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2
|
||||
]:
|
||||
self.assertTrue(GroupManager.joinable_group(x, guest_state))
|
||||
|
||||
for x in [
|
||||
self.group_internal,
|
||||
self.group_default_member
|
||||
]:
|
||||
self.assertFalse(GroupManager.joinable_group(x, guest_state))
|
||||
|
||||
|
||||
def test_get_all_non_internal_groups(self):
|
||||
result = GroupManager.get_all_non_internal_groups()
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2,
|
||||
self.group_default_member
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_check_internal_group(self):
|
||||
self.assertTrue(
|
||||
GroupManager.check_internal_group(self.group_default)
|
||||
)
|
||||
self.assertFalse(
|
||||
GroupManager.check_internal_group(self.group_internal)
|
||||
)
|
||||
|
||||
def test_get_joinable_groups_for_user_no_permission(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_guest_state())
|
||||
result = GroupManager.get_joinable_groups_for_user(self.user)
|
||||
expected= {self.group_public_1, self.group_public_2}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_get_joinable_groups_for_user_guest_w_permission_(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_guest_state())
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'groupmanagement.request_groups', self.user
|
||||
)
|
||||
result = GroupManager.get_joinable_groups_for_user(self.user)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_get_joinable_groups_for_user_member_w_permission(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_member_state(), True)
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'groupmanagement.request_groups', self.user
|
||||
)
|
||||
result = GroupManager.get_joinable_groups_for_user(self.user)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_hidden,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_public_2,
|
||||
self.group_default_member
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_get_joinable_groups_for_user_member_w_permission_no_hidden(self):
|
||||
AuthUtils.assign_state(self.user, AuthUtils.get_member_state(), True)
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'groupmanagement.request_groups', self.user
|
||||
)
|
||||
result = GroupManager.get_joinable_groups_for_user(
|
||||
self.user, include_hidden=False
|
||||
)
|
||||
expected = {
|
||||
self.group_default,
|
||||
self.group_open,
|
||||
self.group_public_1,
|
||||
self.group_default_member
|
||||
}
|
||||
self.assertSetEqual(set(result), expected)
|
||||
|
||||
def test_has_management_permission(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.group_management', user
|
||||
)
|
||||
self.assertTrue(GroupManager.has_management_permission(user))
|
||||
|
||||
def test_can_manage_groups_no_perm_no_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.assertFalse(GroupManager.can_manage_groups(user))
|
||||
|
||||
def test_can_manage_groups_user_not_authenticated(self):
|
||||
user = MockUserNotAuthenticated()
|
||||
self.assertFalse(GroupManager.can_manage_groups(user))
|
||||
|
||||
def test_can_manage_groups_has_perm(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.group_management', user
|
||||
)
|
||||
self.assertTrue(GroupManager.can_manage_groups(user))
|
||||
|
||||
def test_can_manage_groups_no_perm_leads_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.group_default.authgroup.group_leaders.add(user)
|
||||
self.assertTrue(GroupManager.can_manage_groups(user))
|
||||
|
||||
def test_can_manage_group_no_perm_no_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.assertFalse(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
|
||||
def test_can_manage_group_has_perm(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_permission_to_user_by_name(
|
||||
'auth.group_management', user
|
||||
)
|
||||
self.assertTrue(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
|
||||
def test_can_manage_group_no_perm_leads_correct_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.group_default.authgroup.group_leaders.add(user)
|
||||
self.assertTrue(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
|
||||
def test_can_manage_group_no_perm_leads_other_group(self):
|
||||
user = AuthUtils.create_user('Clark Kent')
|
||||
self.group_hidden.authgroup.group_leaders.add(user)
|
||||
self.assertFalse(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
|
||||
def test_can_manage_group_user_not_authenticated(self):
|
||||
user = MockUserNotAuthenticated()
|
||||
self.assertFalse(
|
||||
GroupManager.can_manage_group(user, self.group_default)
|
||||
)
|
||||
167
allianceauth/groupmanagement/tests/test_models.py
Normal file
167
allianceauth/groupmanagement/tests/test_models.py
Normal file
@@ -0,0 +1,167 @@
|
||||
from unittest import mock
|
||||
|
||||
from django.contrib.auth.models import User, Group
|
||||
from django.test import TestCase
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from allianceauth.eveonline.models import (
|
||||
EveCorporationInfo, EveAllianceInfo, EveCharacter
|
||||
)
|
||||
|
||||
from ..models import GroupRequest, RequestLog
|
||||
|
||||
|
||||
def create_testdata():
|
||||
# clear DB
|
||||
User.objects.all().delete()
|
||||
Group.objects.all().delete()
|
||||
EveCharacter.objects.all().delete()
|
||||
EveCorporationInfo.objects.all().delete()
|
||||
EveAllianceInfo.objects.all().delete()
|
||||
|
||||
# group 1
|
||||
group = Group.objects.create(name='Superheros')
|
||||
group.authgroup.description = 'Default Group'
|
||||
group.authgroup.internal = False
|
||||
group.authgroup.hidden = False
|
||||
group.authgroup.save()
|
||||
|
||||
# user 1
|
||||
user_1 = AuthUtils.create_user('Bruce Wayne')
|
||||
AuthUtils.add_main_character_2(
|
||||
user_1,
|
||||
name='Bruce Wayne',
|
||||
character_id=1001,
|
||||
corp_id=2001,
|
||||
corp_name='Wayne Technologies'
|
||||
)
|
||||
user_1.groups.add(group)
|
||||
group.authgroup.group_leaders.add(user_1)
|
||||
|
||||
# user 2
|
||||
user_2 = AuthUtils.create_user('Clark Kent')
|
||||
AuthUtils.add_main_character_2(
|
||||
user_2,
|
||||
name='Clark Kent',
|
||||
character_id=1002,
|
||||
corp_id=2002,
|
||||
corp_name='Wayne Technologies'
|
||||
)
|
||||
return group, user_1, user_2
|
||||
|
||||
|
||||
|
||||
class TestGroupRequest(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.group, cls.user_1, _ = create_testdata()
|
||||
|
||||
def test_main_char(self):
|
||||
group_request = GroupRequest.objects.create(
|
||||
status='Pending',
|
||||
user=self.user_1,
|
||||
group=self.group
|
||||
)
|
||||
expected = self.user_1.profile.main_character
|
||||
self.assertEqual(group_request.main_char, expected)
|
||||
|
||||
def test_str(self):
|
||||
group_request = GroupRequest.objects.create(
|
||||
status='Pending',
|
||||
user=self.user_1,
|
||||
group=self.group
|
||||
)
|
||||
expected = 'Bruce Wayne:Superheros'
|
||||
self.assertEqual(str(group_request), expected)
|
||||
|
||||
|
||||
class TestRequestLog(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.group, cls.user_1, cls.user_2 = create_testdata()
|
||||
|
||||
def test_requestor(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1
|
||||
)
|
||||
expected = 'Clark Kent'
|
||||
self.assertEqual(request_log.requestor(), expected)
|
||||
|
||||
def test_type_to_str_removed(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
request_type=None,
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1
|
||||
)
|
||||
expected = 'Removed'
|
||||
self.assertEqual(request_log.type_to_str(), expected)
|
||||
|
||||
def test_type_to_str_leave(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
request_type=True,
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1
|
||||
)
|
||||
expected = 'Leave'
|
||||
self.assertEqual(request_log.type_to_str(), expected)
|
||||
|
||||
def test_type_to_str_join(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
request_type=False,
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1
|
||||
)
|
||||
expected = 'Join'
|
||||
self.assertEqual(request_log.type_to_str(), expected)
|
||||
|
||||
def test_action_to_str_accept(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1,
|
||||
action = True
|
||||
)
|
||||
expected = 'Accept'
|
||||
self.assertEqual(request_log.action_to_str(), expected)
|
||||
|
||||
def test_action_to_str_reject(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1,
|
||||
action = False
|
||||
)
|
||||
expected = 'Reject'
|
||||
self.assertEqual(request_log.action_to_str(), expected)
|
||||
|
||||
def test_req_char(self):
|
||||
request_log = RequestLog.objects.create(
|
||||
group=self.group,
|
||||
request_info='Clark Kent:Superheros',
|
||||
request_actor=self.user_1,
|
||||
action = False
|
||||
)
|
||||
expected = self.user_2.profile.main_character
|
||||
self.assertEqual(request_log.req_char(), expected)
|
||||
|
||||
|
||||
class TestAuthGroup(TestCase):
|
||||
|
||||
def test_str(self):
|
||||
group = Group.objects.create(name='Superheros')
|
||||
group.authgroup.description = 'Default Group'
|
||||
group.authgroup.internal = False
|
||||
group.authgroup.hidden = False
|
||||
group.authgroup.save()
|
||||
|
||||
expected = 'Superheros'
|
||||
self.assertEqual(str(group.authgroup), expected)
|
||||
61
allianceauth/groupmanagement/tests/test_signals.py
Normal file
61
allianceauth/groupmanagement/tests/test_signals.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from unittest import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import User, Group
|
||||
|
||||
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
|
||||
from ..signals import check_groups_on_state_change
|
||||
|
||||
|
||||
class GroupManagementStateTestCase(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = AuthUtils.create_user('test')
|
||||
AuthUtils.add_main_character(
|
||||
cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST'
|
||||
)
|
||||
cls.user.profile.refresh_from_db()
|
||||
cls.alliance = EveAllianceInfo.objects.create(
|
||||
alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2'
|
||||
)
|
||||
cls.corp = EveCorporationInfo.objects.create(
|
||||
corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1
|
||||
)
|
||||
cls.state_group = Group.objects.create(name='state_group')
|
||||
cls.open_group = Group.objects.create(name='open_group')
|
||||
cls.state = AuthUtils.create_state('test state', 500)
|
||||
cls.state_group.authgroup.states.add(cls.state)
|
||||
cls.state_group.authgroup.internal = False
|
||||
cls.state_group.save()
|
||||
|
||||
def setUp(self):
|
||||
self.user.refresh_from_db()
|
||||
self.state.refresh_from_db()
|
||||
|
||||
def _refresh_user(self):
|
||||
self.user = User.objects.get(pk=self.user.pk)
|
||||
|
||||
def _refresh_test_group(self):
|
||||
self.state_group = Group.objects.get(pk=self.state_group.pk)
|
||||
|
||||
def test_drop_state_group(self):
|
||||
self.user.groups.add(self.open_group)
|
||||
self.user.groups.add(self.state_group)
|
||||
self.assertEqual(self.user.profile.state.name, "Guest")
|
||||
|
||||
self.state.member_corporations.add(self.corp)
|
||||
self._refresh_user()
|
||||
self.assertEqual(self.user.profile.state, self.state)
|
||||
groups = self.user.groups.all()
|
||||
self.assertIn(self.state_group, groups) #keeps group
|
||||
self.assertIn(self.open_group, groups) #public group unafected
|
||||
|
||||
self.state.member_corporations.clear()
|
||||
self._refresh_user()
|
||||
self.assertEqual(self.user.profile.state.name, "Guest")
|
||||
groups = self.user.groups.all()
|
||||
self.assertNotIn(self.state_group, groups) #looses group
|
||||
self.assertIn(self.open_group, groups) #public group unafected
|
||||
22
allianceauth/groupmanagement/tests/test_views.py
Normal file
22
allianceauth/groupmanagement/tests/test_views.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
from django.test import RequestFactory, TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from allianceauth.tests.auth_utils import AuthUtils
|
||||
from esi.models import Token
|
||||
|
||||
from .. import views
|
||||
|
||||
|
||||
class TestViews(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.factory = RequestFactory()
|
||||
self.user = AuthUtils.create_user('Bruce Wayne')
|
||||
|
||||
def test_groups_view_can_load(self):
|
||||
request = self.factory.get(reverse('groupmanagement:groups'))
|
||||
request.user = self.user
|
||||
response = views.groups_view(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -1,21 +1,21 @@
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
from django.contrib.auth.models import Group
|
||||
from django.core.exceptions import ObjectDoesNotExist, PermissionDenied
|
||||
from django.core.paginator import Paginator, EmptyPage
|
||||
from django.db.models import Count
|
||||
from django.http import Http404
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from .managers import GroupManager
|
||||
from .models import GroupRequest, RequestLog
|
||||
|
||||
from allianceauth.notifications import notify
|
||||
|
||||
from django.conf import settings
|
||||
from .managers import GroupManager
|
||||
from .models import GroupRequest, RequestLog
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -27,13 +27,14 @@ def group_management(request):
|
||||
acceptrequests = []
|
||||
leaverequests = []
|
||||
|
||||
base_group_query = GroupRequest.objects.select_related('user', 'group')
|
||||
base_group_query = GroupRequest.objects.select_related('user', 'group', 'user__profile__main_character')
|
||||
if GroupManager.has_management_permission(request.user):
|
||||
# Full access
|
||||
group_requests = base_group_query.all()
|
||||
else:
|
||||
# Group specific leader
|
||||
group_requests = base_group_query.filter(group__authgroup__group_leaders__in=[request.user])
|
||||
users__groups = GroupManager.get_group_leaders_groups(request.user)
|
||||
group_requests = base_group_query.filter(group__in=users__groups)
|
||||
|
||||
for grouprequest in group_requests:
|
||||
if grouprequest.leave_request:
|
||||
@@ -74,7 +75,6 @@ def group_membership_audit(request, group_id):
|
||||
logger.debug("group_management_audit called by user %s" % request.user)
|
||||
group = get_object_or_404(Group, id=group_id)
|
||||
try:
|
||||
|
||||
# Check its a joinable group i.e. not corp or internal
|
||||
# And the user has permission to manage it
|
||||
if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
|
||||
@@ -91,37 +91,50 @@ def group_membership_audit(request, group_id):
|
||||
return render(request, 'groupmanagement/audit.html', context=render_items)
|
||||
|
||||
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(GroupManager.can_manage_groups)
|
||||
def group_membership_list(request, group_id):
|
||||
logger.debug("group_membership_list called by user %s for group id %s" % (request.user, group_id))
|
||||
logger.debug(
|
||||
"group_membership_list called by user %s "
|
||||
"for group id %s" % (request.user, group_id)
|
||||
)
|
||||
group = get_object_or_404(Group, id=group_id)
|
||||
try:
|
||||
|
||||
# Check its a joinable group i.e. not corp or internal
|
||||
# And the user has permission to manage it
|
||||
if not GroupManager.check_internal_group(group) or not GroupManager.can_manage_group(request.user, group):
|
||||
logger.warning("User %s attempted to view the membership of group %s but permission was denied" %
|
||||
(request.user, group_id))
|
||||
if (not GroupManager.check_internal_group(group)
|
||||
or not GroupManager.can_manage_group(request.user, group)
|
||||
):
|
||||
logger.warning(
|
||||
"User %s attempted to view the membership of group %s "
|
||||
"but permission was denied" % (request.user, group_id)
|
||||
)
|
||||
raise PermissionDenied
|
||||
|
||||
except ObjectDoesNotExist:
|
||||
raise Http404("Group does not exist")
|
||||
|
||||
group_leaders = group.authgroup.group_leaders.all()
|
||||
members = list()
|
||||
|
||||
for member in group.user_set.select_related('profile').all().order_by('username'):
|
||||
for member in \
|
||||
group.user_set\
|
||||
.all()\
|
||||
.select_related('profile', 'profile__main_character')\
|
||||
.order_by('profile__main_character__character_name'):
|
||||
|
||||
members.append({
|
||||
'user': member,
|
||||
'main_char': member.profile.main_character
|
||||
'main_char': member.profile.main_character,
|
||||
'is_leader': member in group_leaders
|
||||
})
|
||||
|
||||
render_items = {'group': group, 'members': members}
|
||||
|
||||
return render(request, 'groupmanagement/groupmembers.html', context=render_items)
|
||||
return render(
|
||||
request, 'groupmanagement/groupmembers.html',
|
||||
context=render_items
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -219,7 +232,7 @@ def group_reject_request(request, group_request_id):
|
||||
raise p
|
||||
except:
|
||||
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to %(group)s.') % {"mainchar": group_request.main_char, "group": group_request.group})
|
||||
logger.exception("Unhandled exception occured while user %s attempting to reject group request id %s" % (
|
||||
logger.exception("Unhandled exception occurred while user %s attempting to reject group request id %s" % (
|
||||
request.user, group_request_id))
|
||||
pass
|
||||
|
||||
@@ -253,9 +266,9 @@ def group_leave_accept_request(request, group_request_id):
|
||||
(request.user, group_request_id))
|
||||
raise p
|
||||
except:
|
||||
messages.error(request, _('An unhandled error occured while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||
"mainchar": group_request.main_char, "group": group_request.group})
|
||||
logger.exception("Unhandled exception occured while user %s attempting to accept group leave request id %s" % (
|
||||
logger.exception("Unhandled exception occurred while user %s attempting to accept group leave request id %s" % (
|
||||
request.user, group_request_id))
|
||||
pass
|
||||
|
||||
@@ -287,9 +300,9 @@ def group_leave_reject_request(request, group_request_id):
|
||||
(request.user, group_request_id))
|
||||
raise p
|
||||
except:
|
||||
messages.error(request, _('An unhandled error occured while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||
messages.error(request, _('An unhandled error occurred while processing the application from %(mainchar)s to leave %(group)s.') % {
|
||||
"mainchar": group_request.main_char, "group": group_request.group})
|
||||
logger.exception("Unhandled exception occured while user %s attempting to reject group leave request id %s" % (
|
||||
logger.exception("Unhandled exception occurred while user %s attempting to reject group leave request id %s" % (
|
||||
request.user, group_request_id))
|
||||
pass
|
||||
|
||||
@@ -299,24 +312,23 @@ def group_leave_reject_request(request, group_request_id):
|
||||
@login_required
|
||||
def groups_view(request):
|
||||
logger.debug("groups_view called by user %s" % request.user)
|
||||
|
||||
groups_qs = GroupManager.get_joinable_groups_for_user(
|
||||
request.user, include_hidden=False
|
||||
)
|
||||
groups_qs = groups_qs.order_by('name')
|
||||
groups = []
|
||||
for group in groups_qs:
|
||||
group_request = GroupRequest.objects\
|
||||
.filter(user=request.user)\
|
||||
.filter(group=group)
|
||||
groups.append({
|
||||
'group': group,
|
||||
'request': group_request[0] if group_request else None
|
||||
})
|
||||
|
||||
group_query = GroupManager.get_joinable_groups(request.user.profile.state)
|
||||
|
||||
if not request.user.has_perm('groupmanagement.request_groups'):
|
||||
# Filter down to public groups only for non-members
|
||||
group_query = group_query.filter(authgroup__public=True)
|
||||
logger.debug("Not a member, only public groups will be available")
|
||||
|
||||
for group in group_query:
|
||||
# Exclude hidden
|
||||
if not group.authgroup.hidden:
|
||||
group_request = GroupRequest.objects.filter(user=request.user).filter(group=group)
|
||||
|
||||
groups.append({'group': group, 'request': group_request[0] if group_request else None})
|
||||
|
||||
render_items = {'groups': groups}
|
||||
return render(request, 'groupmanagement/groups.html', context=render_items)
|
||||
context = {'groups': groups}
|
||||
return render(request, 'groupmanagement/groups.html', context=context)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -333,13 +345,13 @@ def group_request_add(request, group_id):
|
||||
# User is already a member of this group.
|
||||
logger.warning("User %s attempted to join group id %s but they are already a member." %
|
||||
(request.user, group_id))
|
||||
messages.warning(request, "You are already a member of that group.")
|
||||
messages.warning(request, _("You are already a member of that group."))
|
||||
return redirect('groupmanagement:groups')
|
||||
if not request.user.has_perm('groupmanagement.request_groups') and not group.authgroup.public:
|
||||
# Does not have the required permission, trying to join a non-public group
|
||||
logger.warning("User %s attempted to join group id %s but it is not a public group" %
|
||||
(request.user, group_id))
|
||||
messages.warning(request, "You cannot join that group")
|
||||
messages.warning(request, _("You cannot join that group"))
|
||||
return redirect('groupmanagement:groups')
|
||||
if group.authgroup.open:
|
||||
logger.info("%s joining %s as is an open group" % (request.user, group))
|
||||
@@ -348,7 +360,7 @@ def group_request_add(request, group_id):
|
||||
req = GroupRequest.objects.filter(user=request.user, group=group)
|
||||
if len(req) > 0:
|
||||
logger.info("%s attempted to join %s but already has an open application" % (request.user, group))
|
||||
messages.warning(request, "You already have a pending application for that group.")
|
||||
messages.warning(request, _("You already have a pending application for that group."))
|
||||
return redirect("groupmanagement:groups")
|
||||
grouprequest = GroupRequest()
|
||||
grouprequest.status = _('Pending')
|
||||
@@ -382,7 +394,7 @@ def group_request_leave(request, group_id):
|
||||
req = GroupRequest.objects.filter(user=request.user, group=group)
|
||||
if len(req) > 0:
|
||||
logger.info("%s attempted to leave %s but already has an pending leave request." % (request.user, group))
|
||||
messages.warning(request, "You already have a pending leave request for that group.")
|
||||
messages.warning(request, _("You already have a pending leave request for that group."))
|
||||
return redirect("groupmanagement:groups")
|
||||
if getattr(settings, 'AUTO_LEAVE', False):
|
||||
logger.info("%s leaving joinable group %s due to auto_leave" % (request.user, group))
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<tr>
|
||||
<td class="text-center">
|
||||
<img class="ra-avatar img-responsive img-circle"
|
||||
src="https://image.eveonline.com/Character/{{ char.character_id }}_32.jpg">
|
||||
src="{{ char.portrait_url_32 }}">
|
||||
</td>
|
||||
<td class="text-center">{{ char.character_name }}</td>
|
||||
<td class="text-center">{{ char.corporation_name }}</td>
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
allianceauth/locale/en/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/en/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2016
allianceauth/locale/en/LC_MESSAGES/django.po
Normal file
2016
allianceauth/locale/en/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
allianceauth/locale/ko_KR/LC_MESSAGES/django.mo
Normal file
BIN
allianceauth/locale/ko_KR/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
2023
allianceauth/locale/ko_KR/LC_MESSAGES/django.po
Normal file
2023
allianceauth/locale/ko_KR/LC_MESSAGES/django.po
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user