Compare commits

..

15 Commits

Author SHA1 Message Date
Adarnof
0d64441538 Version bump to v1.15.6 2017-11-17 16:22:03 -05:00
Adarnof
58a333c67a Case-insensitive group name to ID translation
Seems Discourse won't let you create `Group` if `group` already exists (`422 Name has already been taken`).

Thanks @huberfe
2017-11-17 13:07:41 -05:00
Adarnof
6837f94e59 Disable SeAT accounts instead of deleting. (#915)
See eveseat/web@1abb402
2017-11-03 19:20:31 -04:00
phaynu
16987fcaf0 Extending Choices for Questions in hrapplications to Allow Multiselect (#911)
An additional field at the question level defines whether the choices for the question are multi-select or not. The template will render the choices with radio buttons or checkboxes depending on multi-select. Multiple selected choices are saved with a line break between them.
2017-10-25 00:35:19 -04:00
Derptron
ebd3be3f46 Documentation update (#850)
* Update to the Dependency in regard to using SeAT
* Update to the installation of SSL-Certificates with Discourse
* CleanUp of some missing information in the discourse section
2017-10-05 13:15:34 +10:00
Adarnof
a02e5f400a Version bump to v1.15.5 2017-10-03 22:37:56 -04:00
Adarnof
65c168939d Handle FAT ZeroDivisionErrors
Closes #881
2017-10-03 21:50:32 -04:00
Adarnof
313cac6ac7 Handle new zKillboard API format
Closes #872
2017-10-01 12:53:03 -04:00
Adarnof
0145ea82c8 Correct py3 __str__ support.
Change slugify package for py3
2017-09-30 18:38:05 -04:00
Adarnof
0cdc5ffbd5 Use pypi versioned adarnauth-esi 2017-09-27 18:52:48 -04:00
Basraah
0bdd044378 Improve support for milliseconds backoff 2017-09-26 09:02:37 +10:00
Adarnof
ad266ea2ee Increase tested retry after
Apparently tests take longer than 200ms to evaluate here.
2017-09-25 18:36:53 -04:00
Adarnof
7ea8c9e50d Retry after in milliseconds
Closes #874
2017-09-25 18:21:23 -04:00
mmolitor87
9a015fd582 Change index images to font (#841)
* Change index images to font

* Added SEAT_URL reference and added it to the index template
2017-09-23 08:29:08 +10:00
Adarnof
7ca1c87c87 Minimize swagger spec files. 2017-09-20 01:31:04 -04:00
1000 changed files with 24755 additions and 52574 deletions

View File

@@ -1,15 +1,24 @@
[run]
branch = True
source =
allianceauth
alliance_auth
authentication
corputils
eveonline
fleetactivitytracking
fleetup
groupmanagement
hrapplications
notifications
optimer
permissions_tool
services
srp
timerboard
omit =
*/migrations/*
*/example/*
*/project_template/*
*/bin/*
*/tests/*
*/tests.py
[report]
exclude_lines =

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*/*.py.example linguist-language=Python

24
.gitignore vendored
View File

@@ -8,7 +8,6 @@ __pycache__/
# Distribution / packaging
.Python
env/
venv/
build/
develop-eggs/
dist/
@@ -42,6 +41,7 @@ nosetests.xml
coverage.xml
# Translations
*.mo
*.pot
# Django stuff:
@@ -53,27 +53,17 @@ docs/_build/
# PyBuilder
target/
.vagrant/
alliance_auth/settings.py
*Thumbs.db
nginx_config.txt
# custom staticfiles
static/*
#celerybeat
*.pid
celerybeat-schedule
#pycharm
.idea/*
/nbproject/
#VSCode
.vscode/
#gitlab configs
.gitlab/
#transifex
.tx/
#other
.flake8
.pylintrc
Makefile
.idea/*

View File

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

View File

@@ -1,14 +0,0 @@
# Bug
- I have searched [issues](https://gitlab.com/allianceauth/allianceauth/issues?scope=all&utf8=%E2%9C%93&state=all) (Y/N):
- What Version of Alliance Auth:
- What Operating System:
- Version of other components relevant to issue eg. Service, Database:
Please include a brief description of your issue here.
Please include steps to reproduce the issue
Please include any tracebacks or logs
Please include the results of the command `pip list`

View File

@@ -1,7 +0,0 @@
# Feature Request
- Describe the feature are you requesting.
- Is this a Service (external integration), a Module (Alliance Auth extension) or an enhancement to an existing service/module.
- Describe why its useful to you or others.

View File

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

14
.travis.yml Normal file
View File

@@ -0,0 +1,14 @@
language: python
python:
- "2.7"
- "3.5"
# command to install dependencies
install:
- pip install requests
- pip install -r requirements.txt
- pip install -r testing-requirements.txt
# command to run tests
script: coverage run runtests.py
cache: pip
after_success:
coveralls

View File

@@ -1,7 +0,0 @@
include LICENSE
include README.md
include MANIFEST.in
graft allianceauth
global-exclude __pycache__
global-exclude *.py[co]

109
README.md
View File

@@ -1,86 +1,55 @@
# Alliance Auth
Alliance Auth
============
[![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)
[![Join the chat at https://gitter.im/R4stl1n/allianceauth](https://badges.gitter.im/R4stl1n/allianceauth.svg)](https://gitter.im/R4stl1n/allianceauth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![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)
[![Build Status](https://travis-ci.org/allianceauth/allianceauth.svg?branch=master)](https://travis-ci.org/allianceauth/allianceauth)
[![Coverage Status](https://coveralls.io/repos/github/allianceauth/allianceauth/badge.svg?branch=master)](https://coveralls.io/github/allianceauth/allianceauth?branch=master)
An auth system for EVE Online to help in-game organizations manage online service access.
## Content
EVE service auth to help corps, alliances, and coalitions manage services.
Built for "The 99 Percent" open for anyone to use.
- [Overview](#overview)
- [Documentation](http://allianceauth.rtfd.io)
- [Support](#support)
- [Release Notes](https://gitlab.com/allianceauth/allianceauth/-/releases)
- [Developer Team](#developer-team)
- [Contributing](#contributing)
[Read the docs here.](http://allianceauth.rtfd.io)
## Overview
Special Permissions In Admin:
Alliance Auth (AA) is a web site that helps Eve Online organizations efficiently manage access to applications and services.
auth | user | group_management ( Access to add members to groups within the alliance )
auth | user | jabber_broadcast ( Access to broadcast a message over jabber to own groups )
auth | user | jabber_broadcast_all ( Can choose from all groups and the 'all' option when broadcasting )
auth | user | corp_apis ( View APIs, and jackKnife, of all members in user's corp. )
auth | user | alliance_apis ( View APIs, and jackKnife, of all member in user's alliance member corps. )
auth | user | timer_management ( Access to create and remove timers )
auth | user | timer_view ( Access to timerboard to view timers )
auth | user | srp_management ( Allows for an individual to create and remove srp fleets and fleet data )
auth | user | sigtracker_management ( Allows for an individual to create and remove signitures )
auth | user | sigtracker_view ( Allows for an individual view signitures )
auth | user | optimer_management ( Allows for an individual to create and remove fleet operations )
auth | user | optimer_view ( Allows for an individual view fleet operations )
auth | user | logging_notifications ( Generate notifications from logging )
Main features:
auth | user | human_resources ( View applications to user's corp )
hrapplications | application | delete_application ( Can delete applications )
hrapplications | application | accept_application ( Can accept applications )
hrapplications | application | reject_application ( Can reject applications )
hrapplications | application | view_apis ( Can see applicant's API keys )
hrapplications | applicationcomment | add_applicationcomment ( Can comment on applications )
- Automatically grants or revokes user access to external services (e.g. Discord, Mumble) and web apps (e.g. SRP requests) based on the user's current membership to [in-game organizations](https://allianceauth.readthedocs.io/en/latest/features/core/states/) and [groups](https://allianceauth.readthedocs.io/en/latest/features/core/groups/)
Vagrant Instructions:
- 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.
Copy the scripts to the root directory before running
- Includes a set of connectors (called ["services"](https://allianceauth.readthedocs.io/en/latest/features/services/)) for integrating access management with many popular external applications / services like Discord, Mumble, Teamspeak 3, SMF and others
Active Developers:
- Includes a set of web [apps](https://allianceauth.readthedocs.io/en/latest/features/apps/) which add many useful functions, e.g.: fleet schedule, timer board, SRP request management, fleet activity tracker
Adarnof
basraah
- Can be easily extended with additional services and apps. Many are provided by the community and can be found here: [Community Creations](https://gitlab.com/allianceauth/community-creations)
Beta Testers/ Bug Fixers:
- English :flag_gb:, Chinese :flag_cn:, German :flag_de:, Spanish :flag_es:, Korean :flag_kr: and Russian :flag_ru: localization
TrentBartlem ( Testing and Bug Fixes )
IskFiend ( Bug Fixes and Server Configuration )
Mr McClain (Bug Fixes and server configuration )
For further details about AA - including an installation guide and a full list of included services and plugin apps - please see the [official documentation](http://allianceauth.rtfd.io).
Special Thanks:
## 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
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.
Thanks to Nikdoof, without his old auth implementation this project wouldn't be as far as it is now.

2
alliance_auth/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/settings.py
!/*.example

View File

@@ -0,0 +1,8 @@
from __future__ import absolute_import, unicode_literals
# This will make sure the app is always imported when
# Django starts so that shared_task will use this app.
from .celeryapp import app as celery_app # noqa
__version__ = '1.15.6'
NAME = 'Alliance Auth v%s' % __version__

View File

@@ -0,0 +1,17 @@
from __future__ import absolute_import, unicode_literals
import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'alliance_auth.settings')
from django.conf import settings # noqa
app = Celery('alliance_auth')
# Using a string here means the worker don't have to serialize
# the configuration object to child processes.
app.config_from_object('django.conf:settings')
# Load task modules from all registered Django app configs.
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

View File

@@ -30,6 +30,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Based on https://github.com/torchbox/wagtail/blob/master/wagtail/wagtailcore/hooks.py
"""
from __future__ import unicode_literals
from importlib import import_module
from django.apps import apps

View File

@@ -0,0 +1,708 @@
# -*- coding: UTF-8 -*-
"""
Django settings for alliance_auth project.
Generated by 'django-admin startproject' using Django 1.10.1.
For more information on this file, see
https://docs.djangoproject.com/en/1.10/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.10/ref/settings/
"""
import os
from django.contrib import messages
from celery.schedules import crontab
# Celery configuration
BROKER_URL = 'redis://localhost:6379/0'
CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = ''
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = 'True' == os.environ.get('AA_DEBUG','True')
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'django_celery_beat',
'bootstrapform',
'authentication',
'services',
'eveonline',
'groupmanagement',
'hrapplications',
'timerboard',
'srp',
'optimer',
'corputils',
'fleetactivitytracking',
'fleetup',
'notifications',
'esi',
'permissions_tool',
'geelweb.django.navhelper',
'bootstrap_pagination',
'captcha',
# Services
'services.modules.mumble',
'services.modules.discord',
'services.modules.discourse',
'services.modules.ipboard',
'services.modules.ips4',
'services.modules.market',
'services.modules.openfire',
'services.modules.seat',
'services.modules.smf',
'services.modules.phpbb3',
'services.modules.xenforo',
'services.modules.teamspeak3',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
]
ROOT_URLCONF = 'alliance_auth.urls'
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale/'),
)
ugettext = lambda s: s
LANGUAGES = (
('en', ugettext('English')),
('de', ugettext('German')),
)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'customization/templates'),
os.path.join(BASE_DIR, 'stock/templates'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'services.context_processors.auth_settings',
'notifications.context_processors.user_notification_count',
'authentication.context_processors.states',
'authentication.context_processors.membership_state',
'groupmanagement.context_processors.can_manage_groups',
],
},
},
]
WSGI_APPLICATION = 'alliance_auth.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'alliance_auth',
'USER': os.environ.get('AA_DB_DEFAULT_USER', 'allianceserver'),
'PASSWORD': os.environ.get('AA_DB_DEFAULT_PASSWORD', 'password'),
'HOST': os.environ.get('AA_DB_DEFAULT_HOST', '127.0.0.1'),
'PORT': os.environ.get('AA_DB_DEFAULT_PORT', '3306'),
},
}
# If you have run the authentication.0013_service_modules migration
# you will need to set this to True in order to install service modules
# which were involved in that migration after it has been run.
# If you are on a fresh install with no existing database you can safely
# set this to True
# If you have not run the authentication.0013_service_modules migration
# leave this set to False.
SERVICES_MIGRATED = False
# Password validation
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
LOGIN_URL = 'auth_login_user'
SUPERUSER_STATE_BYPASS = 'True' == os.environ.get('AA_SUPERUSER_STATE_BYPASS', 'True')
# Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/
LANGUAGE_CODE = os.environ.get('AA_LANGUAGE_CODE', 'en-us')
TIME_ZONE = os.environ.get('AA_TIME_ZONE', 'UTC')
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.10/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "customization/static"),
os.path.join(BASE_DIR, "stock/static"),
)
# Bootstrap messaging css workaround
MESSAGE_TAGS = {
messages.ERROR: 'danger'
}
CACHES = {
"default": {
"BACKEND": "redis_cache.RedisCache",
"LOCATION": "localhost:6379",
"OPTIONS": {
"DB": 1,
}
}
}
# Google Recaptcha
CAPTCHA_ENABLED = False
RECAPTCHA_PUBLIC_KEY = 'MyRecaptchaKey'
RECAPTCHA_PRIVATE_KEY = 'MyRecaptchaPrivateKey'
NOCAPTCHA = True
#####################################################
##
## Auth configuration starts here
##
#####################################################
#########################
# CELERY SCHEDULED TASKS
#########################
CELERYBEAT_SCHEDULE = {
'run_api_refresh': {
'task': 'eveonline.tasks.run_api_refresh',
'schedule': crontab(minute=0, hour="*/3"),
},
'run_corp_update': {
'task': 'eveonline.tasks.run_corp_update',
'schedule': crontab(minute=0, hour="*/2"),
},
'update_all_corpstats': {
'task': 'corputils.tasks.update_all_corpstats',
'schedule': crontab(minute=0, hour="*/6"),
},
}
#################
# EMAIL SETTINGS
#################
# DOMAIN - The alliance auth domain_url
# EMAIL_HOST - SMTP Server URL
# EMAIL_PORT - SMTP Server PORT
# EMAIL_HOST_USER - Email Username (for gmail, the entire address)
# EMAIL_HOST_PASSWORD - Email Password
# EMAIL_USE_TLS - Set to use TLS encryption
#################
DOMAIN = os.environ.get('AA_DOMAIN', 'https://example.com')
EMAIL_HOST = os.environ.get('AA_EMAIL_HOST', 'smtp.gmail.com')
EMAIL_PORT = int(os.environ.get('AA_EMAIL_PORT', '587'))
EMAIL_HOST_USER = os.environ.get('AA_EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.environ.get('AA_EMAIL_HOST_PASSWORD', '')
EMAIL_USE_TLS = 'True' == os.environ.get('AA_EMAIL_USE_TLS', 'True')
####################
# Front Page Links
####################
# KILLBOARD_URL - URL for your killboard. Blank to hide link
# MEDIA_URL - URL for your media page (youtube etc). Blank to hide link
# FORUM_URL - URL for your forums. Blank to hide link
# SITE_NAME - Name of the auth site.
####################
KILLBOARD_URL = os.environ.get('AA_KILLBOARD_URL', '')
EXTERNAL_MEDIA_URL = os.environ.get('AA_EXTERNAL_MEDIA_URL', '')
FORUM_URL = os.environ.get('AA_FORUM_URL', '')
SITE_NAME = os.environ.get('AA_SITE_NAME', 'Alliance Auth')
###################
# SSO Settings
###################
# Get client ID and client secret from registering an app at
# https://developers.eveonline.com/
# Callback URL should be https://example.com/sso/callback
###################
ESI_SSO_CLIENT_ID = os.environ.get('AA_ESI_SSO_CLIENT_ID', '')
ESI_SSO_CLIENT_SECRET = os.environ.get('AA_ESI_SSO_CLIENT_SECRET', '')
ESI_SSO_CALLBACK_URL = os.environ.get('AA_ESI_SSO_CALLBACK_URL', '')
#########################
# Default Group Settings
#########################
# DEFAULT_AUTH_GROUP - Default group members are put in
# DEFAULT_BLUE_GROUP - Default group for blue members
# MEMBER_CORP_GROUPS - Assign members to a group representing their main corp
# BLUE_CORP_GROUPS - Assign blues to a group representing their main corp
#########################
DEFAULT_AUTH_GROUP = os.environ.get('AA_DEFAULT_ALLIANCE_GROUP', 'Member')
DEFAULT_BLUE_GROUP = os.environ.get('AA_DEFAULT_BLUE_GROUP', 'Blue')
MEMBER_CORP_GROUPS = 'True' == os.environ.get('AA_MEMBER_CORP_GROUPS', 'True')
MEMBER_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_MEMBER_ALLIANCE_GROUPS', 'False')
BLUE_CORP_GROUPS = 'True' == os.environ.get('AA_BLUE_CORP_GROUPS', 'False')
BLUE_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_BLUE_ALLIANCE_GROUPS', 'False')
#########################
# Tenant Configuration
#########################
# CORP_IDS - A list of corporation IDs to treat as members.
# ALLIANCE_IDS - A list of alliance IDs to treat as members.
# Any corps in a specified alliance will be treated as members, so do not include them in CORP_IDS
#########################
CORP_IDS = []
ALLIANCE_IDS = []
#########################
# Standings Configuration
#########################
# Add a corp API key to add blue standings to grant access.
# CORP_API_ID - Set this to the api id for the corp API key
# CORP_API_VCODE - Set this to the api vcode for the corp API key
# BLUE_STANDING - The lowest standings value to consider blue
# STANDING_LEVEL - The level of standings to query. Accepted values are 'corp' and 'alliance'.
# BLUE_CORP_IDS - A list of corps to remain blue regardless of in-game standings
# BLUE_ALLIANCE_IDS - A list of alliances to remain blue regardless of in-game standings
########################
CORP_API_ID = os.environ.get('AA_CORP_API_ID', '')
CORP_API_VCODE = os.environ.get('AA_CORP_API_VCODE', '')
BLUE_STANDING = float(os.environ.get('AA_BLUE_STANDING', '5.0'))
STANDING_LEVEL = os.environ.get('AA_STANDING_LEVEL', 'corp')
BLUE_CORP_IDS = []
BLUE_ALLIANCE_IDS = []
########################
# API Configuration
########################
# MEMBER_API_MASK - Numeric value of minimum API mask required for members
# MEMBER_API_ACCOUNT - Require API to be for Account and not character restricted
# BLUE_API_MASK - Numeric value of minimum API mask required for blues
# BLUE_API_ACCOUNT - Require API to be for Account and not character restricted
# REJECT_OLD_APIS - Require each submitted API be newer than the latest submitted API
# REJECT_OLD_APIS_MARGIN - Margin from latest submitted API ID within which a newly submitted API is still accepted
# API_SSO_VALIDATION - Require users to prove ownership of newly entered API keys via SSO
# Requires SSO to be configured.
#######################
MEMBER_API_MASK = os.environ.get('AA_MEMBER_API_MASK', 268435455)
MEMBER_API_ACCOUNT = 'True' == os.environ.get('AA_MEMBER_API_ACCOUNT', 'True')
BLUE_API_MASK = os.environ.get('AA_BLUE_API_MASK', 8388608)
BLUE_API_ACCOUNT = 'True' == os.environ.get('AA_BLUE_API_ACCOUNT', 'False')
REJECT_OLD_APIS = 'True' == os.environ.get('AA_REJECT_OLD_APIS', 'False')
REJECT_OLD_APIS_MARGIN = os.environ.get('AA_REJECT_OLD_APIS_MARGIN', 50)
API_SSO_VALIDATION = 'True' == os.environ.get('AA_API_SSO_VALIDATION', 'False')
#######################
# EVE Provider Settings
#######################
# EVEONLINE_CHARACTER_PROVIDER - Name of default data source for getting eve character data
# EVEONLINE_CORP_PROVIDER - Name of default data source for getting eve corporation data
# EVEONLINE_ALLIANCE_PROVIDER - Name of default data source for getting eve alliance data
# EVEONLINE_ITEMTYPE_PROVIDER - Name of default data source for getting eve item type data
#
# Available sources are 'esi' and 'xml'. Leaving blank results in the default 'esi' being used.
#######################
EVEONLINE_CHARACTER_PROVIDER = os.environ.get('AA_EVEONLINE_CHARACTER_PROVIDER', '')
EVEONLINE_CORP_PROVIDER = os.environ.get('AA_EVEONLINE_CORP_PROVIDER', '')
EVEONLINE_ALLIANCE_PROVIDER = os.environ.get('AA_EVEONLINE_ALLIANCE_PROVIDER', '')
EVEONLINE_ITEMTYPE_PROVIDER = os.environ.get('AA_EVEONLINE_ITEMTYPE_PROVIDER', '')
#####################
# Alliance Market
#####################
MARKET_URL = os.environ.get('AA_MARKET_URL', 'http://example.com/market')
MARKET_DB = {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'alliance_market',
'USER': os.environ.get('AA_DB_MARKET_USER', 'allianceserver-market'),
'PASSWORD': os.environ.get('AA_DB_MARKET_PASSWORD', 'password'),
'HOST': os.environ.get('AA_DB_MARKET_HOST', '127.0.0.1'),
'PORT': os.environ.get('AA_DB_MARKET_PORT', '3306'),
}
#####################
# HR Configuration
#####################
# API_KEY_AUDIT_URL - URL for viewing API keys.
# Other URLs are supported. Use string formatting notation {} with parameter names api_id, vcode, pk
# Example URL formats are shown below:
# Old Jacknife: 'https://example.com/jacknife/eveapi/audit.php?usid={api_id}&apik={vcode}'
# New Jacknife: 'https://example.com/jacknife/?usid={api_id}&apik={vcode}'
# SeAT: 'https://seat.example.com/api-key/detail/{api_id}'
# Django Admin: '/admin/eveonline/eveapikeypair/{pk}/change'
# Leave blank for the verification code to be shown in a popup on click.
#####################
API_KEY_AUDIT_URL = os.environ.get('AA_API_KEY_AUDIT_URL', '')
#####################
# Forum Configuration
#####################
# IPBOARD_ENDPOINT - Api endpoint if using ipboard
# IPBOARD_APIKEY - Api key to interact with ipboard
# IPBOARD_APIMODULE - Module for alliance auth *leave alone*
#####################
IPBOARD_ENDPOINT = os.environ.get('AA_IPBOARD_ENDPOINT', 'example.com/interface/board/index.php')
IPBOARD_APIKEY = os.environ.get('AA_IPBOARD_APIKEY', 'somekeyhere')
IPBOARD_APIMODULE = 'aa'
########################
# XenForo Configuration
########################
XENFORO_ENDPOINT = os.environ.get('AA_XENFORO_ENDPOINT', 'example.com/api.php')
XENFORO_DEFAULT_GROUP = os.environ.get('AA_XENFORO_DEFAULT_GROUP', 0)
XENFORO_APIKEY = os.environ.get('AA_XENFORO_APIKEY', 'yourapikey')
#####################
######################
# Jabber Configuration
######################
# JABBER_URL - Jabber address url
# JABBER_PORT - Jabber service portal
# JABBER_SERVER - Jabber server url
# OPENFIRE_ADDRESS - Address of the openfire admin console including port
# Please use http with 9090 or https with 9091
# OPENFIRE_SECRET_KEY - Openfire REST API secret key
# BROADCAST_USER - Broadcast user JID
# BROADCAST_USER_PASSWORD - Broadcast user password
######################
JABBER_URL = os.environ.get('AA_JABBER_URL', "example.com")
JABBER_PORT = int(os.environ.get('AA_JABBER_PORT', '5223'))
JABBER_SERVER = os.environ.get('AA_JABBER_SERVER', "example.com")
OPENFIRE_ADDRESS = os.environ.get('AA_OPENFIRE_ADDRESS', "http://example.com:9090")
OPENFIRE_SECRET_KEY = os.environ.get('AA_OPENFIRE_SECRET_KEY', "somekey")
BROADCAST_USER = os.environ.get('AA_BROADCAST_USER', "broadcast@") + JABBER_URL
BROADCAST_USER_PASSWORD = os.environ.get('AA_BROADCAST_USER_PASSWORD', "somepassword")
BROADCAST_SERVICE_NAME = os.environ.get('AA_BROADCAST_SERVICE_NAME', "broadcast")
######################################
# Mumble Configuration
######################################
# MUMBLE_URL - Mumble server host
# Do not include leading http:// or mumble://
######################################
MUMBLE_URL = os.environ.get('AA_MUMBLE_URL', "example.com")
######################################
# PHPBB3 Configuration
######################################
PHPBB3_DB = {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'alliance_forum',
'USER': os.environ.get('AA_DB_PHPBB3_USER', 'allianceserver-phpbb3'),
'PASSWORD': os.environ.get('AA_DB_PHPBB3_PASSWORD', 'password'),
'HOST': os.environ.get('AA_DB_PHPBB3_HOST', '127.0.0.1'),
'PORT': os.environ.get('AA_DB_PHPBB3_PORT', '3306'),
}
######################################
# Teamspeak3 Configuration
######################################
# TEAMSPEAK3_SERVER_IP - Teamspeak3 server ip
# TEAMSPEAK3_SERVER_PORT - Teamspeak3 server port
# TEAMSPEAK3_SERVERQUERY_USER - Teamspeak3 serverquery username
# TEAMSPEAK3_SERVERQUERY_PASSWORD - Teamspeak3 serverquery password
# TEAMSPEAK3_VIRTUAL_SERVER - Virtual server id
# TEAMSPEAK3_AUTHED_GROUP_ID - Default authed group id
# TEAMSPEAK3_PUBLIC_URL - teamspeak3 public url used for link creation
######################################
TEAMSPEAK3_SERVER_IP = os.environ.get('AA_TEAMSPEAK3_SERVER_IP', '127.0.0.1')
TEAMSPEAK3_SERVER_PORT = int(os.environ.get('AA_TEAMSPEAK3_SERVER_PORT', '10011'))
TEAMSPEAK3_SERVERQUERY_USER = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_USER', 'serveradmin')
TEAMSPEAK3_SERVERQUERY_PASSWORD = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_PASSWORD', 'passwordhere')
TEAMSPEAK3_VIRTUAL_SERVER = int(os.environ.get('AA_TEAMSPEAK3_VIRTUAL_SERVER', '1'))
TEAMSPEAK3_PUBLIC_URL = os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'example.com')
######################################
# Discord Configuration
######################################
# DISCORD_GUILD_ID - ID of the guild to manage
# DISCORD_BOT_TOKEN - oauth token of the app bot user
# DISCORD_INVITE_CODE - invite code to the server
# DISCORD_APP_ID - oauth app client ID
# DISCORD_APP_SECRET - oauth app secret
# DISCORD_CALLBACK_URL - oauth callback url
# DISCORD_SYNC_NAMES - enable to force discord nicknames to be set to eve char name (bot needs Manage Nicknames permission)
######################################
DISCORD_GUILD_ID = os.environ.get('AA_DISCORD_GUILD_ID', '')
DISCORD_BOT_TOKEN = os.environ.get('AA_DISCORD_BOT_TOKEN', '')
DISCORD_INVITE_CODE = os.environ.get('AA_DISCORD_INVITE_CODE', '')
DISCORD_APP_ID = os.environ.get('AA_DISCORD_APP_ID', '')
DISCORD_APP_SECRET = os.environ.get('AA_DISCORD_APP_SECRET', '')
DISCORD_CALLBACK_URL = os.environ.get('AA_DISCORD_CALLBACK_URL', 'http://example.com/discord/callback')
DISCORD_SYNC_NAMES = 'True' == os.environ.get('AA_DISCORD_SYNC_NAMES', 'False')
######################################
# Discourse Configuration
######################################
# DISCOURSE_URL - Web address of the forums (no trailing slash)
# DISCOURSE_API_USERNAME - API account username
# DISCOURSE_API_KEY - API Key
# DISCOURSE_SSO_SECRET - SSO secret key
######################################
DISCOURSE_URL = os.environ.get('AA_DISCOURSE_URL', '')
DISCOURSE_API_USERNAME = os.environ.get('AA_DISCOURSE_API_USERNAME', '')
DISCOURSE_API_KEY = os.environ.get('AA_DISCOURSE_API_KEY', '')
DISCOURSE_SSO_SECRET = os.environ.get('AA_DISCOURSE_SSO_SECRET', '')
#####################################
# IPS4 Configuration
#####################################
# IPS4_URL - base url of the IPS4 install (no trailing slash)
# IPS4_API_KEY - API key provided by IPS4
#####################################
IPS4_URL = os.environ.get('AA_IPS4_URL', 'http://example.com/ips4')
IPS4_API_KEY = os.environ.get('AA_IPS4_API_KEY', '')
IPS4_DB = {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'alliance_ips4',
'USER': os.environ.get('AA_DB_IPS4_USER', 'allianceserver-ips4'),
'PASSWORD': os.environ.get('AA_DB_IPS4_PASSWORD', 'password'),
'HOST': os.environ.get('AA_DB_IPS4_HOST', '127.0.0.1'),
'PORT': os.environ.get('AA_DB_IPS4_PORT', '3306'),
}
#####################################
# SEAT Configuration
#####################################
# SEAT_URL - base url of the seat install (no trailing slash)
# SEAT_XTOKEN - API key X-Token provided by SeAT
#####################################
SEAT_URL = os.environ.get('AA_SEAT_URL', 'http://example.com/seat')
SEAT_XTOKEN = os.environ.get('AA_SEAT_XTOKEN', '')
######################################
# SMF Configuration
######################################
SMF_URL = os.environ.get('AA_SMF_URL', 'https://example.com')
SMF_DB = {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'alliance_smf',
'USER': os.environ.get('AA_DB_SMF_USER', 'allianceserver-smf'),
'PASSWORD': os.environ.get('AA_DB_SMF_PASSWORD', 'password'),
'HOST': os.environ.get('AA_DB_SMF_HOST', '127.0.0.1'),
'PORT': os.environ.get('AA_DB_SMF_PORT', '3306'),
}
######################################
# 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 = os.environ.get('AA_FLEETUP_APP_KEY', '')
FLEETUP_USER_ID = os.environ.get('AA_FLEETUP_USER_ID', '')
FLEETUP_API_ID = os.environ.get('AA_FLEETUP_API_ID', '')
FLEETUP_GROUP_ID = os.environ.get('AA_FLEETUP_GROUP_ID', '')
#####################################
# Logging Configuration
#####################################
# Set log_file and console level to desired state:
# DEBUG - basically stack trace, explains every step
# INFO - model creation, deletion, updates, etc
# WARN - unexpected function outcomes that do not impact user
# ERROR - unexcpeted function outcomes which prevent user from achieving desired outcome
# EXCEPTION - something critical went wrong, unhandled
#####################################
# Recommended level for log_file is INFO, console is DEBUG
# Change log level of individual apps below to narrow your debugging
#####################################
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format' : "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s",
'datefmt' : "%d/%b/%Y %H:%M:%S"
},
'simple': {
'format': '%(levelname)s %(message)s'
},
},
'handlers': {
'log_file': {
'level': 'INFO', # edit this line to change logging level to file
'class': 'logging.handlers.RotatingFileHandler',
'filename': os.path.join(BASE_DIR,'log/allianceauth.log'),
'formatter': 'verbose',
'maxBytes': 1024*1024*5, # edit this line to change max log file size
'backupCount': 5, # edit this line to change number of log backups
},
'console': {
'level': 'DEBUG', # edit this line to change logging level to console
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
'notifications': { # creates notifications for users with logging_notifications permission
'level': 'ERROR', # edit this line to change logging level to notifications
'class': 'notifications.handlers.NotificationHandler',
'formatter': 'verbose',
},
},
'loggers': {
'authentication': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'celerytask': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'eveonline': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'groupmanagement': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'hrapplications': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'portal': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'registration': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'services': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'srp': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'timerboard': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'sigtracker': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'optimer': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'corputils': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'fleetactivitytracking': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'ERROR',
},
'fleetup': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'util': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'DEBUG',
},
'django': {
'handlers': ['log_file', 'console', 'notifications'],
'level': 'ERROR',
},
}
}
# Conditionally add databases only if configured
if 'services.modules.phpbb3' in INSTALLED_APPS:
DATABASES['phpbb3'] = PHPBB3_DB
if 'services.modules.smf' in INSTALLED_APPS:
DATABASES['smf'] = SMF_DB
if 'services.modules.market' in INSTALLED_APPS:
DATABASES['market'] = MARKET_DB
if 'services.modules.ips4' in INSTALLED_APPS:
DATABASES['ips4'] = IPS4_DB
# Ensure corp/alliance IDs are expected types
STR_CORP_IDS = [str(id) for id in CORP_IDS]
STR_ALLIANCE_IDS = [str(id) for id in ALLIANCE_IDS]
STR_BLUE_CORP_IDS = [str(id) for id in BLUE_CORP_IDS]
STR_BLUE_ALLIANCE_IDS = [str(id) for id in BLUE_ALLIANCE_IDS]
# Conditionally add periodic tasks for services if installed
if 'services.modules.teamspeak3' in INSTALLED_APPS:
CELERYBEAT_SCHEDULE['run_ts3_group_update'] = {
'task': 'services.modules.teamspeak3.tasks.run_ts3_group_update',
'schedule': crontab(minute='*/30'),
}
if 'services.modules.seat' in INSTALLED_APPS:
CELERYBEAT_SCHEDULE['run_seat_api_sync'] = {
'task': 'services.modules.seat.tasks.run_api_sync',
'schedule': crontab(minute='*/30'),
}

