Merge branch 'k-fet' into 'master'

K-Fêt - 17-01-07

## K-Fêt - Général

- Ajout: peuplement des modèles (tables BDD) pour le développement et
  les tests
- Fix: incohérences d'arrondi d'UKF entre les différents composants
- Fix: URL vers les informations détaillées des comptes dont le
  trigramme contient certains caractères spéciaux
- Fix: freeze lors de la saisie de nouveaux transferts
- Fix: il n'est plus possible de rentrer des commandes et transferts sur
  le compte utilisateur partagé K-Fêt
- Fix: la balance de ``#13`` n'est plus comptée dans le négatif total
  des comptes
- Fix: oubli déclaration du backend d'authentification au compte
  utilisateur partagé K-Fêt
- Fix: warning pip sur la référence au package ``channels`` modifié

## K-Psul

- Ajout d'un indicateur lors de la saisie d'un mot de passe si le
  verrouillage des majuscules est activé
- Ajout d'indications visibles pour les raccourcis clavier
- Modification des raccourcis clavier ``F2`` (reset du panier) et
  ``Shift + F2`` (reset du compte)
- Ajout d'un affichage d'informations sur la dernière opération validée
  lors de la session en cours
- Ajout d'un lien vers la liste des comptes depuis la zone de compte de
  K-Psul
- Meilleur focus automatique après l'ajout d'une charge au panier

See merge request !147
This commit is contained in:
Martin Pepin 2017-01-07 17:42:01 +01:00
commit 0e7fc99a09
17 changed files with 9170 additions and 1457 deletions

View file

@ -173,7 +173,7 @@ Il ne vous reste plus qu'à initialiser les modèles de Django avec la commande
Une base de donnée pré-remplie est disponible en lançant la commande : Une base de donnée pré-remplie est disponible en lançant la commande :
python manage.py loaddata users root bda gestion sites python manage.py loaddata users root bda gestion sites accounts groups articles
Vous êtes prêts à développer ! Lancer GestioCOF en faisant Vous êtes prêts à développer ! Lancer GestioCOF en faisant

View file

@ -159,6 +159,7 @@ CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
AUTHENTICATION_BACKENDS = ( AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', 'django.contrib.auth.backends.ModelBackend',
'gestioncof.shared.COFCASBackend', 'gestioncof.shared.COFCASBackend',
'kfet.backends.GenericTeamBackend',
) )
# EMAIL_HOST="nef.ens.fr" # EMAIL_HOST="nef.ens.fr"

File diff suppressed because it is too large Load diff

View file

@ -30,9 +30,17 @@ class GenericTeamBackend(object):
def authenticate(self, username=None, token=None): def authenticate(self, username=None, token=None):
valid_token = GenericTeamToken.objects.get(token=token) valid_token = GenericTeamToken.objects.get(token=token)
if username == 'kfet_genericteam' and valid_token: if username == 'kfet_genericteam' and valid_token:
user, created = User.objects.get_or_create(username='kfet_genericteam') # Création du user s'il n'existe pas déjà
user, _ = User.objects.get_or_create(username='kfet_genericteam')
profile, _ = CofProfile.objects.get_or_create(user=user)
account, _ = Account.objects.get_or_create(
cofprofile=profile,
trigramme='GNR')
# Ajoute la permission kfet.is_team à ce user
perm_is_team = Permission.objects.get(codename='is_team') perm_is_team = Permission.objects.get(codename='is_team')
user.user_permissions.add(perm_is_team) user.user_permissions.add(perm_is_team)
return user return user
return None return None

1178
kfet/fixtures/accounts.json Normal file

File diff suppressed because it is too large Load diff

2322
kfet/fixtures/articles.json Normal file

File diff suppressed because it is too large Load diff

98
kfet/fixtures/groups.json Normal file
View file

@ -0,0 +1,98 @@
[
{
"model": "auth.group",
"pk": 1,
"fields": {
"name": "K-F\u00eat chef",
"permissions": [
115,
116,
117,
118,
119,
120,
133,
134,
135,
130,
131,
132,
136,
137,
138,
121,
122,
123,
127,
128,
129,
124,
125,
126,
188,
189,
190,
169,
176,
183,
170,
171,
182,
172,
178,
177,
181,
175,
179,
173,
174,
184,
180,
139,
140,
141,
142,
143,
144,
166,
167,
168,
163,
164,
165,
151,
152,
153,
154,
155,
156,
185,
186,
187,
145,
146,
147,
148,
149,
150,
160,
161,
162,
157,
158,
159
]
}
},
{
"model": "auth.group",
"pk": 2,
"fields": {
"name": "K-F\u00eat girl",
"permissions": [
172,
173
]
}
}
]

