Merge branch 'master' of https://github.com/Adarnof/allianceauth into custom_user

# Conflicts:
#	alliance_auth/settings.py.example
#	eveonline/views.py

Fix some tests.
This commit is contained in:
Adarnof 2017-05-27 17:25:15 -04:00
commit 971ce294ad
39 changed files with 997 additions and 417 deletions

View File

@ -12,8 +12,6 @@ https://docs.djangoproject.com/en/1.10/ref/settings/
import os import os
import djcelery
from django.contrib import messages from django.contrib import messages
from celery.schedules import crontab from celery.schedules import crontab
@ -25,7 +23,7 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.humanize', 'django.contrib.humanize',
'djcelery', 'django_celery_beat',
'bootstrapform', 'bootstrapform',
'authentication', 'authentication',
'services', 'services',
@ -68,11 +66,9 @@ INSTALLED_APPS = [
# Scroll down for Auth Configuration # Scroll down for Auth Configuration
##################################################### #####################################################
djcelery.setup_loader()
# Celery configuration # Celery configuration
BROKER_URL = 'redis://localhost:6379/0' BROKER_URL = 'redis://localhost:6379/0'
CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler" CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
CELERYBEAT_SCHEDULE = { CELERYBEAT_SCHEDULE = {
'esi_cleanup_callbackredirect': { 'esi_cleanup_callbackredirect': {
'task': 'esi.tasks.cleanup_callbackredirect', 'task': 'esi.tasks.cleanup_callbackredirect',
@ -82,6 +78,14 @@ CELERYBEAT_SCHEDULE = {
'task': 'esi.tasks.cleanup_token', 'task': 'esi.tasks.cleanup_token',
'schedule': crontab(day_of_month='*/1'), 'schedule': crontab(day_of_month='*/1'),
}, },
'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"),
},
} }
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)

View File

@ -4,14 +4,10 @@ Alliance Auth Test Suite Django settings.
import os import os
import djcelery
from django.contrib import messages from django.contrib import messages
import alliance_auth import alliance_auth
djcelery.setup_loader()
# Use nose to run all tests # Use nose to run all tests
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner' TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
@ -40,7 +36,7 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.humanize', 'django.contrib.humanize',
'djcelery', 'django_celery_beat',
'bootstrapform', 'bootstrapform',
'authentication', 'authentication',
'services', 'services',

View File