View File

@@ -0,0 +1,98 @@
from __future__ import unicode_literals
from django.db.models.signals import m2m_changed, pre_save
from django.contrib.auth.models import User, Group, Permission
from services.signals import m2m_changed_user_groups, pre_save_user
from services.signals import m2m_changed_group_permissions, m2m_changed_user_permissions
from authentication.signals import pre_save_auth_state
from authentication.tasks import make_member, make_blue
from authentication.models import AuthServicesInfo
from authentication.states import MEMBER_STATE, BLUE_STATE, NONE_STATE
from eveonline.models import EveCharacter
class AuthUtils:
def __init__(self):
pass
@staticmethod
def _create_user(username):
return User.objects.create(username=username)
@classmethod
def create_user(cls, username, disconnect_signals=False):
if disconnect_signals:
cls.disconnect_signals()
user = cls._create_user(username)
user.authservicesinfo.state = NONE_STATE
user.authservicesinfo.save()
if disconnect_signals:
cls.connect_signals()
return user
@classmethod
def create_member(cls, username):
cls.disconnect_signals()
user = cls._create_user(username)
user.authservicesinfo.state = MEMBER_STATE
user.authservicesinfo.save()
make_member(user.authservicesinfo)
cls.connect_signals()
return user
@classmethod
def create_blue(cls, username):
cls.disconnect_signals()
user = cls._create_user(username)
user.authservicesinfo.state = BLUE_STATE
user.authservicesinfo.save()
make_blue(user.authservicesinfo)
cls.connect_signals()
return user
@classmethod
def disconnect_signals(cls):
m2m_changed.disconnect(m2m_changed_user_groups, sender=User.groups.through)
m2m_changed.disconnect(m2m_changed_group_permissions, sender=Group.permissions.through)
m2m_changed.disconnect(m2m_changed_user_permissions, sender=User.user_permissions.through)
pre_save.disconnect(pre_save_user, sender=User)
pre_save.disconnect(pre_save_auth_state, sender=AuthServicesInfo)
@classmethod
def connect_signals(cls):
m2m_changed.connect(m2m_changed_user_groups, sender=User.groups.through)
m2m_changed.connect(m2m_changed_group_permissions, sender=Group.permissions.through)
m2m_changed.connect(m2m_changed_user_permissions, sender=User.user_permissions.through)
pre_save.connect(pre_save_user, sender=User)
pre_save.connect(pre_save_auth_state, sender=AuthServicesInfo)
@classmethod
def add_main_character(cls, user, name, character_id, corp_id='', corp_name='', corp_ticker='', alliance_id='',
alliance_name=''):
EveCharacter.objects.create(
character_id=character_id,
character_name=name,
corporation_id=corp_id,
corporation_name=corp_name,
corporation_ticker=corp_ticker,
alliance_id=alliance_id,
alliance_name=alliance_name,
api_id='1234',
user=user
)
AuthServicesInfo.objects.update_or_create(user=user, defaults={'main_char_id': character_id})
@classmethod
def add_permissions_to_groups(cls, perms, groups, disconnect_signals=True):
if disconnect_signals:
cls.disconnect_signals()
for group in groups:
for perm in perms:
group.permissions.add(perm)
if disconnect_signals:
cls.connect_signals()

View File

@@ -0,0 +1,556 @@
"""
Alliance Auth Test Suite Django settings.
"""
import os
from django.contrib import messages
import alliance_auth
# Use nose to run all tests
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = [
#'--with-coverage',
#'--cover-package=',
#'--exe', # If your tests need this to be found/run, check they py files are not chmodded +x
]
# Celery configuration
CELERY_ALWAYS_EAGER = True # Forces celery to run locally for testing
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(alliance_auth.__file__)))
SECRET_KEY = 'testing only'
DEBUG = True
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.humanize',
'django_celery_beat',
'bootstrapform',
'authentication',
'services',
'eveonline',
'groupmanagement',
'hrapplications',
'timerboard',
'srp',
'optimer',
'corputils',
'fleetactivitytracking',
'fleetup',
'notifications',
'esi',
'permissions_tool',
'geelweb.django.navhelper',
'bootstrap_pagination',
'services.modules.mumble',
'services.modules.discord',
'services.modules.discourse',
'services.modules.ipboard',
'services.modules.ips4',
'services.modules.market',
'services.modules.openfire',
'services.modules.seat',
'services.modules.smf',
'services.modules.phpbb3',
'services.modules.xenforo',
'services.modules.teamspeak3',
'django_nose',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware',
]
ROOT_URLCONF = 'alliance_auth.urls'
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale/'),
)
ugettext = lambda s: s
LANGUAGES = (
('en', ugettext('English')),
('de', ugettext('German')),
)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [
os.path.join(BASE_DIR, 'customization/templates'),
os.path.join(BASE_DIR, 'stock/templates'),
],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'services.context_processors.auth_settings',
'notifications.context_processors.user_notification_count',
'authentication.context_processors.states',
'authentication.context_processors.membership_state',
'groupmanagement.context_processors.can_manage_groups',
],
},
},
]
# Database
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': 'alliance_auth',
'USER': os.environ.get('AA_DB_DEFAULT_USER', None),
'PASSWORD': os.environ.get('AA_DB_DEFAULT_PASSWORD', None),
'HOST': os.environ.get('AA_DB_DEFAULT_HOST', None)
},
}
LOGIN_URL = 'auth_login_user'
SUPERUSER_STATE_BYPASS = 'True' == os.environ.get('AA_SUPERUSER_STATE_BYPASS', 'True')
# Internationalization
# https://docs.djangoproject.com/en/1.10/topics/i18n/
LANGUAGE_CODE = os.environ.get('AA_LANGUAGE_CODE', 'en')
TIME_ZONE = os.environ.get('AA_TIME_ZONE', 'UTC')
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, "static")
STATICFILES_DIRS = (
os.path.join(BASE_DIR, "customization/static"),
os.path.join(BASE_DIR, "stock/static"),
)
# Bootstrap messaging css workaround
MESSAGE_TAGS = {
messages.ERROR: 'danger'
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
#####################################################
##
## Auth configuration starts here
##
#####################################################
###########################
# ALLIANCE / CORP TOGGLE
###########################
# Specifies to run membership checks against corp or alliance
# Set to FALSE for alliance
# Set to TRUE for corp
###########################
IS_CORP = 'True' == os.environ.get('AA_IS_CORP', 'True')
#################
# EMAIL SETTINGS
#################
# DOMAIN - The alliance auth domain_url
# EMAIL_HOST - SMTP Server URL
# EMAIL_PORT - SMTP Server PORT
# EMAIL_HOST_USER - Email Username (for gmail, the entire address)
# EMAIL_HOST_PASSWORD - Email Password
# EMAIL_USE_TLS - Set to use TLS encryption
#################
DOMAIN = os.environ.get('AA_DOMAIN', 'https://example.com')
EMAIL_HOST = os.environ.get('AA_EMAIL_HOST', 'smtp.example.com')
EMAIL_PORT = int(os.environ.get('AA_EMAIL_PORT', '587'))
EMAIL_HOST_USER = os.environ.get('AA_EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.environ.get('AA_EMAIL_HOST_PASSWORD', '')
EMAIL_USE_TLS = 'True' == os.environ.get('AA_EMAIL_USE_TLS', 'True')
####################
# Front Page Links
####################
# KILLBOARD_URL - URL for your killboard. Blank to hide link
# MEDIA_URL - URL for your media page (youtube etc). Blank to hide link
# FORUM_URL - URL for your forums. Blank to hide link
# SITE_NAME - Name of the auth site.
####################
KILLBOARD_URL = os.environ.get('AA_KILLBOARD_URL', '')
EXTERNAL_MEDIA_URL = os.environ.get('AA_EXTERNAL_MEDIA_URL', '')
FORUM_URL = os.environ.get('AA_FORUM_URL', '')
SITE_NAME = os.environ.get('AA_SITE_NAME', 'Test Alliance Auth')
###################
# SSO Settings
###################
# Optional SSO.
# Get client ID and client secret from registering an app at
# https://developers.eveonline.com/
# Callback URL should be http://mydomain.com/sso/callback
# Leave callback blank to hide SSO button on login page
###################
ESI_SSO_CLIENT_ID = os.environ.get('AA_ESI_SSO_CLIENT_ID', '')
ESI_SSO_CLIENT_SECRET = os.environ.get('AA_ESI_SSO_CLIENT_SECRET', '')
ESI_SSO_CALLBACK_URL = os.environ.get('AA_ESI_SSO_CALLBACK_URL', '')
#########################
# Default Group Settings
#########################
# DEFAULT_AUTH_GROUP - Default group members are put in
# DEFAULT_BLUE_GROUP - Default group for blue members
# MEMBER_CORP_GROUPS - Assign members to a group representing their main corp
# BLUE_CORP_GROUPS - Assign blues to a group representing their main corp
#########################
DEFAULT_AUTH_GROUP = os.environ.get('AA_DEFAULT_ALLIANCE_GROUP', 'Member')
DEFAULT_BLUE_GROUP = os.environ.get('AA_DEFAULT_BLUE_GROUP', 'Blue')
MEMBER_CORP_GROUPS = 'True' == os.environ.get('AA_MEMBER_CORP_GROUPS', 'True')
MEMBER_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_MEMBER_ALLIANCE_GROUPS', 'False')
BLUE_CORP_GROUPS = 'True' == os.environ.get('AA_BLUE_CORP_GROUPS', 'False')
BLUE_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_BLUE_ALLIANCE_GROUPS', 'False')
#########################
# Corp Configuration
#########################
# If running in alliance mode, the following should be for the executor corp#
# CORP_ID - Set this to your corp ID (get this from https://zkillboard.com/corporation/#######)
# CORP_NAME - Set this to your Corporation Name
# CORP_API_ID - Set this to the api id for the corp API key
# CORP_API_VCODE - Set this to the api vcode for the corp API key
########################
CORP_ID = os.environ.get('AA_CORP_ID', '1234')
CORP_NAME = os.environ.get('AA_CORP_NAME', 'Alliance Auth Test Corp')
CORP_API_ID = os.environ.get('AA_CORP_API_ID', '')
CORP_API_VCODE = os.environ.get('AA_CORP_API_VCODE', '')
#########################
# Alliance Configuration
#########################
# ALLIANCE_ID - Set this to your Alliance ID (get this from https://zkillboard.com/alliance/#######)
# ALLIANCE_NAME - Set this to your Alliance Name
########################
ALLIANCE_ID = os.environ.get('AA_ALLIANCE_ID', '12345')
ALLIANCE_NAME = os.environ.get('AA_ALLIANCE_NAME', 'Alliance Auth Test Alliance')
########################
# API Configuration
########################
# MEMBER_API_MASK - Numeric value of minimum API mask required for members
# MEMBER_API_ACCOUNT - Require API to be for Account and not character restricted
# BLUE_API_MASK - Numeric value of minimum API mask required for blues
# BLUE_API_ACCOUNT - Require API to be for Account and not character restricted
# REJECT_OLD_APIS - Require each submitted API be newer than the latest submitted API
# REJECT_OLD_APIS_MARGIN - Margin from latest submitted API ID within which a newly submitted API is still accepted
# API_SSO_VALIDATION - Require users to prove ownership of newly entered API keys via SSO
# Requires SSO to be configured.
#######################
MEMBER_API_MASK = os.environ.get('AA_MEMBER_API_MASK', 268435455)
MEMBER_API_ACCOUNT = 'True' == os.environ.get('AA_MEMBER_API_ACCOUNT', 'True')
BLUE_API_MASK = os.environ.get('AA_BLUE_API_MASK', 8388608)
BLUE_API_ACCOUNT = 'True' == os.environ.get('AA_BLUE_API_ACCOUNT', 'True')
REJECT_OLD_APIS = 'True' == os.environ.get('AA_REJECT_OLD_APIS', 'False')
REJECT_OLD_APIS_MARGIN = os.environ.get('AA_REJECT_OLD_APIS_MARGIN', 50)
API_SSO_VALIDATION = 'True' == os.environ.get('AA_API_SSO_VALIDATION', 'False')
#######################
# EVE Provider Settings
#######################
# EVEONLINE_CHARACTER_PROVIDER - Name of default data source for getting eve character data
# EVEONLINE_CORP_PROVIDER - Name of default data source for getting eve corporation data
# EVEONLINE_ALLIANCE_PROVIDER - Name of default data source for getting eve alliance data
# EVEONLINE_ITEMTYPE_PROVIDER - Name of default data source for getting eve item type data
#
# Available sources are 'esi' and 'xml'. Leaving blank results in the default 'esi' being used.
#######################
EVEONLINE_CHARACTER_PROVIDER = os.environ.get('AA_EVEONLINE_CHARACTER_PROVIDER', 'xml')
EVEONLINE_CORP_PROVIDER = os.environ.get('AA_EVEONLINE_CORP_PROVIDER', 'xml')
EVEONLINE_ALLIANCE_PROVIDER = os.environ.get('AA_EVEONLINE_ALLIANCE_PROVIDER', 'xml')
EVEONLINE_ITEMTYPE_PROVIDER = os.environ.get('AA_EVEONLINE_ITEMTYPE_PROVIDER', 'xml')
#####################
# Alliance Market
#####################
MARKET_URL = os.environ.get('AA_MARKET_URL', 'http://yourdomain.com/market')
#####################
# HR Configuration
#####################
# JACK_KNIFE_URL - Url for the audit page of API Jack knife
# Should seriously replace with your own.
#####################
JACK_KNIFE_URL = os.environ.get('AA_JACK_KNIFE_URL', 'http://example.com/eveapi/audit.php')
#####################
# Forum Configuration
#####################
# IPBOARD_ENDPOINT - Api endpoint if using ipboard
# IPBOARD_APIKEY - Api key to interact with ipboard
# IPBOARD_APIMODULE - Module for alliance auth *leave alone*
#####################
IPBOARD_ENDPOINT = os.environ.get('AA_IPBOARD_ENDPOINT', 'example.com/interface/board/index.php')
IPBOARD_APIKEY = os.environ.get('AA_IPBOARD_APIKEY', 'somekeyhere')
IPBOARD_APIMODULE = 'aa'
########################
# XenForo Configuration
########################
XENFORO_ENDPOINT = os.environ.get('AA_XENFORO_ENDPOINT', 'example.com/api.php')
XENFORO_DEFAULT_GROUP = os.environ.get('AA_XENFORO_DEFAULT_GROUP', 0)
XENFORO_APIKEY = os.environ.get('AA_XENFORO_APIKEY', 'yourapikey')
#####################
######################
# Jabber Configuration
######################
# JABBER_URL - Jabber address url
# JABBER_PORT - Jabber service portal
# JABBER_SERVER - Jabber server url
# OPENFIRE_ADDRESS - Address of the openfire admin console including port
# Please use http with 9090 or https with 9091
# OPENFIRE_SECRET_KEY - Openfire REST API secret key
# BROADCAST_USER - Broadcast user JID
# BROADCAST_USER_PASSWORD - Broadcast user password
######################
JABBER_URL = os.environ.get('AA_JABBER_URL', "example.com")
JABBER_PORT = int(os.environ.get('AA_JABBER_PORT', '5223'))
JABBER_SERVER = os.environ.get('AA_JABBER_SERVER', "example.com")
OPENFIRE_ADDRESS = os.environ.get('AA_OPENFIRE_ADDRESS', "http://example.com:9090")
OPENFIRE_SECRET_KEY = os.environ.get('AA_OPENFIRE_SECRET_KEY', "somekey")
BROADCAST_USER = os.environ.get('AA_BROADCAST_USER', "broadcast@") + JABBER_URL
BROADCAST_USER_PASSWORD = os.environ.get('AA_BROADCAST_USER_PASSWORD', "somepassword")
BROADCAST_SERVICE_NAME = os.environ.get('AA_BROADCAST_SERVICE_NAME', "broadcast")
######################################
# Mumble Configuration
######################################
# MUMBLE_URL - Mumble server url
# MUMBLE_SERVER_ID - Mumble server id
######################################
MUMBLE_URL = os.environ.get('AA_MUMBLE_URL', "example.com")
MUMBLE_SERVER_ID = int(os.environ.get('AA_MUMBLE_SERVER_ID', '1'))
######################################
# PHPBB3 Configuration
######################################
######################################
# Teamspeak3 Configuration
######################################
# TEAMSPEAK3_SERVER_IP - Teamspeak3 server ip
# TEAMSPEAK3_SERVER_PORT - Teamspeak3 server port
# TEAMSPEAK3_SERVERQUERY_USER - Teamspeak3 serverquery username
# TEAMSPEAK3_SERVERQUERY_PASSWORD - Teamspeak3 serverquery password
# TEAMSPEAK3_VIRTUAL_SERVER - Virtual server id
# TEAMSPEAK3_AUTHED_GROUP_ID - Default authed group id
# TEAMSPEAK3_PUBLIC_URL - teamspeak3 public url used for link creation
######################################
TEAMSPEAK3_SERVER_IP = os.environ.get('AA_TEAMSPEAK3_SERVER_IP', '127.0.0.1')
TEAMSPEAK3_SERVER_PORT = int(os.environ.get('AA_TEAMSPEAK3_SERVER_PORT', '10011'))
TEAMSPEAK3_SERVERQUERY_USER = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_USER', 'serveradmin')
TEAMSPEAK3_SERVERQUERY_PASSWORD = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_PASSWORD', 'passwordhere')
TEAMSPEAK3_VIRTUAL_SERVER = int(os.environ.get('AA_TEAMSPEAK3_VIRTUAL_SERVER', '1'))
TEAMSPEAK3_PUBLIC_URL = os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'example.com')
######################################
# Discord Configuration
######################################
# DISCORD_GUILD_ID - ID of the guild to manage
# DISCORD_BOT_TOKEN - oauth token of the app bot user
# DISCORD_INVITE_CODE - invite code to the server
# DISCORD_APP_ID - oauth app client ID
# DISCORD_APP_SECRET - oauth app secret
# DISCORD_CALLBACK_URL - oauth callback url
# DISCORD_SYNC_NAMES - enable to force discord nicknames to be set to eve char name (bot needs Manage Nicknames permission)
######################################
DISCORD_GUILD_ID = os.environ.get('AA_DISCORD_GUILD_ID', '0118999')
DISCORD_BOT_TOKEN = os.environ.get('AA_DISCORD_BOT_TOKEN', 'bottoken')
DISCORD_INVITE_CODE = os.environ.get('AA_DISCORD_INVITE_CODE', 'invitecode')
DISCORD_APP_ID = os.environ.get('AA_DISCORD_APP_ID', 'appid')
DISCORD_APP_SECRET = os.environ.get('AA_DISCORD_APP_SECRET', 'secret')
DISCORD_CALLBACK_URL = os.environ.get('AA_DISCORD_CALLBACK_URL', 'http://example.com/discord/callback')
DISCORD_SYNC_NAMES = 'True' == os.environ.get('AA_DISCORD_SYNC_NAMES', 'False')
######################################
# Discourse Configuration
######################################
# DISCOURSE_URL - Web address of the forums (no trailing slash)
# DISCOURSE_API_USERNAME - API account username
# DISCOURSE_API_KEY - API Key
# DISCOURSE_SSO_SECRET - SSO secret key
######################################
DISCOURSE_URL = os.environ.get('AA_DISCOURSE_URL', 'https://example.com')
DISCOURSE_API_USERNAME = os.environ.get('AA_DISCOURSE_API_USERNAME', '')
DISCOURSE_API_KEY = os.environ.get('AA_DISCOURSE_API_KEY', '')
DISCOURSE_SSO_SECRET = 'd836444a9e4084d5b224a60c208dce14'
# Example secret from https://meta.discourse.org/t/official-single-sign-on-for-discourse/13045
#####################################
# IPS4 Configuration
#####################################
# IPS4_URL - base url of the IPS4 install (no trailing slash)
# IPS4_API_KEY - API key provided by IPS4
#####################################
IPS4_URL = os.environ.get('AA_IPS4_URL', 'http://example.com/ips4')
IPS4_API_KEY = os.environ.get('AA_IPS4_API_KEY', '')
#####################################
# SEAT Configuration
#####################################
# SEAT_URL - base url of the seat install (no trailing slash)
# SEAT_XTOKEN - API key X-Token provided by SeAT
#####################################
SEAT_URL = os.environ.get('AA_SEAT_URL', 'http://example.com/seat')
SEAT_XTOKEN = os.environ.get('AA_SEAT_XTOKEN', 'tokentokentoken')
######################################
# SMF Configuration
######################################
SMF_URL = os.environ.get('AA_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 = os.environ.get('AA_FLEETUP_APP_KEY', '')
FLEETUP_USER_ID = os.environ.get('AA_FLEETUP_USER_ID', '')
FLEETUP_API_ID = os.environ.get('AA_FLEETUP_API_ID', '')
FLEETUP_GROUP_ID = os.environ.get('AA_FLEETUP_GROUP_ID', '')
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.MD5PasswordHasher',
]
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format' : "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s",
'datefmt' : "%d/%b/%Y %H:%M:%S"
},
'simple': {
'format': '%(levelname)s %(message)s'
},
},
'handlers': {
'console': {
'level': 'DEBUG', # edit this line to change logging level to console
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
'notifications': { # creates notifications for users with logging_notifications permission
'level': 'ERROR', # edit this line to change logging level to notifications
'class': 'notifications.handlers.NotificationHandler',
'formatter': 'verbose',
},
},
'loggers': {
'authentication': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'celerytask': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'eveonline': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'groupmanagement': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'hrapplications': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'portal': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'registration': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'services': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'srp': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'timerboard': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'sigtracker': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'optimer': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'corputils': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'fleetactivitytracking': {
'handlers': ['console', 'notifications'],
'level': 'ERROR',
},
'util': {
'handlers': ['console', 'notifications'],
'level': 'DEBUG',
},
'django': {
'handlers': ['console', 'notifications'],
'level': 'ERROR',
},
}
}
LOGGING = None # Comment out to enable logging for debugging

222
alliance_auth/urls.py Executable file
View File

@@ -0,0 +1,222 @@
from django.conf.urls import include, url
from django.conf.urls.i18n import i18n_patterns
from django.utils.translation import ugettext_lazy as _
from django.contrib import admin
import django.contrib.auth.views
import authentication.views
import eveonline.views
import services.views
import groupmanagement.views
import optimer.views
import timerboard.views
import fleetactivitytracking.views
import fleetup.urls
import srp.views
import notifications.views
import hrapplications.views
import corputils.urls
import esi.urls
import permissions_tool.urls
from alliance_auth import NAME
admin.site.site_header = NAME
from alliance_auth.hooks import get_hooks
# Functional/Untranslated URL's
urlpatterns = [
# Locale
url(r'^i18n/', include('django.conf.urls.i18n')),
# Admin urls
url(r'^admin/', include(admin.site.urls)),
# SSO
url(r'^sso/', include(esi.urls, namespace='esi')),
url(r'^sso/login$', authentication.views.sso_login, name='auth_sso_login'),
# Index
url(_(r'^$'), authentication.views.index_view, name='auth_index'),
# Authentication
url(r'^logout_user/', authentication.views.logout_user, name='auth_logout_user'),
# Eve Online
url(r'^main_character_change/(\w+)/$', eveonline.views.main_character_change,
name='auth_main_character_change'),
url(r'^api_verify_owner/(\w+)/$', eveonline.views.api_sso_validate, name='auth_api_sso'),
# SRP URLS
url(r'^srp_fleet_remove/(\w+)$', srp.views.srp_fleet_remove, name='auth_srp_fleet_remove'),
url(r'^srp_fleet_disable/(\w+)$', srp.views.srp_fleet_disable, name='auth_srp_fleet_disable'),
url(r'^srp_fleet_enable/(\w+)$', srp.views.srp_fleet_enable, name='auth_srp_fleet_enable'),
url(r'^srp_fleet_mark_completed/(\w+)', srp.views.srp_fleet_mark_completed,
name='auth_srp_fleet_mark_completed'),
url(r'^srp_fleet_mark_uncompleted/(\w+)', srp.views.srp_fleet_mark_uncompleted,
name='auth_srp_fleet_mark_uncompleted'),
url(r'^srp_request_remove/', srp.views.srp_request_remove,
name="auth_srp_request_remove"),
url(r'srp_request_approve/', srp.views.srp_request_approve,
name='auth_srp_request_approve'),
url(r'srp_request_reject/', srp.views.srp_request_reject,
name='auth_srp_request_reject'),
url(_(r'srp_request_amount_update/(\w+)'), srp.views.srp_request_update_amount,
name="auth_srp_request_update_amount"),
# Notifications
url(r'^remove_notifications/(\w+)/$', notifications.views.remove_notification, name='auth_remove_notification'),
url(r'^notifications/mark_all_read/$', notifications.views.mark_all_read, name='auth_mark_all_notifications_read'),
url(r'^notifications/delete_all_read/$', notifications.views.delete_all_read,
name='auth_delete_all_read_notifications'),
]
# User viewed/translated URLS
urlpatterns += i18n_patterns(
# Fleetup
url(r'^fleetup/', include(fleetup.urls.urlpatterns)),
# Authentication
url(_(r'^login_user/'), authentication.views.login_user, name='auth_login_user'),
url(_(r'^register_user/'), authentication.views.register_user_view, name='auth_register_user'),
url(_(r'^user/password/$'), django.contrib.auth.views.password_change, name='password_change'),
url(_(r'^user/password/done/$'), django.contrib.auth.views.password_change_done,
name='password_change_done'),
url(_(r'^user/password/reset/$'), django.contrib.auth.views.password_reset,
name='password_reset'),
url(_(r'^user/password/password/reset/done/$'), django.contrib.auth.views.password_reset_done,
name='password_reset_done'),
url(_(r'^user/password/reset/complete/$'), django.contrib.auth.views.password_reset_complete,
name='password_reset_complete'),
url(_(r'^user/password/reset/confirm/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>.+)/$'),
django.contrib.auth.views.password_reset_confirm, name='password_reset_confirm'),
# Portal Urls
url(_(r'^dashboard/$'), eveonline.views.dashboard_view, name='auth_dashboard'),
url(_(r'^help/$'), authentication.views.help_view, name='auth_help'),
# Eveonline Urls
url(_(r'^add_api_key/'), eveonline.views.add_api_key, name='auth_add_api_key'),
url(_(r'^refresh_api_pair/([0-9]+)/$'), eveonline.views.user_refresh_api, name='auth_user_refresh_api'),
url(_(r'^delete_api_pair/(\w+)/$'), eveonline.views.api_key_removal, name='auth_api_key_removal'),
url(_(r'^characters/'), eveonline.views.characters_view, name='auth_characters'),
# Corputils
url(_(r'^corpstats/'), include(corputils.urls, namespace='corputils')),
# Group management
url(_(r'^groups/'), groupmanagement.views.groups_view, name='auth_groups'),
url(_(r'^group/management/'), groupmanagement.views.group_management,
name='auth_group_management'),
url(_(r'^group/membership/$'), groupmanagement.views.group_membership,
name='auth_group_membership'),
url(_(r'^group/membership/(\w+)/$'), groupmanagement.views.group_membership_list,
name='auth_group_membership_list'),
url(_(r'^group/membership/(\w+)/remove/(\w+)/$'), groupmanagement.views.group_membership_remove,
name='auth_group_membership_remove'),
url(_(r'^group/request_add/(\w+)'), groupmanagement.views.group_request_add,
name='auth_group_request_add'),
url(_(r'^group/request/accept/(\w+)'), groupmanagement.views.group_accept_request,
name='auth_group_accept_request'),
url(_(r'^group/request/reject/(\w+)'), groupmanagement.views.group_reject_request,
name='auth_group_reject_request'),
url(_(r'^group/request_leave/(\w+)'), groupmanagement.views.group_request_leave,
name='auth_group_request_leave'),
url(_(r'group/leave_request/accept/(\w+)'), groupmanagement.views.group_leave_accept_request,
name='auth_group_leave_accept_request'),
url(_(r'^group/leave_request/reject/(\w+)'), groupmanagement.views.group_leave_reject_request,
name='auth_group_leave_reject_request'),
# HR Application Management
url(_(r'^hr_application_management/'), hrapplications.views.hr_application_management_view,
name="auth_hrapplications_view"),
url(_(r'^hr_application_create/$'), hrapplications.views.hr_application_create_view,
name="auth_hrapplication_create_view"),
url(_(r'^hr_application_create/(\d+)'), hrapplications.views.hr_application_create_view,
name="auth_hrapplication_create_view"),
url(_(r'^hr_application_remove/(\w+)'), hrapplications.views.hr_application_remove,
name="auth_hrapplication_remove"),
url(_(r'hr_application_view/(\w+)'), hrapplications.views.hr_application_view,
name="auth_hrapplication_view"),
url(_(r'hr_application_personal_view/(\w+)'), hrapplications.views.hr_application_personal_view,
name="auth_hrapplication_personal_view"),
url(_(r'hr_application_personal_removal/(\w+)'),
hrapplications.views.hr_application_personal_removal,
name="auth_hrapplication_personal_removal"),
url(_(r'hr_application_approve/(\w+)'), hrapplications.views.hr_application_approve,
name="auth_hrapplication_approve"),
url(_(r'hr_application_reject/(\w+)'), hrapplications.views.hr_application_reject,
name="auth_hrapplication_reject"),
url(_(r'hr_application_search/'), hrapplications.views.hr_application_search,
name="auth_hrapplication_search"),
url(_(r'hr_mark_in_progress/(\w+)'), hrapplications.views.hr_application_mark_in_progress,
name="auth_hrapplication_mark_in_progress"),
# Fleet Operations Timers
url(_(r'^optimer/$'), optimer.views.optimer_view, name='auth_optimer_view'),
url(_(r'^add_optimer/$'), optimer.views.add_optimer_view, name='auth_add_optimer_view'),
url(_(r'^remove_optimer/(\w+)'), optimer.views.remove_optimer, name='auth_remove_optimer'),
url(_(r'^edit_optimer/(\w+)$'), optimer.views.edit_optimer, name='auth_edit_optimer'),
# Service Urls
url(_(r'^services/$'), services.views.services_view, name='auth_services'),
# Timer URLS
url(_(r'^timers/$'), timerboard.views.timer_view, name='auth_timer_view'),
url(_(r'^add_timer/$'), timerboard.views.add_timer_view, name='auth_add_timer_view'),
url(_(r'^remove_timer/(\w+)'), timerboard.views.remove_timer, name='auth_remove_timer'),
url(_(r'^edit_timer/(\w+)$'), timerboard.views.edit_timer, name='auth_edit_timer'),
# SRP URLS
url(_(r'^srp/$'), srp.views.srp_management, name='auth_srp_management_view'),
url(_(r'^srp_all/$'), srp.views.srp_management_all, name='auth_srp_management_all_view'),
url(_(r'^srp_fleet_view/(\w+)$'), srp.views.srp_fleet_view, name='auth_srp_fleet_view'),
url(_(r'^srp_fleet_add_view/$'), srp.views.srp_fleet_add_view, name='auth_srp_fleet_add_view'),
url(_(r'^srp_fleet_edit/(\w+)$'), srp.views.srp_fleet_edit_view, name='auth_srp_fleet_edit_view'),
url(_(r'^srp_request/(\w+)'), srp.views.srp_request_view, name='auth_srp_request_view'),
# Tools
url(_(r'^tool/fleet_formatter_tool/$'), services.views.fleet_formatter_view,
name='auth_fleet_format_tool_view'),
# Notifications
url(_(r'^notifications/$'), notifications.views.notification_list, name='auth_notification_list'),
url(_(r'^notifications/(\w+)/$'), notifications.views.notification_view, name='auth_notification_view'),
# FleetActivityTracking (FAT)
url(r'^fat/$', fleetactivitytracking.views.fatlink_view, name='auth_fatlink_view'),
url(r'^fat/statistics/$', fleetactivitytracking.views.fatlink_statistics_view, name='auth_fatlink_view_statistics'),
url(r'^fat/statistics/corp/(\w+)$', fleetactivitytracking.views.fatlink_statistics_corp_view, name='auth_fatlink_view_statistics_corp'),
url(r'^fat/statistics/corp/(?P<corpid>\w+)/(?P<year>[0-9]+)/(?P<month>[0-9]+)/', fleetactivitytracking.views.fatlink_statistics_corp_view,
name='auth_fatlink_view_statistics_corp_month'),
url(r'^fat/statistics/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$', fleetactivitytracking.views.fatlink_statistics_view,
name='auth_fatlink_view_statistics_month'),
url(r'^fat/user/statistics/$', fleetactivitytracking.views.fatlink_personal_statistics_view,
name='auth_fatlink_view_personal_statistics'),
url(r'^fat/user/statistics/(?P<year>[0-9]+)/$', fleetactivitytracking.views.fatlink_personal_statistics_view,
name='auth_fatlink_view_personal_statistics_year'),
url(r'^fat/user/statistics/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$',
fleetactivitytracking.views.fatlink_monthly_personal_statistics_view,
name='auth_fatlink_view_personal_statistics_month'),
url(r'^fat/user/(?P<char_id>[0-9]+)/statistics/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$',
fleetactivitytracking.views.fatlink_monthly_personal_statistics_view,
name='auth_fatlink_view_user_statistics_month'),
url(r'^fat/create/$', fleetactivitytracking.views.create_fatlink_view, name='auth_create_fatlink_view'),
url(r'^fat/modify/$', fleetactivitytracking.views.modify_fatlink_view, name='auth_modify_fatlink_view'),
url(r'^fat/modify/(?P<hash>[a-zA-Z0-9_-]+)/([a-z0-9_-]+)$',
fleetactivitytracking.views.modify_fatlink_view),
url(r'^fat/link/$', fleetactivitytracking.views.fatlink_view, name='auth_click_fatlink_view'),
url(r'^fat/link/(?P<hash>[a-zA-Z0-9]+)/(?P<fatname>[a-z0-9_-]+)/$',
fleetactivitytracking.views.click_fatlink_view),
url(r'^permissions/', include(permissions_tool.urls))
)
# Append hooked service urls
services = get_hooks('services_hook')
for svc in services:
urlpatterns += svc().urlpatterns