View file

@ -280,12 +280,12 @@ class KPsulOperationGroupForm(forms.ModelForm):
is_protected=False, valid_from__lte=timezone.now(), is_protected=False, valid_from__lte=timezone.now(),
valid_to__gte=timezone.now()), valid_to__gte=timezone.now()),
widget = forms.HiddenInput()) widget = forms.HiddenInput())
on_acc = forms.ModelChoiceField(
queryset = Account.objects.exclude(trigramme='GNR'),
widget = forms.HiddenInput())
class Meta: class Meta:
model = OperationGroup model = OperationGroup
fields = ['on_acc', 'checkout', 'comment'] fields = ['on_acc', 'checkout', 'comment']
widgets = {
'on_acc' : forms.HiddenInput(),
}
class KPsulAccountForm(forms.ModelForm): class KPsulAccountForm(forms.ModelForm):
class Meta: class Meta:
@ -418,11 +418,11 @@ class TransferGroupForm(forms.ModelForm):
class TransferForm(forms.ModelForm): class TransferForm(forms.ModelForm):
from_acc = forms.ModelChoiceField( from_acc = forms.ModelChoiceField(
queryset = Account.objects.exclude(trigramme__in=['LIQ', '#13']), queryset = Account.objects.exclude(trigramme__in=['LIQ', '#13', 'GNR']),
widget = forms.HiddenInput() widget = forms.HiddenInput()
) )
to_acc = forms.ModelChoiceField( to_acc = forms.ModelChoiceField(
queryset = Account.objects.exclude(trigramme__in=['LIQ', '#13']), queryset = Account.objects.exclude(trigramme__in=['LIQ', '#13', 'GNR']),
widget = forms.HiddenInput() widget = forms.HiddenInput()
) )

View file

@ -64,3 +64,22 @@
color:#FFF !important; color:#FFF !important;
background:#C8102E !important; background:#C8102E !important;
} }
.jconfirm .capslock {
position: relative ;
}
.jconfirm .capslock .glyphicon {
position: absolute;
padding: 10px;
right: 0px;
top: 15px;
font-size: 30px;
display: none ;
margin-left: 60px !important;
}
.jconfirm .capslock input {
padding-right: 50px;
padding-left: 50px;
}

View file

@ -8,7 +8,7 @@ input[type=number]::-webkit-outer-spin-button {
margin: 0; margin: 0;
} }
#account, #checkout, input, #history, #basket, #basket_rel, #articles_data { #account, #checkout, input, #history, #basket, #basket_rel, #previous_op, #articles_data {
background:#fff; background:#fff;
} }
@ -335,12 +335,13 @@ input[type=number]::-webkit-outer-spin-button {
padding:0; padding:0;
} }
#basket, #basket_rel { #basket, #basket_rel, #previous_op {
height:100%; height:100%;
} }
#basket_rel { #basket_rel, #previous_op {
border-top:1px solid #C8102E; border-top:1px solid #C8102E;
padding-left: 3px;
} }
#basket { #basket {
@ -354,6 +355,11 @@ input[type=number]::-webkit-outer-spin-button {
#basket_rel { #basket_rel {
border-top:0; border-top:0;
margin-left:7px; margin-left:7px;
margin-right:7px;
}
#previous_op {
border-top:0;
margin-left:7px;
} }
} }
@ -385,6 +391,16 @@ input[type=number]::-webkit-outer-spin-button {
/* History */ /* History */
#previous_op .trigramme {
width:100%;
background-color:rgba(200,16,46,0.85);
color:#FFF;
font-weight:bold;
padding:3px;
margin-left: -3px;
margin-bottom: 3px;
}
.kpsul_middle_right_col { .kpsul_middle_right_col {
overflow:auto; overflow:auto;
} }

View file