@ -50,11 +50,14 @@ urlpatterns = [
name='auth_srp_fleet_mark_completed'), name='auth_srp_fleet_mark_completed'),
url(r'^srp_fleet_mark_uncompleted/(\w+)', srp.views.srp_fleet_mark_uncompleted, url(r'^srp_fleet_mark_uncompleted/(\w+)', srp.views.srp_fleet_mark_uncompleted,
name='auth_srp_fleet_mark_uncompleted'), name='auth_srp_fleet_mark_uncompleted'),
url(r'^srp_request_remove/(\w+)', srp.views.srp_request_remove, url(r'^srp_request_remove/', srp.views.srp_request_remove,
name="auth_srp_request_remove"), name="auth_srp_request_remove"),
url(r'srp_request_approve/(\w+)', srp.views.srp_request_approve, url(r'srp_request_approve/', srp.views.srp_request_approve,
name='auth_srp_request_approve'), name='auth_srp_request_approve'),
url(r'srp_request_reject/(\w+)', srp.views.srp_request_reject, name='auth_srp_request_reject'), 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 # Notifications
url(r'^remove_notifications/(\w+)/$', notifications.views.remove_notification, name='auth_remove_notification'), url(r'^remove_notifications/(\w+)/$', notifications.views.remove_notification, name='auth_remove_notification'),
@ -148,8 +151,6 @@ urlpatterns += i18n_patterns(
url(_(r'^srp_fleet_add_view/$'), srp.views.srp_fleet_add_view, name='auth_srp_fleet_add_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_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'), url(_(r'^srp_request/(\w+)'), srp.views.srp_request_view, name='auth_srp_request_view'),
url(_(r'srp_request_amount_update/(\w+)'), srp.views.srp_request_update_amount_view,
name="auth_srp_request_update_amount_view"),
# Tools # Tools
url(_(r'^tool/fleet_formatter_tool/$'), services.views.fleet_formatter_view, url(_(r'^tool/fleet_formatter_tool/$'), services.views.fleet_formatter_view,
@ -162,6 +163,9 @@ urlpatterns += i18n_patterns(
# FleetActivityTracking (FAT) # FleetActivityTracking (FAT)
url(r'^fat/$', fleetactivitytracking.views.fatlink_view, name='auth_fatlink_view'), 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/$', 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, url(r'^fat/statistics/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$', fleetactivitytracking.views.fatlink_statistics_view,
name='auth_fatlink_view_statistics_month'), name='auth_fatlink_view_statistics_month'),
url(r'^fat/user/statistics/$', fleetactivitytracking.views.fatlink_personal_statistics_view, url(r'^fat/user/statistics/$', fleetactivitytracking.views.fatlink_personal_statistics_view,

View File

@ -1,15 +1,14 @@
from corputils.models import CorpStats from corputils.models import CorpStats
from celery.task import task, periodic_task from alliance_auth.celeryapp import app
from celery.task.schedules import crontab
@task @app.task
def update_corpstats(pk): def update_corpstats(pk):
cs = CorpStats.objects.get(pk=pk) cs = CorpStats.objects.get(pk=pk)
cs.update() cs.update()
@periodic_task(run_every=crontab(minute=0, hour="*/6")) @app.task
def update_all_corpstats(): def update_all_corpstats():
for cs in CorpStats.objects.all(): for cs in CorpStats.objects.all():
update_corpstats.delay(cs.pk) update_corpstats.delay(cs.pk)

View File

@ -5,6 +5,8 @@ AllianceAuth gets served using a Web Server Gateway Interface (WSGI) script. Thi
In the interest of ~~laziness~~ time-efficiency, scroll down for example configs. Use these, changing the ServerName to your domain name. In the interest of ~~laziness~~ time-efficiency, scroll down for example configs. Use these, changing the ServerName to your domain name.
If you're using a small VPS to host services with very limited memory resources, consider using NGINX with [Gunicorn](gunicorn.md). Even if you would like to use Apache, Gunicorn may give you lower memory usage over mod_wsgi.
### Required Parameters for AllianceAuth Core ### Required Parameters for AllianceAuth Core
The AllianceAuth core requires the following parameters to be set: The AllianceAuth core requires the following parameters to be set:
@ -52,6 +54,31 @@ You can supply your own SSL certificates if you so desire. The alternative is ru
## Sample Config Files ## Sample Config Files
### Minimally functional config
```
<VirtualHost *:80>
ServerName example.com
ServerAdmin webmaster@localhost
DocumentRoot /var/www
WSGIDaemonProcess allianceauth python-path=/home/allianceserver/allianceauth
WSGIProcessGroup allianceauth
WSGIScriptAlias / /home/allianceserver/allianceauth/alliance_auth/wsgi.py
Alias /static/ /home/allianceserver/allianceauth/static/
<Directory /home/allianceserver/allianceauth/>
Require all granted
</Directory>
<Directory /var/www/>
Require all granted
</Directory>
</VirtualHost>
```
### Own SSL Cert ### Own SSL Cert
- Apache 2.4 or newer: - Apache 2.4 or newer:
- [000-default.conf](http://pastebin.com/3LLzyNmV) - [000-default.conf](http://pastebin.com/3LLzyNmV)

View File

@ -0,0 +1,121 @@
# Gunicorn
[Gunicorn](http://gunicorn.org) is a Python WSGI HTTP Server for UNIX. The Gunicorn server is light on server resources, and fairly speedy.
If you find Apache's `mod_wsgi` to be a headache or want to use NGINX (or some other webserver), then Gunicorn could be for you. There are a number of other WSGI server options out there and this documentation should be enough for you to piece together how to get them working with your environment.
Check out the full [Gunicorn docs](http://docs.gunicorn.org/en/latest/index.html).
## Setting up Gunicorn
```eval_rst
.. note::
If you're using a virtual environment (and I would encourage you to do so when hosting Alliance Auth), activate it now. `source /path/to/venv/bin/activate`.
```
Install Gunicorn using pip, `pip install gunicorn`.
In your `allianceauth` base directory, try running `gunicorn --bind 0.0.0.0:8000 alliance_auth.wsgi`. You should be able to browse to http://yourserver:8000 and see your Alliance Auth installation running. Images and styling will be missing, but dont worry, your web server will provide them.
Once you validate its running, you can kill the process with Ctrl+C and continue.
## Running Gunicorn with Supervisor
You should use [Supervisor](supervisor.md) to keep all of Alliance Auth components running (instead of using screen). You don't _have to_ but we will be using it to start and run Gunicorn so you might as well.
### Sample Supervisor config
You'll want to edit `/etc/supervisor/conf.d/aauth_gunicorn.conf` (or whatever you want to call the config file)
```
[program:aauth-gunicorn]
user = www-data
directory=/home/allianceserver/allianceauth/
command=gunicorn alliance_auth.wsgi --workers=3 --timeout 120
autostart=true
autorestart=true
stopsignal=INT
```
- `[program:aauth-gunicorn]` - Change aauth-gunicorn to whatever you wish to call your process in Supervisor.
- `user = www-data` - Change to whatever user you wish Gunicorn to run as. You could even set this as allianceserver if you wished. I'll leave the question security of that up to you.
- `directory=/home/allianceserver/allianceauth/` - Needs to be the path to your Alliance Auth install.
- `command=gunicorn alliance_auth.wsgi --workers=3 --timeout 120` - Running Gunicorn and the options to launch with. This is where you have some decisions to make, we'll continue below.
#### Gunicorn Arguments
See the [Commonly Used Arguments](http://docs.gunicorn.org/en/latest/run.html#commonly-used-arguments) or [Full list of settings](http://docs.gunicorn.org/en/stable/settings.html) for more information.
##### Where to bind Gunicorn to?
What address are you going to use to reference it? By default, without a bind parameter, Gunicorn will bind to `127.0.0.1:8000`. This might be fine for your application. If it clashes with another application running on that port you will need to change it. I would suggest using UNIX sockets too, if you can.
For UNIX sockets add `--bind=unix:/run/allianceauth.sock` (or to a path you wish to use). Remember that your web server will need to be able to access this socket file.
For a TCP address add `--bind=127.0.0.1:8001` (or to the address/port you wish to use, but I would strongly advise against binding it to an external address).
Whatever you decide to use, remember it because we'll need it when configuring your webserver.
##### Number of workers
By default Gunicorn will spawn only one worker. The number you set this to will depend on your own server environment, how many visitors you have etc. Gunicorn suggests between 2-4 workers per core. Really you could probably get away with 2-4 in total for most installs.
Change it by adding `--workers=2` to the command.
##### Running with a virtual environment
If you're running with a virtual environment, you'll need to add the path to the `command=` config line.
e.g. `command=/path/to/venv/bin/gunicorn alliance_auth.wsgi`
### Starting via Supervisor
Once you have your configuration all sorted, you will need to reload your supervisor config `sudo service supervisor reload` and then you can start the Gunicorn server via `sudo supervisorctl start aauth-gunicorn` (or whatever you renamed it to). You should see something like the following `aauth-gunicorn: started`. If you get some other message, you'll need to consult the Supervisor log files, usually found in `/var/log/supervisor/`.
## Configuring your webserver
### NGINX
To your server config add:
```
location / {
proxy_pass http://127.0.0.1:8000;
proxy_read_timeout 90;
proxy_redirect http://127.0.0.1:8000/ http://$host/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
```
Set `proxy_pass` and `proxy_redirect` to the address you set under `--bind=`. Set the second part of `proxy_redirect` to the URL you're hosting services on. Tell NGINX to reload your config, job done. Enjoy your lower memory usage and better performance!
If PHP is stopping you moving to NGINX, check out php-fpm as a way to run your PHP applications.
### Apache
If you were using mod_wsgi before, make a backup of your old config first and then strip out all of the mod_wsgi config from your Apache VirtualHost first config.
Your config will need something along these lines:
```
ProxyPreserveHost On
<Location />
SSLRequireSSL
ProxyPass http://127.0.0.1:8000/
ProxyPassReverse http://127.0.0.1:8000/
RequestHeader set X-FORWARDED-PROTOCOL ssl
RequestHeader set X-FORWARDED-SSL on
</Location>
```
Set `ProxyPass` and `ProxyPassReverse` addresses to your `--bind=` address set earlier.
You will need to enable some Apache mods. `sudo a2enmod http_proxy` should take care of the dependencies.
Restart Apache and you should be done.
### Other web servers
Any web server capable of proxy passing should be able to sit in front of Gunicorn. Consult their documentation armed with your `--bind=` address and you should be able to find how to do it relatively easy.
## Restarting Gunicorn
In the past when you made changes you restarted the entire Apache server. This is no longer required. When you update or make configuration changes that ask you to restart Apache, instead you can just restart Gunicorn:
`sudo supervisorctl restart aauth-gunicorn`, or the service name you chose for it.

View File

@ -7,7 +7,9 @@
ubuntu ubuntu
centos centos
settings settings
nginx
apache apache
gunicorn
cloudflare cloudflare
supervisor supervisor
quickstart quickstart

View File

@ -0,0 +1,93 @@
# NGINX
## Overivew
Nginx (engine x) is a HTTP server known for its high performance, stability, simple configuration, and low resource consumption. Unlike traditional servers (i.e. Apache), Nginx doesn't rely on threads to serve requests, rather using an asynchronous event driven approach which permits predictable resource usage and performance under load.
If you're trying to cram Alliance Auth into a very small VPS of say, 1-2GB or less, then Nginx will be considerably friendlier to your resources compared to Apache.
You can read more about NGINX on the [NGINX wiki](https://www.nginx.com/resources/wiki/).
## Coming from Apache
If you're converting from Apache, here are some things to consider.
Nginx is lightweight for a reason. It doesn't try to do everything internally and instead concentrates on just being a good HTTP server. This means that, unlike Apache, it wont automatically run PHP scripts via mod_php and doesn't have an internal WSGI server like mod_wsgi. That doesn't mean that it can't, just that it relies on external processes to run these instead. This might be good or bad depending on your outlook. It's good because it allows you to segment your applications, restarting Alliance Auth wont impact your PHP applications. On the other hand it means more config and more management of services. For some people it will be worth it, for others losing the centralised nature of Apache may not be worth it.
```eval_rst
+-----------+----------------------------------------+
| Apache | Nginx Replacement |
+===========+========================================+
| mod_php | php5-fpm or php7-fpm (PHP FastCGI) |
+-----------+----------------------------------------+
| mod_wsgi | Gunicorn or other external WSGI server |
+-----------+----------------------------------------+
```
Your .htaccess files wont work. Nginx has a separate way of managing access to folders via the server config. Everything you can do with htaccess files you can do with Nginx config. [Read more on the Nginx wiki](https://www.nginx.com/resources/wiki/start/topics/examples/likeapache-htaccess/)
## Setting up Nginx
Install Nginx via your preferred package manager or other method. If you need help just search, there are plenty of guides on installing Nginx out there.
You will need to have [Gunicorn](gunicorn.md) or some other WSGI server setup for hosting Alliance Auth.
Create a config file in `/etc/nginx/sites-available` call it `alliance-auth.conf` or whatever your preferred name is and copy the basic config in. Make whatever changes you feel are necessary.
Create a symbolic link to enable the site `sudo ln -s /etc/nginx/sites-available/alliance-auth.conf /etc/nginx/sites-enabled/` and then reload Nginx for the config to take effect, `sudo service nginx reload` for Ubuntu.
### Basic config
```
server {
listen 80;
server_name example.com;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
alias /home/allianceserver/allianceauth/static/;
autoindex off;
}
# Gunicorn config goes below
location / {
include proxy_params;
proxy_pass http://127.0.0.1:8000;
}
}
```
#### Adding TLS/SSL
With [Let's Encrypt](https://letsencrypt.org/) offering free SSL certificates, there's no good reason to not run HTTPS anymore.
Your config will need a few additions once you've got your certificate.
```
listen 443 ssl http2; # Replace listen 80; with this
ssl_certificate /path/to/your/cert.crt;
ssl_certificate_key /path/to/your/cert.key;
ssl on;
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS;
ssl_prefer_server_ciphers on;
```
If you want to redirect all your non-SSL visitors to your secure site, below your main configs `server` block, add the following:
```
server {
listen 80;
server_name example.com;
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
return 301 https://$host$request_uri;
}
```
If you have trouble with the `ssl_ciphers` listed here or some other part of the SSL config, try getting the values from [Mozilla's SSL Config Generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/).

View File

@ -10,5 +10,5 @@ The big goal of AllianceAuth is the automation of group membership, so well n
To start the background processes to sync groups and check api keys, issue these commands: To start the background processes to sync groups and check api keys, issue these commands:
screen -dm bash -c 'python manage.py celeryd' screen -dm bash -c 'celery -A alliance_auth worker'
screen -dm bash -c 'python manage.py celerybeat' screen -dm bash -c 'celery -A alliance_auth beat'

View File

@ -15,12 +15,12 @@ Ubuntu:
CentOS: CentOS:
sudo yum install supervisor sudo yum install supervisor
sudo systemctl enable supervisor.service sudo systemctl enable supervisord.service
sudo systemctl start supervisor.service sudo systemctl start supervisord.service
## Configuration ## Configuration
Auth provides example config files for the celery workers (celeryd), the periodic task scheduler (celerybeat), and the mumble authenticator. All of these are available in `thirdparty/Supervisor`. Auth provides example config files for the celery workers, the periodic task scheduler (celery beat), and the mumble authenticator. All of these are available in `thirdparty/Supervisor`.
For most users, all you have to do is copy the config files to `/etc/supervisor/conf.d` then restart the service. Copy `auth-celerybeat.conf` and `auth-celeryd.conf` for the celery workers, and `auth-mumble.conf` for the mumble authenticator. For all three just use a wildcard: For most users, all you have to do is copy the config files to `/etc/supervisor/conf.d` then restart the service. Copy `auth-celerybeat.conf` and `auth-celeryd.conf` for the celery workers, and `auth-mumble.conf` for the mumble authenticator. For all three just use a wildcard:
@ -41,15 +41,15 @@ To ensure the processes are working, check their status:
sudo supervisorctl status sudo supervisorctl status
Processes will be `STARTING`, `RUNNING`, or `ERROR`. If an error has occurred, check their log files: Processes will be `STARTING`, `RUNNING`, or `ERROR`. If an error has occurred, check their log files:
- celeryd: `log/worker.log` - celery workers: `log/worker.log`
- celerybeat: `log/beat.log` - celery beat: `log/beat.log`
- authenticator: `log/authenticator.log` - authenticator: `log/authenticator.log`
## Customizing Config Files ## Customizing Config Files
The only real customization needed is if running in a virtual environment. The python path will have to be changed in order to start in the venv. The only real customization needed is if running in a virtual environment. The python path will have to be changed in order to start in the venv.
Edit the config files and find the line saying `command`. Replace `python` with `/path/to/venv/python`. This can be relative to the `directory` specified in the config file. Edit the config files and find the line saying `command`. Replace `python` with `/path/to/venv/bin/python`. For Celery replace `celery` with `/path/to/venv/bin/celery`. This can be relative to the `directory` specified in the config file.
Note that for config changes to be loaded, the supervisor service must be restarted. Note that for config changes to be loaded, the supervisor service must be restarted.
@ -60,4 +60,4 @@ Most often this is caused by a permissions issue on the allianceauth directory (
### Workers are using old settings ### Workers are using old settings
Every time the codebase is updated or settings file changed, workers will have to be restarted. Easiest way is to restart the supervisor service (see configuration above for commands) Every time the codebase is updated or settings file changed, workers will have to be restarted. Easiest way is to restart the supervisor service (see configuration above for commands)

View File

@ -84,7 +84,7 @@ To enable advanced permissions, on your client go to the `Tools` menu, `Applicat
### TS group models not populating on admin site ### TS group models not populating on admin site
The method which populates these runs every 30 minutes. To populate manually, start a celery shell: The method which populates these runs every 30 minutes. To populate manually, start a celery shell:
python manage.py celery shell celery -A alliance_auth shell
And execute the update: And execute the update:

View File

@ -19,7 +19,7 @@ Either you need to `sudo` that command, or it's a missing dependency. Check [the
### I'm getting an error 500 trying to connect to the website on a new install ### I'm getting an error 500 trying to connect to the website on a new install
Read the apache error log: `sudo nano /var/log/apache2/error.log` Read the apache error log: `sudo less /var/log/apache2/error.log`. Press Shift+G to go to the end of the file.
If it talks about failing to import something, google its name and install it. If it talks about failing to import something, google its name and install it.
@ -36,13 +36,9 @@ Make sure the background processes are running: `ps aux | grep celery` should re
If that doesn't do it, try clearing the worker queue. First kill all celery processes as described above, then do the following: If that doesn't do it, try clearing the worker queue. First kill all celery processes as described above, then do the following:
redis-cli FLUSHALL redis-cli FLUSHALL
python manage.py celeryd --purge celery -A alliance_auth worker --purge
Press control+C once. Press Control+C once.
python manage.py celeryd --discard
Press control+C once.
Now start celery again with [these background process commands.](../installation/auth/quickstart.md) Now start celery again with [these background process commands.](../installation/auth/quickstart.md)

View File

@ -9,26 +9,28 @@ from eveonline.models import EveAllianceInfo
from eveonline.providers import ObjectNotFound from eveonline.providers import ObjectNotFound
import logging import logging
from alliance_auth.celeryapp import app
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@task @app.task
def update_corp(corp_id): def update_corp(corp_id):
EveManager.update_corporation(corp_id) EveManager.update_corporation(corp_id)
@task @app.task
def update_alliance(alliance_id): def update_alliance(alliance_id):
EveManager.update_alliance(alliance_id) EveManager.update_alliance(alliance_id)
EveManager.populate_alliance(alliance_id) EveManager.populate_alliance(alliance_id)
@periodic_task(run_every=crontab(minute=0, hour="*/2")) @app.task
def run_corp_update(): def run_corp_update():
# generate member corps # generate member corps
for corp_id in settings.STR_CORP_IDS + settings.STR_BLUE_CORP_IDS: for corp_id in settings.STR_CORP_IDS + settings.STR_BLUE_CORP_IDS:
try: try:
if EveCorporationInfo.objects.filter(corporation_id=corp_id).exists(): if EveCorporationInfo.objects.filter(corporation_id=corp_id).exists():
update_corp(corp_id) update_corp.apply(arge=[corp_id])
else: else:
EveManager.create_corporation(corp_id) EveManager.create_corporation(corp_id)
except ObjectNotFound: except ObjectNotFound:
@ -39,7 +41,7 @@ def run_corp_update():
try: try:
if EveAllianceInfo.objects.filter(alliance_id=alliance_id).exists(): if EveAllianceInfo.objects.filter(alliance_id=alliance_id).exists():
logger.debug("Updating existing owner alliance model with id %s" % alliance_id) logger.debug("Updating existing owner alliance model with id %s" % alliance_id)
update_alliance(alliance_id) update_alliance.apply(args=[alliance_id])
else: else:
EveManager.create_alliance(alliance_id) EveManager.create_alliance(alliance_id)
EveManager.populate_alliance(alliance_id) EveManager.populate_alliance(alliance_id)

View File

@ -0,0 +1,48 @@
{% extends "public/base.html" %}
{% load bootstrap %}
{% load staticfiles %}
{% load i18n %}
{% block title %}Alliance Auth{% endblock %}
{% block page_title %}{% trans "Fatlink Corp Statistics" %}{% endblock page_title %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
<div class="text-right">
<a href="{% url 'auth_fatlink_view_statistics_corp_month' corpid previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% trans "Previous month" %}</a>
{% if next_month %}
<a href="{% url 'auth_fatlink_view_statistics_corp_month' corpid next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% trans "Next month" %}</a>
{% endif %}
</div>
</h1>
{% if fatStats %}
<table class="table table-responsive">
<tr>
<th class="col-md-1"></th>
<th class="col-md-2 text-center">{% trans "Main Character" %}</th>
<th class="col-md-2 text-center">{% trans "Characters" %}</th>
<th class="col-md-2 text-center">{% trans "Fats" %}</th>
<th class="col-md-2 text-center">{% trans "Average fats" %}
<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="Fats ÷ Characters"></i>
</th>
</tr>
{% for memberStat in fatStats %}
<tr>
<td>
<img src="https://image.eveonline.com/Character/{{ memberStat.mainchid }}_32.jpg" class="ra-avatar img-responsive">
</td>
<td class="text-center">{{ memberStat.mainchar.character_name }}</td>
<td class="text-center">{{ memberStat.n_chars }}</td>
<td class="text-center">{{ memberStat.n_fats }}</td>
<td class="text-center">{{ memberStat.avg_fat }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
</div>
{% endblock content %}
{% block extra_script %}
$(document).ready(function(){
$("[rel=tooltip]").tooltip();
{% endblock extra_script %}

View File

@ -24,14 +24,16 @@
<th class="col-md-5 text-center">{% trans "Corp" %}</th> <th class="col-md-5 text-center">{% trans "Corp" %}</th>
<th class="col-md-2 text-center">{% trans "Members" %}</th> <th class="col-md-2 text-center">{% trans "Members" %}</th>
<th class="col-md-2 text-center">{% trans "Fats" %}</th> <th class="col-md-2 text-center">{% trans "Fats" %}</th>
<th class="col-md-2 text-center">{% trans "Average fats" %}</th> <th class="col-md-2 text-center">{% trans "Average fats" %}
<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="Fats ÷ Characters"></i>
</th>
</tr> </tr>
{% for corpStat in fatStats %} {% for corpStat in fatStats %}
<tr> <tr>
<td> <td>
<img src="https://image.eveonline.com/Corporation/{{ corpStat.corp.corporation_id }}_32.png" class="ra-avatar img-responsive"> <img src="https://image.eveonline.com/Corporation/{{ corpStat.corp.corporation_id }}_32.png" class="ra-avatar img-responsive">
</td> </td>
<td class="text-center">[{{ corpStat.corp.corporation_ticker }}]</td> <td class="text-center"><a href="{% url 'auth_fatlink_view_statistics_corp' corpStat.corp.corporation_id %}">[{{ corpStat.corp.corporation_ticker }}]</td>
<td class="text-center">{{ corpStat.corp.corporation_name }}</td> <td class="text-center">{{ corpStat.corp.corporation_name }}</td>
<td class="text-center">{{ corpStat.corp.member_count }}</td> <td class="text-center">{{ corpStat.corp.member_count }}</td>
<td class="text-center">{{ corpStat.n_fats }}</td> <td class="text-center">{{ corpStat.n_fats }}</td>
@ -42,3 +44,7 @@
{% endif %} {% endif %}
</div> </div>
{% endblock content %} {% endblock content %}
{% block extra_script %}
$(document).ready(function(){
$("[rel=tooltip]").tooltip();
{% endblock extra_script %}

View File

@ -8,9 +8,11 @@ from django.utils.translation import ugettext_lazy as _
from django.utils import timezone from django.utils import timezone
from django.contrib import messages from django.contrib import messages
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.db.models import Q
from eveonline.models import EveCharacter from eveonline.models import EveCharacter
from eveonline.models import EveCorporationInfo from eveonline.models import EveCorporationInfo
from eveonline.managers import EveManager from eveonline.managers import EveManager
from authentication.models import AuthServicesInfo
from fleetactivitytracking.forms import FatlinkForm from fleetactivitytracking.forms import FatlinkForm
from fleetactivitytracking.models import Fatlink, Fat from fleetactivitytracking.models import Fatlink, Fat
@ -53,6 +55,24 @@ class CorpStat(object):
return "%.2f" % (float(self.n_fats) / float(self.corp.member_count)) return "%.2f" % (float(self.n_fats) / float(self.corp.member_count))
class MemberStat(object):
def __init__(self, member, start_of_month, start_of_next_month, mainchid=None):
if mainchid:
self.mainchid = mainchid
else:
self.mainchid = request.user.profile.main_character.character_id if request.user.profile.main_character else None
self.mainchar = EveCharacter.objects.get(character_id=self.mainchid)
nchars = 0
for alliance_id in settings.STR_ALLIANCE_IDS:
nchars += EveCharacter.objects.filter(user_id=member['user_id']).filter(alliance_id=alliance_id).count()
self.n_chars = nchars
self.n_fats = Fat.objects.filter(user_id=member['user_id']).filter(
fatlink__fatdatetime__gte=start_of_month).filter(fatlink__fatdatetime__lte=start_of_next_month).count()
def avg_fat(self):
return "%.2f" % (float(self.n_fats) / float(self.n_chars))
def first_day_of_next_month(year, month): def first_day_of_next_month(year, month):
if month == 12: if month == 12:
return datetime.datetime(year + 1, 1, 1) return datetime.datetime(year + 1, 1, 1)
@ -86,6 +106,41 @@ def fatlink_view(request):
return render(request, 'fleetactivitytracking/fatlinkview.html', context=context) return render(request, 'fleetactivitytracking/fatlinkview.html', context=context)
@login_required
@permission_required('auth.fleetactivitytracking_statistics')
def fatlink_statistics_corp_view(request, corpid, year=None, month=None):
if year is None:
year = datetime.date.today().year
if month is None:
month = datetime.date.today().month
year = int(year)
month = int(month)
start_of_month = datetime.datetime(year, month, 1)
start_of_next_month = first_day_of_next_month(year, month)
start_of_previous_month = first_day_of_previous_month(year, month)
fat_stats = {}
corp_members = EveCharacter.objects.filter(corporation_id=corpid).values('user_id').distinct()
for member in corp_members:
try:
fat_stats[member['user_id']] = MemberStat(member, start_of_month, start_of_next_month)
except ObjectDoesNotExist:
continue
# collect and sort stats
stat_list = [fat_stats[x] for x in fat_stats]
stat_list.sort(key=lambda stat: stat.mainchar.character_name)
stat_list.sort(key=lambda stat: (stat.n_fats, stat.n_fats / stat.n_chars), reverse=True)
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year,
'previous_month': start_of_previous_month, 'corpid': corpid}
if datetime.datetime.now() > start_of_next_month:
context.update({'next_month': start_of_next_month})
return render(request, 'fleetactivitytracking/fatlinkstatisticscorpview.html', context=context)
@login_required @login_required
@permission_required('auth.fleetactivitytracking_statistics') @permission_required('auth.fleetactivitytracking_statistics')
def fatlink_statistics_view(request, year=datetime.date.today().year, month=datetime.date.today().month): def fatlink_statistics_view(request, year=datetime.date.today().year, month=datetime.date.today().month):
@ -104,8 +159,8 @@ def fatlink_statistics_view(request, year=datetime.date.today().year, month=date
fats_in_span = Fat.objects.filter(fatlink__fatdatetime__gte=start_of_month).filter( fats_in_span = Fat.objects.filter(fatlink__fatdatetime__gte=start_of_month).filter(
fatlink__fatdatetime__lt=start_of_next_month).exclude(character__corporation_id__in=fat_stats) fatlink__fatdatetime__lt=start_of_next_month).exclude(character__corporation_id__in=fat_stats)
for fat in fats_in_span: for fat in fats_in_span.exclude(character__corporation_id__in=fat_stats):
if fat.character.corporation_id not in fat_stats: if EveCorporationInfo.objects.filter(corporation_id=fat.character.corporation_id).exists():
fat_stats[fat.character.corporation_id] = CorpStat(fat.character.corporation_id, start_of_month, fat_stats[fat.character.corporation_id] = CorpStat(fat.character.corporation_id, start_of_month,
start_of_next_month) start_of_next_month)
@ -114,12 +169,10 @@ def fatlink_statistics_view(request, year=datetime.date.today().year, month=date
stat_list.sort(key=lambda stat: stat.corp.corporation_name) stat_list.sort(key=lambda stat: stat.corp.corporation_name)
stat_list.sort(key=lambda stat: (stat.n_fats, stat.n_fats / stat.corp.member_count), reverse=True) stat_list.sort(key=lambda stat: (stat.n_fats, stat.n_fats / stat.corp.member_count), reverse=True)
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year,
'previous_month': start_of_previous_month}
if datetime.datetime.now() > start_of_next_month: if datetime.datetime.now() > start_of_next_month:
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year, context.update({'next_month': start_of_next_month})
'previous_month': start_of_previous_month, 'next_month': start_of_next_month}
else:
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year,
'previous_month': start_of_previous_month}
return render(request, 'fleetactivitytracking/fatlinkstatisticsview.html', context=context) return render(request, 'fleetactivitytracking/fatlinkstatisticsview.html', context=context)

View File

@ -6,9 +6,7 @@ from django.utils.translation import ugettext_lazy as _
class OpForm(forms.Form): class OpForm(forms.Form):
doctrine = forms.CharField(max_length=254, required=True, label=_('Doctrine')) doctrine = forms.CharField(max_length=254, required=True, label=_('Doctrine'))
system = forms.CharField(max_length=254, required=True, label=_("System")) system = forms.CharField(max_length=254, required=True, label=_("System"))
location = forms.CharField(max_length=254, required=True, label=_("Location"))
start = forms.DateTimeField(required=True, label=_("Start Time")) start = forms.DateTimeField(required=True, label=_("Start Time"))
duration = forms.CharField(max_length=254, required=True, label=_("Duration")) duration = forms.CharField(max_length=254, required=True, label=_("Duration"))
operation_name = forms.CharField(max_length=254, required=True, label=_("Operation Name")) operation_name = forms.CharField(max_length=254, required=True, label=_("Operation Name"))
fc = forms.CharField(max_length=254, required=True, label=_("Fleet Commander")) fc = forms.CharField(max_length=254, required=True, label=_("Fleet Commander"))
details = forms.CharField(max_length=254, required=False, label=_("Extra Details"))

View File

@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11 on 2017-04-13 04:42
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('optimer', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='optimer',
name='details',
),
migrations.RemoveField(
model_name='optimer',
name='location',
),
]

View File

@ -13,12 +13,10 @@ class OpTimer(models.Model):
doctrine = models.CharField(max_length=254, default="") doctrine = models.CharField(max_length=254, default="")
system = models.CharField(max_length=254, default="") system = models.CharField(max_length=254, default="")
location = models.CharField(max_length=254, default="")
start = models.DateTimeField(default=datetime.now) start = models.DateTimeField(default=datetime.now)
duration = models.CharField(max_length=25, default="") duration = models.CharField(max_length=25, default="")
operation_name = models.CharField(max_length=254, default="") operation_name = models.CharField(max_length=254, default="")
fc = models.CharField(max_length=254, default="") fc = models.CharField(max_length=254, default="")
details = models.CharField(max_length=254, default="")
post_time = models.DateTimeField(default=timezone.now) post_time = models.DateTimeField(default=timezone.now)
eve_character = models.ForeignKey(EveCharacter) eve_character = models.ForeignKey(EveCharacter)

View File

@ -18,7 +18,11 @@ logger = logging.getLogger(__name__)
@permission_required('auth.optimer_view') @permission_required('auth.optimer_view')
def optimer_view(request): def optimer_view(request):
logger.debug("optimer_view called by user %s" % request.user) logger.debug("optimer_view called by user %s" % request.user)
render_items = {'optimer': OpTimer.objects.all(), } render_items = {'optimer': optimer.objects.all(),
'future_timers': optimer.objects.all().filter(
start__gte=timezone.now()),
'past_timers': optimer.objects.all().filter(
start__lt=timezone.now()).order_by('-start')}
return render(request, 'registered/operationmanagement.html', context=render_items) return render(request, 'registered/operationmanagement.html', context=render_items)
@ -39,12 +43,10 @@ def add_optimer_view(request):
op = OpTimer() op = OpTimer()
op.doctrine = form.cleaned_data['doctrine'] op.doctrine = form.cleaned_data['doctrine']
op.system = form.cleaned_data['system'] op.system = form.cleaned_data['system']
op.location = form.cleaned_data['location']
op.start = form.cleaned_data['start'] op.start = form.cleaned_data['start']
op.duration = form.cleaned_data['duration'] op.duration = form.cleaned_data['duration']
op.operation_name = form.cleaned_data['operation_name'] op.operation_name = form.cleaned_data['operation_name']
op.fc = form.cleaned_data['fc'] op.fc = form.cleaned_data['fc']
op.details = form.cleaned_data['details']
op.create_time = post_time op.create_time = post_time
op.eve_character = character op.eve_character = character
op.save() op.save()
@ -83,12 +85,10 @@ def edit_optimer(request, optimer_id):
character = request.user.profile.main_character character = request.user.profile.main_character
op.doctrine = form.cleaned_data['doctrine'] op.doctrine = form.cleaned_data['doctrine']
op.system = form.cleaned_data['system'] op.system = form.cleaned_data['system']
op.location = form.cleaned_data['location']
op.start = form.cleaned_data['start'] op.start = form.cleaned_data['start']
op.duration = form.cleaned_data['duration'] op.duration = form.cleaned_data['duration']
op.operation_name = form.cleaned_data['operation_name'] op.operation_name = form.cleaned_data['operation_name']
op.fc = form.cleaned_data['fc'] op.fc = form.cleaned_data['fc']
op.details = form.cleaned_data['details']
op.eve_character = character op.eve_character = character
logger.info("User %s updating optimer id %s " % (request.user, optimer_id)) logger.info("User %s updating optimer id %s " % (request.user, optimer_id))
op.save() op.save()
@ -98,12 +98,10 @@ def edit_optimer(request, optimer_id):
data = { data = {
'doctrine': op.doctrine, 'doctrine': op.doctrine,
'system': op.system, 'system': op.system,
'location': op.location,
'start': op.start, 'start': op.start,
'duration': op.duration, 'duration': op.duration,
'operation_name': op.operation_name, 'operation_name': op.operation_name,
'fc': op.fc, 'fc': op.fc,
'details': op.details,
} }
form = OpForm(initial=data) form = OpForm(initial=data)
return render(request, 'registered/optimerupdate.html', context={'form': form}) return render(request, 'registered/optimerupdate.html', context={'form': form})

View File

@ -9,6 +9,7 @@ python-slugify>=1.2
requests-oauthlib requests-oauthlib
sleekxmpp sleekxmpp
redis redis
celery>=4.0.2
# Django Stuff # # Django Stuff #
django>=1.10,<2.0 django>=1.10,<2.0
@ -17,6 +18,7 @@ django-bootstrap-pagination
django-redis>=4.4 django-redis>=4.4
django-registration django-registration
django-sortedm2m django-sortedm2m
django-celery-beat
git+https://github.com/adarnof/django-navhelper git+https://github.com/adarnof/django-navhelper
# awating release for fix to celery/django-celery#447 # awating release for fix to celery/django-celery#447

View File

@ -376,6 +376,5 @@ class DiscourseManager:
logger.debug("Disabling user %s Discourse access." % user) logger.debug("Disabling user %s Discourse access." % user)
d_user = DiscourseManager.__get_user_by_external(user.pk) d_user = DiscourseManager.__get_user_by_external(user.pk)
DiscourseManager.__logout(d_user['user']['id']) DiscourseManager.__logout(d_user['user']['id'])
DiscourseManager.__suspend_user(d_user['user']['username'])
logger.info("Disabled user %s Discourse access." % user) logger.info("Disabled user %s Discourse access." % user)
return True return True

View File

@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
class Phpbb3Manager: class Phpbb3Manager:
SQL_ADD_USER = r"INSERT INTO phpbb_users (username, username_clean, " \ SQL_ADD_USER = r"INSERT INTO phpbb_users (username, username_clean, " \
r"user_password, user_email, group_id, user_regdate, user_permissions, " \ r"user_password, user_email, group_id, user_regdate, user_permissions, " \
r"user_sig) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)" r"user_sig, user_lang) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, 'en')"
SQL_DEL_USER = r"DELETE FROM phpbb_users where username = %s" SQL_DEL_USER = r"DELETE FROM phpbb_users where username = %s"

View File

@ -2,9 +2,11 @@ from __future__ import unicode_literals
import random import random
import string import string
import requests import requests
import hashlib
from eveonline.managers import EveManager from eveonline.managers import EveManager
from django.conf import settings from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core.cache import cache
from six import iteritems from six import iteritems
@ -20,7 +22,7 @@ class SeatManager:
RESPONSE_OK = 'ok' RESPONSE_OK = 'ok'
@staticmethod @staticmethod
def __santatize_username(username): def __sanitize_username(username):
sanatized = username.replace(" ", "_") sanatized = username.replace(" ", "_")
return sanatized.lower() return sanatized.lower()
@ -33,7 +35,7 @@ class SeatManager:
return cls.RESPONSE_OK in response return cls.RESPONSE_OK in response
@staticmethod @staticmethod
def exec_request(endpoint, func, **kwargs): def exec_request(endpoint, func, raise_for_status=False, **kwargs):
""" Send an https api request """ """ Send an https api request """
try: try:
endpoint = '{0}/api/v1/{1}'.format(settings.SEAT_URL, endpoint) endpoint = '{0}/api/v1/{1}'.format(settings.SEAT_URL, endpoint)
@ -43,22 +45,24 @@ class SeatManager:
ret = getattr(requests, func)(endpoint, headers=headers, data=kwargs) ret = getattr(requests, func)(endpoint, headers=headers, data=kwargs)
ret.raise_for_status() ret.raise_for_status()
return ret.json() return ret.json()
except: except requests.HTTPError as e:
if raise_for_status:
raise e
logger.exception("Error encountered while performing API request to SeAT with url {}".format(endpoint)) logger.exception("Error encountered while performing API request to SeAT with url {}".format(endpoint))
return {} return {}
@classmethod @classmethod
def add_user(cls, username, email): def add_user(cls, username, email):
""" Add user to service """ """ Add user to service """
sanatized = str(SeatManager.__santatize_username(username)) sanitized = str(cls.__sanitize_username(username))
logger.debug("Adding user to SeAT with username %s" % sanatized) logger.debug("Adding user to SeAT with username %s" % sanitized)
password = SeatManager.__generate_random_pass() password = cls.__generate_random_pass()
ret = SeatManager.exec_request('user', 'post', username=sanatized, email=str(email), password=password) ret = cls.exec_request('user', 'post', username=sanitized, email=str(email), password=password)
logger.debug(ret) logger.debug(ret)
if cls._response_ok(ret): if cls._response_ok(ret):
logger.info("Added SeAT user with username %s" % sanatized) logger.info("Added SeAT user with username %s" % sanitized)
return sanatized, password return sanitized, password
logger.info("Failed to add SeAT user with username %s" % sanatized) logger.info("Failed to add SeAT user with username %s" % sanitized)
return None return None
@classmethod @classmethod
@ -93,7 +97,7 @@ class SeatManager:
@classmethod @classmethod
def enable_user(cls, username): def enable_user(cls, username):
""" Enable user """ """ Enable user """
ret = SeatManager.exec_request('user/{}'.format(username), 'put', active=1) ret = cls.exec_request('user/{}'.format(username), 'put', active=1)
logger.debug(ret) logger.debug(ret)
if cls._response_ok(ret): if cls._response_ok(ret):
logger.info("Enabled SeAT user with username %s" % username) logger.info("Enabled SeAT user with username %s" % username)
@ -104,13 +108,12 @@ class SeatManager:
@classmethod @classmethod
def update_user(cls, username, email, password): def update_user(cls, username, email, password):
""" Edit user info """ """ Edit user info """
logger.debug("Updating SeAT username %s with email %s and password hash starting with %s" % (username, email, logger.debug("Updating SeAT username %s with email %s and password" % (username, email))
password[0:5])) ret = cls.exec_request('user/{}'.format(username), 'put', email=email)
ret = SeatManager.exec_request('user/{}'.format(username), 'put', email=email)
logger.debug(ret) logger.debug(ret)
if not cls._response_ok(ret): if not cls._response_ok(ret):
logger.warn("Failed to update email for username {}".format(username)) logger.warn("Failed to update email for username {}".format(username))
ret = SeatManager.exec_request('user/{}'.format(username), 'put', password=password) ret = cls.exec_request('user/{}'.format(username), 'put', password=password)
logger.debug(ret) logger.debug(ret)
if not cls._response_ok(ret): if not cls._response_ok(ret):
logger.warn("Failed to update password for username {}".format(username)) logger.warn("Failed to update password for username {}".format(username))
@ -118,25 +121,25 @@ class SeatManager:
logger.info("Updated SeAT user with username %s" % username) logger.info("Updated SeAT user with username %s" % username)
return username return username
@staticmethod @classmethod
def update_user_password(username, email, plain_password=None): def update_user_password(cls, username, email, plain_password=None):
logger.debug("Settings new SeAT password for user %s" % username) logger.debug("Settings new SeAT password for user %s" % username)
if not plain_password: if not plain_password:
plain_password = SeatManager.__generate_random_pass() plain_password = cls.__generate_random_pass()
if SeatManager.update_user(username, email, plain_password): if cls.update_user(username, email, plain_password):
return plain_password return plain_password
@staticmethod @classmethod
def check_user_status(username): def check_user_status(cls, username):
sanatized = str(SeatManager.__santatize_username(username)) sanitized = str(cls.__sanitize_username(username))
logger.debug("Checking SeAT status for user %s" % sanatized) logger.debug("Checking SeAT status for user %s" % sanitized)
ret = SeatManager.exec_request('user/{}'.format(sanatized), 'get') ret = cls.exec_request('user/{}'.format(sanitized), 'get')
logger.debug(ret) logger.debug(ret)
return ret return ret
@staticmethod @classmethod
def get_all_seat_eveapis(): def get_all_seat_eveapis(cls):
seat_all_keys = SeatManager.exec_request('key', 'get') seat_all_keys = cls.exec_request('key', 'get')
seat_keys = {} seat_keys = {}
for key in seat_all_keys: for key in seat_all_keys:
try: try:
@ -145,117 +148,132 @@ class SeatManager:
seat_keys[key["key_id"]] = None seat_keys[key["key_id"]] = None
return seat_keys return seat_keys
@classmethod
def synchronize_eveapis(cls, user=None):
# Fetch all of the API keys stored in SeAT already
seat_all_keys = cls.get_all_seat_eveapis()
@staticmethod
def synchronize_eveapis(user=None):
seat_all_keys = SeatManager.get_all_seat_eveapis()
userinfo = None
# retrieve only user-specific api keys if user is specified # retrieve only user-specific api keys if user is specified
if user: if user:
keypars = EveManager.get_api_key_pairs(user) keypairs = EveManager.get_api_key_pairs(user)
try:
userinfo = SeatManager.check_user_status(user.seat.username)
except ObjectDoesNotExist:
pass
else: else:
# retrieve all api keys instead # retrieve all api keys instead
keypars = EveManager.get_all_api_key_pairs() keypairs = EveManager.get_all_api_key_pairs()
if keypars:
for keypar in keypars:
if keypar.api_id not in seat_all_keys.keys():
#Add new keys
logger.debug("Adding Api Key with ID %s" % keypar.api_id)
ret = SeatManager.exec_request('key', 'post', key_id=keypar.api_id, v_code=keypar.api_key)
logger.debug(ret)
else:
# remove it from the list so it doesn't get deleted in the last step
seat_all_keys.pop(keypar.api_id)
if not userinfo: # TODO: should the following be done only for new keys?
# Check the key's user status
logger.debug("Retrieving user name from Auth's SeAT users database")
try:
if keypar.user.seat.username:
logger.debug("Retrieving user %s info from SeAT users database" % keypar.user.seat.username)
userinfo = SeatManager.check_user_status(keypar.user.seat.username)
except ObjectDoesNotExist:
pass
if userinfo:
try:
# If the user has activated seat, assign the key to him.
logger.debug("Transferring Api Key with ID %s to user %s with ID %s " % (
keypar.api_id,
keypar.user.seat.username,
userinfo['id']))
ret = SeatManager.exec_request('key/transfer/{}/{}'.format(keypar.api_id, userinfo['id']),
'get')
logger.debug(ret)
except ObjectDoesNotExist:
logger.debug("User does not have SeAT activated, could not assign key to user")
if bool(seat_all_keys) and not user and hasattr(settings, 'SEAT_PURGE_DELETED') and settings.SEAT_PURGE_DELETED: for keypair in keypairs:
# Transfer the key if it isn't already in SeAT
if keypair.api_id not in seat_all_keys.keys():
# Add new keys
logger.debug("Adding Api Key with ID %s" % keypair.api_id)
try:
ret = cls.exec_request('key', 'post',
key_id=keypair.api_id,
v_code=keypair.api_key,
raise_for_status=True)
logger.debug(ret)
except requests.HTTPError as e:
if e.response.status_code == 400:
logger.debug("API key already exists")
else:
logger.exception("API key sync failed")
continue # Skip the rest of the key processing
else:
# remove it from the list so it doesn't get deleted in the last step
seat_all_keys.pop(keypair.api_id)
# Attach API key to the users SeAT account, if possible
try:
userinfo = cache.get_or_set('seat_user_status_' + cls.username_hash(keypair.user.seat.username),
lambda: cls.check_user_status(keypair.user.seat.username),
300) # Cache for 5 minutes
if not bool(userinfo):
# No SeAT account, skip
logger.debug("Could not find users SeAT id, cannot assign key to them")
continue
# If the user has activated seat, assign the key to them
logger.debug("Transferring Api Key with ID %s to user %s with ID %s " % (
keypair.api_id,
keypair.user.seat.username,
userinfo['id']))
ret = cls.exec_request('key/transfer/{}/{}'.format(keypair.api_id, userinfo['id']),
'get')
logger.debug(ret)
except ObjectDoesNotExist:
logger.debug("User does not have SeAT activated, could not assign key to user")
if bool(seat_all_keys) and not user and getattr(settings, 'SEAT_PURGE_DELETED', False):
# remove from SeAT keys that were removed from Auth # remove from SeAT keys that were removed from Auth
for key, key_user in iteritems(seat_all_keys): for key, key_user in iteritems(seat_all_keys):
# Remove the key only if it is an account or character key # Remove the key only if it is an account or character key
ret = SeatManager.exec_request('key/{}'.format(key), 'get') ret = cls.exec_request('key/{}'.format(key), 'get')
logger.debug(ret) logger.debug(ret)
try: try:
if (ret['info']['type'] == "Account") or (ret['info']['type'] == "Character"): if (ret['info']['type'] == "Account") or (ret['info']['type'] == "Character"):
logger.debug("Removing api key %s from SeAT database" % key) logger.debug("Removing api key %s from SeAT database" % key)
ret = SeatManager.exec_request('key/{}'.format(key), 'delete') ret = cls.exec_request('key/{}'.format(key), 'delete')
logger.debug(ret) logger.debug(ret)
except KeyError: except KeyError:
pass pass
@staticmethod @classmethod
def get_all_roles(): def get_all_roles(cls):
groups = {} groups = {}
ret = SeatManager.exec_request('role', 'get') ret = cls.exec_request('role', 'get')
logger.debug(ret) logger.debug(ret)
for group in ret: for group in ret:
groups[group["title"]] = group["id"] groups[group["title"]] = group["id"]
logger.debug("Retrieved role list from SeAT: %s" % str(groups)) logger.debug("Retrieved role list from SeAT: %s" % str(groups))
return groups return groups
@staticmethod @classmethod
def add_role(role): def add_role(cls, role):
ret = SeatManager.exec_request('role/new', 'post', name=role) ret = cls.exec_request('role/new', 'post', name=role)
logger.debug(ret) logger.debug(ret)
logger.info("Added Seat group %s" % role) logger.info("Added Seat group %s" % role)
role_info = SeatManager.exec_request('role/detail/{}'.format(role), 'get') role_info = cls.exec_request('role/detail/{}'.format(role), 'get')
logger.debug(role_info) logger.debug(role_info)
return role_info["id"] return role_info["id"]
@staticmethod @classmethod
def add_role_to_user(user_id, role_id): def add_role_to_user(cls, user_id, role_id):
ret = SeatManager.exec_request('role/grant-user-role/{}/{}'.format(user_id, role_id), 'get') ret = cls.exec_request('role/grant-user-role/{}/{}'.format(user_id, role_id), 'get')
logger.info("Added role %s to user %s" % (role_id, user_id)) logger.info("Added role %s to user %s" % (role_id, user_id))
return ret return ret
@staticmethod @classmethod
def revoke_role_from_user(user_id, role_id): def revoke_role_from_user(cls, user_id, role_id):
ret = SeatManager.exec_request('role/revoke-user-role/{}/{}'.format(user_id, role_id), 'get') ret = cls.exec_request('role/revoke-user-role/{}/{}'.format(user_id, role_id), 'get')
logger.info("Revoked role %s from user %s" % (role_id, user_id)) logger.info("Revoked role %s from user %s" % (role_id, user_id))
return ret return ret
@staticmethod @classmethod
def update_roles(seat_user, roles): def update_roles(cls, seat_user, roles):
logger.debug("Updating SeAT user %s with roles %s" % (seat_user, roles)) logger.debug("Updating SeAT user %s with roles %s" % (seat_user, roles))
user_info = SeatManager.check_user_status(seat_user) user_info = cls.check_user_status(seat_user)
user_roles = {} user_roles = {}
if type(user_info["roles"]) is list: if type(user_info["roles"]) is list:
for role in user_info["roles"]: for role in user_info["roles"]:
user_roles[role["title"]] = role["id"] user_roles[role["title"]] = role["id"]
logger.debug("Got user %s SeAT roles %s" % (seat_user, user_roles)) logger.debug("Got user %s SeAT roles %s" % (seat_user, user_roles))
seat_roles = SeatManager.get_all_roles() seat_roles = cls.get_all_roles()
addroles = set(roles) - set(user_roles.keys()) addroles = set(roles) - set(user_roles.keys())
remroles = set(user_roles.keys()) - set(roles) remroles = set(user_roles.keys()) - set(roles)
logger.info("Updating SeAT roles for user %s - adding %s, removing %s" % (seat_user, addroles, remroles)) logger.info("Updating SeAT roles for user %s - adding %s, removing %s" % (seat_user, addroles, remroles))
for r in addroles: for r in addroles:
if r not in seat_roles: if r not in seat_roles:
seat_roles[r] = SeatManager.add_role(r) seat_roles[r] = cls.add_role(r)
logger.debug("Adding role %s to SeAT user %s" % (r, seat_user)) logger.debug("Adding role %s to SeAT user %s" % (r, seat_user))
SeatManager.add_role_to_user(user_info["id"], seat_roles[r]) cls.add_role_to_user(user_info["id"], seat_roles[r])
for r in remroles: for r in remroles:
logger.debug("Removing role %s from user %s" % (r, seat_user)) logger.debug("Removing role %s from user %s" % (r, seat_user))
SeatManager.revoke_role_from_user(user_info["id"], seat_roles[r]) cls.revoke_role_from_user(user_info["id"], seat_roles[r])
@staticmethod
def username_hash(username):
m = hashlib.sha1()
m.update(username)
return m.hexdigest()

View File

@ -1,6 +1,8 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required, permission_required from django.contrib.auth.decorators import login_required, permission_required
from django.contrib import messages
from django.utils.translation import ugettext_lazy as _
from .manager import SeatManager from .manager import SeatManager
@ -15,6 +17,7 @@ import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
ACCESS_PERM = 'seat.access_seat' ACCESS_PERM = 'seat.access_seat'
SERVICE_NAME = {'service': 'SeAT'}
@login_required @login_required
@ -38,6 +41,8 @@ def activate_seat(request):
logger.debug("Updated SeatUser for user %s with SeAT credentials. Adding eve-apis..." % request.user) logger.debug("Updated SeatUser for user %s with SeAT credentials. Adding eve-apis..." % request.user)
SeatTasks.update_roles.delay(request.user.pk) SeatTasks.update_roles.delay(request.user.pk)
logger.info("Successfully activated SeAT for user %s" % request.user) logger.info("Successfully activated SeAT for user %s" % request.user)
messages.add_message(request, messages.SUCCESS, _('Successfully activated your %(service)s account.') %
SERVICE_NAME)
SeatManager.synchronize_eveapis(request.user) SeatManager.synchronize_eveapis(request.user)
credentials = { credentials = {
'username': request.user.seat.username, 'username': request.user.seat.username,
@ -45,6 +50,9 @@ def activate_seat(request):
} }
return render(request, 'registered/service_credentials.html', return render(request, 'registered/service_credentials.html',
context={'credentials': credentials, 'service': 'SeAT'}) context={'credentials': credentials, 'service': 'SeAT'})
messages.add_message(request, messages.ERROR,
_('Failed to activate your %(service)s account, please contact your administrator.') %
SERVICE_NAME)
logger.error("Unsuccessful attempt to activate seat for user %s" % request.user) logger.error("Unsuccessful attempt to activate seat for user %s" % request.user)
return redirect("auth_services") return redirect("auth_services")
@ -55,10 +63,15 @@ def deactivate_seat(request):
logger.debug("deactivate_seat called by user %s" % request.user) logger.debug("deactivate_seat called by user %s" % request.user)
# false we failed # false we failed
if SeatTasks.delete_user(request.user): if SeatTasks.delete_user(request.user):
messages.add_message(request, messages.SUCCESS,
_('Successfully deactivated your %(service)s account.') % SERVICE_NAME)
logger.info("Successfully deactivated SeAT for user %s" % request.user) logger.info("Successfully deactivated SeAT for user %s" % request.user)
return redirect("auth_services") return redirect("auth_services")
else: else:
logging.error("User does not have a SeAT account") logging.error("User does not have a SeAT account")
messages.add_message(request, messages.ERROR,
_('Failed to deactivate your %(service)s account, please contact your administrator.') %
SERVICE_NAME)
logger.error("Unsuccessful attempt to activate SeAT for user %s" % request.user) logger.error("Unsuccessful attempt to activate SeAT for user %s" % request.user)
return redirect("auth_services") return redirect("auth_services")
@ -75,10 +88,15 @@ def reset_seat_password(request):
'username': request.user.seat.username, 'username': request.user.seat.username,
'password': result, 'password': result,
} }
messages.add_message(request, messages.SUCCESS,
_('Successfully reset your %(service)s password.') % {'service': 'SeAT'})
logger.info("Succesfully reset SeAT password for user %s" % request.user) logger.info("Succesfully reset SeAT password for user %s" % request.user)
return render(request, 'registered/service_credentials.html', return render(request, 'registered/service_credentials.html',
context={'credentials': credentials, 'service': 'SeAT'}) context={'credentials': credentials, 'service': 'SeAT'})
logger.error("Unsuccessful attempt to reset SeAT password for user %s" % request.user) logger.error("Unsuccessful attempt to reset SeAT password for user %s" % request.user)
messages.add_message(request, messages.ERROR,
_('Failed to reset your %(service)s password, please contact your administrator.') %
{'service': 'SeAT'})
return redirect("auth_services") return redirect("auth_services")
@ -97,11 +115,17 @@ def set_seat_password(request):
request.user.email, request.user.email,
plain_password=password) plain_password=password)
if result: if result:
messages.add_message(request, messages.SUCCESS,
_('Successfully set your %(service)s password.') % SERVICE_NAME)
logger.info("Succesfully reset SeAT password for user %s" % request.user) logger.info("Succesfully reset SeAT password for user %s" % request.user)
return redirect("auth_services") return redirect("auth_services")
else: else:
messages.add_message(request, messages.ERROR,
_('Failed to set your %(service)s password, please contact your administrator.') %
SERVICE_NAME)
logger.error("Failed to install custom SeAT password for user %s" % request.user) logger.error("Failed to install custom SeAT password for user %s" % request.user)
else: else:
messages.add_message(request, messages.ERROR, _('Invalid password.'))
logger.error("Invalid SeAT password provided") logger.error("Invalid SeAT password provided")
else: else:
logger.debug("Request is not type POST - providing empty form.") logger.debug("Request is not type POST - providing empty form.")

View File

@ -25,9 +25,5 @@ class SrpFleetUserRequestForm(forms.Form):
return data return data
class SrpFleetUpdateCostForm(forms.Form):
srp_total_amount = forms.IntegerField(required=True, label=_("Total SRP Amount"))
class SrpFleetMainUpdateForm(forms.Form): class SrpFleetMainUpdateForm(forms.Form):
fleet_aar_link = forms.CharField(required=True, label=_("After Action Report Link")) fleet_aar_link = forms.CharField(required=True, label=_("After Action Report Link"))

View File

@ -31,6 +31,7 @@ class SRPManager:
logger.debug("Ship type for kill ID %s is determined to be %s" % (kill_id, ship_type)) logger.debug("Ship type for kill ID %s is determined to be %s" % (kill_id, ship_type))
ship_value = result['zkb']['totalValue'] ship_value = result['zkb']['totalValue']
logger.debug("total loss value for kill id %s is %s" % (kill_id, ship_value)) logger.debug("total loss value for kill id %s is %s" % (kill_id, ship_value))
return ship_type, ship_value victim_name = result['victim']['characterName']
return ship_type, ship_value, victim_name
else: else:
raise ValueError("Invalid Kill ID") raise ValueError("Invalid Kill ID")

View File

@ -3,12 +3,12 @@ from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.decorators import permission_required from django.contrib.auth.decorators import permission_required
from django.contrib import messages from django.contrib import messages
from django.http import JsonResponse
from eveonline.managers import EveManager from eveonline.managers import EveManager
from srp.models import SrpFleetMain from srp.models import SrpFleetMain
from srp.models import SrpUserRequest from srp.models import SrpUserRequest
from srp.form import SrpFleetMainForm from srp.form import SrpFleetMainForm
from srp.form import SrpFleetUserRequestForm from srp.form import SrpFleetUserRequestForm
from srp.form import SrpFleetUpdateCostForm
from srp.form import SrpFleetMainUpdateForm from srp.form import SrpFleetMainUpdateForm
from srp.managers import SRPManager from srp.managers import SRPManager
from notifications import notify from notifications import notify
@ -56,7 +56,7 @@ def srp_fleet_view(request, fleet_id):
logger.debug("srp_fleet_view called by user %s for fleet id %s" % (request.user, fleet_id)) logger.debug("srp_fleet_view called by user %s for fleet id %s" % (request.user, fleet_id))
fleet_main = get_object_or_404(SrpFleetMain, id=fleet_id) fleet_main = get_object_or_404(SrpFleetMain, id=fleet_id)
context = {"fleet_id": fleet_id, "fleet_status": fleet_main.fleet_srp_status, context = {"fleet_id": fleet_id, "fleet_status": fleet_main.fleet_srp_status,
"srpfleetrequests": fleet_main.srpuserrequest_set.all(), "srpfleetrequests": fleet_main.srpuserrequest_set.order_by('srp_ship_name'),
"totalcost": fleet_main.total_cost} "totalcost": fleet_main.total_cost}
return render(request, 'registered/srpfleetdata.html', context=context) return render(request, 'registered/srpfleetdata.html', context=context)
@ -163,14 +163,21 @@ def srp_request_view(request, fleet_srp):
logger.debug("srp_request_view called by user %s for fleet srp code %s" % (request.user, fleet_srp)) logger.debug("srp_request_view called by user %s for fleet srp code %s" % (request.user, fleet_srp))
if SrpFleetMain.objects.filter(fleet_srp_code=fleet_srp).exists() is False: if SrpFleetMain.objects.filter(fleet_srp_code=fleet_srp).exists() is False:
messages.error(request, _("Unable to locate SRP Fleet using code %(code)s") % fleet_srp) logger.error("Unable to locate SRP Fleet using code %s for user %s" % (fleet_srp, request.user))
return redirect(srp_management) messages.error(request,
_('Unable to locate SRP code with ID %(srpfleetid)s') % {"srpfleetid": fleet_srp})
return redirect("auth_srp_management_view")
if request.method == 'POST': if request.method == 'POST':
form = SrpFleetUserRequestForm(request.POST) form = SrpFleetUserRequestForm(request.POST)
logger.debug("Request type POST contains form valid: %s" % form.is_valid()) logger.debug("Request type POST contains form valid: %s" % form.is_valid())
if form.is_valid(): if form.is_valid():
if SrpUserRequest.objects.filter(killboard_link=form.cleaned_data['killboard_link']).exists():
messages.error(request,
_("This Killboard link has already been posted."))
return redirect("auth_srp_management_view")
character = request.user.profile.main_character character = request.user.profile.main_character
srp_fleet_main = SrpFleetMain.objects.get(fleet_srp_code=fleet_srp) srp_fleet_main = SrpFleetMain.objects.get(fleet_srp_code=fleet_srp)
post_time = timezone.now() post_time = timezone.now()
@ -183,7 +190,7 @@ def srp_request_view(request, fleet_srp):
try: try:
srp_kill_link = SRPManager.get_kill_id(srp_request.killboard_link) srp_kill_link = SRPManager.get_kill_id(srp_request.killboard_link)
(ship_type_id, ship_value) = SRPManager.get_kill_data(srp_kill_link) (ship_type_id, ship_value, victim_name) = SRPManager.get_kill_data(srp_kill_link)
except ValueError: except ValueError:
logger.debug("User %s Submitted Invalid Killmail Link %s or server could not be reached" % ( logger.debug("User %s Submitted Invalid Killmail Link %s or server could not be reached" % (
request.user, srp_request.killboard_link)) request.user, srp_request.killboard_link))
@ -192,17 +199,21 @@ def srp_request_view(request, fleet_srp):
_( _(
"Your SRP request Killmail link is invalid. Please make sure you are using zKillboard.")) "Your SRP request Killmail link is invalid. Please make sure you are using zKillboard."))
return redirect("auth_srp_management_view") return redirect("auth_srp_management_view")
srp_ship_name = EveManager.get_itemtype(ship_type_id).name
srp_request.srp_ship_name = srp_ship_name
kb_total_loss = ship_value
srp_request.kb_total_loss = kb_total_loss
srp_request.post_time = post_time
srp_request.save()
logger.info("Created SRP Request on behalf of user %s for fleet name %s" % (
request.user, srp_fleet_main.fleet_name))
messages.success(request, _('Submitted SRP request for your %(ship)s.') % {"ship": srp_ship_name})
return redirect(srp_management)
if request.user.characterownership_set.filter(character__character_name=victim_name).exists():
srp_request.srp_ship_name = EveManager.get_itemtype(ship_type_id).name
srp_request.kb_total_loss = ship_value
srp_request.post_time = post_time
srp_request.save()
logger.info("Created SRP Request on behalf of user %s for fleet name %s" % (
request.user, srp_fleet_main.fleet_name))
messages.success(request, _('Submitted SRP request for your %(ship)s.') % {"ship": srp_request.srp_ship_name})
return redirect("auth_srp_management_view")
else:
messages.error(request,
_("%(charname)s does not belong to your Auth account. Please add the API key for this character and try again")
% {"charname": victim_name})
return redirect("auth_srp_management_view")
else: else:
logger.debug("Returning blank SrpFleetUserRequestForm") logger.debug("Returning blank SrpFleetUserRequestForm")
form = SrpFleetUserRequestForm() form = SrpFleetUserRequestForm()
@ -214,88 +225,122 @@ def srp_request_view(request, fleet_srp):
@login_required @login_required
@permission_required('auth.srp_management') @permission_required('auth.srp_management')
def srp_request_remove(request, srp_request_id): def srp_request_remove(request):
logger.debug("srp_request_remove called by user %s for srp request id %s" % (request.user, srp_request_id)) numrequests = len(request.POST)-1
logger.debug("srp_request_remove called by user %s for %s srp request id's" % (request.user, numrequests))
srpuserrequest = get_object_or_404(SrpUserRequest, id=srp_request_id) stored_fleet_view = None
srpuserrequest.delete() for srp_request_id in request.POST:
logger.info("Deleted SRP request id %s for user %s" % (srp_request_id, request.user)) if numrequests == 0:
messages.success(request, _('Deleted SRP request from %(character)s for their %(ship)s.') % { messages.warning(request, _("No SRP requests selected"))
"character": srpuserrequest.character, "ship": srpuserrequest.srp_ship_name}) return redirect("auth_srp_management_view")
return redirect("auth_srp_fleet_view", srpuserrequest.srp_fleet_main.id) if srp_request_id == "csrfmiddlewaretoken":
continue
if SrpUserRequest.objects.filter(id=srp_request_id).exists():
srpuserrequest = SrpUserRequest.objects.get(id=srp_request_id)
stored_fleet_view = srpuserrequest.srp_fleet_main.id
srpuserrequest.delete()
logger.info("Deleted SRP request id %s for user %s" % (srp_request_id, request.user))
if stored_fleet_view is None:
logger.error("Unable to delete srp request id %s for user %s - request matching id not found." % (
srp_request_id, request.user))
messages.error(request, _('Unable to locate SRP request with ID %(requestid)s') % {"requestid": srp_request_id})
return redirect("auth_srp_management_view")
else:
messages.success(request, _('Deleted %(numrequests)s SRP requests') % {"numrequests": numrequests})
return redirect("auth_srp_fleet_view", stored_fleet_view)
@login_required @login_required
@permission_required('auth.srp_management') @permission_required('auth.srp_management')
def srp_request_approve(request, srp_request_id): def srp_request_approve(request):
logger.debug("srp_request_approve called by user %s for srp request id %s" % (request.user, srp_request_id)) numrequests = len(request.POST)-1
srpuserrequest = get_object_or_404(SrpUserRequest, id=srp_request_id) logger.debug("srp_request_approve called by user %s for %s srp request id's" % (request.user, numrequests))
srpuserrequest.srp_status = "Approved" stored_fleet_view = None
if srpuserrequest.srp_total_amount == 0: for srp_request_id in request.POST:
srpuserrequest.srp_total_amount = srpuserrequest.kb_total_loss if numrequests == 0:
srpuserrequest.save() messages.warning(request, _("No SRP requests selected"))
logger.info("Approved SRP request id %s for character %s by user %s" % ( return redirect("auth_srp_management_view")
srp_request_id, srpuserrequest.character, request.user)) if srp_request_id == "csrfmiddlewaretoken":
messages.success(request, _('Approved SRP request from %(character)s for their %(ship)s.') % { continue
"character": srpuserrequest.character, "ship": srpuserrequest.srp_ship_name}) if SrpUserRequest.objects.filter(id=srp_request_id).exists():
if srpuserrequest.character.userprofile: srpuserrequest = SrpUserRequest.objects.get(id=srp_request_id)
notify( stored_fleet_view = srpuserrequest.srp_fleet_main.id
srpuserrequest.character.userprofile.user, srpuserrequest.srp_status = "Approved"
'SRP Request Approved', if srpuserrequest.srp_total_amount == 0:
level='success', srpuserrequest.srp_total_amount = srpuserrequest.kb_total_loss
message='Your SRP request for a %s lost during %s has been approved for %s ISK.' % ( srpuserrequest.save()
srpuserrequest.srp_ship_name, srpuserrequest.srp_fleet_main.fleet_name, logger.info("Approved SRP request id %s for character %s by user %s" % (
intcomma(srpuserrequest.srp_total_amount)) srp_request_id, srpuserrequest.character, request.user))
) notify(
return redirect("auth_srp_fleet_view", srpuserrequest.srp_fleet_main.id) srpuserrequest.character.user,
'SRP Request Approved',
level='success',
message='Your SRP request for a %s lost during %s has been approved for %s ISK.' % (
srpuserrequest.srp_ship_name, srpuserrequest.srp_fleet_main.fleet_name, srpuserrequest.srp_total_amount)
)
if stored_fleet_view is None:
logger.error("Unable to approve srp request id %s on behalf of user %s - request matching id not found." % (
srp_request_id, request.user))
messages.error(request, _('Unable to locate SRP request with ID %(requestid)s') % {"requestid": srp_request_id})
return redirect("auth_srp_management_view")
else:
messages.success(request, _('Approved %(numrequests)s SRP requests') % {"numrequests": numrequests})
return redirect("auth_srp_fleet_view", stored_fleet_view)
@login_required @login_required
@permission_required('auth.srp_management') @permission_required('auth.srp_management')
def srp_request_reject(request, srp_request_id): def srp_request_reject(request):
logger.debug("srp_request_reject called by user %s for srp request id %s" % (request.user, srp_request_id)) numrequests = len(request.POST)-1
srpuserrequest = get_object_or_404(SrpUserRequest, id=srp_request_id) logger.debug("srp_request_reject called by user %s for %s srp request id's" % (request.user, numrequests))
srpuserrequest.srp_status = "Rejected" stored_fleet_view = None
srpuserrequest.save() for srp_request_id in request.POST:
logger.info("SRP request id %s for character %s rejected by %s" % ( if numrequests == 0:
srp_request_id, srpuserrequest.character, request.user)) messages.warning(request, _("No SRP requests selected"))
messages.success(request, _('Rejected SRP request from %(character)s for their %(ship)s.') % { return redirect("auth_srp_management_view")
"character": srpuserrequest.character, "ship": srpuserrequest.srp_ship_name}) if srp_request_id == "csrfmiddlewaretoken":
if srpuserrequest.character.userprofile: continue
notify( if SrpUserRequest.objects.filter(id=srp_request_id).exists():
srpuserrequest.character.userprofile.user, srpuserrequest = SrpUserRequest.objects.get(id=srp_request_id)
'SRP Request Rejected', stored_fleet_view = srpuserrequest.srp_fleet_main.id
level='danger', srpuserrequest.srp_status = "Rejected"
message='Your SRP request for a %s lost during %s has been rejected.' % ( srpuserrequest.save()
srpuserrequest.srp_ship_name, srpuserrequest.srp_fleet_main.fleet_name) logger.info("SRP request id %s for character %s rejected by %s" % (
) srp_request_id, srpuserrequest.character, request.user))
return redirect("auth_srp_fleet_view", srpuserrequest.srp_fleet_main.id) notify(
srpuserrequest.character.user,
'SRP Request Rejected',
level='danger',
message='Your SRP request for a %s lost during %s has been rejected.' % (
srpuserrequest.srp_ship_name, srpuserrequest.srp_fleet_main.fleet_name)
)
if stored_fleet_view is None:
logger.error("Unable to reject SRP request id %s on behalf of user %s - request matching id not found." % (
srp_request_id, request.user))
messages.error(request, _('Unable to locate SRP request with ID %(requestid)s') % {"requestid": srp_request_id})
return redirect("auth_srp_management_view")
else:
messages.success(request, _('Rejected %(numrequests)s SRP requests.') % {"numrequests": numrequests})
return redirect("auth_srp_fleet_view", stored_fleet_view)
@login_required @login_required
@permission_required('auth.srp_management') @permission_required('auth.srp_management')
def srp_request_update_amount_view(request, fleet_srp_request_id): def srp_request_update_amount(request, fleet_srp_request_id):
logger.debug("srp_request_update_amount_view called by user %s for fleet srp request id %s" % ( logger.debug("srp_request_update_amount called by user %s for fleet srp request id %s" % (
request.user, fleet_srp_request_id)) request.user, fleet_srp_request_id))
srp_request = get_object_or_404(SrpUserRequest, id=fleet_srp_request_id) if SrpUserRequest.objects.filter(id=fleet_srp_request_id).exists() is False:
if request.method == 'POST': logger.error("Unable to locate SRP request id %s for user %s" % (fleet_srp_request_id, request.user))
form = SrpFleetUpdateCostForm(request.POST) messages.error(request, _('Unable to locate SRP request with ID %(requestid)s') % {"requestid": fleet_srp_request_id})
logger.debug("Request type POST contains form valid: %s" % form.is_valid()) return redirect("auth_srp_management_view")
if form.is_valid():
srp_request.srp_total_amount = form.cleaned_data['srp_total_amount']
srp_request.save()
logger.info("Updated srp request id %s total to %s by user %s" % (
fleet_srp_request_id, intcomma(form.cleaned_data['srp_total_amount']), request.user))
messages.success(request, _('Updated SRP amount.'))
return redirect("auth_srp_fleet_view", srp_request.srp_fleet_main.id)
else:
logger.debug("Returning blank SrpFleetUpdateCostForm")
form = SrpFleetUpdateCostForm(initial={'srp_total_amount': srp_request.srp_total_amount or srp_request.kb_total_loss})
render_items = {'form': form} srp_request = SrpUserRequest.objects.get(id=fleet_srp_request_id)
srp_request.srp_total_amount = request.POST['value']
return render(request, 'registered/srpfleetrequestamount.html', context=render_items) srp_request.save()
logger.info("Updated srp request id %s total to %s by user %s" % (
fleet_srp_request_id, request.POST['value'], request.user))
return JsonResponse({"success":True,"pk":fleet_srp_request_id,"newValue":request.POST['value']})
@login_required @login_required

View File

@ -0,0 +1,58 @@
.checkbox label:after,
.radio label:after {
content: '';
display: table;
clear: both;
}
.checkbox .cr,
.radio .cr {
position: relative;
display: inline-block;
border: 1px solid #a9a9a9;
border-radius: .25em;
width: 1.3em;
height: 1.3em;
float: left;
margin-right: .5em;
}
.radio .cr {
border-radius: 50%;
}
.checkbox .cr .cr-icon,
.radio .cr .cr-icon {
position: absolute;
font-size: .8em;
line-height: 0;
top: 50%;
left: 20%;
}
.radio .cr .cr-icon {
margin-left: 0.04em;
}
.checkbox label input[type="checkbox"],
.radio label input[type="radio"] {
display: none;
}
.checkbox label input[type="checkbox"] + .cr > .cr-icon,
.radio label input[type="radio"] + .cr > .cr-icon {
transform: scale(3) rotateZ(-20deg);
opacity: 0;
transition: all .3s ease-in;
}
.checkbox label input[type="checkbox"]:checked + .cr > .cr-icon,
.radio label input[type="radio"]:checked + .cr > .cr-icon {
transform: scale(1) rotateZ(0deg);
opacity: 1;
}
.checkbox label input[type="checkbox"]:disabled + .cr,
.radio label input[type="radio"]:disabled + .cr {
opacity: .5;
}

View File

@ -0,0 +1,4 @@
{% load static %}
<!-- Start X-Editablle js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/x-editable/1.5.1/bootstrap3-editable/js/bootstrap-editable.min.js"></script>
<!-- End X-Editable js -->

View File

@ -0,0 +1,4 @@
{% load staticfiles %}
<!-- X-Editable Core CSS -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/x-editable/1.5.1/bootstrap3-editable/css/bootstrap-editable.css" rel="stylesheet">
<!-- End Bootstrap CSS -->

View File

@ -0,0 +1,44 @@
{% load i18n %}
{% block content %}
<table class="table">
<thead>
<tr>
<th class="text-center col-lg-3">{% trans "Operation Name" %}</th>
<th class="text-center col lg-2">{% trans "Doctrine" %}</th>
<th class="text-center col-lg-1">{% trans "Form Up System" %}</th>
<th class="text-center col-lg-1">{% trans "Start Time" %}</th>
<th class="text-center col-lg-1">{% trans "Local Time" %}</th>
<th class="text-center col-lg-1">{% trans "Duration" %}</th>
<th class="text-center col-lg-1">{% trans "FC" %}</th>
{% if perms.auth.optimer_management %}
<th class="text-center col-lg-1">{% trans "Creator" %}</th>
<th class="text-center col-lg-2">{% trans "Action" %}</th>
{% endif %}
</tr>
</thead>
{% for ops in timers %}
<tbody>
<tr>
<td class="text-center">{{ ops.operation_name }}</td>
<td class="text-center">{{ ops.doctrine }}</td>
<td class="text-center">
<a href="http://evemaps.dotlan.net/system/{{ ops.system }}">{{ ops.system }}</a>
</td>
<td class="text-center" nowrap>{{ ops.start | date:"Y-m-d H:i" }}</td>
<td class="text-center" nowrap><div id="localtime{{ ops.id }}"></div><div id="countdown{{ ops.id }}"></div></td>
<td class="text-center">{{ ops.duration }}</td>
<td class="text-center">{{ ops.fc }}</td>
{% if perms.auth.optimer_management %}
<td class="text-center">{{ ops.eve_character }}</td>
<td class="text-center">
<a href="{% url 'auth_remove_optimer' ops.id %}" class="btn btn-danger">
<span class="glyphicon glyphicon-remove"></span>
</a><a href="{% url 'auth_edit_optimer' ops.id %}" class="btn btn-info"><span class="glyphicon glyphicon-pencil"></span></a>
</td>
{% endif %}
</tr>
</tbody>
{% endfor %}
</table>
{% endblock content %}

View File

@ -17,62 +17,26 @@
{% endif %} {% endif %}
</div> </div>
</h1> </h1>
<div class="col-lg-12 text-center row">
<div class="label label-info text-left">
<b>{% trans "Current Eve Time:" %} </b>
</div><div class="label label-info text-left" id="current-time"></div>
<br />
</div>
{% if optimer %}
<table class="table table-responsive">
<tr>
<th class="text-center">{% trans "Operation Name" %}</th>
<th class="text-center">{% trans "Doctrine" %}</th>
<th class="text-center">{% trans "Form Up System" %}</th>
<th class="text-center">{% trans "Form Up Location" %}</th>
<th class="text-center">{% trans "Start Time" %}</th>
<th class="text-center">{% trans "Local Time" %}</th>
<th class="text-center">{% trans "Duration" %}</th>
<th class="text-center">{% trans "FC" %}</th>
<th class="text-center">{% trans "Details" %}</th>
<th class="text-center">{% trans "Post Time" %}</th>
{% if perms.auth.optimer_management %}
<th class="text-center">{% trans "Creator" %}</th>
<th class="text-center">{% trans "Action" %}</th>
{% endif %}
</tr>
{% for ops in optimer %} <div class="col-lg-12 text-center row">
<tr> <div class="label label-info text-left">
<td style="width:150px" class="text-center">{{ ops.operation_name }}</td> <b>{% trans "Current Eve Time:" %} </b>
<td style="width:150px" class="text-center">{{ ops.doctrine }}</td> </div><div class="label label-info text-left" id="current-time"></div>
<td class="text-center"> <br />
<a href="http://evemaps.dotlan.net/system/{{ ops.system }}">{{ ops.system }}</a> </div>
</td>
<td style="width:150px" class="text-center">{{ ops.location }}</td> <h4><b>{% trans "Next Timers" %}</b></h4>
<td style="width:150px" class="text-center" nowrap>{{ ops.start | date:"Y-m-d H:i" }}</td> {% if future_timers %}
<td class="text-center" nowrap><div id="localtime{{ ops.id }}"></div><div id="countdown{{ ops.id }}"></div></td> {% include "registered/fleetoptable.html" with timers=future_timers %}
<td style="width:150px" class="text-center">{{ ops.duration }}</td>
<td style="width:150px" class="text-center">{{ ops.fc }}</td>
<td style="width:150px" class="text-center">{{ ops.details }}</td>
<td style="width:150px" class="text-center">{{ ops.post_time}}</td>
{% if perms.auth.optimer_management %}
<td style="width:150px" class="text-center">{{ ops.eve_character }}</td>
<td class="text-center">
<a href="{% url 'auth_remove_optimer' ops.id %}" class="btn btn-danger" title="{% trans 'Delete' %}">
<span class="glyphicon glyphicon-remove"></span>
</a>
<a href="{% url 'auth_edit_optimer' ops.id %}" class="btn btn-info" title="{% trans 'Edit' %}">
<span class="glyphicon glyphicon-pencil"></span>
</a>
</td>
{% endif %}
</tr>
{% endfor %}
</tr>
</table>
{% else %} {% else %}
<br /><div class="alert alert-warning text-center">{% trans "No fleet operations found." %}</div> <div class="alert alert-warning text-center">{% trans "No upcoming timers." %}</div>
{% endif %}
<h4><b>{% trans "Past Timers" %}</b></h4>
{% if past_timers %}
{% include "registered/fleetoptable.html" with timers=past_timers %}
{% else %}
<div class="alert alert-warning text-center">{% trans "No past timers." %}</div>
{% endif %} {% endif %}
</div> </div>

View File

@ -7,7 +7,42 @@
{% block title %}Alliance Auth{% endblock %} {% block title %}Alliance Auth{% endblock %}
{% block page_title %}Srp Fleet Data{% endblock page_title %} {% block page_title %}Srp Fleet Data{% endblock page_title %}
{% block extra_css %}{% endblock extra_css %} {% block extra_css %}
{% include 'bundles/x-editable.css.html' %}
<link href="{% static 'css/checkbox.css' %}" rel="stylesheet" type="text/css">
<style>
.radio label, .checkbox label {
padding-left: 10px;
}
.editable {
width:150px;
text-align: center;
}
.editableform .form-control {
width: 95%;
text-align: center;
margin-left: 10px;
}
.editable-input {
width: 95%;
}
.radio, .checkbox {
margin-top: 0px;
margin-bottom: 0px;
}
.editable-error-block {
white-space: nowrap;
}
.editable-click, a.editable-click, a.editable-click:hover {
border-bottom: none;
}
.tooltip-inner {
white-space:pre;
max-width: none;
}
</style>
{% endblock extra_css %}
{% block content %} {% block content %}
<div class="col-lg-12"> <div class="col-lg-12">
@ -27,92 +62,147 @@
{% endif %} {% endif %}
</div> </div>
</h1> </h1>
<div class="alert alert-info" role="alert">
<div class="text-right">
<b>{% trans "Total Losses:" %} {{ srpfleetrequests.count }}</b>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<b>{% trans "Total ISK Cost:" %} {{ totalcost | intcomma }}</b>
</div>
</div>
{% if srpfleetrequests %} {% if srpfleetrequests %}
<table class="table"> <form method="POST">
<tr> {% csrf_token %}
<th class="text-center">{% trans "Pilot Name" %}</th> <div class="alert alert-info" role="alert">
<th class="text-center">{% trans "Killboard Link" %}</th> <div class="text-right">
<th class="text-center">{% trans "Additional Info" %}</th> <b><span style="padding-right:2.5em">{% trans "Total Losses:" %} {{ srpfleetrequests.count }}</span></b>
<th class="text-center">{% trans "Ship Type" %}</th> <b><span style="padding-right:2.5em">{% trans "Total ISK Cost:" %} {{ totalcost | intcomma }}</span></b>
<th class="text-center">{% trans "Killboard Loss Amt" %}</th>
<th class="text-center">{% trans "SRP ISK Cost" %}</th>
<th class="text-center">{% trans "Post Time" %}</th>
<th class="text-center">{% trans "Status" %}</th>
{% if perms.auth.srp_management %}
<th class="text-center">{% trans "Actions" %}</th>
{% endif %}
</tr>
{% for srpfleetrequest in srpfleetrequests %}
<tr>
<td class="text-center">{{ srpfleetrequest.character.character_name }}</td>
<td class="text-center">
<a href="{{ srpfleetrequest.killboard_link }}"
target="_blank" class="label label-warning">Link</a>
</td>
<td class="text-center">{{ srpfleetrequest.additional_info }}</td>
<td class="text-center">{{ srpfleetrequest.srp_ship_name }}</td>
<td class="text-center">ISK: {{ srpfleetrequest.kb_total_loss | intcomma }}</td>
<td class="text-center">ISK: {{ srpfleetrequest.srp_total_amount | intcomma }}</td>
<td class="text-center">{{ srpfleetrequest.post_time | date:"Y-m-d H:i" }}</td>
<td class="text-center">
{% if srpfleetrequest.srp_status == "Approved" %}
<div class="label label-success">
{% trans "Approved" %}
</div>
{% elif srpfleetrequest.srp_status == "Rejected" %}
<div class="label label-danger">
{% trans "Rejected" %}
</div>
{% else %}
<div class="label label-warning">
{% trans "Pending" %}
</div>
{% endif %}
</td>
{% if perms.auth.srp_management %} {% if perms.auth.srp_management %}
<button type="submit" title="Approve" class="btn btn-success" formaction="{% url 'auth_srp_request_approve' %}">
<td class="text-center"> <span class="glyphicon glyphicon-ok"></span>
<a href="{% url 'auth_srp_request_update_amount_view' srpfleetrequest.id %}" class="btn btn-info" title="Update Value"> </button>
<span class="glyphicon glyphicon-usd"></span> <button type="submit" title="Reject" class="btn btn-warning" formaction="{% url 'auth_srp_request_reject' %}">
</a> <span class="glyphicon glyphicon-remove"></span>
{% if srpfleetrequest.srp_status in "RejectedPending" %} </button>
<a href="{% url 'auth_srp_request_approve' srpfleetrequest.id %}" class="btn btn-success" title="Approve"> <button type="submit" title="Remove" onclick="return confirm('{% trans "Are you sure you want to delete SRP requests?" %}')" class="btn btn-danger" formaction="{% url 'auth_srp_request_remove' %}">
<span class="glyphicon glyphicon-ok"></span> <span class="glyphicon glyphicon-trash"></span>
</a> </button>
{% elif srpfleetrequest.srp_status == "" %} {% endif %}
<a href="{% url 'auth_srp_request_approve' srpfleetrequest.id %}" class="btn btn-success" title="Approve"> </div>
<span class="glyphicon glyphicon-ok"></span> </div>
</a>
{% endif %} <table class="table">
{% if srpfleetrequest.srp_status in "ApprovedPending" %} <tr>
<a href="{% url 'auth_srp_request_reject' srpfleetrequest.id %}" class="btn btn-warning" title="Reject"> <th class="text-center">{% trans "Pilot Name" %}</th>
<span class="glyphicon glyphicon-remove"></span> <th class="text-center">{% trans "Killboard Link" %}</th>
</a> <th class="text-center">{% trans "Additional Info" %}</th>
{% elif srpfleetrequest.srp_status == "" %} <th class="text-center">{% trans "Ship Type" %}</th>
<a href="{% url 'auth_srp_request_reject' srpfleetrequest.id %}" class="btn btn-warning" title="Reject"> <th class="text-center">{% trans "Killboard Loss Amt" %}</th>
<span class="glyphicon glyphicon-remove"></span> <th class="text-center">{% trans "SRP ISK Cost" %}
</a> {% blocktrans %}<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="Click value to edit
{% endif %} Enter to save&next
<a href="{% url 'auth_srp_request_remove' srpfleetrequest.id %}" class="btn btn-danger" title="Remove"> ESC to cancel"
<span class="glyphicon glyphicon-trash"></span> id="blah"></i></th>{% endblocktrans %}
</a> <th class="text-center">{% trans "Post Time" %}</th>
<th class="text-center">{% trans "Status" %}</th>
</td> {% if perms.auth.srp_management %}
<th class="text-center">{% trans "Actions" %}</th>
{% endif %} {% endif %}
</tr> </tr>
{% endfor %} {% for srpfleetrequest in srpfleetrequests %}
</table> <tr>
<td class="text-center">{{ srpfleetrequest.character.character_name }}</td>
<td class="text-center">
<a href="{{ srpfleetrequest.killboard_link }}"
target="_blank" class="label label-warning">Link</a>
</td>
<td class="text-center">{{ srpfleetrequest.additional_info }}</td>
<td class="text-center">{{ srpfleetrequest.srp_ship_name }}</td>
<td class="text-center">{{ srpfleetrequest.kb_total_loss | intcomma }} ISK</td>
<td class="srp" data-name="srp_total_amount" data-type="number" data-pk="{{srpfleetrequest.id}}" data-url="{% url 'auth_srp_request_update_amount' srpfleetrequest.id %}" data-params="{csrfmiddlewaretoken:'{{csrf_token}}'}" class="text-center">{{ srpfleetrequest.srp_total_amount | intcomma }} ISK</td>
<td class="text-center">{{ srpfleetrequest.post_time | date:"Y-m-d H:i" }}</td>
<td class="text-center">
{% if srpfleetrequest.srp_status == "Approved" %}
<div class="label label-success">
{% trans "Approved" %}
</div>
{% elif srpfleetrequest.srp_status == "Rejected" %}
<div class="label label-danger">
{% trans "Rejected" %}
</div>
{% else %}
<div class="label label-warning">
{% trans "Pending" %}
</div>
{% endif %}
</td>
{% if perms.auth.srp_management %}
<td class="text-center">
<div class="checkbox">
<label style="font-size: 1.5em">
<input type="checkbox" name="{{srpfleetrequest.id}}">
<span class="cr"><i class="cr-icon fa fa-check"></i></span>
</label>
</div>
</td>
{% endif %}
</tr>
{% endfor %}
</table>
<div class="alert alert-info" role="alert">
<div class="text-right">
<b><span style="padding-right:2.5em">{% trans "Total Losses:" %} {{ srpfleetrequests.count }}</span></b>
<b><span style="padding-right:2.5em">{% trans "Total ISK Cost:" %} {{ totalcost | intcomma }}</span></b>
{% if perms.auth.srp_management %}
<button type="submit" title="Approve" class="btn btn-success" formaction="{% url 'auth_srp_request_approve' %}">
<span class="glyphicon glyphicon-ok"></span>
</button>
<button type="submit" title="Reject" class="btn btn-warning" formaction="{% url 'auth_srp_request_reject' %}">
<span class="glyphicon glyphicon-remove"></span>
</button>
<button type="submit" title="Remove" onclick="return confirm('{% trans "Are you sure you want to delete SRP requests?" %}')" class="btn btn-danger" formaction="{% url 'auth_srp_request_remove' %}">
<span class="glyphicon glyphicon-trash"></span>
</button>
{% endif %}
</div>
</div>
</form>
{% else %} {% else %}
<div class="alert alert-warning text-center">{% trans "No SRP requests for this fleet." %}</div> <div class="alert alert-warning text-center">{% trans "No SRP requests for this fleet." %}</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}
{% block extra_javascript %}
{% include 'bundles/x-editable-js.html' %}
{% endblock %}
{% block extra_script %}
$(document).ready(function() {
$.fn.editable.defaults.mode = 'inline';
$.fn.editable.defaults.showbuttons = false;
$.fn.editable.defaults.highlight = "#AAFF80";
$('.srp').editable({
display: function(value, response) {
return false;
},
success: function(response, newValue) {
newValue = parseInt(newValue);
newvalue = newValue.toLocaleString() + " ISK";
$(this).html(newvalue.bold());
},
validate: function(value) {
if (value === null || value === '') {
return 'Empty values not allowed';
}
}
});
$('.srp').on('hidden', function(e, reason){
if(reason === 'save' || reason === 'nochange') {
var $next = $(this).closest('tr').next().find('.editable');
setTimeout(function() {
$next.editable('show');
}, 400);
}
});
});
$(document).ready(function(){
$("[rel=tooltip]").tooltip({ placement: 'top'});
});
{% endblock extra_script %}

View File

@ -1,31 +0,0 @@
{% extends "registered/base.html" %}
{% load bootstrap %}
{% load staticfiles %}
{% load i18n %}
{% block title %}Alliance Auth - Update SRP Amount{% endblock %}
{% block page_title %}{% trans "Update SRP Amount" %}{% endblock page_title %}
{% block content %}
<div class="col-lg-12">
<h1 class="page-header text-center">{% trans "Update SRP Amount" %}</h1>
<div class="container-fluid">
<div class="col-md-4 col-md-offset-4">
<div class="row">
<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 "Update SRP Request Amount" %}
</button>
</form>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -41,7 +41,7 @@
<th class="text-center">{% trans "Fleet ISK Cost" %}</th> <th class="text-center">{% trans "Fleet ISK Cost" %}</th>
<th class="text-center">{% trans "SRP Status" %}</th> <th class="text-center">{% trans "SRP Status" %}</th>
<th class="text-center">{% trans "Pending Requests" %}</th> <th class="text-center">{% trans "Pending Requests" %}</th>
<th class="text-center">{% trans "Actions" %}</th> <th width="100px" class="text-center">{% trans "Actions" %}</th>
</tr> </tr>
{% for srpfleet in srpfleets %} {% for srpfleet in srpfleets %}
<tr> <tr>
@ -98,7 +98,7 @@
<span class="glyphicon glyphicon-pencil"></span> <span class="glyphicon glyphicon-pencil"></span>
</a> </a>
<a href="{% url 'auth_srp_fleet_remove' srpfleet.id %}" class="btn btn-danger" title="Remove"> <a href="{% url 'auth_srp_fleet_remove' srpfleet.id %}" onclick="return confirm('{% trans "Are you sure you want to delete this SRP code and its contents?" %}')" class="btn btn-danger" title="Remove">
<span class="glyphicon glyphicon-trash"></span> <span class="glyphicon glyphicon-trash"></span>
</a> </a>
{% if srpfleet.fleet_srp_code %} {% if srpfleet.fleet_srp_code %}

View File

@ -1,5 +1,5 @@
[program:auth-celerybeat] [program:auth-celerybeat]
command=python manage.py celerybeat command=celery -A alliance_auth beat
directory=/home/allianceserver/allianceauth directory=/home/allianceserver/allianceauth
user=allianceserver user=allianceserver
stdout_logfile=/home/allianceserver/allianceauth/log/beat.log stdout_logfile=/home/allianceserver/allianceauth/log/beat.log

View File

@ -1,5 +1,5 @@
[program:auth-celeryd] [program:auth-celeryd]
command=python manage.py celeryd command=celery -A alliance_auth worker
directory=/home/allianceserver/allianceauth directory=/home/allianceserver/allianceauth
user=allianceserver user=allianceserver
numprocs=1 numprocs=1

View File

@ -31,14 +31,8 @@ def timer_view(request):
else: else:
corp_timers = [] corp_timers = []
timer_list = Timer.objects.filter(corp_timer=False) timer_list = Timer.objects.filter(corp_timer=False)
closest_timer = None
if timer_list:
closest_timer = \
sorted(list(Timer.objects.all().filter(corp_timer=False)), key=lambda d: (timezone.now()))[0]
logger.debug("Determined closest timer is %s" % closest_timer)
render_items = {'timers': Timer.objects.all().filter(corp_timer=False), render_items = {'timers': Timer.objects.all().filter(corp_timer=False),
'corp_timers': corp_timers, 'corp_timers': corp_timers,
'closest_timer': closest_timer,
'future_timers': Timer.objects.all().filter(corp_timer=False).filter( 'future_timers': Timer.objects.all().filter(corp_timer=False).filter(
eve_time__gte=datetime.datetime.now()), eve_time__gte=datetime.datetime.now()),
'past_timers': Timer.objects.all().filter(corp_timer=False).filter( 'past_timers': Timer.objects.all().filter(corp_timer=False).filter(