20
alliance_auth/wsgi.py Normal file
View File

@@ -0,0 +1,20 @@
"""
WSGI config for alliance_auth project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "alliance_auth.settings")
# virtualenv wrapper, uncomment below to activate
# activate_env=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'env/bin/activate_this.py')
# execfile(activate_env, dict(__file__=activate_env))
application = get_wsgi_application()

View File

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

View File

@@ -1,5 +0,0 @@
from django.apps import AppConfig
class AllianceAuthConfig(AppConfig):
name = 'allianceauth'

View File

@@ -1 +0,0 @@
default_app_config = 'allianceauth.authentication.apps.AuthenticationConfig'

View File

@@ -1,615 +0,0 @@
from django.conf import settings
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User as BaseUser, \
Permission as BasePermission, Group
from django.db.models import Q, F
from allianceauth.services.hooks import ServicesHook
from django.db.models.signals import pre_save, post_save, pre_delete, \
post_delete, m2m_changed
from django.db.models.functions import Lower
from django.dispatch import receiver
from django.forms import ModelForm
from django.utils.html import format_html
from django.urls import reverse
from django.utils.text import slugify
from allianceauth.authentication.models import State, get_guest_state,\
CharacterOwnership, UserProfile, OwnershipRecord
from allianceauth.hooks import get_hooks
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\
EveAllianceInfo
from allianceauth.eveonline.tasks import update_character
from .app_settings import AUTHENTICATION_ADMIN_USERS_MAX_GROUPS, \
AUTHENTICATION_ADMIN_USERS_MAX_CHARS
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True
else:
_has_auto_groups = False
def make_service_hooks_update_groups_action(service):
"""
Make a admin action for the given service
:param service: services.hooks.ServicesHook
:return: fn to update services groups for the selected users
"""
def update_service_groups(modeladmin, request, queryset):
if hasattr(service, 'update_groups_bulk'):
service.update_groups_bulk(queryset)
else:
for user in queryset: # queryset filtering doesn't work here?
service.update_groups(user)
update_service_groups.__name__ = str('update_{}_groups'.format(slugify(service.name)))
update_service_groups.short_description = "Sync groups for selected {} accounts".format(service.title)
return update_service_groups
def make_service_hooks_sync_nickname_action(service):
"""
Make a sync_nickname admin action for the given service
:param service: services.hooks.ServicesHook
:return: fn to sync nickname for the selected users
"""
def sync_nickname(modeladmin, request, queryset):
if hasattr(service, 'sync_nicknames_bulk'):
service.sync_nicknames_bulk(queryset)
else:
for user in queryset: # queryset filtering doesn't work here?
service.sync_nickname(user)
sync_nickname.__name__ = str('sync_{}_nickname'.format(slugify(service.name)))
sync_nickname.short_description = "Sync nicknames for selected {} accounts".format(service.title)
return sync_nickname
class QuerysetModelForm(ModelForm):
# allows specifying FK querysets through kwarg
def __init__(self, querysets=None, *args, **kwargs):
querysets = querysets or {}
super().__init__(*args, **kwargs)
for field, qs in querysets.items():
self.fields[field].queryset = qs
class UserProfileInline(admin.StackedInline):
model = UserProfile
readonly_fields = ('state',)
form = QuerysetModelForm
verbose_name = ''
verbose_name_plural = 'Profile'
def get_formset(self, request, obj=None, **kwargs):
# main_character field can only show current value or unclaimed alts
# if superuser, allow selecting from any unclaimed main
query = Q()
if obj and obj.profile.main_character:
query |= Q(pk=obj.profile.main_character_id)
if request.user.is_superuser:
query |= Q(userprofile__isnull=True)
else:
query |= Q(character_ownership__user=obj)
qs = EveCharacter.objects.filter(query)
formset = super().get_formset(request, obj=obj, **kwargs)
def get_kwargs(self, index):
return {'querysets': {'main_character': EveCharacter.objects.filter(query)}}
formset.get_form_kwargs = get_kwargs
return formset
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
def user_profile_pic(obj):
"""profile pic column data for user objects
works for both User objects and objects with `user` as FK to User
To be used for all user based admin lists (requires CSS)
"""
user_obj = obj.user if hasattr(obj, 'user') else obj
if user_obj.profile.main_character:
return format_html(
'<img src="{}" class="img-circle">',
user_obj.profile.main_character.portrait_url(size=32)
)
else:
return None
user_profile_pic.short_description = ''
def user_username(obj):
"""user column data for user objects
works for both User objects and objects with `user` as FK to User
To be used for all user based admin lists
"""
link = reverse(
'admin:{}_{}_change'.format(
obj._meta.app_label,
type(obj).__name__.lower()
),
args=(obj.pk,)
)
user_obj = obj.user if hasattr(obj, 'user') else obj
if user_obj.profile.main_character:
return format_html(
'<strong><a href="{}">{}</a></strong><br>{}',
link,
user_obj.username,
user_obj.profile.main_character.character_name
)
else:
return format_html(
'<strong><a href="{}">{}</a></strong>',
link,
user_obj.username,
)
user_username.short_description = 'user / main'
user_username.admin_order_field = 'username'
def user_main_organization(obj):
"""main organization column data for user objects
works for both User objects and objects with `user` as FK to User
To be used for all user based admin lists
"""
user_obj = obj.user if hasattr(obj, 'user') else obj
if not user_obj.profile.main_character:
result = None
else:
corporation = user_obj.profile.main_character.corporation_name
if user_obj.profile.main_character.alliance_id:
result = format_html('{}<br>{}',
corporation,
user_obj.profile.main_character.alliance_name
)
else:
result = corporation
return result
user_main_organization.short_description = 'Corporation / Alliance (Main)'
user_main_organization.admin_order_field = \
'profile__main_character__corporation_name'
class MainCorporationsFilter(admin.SimpleListFilter):
"""Custom filter to filter on corporations from mains only
works for both User objects and objects with `user` as FK to User
To be used for all user based admin lists
"""
title = 'corporation'
parameter_name = 'main_corporation_id__exact'
def lookups(self, request, model_admin):
qs = EveCharacter.objects\
.exclude(userprofile=None)\
.values('corporation_id', 'corporation_name')\
.distinct()\
.order_by(Lower('corporation_name'))
return tuple(
[(x['corporation_id'], x['corporation_name']) for x in qs]
)
def queryset(self, request, qs):
if self.value() is None:
return qs.all()
else:
if qs.model == User:
return qs\
.filter(profile__main_character__corporation_id=\
self.value())
else:
return qs\
.filter(user__profile__main_character__corporation_id=\
self.value())
class MainAllianceFilter(admin.SimpleListFilter):
"""Custom filter to filter on alliances from mains only
works for both User objects and objects with `user` as FK to User
To be used for all user based admin lists
"""
title = 'alliance'
parameter_name = 'main_alliance_id__exact'
def lookups(self, request, model_admin):
qs = EveCharacter.objects\
.exclude(alliance_id=None)\
.exclude(userprofile=None)\
.values('alliance_id', 'alliance_name')\
.distinct()\
.order_by(Lower('alliance_name'))
return tuple(
[(x['alliance_id'], x['alliance_name']) for x in qs]
)
def queryset(self, request, qs):
if self.value() is None:
return qs.all()
else:
if qs.model == User:
return qs\
.filter(profile__main_character__alliance_id=self.value())
else:
return qs\
.filter(user__profile__main_character__alliance_id=\
self.value())
def update_main_character_model(modeladmin, request, queryset):
tasks_count = 0
for obj in queryset:
if obj.profile.main_character:
update_character.delay(obj.profile.main_character.character_id)
tasks_count += 1
modeladmin.message_user(
request,
'Update from ESI started for {} characters'.format(tasks_count)
)
update_main_character_model.short_description = \
'Update main character model from ESI'
class UserAdmin(BaseUserAdmin):
"""Extending Django's UserAdmin model
Behavior of groups and characters columns can be configured via settings
"""
class Media:
css = {
"all": ("authentication/css/admin.css",)
}
class RealGroupsFilter(admin.SimpleListFilter):
"""Custom filter to get groups w/o Autogroups"""
title = 'group'
parameter_name = 'group_id__exact'
def lookups(self, request, model_admin):
qs = Group.objects.all().order_by(Lower('name'))
if _has_auto_groups:
qs = qs\
.filter(managedalliancegroup__isnull=True)\
.filter(managedcorpgroup__isnull=True)
return tuple([(x.pk, x.name) for x in qs])
def queryset(self, request, queryset):
if self.value() is None:
return queryset.all()
else:
return queryset.filter(groups__pk=self.value())
def get_actions(self, request):
actions = super(BaseUserAdmin, self).get_actions(request)
actions[update_main_character_model.__name__] = (
update_main_character_model,
update_main_character_model.__name__,
update_main_character_model.short_description
)
for hook in get_hooks('services_hook'):
svc = hook()
# Check update_groups is redefined/overloaded
if svc.update_groups.__module__ != ServicesHook.update_groups.__module__:
action = make_service_hooks_update_groups_action(svc)
actions[action.__name__] = (
action,
action.__name__,
action.short_description
)
# Create sync nickname action if service implements it
if svc.sync_nickname.__module__ != ServicesHook.sync_nickname.__module__:
action = make_service_hooks_sync_nickname_action(svc)
actions[action.__name__] = (
action, action.__name__,
action.short_description
)
return actions
def _list_2_html_w_tooltips(self, my_items: list, max_items: int) -> str:
"""converts list of strings into HTML with cutoff and tooltip"""
items_truncated_str = ', '.join(my_items[:max_items])
if not my_items:
result = None
elif len(my_items) <= max_items:
result = items_truncated_str
else:
items_truncated_str += ', (...)'
items_all_str = ', '.join(my_items)
result = format_html(
'<span data-tooltip="{}" class="tooltip">{}</span>',
items_all_str,
items_truncated_str
)
return result
inlines = BaseUserAdmin.inlines + [UserProfileInline]
ordering = ('username', )
list_select_related = True
show_full_result_count = True
list_display = (
user_profile_pic,
user_username,
'_state',
'_groups',
user_main_organization,
'_characters',
'is_active',
'date_joined',
'_role'
)
list_display_links = None
list_filter = (
'profile__state',
RealGroupsFilter,
MainCorporationsFilter,
MainAllianceFilter,
'is_active',
'date_joined',
'is_staff',
'is_superuser'
)
search_fields = (
'username',
'character_ownerships__character__character_name'
)
def _characters(self, obj):
my_characters = [
x.character.character_name
for x in CharacterOwnership.objects\
.filter(user=obj)\
.order_by('character__character_name')\
.select_related()
]
return self._list_2_html_w_tooltips(
my_characters,
AUTHENTICATION_ADMIN_USERS_MAX_CHARS
)
_characters.short_description = 'characters'
def _state(self, obj):
return obj.profile.state.name
_state.short_description = 'state'
_state.admin_order_field = 'profile__state'
def _groups(self, obj):
if not _has_auto_groups:
my_groups = [x.name for x in obj.groups.order_by('name')]
else:
my_groups = [
x.name for x in obj.groups\
.filter(managedalliancegroup__isnull=True)\
.filter(managedcorpgroup__isnull=True)\
.order_by('name')
]
return self._list_2_html_w_tooltips(
my_groups,
AUTHENTICATION_ADMIN_USERS_MAX_GROUPS
)
_groups.short_description = 'groups'
def _role(self, obj):
if obj.is_superuser:
role = 'Superuser'
elif obj.is_staff:
role = 'Staff'
else:
role = 'User'
return role
_role.short_description = 'role'
def has_change_permission(self, request, obj=None):
return request.user.has_perm('auth.change_user')
def has_add_permission(self, request, obj=None):
return request.user.has_perm('auth.add_user')
def has_delete_permission(self, request, obj=None):
return request.user.has_perm('auth.delete_user')
def formfield_for_manytomany(self, db_field, request, **kwargs):
"""overriding this formfield to have sorted lists in the form"""
if db_field.name == "groups":
kwargs["queryset"] = Group.objects.all().order_by(Lower('name'))
return super().formfield_for_manytomany(db_field, request, **kwargs)
@admin.register(State)
class StateAdmin(admin.ModelAdmin):
list_select_related = True
list_display = ('name', 'priority', '_user_count')
def _user_count(self, obj):
return obj.userprofile_set.all().count()
_user_count.short_description = 'Users'
fieldsets = (
(None, {
'fields': ('name', 'permissions', 'priority'),
}),
('Membership', {
'fields': (
'public',
'member_characters',
'member_corporations',
'member_alliances'
),
})
)
filter_horizontal = [
'member_characters',
'member_corporations',
'member_alliances',
'permissions'
]
def formfield_for_manytomany(self, db_field, request, **kwargs):
"""overriding this formfield to have sorted lists in the form"""
if db_field.name == "member_characters":
kwargs["queryset"] = EveCharacter.objects.all()\
.order_by(Lower('character_name'))
elif db_field.name == "member_corporations":
kwargs["queryset"] = EveCorporationInfo.objects.all()\
.order_by(Lower('corporation_name'))
elif db_field.name == "member_alliances":
kwargs["queryset"] = EveAllianceInfo.objects.all()\
.order_by(Lower('alliance_name'))
return super().formfield_for_manytomany(db_field, request, **kwargs)
def has_delete_permission(self, request, obj=None):
if obj == get_guest_state():
return False
return super(StateAdmin, self).has_delete_permission(request, obj=obj)
def get_fieldsets(self, request, obj=None):
if obj == get_guest_state():
return (
(None, {
'fields': ('permissions', 'priority'),
}),
)
return super(StateAdmin, self).get_fieldsets(request, obj=obj)
class BaseOwnershipAdmin(admin.ModelAdmin):
class Media:
css = {
"all": ("authentication/css/admin.css",)
}
list_select_related = True
list_display = (
user_profile_pic,
user_username,
user_main_organization,
'character',
)
search_fields = (
'user__username',
'character__character_name',
'character__corporation_name',
'character__alliance_name'
)
list_filter = (
MainCorporationsFilter,
MainAllianceFilter,
)
def get_readonly_fields(self, request, obj=None):
if obj and obj.pk:
return 'owner_hash', 'character'
return tuple()
@admin.register(OwnershipRecord)
class OwnershipRecordAdmin(BaseOwnershipAdmin):
list_display = BaseOwnershipAdmin.list_display + ('created',)
@admin.register(CharacterOwnership)
class CharacterOwnershipAdmin(BaseOwnershipAdmin):
def has_add_permission(self, request):
return False
class PermissionAdmin(admin.ModelAdmin):
actions = None
readonly_fields = [field.name for field in BasePermission._meta.fields]
list_display = ('admin_name', 'name', 'codename', 'content_type')
list_filter = ('content_type__app_label',)
@staticmethod
def admin_name(obj):
return str(obj)
def has_add_permission(self, request, obj=None):
return False
def has_delete_permission(self, request, obj=None):
return False
def has_module_permission(self, request):
return True
def has_change_permission(self, request, obj=None):
# can see list but not edit it
return not obj
# Hack to allow registration of django.contrib.auth models in our authentication app
class User(BaseUser):
class Meta:
proxy = True
verbose_name = BaseUser._meta.verbose_name
verbose_name_plural = BaseUser._meta.verbose_name_plural
class Permission(BasePermission):
class Meta:
proxy = True
verbose_name = BasePermission._meta.verbose_name
verbose_name_plural = BasePermission._meta.verbose_name_plural
try:
admin.site.unregister(BaseUser)
finally:
admin.site.register(User, UserAdmin)
admin.site.register(Permission, PermissionAdmin)
@receiver(pre_save, sender=User)
def redirect_pre_save(sender, signal=None, *args, **kwargs):
pre_save.send(BaseUser, *args, **kwargs)
@receiver(post_save, sender=User)
def redirect_post_save(sender, signal=None, *args, **kwargs):
post_save.send(BaseUser, *args, **kwargs)
@receiver(pre_delete, sender=User)
def redirect_pre_delete(sender, signal=None, *args, **kwargs):
pre_delete.send(BaseUser, *args, **kwargs)
@receiver(post_delete, sender=User)
def redirect_post_delete(sender, signal=None, *args, **kwargs):
post_delete.send(BaseUser, *args, **kwargs)
@receiver(m2m_changed, sender=User.groups.through)
def redirect_m2m_changed_groups(sender, signal=None, *args, **kwargs):
m2m_changed.send(BaseUser, *args, **kwargs)
@receiver(m2m_changed, sender=User.user_permissions.through)
def redirect_m2m_changed_permissions(sender, signal=None, *args, **kwargs):
m2m_changed.send(BaseUser, *args, **kwargs)

View File

@@ -1,46 +0,0 @@
from django.conf import settings
def _clean_setting(
name: str,
default_value: object,
min_value: int = None,
max_value: int = None,
required_type: type = None
):
"""cleans the input for a custom setting
Will use `default_value` if settings does not exit or has the wrong type
or is outside define boundaries (for int only)
Need to define `required_type` if `default_value` is `None`
Will assume `min_value` of 0 for int (can be overriden)
Returns cleaned value for setting
"""
if default_value is None and not required_type:
raise ValueError('You must specify a required_type for None defaults')
if not required_type:
required_type = type(default_value)
if min_value is None and required_type == int:
min_value = 0
if (hasattr(settings, name)
and isinstance(getattr(settings, name), required_type)
and (min_value is None or getattr(settings, name) >= min_value)
and (max_value is None or getattr(settings, name) <= max_value)
):
return getattr(settings, name)
else:
return default_value
AUTHENTICATION_ADMIN_USERS_MAX_GROUPS = \
_clean_setting('AUTHENTICATION_ADMIN_USERS_MAX_GROUPS', 10)
AUTHENTICATION_ADMIN_USERS_MAX_CHARS = \
_clean_setting('AUTHENTICATION_ADMIN_USERS_MAX_CHARS', 5)

View File

@@ -1,12 +0,0 @@
from django.apps import AppConfig
from django.core.checks import register, Tags
class AuthenticationConfig(AppConfig):
name = 'allianceauth.authentication'
label = 'authentication'
def ready(self):
super(AuthenticationConfig, self).ready()
from allianceauth.authentication import checks, signals
register(Tags.security)(checks.check_login_scopes_setting)

View File

@@ -1,96 +0,0 @@
import logging
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User, Permission
from .models import UserProfile, CharacterOwnership, OwnershipRecord
logger = logging.getLogger(__name__)
class StateBackend(ModelBackend):
@staticmethod
def _get_state_permissions(user_obj):
"""returns permissions for state of given user object"""
if hasattr(user_obj, "profile") and user_obj.profile:
return Permission.objects.filter(state=user_obj.profile.state)
else:
return Permission.objects.none()
def get_state_permissions(self, user_obj, obj=None):
return self._get_permissions(user_obj, obj, 'state')
def get_all_permissions(self, user_obj, obj=None):
if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
return set()
if not hasattr(user_obj, '_perm_cache'):
user_obj._perm_cache = self.get_user_permissions(user_obj)
user_obj._perm_cache.update(self.get_group_permissions(user_obj))
user_obj._perm_cache.update(self.get_state_permissions(user_obj))
return user_obj._perm_cache
def authenticate(self, request=None, token=None, **credentials):
if not token:
return None
try:
ownership = CharacterOwnership.objects.get(character__character_id=token.character_id)
if ownership.owner_hash == token.character_owner_hash:
logger.debug('Authenticating {0} by ownership of character {1}'.format(ownership.user, token.character_name))
return ownership.user
else:
logger.debug('{0} has changed ownership. Creating new user account.'.format(token.character_name))
ownership.delete()
return self.create_user(token)
except CharacterOwnership.DoesNotExist:
try:
# insecure legacy main check for pre-sso registration auth installs
profile = UserProfile.objects.get(main_character__character_id=token.character_id)
logger.debug('Authenticating {0} by their main character {1} without active ownership.'.format(profile.user, profile.main_character))
# attach an ownership
token.user = profile.user
CharacterOwnership.objects.create_by_token(token)
return profile.user
except UserProfile.DoesNotExist:
# now we check historical records to see if this is a returning user
records = OwnershipRecord.objects.filter(owner_hash=token.character_owner_hash).filter(character__character_id=token.character_id)
if records.exists():
# we've seen this character owner before. Re-attach to their old user account
user = records[0].user
token.user = user
co = CharacterOwnership.objects.create_by_token(token)
logger.debug('Authenticating {0} by matching owner hash record of character {1}'.format(user, co.character))
if not user.profile.main_character:
# set this as their main by default if they have none
user.profile.main_character = co.character
user.profile.save()
return user
logger.debug('Unable to authenticate character {0}. Creating new user.'.format(token.character_name))
return self.create_user(token)
def create_user(self, token):
username = self.iterate_username(token.character_name) # build unique username off character name
user = User.objects.create_user(username, is_active=False) # prevent login until email set
user.set_unusable_password() # prevent login via password
user.save()
token.user = user
co = CharacterOwnership.objects.create_by_token(token) # assign ownership to this user
user.profile.main_character = co.character # assign main character as token character
user.profile.save()
logger.debug('Created new user {0}'.format(user))
return user
@staticmethod
def iterate_username(name):
name = str.replace(name, "'", "")
name = str.replace(name, ' ', '_')
if User.objects.filter(username__startswith=name).exists():
u = User.objects.filter(username__startswith=name)
num = len(u)
username = "%s_%s" % (name, num)
while u.filter(username=username).exists():
num += 1
username = "%s_%s" % (name, num)
else:
username = name
return username

View File

@@ -1,12 +0,0 @@
from django.core.checks import Error
from django.conf import settings
def check_login_scopes_setting(*args, **kwargs):
errors = []
try:
assert settings.LOGIN_TOKEN_SCOPES
except (AssertionError, AttributeError):
errors.append(Error('LOGIN_TOKEN_SCOPES setting cannot be blank.',
hint='SSO tokens used for logging in must require scopes to be refreshable.'))
return errors

View File

@@ -1,68 +0,0 @@
from django.conf.urls import include
from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import PermissionDenied
from functools import wraps
from django.shortcuts import redirect
from django.contrib import messages
from django.utils.translation import gettext_lazy as _
from django.contrib.auth.decorators import login_required
def user_has_main_character(user):
return bool(user.profile.main_character)
def decorate_url_patterns(urls, decorator):
url_list, app_name, namespace = include(urls)
def process_patterns(url_patterns):
for pattern in url_patterns:
if hasattr(pattern, 'url_patterns'):
# this is an include - apply to all nested patterns
process_patterns(pattern.url_patterns)
else:
# this is a pattern
pattern.callback = decorator(pattern.callback)
process_patterns(url_list)
return url_list, app_name, namespace
def main_character_required(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
if user_has_main_character(request.user):
return view_func(request, *args, **kwargs)
messages.error(request, _('A main character is required to perform that action. Add one below.'))
return redirect('authentication:dashboard')
return login_required(_wrapped_view)
def permissions_required(perm, login_url=None, raise_exception=False):
"""
Decorator for views that checks whether a user has a particular permission
enabled, redirecting to the log-in page if necessary.
If the raise_exception parameter is given the PermissionDenied exception
is raised.
This decorator is identical to the django permission_required except it
allows for passing a tuple/list of perms that will return true if any one
of them is present.
"""
def check_perms(user):
if isinstance(perm, str):
perms = (perm,)
else:
perms = perm
# First check if the user has the permission (even anon users)
for perm_ in perms:
perm_ = (perm_,)
if user.has_perms(perm_):
return True
# In case the 403 handler should be called raise the exception
if raise_exception:
raise PermissionDenied
# As the last resort, show the login form
return False
return user_passes_test(check_perms, login_url=login_url)

View File

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

View File

@@ -1,14 +0,0 @@
from django.conf.urls import url, include
from allianceauth.authentication import views
urlpatterns = [
url(r'^activate/complete/$', views.activation_complete, name='registration_activation_complete'),
# The activation key can make use of any character from the
# URL-safe base64 alphabet, plus the colon as a separator.
url(r'^activate/(?P<activation_key>[-:\w]+)/$', views.ActivationView.as_view(), name='registration_activate'),
url(r'^register/$', views.RegistrationView.as_view(), name='registration_register'),
url(r'^register/complete/$', views.registration_complete, name='registration_complete'),
url(r'^register/closed/$', views.registration_closed, name='registration_disallowed'),
url(r'', include('django.contrib.auth.urls')),
]

View File

@@ -1,20 +0,0 @@
from django.core.management.base import BaseCommand
from allianceauth.authentication.models import UserProfile
class Command(BaseCommand):
help = 'Ensures all main characters have an active ownership'
def handle(self, *args, **options):
profiles = UserProfile.objects.filter(main_character__isnull=False).filter(
main_character__character_ownership__isnull=True)
if profiles.exists():
for profile in profiles:
self.stdout.write(self.style.ERROR(
'{0} does not have an ownership. Resetting user {1} main character.'.format(profile.main_character,
profile.user)))
profile.main_character = None
profile.save()
self.stdout.write(self.style.WARNING('Reset {0} main characters.'.format(profiles.count())))
else:
self.stdout.write(self.style.SUCCESS('All main characters have active ownership.'))

View File

@@ -1,75 +0,0 @@
import logging
from django.db import transaction
from django.db.models import Manager, QuerySet, Q
from allianceauth.eveonline.models import EveCharacter
logger = logging.getLogger(__name__)
def available_states_query(character):
query = Q(public=True)
if character.character_id:
query |= Q(member_characters__character_id=character.character_id)
if character.corporation_id:
query |= Q(member_corporations__corporation_id=character.corporation_id)
if character.alliance_id:
query |= Q(member_alliances__alliance_id=character.alliance_id)
return query
class CharacterOwnershipManager(Manager):
def create_by_token(self, token):
if not EveCharacter.objects.filter(character_id=token.character_id).exists():
EveCharacter.objects.create_character(token.character_id)
return self.create(character=EveCharacter.objects.get(character_id=token.character_id), user=token.user,
owner_hash=token.character_owner_hash)
class StateQuerySet(QuerySet):
def available_to_character(self, character):
return self.filter(available_states_query(character))
def available_to_user(self, user):
if user.profile.main_character:
return self.available_to_character(user.profile.main_character)
else:
return self.none()
def get_for_user(self, user):
states = self.available_to_user(user)
if states.exists():
return states[0]
else:
from allianceauth.authentication.models import get_guest_state
return get_guest_state()
def delete(self):
with transaction.atomic():
for state in self:
for profile in state.userprofile_set.all():
profile.assign_state(state=self.model.objects.exclude(pk=state.pk).get_for_user(profile.user))
super(StateQuerySet, self).delete()
class StateManager(Manager):
def get_queryset(self):
return StateQuerySet(self.model, using=self._db)
def available_to_character(self, character):
return self.get_queryset().available_to_character(character)
def available_to_user(self, user):
return self.get_queryset().available_to_user(user)
def get_for_character(self, character):
states = self.get_queryset().available_to_character(character)
if states.exists():
return states[0]
else:
from allianceauth.authentication.models import get_guest_state
return get_guest_state()
def get_for_user(self, user):
return self.get_queryset().get_for_user(user)

View File

@@ -1,72 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.2 on 2016-12-11 23:14
from __future__ import unicode_literals
from django.db import migrations
import logging
logger = logging.getLogger(__name__)
class Migration(migrations.Migration):
dependencies = [
('authentication', '0012_remove_add_delete_authservicesinfo_permissions'),
]
operations = [
# Remove fields
migrations.RemoveField(
model_name='authservicesinfo',
name='discord_uid',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='discourse_enabled',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='forum_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='ipboard_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='ips4_id',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='ips4_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='jabber_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='market_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='mumble_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='smf_username',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='teamspeak3_perm_key',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='teamspeak3_uid',
),
migrations.RemoveField(
model_name='authservicesinfo',
name='xenforo_username',
),
]

View File

@@ -1,24 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.1 on 2016-09-09 23:19
from __future__ import unicode_literals
from django.db import migrations
def create_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.get_or_create(codename="view_fleetup", content_type=ct, name="view_fleetup")
class Migration(migrations.Migration):
dependencies = [
('authentication', '0013_service_modules'),
]
operations = [
migrations.RunPython(create_permission, migrations.RunPython.noop)
]

View File

@@ -1,285 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-22 23:09
from __future__ import unicode_literals
import allianceauth.authentication.models
import django.db.models.deletion
from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.db import migrations, models
def create_guest_state(apps, schema_editor):
State = apps.get_model('authentication', 'State')
State.objects.update_or_create(name='Guest', defaults={'priority': 0, 'public': True})
def create_member_state(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
State = apps.get_model('authentication', 'State')
EveAllianceInfo = apps.get_model('eveonline', 'EveAllianceInfo')
EveCorporationInfo = apps.get_model('eveonline', 'EveCorporationInfo')
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
s = State.objects.update_or_create(name=member_state_name, defaults={'priority': 100, 'public': False})[0]
try:
# move group permissions to state
g = Group.objects.get(name=member_state_name)
[s.permissions.add(p.pk) for p in g.permissions.all()]
g.delete()
except Group.DoesNotExist:
pass
# auto-populate member IDs
CORP_IDS = getattr(settings, 'CORP_IDS', [])
ALLIANCE_IDS = getattr(settings, 'ALLIANCE_IDS', [])
[s.member_corporations.add(c.pk) for c in EveCorporationInfo.objects.filter(corporation_id__in=CORP_IDS)]
[s.member_alliances.add(a.pk) for a in EveAllianceInfo.objects.filter(alliance_id__in=ALLIANCE_IDS)]
def create_member_group(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
State = apps.get_model('authentication', 'State')
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
try:
g, _ = Group.objects.get_or_create(name=member_state_name)
# move permissions back
state = State.objects.get(name=member_state_name)
[g.permissions.add(p.pk) for p in state.permissions.all()]
# move users back
for profile in state.userprofile_set.all().select_related('user'):
profile.user.groups.add(g.pk)
except State.DoesNotExist:
pass
def create_blue_state(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
State = apps.get_model('authentication', 'State')
EveAllianceInfo = apps.get_model('eveonline', 'EveAllianceInfo')
EveCorporationInfo = apps.get_model('eveonline', 'EveCorporationInfo')
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
s = State.objects.update_or_create(name=blue_state_name, defaults={'priority': 50, 'public': False})[0]
try:
# move group permissions to state
g = Group.objects.get(name=blue_state_name)
[s.permissions.add(p.pk) for p in g.permissions.all()]
g.delete()
except Group.DoesNotExist:
pass
# auto-populate blue member IDs
BLUE_CORP_IDS = getattr(settings, 'BLUE_CORP_IDS', [])
BLUE_ALLIANCE_IDS = getattr(settings, 'BLUE_ALLIANCE_IDS', [])
[s.member_corporations.add(c.pk) for c in EveCorporationInfo.objects.filter(corporation_id__in=BLUE_CORP_IDS)]
[s.member_alliances.add(a.pk) for a in EveAllianceInfo.objects.filter(alliance_id__in=BLUE_ALLIANCE_IDS)]
def create_blue_group(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
State = apps.get_model('authentication', 'State')
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
try:
g, _ = Group.objects.get_or_create(name=blue_state_name)
# move permissions back
state = State.objects.get(name=blue_state_name)
[g.permissions.add(p.pk) for p in state.permissions.all()]
# move users back
for profile in state.userprofile_set.all().select_related('user'):
profile.user.groups.add(g.pk)
except State.DoesNotExist:
pass
def purge_tokens(apps, schema_editor):
Token = apps.get_model('esi', 'Token')
Token.objects.filter(refresh_token__isnull=True).delete()
def populate_ownerships(apps, schema_editor):
Token = apps.get_model('esi', 'Token')
CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership')
EveCharacter = apps.get_model('eveonline', 'EveCharacter')
unique_character_owners = [t['character_id'] for t in
Token.objects.all().values('character_id').annotate(n=models.Count('user')) if
t['n'] == 1 and EveCharacter.objects.filter(character_id=t['character_id']).exists()]
tokens = Token.objects.filter(character_id__in=unique_character_owners)
for c_id in unique_character_owners:
# find newest refreshable token and use it as basis for CharacterOwnership
ts = tokens.filter(character_id=c_id).exclude(refresh_token__isnull=True).order_by('created')
if ts.exists():
token = ts[0]
char = EveCharacter.objects.get(character_id=token.character_id)
CharacterOwnership.objects.create(user_id=token.user_id, character_id=char.id, owner_hash=token.character_owner_hash)
def create_profiles(apps, schema_editor):
AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo')
State = apps.get_model('authentication', 'State')
UserProfile = apps.get_model('authentication', 'UserProfile')
EveCharacter = apps.get_model('eveonline', 'EveCharacter')
# grab AuthServicesInfo if they have a unique main_char_id and the EveCharacter exists
unique_mains = [auth['main_char_id'] for auth in
AuthServicesInfo.objects.exclude(main_char_id='').values('main_char_id').annotate(
n=models.Count('main_char_id')) if
auth['n'] == 1 and EveCharacter.objects.filter(character_id=auth['main_char_id']).exists()]
auths = AuthServicesInfo.objects.filter(main_char_id__in=unique_mains).select_related('user')
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
states = {
'Member': State.objects.get(name=member_state_name),
'Blue': State.objects.get(name=blue_state_name),
}
guest_state = State.objects.get(name='Guest')
for auth in auths:
# carry states and mains forward
state = states.get(auth.state, guest_state)
char = EveCharacter.objects.get(character_id=auth.main_char_id)
UserProfile.objects.create(user=auth.user, state=state, main_character=char)
for auth in AuthServicesInfo.objects.exclude(main_char_id__in=unique_mains).select_related('user'):
# prepare empty profiles
UserProfile.objects.create(user=auth.user, state=guest_state)
def recreate_authservicesinfo(apps, schema_editor):
AuthServicesInfo = apps.get_model('authentication', 'AuthServicesInfo')
UserProfile = apps.get_model('authentication', 'UserProfile')
User = apps.get_model('auth', 'User')
blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue')
member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member')
states = {
member_state_name: 'Member',
blue_state_name: 'Blue',
}
# recreate all missing AuthServicesInfo models
AuthServicesInfo.objects.bulk_create([AuthServicesInfo(user_id=u.pk) for u in User.objects.all()])
# repopulate main characters
for profile in UserProfile.objects.exclude(main_character__isnull=True).select_related('user', 'main_character'):
AuthServicesInfo.objects.update_or_create(user=profile.user,
defaults={'main_char_id': profile.main_character.character_id})
# repopulate states we understand
for profile in UserProfile.objects.exclude(state__name='Guest').filter(
state__name__in=[member_state_name, blue_state_name]).select_related('user', 'state'):
AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'state': states[profile.state.name]})
def disable_passwords(apps, schema_editor):
User = apps.get_model('auth', 'User')
for u in User.objects.exclude(is_staff=True):
# remove passwords for non-staff users to prevent password-based authentication
# set_unusable_password is unavailable in migrations because :reasons:
u.password = make_password(None)
u.save()
class Migration(migrations.Migration):
dependencies = [
('auth', '0008_alter_user_username_max_length'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('eveonline', '0008_remove_apikeys'),
('authentication', '0014_fleetup_permission'),
('esi', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='CharacterOwnership',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('owner_hash', models.CharField(max_length=28, unique=True)),
('character', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='character_ownership', to='eveonline.EveCharacter')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='character_ownerships', to=settings.AUTH_USER_MODEL)),
],
options={
'default_permissions': ('change', 'delete'),
'ordering': ['user', 'character__character_name'],
},
),
migrations.CreateModel(
name='State',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20, unique=True)),
('priority', models.IntegerField(help_text='Users get assigned the state with the highest priority available to them.', unique=True)),
('public', models.BooleanField(default=False, help_text='Make this state available to any character.')),
('member_alliances', models.ManyToManyField(blank=True, help_text='Alliances to whose members this state is available.', to='eveonline.EveAllianceInfo')),
('member_characters', models.ManyToManyField(blank=True, help_text='Characters to which this state is available.', to='eveonline.EveCharacter')),
('member_corporations', models.ManyToManyField(blank=True, help_text='Corporations to whose members this state is available.', to='eveonline.EveCorporationInfo')),
('permissions', models.ManyToManyField(blank=True, to='auth.Permission')),
],
options={
'ordering': ['-priority'],
},
),
migrations.CreateModel(
name='UserProfile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('main_character', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='eveonline.EveCharacter')),
('state', models.ForeignKey(default=allianceauth.authentication.models.get_guest_state_pk, on_delete=django.db.models.deletion.SET_DEFAULT, to='authentication.State')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL)),
],
options={
'default_permissions': ('change',),
},
),
migrations.RunPython(create_guest_state, migrations.RunPython.noop),
migrations.RunPython(create_member_state, create_member_group),
migrations.RunPython(create_blue_state, create_blue_group),
migrations.RunPython(purge_tokens, migrations.RunPython.noop),
migrations.RunPython(populate_ownerships, migrations.RunPython.noop),
migrations.RunPython(create_profiles, recreate_authservicesinfo),
migrations.RemoveField(
model_name='authservicesinfo',
name='user',
),
migrations.DeleteModel(
name='AuthServicesInfo',
),
migrations.RunPython(disable_passwords, migrations.RunPython.noop),
migrations.CreateModel(
name='Permission',
fields=[
],
options={
'proxy': True,
'verbose_name': 'permission',
'verbose_name_plural': 'permissions',
},
bases=('auth.permission',),
managers=[
('objects', django.contrib.auth.models.PermissionManager()),
],
),
migrations.CreateModel(
name='User',
fields=[
],
options={
'proxy': True,
'verbose_name': 'user',
'verbose_name_plural': 'users',
},
bases=('auth.user',),
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]

View File

@@ -1,40 +0,0 @@
# Generated by Django 2.0.4 on 2018-04-14 18:28
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
def create_initial_records(apps, schema_editor):
OwnershipRecord = apps.get_model('authentication', 'OwnershipRecord')
CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership')
OwnershipRecord.objects.bulk_create([
OwnershipRecord(user=o.user, character=o.character, owner_hash=o.owner_hash) for o in CharacterOwnership.objects.all()
])
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('eveonline', '0009_on_delete'),
('authentication', '0015_user_profiles'),
]
operations = [
migrations.CreateModel(
name='OwnershipRecord',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('owner_hash', models.CharField(db_index=True, max_length=28)),
('created', models.DateTimeField(auto_now=True)),
('character', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ownership_records', to='eveonline.EveCharacter')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ownership_records', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-created'],
},
),
migrations.RunPython(create_initial_records, migrations.RunPython.noop)
]

View File

@@ -1,23 +0,0 @@
# -*- 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

@@ -1,117 +0,0 @@
import logging
from django.contrib.auth.models import User, Permission
from django.db import models, transaction
from django.utils.translation import ugettext_lazy as _
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from allianceauth.notifications import notify
from .managers import CharacterOwnershipManager, StateManager
logger = logging.getLogger(__name__)
class State(models.Model):
name = models.CharField(max_length=20, unique=True)
permissions = models.ManyToManyField(Permission, blank=True)
priority = models.IntegerField(unique=True,
help_text="Users get assigned the state with the highest priority available to them.")
member_characters = models.ManyToManyField(EveCharacter, blank=True,
help_text="Characters to which this state is available.")
member_corporations = models.ManyToManyField(EveCorporationInfo, blank=True,
help_text="Corporations to whose members this state is available.")
member_alliances = models.ManyToManyField(EveAllianceInfo, blank=True,
help_text="Alliances to whose members this state is available.")
public = models.BooleanField(default=False, help_text="Make this state available to any character.")
objects = StateManager()
class Meta:
ordering = ['-priority']
def __str__(self):
return self.name
def available_to_character(self, character):
return self in State.objects.available_to_character(character)
def available_to_user(self, user):
return self in State.objects.available_to_user(user)
def delete(self, **kwargs):
with transaction.atomic():
for profile in self.userprofile_set.all():
profile.assign_state(state=State.objects.exclude(pk=self.pk).get_for_user(profile.user))
super(State, self).delete(**kwargs)
def get_guest_state():
try:
return State.objects.get(name='Guest')
except State.DoesNotExist:
return State.objects.create(name='Guest', priority=0, public=True)
def get_guest_state_pk():
return get_guest_state().pk
class UserProfile(models.Model):
class Meta:
default_permissions = ('change',)
user = models.OneToOneField(User, related_name='profile', on_delete=models.CASCADE)
main_character = models.OneToOneField(EveCharacter, blank=True, null=True, on_delete=models.SET_NULL)
state = models.ForeignKey(State, on_delete=models.SET_DEFAULT, default=get_guest_state_pk)
def assign_state(self, state=None, commit=True):
if not state:
state = State.objects.get_for_user(self.user)
if self.state != state:
self.state = state
if commit:
logger.info('Updating {} state to {}'.format(self.user, self.state))
self.save(update_fields=['state'])
notify(
self.user,
_('State changed to: %s' % state),
_('Your user\'s state is now: %(state)s')
% ({'state': state}),
'info'
)
from allianceauth.authentication.signals import state_changed
state_changed.send(
sender=self.__class__, user=self.user, state=self.state
)
def __str__(self):
return str(self.user)
class CharacterOwnership(models.Model):
class Meta:
default_permissions = ('change', 'delete')
ordering = ['user', 'character__character_name']
character = models.OneToOneField(EveCharacter, on_delete=models.CASCADE, related_name='character_ownership')
owner_hash = models.CharField(max_length=28, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='character_ownerships')
objects = CharacterOwnershipManager()
def __str__(self):
return "%s: %s" % (self.user, self.character)
class OwnershipRecord(models.Model):
character = models.ForeignKey(EveCharacter, on_delete=models.CASCADE, related_name='ownership_records')
owner_hash = models.CharField(max_length=28, db_index=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ownership_records')
created = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['-created']
def __str__(self):
return "%s: %s on %s" % (self.user, self.character, self.created)

View File

@@ -1,159 +0,0 @@
import logging
from .models import CharacterOwnership, UserProfile, get_guest_state, State, OwnershipRecord
from django.contrib.auth.models import User
from django.db.models import Q
from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed
from django.dispatch import receiver, Signal
from esi.models import Token
from allianceauth.eveonline.models import EveCharacter
logger = logging.getLogger(__name__)
state_changed = Signal(providing_args=['user', 'state'])
def trigger_state_check(state):
# evaluate all current members to ensure they still have access
for profile in state.userprofile_set.all():
profile.assign_state()
# we may now be available to others with lower states
check_states = State.objects.filter(priority__lt=state.priority)
for profile in UserProfile.objects.filter(state__in=check_states):
if state.available_to_user(profile.user):
profile.assign_state(state)
@receiver(m2m_changed, sender=State.member_characters.through)
def state_member_characters_changed(sender, instance, action, *args, **kwargs):
if action.startswith('post_'):
logger.debug('State {} member characters changed. Re-evaluating membership.'.format(instance))
trigger_state_check(instance)
@receiver(m2m_changed, sender=State.member_corporations.through)
def state_member_corporations_changed(sender, instance, action, *args, **kwargs):
if action.startswith('post_'):
logger.debug('State {} member corporations changed. Re-evaluating membership.'.format(instance))
trigger_state_check(instance)
@receiver(m2m_changed, sender=State.member_alliances.through)
def state_member_alliances_changed(sender, instance, action, *args, **kwargs):
if action.startswith('post_'):
logger.debug('State {} member alliances changed. Re-evaluating membership.'.format(instance))
trigger_state_check(instance)
@receiver(post_save, sender=State)
def state_saved(sender, instance, *args, **kwargs):
logger.debug('State {} saved. Re-evaluating membership.'.format(instance))
trigger_state_check(instance)
# Is there a smarter way to intercept pre_save with a diff main_character or state?
@receiver(post_save, sender=UserProfile)
def reassess_on_profile_save(sender, instance, created, *args, **kwargs):
# catches post_save from profiles to trigger necessary service and state checks
if not created:
update_fields = kwargs.pop('update_fields', []) or []
if 'state' not in update_fields:
logger.debug('Profile for {} saved without state change. Re-evaluating state.'.format(instance.user))
instance.assign_state()
@receiver(post_save, sender=User)
def create_required_models(sender, instance, created, *args, **kwargs):
# ensure all users have a model
if created:
logger.debug('User {} created. Creating default UserProfile.'.format(instance))
UserProfile.objects.get_or_create(user=instance)
@receiver(post_save, sender=Token)
def record_character_ownership(sender, instance, created, *args, **kwargs):
if created:
logger.debug('New token for {0} character {1} saved. Evaluating ownership.'.format(instance.user,
instance.character_name))
if instance.user:
query = Q(owner_hash=instance.character_owner_hash) & Q(user=instance.user)
else:
query = Q(owner_hash=instance.character_owner_hash)
# purge ownership records if the hash or auth user account has changed
CharacterOwnership.objects.filter(character__character_id=instance.character_id).exclude(query).delete()
# create character if needed
if EveCharacter.objects.filter(character_id=instance.character_id).exists() is False:
logger.debug('Token is for a new character. Creating model for {0} ({1})'.format(instance.character_name,
instance.character_id))
EveCharacter.objects.create_character(instance.character_id)
char = EveCharacter.objects.get(character_id=instance.character_id)
# check if we need to create ownership
if instance.user and not CharacterOwnership.objects.filter(
character__character_id=instance.character_id).exists():
logger.debug("Character {0} is not yet owned. Assigning ownership to {1}".format(instance.character_name,
instance.user))
CharacterOwnership.objects.update_or_create(character=char,
defaults={'owner_hash': instance.character_owner_hash,
'user': instance.user})
@receiver(pre_delete, sender=CharacterOwnership)
def validate_main_character(sender, instance, *args, **kwargs):
try:
if instance.user.profile.main_character == instance.character:
logger.info("Ownership of a main character {0} has been revoked. Resetting {1} main character.".format(
instance.character, instance.user))
# clear main character as user no longer owns them
instance.user.profile.main_character = None
instance.user.profile.save()
except UserProfile.DoesNotExist:
# a user is being deleted
pass
@receiver(post_delete, sender=Token)
def validate_ownership(sender, instance, *args, **kwargs):
if not Token.objects.filter(character_owner_hash=instance.character_owner_hash).filter(refresh_token__isnull=False).exists():
logger.info("No remaining tokens to validate ownership of character {0}. Revoking ownership.".format(instance.character_name))
CharacterOwnership.objects.filter(owner_hash=instance.character_owner_hash).delete()
@receiver(pre_save, sender=User)
def assign_state_on_active_change(sender, instance, *args, **kwargs):
# set to guest state if inactive, assign proper state if reactivated
if instance.pk:
old_instance = User.objects.get(pk=instance.pk)
if old_instance.is_active != instance.is_active:
if instance.is_active:
logger.debug("User {0} has been activated. Assigning state.".format(instance))
instance.profile.assign_state()
else:
logger.debug(
"User {0} has been deactivated. Revoking state and assigning to guest state.".format(instance))
instance.profile.state = get_guest_state()
instance.profile.save(update_fields=['state'])
@receiver(post_save, sender=EveCharacter)
def check_state_on_character_update(sender, instance, *args, **kwargs):
# if this is a main character updating, check that user's state
try:
logger.debug("Character {0} has been saved. Assessing owner's state for changes.".format(instance))
instance.userprofile.assign_state()
except UserProfile.DoesNotExist:
logger.debug("Character {0} is not a main character. No state assessment required.".format(instance))
pass
@receiver(post_save, sender=CharacterOwnership)
def ownership_record_creation(sender, instance, created, *args, **kwargs):
if created:
records = OwnershipRecord.objects.filter(owner_hash=instance.owner_hash).filter(character=instance.character)
if records.exists():
if records[0].user == instance.user: # most recent record is sorted first
logger.debug("Already have ownership record of {0} by user {1}".format(instance.character, instance.user))
return
logger.info("Character {0} has a new owner {1}. Creating ownership record.".format(instance.character, instance.user))
OwnershipRecord.objects.create(user=instance.user, character=instance.character, owner_hash=instance.owner_hash)

View File

@@ -1,29 +0,0 @@
/*
CSS for allianceauth admin site
*/
/* styling for profile pic */
.img-circle {
border-radius: 50%;
}
.column-user_profile_pic {
width: 1px;
white-space: nowrap;
}
/* tooltip */
.tooltip {
position: relative ;
}
.tooltip:hover::after {
content: attr(data-tooltip) ;
position: absolute ;
top: 1.1em ;
left: 1em ;
min-width: 200px ;
border: 1px #808080 solid ;
padding: 8px ;
color: black ;
background-color: rgb(255, 255, 204) ;
z-index: 1 ;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

View File

@@ -1,43 +0,0 @@
import logging
from esi.errors import TokenExpiredError, TokenInvalidError, IncompleteResponseError
from esi.models import Token
from celery import shared_task
from allianceauth.authentication.models import CharacterOwnership
logger = logging.getLogger(__name__)
@shared_task
def check_character_ownership(owner_hash):
tokens = Token.objects.filter(character_owner_hash=owner_hash)
if tokens:
for t in tokens:
old_hash = t.character_owner_hash
try:
t.update_token_data(commit=False)
except (TokenExpiredError, TokenInvalidError):
t.delete()
continue
except (KeyError, IncompleteResponseError):
# We can't validate the hash hasn't changed but also can't assume it has. Abort for now.
logger.warning("Failed to validate owner hash of {0} due to problems contacting SSO servers.".format(
tokens[0].character_name))
break
if not t.character_owner_hash == old_hash:
logger.info(
'Character %s has changed ownership. Revoking %s tokens.' % (t.character_name, tokens.count()))
tokens.delete()
break
if not Token.objects.filter(character_owner_hash=owner_hash).exists():
logger.info('No tokens found with owner hash %s. Revoking ownership.' % owner_hash)
CharacterOwnership.objects.filter(owner_hash=owner_hash).delete()
@shared_task
def check_all_character_ownership():
for c in CharacterOwnership.objects.all().only('owner_hash'):
check_character_ownership.delay(c.owner_hash)

View File

@@ -1,165 +0,0 @@
{% extends "allianceauth/base.html" %}
{% load static %}
{% load i18n %}
{% block page_title %}{% trans "Dashboard" %}{% endblock %}
{% block content %}
<h1 class="page-header text-center">{% trans "Dashboard" %}</h1>
{% if user.is_staff %}
{% include 'allianceauth/admin-status/include.html' %}
{% endif %}
<div class="col-sm-12">
<div class="row vertical-flexbox-row2">
<div class="col-sm-6 text-center">
<div class="panel panel-primary" style="height:100%">
<div class="panel-heading">
<h3 class="panel-title">
{% blocktrans with state=request.user.profile.state %}
Main Character (State: {{ state }})
{% endblocktrans %}
</h3>
</div>
<div class="panel-body">
{% if request.user.profile.main_character %}
{% with request.user.profile.main_character as main %}
<div class="hidden-xs">
<div class="col-lg-4 col-sm-2">
<table class="table">
<tr>
<td class="text-center">
<img class="ra-avatar"src="{{ main.portrait_url_128 }}">
</td>
</tr>
<tr>
<td class="text-center">{{ main.character_name }}</td>
</tr>
</table>
</div>
<div class="col-lg-4 col-sm-2">
<table class="table">
<tr>
<td class="text-center">
<img class="ra-avatar"src="{{ main.corporation_logo_url_128 }}">
</td>
</tr>
<tr>
<td class="text-center">{{ main.corporation_name }}</td>
</tr>
</table>
</div>
<div class="col-lg-4 col-sm-2">
{% if main.alliance_id %}
<table class="table">
<tr>
<td class="text-center">
<img class="ra-avatar"src="{{ main.alliance_logo_url_128 }}">
</td>
</tr>
<tr>
<td class="text-center">{{ main.alliance_name }}</td>
<tr>
</table>
{% endif %}
</div>
</div>
<div class="table visible-xs-block">
<p>
<img class="ra-avatar" src="{{ main.portrait_url_64 }}">
<img class="ra-avatar" src="{{ main.corporation_logo_url_64 }}">
<img class="ra-avatar" src="{{ main.alliance_logo_url_64 }}">
</p>
<p>
<strong>{{ main.character_name }}</strong><br>
{{ main.corporation_name }}<br>
{{ main.alliance_name }}
</p>
</div>
{% endwith %}
{% else %}
<div class="alert alert-danger" role="alert">
{% trans "No main character set." %}
</div>
{% endif %}
<div class="clearfix"></div>
<div class="row">
<div class="col-sm-6 button-wrapper">
<a href="{% url 'authentication:add_character' %}" class="btn btn-block btn-info"
title="Add Character">{% trans 'Add Character' %}</a>
</div>
<div class="col-sm-6 button-wrapper">
<a href="{% url 'authentication:change_main_character' %}" class="btn btn-block btn-info"
title="Change Main Character">{% trans "Change Main" %}</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6 text-center">
<div class="panel panel-success" style="height:100%">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Group Memberships" %}</h3>
</div>
<div class="panel-body">
<div style="height: 240px;overflow:-moz-scrollbars-vertical;overflow-y:auto;">
<table class="table table-aa">
{% for group in groups %}
<tr>
<td>{{ group.name }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
</div>
</div>
</div>
<div class="clearfix"></div>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title text-center" style="text-align: center">
{% trans 'Characters' %}
</h3>
</div>
<div class="panel-body">
<table class="table table-aa hidden-xs">
<thead>
<tr>
<th class="text-center"></th>
<th class="text-center">{% trans 'Name' %}</th>
<th class="text-center">{% trans 'Corp' %}</th>
<th class="text-center">{% trans 'Alliance' %}</th>
</tr>
</thead>
<tbody>
{% for char in characters %}
<tr>
<td class="text-center"><img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
</td>
<td class="text-center">{{ char.character_name }}</td>
<td class="text-center">{{ char.corporation_name }}</td>
<td class="text-center">{{ char.alliance_name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<table class="table table-aa visible-xs-block" style="width: 100%">
<tbody>
{% for char in characters %}
<tr>
<td class="text-center" style="vertical-align: middle">
<img class="ra-avatar img-circle" src="{{ char.portrait_url_32 }}">
</td>
<td class="text-center" style="vertical-align: middle; width: 100%">
<strong>{{ char.character_name }}</strong><br>
{{ char.corporation_name }}<br>
{{ char.alliance_name|default:"" }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,55 +0,0 @@
{% load static %}
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<meta name="author" content="">
<meta property="og:title" content="{{ SITE_NAME }}">
<meta property="og:image" content="{{ request.scheme }}://{{ request.get_host }}{% static 'icons/apple-touch-icon.png' %}">
<meta property="og:description" content="Alliance Auth - An auth system for EVE Online to help in-game organizations manage online service access.">
{% include 'allianceauth/icons.html' %}
<title>{% block title %}{{ SITE_NAME }}{% endblock %}</title>
{% include 'bundles/bootstrap-css.html' %}
{% include 'bundles/fontawesome.html' %}
{% block extra_include %}
{% endblock %}
<style>
body {
background: url('{% static 'authentication/img/background.jpg' %}') no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}
.panel-transparent {
background: rgba(48, 48, 48, 0.7);
color: #ffffff;
}
.panel-body {
}
#lang-select {
width: 40%;
margin-left: auto;
margin-right: auto;
}
{% block extra_style %}
{% endblock %}
</style>
</head>
<body>
<div class="container" style="margin-top:150px">
{% block content %}
{% endblock %}
</div>
</body>
</html>

View File

@@ -1,14 +0,0 @@
{% load i18n %}
<div class="dropdown">
<form action="{% url 'set_language' %}" method="post">
{% csrf_token %}
<select onchange="this.form.submit()" class="form-control" id="lang-select" name="language">
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected="selected"{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
</form>
</div>

View File

@@ -1,8 +0,0 @@
{% extends 'public/middle_box.html' %}
{% load static %}
{% block page_title %}Login{% endblock %}
{% block middle_box_content %}
<a href="{% url 'auth_sso_login' %}{% if request.GET.next %}?next={{request.GET.next}}{%endif%}">
<img class="img-responsive center-block" src="{% static 'img/sso/EVE_SSO_Login_Buttons_Large_Black.png' %}" border=0>
</a>
{% endblock %}

View File

@@ -1,24 +0,0 @@
{% extends 'public/base.html' %}
{% load static %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="col-md-4 col-md-offset-4">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.level_tag}}">{{ message }}</div>
{% endfor %}
{% endif %}
<div class="panel panel-default panel-transparent">
<div class="panel-body">
<div class="col-md-12">
{% block middle_box_content %}
{% endblock %}
</div>
</div>
{% include 'public/lang_select.html' %}
</div>
</div>
{% endblock %}
{% block extra_include %}
{% include 'bundles/bootstrap-js.html' %}
{% endblock %}

View File

@@ -1,24 +0,0 @@
{% extends 'public/base.html' %}
{% load static %}
{% load bootstrap %}
{% load i18n %}
{% block page_title %}Registration{% endblock %}
{% block extra_include %}
{% include 'bundles/bootstrap-css.html' %}
{% include 'bundles/fontawesome.html' %}
{% include 'bundles/bootstrap-js.html' %}
{% endblock %}
{% block content %}
<div class="col-md-4 col-md-offset-4">
<div class="panel panel-default panel-transparent">
<div class="panel-body">
<form method="POST">
{% csrf_token %}
{{ form|bootstrap }}
<button class="btn btn-lg btn-primary btn-block" type="submit">{% trans "Register" %}</button>
</form>
</div>
</div>
{% include 'public/lang_select.html' %}
</div>
{% endblock %}

View File

@@ -1,5 +0,0 @@
{% extends 'public/middle_box.html' %}
{% load i18n %}
{% block middle_box_content %}
<div class="alert alert-danger">{% trans 'Invalid or expired activation link.' %}</div>
{% endblock %}

View File

@@ -1,13 +0,0 @@
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 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 }}
This link will expire in {{ expiration_days }} day(s).
If this was not you, it is safe to ignore this email.

View File

@@ -1 +0,0 @@
Confirm your Alliance Auth account email address

View File

@@ -1,14 +0,0 @@
{% extends 'public/middle_box.html' %}
{% load bootstrap %}
{% load i18n %}
{% load static %}
{% block page_title %}Register{% endblock %}
{% block middle_box_content %}
<form class="form-signin" role="form" action="" method="POST">
{% csrf_token %}
{{ form|bootstrap }}
<br/>
<button class="btn btn-lg btn-primary btn-block" type="submit">{% trans "Submit" %}</button>
<br/>
</form>
{% endblock %}

View File

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

View File

@@ -1,635 +0,0 @@
from urllib.parse import quote
from unittest.mock import patch, MagicMock
from django.conf import settings
from django.contrib import admin
from django.contrib.admin.sites import AdminSite
from django.contrib.auth.models import User as BaseUser, Group
from django.test import TestCase, RequestFactory, Client
from allianceauth.authentication.models import (
CharacterOwnership, State, OwnershipRecord
)
from allianceauth.eveonline.models import (
EveCharacter, EveCorporationInfo, EveAllianceInfo
)
from allianceauth.services.hooks import ServicesHook
from allianceauth.tests.auth_utils import AuthUtils
from ..admin import (
BaseUserAdmin,
CharacterOwnershipAdmin,
PermissionAdmin,
StateAdmin,
MainCorporationsFilter,
MainAllianceFilter,
OwnershipRecordAdmin,
User,
UserAdmin,
user_main_organization,
user_profile_pic,
user_username,
update_main_character_model,
make_service_hooks_update_groups_action,
make_service_hooks_sync_nickname_action
)
from . import get_admin_change_view_url, get_admin_search_url
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True
from allianceauth.eveonline.autogroups.models import AutogroupsConfig
else:
_has_auto_groups = False
MODULE_PATH = 'allianceauth.authentication.admin'
class MockRequest(object):
def __init__(self, user=None):
self.user = user
class TestCaseWithTestData(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
for MyModel in [
EveAllianceInfo, EveCorporationInfo, EveCharacter, Group, User
]:
MyModel.objects.all().delete()
# groups
cls.group_1 = Group.objects.create(
name='Group 1'
)
cls.group_2 = Group.objects.create(
name='Group 2'
)
# user 1 - corp and alliance, normal user
character_1 = EveCharacter.objects.create(
character_id=1001,
character_name='Bruce Wayne',
corporation_id=2001,
corporation_name='Wayne Technologies',
corporation_ticker='WT',
alliance_id=3001,
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
)
character_1a = EveCharacter.objects.create(
character_id=1002,
character_name='Batman',
corporation_id=2001,
corporation_name='Wayne Technologies',
corporation_ticker='WT',
alliance_id=3001,
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
)
alliance = EveAllianceInfo.objects.create(
alliance_id=3001,
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
executor_corp_id=2001
)
EveCorporationInfo.objects.create(
corporation_id=2001,
corporation_name='Wayne Technologies',
corporation_ticker='WT',
member_count=42,
alliance=alliance
)
cls.user_1 = User.objects.create_user(
character_1.character_name.replace(' ', '_'),
'abc@example.com',
'password'
)
CharacterOwnership.objects.create(
character=character_1,
owner_hash='x1' + character_1.character_name,
user=cls.user_1
)
CharacterOwnership.objects.create(
character=character_1a,
owner_hash='x1' + character_1a.character_name,
user=cls.user_1
)
cls.user_1.profile.main_character = character_1
cls.user_1.profile.save()
cls.user_1.groups.add(cls.group_1)
# user 2 - corp only, staff
character_2 = EveCharacter.objects.create(
character_id=1003,
character_name='Clark Kent',
corporation_id=2002,
corporation_name='Daily Planet',
corporation_ticker='DP',
alliance_id=None
)
EveCorporationInfo.objects.create(
corporation_id=2002,
corporation_name='Daily Plane',
corporation_ticker='DP',
member_count=99,
alliance=None
)
cls.user_2 = User.objects.create_user(
character_2.character_name.replace(' ', '_'),
'abc@example.com',
'password'
)
CharacterOwnership.objects.create(
character=character_2,
owner_hash='x1' + character_2.character_name,
user=cls.user_2
)
cls.user_2.profile.main_character = character_2
cls.user_2.profile.save()
cls.user_2.groups.add(cls.group_2)
cls.user_2.is_staff = True
cls.user_2.save()
# user 3 - no main, no group, superuser
character_3 = EveCharacter.objects.create(
character_id=1101,
character_name='Lex Luthor',
corporation_id=2101,
corporation_name='Lex Corp',
corporation_ticker='LC',
alliance_id=None
)
EveCorporationInfo.objects.create(
corporation_id=2101,
corporation_name='Lex Corp',
corporation_ticker='LC',
member_count=666,
alliance=None
)
EveAllianceInfo.objects.create(
alliance_id=3101,
alliance_name='Lex World Domination',
alliance_ticker='LWD',
executor_corp_id=2101
)
cls.user_3 = User.objects.create_user(
character_3.character_name.replace(' ', '_'),
'abc@example.com',
'password'
)
CharacterOwnership.objects.create(
character=character_3,
owner_hash='x1' + character_3.character_name,
user=cls.user_3
)
cls.user_3.is_superuser = True
cls.user_3.save()
def make_generic_search_request(ModelClass: type, search_term: str):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
return c.get(
'%s?q=%s' % (get_admin_search_url(ModelClass), quote(search_term))
)
class TestCharacterOwnershipAdmin(TestCaseWithTestData):
def setUp(self):
self.modeladmin = CharacterOwnershipAdmin(
model=User, admin_site=AdminSite()
)
def test_change_view_loads_normally(self):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
ownership = self.user_1.character_ownerships.first()
response = c.get(get_admin_change_view_url(ownership))
self.assertEqual(response.status_code, 200)
def test_search_works(self):
obj = CharacterOwnership.objects\
.filter(user=self.user_1)\
.first()
response = make_generic_search_request(type(obj), obj.user.username)
expected = 200
self.assertEqual(response.status_code, expected)
class TestOwnershipRecordAdmin(TestCaseWithTestData):
def setUp(self):
self.modeladmin = OwnershipRecordAdmin(
model=User, admin_site=AdminSite()
)
def test_change_view_loads_normally(self):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
ownership_record = OwnershipRecord.objects\
.filter(user=self.user_1)\
.first()
response = c.get(get_admin_change_view_url(ownership_record))
self.assertEqual(response.status_code, 200)
def test_search_works(self):
obj = OwnershipRecord.objects.first()
response = make_generic_search_request(type(obj), obj.user.username)
expected = 200
self.assertEqual(response.status_code, expected)
class TestStateAdmin(TestCaseWithTestData):
def setUp(self):
self.modeladmin = StateAdmin(
model=User, admin_site=AdminSite()
)
def test_change_view_loads_normally(self):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
guest_state = AuthUtils.get_guest_state()
response = c.get(get_admin_change_view_url(guest_state))
self.assertEqual(response.status_code, 200)
member_state = AuthUtils.get_member_state()
response = c.get(get_admin_change_view_url(member_state))
self.assertEqual(response.status_code, 200)
def test_search_works(self):
obj = State.objects.first()
response = make_generic_search_request(type(obj), obj.name)
expected = 200
self.assertEqual(response.status_code, expected)
class TestUserAdmin(TestCaseWithTestData):
def setUp(self):
self.factory = RequestFactory()
self.modeladmin = UserAdmin(
model=User, admin_site=AdminSite()
)
self.character_1 = self.user_1.character_ownerships.first().character
def _create_autogroups(self):
"""create autogroups for corps and alliances"""
if _has_auto_groups:
autogroups_config = AutogroupsConfig(
corp_groups = True,
alliance_groups = True
)
autogroups_config.save()
for state in State.objects.all():
autogroups_config.states.add(state)
autogroups_config.update_corp_group_membership(self.user_1)
# column rendering
def test_user_profile_pic_u1(self):
expected = ('<img src="https://images.evetech.net/characters/1001/'
'portrait?size=32" class="img-circle">')
self.assertEqual(user_profile_pic(self.user_1), expected)
def test_user_profile_pic_u3(self):
self.assertIsNone(user_profile_pic(self.user_3))
def test_user_username_u1(self):
expected = (
'<strong><a href="/admin/authentication/user/{}/change/">'
'Bruce_Wayne</a></strong><br>Bruce Wayne'.format(self.user_1.pk)
)
self.assertEqual(user_username(self.user_1), expected)
def test_user_username_u3(self):
expected = (
'<strong><a href="/admin/authentication/user/{}/change/">'
'Lex_Luthor</a></strong>'.format(self.user_3.pk)
)
self.assertEqual(user_username(self.user_3), expected)
def test_user_main_organization_u1(self):
expected = 'Wayne Technologies<br>Wayne Enterprises'
self.assertEqual(user_main_organization(self.user_1), expected)
def test_user_main_organization_u2(self):
expected = 'Daily Planet'
self.assertEqual(user_main_organization(self.user_2), expected)
def test_user_main_organization_u3(self):
expected = None
self.assertEqual(user_main_organization(self.user_3), expected)
def test_characters_u1(self):
expected = 'Batman, Bruce Wayne'
result = self.modeladmin._characters(self.user_1)
self.assertEqual(result, expected)
def test_characters_u2(self):
expected = 'Clark Kent'
result = self.modeladmin._characters(self.user_2)
self.assertEqual(result, expected)
def test_characters_u3(self):
expected = 'Lex Luthor'
result = self.modeladmin._characters(self.user_3)
self.assertEqual(result, expected)
def test_groups_u1(self):
self._create_autogroups()
expected = 'Group 1'
result = self.modeladmin._groups(self.user_1)
self.assertEqual(result, expected)
def test_groups_u2(self):
self._create_autogroups()
expected = 'Group 2'
result = self.modeladmin._groups(self.user_2)
self.assertEqual(result, expected)
def test_groups_u3(self):
self._create_autogroups()
result = self.modeladmin._groups(self.user_3)
self.assertIsNone(result)
@patch(MODULE_PATH + '._has_auto_groups', False)
def test_groups_u1_no_autogroups(self):
expected = 'Group 1'
result = self.modeladmin._groups(self.user_1)
self.assertEqual(result, expected)
@patch(MODULE_PATH + '._has_auto_groups', False)
def test_groups_u2_no_autogroups(self):
expected = 'Group 2'
result = self.modeladmin._groups(self.user_2)
self.assertEqual(result, expected)
@patch(MODULE_PATH + '._has_auto_groups', False)
def test_groups_u3_no_autogroups(self):
result = self.modeladmin._groups(self.user_3)
self.assertIsNone(result)
def test_state(self):
expected = 'Guest'
result = self.modeladmin._state(self.user_1)
self.assertEqual(result, expected)
def test_role_u1(self):
expected = 'User'
result = self.modeladmin._role(self.user_1)
self.assertEqual(result, expected)
def test_role_u2(self):
expected = 'Staff'
result = self.modeladmin._role(self.user_2)
self.assertEqual(result, expected)
def test_role_u3(self):
expected = 'Superuser'
result = self.modeladmin._role(self.user_3)
self.assertEqual(result, expected)
def test_list_2_html_w_tooltips_no_cutoff(self):
items = ['one', 'two', 'three']
expected = 'one, two, three'
result = self.modeladmin._list_2_html_w_tooltips(items, 5)
self.assertEqual(expected, result)
def test_list_2_html_w_tooltips_w_cutoff(self):
items = ['one', 'two', 'three']
expected = ('<span data-tooltip="one, two, three" '
'class="tooltip">one, two, (...)</span>')
result = self.modeladmin._list_2_html_w_tooltips(items, 2)
self.assertEqual(expected, result)
def test_list_2_html_w_tooltips_empty_list(self):
items = []
expected = None
result = self.modeladmin._list_2_html_w_tooltips(items, 5)
self.assertEqual(expected, result)
# actions
@patch(MODULE_PATH + '.UserAdmin.message_user', auto_spec=True)
@patch(MODULE_PATH + '.update_character')
def test_action_update_main_character_model(
self, mock_task, mock_message_user
):
users_qs = User.objects.filter(pk__in=[self.user_1.pk, self.user_2.pk])
update_main_character_model(
self.modeladmin, MockRequest(self.user_1), users_qs
)
self.assertEqual(mock_task.delay.call_count, 2)
self.assertTrue(mock_message_user.called)
# filters
def test_filter_real_groups_with_autogroups(self):
class UserAdminTest(BaseUserAdmin):
list_filter = (UserAdmin.RealGroupsFilter,)
self._create_autogroups()
my_modeladmin = UserAdminTest(User, AdminSite())
# Make sure the lookups are correct
request = self.factory.get('/')
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
filters = changelist.get_filters(request)
filterspec = filters[0][0]
expected = [
(self.group_1.pk, self.group_1.name),
(self.group_2.pk, self.group_2.name),
]
self.assertEqual(filterspec.lookup_choices, expected)
# Make sure the correct queryset is returned
request = self.factory.get('/', {'group_id__exact': self.group_1.pk})
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request)
expected = User.objects.filter(groups__in=[self.group_1])
self.assertSetEqual(set(queryset), set(expected))
@patch(MODULE_PATH + '._has_auto_groups', False)
def test_filter_real_groups_no_autogroups(self):
class UserAdminTest(BaseUserAdmin):
list_filter = (UserAdmin.RealGroupsFilter,)
my_modeladmin = UserAdminTest(User, AdminSite())
# Make sure the lookups are correct
request = self.factory.get('/')
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
filters = changelist.get_filters(request)
filterspec = filters[0][0]
expected = [
(self.group_1.pk, self.group_1.name),
(self.group_2.pk, self.group_2.name),
]
self.assertEqual(filterspec.lookup_choices, expected)
# Make sure the correct queryset is returned
request = self.factory.get('/', {'group_id__exact': self.group_1.pk})
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request)
expected = User.objects.filter(groups__in=[self.group_1])
self.assertSetEqual(set(queryset), set(expected))
def test_filter_main_corporations(self):
class UserAdminTest(BaseUserAdmin):
list_filter = (MainCorporationsFilter,)
my_modeladmin = UserAdminTest(User, AdminSite())
# Make sure the lookups are correct
request = self.factory.get('/')
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
filters = changelist.get_filters(request)
filterspec = filters[0][0]
expected = [
(2002, 'Daily Planet'),
(2001, 'Wayne Technologies'),
]
self.assertEqual(filterspec.lookup_choices, expected)
# Make sure the correct queryset is returned
request = self.factory.get(
'/',
{'main_corporation_id__exact': self.character_1.corporation_id}
)
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request)
expected = [self.user_1]
self.assertSetEqual(set(queryset), set(expected))
def test_filter_main_alliances(self):
class UserAdminTest(BaseUserAdmin):
list_filter = (MainAllianceFilter,)
my_modeladmin = UserAdminTest(User, AdminSite())
# Make sure the lookups are correct
request = self.factory.get('/')
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
filters = changelist.get_filters(request)
filterspec = filters[0][0]
expected = [
(3001, 'Wayne Enterprises'),
]
self.assertEqual(filterspec.lookup_choices, expected)
# Make sure the correct queryset is returned
request = self.factory.get(
'/',
{'main_alliance_id__exact': self.character_1.alliance_id}
)
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request)
expected = [self.user_1]
self.assertSetEqual(set(queryset), set(expected))
def test_change_view_loads_normally(self):
User.objects.create_superuser(
username='superuser', password='secret', email='admin@example.com'
)
c = Client()
c.login(username='superuser', password='secret')
response = c.get(get_admin_change_view_url(self.user_1))
self.assertEqual(response.status_code, 200)
def test_search_works(self):
obj = User.objects.first()
response = make_generic_search_request(type(obj), obj.username)
expected = 200
self.assertEqual(response.status_code, expected)
class TestMakeServicesHooksActions(TestCaseWithTestData):
class MyServicesHookTypeA(ServicesHook):
def __init__(self):
super().__init__()
self.name = 'My Service A'
def update_groups(self, user):
pass
def sync_nicknames(self, user):
pass
class MyServicesHookTypeB(ServicesHook):
def __init__(self):
super().__init__()
self.name = 'My Service B'
def update_groups(self, user):
pass
def update_groups_bulk(self, user):
pass
def sync_nicknames(self, user):
pass
def sync_nicknames_bulk(self, user):
pass
def test_service_has_update_groups_only(self):
service = self.MyServicesHookTypeA()
mock_service = MagicMock(spec=service)
action = make_service_hooks_update_groups_action(mock_service)
action(MagicMock(), MagicMock(), [self.user_1])
self.assertTrue(mock_service.update_groups.called)
def test_service_has_update_groups_bulk(self):
service = self.MyServicesHookTypeB()
mock_service = MagicMock(spec=service)
action = make_service_hooks_update_groups_action(mock_service)
action(MagicMock(), MagicMock(), [self.user_1])
self.assertFalse(mock_service.update_groups.called)
self.assertTrue(mock_service.update_groups_bulk.called)
def test_service_has_sync_nickname_only(self):
service = self.MyServicesHookTypeA()
mock_service = MagicMock(spec=service)
action = make_service_hooks_sync_nickname_action(mock_service)
action(MagicMock(), MagicMock(), [self.user_1])
self.assertTrue(mock_service.sync_nickname.called)
def test_service_has_sync_nicknames_bulk(self):
service = self.MyServicesHookTypeB()
mock_service = MagicMock(spec=service)
action = make_service_hooks_sync_nickname_action(mock_service)
action(MagicMock(), MagicMock(), [self.user_1])
self.assertFalse(mock_service.sync_nickname.called)
self.assertTrue(mock_service.sync_nicknames_bulk.called)

View File

@@ -1,102 +0,0 @@
from unittest.mock import Mock, patch
from django.test import TestCase
from .. import app_settings
MODULE_PATH = 'allianceauth.authentication'
class TestSetAppSetting(TestCase):
@patch(MODULE_PATH + '.app_settings.settings')
def test_default_if_not_set(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = Mock(spec=None)
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
False,
)
self.assertEqual(result, False)
@patch(MODULE_PATH + '.app_settings.settings')
def test_default_if_not_set_for_none(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = Mock(spec=None)
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
None,
required_type=int
)
self.assertEqual(result, None)
@patch(MODULE_PATH + '.app_settings.settings')
def test_true_stays_true(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = True
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
False,
)
self.assertEqual(result, True)
@patch(MODULE_PATH + '.app_settings.settings')
def test_false_stays_false(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = False
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
False
)
self.assertEqual(result, False)
@patch(MODULE_PATH + '.app_settings.settings')
def test_default_for_invalid_type_bool(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
False
)
self.assertEqual(result, False)
@patch(MODULE_PATH + '.app_settings.settings')
def test_default_for_invalid_type_int(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
50
)
self.assertEqual(result, 50)
@patch(MODULE_PATH + '.app_settings.settings')
def test_default_if_below_minimum_1(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = -5
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
default_value=50
)
self.assertEqual(result, 50)
@patch(MODULE_PATH + '.app_settings.settings')
def test_default_if_below_minimum_2(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = -50
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
default_value=50,
min_value=-10
)
self.assertEqual(result, 50)
@patch(MODULE_PATH + '.app_settings.settings')
def test_default_for_invalid_type_int(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = 1000
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
default_value=50,
max_value=100
)
self.assertEqual(result, 50)
@patch(MODULE_PATH + '.app_settings.settings')
def test_default_is_none_needs_required_type(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
with self.assertRaises(ValueError):
result = app_settings._clean_setting(
'TEST_SETTING_DUMMY',
default_value=None
)

View File

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

View File

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

View File

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

View File

@@ -1,253 +0,0 @@
from unittest import mock
from django.contrib.auth.models import User
from django.test import TestCase
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\
EveAllianceInfo
from allianceauth.tests.auth_utils import AuthUtils
from esi.errors import IncompleteResponseError
from esi.models import Token
from ..models import CharacterOwnership, State, get_guest_state
from ..tasks import check_character_ownership
MODULE_PATH = 'allianceauth.authentication'
class CharacterOwnershipTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('user', disconnect_signals=True)
cls.alt_user = AuthUtils.create_user('alt_user', disconnect_signals=True)
cls.character = EveCharacter.objects.create(
character_id=1,
character_name='Main Character',
corporation_id=1,
corporation_name='Corp',
corporation_ticker='CORP',
)
def test_create_ownership(self):
Token.objects.create(
user=self.user,
character_id=self.character.character_id,
character_name=self.character.character_name,
character_owner_hash='1',
)
co = CharacterOwnership.objects.get(character=self.character)
self.assertEquals(co.user, self.user)
self.assertEquals(co.owner_hash, '1')
def test_transfer_ownership(self):
Token.objects.create(
user=self.user,
character_id=self.character.character_id,
character_name=self.character.character_name,
character_owner_hash='1',
)
Token.objects.create(
user=self.alt_user,
character_id=self.character.character_id,
character_name=self.character.character_name,
character_owner_hash='2',
)
co = CharacterOwnership.objects.get(character=self.character)
self.assertNotEqual(self.user, co.user)
self.assertEquals(self.alt_user, co.user)
def test_clear_main_character(self):
Token.objects.create(
user=self.user,
character_id=self.character.character_id,
character_name=self.character.character_name,
character_owner_hash='1',
)
self.user.profile.main_character = self.character
self.user.profile.save()
Token.objects.create(
user=self.alt_user,
character_id=self.character.character_id,
character_name=self.character.character_name,
character_owner_hash='2',
)
self.user = User.objects.get(pk=self.user.pk)
self.assertIsNone(self.user.profile.main_character)
class StateTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1',
corp_name='Test Corp', alliance_name='Test Alliance')
cls.guest_state = get_guest_state()
cls.test_character = EveCharacter.objects.get(character_id='1')
cls.test_corporation = EveCorporationInfo.objects.create(corporation_id='1', corporation_name='Test Corp',
corporation_ticker='TEST', member_count=1)
cls.test_alliance = EveAllianceInfo.objects.create(alliance_id='1', alliance_name='Test Alliance',
alliance_ticker='TEST', executor_corp_id='1')
cls.member_state = State.objects.create(
name='Test Member',
priority=150,
)
def _refresh_user(self):
self.user = User.objects.get(pk=self.user.pk)
def test_state_assignment_on_character_change(self):
self.member_state.member_characters.add(self.test_character)
self._refresh_user()
self.assertEquals(self.user.profile.state, self.member_state)
self.member_state.member_characters.remove(self.test_character)
self._refresh_user()
self.assertEquals(self.user.profile.state, self.guest_state)
def test_state_assignment_on_corporation_change(self):
self.member_state.member_corporations.add(self.test_corporation)
self._refresh_user()
self.assertEquals(self.user.profile.state, self.member_state)
self.member_state.member_corporations.remove(self.test_corporation)
self._refresh_user()
self.assertEquals(self.user.profile.state, self.guest_state)
def test_state_assignment_on_alliance_addition(self):
self.member_state.member_alliances.add(self.test_alliance)
self._refresh_user()
self.assertEquals(self.user.profile.state, self.member_state)
self.member_state.member_alliances.remove(self.test_alliance)
self._refresh_user()
self.assertEquals(self.user.profile.state, self.guest_state)
def test_state_assignment_on_higher_priority_state_creation(self):
self.member_state.member_characters.add(self.test_character)
higher_state = State.objects.create(
name='Higher State',
priority=200,
)
higher_state.member_characters.add(self.test_character)
self._refresh_user()
self.assertEquals(higher_state, self.user.profile.state)
higher_state.member_characters.clear()
self._refresh_user()
self.assertEquals(self.member_state, self.user.profile.state)
self.member_state.member_characters.clear()
def test_state_assignment_on_lower_priority_state_creation(self):
self.member_state.member_characters.add(self.test_character)
lower_state = State.objects.create(
name='Lower State',
priority=125,
)
lower_state.member_characters.add(self.test_character)
self._refresh_user()
self.assertEquals(self.member_state, self.user.profile.state)
lower_state.member_characters.clear()
self._refresh_user()
self.assertEquals(self.member_state, self.user.profile.state)
self.member_state.member_characters.clear()
def test_state_assignment_on_priority_change(self):
self.member_state.member_characters.add(self.test_character)
lower_state = State.objects.create(
name='Lower State',
priority=125,
)
lower_state.member_characters.add(self.test_character)
self._refresh_user()
lower_state.priority = 500
lower_state.save()
self._refresh_user()
self.assertEquals(lower_state, self.user.profile.state)
lower_state.priority = 125
lower_state.save()
self._refresh_user()
self.assertEquals(self.member_state, self.user.profile.state)
def test_state_assignment_on_state_deletion(self):
self.member_state.member_characters.add(self.test_character)
higher_state = State.objects.create(
name='Higher State',
priority=200,
)
higher_state.member_characters.add(self.test_character)
self._refresh_user()
self.assertEquals(higher_state, self.user.profile.state)
higher_state.delete()
self.assertFalse(State.objects.filter(name='Higher State').count())
self._refresh_user()
self.assertEquals(self.member_state, self.user.profile.state)
def test_state_assignment_on_public_toggle(self):
self.member_state.member_characters.add(self.test_character)
higher_state = State.objects.create(
name='Higher State',
priority=200,
)
self._refresh_user()
self.assertEquals(self.member_state, self.user.profile.state)
higher_state.public = True
higher_state.save()
self._refresh_user()
self.assertEquals(higher_state, self.user.profile.state)
higher_state.public = False
higher_state.save()
self._refresh_user()
self.assertEquals(self.member_state, self.user.profile.state)
def test_state_assignment_on_active_changed(self):
self.member_state.member_characters.add(self.test_character)
self.user.is_active = False
self.user.save()
self._refresh_user()
self.assertEquals(self.user.profile.state, self.guest_state)
self.user.is_active = True
self.user.save()
self._refresh_user()
self.assertEquals(self.user.profile.state, self.member_state)
class CharacterOwnershipCheckTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('test_user', disconnect_signals=True)
AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1',
corp_name='Test Corp', alliance_name='Test Alliance')
cls.character = EveCharacter.objects.get(character_id=1)
cls.token = Token.objects.create(
user=cls.user,
character_id=1,
character_name='Test',
character_owner_hash='1',
)
cls.ownership = CharacterOwnership.objects.get(character=cls.character)
@mock.patch(MODULE_PATH + '.tasks.Token.update_token_data')
def test_no_change_owner_hash(self, update_token_data):
# makes sure the ownership isn't delete if owner hash hasn't changed
check_character_ownership(self.ownership)
self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists())
@mock.patch(MODULE_PATH + '.tasks.Token.update_token_data')
def test_unable_to_update_token_data(self, update_token_data):
# makes sure ownerships and tokens aren't hellpurged when there's problems with the SSO servers
update_token_data.side_effect = IncompleteResponseError()
check_character_ownership(self.ownership)
self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists())
update_token_data.side_effect = KeyError()
check_character_ownership(self.ownership)
self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists())
@mock.patch(MODULE_PATH + '.tasks.Token.update_token_data')
@mock.patch(MODULE_PATH + '.tasks.Token.delete')
@mock.patch(MODULE_PATH + '.tasks.Token.objects.exists')
@mock.patch(MODULE_PATH + '.tasks.CharacterOwnership.objects.filter')
def test_owner_hash_changed(self, filter, exists, delete, update_token_data):
# makes sure the ownership is revoked when owner hash changes
filter.return_value.exists.return_value = False
check_character_ownership(self.ownership)
self.assertTrue(filter.return_value.delete.called)

View File

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

View File

@@ -1,27 +0,0 @@
from django.conf.urls import url
from django.contrib.auth.decorators import login_required
from django.views.generic.base import TemplateView
from . import views
app_name = 'authentication'
urlpatterns = [
url(r'^$', views.index, name='index'),
url(
r'^account/login/$',
TemplateView.as_view(template_name='public/login.html'),
name='login'
),
url(
r'^account/characters/main/$',
views.main_character_change,
name='change_main_character'
),
url(
r'^account/characters/add/$',
views.add_character,
name='add_character'
),
url(r'^dashboard/$', views.dashboard, name='dashboard'),
]

View File

@@ -1,218 +0,0 @@
import logging
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import login, authenticate
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.core import signing
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
from allianceauth.eveonline.models import EveCharacter
from esi.decorators import token_required
from esi.models import Token
from django_registration.backends.activation.views import (
RegistrationView as BaseRegistrationView,
ActivationView as BaseActivationView,
REGISTRATION_SALT
)
from django_registration.signals import user_registered
from .models import CharacterOwnership
from .forms import RegistrationForm
if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS:
_has_auto_groups = True
from allianceauth.eveonline.autogroups.models import *
else:
_has_auto_groups = False
logger = logging.getLogger(__name__)
@login_required
def index(request):
return redirect('authentication:dashboard')
@login_required
def dashboard(request):
groups = request.user.groups.all()
if _has_auto_groups:
groups = groups\
.filter(managedalliancegroup__isnull=True)\
.filter(managedcorpgroup__isnull=True)
groups = groups.order_by('name')
characters = EveCharacter.objects\
.filter(character_ownership__user=request.user)\
.select_related()\
.order_by('character_name')
context = {
'groups': groups,
'characters': characters
}
return render(request, 'authentication/dashboard.html', context)
@login_required
@token_required(scopes=settings.LOGIN_TOKEN_SCOPES)
def main_character_change(request, token):
logger.debug("main_character_change called by user %s for character %s" % (request.user, token.character_name))
try:
co = CharacterOwnership.objects.get(character__character_id=token.character_id, user=request.user)
except CharacterOwnership.DoesNotExist:
if not CharacterOwnership.objects.filter(character__character_id=token.character_id).exists():
co = CharacterOwnership.objects.create_by_token(token)
else:
messages.error(
request,
_('Cannot change main character to %(char)s: character owned by a different account.') % ({'char': token.character_name})
)
co = None
if co:
request.user.profile.main_character = co.character
request.user.profile.save(update_fields=['main_character'])
messages.success(request, _('Changed main character to %(char)s') % {"char": co.character})
logger.info('Changed user %(user)s main character to %(char)s' % ({'user': request.user, 'char': co.character}))
return redirect("authentication:dashboard")
@token_required(new=True, scopes=settings.LOGIN_TOKEN_SCOPES)
def add_character(request, token):
if CharacterOwnership.objects.filter(character__character_id=token.character_id).filter(
owner_hash=token.character_owner_hash).filter(user=request.user).exists():
messages.success(request, _('Added %(name)s to your account.'% ({'name': token.character_name})))
else:
messages.error(request, _('Failed to add %(name)s to your account: they already have an account.' % ({'name': token.character_name})))
return redirect('authentication:dashboard')
"""
Override the HMAC two-step registration view to accommodate the three-step registration required.
Step 1: OAuth token to create user and profile.
Step 2: Get email and send activation link (but do not save email).
Step 3: Get link, save email and activate.
Step 1 is necessary to automatically assign character ownership and a main character, both of which require a saved User
model - this means the ensuing registration form cannot create the user because it already exists.
Email is not saved to the user model in Step 2 as a way of differentiating users who have not yet completed registration
(is_active=False) and users who have been disabled by an admin (is_active=False, email present).
Because of this, the email address needs to be assigned in Step 3 after clicking the link, which means the link must
have the email address embedded much like the username. Key creation and decoding is overridden to support this action.
"""
# Step 1
@token_required(new=True, scopes=settings.LOGIN_TOKEN_SCOPES)
def sso_login(request, token):
user = authenticate(token=token)
if user:
token.user = user
if Token.objects.exclude(pk=token.pk).equivalent_to(token).require_valid().exists():
token.delete()
else:
token.save()
if user.is_active:
login(request, user)
return redirect(request.POST.get('next', request.GET.get('next', 'authentication:dashboard')))
elif not user.email:
# Store the new user PK in the session to enable us to identify the registering user in Step 2
request.session['registration_uid'] = user.pk
# Go to Step 2
return redirect('registration_register')
messages.error(request, _('Unable to authenticate as the selected character.'))
return redirect(settings.LOGIN_URL)
# Step 2
class RegistrationView(BaseRegistrationView):
form_class = RegistrationForm
template_name = "public/register.html"
email_body_template = "registration/activation_email.txt"
email_subject_template = "registration/activation_email_subject.txt"
success_url = reverse_lazy('registration_complete')
def get_success_url(self, user):
if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
return reverse_lazy('authentication:dashboard')
return super().get_success_url(user)
def dispatch(self, request, *args, **kwargs):
# We're storing a key in the session to pass user information from OAuth response. Make sure it's there.
if not self.request.session.get('registration_uid', None) or not User.objects.filter(
pk=self.request.session.get('registration_uid')).exists():
messages.error(self.request, _('Registration token has expired.'))
return redirect(settings.LOGIN_URL)
if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
# Keep the request so the user can be automagically logged in.
setattr(self, 'request', request)
return super(RegistrationView, self).dispatch(request, *args, **kwargs)
def register(self, form):
user = User.objects.get(pk=self.request.session.get('registration_uid'))
user.email = form.cleaned_data['email']
user_registered.send(self.__class__, user=user, request=self.request)
if getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True):
# Go to Step 3
self.send_activation_email(user)
else:
user.is_active = True
user.save()
login(self.request, user, 'allianceauth.authentication.backends.StateBackend')
return user
def get_activation_key(self, user):
return signing.dumps(obj=[getattr(user, User.USERNAME_FIELD), user.email], salt=REGISTRATION_SALT)
def get_email_context(self, activation_key):
context = super(RegistrationView, self).get_email_context(activation_key)
context['url'] = context['site'].domain + reverse('registration_activate', args=[activation_key])
return context
# Step 3
class ActivationView(BaseActivationView):
template_name = "registration/activate.html"
success_url = reverse_lazy('registration_activation_complete')
def validate_key(self, activation_key):
try:
dump = signing.loads(activation_key, salt=REGISTRATION_SALT,
max_age=settings.ACCOUNT_ACTIVATION_DAYS * 86400)
return dump
except signing.BadSignature:
return None
def activate(self, *args, **kwargs):
dump = self.validate_key(kwargs.get('activation_key'))
if dump:
user = self.get_user(dump[0])
if user:
user.email = dump[1]
user.is_active = True
user.save()
return user
return False
def registration_complete(request):
messages.success(request, _('Sent confirmation email. Please follow the link to confirm your email address.'))
return redirect('authentication:login')
def activation_complete(request):
messages.success(request, _('Confirmed your email address. Please login to continue.'))
return redirect('authentication:dashboard')
def registration_closed(request):
messages.error(request, _('Registration of new accounts is not allowed at this time.'))
return redirect('authentication:login')

View File

@@ -1,103 +0,0 @@
#!/usr/bin/env python
import os
import shutil
from optparse import OptionParser
from django.core.management import call_command
from django.core.management.commands.startproject import Command as BaseStartProject
class StartProject(BaseStartProject):
def add_arguments(self, parser):
super().add_arguments(parser)
parser.add_argument('--python', help='The path to the python executable.')
parser.add_argument('--celery', help='The path to the celery executable.')
parser.add_argument('--gunicorn', help='The path to the gunicorn executable.')
def create_project(parser, options, args):
# Validate args
if len(args) < 2:
parser.error("Please specify a name for your Alliance Auth installation.")
elif len(args) > 3:
parser.error("Too many arguments.")
# First find the path to Alliance Auth
import allianceauth
allianceauth_path = os.path.dirname(allianceauth.__file__)
template_path = os.path.join(allianceauth_path, 'project_template')
# Determine locations of commands to render supervisor cond
command_options = {
'template': template_path,
'python': shutil.which('python'),
'gunicorn': shutil.which('gunicorn'),
'celery': shutil.which('celery'),
'extensions': ['py', 'conf', 'json'],
}
# Strip 'start' out of the arguments, leaving project name (and optionally destination dir)
args = args[1:]
# Call the command with extra context
call_command(StartProject(), *args, **command_options)
print("Success! %(project_name)s has been created." % {'project_name': args[0]}) # noqa
def update_settings(parser, options, args):
if len(args) < 2:
parser.error("Please specify the path to your Alliance Auth installation.")
elif len(args) > 2:
parser.error("Too many arguments.")
project_path = args[1]
project_name = os.path.split(project_path)[-1]
# find the target settings/base.py file, handing both the project and app as valid paths
# first check if given path is to the app
settings_path = os.path.join(project_path, 'settings/base.py')
if not os.path.exists(settings_path):
# next check if given path is to the project, so the app is within it
settings_path = os.path.join(project_path, project_name, 'settings/base.py')
if not os.path.exists(settings_path):
parser.error("Unable to locate the Alliance Auth project at %s" % project_path)
# first find the path to the Alliance Auth template settings
import allianceauth
allianceauth_path = os.path.dirname(allianceauth.__file__)
template_path = os.path.join(allianceauth_path, 'project_template')
template_settings_path = os.path.join(template_path, 'project_name/settings/base.py')
# overwrite the local project's base settings
with open(template_settings_path, 'r') as template, open(settings_path, 'w') as target:
target.write(template.read())
print("Successfully updated %(project_name)s settings." % {'project_name': project_name})
COMMANDS = {
'start': create_project,
'update': update_settings,
}
def main():
# Parse options
parser = OptionParser(usage="Usage: %prog [start|update] project_name [directory]")
(options, args) = parser.parse_args()
# Find command
try:
command = args[0]
except IndexError:
parser.print_help()
return
if command in COMMANDS:
COMMANDS[command](parser, options, args)
else:
parser.error("Unrecognised command: " + command)
if __name__ == "__main__":
main()

View File

@@ -1,9 +0,0 @@
from django.conf import settings
from .views import NightModeRedirectView
def auth_settings(request):
return {
'SITE_NAME': settings.SITE_NAME,
'NIGHT_MODE': NightModeRedirectView.night_mode_state(request),
}

View File

@@ -1 +0,0 @@
default_app_config = 'allianceauth.corputils.apps.CorpUtilsConfig'

View File

@@ -1,6 +0,0 @@
from django.contrib import admin
from .models import CorpStats, CorpMember
admin.site.register(CorpStats)
admin.site.register(CorpMember)

View File

@@ -1,30 +0,0 @@
from allianceauth.services.hooks import MenuItemHook, UrlHook
from django.utils.translation import ugettext_lazy as _
from allianceauth import hooks
from allianceauth.corputils import urls
class CorpStats(MenuItemHook):
def __init__(self):
MenuItemHook.__init__(self,
_('Corporation Stats'),
'fas fa-share-alt fa-fw',
'corputils:view',
navactive=['corputils:'])
def render(self, request):
if request.user.has_perm('corputils.view_corp_corpstats') or request.user.has_perm(
'corputils.view_alliance_corpstats') or request.user.has_perm(
'corputils.add_corpstats') or request.user.has_perm('corputils.view_state_corpstats'):
return MenuItemHook.render(self, request)
return ''
@hooks.register('menu_item_hook')
def register_menu():
return CorpStats()
@hooks.register('url_hook')
def register_url():
return UrlHook(urls, 'corputils', r'^corpstats/')

View File

@@ -1,48 +0,0 @@
from django.db import models
import logging
logger = logging.getLogger(__name__)
class CorpStatsQuerySet(models.QuerySet):
def visible_to(self, user):
# superusers get all visible
if user.is_superuser:
logger.debug('Returning all corpstats for superuser %s.' % user)
return self
try:
char = user.profile.main_character
assert char
# build all accepted queries
queries = [models.Q(token__user=user)]
if user.has_perm('corputils.view_alliance_corpstats'):
if char.alliance_id is not None:
queries.append(models.Q(corp__alliance__alliance_id=char.alliance_id))
else:
queries.append(models.Q(corp__corporation_id=char.corporation_id))
if user.has_perm('corputils.view_corp_corpstats'):
if user.has_perm('corputils.view_alliance_corpstats'):
pass
else:
queries.append(models.Q(corp__corporation_id=char.corporation_id))
if user.has_perm('corputils.view_state_corpstats'):
queries.append(models.Q(corp__in=user.profile.state.member_corporations.all()))
queries.append(models.Q(corp__alliance__in=user.profile.state.member_alliances.all()))
logger.debug('%s queries for user %s visible corpstats.' % (len(queries), user))
# filter based on queries
query = queries.pop()
for q in queries:
query |= q
return self.filter(query)
except AssertionError:
logger.debug('User %s has no main character. No corpstats visible.' % user)
return self.none()
class CorpStatsManager(models.Manager):
def get_queryset(self):
return CorpStatsQuerySet(self.model, using=self._db)
def visible_to(self, user):
return self.get_queryset().visible_to(user)

View File

@@ -1,19 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-22 23:35
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('corputils', '0002_migrate_permissions'),
]
operations = [
migrations.AlterModelOptions(
name='corpstats',
options={'permissions': (('corp_apis', 'Can view API keys of members of their corporation.'), ('alliance_apis', 'Can view API keys of members of their alliance.'), ('blue_apis', 'Can view API keys of members of blue corporations.'), ('view_corp_corpstats', 'Can view corp stats of their corporation.'), ('view_alliance_corpstats', 'Can view corp stats of members of their alliance.'), ('view_blue_corpstats', 'Can view corp stats of blue corporations.')), 'verbose_name': 'corp stats', 'verbose_name_plural': 'corp stats'},
),
]

View File

@@ -1,60 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-26 20:13
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import json
def convert_json_to_members(apps, schema_editor):
CorpStats = apps.get_model('corputils', 'CorpStats')
CorpMember = apps.get_model('corputils', 'CorpMember')
for cs in CorpStats.objects.all():
members = json.loads(cs._members)
CorpMember.objects.bulk_create(
[CorpMember(corpstats=cs, character_id=member_id, character_name=member_name) for member_id, member_name in
members.items()]
)
def convert_members_to_json(apps, schema_editor):
CorpStats = apps.get_model('corputils', 'CorpStats')
for cs in CorpStats.objects.all():
cs._members = json.dumps({m.character_id: m.character_name for m in cs.members.all()})
cs.save()
class Migration(migrations.Migration):
dependencies = [
('corputils', '0003_granular_permissions'),
]
operations = [
migrations.CreateModel(
name='CorpMember',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('character_id', models.PositiveIntegerField()),
('character_name', models.CharField(max_length=37)),
],
options={
'ordering': ['character_name'],
},
),
migrations.AddField(
model_name='corpmember',
name='corpstats',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='members',
to='corputils.CorpStats'),
),
migrations.AlterUniqueTogether(
name='corpmember',
unique_together=set([('corpstats', 'character_id')]),
),
migrations.RunPython(convert_json_to_members, convert_members_to_json),
migrations.RemoveField(
model_name='corpstats',
name='_members',
),
]

View File

@@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-06-10 15:34
from __future__ import unicode_literals
from django.db import migrations
def delete_permissions(apps, schema_editor):
CorpStats = apps.get_model('corputils', 'CorpStats')
ContentType = apps.get_model('contenttypes', 'ContentType')
Permission = apps.get_model('auth', 'Permission')
ct = ContentType.objects.get_for_model(CorpStats)
perms = Permission.objects.filter(content_type=ct)
perms.filter(codename__contains='api').delete()
perms.filter(codename='view_corpstats').delete()
perms.filter(codename__contains='blue').delete()
perms.filter(codename__contains='remove').delete()
g = perms.get(codename='view_corp_corpstats')
g.name = 'Can view corp stats of their corporation.'
g.save()
g = perms.get(codename='view_alliance_corpstats')
g.name = 'Can view corp stats of members of their alliance.'
g.save()
class Migration(migrations.Migration):
dependencies = [
('corputils', '0004_member_models'),
]
operations = [
migrations.AlterModelOptions(
name='corpstats',
options={'permissions': (('view_corp_corpstats', 'Can view corp stats of their corporation.'), ('view_alliance_corpstats', 'Can view corp stats of members of their alliance.'), ('view_state_corpstats', 'Can view corp stats of members of their auth state.')), 'verbose_name': 'corp stats', 'verbose_name_plural': 'corp stats'},
),
migrations.RunPython(delete_permissions, migrations.RunPython.noop),
]

View File

@@ -1,201 +0,0 @@
import logging
import os
from allianceauth.authentication.models import CharacterOwnership, UserProfile
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,\
EveAllianceInfo
from allianceauth.notifications import notify
from allianceauth.corputils.managers import CorpStatsManager
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
"""
Swagger spec operations:
Character
get_characters_character_id
get_corporations_corporation_id_members
Universe
post_universe_names
"""
logger = logging.getLogger(__name__)
class CorpStats(models.Model):
token = models.ForeignKey(Token, on_delete=models.CASCADE)
corp = models.OneToOneField(EveCorporationInfo, on_delete=models.CASCADE)
last_update = models.DateTimeField(auto_now=True)
class Meta:
permissions = (
('view_corp_corpstats', 'Can view corp stats of their corporation.'),
('view_alliance_corpstats', 'Can view corp stats of members of their alliance.'),
('view_state_corpstats', 'Can view corp stats of members of their auth state.'),
)
verbose_name = "corp stats"
verbose_name_plural = "corp stats"
objects = CorpStatsManager()
def __str__(self):
return "%s for %s" % (self.__class__.__name__, self.corp)
def update(self):
try:
c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[
'corporation_id'] == int(self.corp.corporation_id)
member_ids = c.Corporation.get_corporations_corporation_id_members(
corporation_id=self.corp.corporation_id).result()
# requesting too many ids per call results in a HTTP400
# the swagger spec doesn't have a maxItems count
# manual testing says we can do over 350, but let's not risk it
member_id_chunks = [member_ids[i:i + 255] for i in range(0, len(member_ids), 255)]
member_name_chunks = [c.Universe.post_universe_names(ids=id_chunk).result() for id_chunk in
member_id_chunks]
member_list = {}
for name_chunk in member_name_chunks:
member_list.update({m['id']: m['name'] for m in name_chunk})
# bulk create new member models
missing_members = [m_id for m_id in member_ids if
not CorpMember.objects.filter(corpstats=self, character_id=m_id).exists()]
CorpMember.objects.bulk_create(
[CorpMember(character_id=m_id, character_name=member_list[m_id], corpstats=self) for m_id in
missing_members])
# purge old members
self.members.exclude(character_id__in=member_ids).delete()
# update the timer
self.save()
except TokenError as e:
logger.warning("%s failed to update: %s" % (self, e))
if self.token.user:
notify(self.token.user, "%s failed to update with your ESI token." % self,
message="Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.",
level="error")
self.delete()
except HTTPForbidden as e:
logger.warning("%s failed to update: %s" % (self, e))
if self.token.user:
notify(self.token.user, "%s failed to update with your ESI token." % self,
message="%s: %s" % (e.status_code, e.message), level="error")
self.delete()
except AssertionError:
logger.warning("%s token character no longer in corp." % self)
if self.token.user:
notify(self.token.user, "%s cannot update with your ESI token." % self,
message="%s cannot update with your ESI token as you have left corp." % self, level="error")
self.delete()
@property
def member_count(self):
return self.members.count()
@property
def user_count(self):
return len(set([m.main_character for m in self.members.all() if m.main_character]))
@property
def registered_member_count(self):
return len(self.registered_members)
@property
def registered_members(self):
return self.members.filter(pk__in=[m.pk for m in self.members.all() if m.registered])
@property
def unregistered_member_count(self):
return self.member_count - self.registered_member_count
@property
def unregistered_members(self):
return self.members.filter(pk__in=[m.pk for m in self.members.all() if not m.registered])
@property
def main_count(self):
return len(self.mains)
@property
def mains(self):
return self.members.filter(pk__in=[m.pk for m in self.members.all() if
m.main_character and int(m.main_character.character_id) == int(
m.character_id)])
def visible_to(self, user):
return CorpStats.objects.filter(pk=self.pk).visible_to(user).exists()
def can_update(self, user):
return self.token.user == user or self.visible_to(user)
def corp_logo(self, size=128):
return self.corp.logo_url(size)
def alliance_logo(self, size=128):
if self.corp.alliance:
return self.corp.alliance.logo_url(size)
else:
return EveAllianceInfo.generic_logo_url(1, size)
class CorpMember(models.Model):
character_id = models.PositiveIntegerField()
character_name = models.CharField(max_length=37)
corpstats = models.ForeignKey(CorpStats, on_delete=models.CASCADE, related_name='members')
class Meta:
# not making character_id unique in case a character moves between two corps while only one updates
unique_together = ('corpstats', 'character_id')
ordering = ['character_name']
def __str__(self):
return self.character_name
@property
def character(self):
try:
return EveCharacter.objects.get(character_id=self.character_id)
except EveCharacter.DoesNotExist:
return None
@property
def main_character(self):
try:
return self.character.character_ownership.user.profile.main_character
except (CharacterOwnership.DoesNotExist, UserProfile.DoesNotExist, AttributeError):
return None
@property
def alts(self):
if self.main_character:
return [co.character for co in self.main_character.character_ownership.user.character_ownerships.all()]
else:
return []
@property
def registered(self):
return CharacterOwnership.objects.filter(character__character_id=self.character_id).exists()
def portrait_url(self, size=32):
return EveCharacter.generic_portrait_url(self.character_id, size)
@property
def portrait_url_32(self):
return self.portrait_url(32)
@property
def portrait_url_64(self):
return self.portrait_url(64)
@property
def portrait_url_128(self):
return self.portrait_url(128)

File diff suppressed because one or more lines are too long

View File

@@ -1,219 +0,0 @@
{% extends 'corputils/base.html' %}
{% load i18n %}
{% load humanize %}
{% block member_data %}
{% if corpstats %}
<div class="row">
<div class="col-lg-12 text-center">
<table class="table">
<tr>
<td class="text-center col-lg-6{% if corpstats.corp.alliance %}{% else %}col-lg-offset-3{% endif %}">
<img class="ra-avatar" src="{{ corpstats.corp.logo_url_64 }}">
</td>
{% if corpstats.corp.alliance %}
<td class="text-center col-lg-6">
<img class="ra-avatar" src="{{ corpstats.corp.alliance.logo_url_64 }}">
</td>
{% endif %}
</tr>
<tr>
<td class="text-center"><h4>{{ corpstats.corp.corporation_name }}</h4></td>
{% if corpstats.corp.alliance %}
<td class="text-center"><h4>{{ corpstats.corp.alliance.alliance_name }}</h4></td>
{% endif %}
</tr>
</table>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<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' %} ({{ 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' %} ({{ unregistered.count }})</a></li>
</ul>
<div class="pull-right hidden-xs">
{% trans "Last update:" %} {{ corpstats.last_update|naturaltime }}&nbsp;
<a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now">
<span class="glyphicon glyphicon-refresh"></span>
</a>
</div>
<div class="clearfix"></div>
</div>
<div class="panel-body">
<div class="tab-content">
<div class="tab-pane fade in active" id="mains">
{% if mains %}
<div class="table-responsive">
<table class="table table-hover" id="table-mains">
<thead>
<tr>
<th style="height:1em;"><!-- Must have text or height to prevent clipping --></th>
<th></th>
</tr>
</thead>
<tbody>
{% 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.main.portrait_url_64 }}" class="img-circle">
<div class="caption text-center">
{{ main.main }}
</div>
</div>
</td>
<td>
<table class="table table-hover">
{% for alt in main.alts %}
{% if forloop.first %}
<tr>
<th></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"></th>
</tr>
{% endif %}
<tr>
<td class="text-center" style="width:5%">
<div class="thumbnail" style="border: 0 none; box-shadow: none; background: transparent;">
<img src="{{ alt.portrait_url_32 }}" class="img-circle">
</div>
</td>
<td class="text-center" style="width:30%">{{ alt.character_name }}</td>
<td class="text-center" style="width:30%">{{ alt.corporation_name }}</td>
<td class="text-center" style="width:30%">{{ alt.alliance_name }}</td>
<td class="text-center" style="width:5%">
<a href="https://zkillboard.com/character/{{ alt.character_id }}/"
class="label label-danger" target="_blank">
{% trans "Killboard" %}
</a>
</td>
</tr>
{% endfor %}
</table>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<div class="tab-pane fade" id="members">
{% if members %}
<div class="table-responsive">
<table class="table table-hover" id="table-members">
<thead>
<tr>
<th></th>
<th class="text-center">{% trans "Character" %}</th>
<th class="text-center"></th>
<th class="text-center">{% trans "Main Character" %}</th>
<th class="text-center">{% trans "Main Corporation" %}</th>
<th class="text-center">{% trans "Main Alliance" %}</th>
</tr>
</thead>
<tbody>
{% for member in members %}
<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"></td>
<td class="text-center"></td>
<td class="text-center"></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
<div class="tab-pane fade" id="unregistered">
{% if unregistered %}
<div class="table-responsive">
<table class="table table-hover" id="table-unregistered">
<thead>
<tr>
<th></th>
<th class="text-center">{% trans "Character" %}</th>
<th class="text-center"></th>
</tr>
</thead>
<tbody>
{% 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>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% endblock %}
{% block extra_javascript %}
{% include 'bundles/datatables-js.html' %}
{% endblock %}
{% block extra_css %}
{% include 'bundles/datatables-css.html' %}
{% endblock %}
{% block extra_script %}
$(document).ready(function(){
$('#table-mains').DataTable({
"columnDefs": [
{ "sortable": false, "targets": [1] },
],
});
$('#table-members').DataTable({
"columnDefs": [
{ "searchable": false, "targets": [0, 2] },
{ "sortable": false, "targets": [0, 2] },
],
"order": [[ 1, "asc" ]],
});
$('#table-unregistered').DataTable({
"columnDefs": [
{ "searchable": false, "targets": [0, 2] },
{ "sortable": false, "targets": [0, 2] },
],
"order": [[ 1, "asc" ]],
});
});
{% endblock %}

View File

@@ -1,48 +0,0 @@
{% extends "corputils/base.html" %}
{% load i18n %}
{% block member_data %}
<div class="panel panel-default">
<div class="panel-heading clearfix">
<div class="panel-title pull-left">{% trans "Search Results" %}</div>
</div>
<div class="panel-body">
<table class="table table-hover" id="table-search">
<thead>
<tr>
<th class="text-center"></th>
<th class="text-center">{% trans "Character" %}</th>
<th class="text-center">{% trans "Corporation" %}</th>
<th class="text-center">{% trans "zKillboard" %}</th>
<th class="text-center">{% trans "Main Character" %}</th>
<th class="text-center">{% trans "Main Corporation" %}</th>
<th class="text-center">{% trans "Main Alliance" %}</th>
</tr>
</thead>
<tbody>
{% for result in results %}
<tr {% if not result.1.registered %}class="danger"{% endif %}>
<td class="text-center"><img src="{{ result.1.portrait_url }}" class="img-circle"></td>
<td class="text-center">{{ result.1.character_name }}</td>
<td class="text-center">{{ result.0.corp.corporation_name }}</td>
<td class="text-center"><a href="https://zkillboard.com/character/{{ result.1.character_id }}/" class="label label-danger" target="_blank">{% trans "Killboard" %}</a></td>
<td class="text-center">{{ result.1.main_character.character_name }}</td>
<td class="text-center">{{ result.1.main_character.corporation_name }}</td>
<td class="text-center">{{ result.1.main_character.alliance_name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}
{% block extra_javascript %}
{% include 'bundles/datatables-js.html' %}
{% endblock %}
{% block extra_css %}
{% include 'bundles/datatables-css.html' %}
{% endblock %}
{% block extra_script %}
$(document).ready(function(){
$('#table-search').DataTable();
});
{% endblock %}

View File

@@ -1,280 +0,0 @@
from unittest import mock
from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils
from .models import CorpStats, CorpMember
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo, EveCharacter
from esi.models import Token
from esi.errors import TokenError
from bravado.exception import HTTPForbidden
from django.contrib.auth.models import Permission
from allianceauth.authentication.models import CharacterOwnership
class CorpStatsManagerTestCase(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.token = Token.objects.create(user=cls.user, access_token='a', character_id=1, character_name='test character', character_owner_hash='z')
cls.corpstats = CorpStats.objects.create(corp=cls.corp, token=cls.token)
cls.view_corp_permission = Permission.objects.get_by_natural_key('view_corp_corpstats', 'corputils', 'corpstats')
cls.view_alliance_permission = Permission.objects.get_by_natural_key('view_alliance_corpstats', 'corputils', 'corpstats')
cls.view_state_permission = Permission.objects.get_by_natural_key('view_state_corpstats', 'corputils', 'corpstats')
cls.state = AuthUtils.create_state('test state', 500)
AuthUtils.assign_state(cls.user, cls.state, disconnect_signals=True)
def setUp(self):
self.user.refresh_from_db()
self.user.user_permissions.clear()
self.state.refresh_from_db()
self.state.member_corporations.clear()
self.state.member_alliances.clear()
def test_visible_superuser(self):
self.user.is_superuser = True
cs = CorpStats.objects.visible_to(self.user)
self.assertIn(self.corpstats, cs)
def test_visible_corporation(self):
self.user.user_permissions.add(self.view_corp_permission)
cs = CorpStats.objects.visible_to(self.user)
self.assertIn(self.corpstats, cs)
def test_visible_alliance(self):
self.user.user_permissions.add(self.view_alliance_permission)
cs = CorpStats.objects.visible_to(self.user)
self.assertIn(self.corpstats, cs)
def test_visible_state_corp_member(self):
self.state.member_corporations.add(self.corp)
self.user.user_permissions.add(self.view_state_permission)
cs = CorpStats.objects.visible_to(self.user)
self.assertIn(self.corpstats, cs)
def test_visible_state_alliance_member(self):
self.state.member_alliances.add(self.alliance)
self.user.user_permissions.add(self.view_state_permission)
cs = CorpStats.objects.visible_to(self.user)
self.assertIn(self.corpstats, cs)
class CorpStatsUpdateTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('test')
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id=2, corp_name='test_corp', corp_ticker='TEST', alliance_id=3, alliance_name='TEST')
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id=1, character_name='test character', character_owner_hash='z')
cls.corp = EveCorporationInfo.objects.create(corporation_id=2, corporation_name='test corp', corporation_ticker='TEST', member_count=1)
def setUp(self):
self.corpstats = CorpStats.objects.get_or_create(token=self.token, corp=self.corp)[0]
def test_can_update(self):
self.assertTrue(self.corpstats.can_update(self.user))
self.corpstats.token.user = None
self.assertFalse(self.corpstats.can_update(self.user))
self.user.is_superuser = True
self.assertTrue(self.corpstats.can_update(self.user))
self.user.refresh_from_db()
self.corpstats.token.refresh_from_db()
@mock.patch('esi.clients.SwaggerClient')
def test_update_add_member(self, SwaggerClient):
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1]
SwaggerClient.from_spec.return_value.Universe.post_universe_names.return_value.result.return_value = [{'id': 1, 'name': 'test character'}]
self.corpstats.update()
self.assertTrue(CorpMember.objects.filter(character_id=1, character_name='test character', corpstats=self.corpstats).exists())
@mock.patch('esi.clients.SwaggerClient')
def test_update_remove_member(self, SwaggerClient):
CorpMember.objects.create(character_id=2, character_name='old test character', corpstats=self.corpstats)
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1]
SwaggerClient.from_spec.return_value.Universe.post_universe_names.return_value.result.return_value = [{'id': 1, 'name': 'test character'}]
self.corpstats.update()
self.assertFalse(CorpMember.objects.filter(character_id='2', corpstats=self.corpstats).exists())
@mock.patch('allianceauth.corputils.models.notify')
@mock.patch('esi.clients.SwaggerClient')
def test_update_deleted_token(self, SwaggerClient, notify):
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.side_effect = TokenError()
self.corpstats.update()
self.assertFalse(CorpStats.objects.filter(corp=self.corp).exists())
self.assertTrue(notify.called)
@mock.patch('allianceauth.corputils.models.notify')
@mock.patch('esi.clients.SwaggerClient')
def test_update_http_forbidden(self, SwaggerClient, notify):
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2}
SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.side_effect = HTTPForbidden(mock.Mock())
self.corpstats.update()
self.assertFalse(CorpStats.objects.filter(corp=self.corp).exists())
self.assertTrue(notify.called)
@mock.patch('allianceauth.corputils.models.notify')
@mock.patch('esi.clients.SwaggerClient')
def test_update_token_character_corp_changed(self, SwaggerClient, notify):
SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 3}
self.corpstats.update()
self.assertFalse(CorpStats.objects.filter(corp=self.corp).exists())
self.assertTrue(notify.called)
class CorpStatsPropertiesTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.user = AuthUtils.create_user('test')
AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id=2, corp_name='test_corp', corp_ticker='TEST', alliance_id=3, alliance_name='TEST')
cls.user.profile.refresh_from_db()
cls.token = Token.objects.create(user=cls.user, access_token='a', character_id=1, character_name='test character', character_owner_hash='z')
cls.corp = EveCorporationInfo.objects.create(corporation_id=2, corporation_name='test corp', corporation_ticker='TEST', member_count=1)
cls.corpstats = CorpStats.objects.create(token=cls.token, corp=cls.corp)
cls.character = EveCharacter.objects.create(character_name='another test character', character_id=4, corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
def test_member_count(self):
member = CorpMember.objects.create(corpstats=self.corpstats, character_id=2, character_name='test character')
self.assertEqual(self.corpstats.member_count, 1)
member.delete()
self.assertEqual(self.corpstats.member_count, 0)
def test_user_count(self):
AuthUtils.disconnect_signals()
co = CharacterOwnership.objects.create(character=self.character, user=self.user, owner_hash='a')
AuthUtils.connect_signals()
CorpMember.objects.create(corpstats=self.corpstats, character_id=4, character_name='test character')
self.assertEqual(self.corpstats.user_count, 1)
co.delete()
self.assertEqual(self.corpstats.user_count, 0)
def test_registered_members(self):
AuthUtils.disconnect_signals()
co = CharacterOwnership.objects.create(character=self.character, user=self.user, owner_hash='a')
AuthUtils.connect_signals()
member = CorpMember.objects.create(corpstats=self.corpstats, character_id=4, character_name='test character')
self.corpstats.refresh_from_db()
self.assertIn(member, self.corpstats.registered_members)
self.assertEqual(self.corpstats.registered_member_count, 1)
co.delete()
self.assertNotIn(member, self.corpstats.registered_members)
self.assertEqual(self.corpstats.registered_member_count, 0)
def test_unregistered_members(self):
member = CorpMember.objects.create(corpstats=self.corpstats, character_id=4, character_name='test character')
self.corpstats.refresh_from_db()
self.assertIn(member, self.corpstats.unregistered_members)
self.assertEqual(self.corpstats.unregistered_member_count, 1)
AuthUtils.disconnect_signals()
CharacterOwnership.objects.create(character=self.character, user=self.user, owner_hash='a')
AuthUtils.connect_signals()
self.assertNotIn(member, self.corpstats.unregistered_members)
self.assertEqual(self.corpstats.unregistered_member_count, 0)
def test_mains(self):
# test when is a main
member = CorpMember.objects.create(corpstats=self.corpstats, character_id=1, character_name='test character')
self.assertIn(member, self.corpstats.mains)
self.assertEqual(self.corpstats.main_count, 1)
# test when is an alt
old_main = self.user.profile.main_character
character = EveCharacter.objects.create(character_name='other character', character_id=10, corporation_name='test corp', corporation_id=2, corporation_ticker='TEST')
AuthUtils.disconnect_signals()
co = CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
self.user.profile.main_character = character
self.user.profile.save()
AuthUtils.connect_signals()
self.assertNotIn(member, self.corpstats.mains)
self.assertEqual(self.corpstats.main_count, 0)
# test when no ownership
co.delete()
self.assertNotIn(member, self.corpstats.mains)
self.assertEqual(self.corpstats.main_count, 0)
# transaction won't roll this back
AuthUtils.disconnect_signals()
self.user.profile.main_character = old_main
self.user.profile.save()
AuthUtils.connect_signals()
def test_logos(self):
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://images.evetech.net/alliances/3/logo?size=128')
alliance.delete()
class CorpMemberTestCase(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.token = Token.objects.create(user=cls.user, access_token='a', character_id=1, character_name='test character', character_owner_hash='a')
cls.corp = EveCorporationInfo.objects.create(corporation_id=2, corporation_name='test corp', corporation_ticker='TEST', member_count=1)
cls.corpstats = CorpStats.objects.create(token=cls.token, corp=cls.corp)
cls.member = CorpMember.objects.create(corpstats=cls.corpstats, character_id=2, character_name='other test character')
def test_character(self):
self.assertIsNone(self.member.character)
character = EveCharacter.objects.create(character_id=2, character_name='other test character', corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
self.assertEqual(self.member.character, character)
def test_main_character(self):
AuthUtils.disconnect_signals()
# test when member.character is None
self.assertIsNone(self.member.main_character)
# test when member.character is not None but also not a main
character = EveCharacter.objects.create(character_id=2, character_name='other test character', corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
self.member.refresh_from_db()
self.assertNotEqual(self.member.main_character, self.member.character)
self.assertEquals(self.member.main_character, self.user.profile.main_character)
# test when is main
old_main = self.user.profile.main_character
self.user.profile.main_character = character
self.user.profile.save()
self.member.refresh_from_db()
self.assertEqual(self.member.main_character, self.member.character)
self.assertEqual(self.user.profile.main_character, self.member.main_character)
# transaction won't roll this back
self.user.profile.main_character = old_main
self.user.profile.save()
AuthUtils.connect_signals()
def test_alts(self):
self.assertListEqual(self.member.alts, [])
character = EveCharacter.objects.create(character_id=2, character_name='other test character', corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
self.assertIn(character, self.member.alts)
def test_registered(self):
self.assertFalse(self.member.registered)
AuthUtils.disconnect_signals()
character = EveCharacter.objects.create(character_id=2, character_name='other test character', corporation_id=2, corporation_name='test corp', corporation_ticker='TEST')
CharacterOwnership.objects.create(character=character, user=self.user, owner_hash='b')
self.assertTrue(self.member.registered)
AuthUtils.connect_signals()
def test_portrait_url(self):
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

@@ -1,12 +0,0 @@
from django.conf.urls import url
from . import views
app_name = 'corputils'
urlpatterns = [
url(r'^$', views.corpstats_view, name='view'),
url(r'^add/$', views.corpstats_add, name='add'),
url(r'^(?P<corp_id>(\d)*)/$', views.corpstats_view, name='view_corp'),
url(r'^(?P<corp_id>(\d)+)/update/$', views.corpstats_update, name='update'),
url(r'^search/$', views.corpstats_search, name='search'),
]

View File

@@ -1 +0,0 @@
default_app_config = 'allianceauth.eveonline.apps.EveonlineConfig'

View File

@@ -1,135 +0,0 @@
from django import forms
from django.contrib import admin
from django.core.exceptions import ObjectDoesNotExist
from .providers import ObjectNotFound
from .models import EveAllianceInfo
from .models import EveCharacter
from .models import EveCorporationInfo
class EveEntityExistsError(forms.ValidationError):
def __init__(self, entity_type_name, id):
super(EveEntityExistsError, self).__init__(
message='{} with ID {} already exists.'.format(entity_type_name, id))
class EveEntityNotFoundError(forms.ValidationError):
def __init__(self, entity_type_name, id):
super(EveEntityNotFoundError, self).__init__(
message='{} with ID {} not found.'.format(entity_type_name, id))
class EveEntityForm(forms.ModelForm):
id = forms.IntegerField(min_value=1)
entity_model_class = None
def clean_id(self):
raise NotImplementedError()
def save(self, commit=True):
raise NotImplementedError()
def save_m2m(self):
pass
class EveCharacterForm(EveEntityForm):
entity_type_name = 'character'
def clean_id(self):
try:
assert self.Meta.model.provider.get_character(self.cleaned_data['id'])
except (AssertionError, ObjectNotFound):
raise EveEntityNotFoundError(self.entity_type_name, self.cleaned_data['id'])
if self.Meta.model.objects.filter(character_id=self.cleaned_data['id']).exists():
raise EveEntityExistsError(self.entity_type_name, self.cleaned_data['id'])
return self.cleaned_data['id']
def save(self, commit=True):
return self.Meta.model.objects.create_character(self.cleaned_data['id'])
class Meta:
fields = ['id']
model = EveCharacter
class EveCorporationForm(EveEntityForm):
entity_type_name = 'corporation'
def clean_id(self):
try:
assert self.Meta.model.provider.get_corporation(self.cleaned_data['id'])
except (AssertionError, ObjectNotFound):
raise EveEntityNotFoundError(self.entity_type_name, self.cleaned_data['id'])
if self.Meta.model.objects.filter(corporation_id=self.cleaned_data['id']).exists():
raise EveEntityExistsError(self.entity_type_name, self.cleaned_data['id'])
return self.cleaned_data['id']
def save(self, commit=True):
return self.Meta.model.objects.create_corporation(self.cleaned_data['id'])
class Meta:
fields = ['id']
model = EveCorporationInfo
class EveAllianceForm(EveEntityForm):
entity_type_name = 'alliance'
def clean_id(self):
try:
assert self.Meta.model.provider.get_alliance(self.cleaned_data['id'])
except (AssertionError, ObjectNotFound):
raise EveEntityNotFoundError(self.entity_type_name, self.cleaned_data['id'])
if self.Meta.model.objects.filter(alliance_id=self.cleaned_data['id']).exists():
raise EveEntityExistsError(self.entity_type_name, self.cleaned_data['id'])
return self.cleaned_data['id']
def save(self, commit=True):
return self.Meta.model.objects.create_alliance(self.cleaned_data['id'])
class Meta:
fields = ['id']
model = EveAllianceInfo
@admin.register(EveCorporationInfo)
class EveCorporationInfoAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if not obj or not obj.pk:
return EveCorporationForm
return super(EveCorporationInfoAdmin, self).get_form(request, obj=obj, **kwargs)
@admin.register(EveAllianceInfo)
class EveAllianceInfoAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
if not obj or not obj.pk:
return EveAllianceForm
return super(EveAllianceInfoAdmin, self).get_form(request, obj=obj, **kwargs)
@admin.register(EveCharacter)
class EveCharacterAdmin(admin.ModelAdmin):
search_fields = ['character_name', 'corporation_name', 'alliance_name', 'character_ownership__user__username']
list_display = ('character_name', 'corporation_name', 'alliance_name', 'user', 'main_character')
@staticmethod
def user(obj):
try:
return obj.character_ownership.user
except (AttributeError, ObjectDoesNotExist):
return None
@staticmethod
def main_character(obj):
try:
return obj.character_ownership.user.profile.main_character
except (AttributeError, ObjectDoesNotExist):
return None
def get_form(self, request, obj=None, **kwargs):
if not obj or not obj.pk:
return EveCharacterForm
return super(EveCharacterAdmin, self).get_form(request, obj=obj, **kwargs)

View File

@@ -1 +0,0 @@
default_app_config = 'allianceauth.eveonline.autogroups.apps.EveAutogroupsConfig'

View File

@@ -1,42 +0,0 @@
from django.contrib import admin
from django.db import models
from .models import AutogroupsConfig, ManagedCorpGroup, ManagedAllianceGroup
import logging
logger = logging.getLogger(__name__)
def sync_user_groups(modeladmin, request, queryset):
for agc in queryset:
logger.debug("update_all_states_group_membership for {}".format(agc))
agc.update_all_states_group_membership()
class AutogroupsConfigAdmin(admin.ModelAdmin):
formfield_overrides = {
models.CharField: {'strip': False}
}
def get_readonly_fields(self, request, obj=None):
if obj: # This is the case when obj is already created i.e. it's an edit
return [
'corp_group_prefix', 'corp_name_source', 'alliance_group_prefix', 'alliance_name_source',
'replace_spaces', 'replace_spaces_with'
]
else:
return []
def get_actions(self, request):
actions = super(AutogroupsConfigAdmin, self).get_actions(request)
actions['sync_user_groups'] = (sync_user_groups,
'sync_user_groups',
'Sync all users groups for this Autogroup Config')
return actions
admin.site.register(AutogroupsConfig, AutogroupsConfigAdmin)
admin.site.register(ManagedCorpGroup)
admin.site.register(ManagedAllianceGroup)

View File

@@ -1,9 +0,0 @@
from django.apps import AppConfig
class EveAutogroupsConfig(AppConfig):
name = 'allianceauth.eveonline.autogroups'
label = 'eve_autogroups'
def ready(self):
import allianceauth.eveonline.autogroups.signals

View File

@@ -1,73 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2017-12-23 04:30
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('authentication', '0015_user_profiles'),
('auth', '0008_alter_user_username_max_length'),
('eveonline', '0009_on_delete'),
]
operations = [
migrations.CreateModel(
name='AutogroupsConfig',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('corp_groups', models.BooleanField(default=False, help_text='Setting this to false will delete all the created groups.')),
('corp_group_prefix', models.CharField(blank=True, default='Corp ', max_length=50)),
('corp_name_source', models.CharField(choices=[('ticker', 'Ticker'), ('name', 'Full name')], default='name', max_length=20)),
('alliance_groups', models.BooleanField(default=False, help_text='Setting this to false will delete all the created groups.')),
('alliance_group_prefix', models.CharField(blank=True, default='Alliance ', max_length=50)),
('alliance_name_source', models.CharField(choices=[('ticker', 'Ticker'), ('name', 'Full name')], default='name', max_length=20)),
('replace_spaces', models.BooleanField(default=False)),
('replace_spaces_with', models.CharField(blank=True, default='', help_text='Any spaces in the group name will be replaced with this.', max_length=10)),
],
),
migrations.CreateModel(
name='ManagedAllianceGroup',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('alliance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='eveonline.EveAllianceInfo')),
('config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='eve_autogroups.AutogroupsConfig')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='ManagedCorpGroup',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='eve_autogroups.AutogroupsConfig')),
('corp', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='eveonline.EveCorporationInfo')),
('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='autogroupsconfig',
name='alliance_managed_groups',
field=models.ManyToManyField(help_text="A list of alliance groups created and maintained by this AutogroupConfig. You should not edit this list unless you know what you're doing.", related_name='alliance_managed_config', through='eve_autogroups.ManagedAllianceGroup', to='auth.Group'),
),
migrations.AddField(
model_name='autogroupsconfig',
name='corp_managed_groups',
field=models.ManyToManyField(help_text="A list of corporation groups created and maintained by this AutogroupConfig. You should not edit this list unless you know what you're doing.", related_name='corp_managed_config', through='eve_autogroups.ManagedCorpGroup', to='auth.Group'),
),
migrations.AddField(
model_name='autogroupsconfig',
name='states',
field=models.ManyToManyField(related_name='autogroups', to='authentication.State'),
),
]

View File

@@ -1,253 +0,0 @@
import logging
from django.db import models, transaction
from django.contrib.auth.models import Group, User
from django.core.exceptions import ObjectDoesNotExist
from allianceauth.authentication.models import State
from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo
logger = logging.getLogger(__name__)
def get_users_for_state(state: State):
return User.objects.select_related('profile').prefetch_related('profile__main_character')\
.filter(profile__state_id=state.pk)
class AutogroupsConfigManager(models.Manager):
def update_groups_for_state(self, state: State):
"""
Update all the Group memberships for the users
who have State
:param state: State to update for
:return:
"""
users = get_users_for_state(state)
for config in self.filter(states=state):
logger.debug("in state loop")
for user in users:
logger.debug("in user loop for {}".format(user))
config.update_group_membership_for_user(user)
def update_groups_for_user(self, user: User, state: State = None):
"""
Update the Group memberships for the given users state
:param user: User to update for
:param state: State to update user for
:return:
"""
if state is None:
state = user.profile.state
for config in self.filter(states=state):
# grant user new groups for their state
config.update_group_membership_for_user(user)
for config in self.exclude(states=state):
# ensure user does not have groups from previous state
config.remove_user_from_alliance_groups(user)
config.remove_user_from_corp_groups(user)
class AutogroupsConfig(models.Model):
OPT_TICKER = 'ticker'
OPT_NAME = 'name'
NAME_OPTIONS = (
(OPT_TICKER, 'Ticker'),
(OPT_NAME, 'Full name'),
)
states = models.ManyToManyField(State, related_name='autogroups')
corp_groups = models.BooleanField(default=False,
help_text="Setting this to false will delete all the created groups.")
corp_group_prefix = models.CharField(max_length=50, default='Corp ', blank=True)
corp_name_source = models.CharField(max_length=20, choices=NAME_OPTIONS, default=OPT_NAME)
alliance_groups = models.BooleanField(default=False,
help_text="Setting this to false will delete all the created groups.")
alliance_group_prefix = models.CharField(max_length=50, default='Alliance ', blank=True)
alliance_name_source = models.CharField(max_length=20, choices=NAME_OPTIONS, default=OPT_NAME)
corp_managed_groups = models.ManyToManyField(
Group, through='ManagedCorpGroup', related_name='corp_managed_config',
help_text='A list of corporation groups created and maintained by this AutogroupConfig. '
'You should not edit this list unless you know what you\'re doing.')
alliance_managed_groups = models.ManyToManyField(
Group, through='ManagedAllianceGroup', related_name='alliance_managed_config',
help_text='A list of alliance groups created and maintained by this AutogroupConfig. '
'You should not edit this list unless you know what you\'re doing.')
replace_spaces = models.BooleanField(default=False)
replace_spaces_with = models.CharField(
max_length=10, default='', blank=True,
help_text='Any spaces in the group name will be replaced with this.')
objects = AutogroupsConfigManager()
def __init__(self, *args, **kwargs):
super(AutogroupsConfig, self).__init__(*args, **kwargs)
def __repr__(self):
return self.__class__.__name__
def __str__(self):
return 'States: ' + (' '.join(list(self.states.all().values_list('name', flat=True))) if self.pk else str(None))
def update_all_states_group_membership(self):
list(map(self.update_group_membership_for_state, self.states.all()))
def update_group_membership_for_state(self, state: State):
list(map(self.update_group_membership_for_user, get_users_for_state(state)))
@transaction.atomic
def update_group_membership_for_user(self, user: User):
self.update_alliance_group_membership(user)
self.update_corp_group_membership(user)
def user_entitled_to_groups(self, user: User) -> bool:
try:
return user.profile.state in self.states.all()
except ObjectDoesNotExist:
return False
@transaction.atomic
def update_alliance_group_membership(self, user: User):
group = None
try:
if not self.alliance_groups or not self.user_entitled_to_groups(user):
logger.debug('User {} does not have required state for alliance group membership'.format(user))
return
else:
alliance = user.profile.main_character.alliance
if alliance is None:
logger.debug('User {} alliance is None, cannot update group membership'.format(user))
return
group = self.get_alliance_group(alliance)
except EveAllianceInfo.DoesNotExist:
logger.debug('User {} main characters alliance does not exist in the database. Creating.'.format(user))
alliance = EveAllianceInfo.objects.create_alliance(user.profile.main_character.alliance_id)
group = self.get_alliance_group(alliance)
except AttributeError:
logger.warning('User {} does not have a main character. Group membership not updated'.format(user))
finally:
self.remove_user_from_alliance_groups(user, except_group=group)
if group is not None:
logger.debug('Adding user {} to alliance group {}'.format(user, group))
user.groups.add(group)
@transaction.atomic
def update_corp_group_membership(self, user: User):
group = None
try:
if not self.corp_groups or not self.user_entitled_to_groups(user):
logger.debug('User {} does not have required state for corp group membership'.format(user))
else:
corp = user.profile.main_character.corporation
group = self.get_corp_group(corp)
except EveCorporationInfo.DoesNotExist:
logger.debug('User {} main characters corporation does not exist in the database. Creating.'.format(user))
corp = EveCorporationInfo.objects.create_corporation(user.profile.main_character.corporation_id)
group = self.get_corp_group(corp)
except AttributeError:
logger.warning('User {} does not have a main character. Group membership not updated'.format(user))
finally:
self.remove_user_from_corp_groups(user, except_group=group)
if group is not None:
logger.debug('Adding user {} to corp group {}'.format(user, group))
user.groups.add(group)
@transaction.atomic
def remove_user_from_alliance_groups(self, user: User, except_group: Group = None):
remove_groups = user.groups.filter(pk__in=self.alliance_managed_groups.all().values_list('pk', flat=True))
if except_group is not None:
remove_groups = remove_groups.exclude(pk=except_group.pk)
list(map(user.groups.remove, remove_groups))
@transaction.atomic
def remove_user_from_corp_groups(self, user: User, except_group: Group = None):
remove_groups = user.groups.filter(pk__in=self.corp_managed_groups.all().values_list('pk', flat=True))
if except_group is not None:
remove_groups = remove_groups.exclude(pk=except_group.pk)
list(map(user.groups.remove, remove_groups))
def get_alliance_group(self, alliance: EveAllianceInfo) -> Group:
return self.create_alliance_group(alliance)
def get_corp_group(self, corp: EveCorporationInfo) -> Group:
return self.create_corp_group(corp)
@transaction.atomic
def create_alliance_group(self, alliance: EveAllianceInfo) -> Group:
group, created = Group.objects.get_or_create(name=self.get_alliance_group_name(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))
ManagedCorpGroup.objects.get_or_create(group=group, config=self, corp=corp)
return group
def delete_alliance_managed_groups(self):
"""
Deletes ALL managed alliance groups
"""
for g in self.alliance_managed_groups.all():
g.delete()
def delete_corp_managed_groups(self):
"""
Deletes ALL managed corp groups
"""
for g in self.corp_managed_groups.all():
g.delete()
def get_alliance_group_name(self, alliance: EveAllianceInfo) -> str:
if self.alliance_name_source == self.OPT_TICKER:
name = alliance.alliance_ticker
elif self.alliance_name_source == self.OPT_NAME:
name = alliance.alliance_name
else:
raise NameSourceException('Not a valid name source')
return self._replace_spaces(self.alliance_group_prefix + name)
def get_corp_group_name(self, corp: EveCorporationInfo) -> str:
if self.corp_name_source == self.OPT_TICKER:
name = corp.corporation_ticker
elif self.corp_name_source == self.OPT_NAME:
name = corp.corporation_name
else:
raise NameSourceException('Not a valid name source')
return self._replace_spaces(self.corp_group_prefix + name)
def _replace_spaces(self, name: str) -> str:
"""
Replace the spaces in the given name based on the config
:param name: name to replace spaces in
:return: name with spaces replaced with the configured character(s) or unchanged if configured
"""
if self.replace_spaces:
return name.strip().replace(' ', str(self.replace_spaces_with))
return name
class ManagedGroup(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
config = models.ForeignKey(AutogroupsConfig, on_delete=models.CASCADE)
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)
class ManagedAllianceGroup(ManagedGroup):
alliance = models.ForeignKey(EveAllianceInfo, on_delete=models.CASCADE)
class NameSourceException(Exception):
pass

View File

@@ -1,75 +0,0 @@
import logging
from django.dispatch import receiver
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
from allianceauth.authentication.models import UserProfile, State
from allianceauth.eveonline.models import EveCharacter
from .models import AutogroupsConfig
logger = logging.getLogger(__name__)
@receiver(pre_save, sender=AutogroupsConfig)
def pre_save_config(sender, instance, *args, **kwargs):
"""
Checks if enable was toggled on group config and
deletes groups if necessary.
"""
logger.debug("Received pre_save from {}".format(instance))
if not instance.pk:
# new model being created
return
try:
old_instance = AutogroupsConfig.objects.get(pk=instance.pk)
# Check if enable was toggled, delete groups?
if old_instance.alliance_groups is True and instance.alliance_groups is False:
instance.delete_alliance_managed_groups()
if old_instance.corp_groups is True and instance.corp_groups is False:
instance.delete_corp_managed_groups()
except AutogroupsConfig.DoesNotExist:
pass
@receiver(pre_delete, sender=AutogroupsConfig)
def pre_delete_config(sender, instance, *args, **kwargs):
"""
Delete groups on deleting config
"""
instance.delete_corp_managed_groups()
instance.delete_alliance_managed_groups()
@receiver(post_save, sender=UserProfile)
def check_groups_on_profile_update(sender, instance, created, *args, **kwargs):
"""
Trigger check when main character or state changes.
"""
AutogroupsConfig.objects.update_groups_for_user(instance.user)
@receiver(m2m_changed, sender=AutogroupsConfig.states.through)
def autogroups_states_changed(sender, instance, action, reverse, model, pk_set, *args, **kwargs):
"""
Trigger group membership update when a state is added or removed from
an autogroup config.
"""
if action.startswith('post_'):
for pk in pk_set:
try:
state = State.objects.get(pk=pk)
instance.update_group_membership_for_state(state)
except State.DoesNotExist:
# Deleted States handled by the profile state change
pass
@receiver(post_save, sender=EveCharacter)
def check_groups_on_character_update(sender, instance, created, *args, **kwargs):
if not created:
try:
profile = UserProfile.objects.prefetch_related('user').get(main_character_id=instance.pk)
AutogroupsConfig.objects.update_groups_for_user(profile.user)
except UserProfile.DoesNotExist:
pass

View File

@@ -1,28 +0,0 @@
from unittest import mock
from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed
from allianceauth.authentication.models import UserProfile
from allianceauth.authentication.signals import reassess_on_profile_save
from .. import signals
from ..models import AutogroupsConfig
MODULE_PATH = 'allianceauth.eveonline.autogroups'
def patch(target, *args, **kwargs):
return mock.patch('{}{}'.format(MODULE_PATH, target), *args, **kwargs)
def connect_signals():
post_save.connect(receiver=reassess_on_profile_save, sender=UserProfile)
pre_save.connect(receiver=signals.pre_save_config, sender=AutogroupsConfig)
pre_delete.connect(receiver=signals.pre_delete_config, sender=AutogroupsConfig)
post_save.connect(receiver=signals.check_groups_on_profile_update, sender=UserProfile)
m2m_changed.connect(receiver=signals.autogroups_states_changed, sender=AutogroupsConfig.states.through)
def disconnect_signals():
post_save.disconnect(receiver=reassess_on_profile_save, sender=UserProfile)
pre_save.disconnect(receiver=signals.pre_save_config, sender=AutogroupsConfig)
pre_delete.disconnect(receiver=signals.pre_delete_config, sender=AutogroupsConfig)
post_save.disconnect(receiver=signals.check_groups_on_profile_update, sender=UserProfile)
m2m_changed.disconnect(receiver=signals.autogroups_states_changed, sender=AutogroupsConfig.states.through)

View File

@@ -1,75 +0,0 @@
from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils
from ..models import AutogroupsConfig
from . import patch
class AutogroupsConfigManagerTestCase(TestCase):
def test_update_groups_for_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_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)
def test_update_groups_for_user(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
)
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')
def test_update_groups_no_config(self, remove_corp, remove_alliance, update_groups):
member = AuthUtils.create_member('test member')
obj = AutogroupsConfig.objects.create()
# Corp and alliance groups should be removed from users if their state has no config
AutogroupsConfig.objects.update_groups_for_user(member)
self.assertFalse(update_groups.called)
self.assertTrue(remove_alliance.called)
self.assertTrue(remove_corp.called)
# The normal group assignment should occur if there state has a config
obj.states.add(member.profile.state)
AutogroupsConfig.objects.update_groups_for_user(member)
self.assertTrue(update_groups.called)

View File

@@ -1,368 +0,0 @@
from django.test import TestCase
from django.contrib.auth.models import Group
from django.db import transaction
from allianceauth.tests.auth_utils import AuthUtils
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from ..models import AutogroupsConfig, get_users_for_state
from . import patch, connect_signals, disconnect_signals
class AutogroupsConfigTestCase(TestCase):
def setUp(self):
# Disconnect signals
disconnect_signals()
state = AuthUtils.get_member_state()
self.alliance = EveAllianceInfo.objects.create(
alliance_id='3456',
alliance_name='alliance name',
alliance_ticker='TIKR',
executor_corp_id='2345',
)
self.corp = EveCorporationInfo.objects.create(
corporation_id='2345',
corporation_name='corp name',
corporation_ticker='TIKK',
member_count=10,
alliance=self.alliance,
)
state.member_alliances.add(self.alliance)
state.member_corporations.add(self.corp)
self.member = AuthUtils.create_member('test user')
def tearDown(self):
# Reconnect signals
connect_signals()
def test_get_users_for_state(self):
result = get_users_for_state(self.member.profile.state)
self.assertIn(self.member, result)
self.assertEqual(len(result), 1)
@patch('.models.AutogroupsConfig.update_alliance_group_membership')
@patch('.models.AutogroupsConfig.update_corp_group_membership')
def test_update_group_membership_for_user(
self,
update_corp,
update_alliance
):
agc = AutogroupsConfig.objects.create()
agc.update_group_membership_for_user(self.member)
self.assertTrue(update_corp.called)
self.assertTrue(update_alliance.called)
args, kwargs = update_corp.call_args
self.assertEqual(args[0], self.member)
args, kwargs = update_alliance.call_args
self.assertEqual(args[0], self.member)
def test_update_alliance_group_membership(self):
obj = AutogroupsConfig.objects.create(alliance_groups=True)
obj.states.add(AuthUtils.get_member_state())
char = EveCharacter.objects.create(
character_id='1234',
character_name='test character',
corporation_id='2345',
corporation_name='test corp',
corporation_ticker='tickr',
alliance_id='3456',
alliance_name='alliance name',
)
self.member.profile.main_character = char
self.member.profile.save()
pre_groups = self.member.groups.all()
# Act
obj.update_alliance_group_membership(self.member)
obj.update_corp_group_membership(self.member) # check for no side effects
group = obj.create_alliance_group(self.alliance)
group_qs = Group.objects.filter(pk=group.pk)
self.assertIn(group, self.member.groups.all())
self.assertQuerysetEqual(self.member.groups.all(), map(repr, pre_groups | group_qs), ordered=False)
def test_update_alliance_group_membership_no_main_character(self):
obj = AutogroupsConfig.objects.create()
obj.states.add(AuthUtils.get_member_state())
# Act
obj.update_alliance_group_membership(self.member)
group = obj.get_alliance_group(self.alliance)
self.assertNotIn(group, self.member.groups.all())
# 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',
character_name='test character',
corporation_id='2345',
corporation_name='test corp',
corporation_ticker='tickr',
alliance_id='3459',
alliance_name='alliance name',
)
self.member.profile.main_character = char
self.member.profile.save()
# 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)
obj.states.add(AuthUtils.get_member_state())
char = EveCharacter.objects.create(
character_id='1234',
character_name='test character',
corporation_id='2345',
corporation_name='test corp',
corporation_ticker='tickr',
alliance_id='3456',
alliance_name='alliance name',
)
self.member.profile.main_character = char
self.member.profile.save()
pre_groups = self.member.groups.all()
# Act
obj.update_corp_group_membership(self.member)
group = obj.get_corp_group(self.corp)
group_qs = Group.objects.filter(pk=group.pk)
self.assertIn(group, self.member.groups.all())
self.assertQuerysetEqual(self.member.groups.all(), map(repr, pre_groups | group_qs), ordered=False)
def test_update_corp_group_membership_no_state(self):
obj = AutogroupsConfig.objects.create(corp_groups=True)
char = EveCharacter.objects.create(
character_id='1234',
character_name='test character',
corporation_id='2345',
corporation_name='test corp',
corporation_ticker='tickr',
alliance_id='3456',
alliance_name='alliance name',
)
self.member.profile.main_character = char
self.member.profile.save()
pre_groups = list(self.member.groups.all())
# Act
obj.update_corp_group_membership(self.member)
group = obj.get_corp_group(self.corp)
post_groups = list(self.member.groups.all())
self.assertNotIn(group, post_groups)
self.assertListEqual(pre_groups, post_groups)
def test_update_corp_group_membership_no_main_character(self):
obj = AutogroupsConfig.objects.create()
obj.states.add(AuthUtils.get_member_state())
# Act
obj.update_corp_group_membership(self.member)
group = obj.get_corp_group(self.corp)
self.assertNotIn(group, self.member.groups.all())
def test_update_corp_group_membership_no_corp_model(self):
obj = AutogroupsConfig.objects.create()
obj.states.add(AuthUtils.get_member_state())
char = EveCharacter.objects.create(
character_id='1234',
character_name='test character',
corporation_id='2348',
corporation_name='test corp',
corporation_ticker='tickr',
alliance_id='3456',
alliance_name='alliance name',
)
self.member.profile.main_character = char
self.member.profile.save()
# Act
obj.update_corp_group_membership(self.member)
group = obj.get_corp_group(self.corp)
self.assertNotIn(group, self.member.groups.all())
def test_remove_user_from_alliance_groups(self):
obj = AutogroupsConfig.objects.create()
result = obj.get_alliance_group(self.alliance)
result.user_set.add(self.member)
self.assertIn(result, self.member.groups.all())
# Act
obj.remove_user_from_alliance_groups(self.member)
self.assertNotIn(result, self.member.groups.all())
def test_remove_user_from_corp_groups(self):
obj = AutogroupsConfig.objects.create()
result = obj.get_corp_group(self.corp)
result.user_set.add(self.member)
self.assertIn(result, self.member.groups.all())
# Act
obj.remove_user_from_corp_groups(self.member)
self.assertNotIn(result, self.member.groups.all())
def test_get_alliance_group(self):
obj = AutogroupsConfig.objects.create()
result = obj.get_alliance_group(self.alliance)
group = Group.objects.get(name='Alliance alliance name')
self.assertEqual(result, group)
self.assertEqual(obj.get_alliance_group_name(self.alliance), result.name)
self.assertTrue(obj.alliance_managed_groups.filter(pk=result.pk).exists())
def test_get_corp_group(self):
obj = AutogroupsConfig.objects.create()
result = obj.get_corp_group(self.corp)
group = Group.objects.get(name='Corp corp name')
self.assertEqual(result, group)
self.assertEqual(obj.get_corp_group_name(self.corp), group.name)
self.assertTrue(obj.corp_managed_groups.filter(pk=group.pk).exists())
def test_create_alliance_group(self):
obj = AutogroupsConfig.objects.create()
result = obj.create_alliance_group(self.alliance)
group = Group.objects.get(name='Alliance alliance name')
self.assertEqual(result, group)
self.assertEqual(obj.get_alliance_group_name(self.alliance), group.name)
self.assertTrue(obj.alliance_managed_groups.filter(pk=group.pk).exists())
def test_create_corp_group(self):
obj = AutogroupsConfig.objects.create()
result = obj.create_corp_group(self.corp)
group = Group.objects.get(name='Corp corp name')
self.assertEqual(result, group)
self.assertEqual(obj.get_corp_group_name(self.corp), group.name)
self.assertTrue(obj.corp_managed_groups.filter(pk=group.pk).exists())
def test_delete_alliance_managed_groups(self):
obj = AutogroupsConfig.objects.create()
obj.create_alliance_group(self.alliance)
self.assertTrue(obj.alliance_managed_groups.all().exists())
obj.delete_alliance_managed_groups()
self.assertFalse(obj.alliance_managed_groups.all().exists())
def test_delete_corp_managed_groups(self):
obj = AutogroupsConfig.objects.create()
obj.create_corp_group(self.corp)
self.assertTrue(obj.corp_managed_groups.all().exists())
obj.delete_corp_managed_groups()
self.assertFalse(obj.corp_managed_groups.all().exists())
def test_get_alliance_group_name(self):
obj = AutogroupsConfig()
obj.replace_spaces = True
obj.replace_spaces_with = '_'
result = obj.get_alliance_group_name(self.alliance)
self.assertEqual(result, 'Alliance_alliance_name')
def test_get_alliance_group_name_ticker(self):
obj = AutogroupsConfig()
obj.replace_spaces = True
obj.replace_spaces_with = '_'
obj.alliance_name_source = obj.OPT_TICKER
result = obj.get_alliance_group_name(self.alliance)
self.assertEqual(result, 'Alliance_TIKR')
def test_get_corp_group_name(self):
obj = AutogroupsConfig()
obj.replace_spaces = True
obj.replace_spaces_with = '_'
result = obj.get_corp_group_name(self.corp)
self.assertEqual(result, 'Corp_corp_name')
def test_get_corp_group_name_ticker(self):
obj = AutogroupsConfig()
obj.replace_spaces = True
obj.replace_spaces_with = '_'
obj.corp_name_source = obj.OPT_TICKER
result = obj.get_corp_group_name(self.corp)
self.assertEqual(result, 'Corp_TIKK')
def test__replace_spaces(self):
obj = AutogroupsConfig()
obj.replace_spaces = True
obj.replace_spaces_with = '*'
name = ' test name '
result = obj._replace_spaces(name)
self.assertEqual(result, 'test*name')

View File

@@ -1,207 +0,0 @@
from django.test import TestCase
from django.contrib.auth.models import User
from allianceauth.tests.auth_utils import AuthUtils
from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from ..models import AutogroupsConfig
from . import patch, disconnect_signals, connect_signals
class SignalsTestCase(TestCase):
def setUp(self):
disconnect_signals()
state = AuthUtils.get_member_state()
self.char = EveCharacter.objects.create(
character_id='1234',
character_name='test character',
corporation_id='2345',
corporation_name='test corp',
corporation_ticker='tickr',
alliance_id='3456',
alliance_name='alliance name',
)
self.alliance = EveAllianceInfo.objects.create(
alliance_id='3456',
alliance_name='alliance name',
alliance_ticker='TIKR',
executor_corp_id='2345',
)
self.corp = EveCorporationInfo.objects.create(
corporation_id='2345',
corporation_name='corp name',
corporation_ticker='TIKK',
member_count=10,
alliance=self.alliance,
)
state.member_alliances.add(self.alliance)
state.member_corporations.add(self.corp)
self.member = AuthUtils.create_member('test user')
self.member.profile.main_character = self.char
self.member.profile.save()
connect_signals()
@patch('.models.AutogroupsConfigManager.update_groups_for_user')
def test_check_groups_on_profile_update_state(self, update_groups_for_user):
# Trigger signal
self.member.profile.assign_state(state=AuthUtils.get_guest_state())
self.assertTrue(update_groups_for_user.called)
self.assertEqual(update_groups_for_user.call_count, 1)
args, kwargs = update_groups_for_user.call_args
self.assertEqual(args[0], self.member)
@patch('.models.AutogroupsConfigManager.update_groups_for_user')
def test_check_groups_on_profile_update_main_character(self, update_groups_for_user):
char = EveCharacter.objects.create(
character_id='1266',
character_name='test character2',
corporation_id='2345',
corporation_name='test corp',
corporation_ticker='tickr',
alliance_id='3456',
alliance_name='alliance name',
)
# Trigger signal
self.member.profile.main_character = char
self.member.profile.save()
self.assertTrue(update_groups_for_user.called)
self.assertEqual(update_groups_for_user.call_count, 1)
args, kwargs = update_groups_for_user.call_args
self.assertEqual(args[0], self.member)
member = User.objects.get(pk=self.member.pk)
self.assertEqual(member.profile.state, AuthUtils.get_member_state())
@patch('.models.AutogroupsConfigManager.update_groups_for_user')
def test_check_groups_on_character_update(self, update_groups_for_user):
"""
Test update_groups_for_user is called when main_character properties
are changed.
"""
# Trigger signal
self.member.profile.main_character.corporation_id = '2300'
self.member.profile.main_character.save()
self.assertTrue(update_groups_for_user.called)
self.assertEqual(update_groups_for_user.call_count, 1)
args, kwargs = update_groups_for_user.call_args
self.assertEqual(args[0], self.member)
member = User.objects.get(pk=self.member.pk)
self.assertEqual(member.profile.state, AuthUtils.get_member_state())
@patch('.models.AutogroupsConfig.delete_corp_managed_groups')
@patch('.models.AutogroupsConfig.delete_alliance_managed_groups')
def test_pre_save_config_deletes_alliance_groups(self, delete_alliance_managed_groups, delete_corp_managed_groups):
"""
Test that delete_alliance_managed_groups is called when the alliance_groups
setting is toggled to False
"""
obj = AutogroupsConfig.objects.create(alliance_groups=True)
obj.create_alliance_group(self.alliance)
# Trigger signal
obj.alliance_groups = False
obj.save()
self.assertTrue(delete_alliance_managed_groups.called)
self.assertFalse(delete_corp_managed_groups.called)
self.assertEqual(delete_alliance_managed_groups.call_count, 1)
@patch('.models.AutogroupsConfig.delete_alliance_managed_groups')
@patch('.models.AutogroupsConfig.delete_corp_managed_groups')
def test_pre_save_config_deletes_corp_groups(self, delete_corp_managed_groups, delete_alliance_managed_groups):
"""
Test that delete_corp_managed_groups is called when the corp_groups
setting is toggled to False
"""
obj = AutogroupsConfig.objects.create(corp_groups=True)
obj.create_corp_group(self.corp)
# Trigger signal
obj.corp_groups = False
obj.save()
self.assertTrue(delete_corp_managed_groups.called)
self.assertFalse(delete_alliance_managed_groups.called)
self.assertEqual(delete_corp_managed_groups.call_count, 1)
@patch('.models.AutogroupsConfig.delete_alliance_managed_groups')
@patch('.models.AutogroupsConfig.delete_corp_managed_groups')
def test_pre_save_config_does_nothing(self, delete_corp_managed_groups, delete_alliance_managed_groups):
"""
Test groups arent deleted if we arent setting the enabled params to False
"""
obj = AutogroupsConfig.objects.create(corp_groups=True)
obj.create_corp_group(self.corp)
# Trigger signal
obj.alliance_groups = True
obj.save()
self.assertFalse(delete_corp_managed_groups.called)
self.assertFalse(delete_alliance_managed_groups.called)
@patch('.models.AutogroupsConfig.delete_alliance_managed_groups')
@patch('.models.AutogroupsConfig.delete_corp_managed_groups')
def test_pre_delete_config(self, delete_corp_managed_groups, delete_alliance_managed_groups):
"""
Test groups are deleted if config is deleted
"""
obj = AutogroupsConfig.objects.create()
# Trigger signal
obj.delete()
self.assertTrue(delete_corp_managed_groups.called)
self.assertTrue(delete_alliance_managed_groups.called)
@patch('.models.AutogroupsConfig.update_group_membership_for_state')
def test_autogroups_states_changed_add(self, update_group_membership_for_state):
"""
Test update_group_membership_for_state is called when a state is added to
the AutogroupsConfig
"""
obj = AutogroupsConfig.objects.create(alliance_groups=True)
state = AuthUtils.get_member_state()
# Trigger
obj.states.add(state)
self.assertTrue(update_group_membership_for_state.called)
self.assertEqual(update_group_membership_for_state.call_count, 1)
args, kwargs = update_group_membership_for_state.call_args
self.assertEqual(args[0], state)
@patch('.models.AutogroupsConfig.update_group_membership_for_state')
def test_autogroups_states_changed_remove(self, update_group_membership_for_state):
"""
Test update_group_membership_for_state is called when a state is removed from
the AutogroupsConfig
"""
obj = AutogroupsConfig.objects.create(alliance_groups=True)
state = AuthUtils.get_member_state()
disconnect_signals()
obj.states.add(state)
connect_signals()
# Trigger
obj.states.remove(state)
self.assertTrue(update_group_membership_for_state.called)
self.assertEqual(update_group_membership_for_state.call_count, 1)
args, kwargs = update_group_membership_for_state.call_args
self.assertEqual(args[0], state)

View File

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

@@ -1,61 +0,0 @@
# this module generates profile URLs for dotlan
from urllib.parse import urljoin, quote
from . import (
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_REGION,
_ESI_CATEGORY_SOLARSYSTEM
)
_BASE_URL = 'http://evemaps.dotlan.net'
def _build_url(category: str, name: str) -> str:
"""return url to profile page for an eve entity"""
if category == _ESI_CATEGORY_ALLIANCE:
partial = 'alliance'
elif category == _ESI_CATEGORY_CORPORATION:
partial = 'corp'
elif category == _ESI_CATEGORY_REGION:
partial = 'map'
elif category == _ESI_CATEGORY_SOLARSYSTEM:
partial = 'system'
else:
raise NotImplementedError(
"Not implemented yet for category:" + category
)
url = urljoin(
_BASE_URL,
'{}/{}'.format(partial, quote(str(name).replace(" ", "_")))
)
return url
def alliance_url(name: str) -> str:
"""url for page about given alliance on dotlan"""
return _build_url(_ESI_CATEGORY_ALLIANCE, name)
def corporation_url(name: str) -> str:
"""url for page about given corporation on dotlan"""
return _build_url(_ESI_CATEGORY_CORPORATION, name)
def region_url(name: str) -> str:
"""url for page about given region on dotlan"""
return _build_url(_ESI_CATEGORY_REGION, name)
def solar_system_url(name: str) -> str:
"""url for page about given solar system on dotlan"""
return _build_url(_ESI_CATEGORY_SOLARSYSTEM, name)

View File

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

View File

@@ -1,51 +0,0 @@
# this module generates profile URLs for evewho
from urllib.parse import urljoin
from . import (
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_CHARACTER,
)
_BASE_URL = 'https://evewho.com'
def _build_url(category: str, eve_id: int) -> str:
"""return url to profile page for an eve entity"""
if category == _ESI_CATEGORY_ALLIANCE:
partial = 'alliance'
elif category == _ESI_CATEGORY_CORPORATION:
partial = 'corporation'
elif category == _ESI_CATEGORY_CHARACTER:
partial = 'character'
else:
raise NotImplementedError(
"Not implemented yet for category:" + category
)
url = urljoin(
_BASE_URL,
'{}/{}'.format(partial, int(eve_id))
)
return url
def alliance_url(eve_id: int) -> str:
"""url for page about given alliance on evewho"""
return _build_url(_ESI_CATEGORY_ALLIANCE, eve_id)
def character_url(eve_id: int) -> str:
"""url for page about given character on evewho"""
return _build_url(_ESI_CATEGORY_CHARACTER, eve_id)
def corporation_url(eve_id: int) -> str:
"""url for page about given corporation on evewho"""
return _build_url(_ESI_CATEGORY_CORPORATION, eve_id)

View File

@@ -1,204 +0,0 @@
from django.test import TestCase
from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from .. import dotlan, zkillboard, evewho, eveimageserver
from ...templatetags import evelinks
class TestEveWho(TestCase):
def test_alliance_url(self):
self.assertEqual(
evewho.alliance_url(12345678),
'https://evewho.com/alliance/12345678'
)
def test_corporation_url(self):
self.assertEqual(
evewho.corporation_url(12345678),
'https://evewho.com/corporation/12345678'
)
def test_character_url(self):
self.assertEqual(
evewho.character_url(12345678),
'https://evewho.com/character/12345678'
)
class TestDotlan(TestCase):
def test_alliance_url(self):
self.assertEqual(
dotlan.alliance_url('Wayne Enterprices'),
'http://evemaps.dotlan.net/alliance/Wayne_Enterprices'
)
def test_corporation_url(self):
self.assertEqual(
dotlan.corporation_url('Wayne Technology'),
'http://evemaps.dotlan.net/corp/Wayne_Technology'
)
self.assertEqual(
dotlan.corporation_url('Crédit Agricole'),
'http://evemaps.dotlan.net/corp/Cr%C3%A9dit_Agricole'
)
def test_region_url(self):
self.assertEqual(
dotlan.region_url('Black Rise'),
'http://evemaps.dotlan.net/map/Black_Rise'
)
def test_solar_system_url(self):
self.assertEqual(
dotlan.solar_system_url('Jita'),
'http://evemaps.dotlan.net/system/Jita'
)
class TestZkillboard(TestCase):
def test_alliance_url(self):
self.assertEqual(
zkillboard.alliance_url(12345678),
'https://zkillboard.com/alliance/12345678/'
)
def test_corporation_url(self):
self.assertEqual(
zkillboard.corporation_url(12345678),
'https://zkillboard.com/corporation/12345678/'
)
def test_character_url(self):
self.assertEqual(
zkillboard.character_url(12345678),
'https://zkillboard.com/character/12345678/'
)
def test_region_url(self):
self.assertEqual(
zkillboard.region_url(12345678),
'https://zkillboard.com/region/12345678/'
)
def test_solar_system_url(self):
self.assertEqual(
zkillboard.solar_system_url(12345678),
'https://zkillboard.com/system/12345678/'
)
class TestEveImageServer(TestCase):
"""unit test for eveimageserver"""
def test_sizes(self):
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=32),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=64),
'https://images.evetech.net/characters/42/portrait?size=64'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=128),
'https://images.evetech.net/characters/42/portrait?size=128'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=256),
'https://images.evetech.net/characters/42/portrait?size=256'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=512),
'https://images.evetech.net/characters/42/portrait?size=512'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, size=1024),
'https://images.evetech.net/characters/42/portrait?size=1024'
)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('corporation', 42, size=-5)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('corporation', 42, size=0)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('corporation', 42, size=31)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('corporation', 42, size=1025)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('corporation', 42, size=2048)
def test_variant(self):
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, variant='portrait'),
'https://images.evetech.net/characters/42/portrait?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('alliance', 42, variant='logo'),
'https://images.evetech.net/alliances/42/logo?size=32'
)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('character', 42, variant='logo')
def test_alliance(self):
self.assertEqual(
eveimageserver._eve_entity_image_url('alliance', 42),
'https://images.evetech.net/alliances/42/logo?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('corporation', 42),
'https://images.evetech.net/corporations/42/logo?size=32'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42),
'https://images.evetech.net/characters/42/portrait?size=32'
)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('station', 42)
def test_tenants(self):
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, tenant='tranquility'),
'https://images.evetech.net/characters/42/portrait?size=32&tenant=tranquility'
)
self.assertEqual(
eveimageserver._eve_entity_image_url('character', 42, tenant='singularity'),
'https://images.evetech.net/characters/42/portrait?size=32&tenant=singularity'
)
with self.assertRaises(ValueError):
eveimageserver._eve_entity_image_url('character', 42, tenant='xxx')
def test_alliance_logo_url(self):
expected = 'https://images.evetech.net/alliances/42/logo?size=128'
self.assertEqual(eveimageserver.alliance_logo_url(42, 128), expected)
def test_corporation_logo_url(self):
expected = 'https://images.evetech.net/corporations/42/logo?size=128'
self.assertEqual(eveimageserver.corporation_logo_url(42, 128), expected)
def test_character_portrait_url(self):
expected = 'https://images.evetech.net/characters/42/portrait?size=128'
self.assertEqual(
eveimageserver.character_portrait_url(42, 128), expected
)
def test_type_icon_url(self):
expected = 'https://images.evetech.net/types/42/icon?size=128'
self.assertEqual(eveimageserver.type_icon_url(42, 128), expected)
def test_type_render_url(self):
expected = 'https://images.evetech.net/types/42/render?size=128'
self.assertEqual(eveimageserver.type_render_url(42, 128), expected)

View File

@@ -1,359 +0,0 @@
from django.test import TestCase
from ...models import EveCharacter, EveCorporationInfo, EveAllianceInfo
from .. import eveimageserver, evewho, dotlan, zkillboard
from ...templatetags import evelinks
class TestTemplateTags(TestCase):
def setUp(self):
self.my_character = EveCharacter.objects.create(
character_id=1001,
character_name='Bruce Wayne',
corporation_id=2001,
corporation_name='Dummy Corporation 1',
corporation_ticker='DC1',
alliance_id=3001,
alliance_name='Dummy Alliance 1',
alliance_ticker='DA1',
)
self.my_character_2 = EveCharacter.objects.create(
character_id=1002,
character_name='Peter Parker',
corporation_id=2002,
corporation_name='Dummy Corporation 2',
corporation_ticker='DC2',
)
self.my_alliance = EveAllianceInfo.objects.create(
alliance_id=3001,
alliance_name='Dummy Alliance 1',
alliance_ticker='DA1',
executor_corp_id=2001
)
self.my_corporation = EveCorporationInfo(
corporation_id=2001,
corporation_name='Dummy Corporation 1',
corporation_ticker='DC1',
member_count=42,
alliance=self.my_alliance
)
self.my_region_id = 8001
self.my_region_name = 'Southpark'
self.my_solar_system_id = 9001
self.my_solar_system_name = 'Gotham'
def test_evewho_character_url(self):
self.assertEqual(
evelinks.evewho_character_url(self.my_character),
evewho.character_url(self.my_character.character_id),
)
self.assertEqual(
evelinks.evewho_character_url(None),
''
)
self.assertEqual(
evelinks.evewho_character_url(self.my_character.character_id),
evewho.character_url(self.my_character.character_id),
)
def test_evewho_corporation_url(self):
self.assertEqual(
evelinks.evewho_corporation_url(self.my_character),
evewho.corporation_url(self.my_character.corporation_id),
)
self.assertEqual(
evelinks.evewho_corporation_url(self.my_corporation),
evewho.corporation_url(self.my_corporation.corporation_id),
)
self.assertEqual(
evelinks.evewho_corporation_url(None),
''
)
self.assertEqual(
evelinks.evewho_corporation_url(self.my_character.corporation_id),
evewho.corporation_url(self.my_character.corporation_id),
)
def test_evewho_alliance_url(self):
self.assertEqual(
evelinks.evewho_alliance_url(self.my_character),
evewho.alliance_url(self.my_character.alliance_id),
)
self.assertEqual(
evelinks.evewho_alliance_url(self.my_character_2),
'',
)
self.assertEqual(
evelinks.evewho_alliance_url(self.my_alliance),
evewho.alliance_url(self.my_alliance.alliance_id),
)
self.assertEqual(
evelinks.evewho_alliance_url(None),
''
)
self.assertEqual(
evelinks.evewho_alliance_url(self.my_character.alliance_id),
evewho.alliance_url(self.my_character.alliance_id),
)
# dotlan
def test_dotlan_corporation_url(self):
self.assertEqual(
evelinks.dotlan_corporation_url(self.my_character),
dotlan.corporation_url(self.my_character.corporation_name),
)
self.assertEqual(
evelinks.dotlan_corporation_url(self.my_corporation),
dotlan.corporation_url(self.my_corporation.corporation_name),
)
self.assertEqual(
evelinks.dotlan_corporation_url(None),
''
)
self.assertEqual(
evelinks.dotlan_corporation_url(self.my_character.corporation_name),
dotlan.corporation_url(self.my_character.corporation_name),
)
def test_dotlan_alliance_url(self):
self.assertEqual(
evelinks.dotlan_alliance_url(self.my_character),
dotlan.alliance_url(self.my_character.alliance_name),
)
self.assertEqual(
evelinks.dotlan_alliance_url(self.my_character_2),
'',
)
self.assertEqual(
evelinks.dotlan_alliance_url(self.my_alliance),
dotlan.alliance_url(self.my_alliance.alliance_name),
)
self.assertEqual(
evelinks.dotlan_alliance_url(None),
''
)
self.assertEqual(
evelinks.dotlan_alliance_url(self.my_character.alliance_name),
dotlan.alliance_url(self.my_character.alliance_name),
)
def test_dotlan_region_url(self):
self.assertEqual(
evelinks.dotlan_region_url(self.my_region_name),
dotlan.region_url(self.my_region_name),
)
self.assertEqual(
evelinks.dotlan_region_url(None),
''
)
def test_dotlan_solar_system_url(self):
self.assertEqual(
evelinks.dotlan_solar_system_url(self.my_solar_system_name),
dotlan.solar_system_url(self.my_solar_system_name),
)
self.assertEqual(
evelinks.dotlan_solar_system_url(None),
''
)
# zkillboard
def test_zkillboard_character_url(self):
self.assertEqual(
evelinks.zkillboard_character_url(self.my_character),
zkillboard.character_url(self.my_character.character_id),
)
self.assertEqual(
evelinks.zkillboard_character_url(None),
''
)
self.assertEqual(
evelinks.zkillboard_character_url(self.my_character.character_id),
zkillboard.character_url(self.my_character.character_id),
)
def test_zkillboard_corporation_url(self):
self.assertEqual(
evelinks.zkillboard_corporation_url(self.my_character),
zkillboard.corporation_url(self.my_character.corporation_id),
)
self.assertEqual(
evelinks.zkillboard_corporation_url(self.my_corporation),
zkillboard.corporation_url(self.my_corporation.corporation_id),
)
self.assertEqual(
evelinks.zkillboard_corporation_url(None),
''
)
self.assertEqual(
evelinks.zkillboard_corporation_url(self.my_character.corporation_id),
zkillboard.corporation_url(self.my_character.corporation_id),
)
def test_zkillboard_alliance_url(self):
self.assertEqual(
evelinks.zkillboard_alliance_url(self.my_character),
zkillboard.alliance_url(self.my_character.alliance_id),
)
self.assertEqual(
evelinks.zkillboard_alliance_url(self.my_character_2),
'',
)
self.assertEqual(
evelinks.zkillboard_alliance_url(self.my_alliance),
zkillboard.alliance_url(self.my_alliance.alliance_id),
)
self.assertEqual(
evelinks.zkillboard_alliance_url(None),
''
)
self.assertEqual(
evelinks.zkillboard_alliance_url(self.my_character.alliance_id),
zkillboard.alliance_url(self.my_character.alliance_id),
)
def test_zkillboard_region_url(self):
self.assertEqual(
evelinks.zkillboard_region_url(self.my_region_id),
zkillboard.region_url(self.my_region_id),
)
self.assertEqual(
evelinks.zkillboard_region_url(None),
''
)
def test_zkillboard_solar_system_url(self):
self.assertEqual(
evelinks.zkillboard_solar_system_url(self.my_solar_system_id),
zkillboard.solar_system_url(self.my_solar_system_id),
)
self.assertEqual(
evelinks.zkillboard_solar_system_url(None),
''
)
# image URLs
def test_character_portrait_url(self):
self.assertEqual(
evelinks.character_portrait_url(123),
EveCharacter.generic_portrait_url(123)
),
self.assertEqual(
evelinks.character_portrait_url(123, 128),
EveCharacter.generic_portrait_url(123, 128)
)
self.assertEqual(
evelinks.character_portrait_url(123, 99),
''
)
self.assertEqual(
evelinks.character_portrait_url(self.my_character),
self.my_character.portrait_url()
)
self.assertEqual(
evelinks.character_portrait_url(None),
''
)
def test_corporation_logo_url(self):
self.assertEqual(
evelinks.corporation_logo_url(123),
EveCorporationInfo.generic_logo_url(123)
),
self.assertEqual(
evelinks.corporation_logo_url(123, 128),
EveCorporationInfo.generic_logo_url(123, 128)
)
self.assertEqual(
evelinks.corporation_logo_url(123, 99),
''
)
self.assertEqual(
evelinks.corporation_logo_url(self.my_corporation),
self.my_corporation.logo_url()
)
self.assertEqual(
evelinks.corporation_logo_url(self.my_character),
self.my_character.corporation_logo_url()
)
self.assertEqual(
evelinks.corporation_logo_url(None),
''
)
def test_alliance_logo_url(self):
self.assertEqual(
evelinks.alliance_logo_url(123),
EveAllianceInfo.generic_logo_url(123)
),
self.assertEqual(
evelinks.alliance_logo_url(123, 128),
EveAllianceInfo.generic_logo_url(123, 128)
)
self.assertEqual(
evelinks.alliance_logo_url(123, 99),
''
)
self.assertEqual(
evelinks.alliance_logo_url(self.my_alliance),
self.my_alliance.logo_url()
)
self.assertEqual(
evelinks.alliance_logo_url(self.my_character),
self.my_character.alliance_logo_url()
)
self.assertEqual(
evelinks.alliance_logo_url(None),
''
)
self.assertEqual(
evelinks.alliance_logo_url(self.my_character_2),
''
)
def test_type_icon_url(self):
expected = eveimageserver.type_icon_url(123)
self.assertEqual(evelinks.type_icon_url(123), expected)
expected = eveimageserver.type_icon_url(123, 128)
self.assertEqual(evelinks.type_icon_url(123, 128), expected)
expected = ''
self.assertEqual(evelinks.type_icon_url(123, 99), expected)
expected = ''
self.assertEqual(evelinks.type_icon_url(None), expected)
def test_type_render_url(self):
expected = eveimageserver.type_render_url(123)
self.assertEqual(evelinks.type_render_url(123), expected)
expected = eveimageserver.type_render_url(123, 128)
self.assertEqual(evelinks.type_render_url(123, 128), expected)
expected = ''
self.assertEqual(evelinks.type_render_url(123, 99), expected)
expected = ''
self.assertEqual(evelinks.type_render_url(None), expected)

View File

@@ -1,68 +0,0 @@
# this module generates profile URLs for zKillboard
from urllib.parse import urljoin
from . import (
_ESI_CATEGORY_ALLIANCE,
_ESI_CATEGORY_CORPORATION,
_ESI_CATEGORY_CHARACTER,
_ESI_CATEGORY_REGION,
_ESI_CATEGORY_SOLARSYSTEM
)
_BASE_URL = 'https://zkillboard.com'
def _build_url(category: str, eve_id: int) -> str:
"""return url to profile page for an eve entity"""
if category == _ESI_CATEGORY_ALLIANCE:
partial = 'alliance'
elif category == _ESI_CATEGORY_CORPORATION:
partial = 'corporation'
elif category == _ESI_CATEGORY_CHARACTER:
partial = 'character'
elif category == _ESI_CATEGORY_REGION:
partial = 'region'
elif category == _ESI_CATEGORY_SOLARSYSTEM:
partial = 'system'
else:
raise NotImplementedError(
"Not implemented yet for category:" + category
)
url = urljoin(
_BASE_URL,
'{}/{}/'.format(partial, int(eve_id))
)
return url
def alliance_url(eve_id: int) -> str:
"""url for page about given alliance on zKillboard"""
return _build_url(_ESI_CATEGORY_ALLIANCE, eve_id)
def character_url(eve_id: int) -> str:
"""url for page about given character on zKillboard"""
return _build_url(_ESI_CATEGORY_CHARACTER, eve_id)
def corporation_url(eve_id: int) -> str:
"""url for page about given corporation on zKillboard"""
return _build_url(_ESI_CATEGORY_CORPORATION, eve_id)
def region_url(eve_id: int) -> str:
"""url for page about given region on zKillboard"""
return _build_url(_ESI_CATEGORY_REGION, eve_id)
def solar_system_url(eve_id: int) -> str:
return _build_url(_ESI_CATEGORY_SOLARSYSTEM, eve_id)

View File

@@ -1,94 +0,0 @@
import logging
from django.db import models
from . import providers
logger = logging.getLogger(__name__)
class EveCharacterProviderManager:
def get_character(self, character_id) -> providers.Character:
return providers.provider.get_character(character_id)
class EveCharacterManager(models.Manager):
provider = EveCharacterProviderManager()
def create_character(self, character_id):
return self.create_character_obj(self.provider.get_character(character_id))
def create_character_obj(self, character: providers.Character):
return self.create(
character_id=character.id,
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,
alliance_ticker=getattr(character.alliance, 'ticker', None),
)
def update_character(self, character_id):
return self.get(character_id=character_id).update_character()
def get_character_by_id(self, char_id):
if self.filter(character_id=char_id).exists():
return self.get(character_id=char_id)
return None
class EveAllianceProviderManager:
def get_alliance(self, alliance_id) -> providers.Alliance:
return providers.provider.get_alliance(alliance_id)
class EveAllianceManager(models.Manager):
provider = EveAllianceProviderManager()
def create_alliance(self, alliance_id):
obj = self.create_alliance_obj(self.provider.get_alliance(alliance_id))
obj.populate_alliance()
return obj
def create_alliance_obj(self, alliance: providers.Alliance):
return self.create(
alliance_id=alliance.id,
alliance_name=alliance.name,
alliance_ticker=alliance.ticker,
executor_corp_id=alliance.executor_corp_id,
)
def update_alliance(self, alliance_id):
return self.get(alliance_id=alliance_id).update_alliance()
class EveCorporationProviderManager:
def get_corporation(self, corp_id) -> providers.Corporation:
return providers.provider.get_corp(corp_id)
class EveCorporationManager(models.Manager):
provider = EveCorporationProviderManager()
def create_corporation(self, corp_id):
return self.create_corporation_obj(self.provider.get_corporation(corp_id))
def create_corporation_obj(self, corp: providers.Corporation):
from .models import EveAllianceInfo
try:
alliance = EveAllianceInfo.objects.get(alliance_id=corp.alliance_id)
except EveAllianceInfo.DoesNotExist:
alliance = None
return self.create(
corporation_id=corp.id,
corporation_name=corp.name,
corporation_ticker=corp.ticker,
member_count=corp.members,
alliance=alliance,
)
def update_corporation(self, corp_id):
return self\
.get(corporation_id=corp_id)\
.update_corporation(self.provider.get_corporation(corp_id))

View File

@@ -1,38 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-03-22 23:09
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('eveonline', '0007_unique_id_name'),
]
operations = [
migrations.RemoveField(
model_name='eveapikeypair',
name='user',
),
migrations.RemoveField(
model_name='eveallianceinfo',
name='is_blue',
),
migrations.RemoveField(
model_name='evecharacter',
name='api_id',
),
migrations.RemoveField(
model_name='evecharacter',
name='user',
),
migrations.RemoveField(
model_name='evecorporationinfo',
name='is_blue',
),
migrations.DeleteModel(
name='EveApiKeyPair',
),
]

View File

@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2017-09-28 02:16
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('eveonline', '0008_remove_apikeys'),
]
operations = [
migrations.AlterField(
model_name='evecorporationinfo',
name='alliance',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='eveonline.EveAllianceInfo'),
),
]

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