@ -39,7 +39,7 @@ function amountDisplay(amount, is_cof=false, tri='') {
function amountToUKF(amount, is_cof=false) { function amountToUKF(amount, is_cof=false) {
var coef_cof = is_cof ? 1 + settings['subvention_cof'] / 100 : 1; var coef_cof = is_cof ? 1 + settings['subvention_cof'] / 100 : 1;
return Math.round(amount * coef_cof * 10); return Math.floor(amount * coef_cof * 10);
} }
function isValidTrigramme(trigramme) { function isValidTrigramme(trigramme) {
@ -88,7 +88,7 @@ function getErrorsHtml(data) {
function requestAuth(data, callback, focus_next = null) { function requestAuth(data, callback, focus_next = null) {
var content = getErrorsHtml(data); var content = getErrorsHtml(data);
content += '<input type="password" name="password" autofocus>', content += '<div class="capslock"><span class="glyphicon glyphicon-lock"></span><input type="password" name="password" autofocus><div>',
$.confirm({ $.confirm({
title: 'Authentification requise', title: 'Authentification requise',
content: content, content: content,
@ -102,14 +102,39 @@ function requestAuth(data, callback, focus_next = null) {
}, },
onOpen: function() { onOpen: function() {
var that = this; var that = this;
var capslock = -1 ; // 1 -> caps on ; 0 -> caps off ; -1 or 2 -> unknown
this.$content.find('input').on('keypress', function(e) { this.$content.find('input').on('keypress', function(e) {
if (e.keyCode == 13) if (e.keyCode == 13)
that.$confirmButton.click(); that.$confirmButton.click();
var s = String.fromCharCode(e.which);
if ((s.toUpperCase() === s && s.toLowerCase() !== s && !e.shiftKey)|| //caps on, shift off
(s.toUpperCase() !== s && s.toLowerCase() === s && e.shiftKey)) { //caps on, shift on
capslock = 1 ;
} else if ((s.toLowerCase() === s && s.toUpperCase() !== s && !e.shiftKey)|| //caps off, shift off
(s.toLowerCase() !== s && s.toUpperCase() === s && e.shiftKey)) { //caps off, shift on
capslock = 0 ;
}
if (capslock == 1)
$('.capslock .glyphicon').show() ;
else if (capslock == 0)
$('.capslock .glyphicon').hide() ;
});
// Capslock key is not detected by keypress
this.$content.find('input').on('keydown', function(e) {
if (e.which == 20) {
capslock = 1-capslock ;
}
if (capslock == 1)
$('.capslock .glyphicon').show() ;
else if (capslock == 0)
$('.capslock .glyphicon').hide() ;
}); });
}, },
onClose: function() { onClose: function() {
if (focus_next) if (focus_next)
this._lastFocused = focus_next; this._lastFocused = focus_next;
} }
}); });
} }

View file

@ -48,11 +48,11 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-xs-3"><b>F2</b></div> <div class="col-xs-3"><b>F2</b></div>
<div class="col-xs-9">Reset compte</div> <div class="col-xs-9">Reset panier</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-xs-3"><b>Shift + F2</b></div> <div class="col-xs-3"><b>Shift + F2</b></div>
<div class="col-xs-9">Reset panier</div> <div class="col-xs-9">Reset compte</div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-xs-3"><b>F9</b></div> <div class="col-xs-3"><b>F9</b></div>
@ -114,10 +114,10 @@
<div class="kpsul_middle_left"> <div class="kpsul_middle_left">
<div class="kpsul_middle_left_top"> <div class="kpsul_middle_left_top">
<div id="special_operations"> <div id="special_operations">
<button role="button" class="btn" id="operation_deposit">Charge</button> <button role="button" class="btn" id="operation_deposit">Charge (F3)</button>
<button role="button" class="btn" id="operation_withdraw">Retrait</button> <button role="button" class="btn" id="operation_withdraw">Retrait (Shift+F3)</button>
<button role="button" class="btn" id="cool_reset">RAZ</button> <button role="button" class="btn" id="cool_reset">RAZ (F1)</button>
<button role="button" class="btn" id="ask_addcost">Major.</button> <button role="button" class="btn" id="ask_addcost">Major. (F9)</button>
</div> </div>
<div id="article_selection"> <div id="article_selection">
<input type="text" id="article_autocomplete" autocomplete="off"> <input type="text" id="article_autocomplete" autocomplete="off">
@ -138,10 +138,14 @@
</table> </table>
</div> </div>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-3">
<div id="basket_rel"> <div id="basket_rel">
</div> </div>
</div> </div>
<div class="col-sm-3">
<div id="previous_op">
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -245,13 +249,18 @@ $(document).ready(function() {
if (account_data['id'] != 0) { if (account_data['id'] != 0) {
var url_base = '{% url 'kfet.account.read' 'LIQ' %}'; var url_base = '{% url 'kfet.account.read' 'LIQ' %}';
url_base = url_base.substr(0, url_base.length - 3); url_base = url_base.substr(0, url_base.length - 3);
buttons += '<a href="'+ url_base + account_data['trigramme']+'" class="btn btn-primary" target="_blank" title="Modifier"><span class="glyphicon glyphicon-cog"></span></a>'; trigramme = encodeURIComponent(account_data['trigramme']) ;
buttons += '<a href="'+ url_base + trigramme +'" class="btn btn-primary" target="_blank" title="Modifier"><span class="glyphicon glyphicon-cog"></span></a>';
} }
if (account_data['id'] == 0) { if (account_data['id'] == 0) {
var trigramme = triInput.val().toUpperCase(); var trigramme = triInput.val().toUpperCase();
var url_base = '{% url 'kfet.account.create' %}'
if (isValidTrigramme(trigramme)) { if (isValidTrigramme(trigramme)) {
var url_base = '{% url 'kfet.account.create' %}'
trigramme = encodeURIComponent(trigramme);
buttons += '<a href="'+url_base+'?trigramme='+trigramme+'" class="btn btn-primary" target="_blank" title="Créer"><span class="glyphicon glyphicon-plus"></span></a>'; buttons += '<a href="'+url_base+'?trigramme='+trigramme+'" class="btn btn-primary" target="_blank" title="Créer"><span class="glyphicon glyphicon-plus"></span></a>';
} else {
var url_base = '{% url 'kfet.account' %}'
buttons += '<a href="'+url_base+'" class="btn btn-primary" target="_blank" title="Rechercher"><span class="glyphicon glyphicon-search"></span></a>';
} }
} }
account_container.find('.buttons').html(buttons); account_container.find('.buttons').html(buttons);
@ -471,6 +480,7 @@ $(document).ready(function() {
}, },
}) })
.done(function(data) { .done(function(data) {
updatePreviousOp();
coolReset(); coolReset();
lock = 0; lock = 0;
}) })
@ -924,7 +934,7 @@ $(document).ready(function() {
} }
}); });
}, },
onClose: function() { this._lastFocused = articleSelect; } onClose: function() { this._lastFocused = (articleSelect.val() ? articleNb : articleSelect) ; }
}); });
} }
@ -1043,6 +1053,20 @@ $(document).ready(function() {
}); });
} }
var previousop_container = $('#previous_op');
function updatePreviousOp() {
var previousop_html = '';
var trigramme = account_data['trigramme'];
previousop_html += '<div class="trigramme">Trigramme : '+trigramme+'</div>';
previousop_html += basketrel_container.html();
previousop_container.html(previousop_html);
}
function resetPreviousOp() {
previousop_container.html('');
}
// ----- // -----
// Addcost // Addcost
// ----- // -----
@ -1211,6 +1235,7 @@ $(document).ready(function() {
coolReset(give_tri_focus); coolReset(give_tri_focus);
checkoutInput.trigger('change'); checkoutInput.trigger('change');
resetArticles(); resetArticles();
resetPreviousOp();
khistory.reset(); khistory.reset();
resetSettings(); resetSettings();
getArticles(); getArticles();
@ -1250,13 +1275,13 @@ $(document).ready(function() {
return false; return false;
case 113: case 113:
if (e.shiftKey) { if (e.shiftKey) {
// Shift+F2 - Basket reset // Shift+F2 - Account reset
resetBasket();
articleSelect.focus();
} else {
// F2 - Account reset
resetAccount(); resetAccount();
triInput.focus(); triInput.focus();
} else {
// F2 - Basket reset
resetBasket();
articleSelect.focus();
} }
return false; return false;
case 114: case 114:

View file

@ -34,12 +34,12 @@
<tr class="transfer_form" id="{{ form.prefix }}"> <tr class="transfer_form" id="{{ form.prefix }}">
<td class="from_acc_data"></td> <td class="from_acc_data"></td>
<td class="from_acc"> <td class="from_acc">
<input type="text" name="from_acc" class="input_from_acc" autocomplete="off" spellcheck="false"> <input type="text" name="from_acc" class="input_from_acc" maxlength="3" autocomplete="off" spellcheck="false">
{{ form.from_acc }} {{ form.from_acc }}
</td> </td>
<td class="amount">{{ form.amount }}</td> <td class="amount">{{ form.amount }}</td>
<td class="to_acc"> <td class="to_acc">
<input type="text" name="to_acc" class="input_to_acc" autocomplete="off" spellcheck="false"> <input type="text" name="to_acc" class="input_to_acc" maxlength="3" autocomplete="off" spellcheck="false">
{{ form.to_acc }} {{ form.to_acc }}
</td> </td>
<td class="to_acc_data"></td> <td class="to_acc_data"></td>
@ -71,18 +71,21 @@ $(document).ready(function () {
var $next = $form.next('.transfer_form').find('.from_acc input'); var $next = $form.next('.transfer_form').find('.from_acc input');
} }
var $input_id = $input.next('input'); var $input_id = $input.next('input');
getAccountData(trigramme, function(data) { if (isValidTrigramme(trigramme)) {
$input_id.val(data.id); getAccountData(trigramme, function(data) {
$data.text(data.name); $input_id.val(data.id);
$next.focus(); $data.text(data.name);
}); $next.focus();
});
} else {
$input_id.val('');
$data.text('');
}
} }
$('.input_from_acc, .input_to_acc').on('input', function() { $('.input_from_acc, .input_to_acc').on('input', function() {
var tri = $(this).val().toUpperCase(); var tri = $(this).val().toUpperCase();
if (isValidTrigramme(tri)) { updateAccountData(tri, $(this));
updateAccountData(tri, $(this));
}
}); });
$('#transfers_form').on('submit', function(e) { $('#transfers_form').on('submit', function(e) {

View file

@ -9,6 +9,7 @@ from django.utils.html import escape
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.core.cache import cache from django.core.cache import cache
from kfet.models import Settings from kfet.models import Settings
from math import floor
import re import re
register = template.Library() register = template.Library()
@ -37,5 +38,4 @@ 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 + Settings.SUBVENTION_COF() / 100) or 1
# float nécessaire car sinon problème avec le round de future.builtins return floor(balance * 10 * grant)
return round(float(balance * 10 * grant))

View file

@ -1,9 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from django.test import TestCase
# Create your tests here.

View file

@ -42,16 +42,22 @@ def home(request):
@teamkfet_required @teamkfet_required
def login_genericteam(request): def login_genericteam(request):
# Check si besoin de déconnecter l'utilisateur de CAS
profile, _ = CofProfile.objects.get_or_create(user=request.user) profile, _ = CofProfile.objects.get_or_create(user=request.user)
logout_cas = '' need_cas_logout = False
if profile.login_clipper: if profile.login_clipper:
need_cas_logout = True
# Récupèration de la vue de déconnexion de CAS
# Ici, car request sera modifié après
logout_cas = django_cas_ng.views.logout(request) logout_cas = django_cas_ng.views.logout(request)
# Authentification du compte générique
token = GenericTeamToken.objects.create(token=get_random_string(50)) token = GenericTeamToken.objects.create(token=get_random_string(50))
user = authenticate(username="kfet_genericteam", token=token.token) user = authenticate(username="kfet_genericteam", token=token.token)
login(request, user) login(request, user)
if logout_cas: if need_cas_logout:
# Vue de déconnexion de CAS
return logout_cas return logout_cas
return render(request, "kfet/login_genericteam.html") return render(request, "kfet/login_genericteam.html")
@ -484,7 +490,8 @@ class AccountGroupUpdate(UpdateView):
class AccountNegativeList(ListView): class AccountNegativeList(ListView):
queryset = (AccountNegative.objects queryset = (AccountNegative.objects
.select_related('account', 'account__cofprofile__user')) .select_related('account', 'account__cofprofile__user')
.exclude(account__trigramme='#13'))
template_name = 'kfet/account_negative.html' template_name = 'kfet/account_negative.html'
context_object_name = 'negatives' context_object_name = 'negatives'
@ -495,12 +502,13 @@ class AccountNegativeList(ListView):
'overdraft_duration': Settings.OVERDRAFT_DURATION(), 'overdraft_duration': Settings.OVERDRAFT_DURATION(),
} }
negs_sum = (AccountNegative.objects negs_sum = (AccountNegative.objects
.exclude(account__trigramme='#13')
.aggregate( .aggregate(
bal = Coalesce(Sum('account__balance'),0), bal = Coalesce(Sum('account__balance'),0),
offset = Coalesce(Sum('balance_offset'),0), offset = Coalesce(Sum('balance_offset'),0),
) )
) )
context['negatives_sum'] = negs_sum['bal'] + negs_sum['offset'] context['negatives_sum'] = negs_sum['bal'] - negs_sum['offset']
return context return context
# ----- # -----

View file

@ -14,7 +14,7 @@ django-bootstrap-form==3.2.1
asgiref==0.14.0 asgiref==0.14.0
daphne==0.14.3 daphne==0.14.3
asgi-redis==0.14.0 asgi-redis==0.14.0
git+https://github.com/Aureplop/channels.git#egg=channel
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://github.com/Aureplop/channels.git#egg=channels