WIP: Aureplop/kpsul js refactor #501

Draft
delobell wants to merge 215 commits from aureplop/kpsul_js_refactor into master
27 changed files with 655 additions and 393 deletions
Showing only changes of commit 20d635137c - Show all commits

View file

@ -18,7 +18,10 @@ class Tirage(models.Model):
fermeture = models.DateTimeField("Date et heure de fermerture du tirage") fermeture = models.DateTimeField("Date et heure de fermerture du tirage")
tokens = models.TextField("Graine(s) du tirage", blank=True) tokens = models.TextField("Graine(s) du tirage", blank=True)
active = models.BooleanField("Tirage actif", default=False) active = models.BooleanField("Tirage actif", default=False)
appear_catalogue = models.BooleanField("Tirage à afficher dans le catalogue", default=False) appear_catalogue = models.BooleanField(
"Tirage à afficher dans le catalogue",
default=False
)
enable_do_tirage = models.BooleanField("Le tirage peut être lancé", enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
default=False) default=False)

View file

@ -1,22 +1,79 @@
# -*- coding: utf-8 -*- import json
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application. from django.test import TestCase, Client
""" from django.utils import timezone
from __future__ import division from .models import Tirage, Spectacle, Salle, CategorieSpectacle
from __future__ import print_function
from __future__ import unicode_literals
from django.test import TestCase class TestBdAViews(TestCase):
def setUp(self):
self.tirage = Tirage.objects.create(
title="Test tirage",
appear_catalogue=True,
ouverture=timezone.now(),
fermeture=timezone.now(),
)
self.category = CategorieSpectacle.objects.create(name="Category")
self.location = Salle.objects.create(name="here")
Spectacle.objects.bulk_create([
Spectacle(
title="foo", date=timezone.now(), location=self.location,
price=0, slots=42, tirage=self.tirage, listing=False,
category=self.category
),
Spectacle(
title="bar", date=timezone.now(), location=self.location,
price=1, slots=142, tirage=self.tirage, listing=False,
category=self.category
),
Spectacle(
title="baz", date=timezone.now(), location=self.location,
price=2, slots=242, tirage=self.tirage, listing=False,
category=self.category
),
])
def test_catalogue(self):
"""Test the catalogue JSON API"""
client = Client()
class SimpleTest(TestCase): # The `list` hooh
def test_basic_addition(self): resp = client.get("/bda/catalogue/list")
""" self.assertJSONEqual(
Tests that 1 + 1 always equals 2. resp.content.decode("utf-8"),
""" [{"id": self.tirage.id, "title": self.tirage.title}]
self.assertEqual(1 + 1, 2) )
# The `details` hook
resp = client.get(
"/bda/catalogue/details?id={}".format(self.tirage.id)
)
self.assertJSONEqual(
resp.content.decode("utf-8"),
{
"categories": [{
"id": self.category.id,
"name": self.category.name
}],
"locations": [{
"id": self.location.id,
"name": self.location.name
}],
}
)
# The `descriptions` hook
resp = client.get(
"/bda/catalogue/descriptions?id={}".format(self.tirage.id)
)
raw = resp.content.decode("utf-8")
try:
results = json.loads(raw)
except ValueError:
self.fail("Not valid JSON: {}".format(raw))
self.assertEqual(len(results), 3)
self.assertEqual(
{(s["title"], s["price"], s["slots"]) for s in results},
{("foo", 0, 42), ("bar", 1, 142), ("baz", 2, 242)}
)

View file

@ -22,7 +22,6 @@ from django.core.urlresolvers import reverse
from django.conf import settings from django.conf import settings
from django.utils import timezone, formats from django.utils import timezone, formats
from django.views.generic.list import ListView from django.views.generic.list import ListView
from django.core.exceptions import ObjectDoesNotExist
from gestioncof.decorators import cof_required, buro_required from gestioncof.decorators import cof_required, buro_required
from bda.models import ( from bda.models import (
Spectacle, Participant, ChoixSpectacle, Attribution, Tirage, Spectacle, Participant, ChoixSpectacle, Attribution, Tirage,
@ -657,29 +656,35 @@ def catalogue(request, request_type):
if request_type == "list": if request_type == "list":
# Dans ce cas on retourne la liste des tirages et de leur id en JSON # Dans ce cas on retourne la liste des tirages et de leur id en JSON
data_return = list( data_return = list(
Tirage.objects.filter(appear_catalogue=True).values('id', 'title')) Tirage.objects.filter(appear_catalogue=True).values('id', 'title')
)
return JsonResponse(data_return, safe=False) return JsonResponse(data_return, safe=False)
if request_type == "details": if request_type == "details":
# Dans ce cas on retourne une liste des catégories et des salles # Dans ce cas on retourne une liste des catégories et des salles
tirage_id = request.GET.get('id', '') tirage_id = request.GET.get('id', None)
try: if tirage_id is None:
tirage = Tirage.objects.get(id=tirage_id)
except ObjectDoesNotExist:
return HttpResponseBadRequest( return HttpResponseBadRequest(
"Aucun tirage correspondant à l'id " "Missing GET parameter: id <int>"
+ tirage_id) )
try:
tirage = get_object_or_404(Tirage, id=int(tirage_id))
except ValueError: except ValueError:
return HttpResponseBadRequest( return HttpResponseBadRequest(
"Mauvais format d'identifiant : " "Bad format: int expected for `id`"
+ tirage_id) )
shows = tirage.spectacle_set.values_list("id", flat=True)
categories = list( categories = list(
CategorieSpectacle.objects.filter( CategorieSpectacle.objects
spectacle__in=tirage.spectacle_set.all()) .filter(spectacle__in=shows)
.distinct().values('id', 'name')) .distinct()
.values('id', 'name')
)
locations = list( locations = list(
Salle.objects.filter( Salle.objects
spectacle__in=tirage.spectacle_set.all()) .filter(spectacle__in=shows)
.distinct().values('id', 'name')) .distinct()
.values('id', 'name')
)
data_return = {'categories': categories, 'locations': locations} data_return = {'categories': categories, 'locations': locations}
return JsonResponse(data_return, safe=False) return JsonResponse(data_return, safe=False)
if request_type == "descriptions": if request_type == "descriptions":
@ -687,33 +692,35 @@ def catalogue(request, request_type):
# à la salle spécifiées # à la salle spécifiées
tirage_id = request.GET.get('id', '') tirage_id = request.GET.get('id', '')
categories = request.GET.get('category', '[0]') categories = request.GET.get('category', '[]')
locations = request.GET.get('location', '[0]') locations = request.GET.get('location', '[]')
try: try:
category_id = json.loads(categories) tirage_id = int(tirage_id)
location_id = json.loads(locations) categories_id = json.loads(categories)
tirage = Tirage.objects.get(id=tirage_id) locations_id = json.loads(locations)
# Integers expected
shows_qs = tirage.spectacle_set if not all(isinstance(id, int) for id in categories_id):
if not(0 in category_id): raise ValueError
shows_qs = shows_qs.filter( if not all(isinstance(id, int) for id in locations_id):
category__id__in=category_id) raise ValueError
if not(0 in location_id):
shows_qs = shows_qs.filter(
location__id__in=location_id)
except ObjectDoesNotExist:
return HttpResponseBadRequest(
"Impossible de trouver des résultats correspondant "
"à ces caractéristiques : "
+ "id = " + tirage_id
+ ", catégories = " + categories
+ ", salles = " + locations)
except ValueError: # Contient JSONDecodeError except ValueError: # Contient JSONDecodeError
return HttpResponseBadRequest( return HttpResponseBadRequest(
"Impossible de parser les paramètres donnés : " "Parse error, please ensure the GET parameters have the "
+ "id = " + request.GET.get('id', '') "following types:\n"
+ ", catégories = " + request.GET.get('category', '[0]') "id: int, category: [int], location: [int]\n"
+ ", salles = " + request.GET.get('location', '[0]')) "Data received:\n"
"id = {}, category = {}, locations = {}"
.format(request.GET.get('id', ''),
request.GET.get('category', '[]'),
request.GET.get('location', '[]'))
)
tirage = get_object_or_404(Tirage, id=tirage_id)
shows_qs = tirage.spectacle_set
if categories_id:
shows_qs = shows_qs.filter(category__id__in=categories_id)
if locations_id:
shows_qs = shows_qs.filter(location__id__in=locations_id)
# On convertit les descriptions à envoyer en une liste facilement # On convertit les descriptions à envoyer en une liste facilement
# JSONifiable (il devrait y avoir un moyen plus efficace en # JSONifiable (il devrait y avoir un moyen plus efficace en

View file

@ -48,6 +48,7 @@ INSTALLED_APPS = (
'widget_tweaks', 'widget_tweaks',
'django_js_reverse', 'django_js_reverse',
'custommail', 'custommail',
'djconfig',
) )
MIDDLEWARE_CLASSES = ( MIDDLEWARE_CLASSES = (
@ -61,6 +62,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'djconfig.middleware.DjConfigMiddleware',
) )
ROOT_URLCONF = 'cof.urls' ROOT_URLCONF = 'cof.urls'
@ -79,8 +81,10 @@ TEMPLATES = [
'django.core.context_processors.i18n', 'django.core.context_processors.i18n',
'django.core.context_processors.media', 'django.core.context_processors.media',
'django.core.context_processors.static', 'django.core.context_processors.static',
'djconfig.context_processors.config',
'gestioncof.shared.context_processor', 'gestioncof.shared.context_processor',
'kfet.context_processors.auth', 'kfet.context_processors.auth',
'kfet.context_processors.config',
], ],
}, },
}, },

View file

@ -12,3 +12,9 @@ class KFetConfig(AppConfig):
def ready(self): def ready(self):
import kfet.signals import kfet.signals
self.register_config()
def register_config(self):
import djconfig
from kfet.forms import KFetConfigForm
djconfig.register(KFetConfigForm)

71
kfet/config.py Normal file
View file

@ -0,0 +1,71 @@
# -*- coding: utf-8 -*-
from django.core.exceptions import ValidationError
from django.db import models
from djconfig import config
class KFetConfig(object):
"""kfet app configuration.
Enhance isolation with backend used to store config.
Usable after DjConfig middleware was called.
"""
prefix = 'kfet_'
def __getattr__(self, key):
if key == 'subvention_cof':
# Allows accessing to the reduction as a subvention
# Other reason: backward compatibility
reduction_mult = 1 - self.reduction_cof/100
return (1/reduction_mult - 1) * 100
return getattr(config, self._get_dj_key(key))
def list(self):
"""Get list of kfet app configuration.
Returns:
(key, value) for each configuration entry as list.
"""
# prevent circular imports
from kfet.forms import KFetConfigForm
return [(field.label, getattr(config, name), )
for name, field in KFetConfigForm.base_fields.items()]
def _get_dj_key(self, key):
return '{}{}'.format(self.prefix, key)
def set(self, **kwargs):
"""Update configuration value(s).
Args:
**kwargs: Keyword arguments. Keys must be in kfet config.
Config entries are updated to given values.
"""
# prevent circular imports
from kfet.forms import KFetConfigForm
# get old config
new_cfg = KFetConfigForm().initial
# update to new config
for key, value in kwargs.items():
dj_key = self._get_dj_key(key)
if isinstance(value, models.Model):
new_cfg[dj_key] = value.pk
else:
new_cfg[dj_key] = value
# save new config
cfg_form = KFetConfigForm(new_cfg)
if cfg_form.is_valid():
cfg_form.save()
else:
raise ValidationError(
'Invalid values in kfet_config.set: %(fields)s',
params={'fields': list(cfg_form.errors)})
kfet_config = KFetConfig()

View file

@ -1,26 +1,41 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import (absolute_import, division, from django.core.serializers.json import json, DjangoJSONEncoder
print_function, unicode_literals)
from builtins import *
from channels import Group
from channels.generic.websockets import JsonWebsocketConsumer from channels.generic.websockets import JsonWebsocketConsumer
class KPsul(JsonWebsocketConsumer):
# Set to True if you want them, else leave out class DjangoJsonWebsocketConsumer(JsonWebsocketConsumer):
strict_ordering = False """Custom Json Websocket Consumer.
slight_ordering = False
def connection_groups(self, **kwargs): Encode to JSON with DjangoJSONEncoder.
return ['kfet.kpsul']
"""
@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): 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): class KPsul(PermConsumerMixin, DjangoJsonWebsocketConsumer):
pass groups = ['kfet.kpsul']
perms_connect = ['kfet.is_team']

View file

@ -1,11 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django.contrib.auth.context_processors import PermWrapper from django.contrib.auth.context_processors import PermWrapper
from kfet.config import kfet_config
def auth(request): def auth(request):
if hasattr(request, 'real_user'): if hasattr(request, 'real_user'):
return { return {
@ -13,3 +12,7 @@ def auth(request):
'perms': PermWrapper(request.real_user), 'perms': PermWrapper(request.real_user),
} }
return {} return {}
def config(request):
return {'kfet_config': kfet_config}

View file

@ -1,6 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from datetime import timedelta
from decimal import Decimal from decimal import Decimal
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.validators import MinLengthValidator from django.core.validators import MinLengthValidator
@ -8,12 +10,16 @@ from django.contrib.auth.models import User, Group, Permission
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.forms import modelformset_factory from django.forms import modelformset_factory
from django.utils import timezone from django.utils import timezone
from djconfig.forms import ConfigForm
from kfet.models import ( from kfet.models import (
Account, Checkout, Article, OperationGroup, Operation, Account, Checkout, Article, OperationGroup, Operation,
CheckoutStatement, ArticleCategory, Settings, AccountNegative, Transfer, CheckoutStatement, ArticleCategory, AccountNegative, Transfer,
TransferGroup, Supplier) TransferGroup, Supplier)
from gestioncof.models import CofProfile from gestioncof.models import CofProfile
# ----- # -----
# Widgets # Widgets
# ----- # -----
@ -389,40 +395,46 @@ class AddcostForm(forms.Form):
self.cleaned_data['amount'] = 0 self.cleaned_data['amount'] = 0
super(AddcostForm, self).clean() super(AddcostForm, self).clean()
# ----- # -----
# Settings forms # Settings forms
# ----- # -----
class SettingsForm(forms.ModelForm):
class Meta:
model = Settings
fields = ['value_decimal', 'value_account', 'value_duration']
def clean(self): class KFetConfigForm(ConfigForm):
name = self.instance.name
value_decimal = self.cleaned_data.get('value_decimal')
value_account = self.cleaned_data.get('value_account')
value_duration = self.cleaned_data.get('value_duration')
type_decimal = ['SUBVENTION_COF', 'ADDCOST_AMOUNT', 'OVERDRAFT_AMOUNT'] kfet_reduction_cof = forms.DecimalField(
type_account = ['ADDCOST_FOR'] label='Réduction COF', initial=Decimal('20'),
type_duration = ['OVERDRAFT_DURATION', 'CANCEL_DURATION'] max_digits=6, decimal_places=2,
help_text="Réduction, à donner en pourcentage, appliquée lors d'un "
"achat par un-e membre du COF sur le montant en euros.",
)
kfet_addcost_amount = forms.DecimalField(
label='Montant de la majoration (en €)', initial=Decimal('0'),
required=False,
max_digits=6, decimal_places=2,
)
kfet_addcost_for = forms.ModelChoiceField(
label='Destinataire de la majoration', initial=None, required=False,
help_text='Laissez vide pour désactiver la majoration.',
queryset=(Account.objects
.select_related('cofprofile', 'cofprofile__user')
.all()),
)
kfet_overdraft_duration = forms.DurationField(
label='Durée du découvert autorisé par défaut',
initial=timedelta(days=1),
)
kfet_overdraft_amount = forms.DecimalField(
label='Montant du découvert autorisé par défaut (en €)',
initial=Decimal('20'),
max_digits=6, decimal_places=2,
)
kfet_cancel_duration = forms.DurationField(
label='Durée pour annuler une commande sans mot de passe',
initial=timedelta(minutes=5),
)
self.cleaned_data['name'] = name
if name in type_decimal:
if not value_decimal:
raise ValidationError('Renseignez une valeur décimale')
self.cleaned_data['value_account'] = None
self.cleaned_data['value_duration'] = None
elif name in type_account:
self.cleaned_data['value_decimal'] = None
self.cleaned_data['value_duration'] = None
elif name in type_duration:
if not value_duration:
raise ValidationError('Renseignez une durée')
self.cleaned_data['value_decimal'] = None
self.cleaned_data['value_account'] = None
super(SettingsForm, self).clean()
class FilterHistoryForm(forms.Form): class FilterHistoryForm(forms.Form):
checkouts = forms.ModelMultipleChoiceField(queryset = Checkout.objects.all()) checkouts = forms.ModelMultipleChoiceField(queryset = Checkout.objects.all())

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('kfet', '0052_category_addcost'),
]
operations = [
migrations.AlterField(
model_name='account',
name='created_at',
field=models.DateTimeField(default=django.utils.timezone.now),
),
]

View file

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
from kfet.forms import KFetConfigForm
def adapt_settings(apps, schema_editor):
Settings = apps.get_model('kfet', 'Settings')
db_alias = schema_editor.connection.alias
obj = Settings.objects.using(db_alias)
cfg = {}
def try_get(new, old, type_field):
try:
value = getattr(obj.get(name=old), type_field)
cfg[new] = value
except Settings.DoesNotExist:
pass
try:
subvention = obj.get(name='SUBVENTION_COF').value_decimal
subvention_mult = 1 + subvention/100
reduction = (1 - 1/subvention_mult) * 100
cfg['kfet_reduction_cof'] = reduction
except Settings.DoesNotExist:
pass
try_get('kfet_addcost_amount', 'ADDCOST_AMOUNT', 'value_decimal')
try_get('kfet_addcost_for', 'ADDCOST_FOR', 'value_account')
try_get('kfet_overdraft_duration', 'OVERDRAFT_DURATION', 'value_duration')
try_get('kfet_overdraft_amount', 'OVERDRAFT_AMOUNT', 'value_decimal')
try_get('kfet_cancel_duration', 'CANCEL_DURATION', 'value_duration')
cfg_form = KFetConfigForm(initial=cfg)
if cfg_form.is_valid():
cfg_form.save()
class Migration(migrations.Migration):
dependencies = [
('kfet', '0053_created_at'),
('djconfig', '0001_initial'),
]
operations = [
migrations.RunPython(adapt_settings),
migrations.RemoveField(
model_name='settings',
name='value_account',
),
migrations.DeleteModel(
name='Settings',
),
]

View file

@ -1,12 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django.db import models from django.db import models
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.contrib.auth.models import User from django.contrib.auth.models import User
from gestioncof.models import CofProfile from gestioncof.models import CofProfile
@ -15,11 +10,12 @@ from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from django.db import transaction from django.db import transaction
from django.db.models import F from django.db.models import F
from django.core.cache import cache from datetime import date
from datetime import date, timedelta
import re import re
import hashlib import hashlib
from kfet.config import kfet_config
def choices_length(choices): def choices_length(choices):
return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0) return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0)
@ -41,7 +37,7 @@ class Account(models.Model):
max_digits = 6, decimal_places = 2, max_digits = 6, decimal_places = 2,
default = 0) default = 0)
is_frozen = models.BooleanField("est gelé", default = False) is_frozen = models.BooleanField("est gelé", default = False)
created_at = models.DateTimeField(auto_now_add = True, null = True) created_at = models.DateTimeField(default=timezone.now)
# Optional # Optional
PROMO_CHOICES = [(r,r) for r in range(1980, date.today().year+1)] PROMO_CHOICES = [(r,r) for r in range(1980, date.today().year+1)]
promo = models.IntegerField( promo = models.IntegerField(
@ -85,7 +81,7 @@ class Account(models.Model):
# Propriétés supplémentaires # Propriétés supplémentaires
@property @property
def real_balance(self): 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 - self.negative.balance_offset
return self.balance return self.balance
@ -113,8 +109,8 @@ class Account(models.Model):
return data return data
def perms_to_perform_operation(self, amount): def perms_to_perform_operation(self, amount):
overdraft_duration_max = Settings.OVERDRAFT_DURATION() overdraft_duration_max = kfet_config.overdraft_duration
overdraft_amount_max = Settings.OVERDRAFT_AMOUNT() overdraft_amount_max = kfet_config.overdraft_amount
perms = set() perms = set()
stop_ope = False stop_ope = False
# Checking is cash account # Checking is cash account
@ -214,6 +210,29 @@ class Account(models.Model):
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
pass 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): class UserHasAccount(Exception):
def __init__(self, trigramme): def __init__(self, trigramme):
self.trigramme = trigramme self.trigramme = trigramme
@ -632,116 +651,6 @@ class GlobalPermissions(models.Model):
('special_add_account', "Créer un compte avec une balance initiale") ('special_add_account', "Créer un compte avec une balance initiale")
) )
class Settings(models.Model):
name = models.CharField(
max_length = 45,
unique = True,
db_index = True)
value_decimal = models.DecimalField(
max_digits = 6, decimal_places = 2,
blank = True, null = True, default = None)
value_account = models.ForeignKey(
Account, on_delete = models.PROTECT,
blank = True, null = True, default = None)
value_duration = models.DurationField(
blank = True, null = True, default = None)
@staticmethod
def setting_inst(name):
return Settings.objects.get(name=name)
@staticmethod
def SUBVENTION_COF():
subvention_cof = cache.get('SUBVENTION_COF')
if subvention_cof:
return subvention_cof
try:
subvention_cof = Settings.setting_inst("SUBVENTION_COF").value_decimal
except Settings.DoesNotExist:
subvention_cof = 0
cache.set('SUBVENTION_COF', subvention_cof)
return subvention_cof
@staticmethod
def ADDCOST_AMOUNT():
try:
return Settings.setting_inst("ADDCOST_AMOUNT").value_decimal
except Settings.DoesNotExist:
return 0
@staticmethod
def ADDCOST_FOR():
try:
return Settings.setting_inst("ADDCOST_FOR").value_account
except Settings.DoesNotExist:
return None;
@staticmethod
def OVERDRAFT_DURATION():
overdraft_duration = cache.get('OVERDRAFT_DURATION')
if overdraft_duration:
return overdraft_duration
try:
overdraft_duration = Settings.setting_inst("OVERDRAFT_DURATION").value_duration
except Settings.DoesNotExist:
overdraft_duration = timedelta()
cache.set('OVERDRAFT_DURATION', overdraft_duration)
return overdraft_duration
@staticmethod
def OVERDRAFT_AMOUNT():
overdraft_amount = cache.get('OVERDRAFT_AMOUNT')
if overdraft_amount:
return overdraft_amount
try:
overdraft_amount = Settings.setting_inst("OVERDRAFT_AMOUNT").value_decimal
except Settings.DoesNotExist:
overdraft_amount = 0
cache.set('OVERDRAFT_AMOUNT', overdraft_amount)
return overdraft_amount
@staticmethod
def CANCEL_DURATION():
cancel_duration = cache.get('CANCEL_DURATION')
if cancel_duration:
return cancel_duration
try:
cancel_duration = Settings.setting_inst("CANCEL_DURATION").value_duration
except Settings.DoesNotExist:
cancel_duration = timedelta()
cache.set('CANCEL_DURATION', cancel_duration)
return cancel_duration
@staticmethod
def create_missing():
s, created = Settings.objects.get_or_create(name='SUBVENTION_COF')
if created:
s.value_decimal = 25
s.save()
s, created = Settings.objects.get_or_create(name='ADDCOST_AMOUNT')
if created:
s.value_decimal = 0.5
s.save()
s, created = Settings.objects.get_or_create(name='ADDCOST_FOR')
s, created = Settings.objects.get_or_create(name='OVERDRAFT_DURATION')
if created:
s.value_duration = timedelta(days=1) # 24h
s.save()
s, created = Settings.objects.get_or_create(name='OVERDRAFT_AMOUNT')
if created:
s.value_decimal = 20
s.save()
s, created = Settings.objects.get_or_create(name='CANCEL_DURATION')
if created:
s.value_duration = timedelta(minutes=5) # 5min
s.save()
@staticmethod
def empty_cache():
cache.delete_many([
'SUBVENTION_COF', 'OVERDRAFT_DURATION', 'OVERDRAFT_AMOUNT',
'CANCEL_DURATION', 'ADDCOST_AMOUNT', 'ADDCOST_FOR',
])
class GenericTeamToken(models.Model): class GenericTeamToken(models.Model):
token = models.CharField(max_length = 50, unique = True) token = models.CharField(max_length = 50, unique = True)

View file

@ -86,6 +86,16 @@ textarea {
color:#FFF; color:#FFF;
} }
.buttons .nav-pills > li > a {
border-radius:0;
border:1px solid rgba(200,16,46,0.9);
}
.buttons .nav-pills > li.active > a {
background-color:rgba(200,16,46,0.9);
background-clip:padding-box;
}
.row-page-header { .row-page-header {
background-color:rgba(200,16,46,1); background-color:rgba(200,16,46,1);
color:#FFF; color:#FFF;

View file

@ -16,8 +16,8 @@
</div> </div>
<div class="block"> <div class="block">
<div class="line"><b>Découvert autorisé par défaut</b></div> <div class="line"><b>Découvert autorisé par défaut</b></div>
<div class="line">Montant: {{ settings.overdraft_amount }}€</div> <div class="line">Montant: {{ kfet_config.overdraft_amount }}€</div>
<div class="line">Pendant: {{ settings.overdraft_duration }}</div> <div class="line">Pendant: {{ kfet_config.overdraft_duration }}</div>
</div> </div>
</div> </div>
{% if perms.kfet.change_settings %} {% if perms.kfet.change_settings %}

View file

@ -23,11 +23,11 @@
$(document).ready(function() { $(document).ready(function() {
var stat_last = new StatsGroup( var stat_last = new StatsGroup(
"{% url 'kfet.account.stat.operation.list' trigramme=account.trigramme %}", "{% url 'kfet.account.stat.operation.list' trigramme=account.trigramme %}",
$("#stat_last"), $("#stat_last")
); );
var stat_balance = new StatsGroup( var stat_balance = new StatsGroup(
"{% url 'kfet.account.stat.balance.list' trigramme=account.trigramme %}", "{% url 'kfet.account.stat.balance.list' trigramme=account.trigramme %}",
$("#stat_balance"), $("#stat_balance")
); );
}); });
</script> </script>
@ -61,45 +61,40 @@ $(document).ready(function() {
<div class="col-sm-8 col-md-9 col-content-right"> <div class="col-sm-8 col-md-9 col-content-right">
{% include "kfet/base_messages.html" %} {% include "kfet/base_messages.html" %}
<div class="content-right"> <div class="content-right">
{% if addcosts %}
<div class="content-right-block">
<h2>Gagné des majorations</h2>
<div>
<ul>
{% for addcost in addcosts %}
<li>{{ addcost.date|date:'l j F' }}: +{{ addcost.sum_addcosts }}€</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
{% if account.user == request.user %}
<div class="content-right-block content-right-block-transparent">
<h2>Statistiques</h2>
<div class="row">
<div class="col-sm-12 nopadding">
<div class="panel-md-margin">
<h3>Ma balance</h3>
<div id="stat_balance"></div>
</div>
</div>
</div><!-- /row -->
<div class="row">
<div class="col-sm-12 nopadding">
<div class="panel-md-margin">
<h3>Ma consommation</h3>
<div id="stat_last"></div>
</div>
</div>
</div><!-- /row -->
</div>
{% endif %}
<div class="content-right-block"> <div class="content-right-block">
<h2>Historique</h2> <div class="col-sm-12 nopadding">
<div id="history"> {% if account.user == request.user %}
</div> <div class='tab-content'>
</div> <div class="tab-pane fade in active" id="tab_stats">
</div> <h2>Statistiques</h2>
<div class="panel-md-margin">
<h3>Ma balance</h3>
<div id="stat_balance"></div>
<h3>Ma consommation</h3>
<div id="stat_last"></div>
</div>
</div>
<div class="tab-pane fade" id="tab_history">
{% endif %}
{% if addcosts %}
<h2>Gagné des majorations</h2>
<div>
<ul>
{% for addcost in addcosts %}
<li>{{ addcost.date|date:'l j F' }}: +{{ addcost.sum_addcosts }}€</li>
{% endfor %}
</ul>
</div>
{% endif %}
<h2>Historique</h2>
<div id="history"></div>
{% if account.user == request.user %}
</div>
</div><!-- tab-content -->
{% endif %}
</div><!-- col-sm-12 -->
</div><!-- content-right-block -->
</div><!-- content-right-->
</div> </div>
</div> </div>

View file

@ -20,40 +20,42 @@
<div class="content-right"> <div class="content-right">
<div class="content-right-block"> <div class="content-right-block">
<h2>Carte</h2> <h2>Carte</h2>
<div class="column-row"> <div class="column-row">
<div class="column-sm-1 column-md-2 column-lg-3"> <div class="column-sm-1 column-md-2 column-lg-3">
<div class="unbreakable carte-inverted"> <div class="unbreakable carte-inverted">
<h3>Pressions du moment</h3> {% if pressions %}
<ul class="carte"> <h3>Pressions du moment</h3>
{% for article in pressions %} <ul class="carte">
<li class="carte-line"> {% for article in pressions %}
<div class="filler"></div> <li class="carte-line">
<span class="carte-label">{{ article.name }}</span> <div class="filler"></div>
<span class="carte-ukf">{{ article.price | ukf:False}} UKF</span> <span class="carte-label">{{ article.name }}</span>
</li> <span class="carte-ukf">{{ article.price | ukf:False}} UKF</span>
{% endfor %} </li>
</ul>
</div><!-- endblock unbreakable -->
{% for article in articles %}
{% ifchanged article.category %}
{% if not forloop.first %}
</ul>
</div><!-- endblock unbreakable -->
{% endif %}
<div class="unbreakable">
<h3>{{ article.category.name }}</h3>
<ul class="carte">
{% endifchanged %}
<li class="carte-line">
<div class="filler"></div>
<span class="carte-label">{{ article.name }}</span>
<span class="carte-ukf">{{ article.price | ukf:False}} UKF</span>
</li>
{% if foorloop.last %}
</ul>
</div><!-- endblock unbreakable -->
{% endif %}
{% endfor %} {% endfor %}
</ul>
{% endif %}
</div><!-- endblock unbreakable -->
{% for article in articles %}
{% ifchanged article.category %}
{% if not forloop.first %}
</ul>
</div><!-- endblock unbreakable -->
{% endif %}
<div class="unbreakable">
<h3>{{ article.category.name }}</h3>
<ul class="carte">
{% endifchanged %}
<li class="carte-line">
<div class="filler"></div>
<span class="carte-label">{{ article.name }}</span>
<span class="carte-ukf">{{ article.price | ukf:False}} UKF</span>
</li>
{% if forloop.last %}
</ul>
</div><!-- endblock unbreakable -->
{% endif %}
{% endfor %}
</div> </div>
</div> </div>
</div> </div>

View file

@ -246,7 +246,7 @@ $(document).ready(function() {
var reduc_divisor = 1; var reduc_divisor = 1;
if (kpsul.account_manager.account.is_cof) if (kpsul.account_manager.account.is_cof)
reduc_divisor = 1 + Config.get('subvention_cof') / 100; reduc_divisor = 1 + Config.get('subvention_cof') / 100;
return amount_euro / reduc_divisor; return (amount_euro / reduc_divisor).toFixed(2);
} }
function addPurchase(article, nb) { function addPurchase(article, nb) {
@ -261,7 +261,7 @@ $(document).ready(function() {
} }
}); });
if (!existing) { if (!existing) {
var amount_euro = amountEuroPurchase(article, nb).toFixed(2); var amount_euro = amountEuroPurchase(article, nb);
var index = addPurchaseToFormset(article.id, nb, amount_euro); var index = addPurchaseToFormset(article.id, nb, amount_euro);
var article_basket_html = $(item_basket_default_html); var article_basket_html = $(item_basket_default_html);
article_basket_html article_basket_html

View file

@ -36,6 +36,12 @@
</div> </div>
</div> </div>
<div class="buttons"> <div class="buttons">
{% if account.user == request.user %}
<ul class='nav nav-pills nav-justified'>
<li class="active"><a data-toggle="pill" href="#tab_stats">Statistiques</a></li>
<li><a data-toggle="pill" href="#tab_history">Historique</a></li>
</ul>
{% endif %}
<a class="btn btn-primary btn-lg" href="{% url 'kfet.account.update' account.trigramme %}"> <a class="btn btn-primary btn-lg" href="{% url 'kfet.account.update' account.trigramme %}">
Modifier Modifier
</a> </a>

View file

@ -5,20 +5,42 @@
{% block content %} {% block content %}
{% include 'kfet/base_messages.html' %} <div class="row">
<table> <div class="col-sm-4 col-md-3 col-content-left">
<tr> <div class="content-left">
<td></td> <div class="buttons">
<td>Nom</td> <a class="btn btn-primary btn-lg" href="{% url 'kfet.settings.update' %}">
<td>Valeur</td> Modifier
</tr> </a>
{% for setting in settings %} </div>
<tr> </div>
<td><a href="{% url 'kfet.settings.update' setting.pk %}">Modifier</a></td> </div>
<td>{{ setting.name }}</td> <div class="col-sm-8 col-md-9 col-content-right">
<td>{% firstof setting.value_decimal setting.value_duration setting.value_account %}</td> {% include 'kfet/base_messages.html' %}
</tr> <div class="content-right">
{% endfor %} <div class="content-right-block">
</table> <h2>Valeurs</h2>
<div class="table-responsive">
<table class="table table-condensed">
<thead>
<tr>
<td>Nom</td>
<td>Valeur</td>
</tr>
</thead>
<tbody>
{% for key, value in kfet_config.list %}
<tr>
<td>{{ key }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View file

@ -1,14 +1,25 @@
{% extends 'kfet/base.html' %} {% extends 'kfet/base.html' %}
{% block title %}Modification de {{ settings.name }}{% endblock %} {% block title %}Modification des paramètres{% endblock %}
{% block content-header-title %}Modification de {{ settings.name }}{% endblock %} {% block content-header-title %}Modification des paramètres{% endblock %}
{% block content %} {% block content %}
<form action="" method="post"> {% include "kfet/base_messages.html" %}
{% csrf_token %}
{{ form.as_p }} <div class="row form-only">
<input type="submit" value="Mettre à jour"> <div class="col-sm-12 col-md-8 col-md-offset-2">
</form> <div class="content-form">
<form submit="" method="post" class="form-horizontal">
{% csrf_token %}
{% include 'kfet/form_snippet.html' with form=form %}
{% if not perms.kfet.change_settings %}
{% include 'kfet/form_authentication_snippet.html' %}
{% endif %}
{% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %}
</form>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View file

@ -3,10 +3,11 @@
from django import template from django import template
from django.utils.html import escape from django.utils.html import escape
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from kfet.models import Settings
from math import floor from math import floor
import re import re
from kfet.config import kfet_config
register = template.Library() register = template.Library()
@ -40,5 +41,5 @@ def highlight_clipper(clipper, q):
@register.filter() @register.filter()
def ukf(balance, is_cof): def ukf(balance, is_cof):
grant = is_cof and (1 + Settings.SUBVENTION_COF() / 100) or 1 grant = is_cof and (1 + kfet_config.subvention_cof / 100) or 1
return floor(balance * 10 * grant) return floor(balance * 10 * grant)

0
kfet/tests/__init__.py Normal file
View file

56
kfet/tests/test_config.py Normal file
View file

@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
from decimal import Decimal
from django.test import TestCase
from django.utils import timezone
import djconfig
from gestioncof.models import User
from kfet.config import kfet_config
from kfet.models import Account
class ConfigTest(TestCase):
"""Tests suite for kfet configuration."""
def setUp(self):
# load configuration as in djconfig middleware
djconfig.reload_maybe()
def test_get(self):
self.assertTrue(hasattr(kfet_config, 'subvention_cof'))
def test_subvention_cof(self):
reduction_cof = Decimal('20')
subvention_cof = Decimal('25')
kfet_config.set(reduction_cof=reduction_cof)
self.assertEqual(kfet_config.subvention_cof, subvention_cof)
def test_set_decimal(self):
"""Test field of decimal type."""
reduction_cof = Decimal('10')
# IUT
kfet_config.set(reduction_cof=reduction_cof)
# check
self.assertEqual(kfet_config.reduction_cof, reduction_cof)
def test_set_modelinstance(self):
"""Test field of model instance type."""
user = User.objects.create(username='foo_user')
account = Account.objects.create(trigramme='FOO',
cofprofile=user.profile)
# IUT
kfet_config.set(addcost_for=account)
# check
self.assertEqual(kfet_config.addcost_for, account)
def test_set_duration(self):
"""Test field of duration type."""
cancel_duration = timezone.timedelta(days=2, hours=4)
# IUT
kfet_config.set(cancel_duration=cancel_duration)
# check
self.assertEqual(kfet_config.cancel_duration, cancel_duration)

View file

@ -5,7 +5,7 @@ from unittest.mock import patch
from django.test import TestCase, Client from django.test import TestCase, Client
from django.contrib.auth.models import User, Permission from django.contrib.auth.models import User, Permission
from .models import Account, Article, ArticleCategory from kfet.models import Account, Article, ArticleCategory
class TestStats(TestCase): class TestStats(TestCase):

View file

@ -188,7 +188,7 @@ urlpatterns = [
permission_required('kfet.change_settings') permission_required('kfet.change_settings')
(views.SettingsList.as_view()), (views.SettingsList.as_view()),
name='kfet.settings'), name='kfet.settings'),
url(r'^settings/(?P<pk>\d+)/edit$', url(r'^settings/edit$',
permission_required('kfet.change_settings') permission_required('kfet.change_settings')
(views.SettingsUpdate.as_view()), (views.SettingsUpdate.as_view()),
name='kfet.settings.update'), name='kfet.settings.update'),

View file

@ -6,7 +6,7 @@ from urllib.parse import urlencode
from django.shortcuts import render, get_object_or_404, redirect from django.shortcuts import render, get_object_or_404, redirect
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.cache import cache from django.core.cache import cache
from django.views.generic import ListView, DetailView, TemplateView from django.views.generic import ListView, DetailView, TemplateView, FormView
from django.views.generic.detail import BaseDetailView from django.views.generic.detail import BaseDetailView
from django.views.generic.edit import CreateView, UpdateView from django.views.generic.edit import CreateView, UpdateView
from django.core.urlresolvers import reverse, reverse_lazy from django.core.urlresolvers import reverse, reverse_lazy
@ -24,9 +24,11 @@ from django.utils import timezone
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from gestioncof.models import CofProfile from gestioncof.models import CofProfile
from kfet.config import kfet_config
from kfet.decorators import teamkfet_required from kfet.decorators import teamkfet_required
from kfet.models import ( from kfet.models import (
Account, Checkout, Article, Settings, AccountNegative, Account, Checkout, Article, AccountNegative,
CheckoutStatement, GenericTeamToken, Supplier, SupplierArticle, Inventory, CheckoutStatement, GenericTeamToken, Supplier, SupplierArticle, Inventory,
InventoryArticle, Order, OrderArticle, Operation, OperationGroup, InventoryArticle, Order, OrderArticle, Operation, OperationGroup,
TransferGroup, Transfer, ArticleCategory) TransferGroup, Transfer, ArticleCategory)
@ -37,9 +39,9 @@ from kfet.forms import (
GroupForm, CheckoutForm, CheckoutRestrictForm, CheckoutStatementCreateForm, GroupForm, CheckoutForm, CheckoutRestrictForm, CheckoutStatementCreateForm,
CheckoutStatementUpdateForm, ArticleForm, ArticleRestrictForm, CheckoutStatementUpdateForm, ArticleForm, ArticleRestrictForm,
KPsulOperationGroupForm, KPsulAccountForm, KPsulCheckoutForm, KPsulOperationGroupForm, KPsulAccountForm, KPsulCheckoutForm,
KPsulOperationFormSet, AddcostForm, FilterHistoryForm, SettingsForm, KPsulOperationFormSet, AddcostForm, FilterHistoryForm,
TransferFormSet, InventoryArticleForm, OrderArticleForm, TransferFormSet, InventoryArticleForm, OrderArticleForm,
OrderArticleToInventoryForm, CategoryForm OrderArticleToInventoryForm, CategoryForm, KFetConfigForm
) )
from collections import defaultdict from collections import defaultdict
from kfet import consumers from kfet import consumers
@ -378,8 +380,8 @@ def account_create_ajax(request, username=None, login_clipper=None,
@login_required @login_required
def account_read(request, trigramme): def account_read(request, trigramme):
try: try:
account = Account.objects.select_related('negative')\ account = (Account.objects.select_related('negative')
.get(trigramme=trigramme) .get(trigramme=trigramme))
except Account.DoesNotExist: except Account.DoesNotExist:
raise Http404 raise Http404
@ -392,7 +394,6 @@ def account_read(request, trigramme):
export_keys = ['id', 'trigramme', 'first_name', 'last_name', 'name', export_keys = ['id', 'trigramme', 'first_name', 'last_name', 'name',
'email', 'is_cof', 'promo', 'balance', 'is_frozen', 'email', 'is_cof', 'promo', 'balance', 'is_frozen',
'departement', 'nickname'] 'departement', 'nickname']
print(account.first_name)
data = {k: getattr(account, k) for k in export_keys} data = {k: getattr(account, k) for k in export_keys}
return JsonResponse(data) return JsonResponse(data)
@ -408,7 +409,6 @@ def account_read(request, trigramme):
return render(request, "kfet/account_read.html", { return render(request, "kfet/account_read.html", {
'account': account, 'account': account,
'addcosts': addcosts, 'addcosts': addcosts,
'settings': {'subvention_cof': Settings.SUBVENTION_COF()},
}) })
@ -587,10 +587,6 @@ class AccountNegativeList(ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super(AccountNegativeList, self).get_context_data(**kwargs) context = super(AccountNegativeList, self).get_context_data(**kwargs)
context['settings'] = {
'overdraft_amount': Settings.OVERDRAFT_AMOUNT(),
'overdraft_duration': Settings.OVERDRAFT_DURATION(),
}
negs_sum = (AccountNegative.objects negs_sum = (AccountNegative.objects
.exclude(account__trigramme='#13') .exclude(account__trigramme='#13')
.aggregate( .aggregate(
@ -961,13 +957,14 @@ def kpsul(request):
data['operation_formset'] = operation_formset data['operation_formset'] = operation_formset
return render(request, 'kfet/kpsul.html', data) return render(request, 'kfet/kpsul.html', data)
@teamkfet_required @teamkfet_required
def kpsul_get_settings(request): def kpsul_get_settings(request):
addcost_for = Settings.ADDCOST_FOR() addcost_for = kfet_config.addcost_for
data = { data = {
'subvention_cof': Settings.SUBVENTION_COF(), 'subvention_cof': kfet_config.subvention_cof,
'addcost_for' : addcost_for and addcost_for.trigramme or '', 'addcost_for': addcost_for and addcost_for.trigramme or '',
'addcost_amount': Settings.ADDCOST_AMOUNT(), 'addcost_amount': kfet_config.addcost_amount,
} }
return JsonResponse(data) return JsonResponse(data)
@ -991,15 +988,15 @@ def kpsul_update_addcost(request):
trigramme = addcost_form.cleaned_data['trigramme'] trigramme = addcost_form.cleaned_data['trigramme']
account = trigramme and Account.objects.get(trigramme=trigramme) or None account = trigramme and Account.objects.get(trigramme=trigramme) or None
Settings.objects.filter(name='ADDCOST_FOR').update(value_account=account) amount = addcost_form.cleaned_data['amount']
(Settings.objects.filter(name='ADDCOST_AMOUNT')
.update(value_decimal=addcost_form.cleaned_data['amount'])) kfet_config.set(addcost_for=account,
cache.delete('ADDCOST_FOR') addcost_amount=amount)
cache.delete('ADDCOST_AMOUNT')
data = { data = {
'addcost': { 'addcost': {
'for': trigramme and account.trigramme or None, 'for': account and account.trigramme or None,
'amount': addcost_form.cleaned_data['amount'], 'amount': amount,
} }
} }
consumers.KPsul.group_send('kfet.kpsul', data) consumers.KPsul.group_send('kfet.kpsul', data)
@ -1043,10 +1040,10 @@ def kpsul_perform_operations(request):
operations = operation_formset.save(commit=False) operations = operation_formset.save(commit=False)
# Retrieving COF grant # Retrieving COF grant
cof_grant = Settings.SUBVENTION_COF() cof_grant = kfet_config.subvention_cof
# Retrieving addcosts data # Retrieving addcosts data
addcost_amount = Settings.ADDCOST_AMOUNT() addcost_amount = kfet_config.addcost_amount
addcost_for = Settings.ADDCOST_FOR() addcost_for = kfet_config.addcost_for
# Initializing vars # Initializing vars
required_perms = set() # Required perms to perform all operations required_perms = set() # Required perms to perform all operations
@ -1122,22 +1119,15 @@ def kpsul_perform_operations(request):
with transaction.atomic(): with transaction.atomic():
# If not cash account, # If not cash account,
# saving account's balance and adding to Negative if not in # saving account's balance and adding to Negative if not in
if not operationgroup.on_acc.is_cash: on_acc = operationgroup.on_acc
Account.objects.filter(pk=operationgroup.on_acc.pk).update( if not on_acc.is_cash:
balance=F('balance') + operationgroup.amount) (
operationgroup.on_acc.refresh_from_db() Account.objects
if operationgroup.on_acc.balance < 0: .filter(pk=on_acc.pk)
if hasattr(operationgroup.on_acc, 'negative'): .update(balance=F('balance') + operationgroup.amount)
if not operationgroup.on_acc.negative.start: )
operationgroup.on_acc.negative.start = timezone.now() on_acc.refresh_from_db()
operationgroup.on_acc.negative.save() on_acc.update_negative()
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()
# Updating checkout's balance # Updating checkout's balance
if to_checkout_balance: if to_checkout_balance:
@ -1272,8 +1262,9 @@ def kpsul_cancel_operations(request):
opes = [] # Pas déjà annulée opes = [] # Pas déjà annulée
transfers = [] transfers = []
required_perms = set() required_perms = set()
stop_all = False stop_all = False
cancel_duration = Settings.CANCEL_DURATION() cancel_duration = kfet_config.cancel_duration
# Modifs à faire sur les balances des comptes # Modifs à faire sur les balances des comptes
to_accounts_balances = defaultdict(lambda: 0) to_accounts_balances = defaultdict(lambda: 0)
# ------ sur les montants des groupes d'opé # ------ sur les montants des groupes d'opé
@ -1282,6 +1273,7 @@ def kpsul_cancel_operations(request):
to_checkouts_balances = defaultdict(lambda: 0) to_checkouts_balances = defaultdict(lambda: 0)
# ------ sur les stocks d'articles # ------ sur les stocks d'articles
to_articles_stocks = defaultdict(lambda: 0) to_articles_stocks = defaultdict(lambda: 0)
for ope in opes_all: for ope in opes_all:
if ope.canceled_at: if ope.canceled_at:
# Opération déjà annulée, va pour un warning en Response # Opération déjà annulée, va pour un warning en Response
@ -1391,8 +1383,15 @@ def kpsul_cancel_operations(request):
.update(canceled_by=canceled_by, canceled_at=canceled_at)) .update(canceled_by=canceled_by, canceled_at=canceled_at))
for account in to_accounts_balances: 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: for checkout in to_checkouts_balances:
Checkout.objects.filter(pk=checkout.pk).update( Checkout.objects.filter(pk=checkout.pk).update(
balance=F('balance') + to_checkouts_balances[checkout]) balance=F('balance') + to_checkouts_balances[checkout])
@ -1651,34 +1650,28 @@ def kpsul_articles_data(request):
return JsonResponse(data) return JsonResponse(data)
@teamkfet_required @teamkfet_required
def history(request): def history(request):
data = { data = {
'filter_form': FilterHistoryForm(), 'filter_form': FilterHistoryForm(),
'settings': {
'subvention_cof': Settings.SUBVENTION_COF(),
}
} }
return render(request, 'kfet/history.html', data) return render(request, 'kfet/history.html', data)
# ----- # -----
# Settings views # Settings views
# ----- # -----
class SettingsList(ListView):
model = Settings class SettingsList(TemplateView):
context_object_name = 'settings'
template_name = 'kfet/settings.html' template_name = 'kfet/settings.html'
def get_context_data(self, **kwargs):
Settings.create_missing()
return super(SettingsList, self).get_context_data(**kwargs)
class SettingsUpdate(SuccessMessageMixin, UpdateView): class SettingsUpdate(SuccessMessageMixin, FormView):
model = Settings form_class = KFetConfigForm
form_class = SettingsForm
template_name = 'kfet/settings_update.html' template_name = 'kfet/settings_update.html'
success_message = 'Paramètre %(name)s mis à jour' success_message = 'Paramètres mis à jour'
success_url = reverse_lazy('kfet.settings') success_url = reverse_lazy('kfet.settings')
def form_valid(self, form): def form_valid(self, form):
@ -1686,9 +1679,9 @@ class SettingsUpdate(SuccessMessageMixin, UpdateView):
if not self.request.user.has_perm('kfet.change_settings'): if not self.request.user.has_perm('kfet.change_settings'):
form.add_error(None, 'Permission refusée') form.add_error(None, 'Permission refusée')
return self.form_invalid(form) return self.form_invalid(form)
# Creating form.save()
Settings.empty_cache() return super().form_valid(form)
return super(SettingsUpdate, self).form_valid(form)
# ----- # -----
# Transfer views # Transfer views

View file

@ -3,6 +3,7 @@ Django==1.8.*
django-autocomplete-light==2.3.3 django-autocomplete-light==2.3.3
django-autoslug==1.9.3 django-autoslug==1.9.3
django-cas-ng==3.5.5 django-cas-ng==3.5.5
django-djconfig==0.5.3
django-grappelli==2.8.1 django-grappelli==2.8.1
django-recaptcha==1.0.5 django-recaptcha==1.0.5
mysqlclient==1.3.7 mysqlclient==1.3.7
@ -11,14 +12,14 @@ six==1.10.0
unicodecsv==0.14.1 unicodecsv==0.14.1
icalendar==3.10 icalendar==3.10
django-bootstrap-form==3.2.1 django-bootstrap-form==3.2.1
asgiref==0.14.0 asgiref==1.1.1
daphne==0.14.3 daphne==1.2.0
asgi-redis==0.14.0 asgi-redis==1.3.0
statistics==1.0.3.5 statistics==1.0.3.5
future==0.15.2 future==0.15.2
django-widget-tweaks==1.4.1 django-widget-tweaks==1.4.1
git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail git+https://git.eleves.ens.fr/cof-geek/django_custommail.git#egg=django_custommail
ldap3 ldap3
git+https://github.com/Aureplop/channels.git#egg=channels channels==1.1.3
django-js-reverse==0.7.3 django-js-reverse==0.7.3
python-dateutil python-dateutil