Compare commits

...

68 Commits

Author SHA1 Message Date
Ariel Rin
564a25e578 Version Bump v2.5.1 2020-02-07 10:42:50 +00:00
Ariel Rin
f50b08d301 Merge branch 'fix_esi_duplicated_model_warning' into 'master'
Fix esi duplicate model warnings

See merge request allianceauth/allianceauth!1159
2020-02-07 05:01:30 +00:00
Ariel Rin
26566d9ce0 Merge branch 'issue_1200' into 'master'
Fix activation link shown trunctated in some mail clients

Closes #1200

See merge request allianceauth/allianceauth!1160
2020-02-07 04:58:52 +00:00
Ariel Rin
f42d438be2 Merge branch 'callback_redir_fix' into 'master'
HOTFIX: Callback redirect fix

See merge request allianceauth/allianceauth!1161
2020-02-07 04:55:19 +00:00
Ariel Rin
1fbc39b614 Merge branch 'django_min' into 'master'
HOTFIX: Bump Django minimum to work with AA migrations

See merge request allianceauth/allianceauth!1163
2020-02-07 04:50:39 +00:00
Aaron Kable
36af471c3c passthrough the redirect url 2020-02-07 04:50:31 +00:00
Ariel Rin
a5e86c9a36 Merge branch 'py38' into 'master'
Support and Test against Python3.8

See merge request allianceauth/allianceauth!1162
2020-02-07 04:49:17 +00:00
Ariel Rin
4b27dd40b9 Support and Test against Python3.8 2020-02-07 04:49:17 +00:00
AaronKable
ff0aac9d8a Bump Django minimum to work with AA migrations 2020-02-05 21:05:17 +08:00
Col Crunch
bf24c8253e Version Bump v2.5.0 2020-02-04 23:37:47 -05:00
colcrunch
fd92352302 Merge branch 'feature_grouprequest_infos' into 'master'
Add evelinks package and improve group management UI

See merge request allianceauth/allianceauth!1156
2020-02-05 04:31:13 +00:00
Erik Kalkoken
fcb66a11a3 Improve image tags to also work with eveonline objects, use new tags in group managenet, optimer, timerboard 2020-02-05 04:31:13 +00:00
Col Crunch
63d2021a73 Fix migration conflicts. 2020-02-04 23:23:04 -05:00
Col Crunch
d110d9c74e Merge branch 'fix_groupmanagement_requestlog_date'
Closes !1157
2020-02-04 23:15:30 -05:00
Col Crunch
157bf81dcb Change file name to keep it sequential, and add a descriptive title. 2020-02-04 23:14:32 -05:00
Col Crunch
1beb1b1b4f Merge branch 'group-purge' into 'master'
Closes !1147
2020-02-04 23:02:08 -05:00
colcrunch
13f523679c Merge branch 'group_leads' into 'master'
Add option to add groups as group leaders

Closes #1062

See merge request allianceauth/allianceauth!1146
2020-02-05 03:22:57 +00:00
Aaron Kable
ed816d9aea Add option to add groups as group leaders 2020-02-05 03:22:57 +00:00
colcrunch
ebfb51eed5 Merge branch 'eveonline_tests_1' into 'master'
Add more tests to eveonline module

Closes #1206

See merge request allianceauth/allianceauth!1151
2020-02-04 04:44:16 +00:00
Erik Kalkoken
0127b1ea9e Add more tests to eveonline module 2020-02-04 04:44:16 +00:00
colcrunch
61f41a1459 Merge branch 'nicer_readme' into 'master'
Improve README

See merge request allianceauth/allianceauth!1153
2020-02-04 04:43:26 +00:00
ErikKalkoken
d3fbc133a2 Fix issue #1200 2020-02-03 16:08:33 +01:00
ErikKalkoken
ce7a8e7a3d Replace swagger spec file in eveonline package with current version 2020-02-02 20:28:53 +01:00
ErikKalkoken
2b45610080 Fix date field in groupmanagement / RequestLog 2020-01-30 14:16:55 +01:00
AaronKable
5b4dd6731c tests for the test god 2020-01-28 16:22:43 +08:00
ErikKalkoken
7b1bf9a4e2 Improve readme and add missing version info to setup 2020-01-24 20:05:55 +01:00
Col Crunch
337c4d9ce5 Version Bump to v2.4.0 2020-01-22 19:46:20 -05:00
colcrunch
9afc36a009 Merge branch 'corpstat_fix' into 'master'
Increase Corpstats Performance

See merge request allianceauth/allianceauth!1138
2020-01-23 00:38:39 +00:00
Aaron Kable
ebff1387c1 Increase Corpstats Performance 2020-01-23 00:38:39 +00:00
colcrunch
801502ec77 Merge branch 'celery_update' into 'master'
Update Dependencies

Closes #1175

See merge request allianceauth/allianceauth!1141
2020-01-23 00:34:35 +00:00
Aaron Kable
c07f59201e Update Dependencies 2020-01-23 00:34:34 +00:00
Col Crunch
98b799d821 Version Bump 2.3.0 2020-01-16 15:28:07 -05:00
Col Crunch
02714956d8 Add CI stage to deploy to pypi on tags. 2020-01-15 23:16:02 -05:00
colcrunch
4d435d58c5 Merge branch 'affiliations' into 'master'
Affiliations for Character updates

See merge request allianceauth/allianceauth!1140
2020-01-16 04:00:58 +00:00
Aaron Kable
1c2fd3be50 Affiliations for Character updates 2020-01-16 04:00:55 +00:00
colcrunch
6222439e21 Merge branch 'fleetup-removal' into 'master'
Fleetup removal

Closes #1179

See merge request allianceauth/allianceauth!1144
2020-01-16 03:54:18 +00:00
colcrunch
46d46ac90b Fleetup removal 2020-01-16 03:54:18 +00:00
colcrunch
a5fe61eb15 Merge branch 'issue-1172' into 'master'
Fix issue #1172: Replace deprecated eve image URLs in apps and services

Closes #1172

See merge request allianceauth/allianceauth!1145
2020-01-16 03:51:16 +00:00
Erik Kalkoken
0bfec36983 Fix issue #1172: Replace deprecated eve image URLs in apps and services 2020-01-16 03:51:16 +00:00
colcrunch
11607ecf24 Merge branch 'tests_eveonline_providers' into 'master'
Add unit tests for eveonline providers and fix coverage counting

