diff --git a/cof/settings/.gitignore b/cof/settings/.gitignore
new file mode 100644
index 00000000..21425062
--- /dev/null
+++ b/cof/settings/.gitignore
@@ -0,0 +1 @@
+secret.py
diff --git a/cof/settings_dev.py b/cof/settings/common.py
similarity index 66%
rename from cof/settings_dev.py
rename to cof/settings/common.py
index c6b26ef6..98f4f713 100644
--- a/cof/settings_dev.py
+++ b/cof/settings/common.py
@@ -1,32 +1,40 @@
# -*- coding: utf-8 -*-
"""
-Django settings for cof project.
+Django common settings for cof project.
-For more information on this file, see
-https://docs.djangoproject.com/en/1.8/topics/settings/
-
-For the full list of settings and their values, see
-https://docs.djangoproject.com/en/1.8/ref/settings/
+Everything which is supposed to be identical between the production server and
+the local development server should be here.
"""
-# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
-BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+# Database credentials
+try:
+ from .secret import DBNAME, DBUSER, DBPASSWD
+except ImportError:
+ # On the local development VM, theses credentials are in the environment
+ DBNAME = os.environ["DBNAME"]
+ DBUSER = os.environ["DBUSER"]
+ DBPASSWD = os.environ["DBPASSWD"]
+except KeyError:
+ raise RuntimeError("Secrets missing")
-EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+# Other secrets
+try:
+ from .secret import (
+ SECRET_KEY, RECAPTCHA_PUBLIC_KEY, RECAPTCHA_PRIVATE_KEY, ADMINS
+ )
+except ImportError:
+ raise RuntimeError("Secrets missing")
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
-# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = 'q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah'
+BASE_DIR = os.path.dirname(
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+)
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = True
# Application definition
-INSTALLED_APPS = (
+INSTALLED_APPS = [
'gestioncof',
'django.contrib.auth',
'django.contrib.contenttypes',
@@ -41,17 +49,15 @@ INSTALLED_APPS = (
'autocomplete_light',
'captcha',
'django_cas_ng',
- 'debug_toolbar',
'bootstrapform',
'kfet',
'channels',
'widget_tweaks',
'custommail',
'djconfig',
-)
+]
-MIDDLEWARE_CLASSES = (
- 'debug_toolbar.middleware.DebugToolbarMiddleware',
+MIDDLEWARE_CLASSES = [
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
@@ -62,7 +68,7 @@ MIDDLEWARE_CLASSES = (
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
'djconfig.middleware.DjConfigMiddleware',
-)
+]
ROOT_URLCONF = 'cof.urls'
@@ -89,17 +95,12 @@ TEMPLATES = [
},
]
-# WSGI_APPLICATION = 'cof.wsgi.application'
-
-# Database
-# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
-
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
- 'NAME': os.environ['DBNAME'],
- 'USER': os.environ['DBUSER'],
- 'PASSWORD': os.environ['DBPASSWD'],
+ 'NAME': DBNAME,
+ 'USER': DBUSER,
+ 'PASSWORD': DBPASSWD,
'HOST': os.environ.get('DBHOST', 'localhost'),
}
}
@@ -118,19 +119,6 @@ USE_L10N = True
USE_TZ = True
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/1.8/howto/static-files/
-
-STATIC_URL = '/static/'
-STATIC_ROOT = '/var/www/static/'
-
-# Media upload (through ImageField, SiteField)
-# https://docs.djangoproject.com/en/1.9/ref/models/fields/
-
-MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
-MEDIA_URL = '/media/'
-
# Various additional settings
SITE_ID = 1
@@ -163,12 +151,6 @@ AUTHENTICATION_BACKENDS = (
'kfet.backends.GenericTeamBackend',
)
-# LDAP_SERVER_URL = 'ldaps://ldap.spi.ens.fr:636'
-
-# EMAIL_HOST="nef.ens.fr"
-
-RECAPTCHA_PUBLIC_KEY = "DUMMY"
-RECAPTCHA_PRIVATE_KEY = "DUMMY"
RECAPTCHA_USE_SSL = True
@@ -209,22 +191,4 @@ CHANNEL_LAYERS = {
}
}
-
-def show_toolbar(request):
- """
- On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar
- car cela interfère avec l'utilisation de Vagrant. En effet, l'adresse de la
- machine physique n'est pas forcément connue, et peut difficilement être
- mise dans les INTERNAL_IPS.
- """
- if not DEBUG:
- return False
- if request.is_ajax():
- return False
- return True
-
-DEBUG_TOOLBAR_CONFIG = {
- 'SHOW_TOOLBAR_CALLBACK': show_toolbar,
-}
-
FORMAT_MODULE_PATH = 'cof.locale'
diff --git a/cof/settings/dev.py b/cof/settings/dev.py
new file mode 100644
index 00000000..6272e6d9
--- /dev/null
+++ b/cof/settings/dev.py
@@ -0,0 +1,51 @@
+"""
+Django development settings for the cof project.
+The settings that are not listed here are imported from .common
+"""
+
+import os
+
+from .common import *
+
+
+EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
+
+DEBUG = True
+
+
+# ---
+# Apache static/media config
+# ---
+
+STATIC_URL = '/static/'
+STATIC_ROOT = '/var/www/static/'
+
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
+MEDIA_URL = '/media/'
+
+
+# ---
+# Debug tool bar
+# ---
+
+def show_toolbar(request):
+ """
+ On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar
+ car cela interfère avec l'utilisation de Vagrant. En effet, l'adresse de la
+ machine physique n'est pas forcément connue, et peut difficilement être
+ mise dans les INTERNAL_IPS.
+ """
+ if not DEBUG:
+ return False
+ if request.is_ajax():
+ return False
+ return True
+
+INSTALLED_APPS += ["debug_toolbar"]
+MIDDLEWARE_CLASSES = (
+ ["debug_toolbar.middleware.DebugToolbarMiddleware"]
+ + MIDDLEWARE_CLASSES
+)
+DEBUG_TOOLBAR_CONFIG = {
+ 'SHOW_TOOLBAR_CALLBACK': show_toolbar,
+}
diff --git a/cof/settings/prod.py b/cof/settings/prod.py
new file mode 100644
index 00000000..5fae5651
--- /dev/null
+++ b/cof/settings/prod.py
@@ -0,0 +1,26 @@
+"""
+Django development settings for the cof project.
+The settings that are not listed here are imported from .common
+"""
+
+import os
+
+from .common import *
+
+
+DEBUG = False
+
+ALLOWED_HOSTS = [
+ "cof.ens.fr",
+ "www.cof.ens.fr",
+ "dev.cof.ens.fr"
+]
+
+STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), "static")
+STATIC_URL = "/gestion/static/"
+MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "media")
+MEDIA_URL = "/gestion/media/"
+
+LDAP_SERVER_URL = "ldaps://ldap.spi.ens.fr:636"
+
+EMAIL_HOST = "nef.ens.fr"
diff --git a/cof/settings/secret_example.py b/cof/settings/secret_example.py
new file mode 100644
index 00000000..36a8e932
--- /dev/null
+++ b/cof/settings/secret_example.py
@@ -0,0 +1,4 @@
+SECRET_KEY = 'q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah'
+RECAPTCHA_PUBLIC_KEY = "DUMMY"
+RECAPTCHA_PRIVATE_KEY = "DUMMY"
+ADMINS = None
diff --git a/kfet/consumers.py b/kfet/consumers.py
index dcd69bdf..ee096368 100644
--- a/kfet/consumers.py
+++ b/kfet/consumers.py
@@ -1,26 +1,41 @@
# -*- coding: utf-8 -*-
-from __future__ import (absolute_import, division,
- print_function, unicode_literals)
-from builtins import *
+from django.core.serializers.json import json, DjangoJSONEncoder
-from channels import Group
from channels.generic.websockets import JsonWebsocketConsumer
-class KPsul(JsonWebsocketConsumer):
- # Set to True if you want them, else leave out
- strict_ordering = False
- slight_ordering = False
+class DjangoJsonWebsocketConsumer(JsonWebsocketConsumer):
+ """Custom Json Websocket Consumer.
- def connection_groups(self, **kwargs):
- return ['kfet.kpsul']
+ Encode to JSON with DjangoJSONEncoder.
+
+ """
+
+ @classmethod
+ def encode_json(cls, content):
+ return json.dumps(content, cls=DjangoJSONEncoder)
+
+
+class PermConsumerMixin(object):
+ """Add support to check permissions on Consumers.
+
+ Attributes:
+ perms_connect (list): Required permissions to connect to this
+ consumer.
+
+ """
+ http_user = True # Enable message.user
+ perms_connect = []
def connect(self, message, **kwargs):
- pass
+ """Check permissions on connection."""
+ if message.user.has_perms(self.perms_connect):
+ super().connect(message, **kwargs)
+ else:
+ self.close()
- def receive(self, content, **kwargs):
- pass
- def disconnect(self, message, **kwargs):
- pass
+class KPsul(PermConsumerMixin, DjangoJsonWebsocketConsumer):
+ groups = ['kfet.kpsul']
+ perms_connect = ['kfet.is_team']
diff --git a/kfet/models.py b/kfet/models.py
index 7c03191a..6c1f1240 100644
--- a/kfet/models.py
+++ b/kfet/models.py
@@ -81,7 +81,7 @@ class Account(models.Model):
# Propriétés supplémentaires
@property
def real_balance(self):
- if (hasattr(self, 'negative')):
+ if hasattr(self, 'negative') and self.negative.balance_offset:
return self.balance - self.negative.balance_offset
return self.balance
@@ -210,6 +210,29 @@ class Account(models.Model):
def delete(self, *args, **kwargs):
pass
+ def update_negative(self):
+ if self.real_balance < 0:
+ if hasattr(self, 'negative') and not self.negative.start:
+ self.negative.start = timezone.now()
+ self.negative.save()
+ elif not hasattr(self, 'negative'):
+ self.negative = (
+ AccountNegative.objects.create(
+ account=self, start=timezone.now(),
+ )
+ )
+ elif hasattr(self, 'negative'):
+ # self.real_balance >= 0
+ balance_offset = self.negative.balance_offset
+ if balance_offset:
+ (
+ Account.objects
+ .filter(pk=self.pk)
+ .update(balance=F('balance')-balance_offset)
+ )
+ self.refresh_from_db()
+ self.negative.delete()
+
class UserHasAccount(Exception):
def __init__(self, trigramme):
self.trigramme = trigramme
diff --git a/kfet/templates/kfet/account_read.html b/kfet/templates/kfet/account_read.html
index af9eaa89..b55f2b99 100644
--- a/kfet/templates/kfet/account_read.html
+++ b/kfet/templates/kfet/account_read.html
@@ -8,7 +8,6 @@
-
{% if account.user == request.user %}
@@ -18,11 +17,11 @@
$(document).ready(function() {
var stat_last = new StatsGroup(
"{% url 'kfet.account.stat.operation.list' trigramme=account.trigramme %}",
- $("#stat_last"),
+ $("#stat_last")
);
var stat_balance = new StatsGroup(
"{% url 'kfet.account.stat.balance.list' trigramme=account.trigramme %}",
- $("#stat_balance"),
+ $("#stat_balance")
);
});
diff --git a/kfet/views.py b/kfet/views.py
index 330b195a..60dbb44b 100644
--- a/kfet/views.py
+++ b/kfet/views.py
@@ -1105,22 +1105,15 @@ def kpsul_perform_operations(request):
with transaction.atomic():
# If not cash account,
# saving account's balance and adding to Negative if not in
- if not operationgroup.on_acc.is_cash:
- Account.objects.filter(pk=operationgroup.on_acc.pk).update(
- balance=F('balance') + operationgroup.amount)
- operationgroup.on_acc.refresh_from_db()
- if operationgroup.on_acc.balance < 0:
- if hasattr(operationgroup.on_acc, 'negative'):
- if not operationgroup.on_acc.negative.start:
- operationgroup.on_acc.negative.start = timezone.now()
- operationgroup.on_acc.negative.save()
- else:
- negative = AccountNegative(
- account=operationgroup.on_acc, start=timezone.now())
- negative.save()
- elif (hasattr(operationgroup.on_acc, 'negative') and
- not operationgroup.on_acc.negative.balance_offset):
- operationgroup.on_acc.negative.delete()
+ on_acc = operationgroup.on_acc
+ if not on_acc.is_cash:
+ (
+ Account.objects
+ .filter(pk=on_acc.pk)
+ .update(balance=F('balance') + operationgroup.amount)
+ )
+ on_acc.refresh_from_db()
+ on_acc.update_negative()
# Updating checkout's balance
if to_checkout_balance:
@@ -1311,8 +1304,15 @@ def kpsul_cancel_operations(request):
(Operation.objects.filter(pk__in=opes)
.update(canceled_by=canceled_by, canceled_at=canceled_at))
for account in to_accounts_balances:
- Account.objects.filter(pk=account.pk).update(
- balance = F('balance') + to_accounts_balances[account])
+ (
+ Account.objects
+ .filter(pk=account.pk)
+ .update(balance=F('balance') + to_accounts_balances[account])
+ )
+ if not account.is_cash:
+ # Should always be true, but we want to be sure
+ account.refresh_from_db()
+ account.update_negative()
for checkout in to_checkouts_balances:
Checkout.objects.filter(pk=checkout.pk).update(
balance = F('balance') + to_checkouts_balances[checkout])
diff --git a/provisioning/bootstrap.sh b/provisioning/bootstrap.sh
index 269e4f25..c8f73ab6 100644
--- a/provisioning/bootstrap.sh
+++ b/provisioning/bootstrap.sh
@@ -36,7 +36,7 @@ chown -R ubuntu:www-data /var/www/static
# Mise en place du .bash_profile pour tout configurer lors du `vagrant ssh`
cat >> ~ubuntu/.bashrc <