See merge request allianceauth/allianceauth!1150
2020-01-16 03:49:16 +00:00
Erik Kalkoken
9970e5535b Add unit tests for eveonline providers and fix coverage counting 2020-01-16 03:49:16 +00:00
Basraah
99492e9c34 Merge branch 'issue-1198' into 'master'
Re-enable automatic testing against all Python versions

Closes #1198

See merge request allianceauth/allianceauth!1149
2020-01-10 06:55:19 +00:00
Erik Kalkoken
1d6ecffb3b Re-enable automatic testing against all Python versions 2020-01-10 06:55:19 +00:00
Aaron Kable
8c33349dcb Purge groups not available to member on state change 2020-01-07 02:41:18 +00:00
Basraah
cfb2c55a4b Merge branch 'issue-1177' into 'master'
Fix issue #1177: Add project description and classifiers to PyPI page

Closes #1177

See merge request allianceauth/allianceauth!1143
2019-12-13 02:14:54 +00:00
ErikKalkoken
e24d29f1d3 Fix issue #1177 2019-12-05 13:02:31 +01:00
Basraah
debd6ef2b9 Version bump to v2.2.2 2019-12-05 02:37:22 +00:00
Basraah
58e9c21e4f Merge branch 'issue-1176' into 'master'
Fix issue #1176: Prevent Django 3 installation

Closes #1176

See merge request allianceauth/allianceauth!1142
2019-12-05 02:36:03 +00:00
ErikKalkoken
c7c3083e3e Fix issue #1176: Prevent Django 3 installation 2019-12-04 14:02:03 +01:00
Basraah
63d061e9f2 Merge branch 'bug-1165' into 'master'
add fix and unittest for issue #1165

See merge request allianceauth/allianceauth!1137
2019-09-12 09:53:05 +00:00
ErikKalkoken
1887bdb90a add fix and unittest for issue #1165 2019-09-08 23:09:20 +02:00
Basraah
69addb068a Merge branch 'patch-3' into 'master'
Add discord warning for tasks

See merge request allianceauth/allianceauth!1136
2019-08-30 03:22:49 +00:00
Aaron Kable
a62c3ce0f9 Add discord warning for tasks 2019-08-30 03:22:49 +00:00
Basraah
aecc94bdb3 Version bump to v2.2.1 2019-08-25 03:20:45 +00:00
Basraah
fcb7f2905a Merge branch 'url_group' into 'master'
urlize group descriptions to hyperlink if required

See merge request allianceauth/allianceauth!1132
2019-08-24 22:48:57 +00:00
Basraah
34c7169ca3 Merge branch 'local_settings_update' into 'master'
Enable utf8mb4 charset option in local.py by default

See merge request allianceauth/allianceauth!1131
2019-08-24 22:47:25 +00:00
Basraah
6e450061f4 Merge branch 'group-rework' into 'master'
Make group joins a bit clearer

See merge request allianceauth/allianceauth!1134
2019-08-24 22:46:46 +00:00
Basraah
fc3d7e9f43 Merge branch 'auto-group' into 'master'
Fix Autogroups, Add Autogroups to admin

Closes #1087

See merge request allianceauth/allianceauth!1133
2019-08-24 22:45:25 +00:00
Aaron Kable
514db4f9a2 Make group joins a bit clearer 2019-08-23 03:54:24 -04:00
Aaron Kable
23a8b65ce2 Fix Autogroups, Add Autogroups to admin 2019-08-22 20:37:55 -04:00
Aaron Kable
f8e6662bc8 urlize group descriptions to hyperlink if required 2019-07-26 00:35:59 -04:00
Col Crunch
89be2456fb Update local.py to have the utf8mb4 charset option enabled in the database settings. This brings the settings inline with the documentation update. 2019-06-30 22:58:13 -04:00
Basraah
bfd3451717 Merge branch 'db_doc' into 'master'
Use utf8mb4 When Creating Auth DB

See merge request allianceauth/allianceauth!1130
2019-06-20 00:49:30 +00:00
Col Crunch
0b759d6a32 Use utf8mb4 when creating auth db rather than utf8 2019-06-19 16:27:18 -04:00
Basraah
65e05084e6 Merge branch 'master' into 'master'
Show "Help" link only to superuser

See merge request allianceauth/allianceauth!1129
2019-06-17 00:56:28 +00:00
Peter Pfeufer
f25a4ed386 Show "Help" lnk only to superuser 2019-06-15 23:44:36 +02:00
Basraah
b2a1d41829 Merge branch 'patch-1' into 'master'
Add Ariel Rin and Col Crunch as developers to readme

See merge request allianceauth/allianceauth!1128
2019-05-29 09:21:51 +00:00
Ariel Rin
3570ce86d7 Add Ariel Rin and Col Crunch as developers to readme 2019-03-17 05:39:43 +00:00
86 changed files with 3269 additions and 1897 deletions

View File

@@ -8,6 +8,8 @@ omit =
*/example/*
*/project_template/*
*/bin/*
*/tests/*
*/tests.py
[report]
exclude_lines =

3
.gitignore vendored
View File

@@ -64,5 +64,8 @@ celerybeat-schedule
.idea/*
/nbproject/
#VSCode
.vscode/
#gitlab configs
.gitlab/

View File

@@ -1,33 +1,41 @@
# 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:
- python -V
- pip install wheel tox
py36-dj20:
<<: *job_definition
test-3.5:
image: python:3.5-buster
script:
- tox -e py35
test-3.6:
image: python:3.6-buster
script:
- tox -e py36
test-3.7:
image: python:3.7-buster
script:
- tox -e py37
test-3.8:
image: python:3.8-buster
script:
- tox -e py38
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/*
only:
- tags

View File

@@ -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

View File

@@ -1,35 +1,84 @@
Alliance Auth
============
# Alliance Auth
[![Chat on Discord](https://img.shields.io/discord/399006117012832262.svg)](https://discord.gg/fjnHAmk)
[![Documentation Status](https://readthedocs.org/projects/allianceauth/badge/?version=latest)](http://allianceauth.readthedocs.io/?badge=latest)
[![license](https://img.shields.io/badge/license-GPLv2-green)](https://pypi.org/project/allianceauth/)
[![python](https://img.shields.io/pypi/pyversions/allianceauth)](https://pypi.org/project/allianceauth/)
[![django](https://img.shields.io/pypi/djversions/allianceauth?label=django)](https://pypi.org/project/allianceauth/)
[![version](https://img.shields.io/pypi/v/allianceauth?label=release)](https://pypi.org/project/allianceauth/)
[![pipeline status](https://gitlab.com/allianceauth/allianceauth/badges/master/pipeline.svg)](https://gitlab.com/allianceauth/allianceauth/commits/master)
[![Documentation Status](https://readthedocs.org/projects/allianceauth/badge/?version=latest)](http://allianceauth.readthedocs.io/?badge=latest)
[![coverage report](https://gitlab.com/allianceauth/allianceauth/badges/master/coverage.svg)](https://gitlab.com/allianceauth/allianceauth/commits/master)
[![Chat on Discord](https://img.shields.io/discord/399006117012832262.svg)](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)
## Contens
[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)
- [Devloper Team](#developer-team)
- [Contributing](#contributing)
## Overview
Active Developers:
Alliance Auth (AA) is a web application that helps Eve Online organizations efficiently manage access to their applications and services.
- [Adarnof](https://gitlab.com/adarnof/)
- [Basraah](https://gitlab.com/basraah/)
Main features:
Beta Testers / Bug Fixers:
- Automatically grants or revokes user access to external applications / services (e.g. Discord, Mumble) and web apps (e.g. SRP requests) based on the user's current membership to [in-game organizations](https://allianceauth.readthedocs.io/en/latest/features/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/groups/)
- [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/installation/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others
- Includes a set of web apps called ["plug-in apps"](https://allianceauth.readthedocs.io/en/latest/features/) which add many useful functions: fleet schedule, timer board, SRP request management, fleet activity tracker and character application management
- Can be easily extended with new services and plugin-apps. Many additional services and plugin-apps are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [offical documentation](http://allianceauth.rtfd.io).
## Screenshot
Here is an example of the Alliance Auth web site with some plug-ins apps and services enabled:
![screenshot](https://i.imgur.com/2tnX9kD.png)
## 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.

View File

@@ -1,6 +1,6 @@
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
__version__ = '2.2.0'
__version__ = '2.5.1'
NAME = 'Alliance Auth v%s' % __version__
default_app_config = 'allianceauth.apps.AllianceAuthConfig'

View File

@@ -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)
]

View File

@@ -20,8 +20,8 @@
<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 class="text-center">
<img class="ra-avatar"src="{{ main.portrait_url_128 }}">
</td>
</tr>
<tr>
@@ -32,8 +32,8 @@
<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 class="text-center">
<img class="ra-avatar"src="{{ main.corporation_logo_url_128 }}">
</td>
</tr>
<tr>
@@ -45,8 +45,8 @@
{% 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 class="text-center">
<img class="ra-avatar"src="{{ main.alliance_logo_url_128 }}">
</td>
</tr>
<tr>

View File

@@ -3,7 +3,7 @@
{% block page_title %}Login{% endblock %}
{% block middle_box_content %}
<p style="text-align:center">
<a href="{% url 'auth_sso_login' %}">
<a href="{% url 'auth_sso_login' %}{% if request.GET.next %}?next={{request.GET.next}}{%endif%}">
<img src="{% static 'img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" border=0>
</a>
</p>

View File

@@ -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 }}

View File

@@ -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)

View File

@@ -29,9 +29,9 @@
<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 }}
@@ -54,14 +54,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 +80,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 +119,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>

View File

@@ -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)

View File

@@ -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,49 @@ 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.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,
})

View File

@@ -1,6 +1,6 @@
from django.contrib import admin
from django.db import models
from .models import AutogroupsConfig
from .models import AutogroupsConfig, ManagedCorpGroup, ManagedAllianceGroup
import logging
@@ -37,3 +37,6 @@ class AutogroupsConfigAdmin(admin.ModelAdmin):
admin.site.register(AutogroupsConfig, AutogroupsConfigAdmin)
admin.site.register(ManagedCorpGroup)
admin.site.register(ManagedAllianceGroup)

View File

@@ -179,15 +179,13 @@ class AutogroupsConfig(models.Model):
@transaction.atomic
def create_alliance_group(self, alliance: EveAllianceInfo) -> Group:
group, created = Group.objects.get_or_create(name=self.get_alliance_group_name(alliance))
if created:
ManagedAllianceGroup.objects.create(group=group, config=self, alliance=alliance)
ManagedAllianceGroup.objects.get_or_create(group=group, config=self, alliance=alliance)
return group
@transaction.atomic
def create_corp_group(self, corp: EveCorporationInfo) -> Group:
group, created = Group.objects.get_or_create(name=self.get_corp_group_name(corp))
if created:
ManagedCorpGroup.objects.create(group=group, config=self, corp=corp)
ManagedCorpGroup.objects.get_or_create(group=group, config=self, corp=corp)
return group
def delete_alliance_managed_groups(self):
@@ -240,6 +238,8 @@ class ManagedGroup(models.Model):
class Meta:
abstract = True
def __str__(self):
return "Managed Group: %s" % self.group.name
class ManagedCorpGroup(ManagedGroup):
corp = models.ForeignKey(EveCorporationInfo, on_delete=models.CASCADE)

View File

@@ -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')

View File

@@ -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)

View 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"

View File

@@ -0,0 +1,52 @@
# this module generates profile URLs for dotlan
from urllib.parse import urljoin, quote
from . import *
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)

View File

@@ -0,0 +1,44 @@
# this module generates profile URLs for evewho
from urllib.parse import urljoin, quote
from . import *
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)

View File

@@ -0,0 +1,92 @@
from django.test import TestCase
from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from .. import dotlan, zkillboard, evewho
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/'
)

View File

@@ -0,0 +1,334 @@
from django.test import TestCase
from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from .. import dotlan, zkillboard, evewho
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),
''
)

View File

@@ -0,0 +1,57 @@
# this module generates profile URLs for zKillboard
from urllib.parse import urljoin, quote
from . import *
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)

View File

@@ -7,6 +7,90 @@ from .managers import EveAllianceManager, EveAllianceProviderManager
from . import providers
EVE_IMAGE_SERVER_URL = 'https://images.evetech.net'
def _eve_entity_image_url(
category: str,
id: int,
size: int = 32,
variant: str = None,
tenant: str = None,
) -> str:
"""returns image URL for an Eve Online ID.
Supported categories: `alliance`, `corporation`, `character`
Arguments:
- category: category of the ID
- id: Eve ID of the entity
- size: (optional) render size of the image.must be between 32 (default) and 1024
- variant: (optional) image variant for category. currently not relevant.
- tentant: (optional) Eve Server, either `tranquility`(default) or `singularity`
Returns:
- URL string for the requested image on the Eve image server
Exceptions:
- Throws ValueError on invalid input
"""
# input validations
categories = {
'alliance': {
'endpoint': 'alliances',
'variants': [
'logo'
]
},
'corporation': {
'endpoint': 'corporations',
'variants': [
'logo'
]
},
'character': {
'endpoint': 'characters',
'variants': [
'portrait'
]
}
}
tenants = ['tranquility', 'singularity']
if size < 32 or size > 1024 or (size & (size - 1) != 0):
raise ValueError('Invalid size: {}'.format(size))
if category not in categories:
raise ValueError('Invalid category {}'.format(category))
else:
endpoint = categories[category]['endpoint']
if variant:
if variant not in categories[category]['variants']:
raise ValueError('Invalid variant {} for category {}'.format(
variant,
category
))
else:
variant = categories[category]['variants'][0]
if tenant and tenant not in tenants:
raise ValueError('Invalid tentant {}'.format(tenant))
# compose result URL
result = '{}/{}/{}/{}?size={}'.format(
EVE_IMAGE_SERVER_URL,
endpoint,
id,
variant,
size
)
if tenant:
result += '&tenant={}'.format(tenant)
return result
class EveAllianceInfo(models.Model):
alliance_id = models.CharField(max_length=254, unique=True)
alliance_name = models.CharField(max_length=254, unique=True)
@@ -35,14 +119,34 @@ 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 = 32) -> str:
"""image URL for the given alliance ID"""
return _eve_entity_image_url('alliance', alliance_id, size)
def logo_url(self, size:int = 32) -> 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):
@@ -69,14 +173,34 @@ 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 = 32) -> str:
"""image URL for the given corporation ID"""
return _eve_entity_image_url('corporation', 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 = 32) -> 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 +252,82 @@ 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 = 32) -> str:
"""image URL for the given character ID"""
return _eve_entity_image_url('character', 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 = 32) -> 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 = 32) -> 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 = 32) -> 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)

View File

@@ -12,6 +12,7 @@ get_alliances_alliance_id_corporations
get_corporations_corporation_id
get_characters_character_id
get_universe_types_type_id
post_character_affiliation
"""
@@ -189,11 +190,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

View File

@@ -0,0 +1,286 @@
# 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 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 ''

View 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 %}

View File

@@ -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
@@ -58,7 +68,12 @@ class EveCharacterManagerTestCase(TestCase):
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
@@ -73,6 +88,7 @@ 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',
@@ -83,11 +99,15 @@ class EveCharacterManagerTestCase(TestCase):
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 +130,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 +157,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 +189,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 +216,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 +236,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',
@@ -208,8 +276,14 @@ class EveCorporationManagerTestCase(TestCase):
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

View File

@@ -1,8 +1,104 @@
from unittest.mock import Mock, patch
from django.test import TestCase
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from ..models import EveCharacter, EveCorporationInfo, \
EveAllianceInfo, _eve_entity_image_url
from ..providers import Alliance, Corporation, Character
class EveUniverseImageUrlTestCase(TestCase):
"""unit test for _eve_entity_image_url()"""
def test_sizes(self):
self.assertEqual(
_eve_entity_image_url('character', 42),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
_eve_entity_image_url('character', 42, size=32),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
_eve_entity_image_url('character', 42, size=64),
'https://images.evetech.net/characters/42/portrait?size=64'
)
self.assertEqual(
_eve_entity_image_url('character', 42, size=128),
'https://images.evetech.net/characters/42/portrait?size=128'
)
self.assertEqual(
_eve_entity_image_url('character', 42, size=256),
'https://images.evetech.net/characters/42/portrait?size=256'
)
self.assertEqual(
_eve_entity_image_url('character', 42, size=512),
'https://images.evetech.net/characters/42/portrait?size=512'
)
self.assertEqual(
_eve_entity_image_url('character', 42, size=1024),
'https://images.evetech.net/characters/42/portrait?size=1024'
)
with self.assertRaises(ValueError):
_eve_entity_image_url('corporation', 42, size=-5)
with self.assertRaises(ValueError):
_eve_entity_image_url('corporation', 42, size=0)
with self.assertRaises(ValueError):
_eve_entity_image_url('corporation', 42, size=31)
with self.assertRaises(ValueError):
_eve_entity_image_url('corporation', 42, size=1025)
with self.assertRaises(ValueError):
_eve_entity_image_url('corporation', 42, size=2048)
def test_variant(self):
self.assertEqual(
_eve_entity_image_url('character', 42, variant='portrait'),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
_eve_entity_image_url('alliance', 42, variant='logo'),
'https://images.evetech.net/alliances/42/logo?size=32'
)
with self.assertRaises(ValueError):
_eve_entity_image_url('character', 42, variant='logo')
def test_alliance(self):
self.assertEqual(
_eve_entity_image_url('alliance', 42),
'https://images.evetech.net/alliances/42/logo?size=32'
)
self.assertEqual(
_eve_entity_image_url('corporation', 42),
'https://images.evetech.net/corporations/42/logo?size=32'
)
self.assertEqual(
_eve_entity_image_url('character', 42),
'https://images.evetech.net/characters/42/portrait?size=32'
)
with self.assertRaises(ValueError):
_eve_entity_image_url('station', 42)
def test_tenants(self):
self.assertEqual(
_eve_entity_image_url('character', 42, tenant='tranquility'),
'https://images.evetech.net/characters/42/portrait?size=32&tenant=tranquility'
)
self.assertEqual(
_eve_entity_image_url('character', 42, tenant='singularity'),
'https://images.evetech.net/characters/42/portrait?size=32&tenant=singularity'
)
with self.assertRaises(ValueError):
_eve_entity_image_url('character', 42, tenant='xxx')
class EveCharacterTestCase(TestCase):
def test_corporation_prop(self):
"""
@@ -119,3 +215,410 @@ class EveCharacterTestCase(TestCase):
)
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),
_eve_entity_image_url('character', 42)
)
self.assertEqual(
EveCharacter.generic_portrait_url(42, 256),
_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(),
_eve_entity_image_url('character', 42)
)
self.assertEqual(
x.portrait_url(64),
_eve_entity_image_url('character', 42, size=64)
)
self.assertEqual(
x.portrait_url_32,
_eve_entity_image_url('character', 42, size=32)
)
self.assertEqual(
x.portrait_url_64,
_eve_entity_image_url('character', 42, size=64)
)
self.assertEqual(
x.portrait_url_128,
_eve_entity_image_url('character', 42, size=128)
)
self.assertEqual(
x.portrait_url_256,
_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(),
_eve_entity_image_url('corporation', 123)
)
self.assertEqual(
x.corporation_logo_url(256),
_eve_entity_image_url('corporation', 123, size=256)
)
self.assertEqual(
x.corporation_logo_url_32,
_eve_entity_image_url('corporation', 123, size=32)
)
self.assertEqual(
x.corporation_logo_url_64,
_eve_entity_image_url('corporation', 123, size=64)
)
self.assertEqual(
x.corporation_logo_url_128,
_eve_entity_image_url('corporation', 123, size=128)
)
self.assertEqual(
x.corporation_logo_url_256,
_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(),
_eve_entity_image_url('alliance', 987)
)
self.assertEqual(
x.alliance_logo_url(128),
_eve_entity_image_url('alliance', 987, size=128)
)
self.assertEqual(
x.alliance_logo_url_32,
_eve_entity_image_url('alliance', 987, size=32)
)
self.assertEqual(
x.alliance_logo_url_64,
_eve_entity_image_url('alliance', 987, size=64)
)
self.assertEqual(
x.alliance_logo_url_128,
_eve_entity_image_url('alliance', 987, size=128)
)
self.assertEqual(
x.alliance_logo_url_256,
_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()
updated_alliance = 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),
_eve_entity_image_url('alliance', 42)
)
self.assertEqual(
EveAllianceInfo.generic_logo_url(42, 256),
_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),
_eve_entity_image_url('corporation', 42)
)
self.assertEqual(
EveCorporationInfo.generic_logo_url(42, 256),
_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'
)

View File

@@ -0,0 +1,545 @@
from unittest.mock import Mock, patch
from bravado.exception import HTTPNotFound, HTTPUnprocessableEntity
from django.test import TestCase
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from ..providers import ObjectNotFound, Entity, Character, Corporation, \
Alliance, ItemType, EveProvider, EveSwaggerProvider
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('allianceauth.eveonline.providers.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('allianceauth.eveonline.providers.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('allianceauth.eveonline.providers.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('allianceauth.eveonline.providers.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('allianceauth.eveonline.providers.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('allianceauth.eveonline.providers.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('allianceauth.eveonline.providers.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('allianceauth.eveonline.providers.EveSwaggerProvider.get_alliance')
@patch('allianceauth.eveonline.providers.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('allianceauth.eveonline.providers.esi_client_factory')
def test_str(self, mock_esi_client_factory):
my_provider = EveSwaggerProvider()
self.assertEqual(str(my_provider), 'esi')
@patch('allianceauth.eveonline.providers.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('allianceauth.eveonline.providers.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('allianceauth.eveonline.providers.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('allianceauth.eveonline.providers.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)

View File

@@ -0,0 +1,110 @@
from unittest.mock import patch, Mock
from django.test import TestCase
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from ..tasks import update_alliance, update_corp, update_character, \
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='character.corp.ticker',
alliance_id='character.alliance.id',
alliance_name='character.alliance.name',
)
run_model_update()
self.assertEqual(mock_update_corp.delay.call_count, 1)
self.assertEqual(
int(mock_update_corp.delay.call_args[0][0]),
2345
)
self.assertEqual(mock_update_alliance.delay.call_count, 1)
self.assertEqual(
int(mock_update_alliance.delay.call_args[0][0]),
3456
)
self.assertEqual(mock_update_character.delay.call_count, 1)
self.assertEqual(
int(mock_update_character.delay.call_args[0][0]),
1234
)

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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.'))

View File

@@ -1 +0,0 @@
default_app_config = 'allianceauth.fleetup.apps.FleetupConfig'

View File

@@ -1,6 +0,0 @@
from django.apps import AppConfig
class FleetupConfig(AppConfig):
name = 'allianceauth.fleetup'
label = 'fleetup'

View File

@@ -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/')

View File

@@ -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": {}}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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 %}

View File

@@ -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>&nbsp;<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 %}

View File

@@ -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>

View File

@@ -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)

View File

@@ -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'),
]

View File

@@ -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)

View File

@@ -4,12 +4,12 @@ from django.db.models.signals import pre_save, post_save, pre_delete, post_delet
from django.dispatch import receiver
from .models import AuthGroup
from .models import GroupRequest
from . import signals
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 = ''
@@ -65,4 +65,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)

View File

@@ -17,7 +17,8 @@ class GroupManager:
@staticmethod
def get_group_leaders_groups(user):
return Group.objects.select_related('authgroup').filter(authgroup__group_leaders__in=[user])
return Group.objects.select_related('authgroup').filter(authgroup__group_leaders__in=[user]) | \
Group.objects.select_related('authgroup').filter(authgroup__group_leader_groups__in=user.groups.all())
@staticmethod
def joinable_group(group, state):
@@ -66,7 +67,7 @@ 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

View 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'),
)
]

View File

@@ -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),
),
]

View File

@@ -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 "

View 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)

View File

@@ -8,43 +8,47 @@
<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> 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>
</div>
</div>
{% endblock %}

View File

@@ -1,6 +1,7 @@
{% 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 %}
@@ -9,35 +10,62 @@
<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">
<div id="list" class="">
{% if group.user_set %}
<table class="table">
<tr>
<th class="text-center">{% trans "Leader" %}</th>
<th class="text-center">{% trans "Portrait" %}</th>
<th class="text-center">{% trans "Character" %}</th>
<th class="text-center">{% trans "Corporation" %}</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">
{% if member.is_leader %}
<i class="fa fa-star"></i>
{% endif %}
</td>
<td class="text-center">
<img src="{{ member.main_char|character_portrait_url:32 }}" class="img-circle">
</td>
<td class="text-center">
<a href="{{ member.main_char|evewho_character_url }}" target="_blank">
{{ member.main_char.character_name }}
</a>
</td>
<td class="text-center">
<a href="{{ member.main_char|dotlan_corporation_url }}" target="_blank">
{{ member.main_char.corporation_name }}
</a>
</td>
<td class="text-center">
<a href="{{ member.main_char|dotlan_alliance_url }}" target="_blank">
{{ member.main_char.alliance_name|default_if_none:"" }}
</a>
</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>
</div>
</div>
</div>
{% endblock content %}

View File

@@ -9,48 +9,52 @@
<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">
Groups
</div>
<div class="panel-body">
{% 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 "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>
</div>
</div>
{% endblock content %}

View File

@@ -19,7 +19,7 @@ url
{% for g in groups %}
<tr>
<td class="text-center">{{ g.group.name }}</td>
<td class="text-center">{{ g.group.authgroup.description }}</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 %}
@@ -32,9 +32,15 @@ url
</button>
{% endif %}
{% elif not g.request %}
<a href="{% url 'groupmanagement:request_add' g.group.id %}" class="btn btn-success">
{% trans "Request" %}
</a>
{% 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 }}

View File

@@ -1,33 +1,65 @@
{% 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>
</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>
<th class="text-center">{% trans "#" %}</th>
<th class="text-center">{% trans "Portrait" %}</th>
<th class="text-center">{% trans "Character" %}</th>
<th class="text-center">{% trans "Corporation" %}</th>
<th class="text-center">{% trans "Alliance" %}</th>
<th class="text-center">{% trans "Group" %}</th>
<th class="text-center"></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">
<img src="{{ acceptrequest.main_char|character_portrait_url:32 }}" class="img-circle">
</td>
<td class="text-center">
<a href="{{ acceptrequest.main_char|evewho_character_url }}" target="_blank">
{{ acceptrequest.main_char.character_name }}
</a>
</td>
<td class="text-center">
<a href="{{ acceptrequest.main_char|dotlan_corporation_url }}" target="_blank">
{{ acceptrequest.main_char.corporation_name }}
</a>
</td>
<td class="text-center">
<a href="{{ acceptrequest.main_char|dotlan_alliance_url }}" target="_blank">
{{ acceptrequest.main_char.alliance_name|default_if_none:"" }}
</a>
</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">
@@ -45,20 +77,41 @@
{% 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>
<th class="text-center">{% trans "#" %}</th>
<th class="text-center">{% trans "Portrait" %}</th>
<th class="text-center">{% trans "Character" %}</th>
<th class="text-center">{% trans "Corporation" %}</th>
<th class="text-center">{% trans "Alliance" %}</th>
<th class="text-center">{% trans "Group" %}</th>
<th class="text-center"></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">
<img src="{{ leaverequest.main_char|character_portrait_url:32 }}" class="img-circle">
</td>
<td class="text-center">
<a href="{{ leaverequest.main_char|evewho_character_url }}" target="_blank">
{{ leaverequest.main_char.character_name }}
</a>
</td>
<td class="text-center">
<a href="{{ leaverequest.main_char|dotlan_corporation_url }}" target="_blank">
{{ leaverequest.main_char.corporation_name }}
</a>
</td>
<td class="text-center">
<a href="{{ leaverequest.main_char|dotlan_alliance_url }}" target="_blank">
{{ leaverequest.main_char.alliance_name|default_if_none:"" }}
</a>
</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">
@@ -76,6 +129,7 @@
{% endif %}
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@@ -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>

View File

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

View File

@@ -96,32 +96,47 @@ def group_membership_audit(request, group_id):
@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')\
.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

View File

@@ -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>

View File

@@ -1,4 +1,5 @@
{% load i18n %}
{% load evelinks %}
{% block content %}
<table class="table">
@@ -23,7 +24,7 @@
<td class="text-center">{{ ops.operation_name }}</td>
<td class="text-center">{{ ops.doctrine }}</td>
<td class="text-center">
<a href="http://evemaps.dotlan.net/system/{{ ops.system }}">{{ ops.system }}</a>
<a href="{{ ops.system|dotlan_solar_system_url }}">{{ ops.system }}</a>
</td>
<td class="text-center" nowrap>{{ ops.start | date:"Y-m-d H:i" }}</td>
<td class="text-center" nowrap><div id="localtime{{ ops.id }}"></div><div id="countdown{{ ops.id }}"></div></td>

View File

@@ -30,6 +30,7 @@ DATABASES['default'] = {
'PASSWORD': '',
'HOST': '127.0.0.1',
'PORT': '3306',
'OPTIONS': {'charset': 'utf8mb4'},
}
# Register an application at https://developers.eveonline.com for Authentication

View File

@@ -12,7 +12,6 @@ from hashlib import md5
logger = logging.getLogger(__name__)
DISCORD_URL = "https://discordapp.com/api"
EVE_IMAGE_SERVER = "https://image.eveonline.com"
AUTH_URL = "https://discordapp.com/api/oauth2/authorize"
TOKEN_URL = "https://discordapp.com/api/oauth2/token"

View File

@@ -83,7 +83,7 @@ def discourse_sso(request):
}
if main_char:
params['avatar_url'] = 'https://image.eveonline.com/Character/%s_256.jpg' % main_char.character_id
params['avatar_url'] = main_char.portrait_url(256)
return_payload = base64.encodestring(urlencode(params).encode('utf-8'))
h = hmac.new(key, return_payload, digestmod=hashlib.sha256)

View File

@@ -25,7 +25,7 @@ def activate_jabber(request):
logger.debug("Adding jabber user for user %s with main character %s" % (request.user, character))
info = OpenfireManager.add_user(OpenfireTasks.get_username(request.user))
# If our username is blank means we already had a user
if info[0] is not "":
if info[0] != "":
OpenfireUser.objects.update_or_create(user=request.user, defaults={'username': info[0]})
logger.debug("Updated authserviceinfo for user %s with jabber credentials. Updating groups." % request.user)
OpenfireTasks.update_groups.delay(request.user.pk)

View File

@@ -6,6 +6,7 @@ from datetime import datetime
from passlib.apps import phpbb3_context
from django.db import connections
from allianceauth.eveonline.models import EveCharacter
import logging
@@ -58,7 +59,7 @@ class Phpbb3Manager:
@staticmethod
def __add_avatar(username, characterid):
logger.debug("Adding EVE character id %s portrait as phpbb avater for user %s" % (characterid, username))
avatar_url = "https://image.eveonline.com/Character/" + characterid + "_64.jpg"
avatar_url = EveCharacter.generic_portrait_url(characterid, 64)
cursor = connections['phpbb3'].cursor()
userid = Phpbb3Manager.__get_user_id(username)
cursor.execute(Phpbb3Manager.SQL_ADD_USER_AVATAR, [avatar_url, userid])

View File

@@ -8,6 +8,7 @@ import re
from django.db import connections
from django.conf import settings
from allianceauth.eveonline.models import EveCharacter
logger = logging.getLogger(__name__)
@@ -101,8 +102,8 @@ class SmfManager:
@classmethod
def add_avatar(cls, member_name, characterid):
logger.debug("Adding EVE character id %s portrait as smf avatar for user %s" % (characterid, member_name))
avatar_url = "https://image.eveonline.com/Character/" + characterid + "_64.jpg"
logger.debug("Adding EVE character id %s portrait as smf avatar for user %s" % (characterid, member_name))
avatar_url = EveCharacter.generic_portrait_url(characterid, 64)
cursor = connections['smf'].cursor()
id_member = cls.get_user_id(member_name)
cursor.execute(cls.SQL_ADD_USER_AVATAR, [avatar_url, id_member])

View File

@@ -199,7 +199,7 @@ class Teamspeak3Manager:
'tokendescription': username_clean,
'tokencustomset': "ident=sso_uid value=%s" % username_clean})
except TeamspeakError as e:
logger.error("Failed to add teamspeak user %s: %s" % (username, str(e)))
logger.error("Failed to add teamspeak user %s: %s" % (username_clean, str(e)))
return "",""
try:

View File

@@ -12,6 +12,10 @@ from .models import Teamspeak3User, AuthTS, TSgroup, StateGroup
from .tasks import Teamspeak3Tasks
from .signals import m2m_changed_authts_group, post_save_authts, post_delete_authts
from .manager import Teamspeak3Manager
from .util.ts3 import TeamspeakError
from allianceauth.authentication.models import State
MODULE_PATH = 'allianceauth.services.modules.teamspeak3'
DEFAULT_AUTH_GROUP = 'Member'
@@ -290,3 +294,31 @@ class Teamspeak3SignalsTestCase(TestCase):
self.member.profile.save()
self.assertTrue(update_groups.called)
class Teamspeak3ManagerTestCase(TestCase):
@staticmethod
def my_side_effect(*args, **kwargs):
raise TeamspeakError(1)
@mock.patch.object(Teamspeak3Manager, '_group_list')
@mock.patch.object(Teamspeak3Manager, '_group_id_by_name')
def test_add_user_exception(self, _group_id_by_name, _group_list):
"""test 1st exception occuring in add_user()"""
# set mocks in Teamspeak3Manager class
_group_list.return_value = ['Member', 'Guest']
_group_id_by_name.return_value = 99
manager = Teamspeak3Manager()
server = mock.MagicMock()
server._connected.return_value = True
server.send_command = mock.Mock(side_effect=Teamspeak3ManagerTestCase.my_side_effect)
manager._server = server
# create test data
user = User.objects.create_user("dummy")
user.profile.state = State.objects.filter(name="Member").first()
# perform test
manager.add_user(user, "Dummy User")

View File

@@ -25,7 +25,7 @@ def activate_teamspeak3(request):
result = ts3man.add_user(request.user, Teamspeak3Tasks.get_username(request.user))
# if its empty we failed
if result[0] is not "":
if result[0] != "":
Teamspeak3User.objects.update_or_create(user=request.user, defaults={'uid': result[0], 'perm_key': result[1]})
logger.debug("Updated authserviceinfo for user %s with TS3 credentials. Updating groups." % request.user)
logger.info("Successfully activated TS3 for user %s" % request.user)

View File

@@ -27,12 +27,14 @@
{% menu_items %}
<li>
<a class="{% navactive request 'authentication:help' %}"
href="{% url 'authentication:help' %}">
<i class="fa fa-question fa-fw"></i> {% trans "Help" %}
</a>
</li>
{% if user.is_superuser %}
<li>
<a class="{% navactive request 'authentication:help' %}"
href="{% url 'authentication:help' %}">
<i class="fa fa-question fa-fw"></i> {% trans "Help" %}
</a>
</li>
{% endif %}
</ul>
</div>
</div>

View File

@@ -2,6 +2,7 @@
{% load staticfiles %}
{% load i18n %}
{% get_current_language as LANGUAGE_CODE %}
{% load evelinks %}
{% block page_title %}{% trans "Structure Timer Management" %}{% endblock page_title %}
{% block extra_css %}{% endblock extra_css %}
@@ -61,7 +62,7 @@
{% endif %}
</td>
<td class="text-center"><a
href="http://evemaps.dotlan.net/system/{{ timer.system }}">{{ timer.system }} {{ timer.planet_moon }} </a>
href="{{ timer.system|dotlan_solar_system_url }}">{{ timer.system }} {{ timer.planet_moon }} </a>
</td>
<td class="text-center">
{% if timer.structure == "POCO" %}
@@ -220,7 +221,7 @@
{% endif %}
</td>
<td class="text-center">
<a href="http://evemaps.dotlan.net/system/{{ timer.system }}">{{ timer.system }} {{ timer.planet_moon }}</a>
<a href="{{ timer.system|dotlan_solar_system_url }}">{{ timer.system }} {{ timer.planet_moon }}</a>
</td>
<td class="text-center">
{% if timer.structure == "POCO" %}
@@ -381,7 +382,7 @@
{% endif %}
</td>
<td class="text-center">
<a href="http://evemaps.dotlan.net/system/{{ timer.system }}">{{ timer.system }} {{ timer.planet_moon }}</a>
<a href="{{ timer.system|dotlan_solar_system_url }}">{{ timer.system }} {{ timer.planet_moon }}</a>
</td>
<td class="text-center">
{% if timer.structure == "POCO" %}

View File

@@ -1,18 +0,0 @@
# Fleetup
## Installation
Add `'allianceauth.fleetup',` to your auth project's `INSTALLED_APPS` list.
Additional settings are required. Append the following settings to the end of your auth project's settings file and fill them out.
FLEETUP_APP_KEY = '' # The app key from http://fleet-up.com/Api/MyApps
FLEETUP_USER_ID = '' # The user id from http://fleet-up.com/Api/MyKeys
FLEETUP_API_ID = '' # The API id from http://fleet-up.com/Api/MyKeys
FLEETUP_GROUP_ID = '' # The id of the group you want to pull data from, see http://fleet-up.com/Api/Endpoints#groups_mygroupmemberships
Once filled out restart Gunicorn and Celery.
## Permissions
The Fleetup module is only visible to users with the `auth | user | view_fleeup` permission.

View File

@@ -19,7 +19,7 @@ Alliance Auth can be installed on any operating system. Dependencies are provide
### Python
Alliance Auth requires python3.4 or higher. Ensure it is installed on your server before proceeding.
Alliance Auth requires python3.5 or higher. Ensure it is installed on your server before proceeding.
Ubuntu:
@@ -71,7 +71,7 @@ CentOS:
Alliance Auth needs a MySQL user account and database. Open an SQL shell with `mysql -u root -p` and create them as follows, replacing `PASSWORD` with an actual secure password:
CREATE USER 'allianceserver'@'localhost' IDENTIFIED BY 'PASSWORD';
CREATE DATABASE alliance_auth CHARACTER SET utf8;
CREATE DATABASE alliance_auth CHARACTER SET utf8mb4;
GRANT ALL PRIVILEGES ON alliance_auth . * TO 'allianceserver'@'localhost';
Add timezone tables to your mysql installation:

View File

@@ -6,6 +6,11 @@ Discord is very popular amongst ad-hoc small groups and larger organizations see
## Setup
```eval_rst
.. warning::
Do not run the `discord.update_*` periodic tasks on a regular schedule, doing so can cause your discord service to stop syncing completely.
```
### Prepare Your Settings File
In your auth project's settings file, do the following:
- Add `'allianceauth.services.modules.discord',` to your `INSTALLED_APPS` list

View File

@@ -1,33 +1,37 @@
# -*- coding: utf-8 -*-
import os
from setuptools import setup
import allianceauth
this_directory = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(this_directory, 'README.md'), encoding='utf-8') as f:
long_description = f.read()
install_requires = [
'mysqlclient',
'dnspython',
'passlib',
'requests>=2.9.1',
'requests>=2.9.1,<3.0.0',
'bcrypt',
'python-slugify>=1.2',
'requests-oauthlib',
'semantic_version',
'redis<=2.10.6',
'celery>=4.0.2,<4.3.0',
'redis>=3.3.1,<4.0.0',
'celery>=4.3.0,<5.0.0',
'celery_once',
'django>=2.0',
'django>=2.2.1,<3.0',
'django-bootstrap-form',
'django-registration==2.4',
'django-sortedm2m',
'django-redis-cache==1.8.1',
'django-celery-beat<=1.1.1',
'django-redis-cache>=2.1.0,<3.0.0',
'django-celery-beat>=1.1.1,<2.0.0',
'openfire-restapi',
'sleekxmpp',
'adarnauth-esi>=1.4.10,<2.0',
'kombu<=4.3.0',
'django-esi>=1.5.0,<2.0'
]
testing_extras = [
@@ -43,12 +47,13 @@ setup(
author='Alliance Auth',
author_email='adarnof@gmail.com',
description='An auth system for EVE Online to help in-game organizations manage online service access.',
long_description=long_description,
long_description_content_type='text/markdown',
install_requires=install_requires,
extras_require={
'testing': testing_extras,
':python_version=="3.4"': ['typing'],
'testing': testing_extras
},
python_requires='~=3.4',
python_requires='~=3.5',
license='GPLv2',
packages=['allianceauth'],
url='https://gitlab.com/allianceauth/allianceauth',
@@ -58,4 +63,20 @@ setup(
[console_scripts]
allianceauth=allianceauth.bin.allianceauth:main
""",
classifiers=[
'Environment :: Web Environment',
'Framework :: Django',
'Framework :: Django :: 2.2',
'Intended Audience :: Developers',
'License :: OSI Approved :: GNU General Public License v2 (GPLv2)',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
],
)

View File

@@ -24,7 +24,6 @@ INSTALLED_APPS += [
'allianceauth.optimer',
'allianceauth.corputils',
'allianceauth.fleetactivitytracking',
'allianceauth.fleetup',
'allianceauth.permissions_tool',
'allianceauth.services.modules.mumble',
'allianceauth.services.modules.discord',
@@ -174,19 +173,6 @@ SEAT_XTOKEN = 'tokentokentoken'
######################################
SMF_URL = ''
######################################
# Fleet-Up Configuration
######################################
# FLEETUP_APP_KEY - The app key from http://fleet-up.com/Api/MyApps
# FLEETUP_USER_ID - The user id from http://fleet-up.com/Api/MyKeys
# FLEETUP_API_ID - The API id from http://fleet-up.com/Api/MyKeys
# FLEETUP_GROUP_ID - The id of the group you want to pull data from, see http://fleet-up.com/Api/Endpoints#groups_mygroupmemberships
######################################
FLEETUP_APP_KEY = ''
FLEETUP_USER_ID = ''
FLEETUP_API_ID = ''
FLEETUP_GROUP_ID = ''
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.MD5PasswordHasher',
]

18
tox.ini
View File

@@ -1,21 +1,19 @@
[tox]
skipsdist = True
usedevelop = True
envlist = py{34,35,36,37}-dj{111,20}
skipsdist = true
usedevelop = true
envlist = py{35,36,37,38}-dj{2X}
[testenv]
setenv =
DJANGO_SETTINGS_MODULE = settings
basepython =
py34: python3.4
basepython =
py35: python3.5
py36: python3.6
py37: python3.7
deps=
dj111: Django>=1.11.1,<2.0
dj20: Django>=2.0
py37: https://github.com/yaml/pyyaml/zipball/master#egg=pyyaml
py37: https://github.com/celery/kombu/zipball/master#egg=kombu
py38: python3.8
deps=
coverage
dj2X: Django>=2.0,<3.0
install_command = pip install -e ".[testing]" -U {opts} {packages}
commands =
coverage run runtests.py -v 2