From f64a865de1229f6323da538567e69307e1d997a6 Mon Sep 17 00:00:00 2001 From: Hugo Roussille Date: Wed, 15 Mar 2017 07:37:24 +0100 Subject: [PATCH 01/60] =?UTF-8?q?C=C3=B4t=C3=A9=20serveur=20du=20catalogue?= =?UTF-8?q?=20des=20spectacles=20BdA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bda/urls.py | 2 ++ bda/views.py | 60 +++++++++++++++++++++++++++++++++++++-- provisioning/bootstrap.sh | 1 + 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/bda/urls.py b/bda/urls.py index bbbf8e39..0af14b53 100644 --- a/bda/urls.py +++ b/bda/urls.py @@ -47,4 +47,6 @@ urlpatterns = [ url(r'^mails-rappel/(?P\d+)$', views.send_rappel), url(r'^descriptions/(?P\d+)$', views.descriptions_spectacles, name='bda-descriptions'), + url(r'^catalogue/(?P[a-z]*)$', views.catalogue, + name='bda-catalogue'), ] diff --git a/bda/views.py b/bda/views.py index 096aacf5..cdc7a296 100644 --- a/bda/views.py +++ b/bda/views.py @@ -3,7 +3,9 @@ import random import hashlib import time +import json from datetime import timedelta +from more_itertools import unique_everseen from custommail.shortcuts import ( send_mass_custom_mail, send_custom_mail, render_custom_mail ) @@ -15,7 +17,7 @@ from django.db import models, transaction from django.core import serializers from django.db.models import Count, Q, Sum from django.forms.models import inlineformset_factory -from django.http import HttpResponseBadRequest, HttpResponseRedirect +from django.http import HttpResponseBadRequest, HttpResponseRedirect, HttpResponse from django.core.urlresolvers import reverse from django.conf import settings from django.utils import timezone, formats @@ -23,7 +25,7 @@ from django.views.generic.list import ListView from gestioncof.decorators import cof_required, buro_required from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\ - Tirage, SpectacleRevente + Tirage, SpectacleRevente, Salle from bda.algorithm import Algorithm from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\ InscriptionReventeForm, SoldForm @@ -639,3 +641,57 @@ def descriptions_spectacles(request, tirage_id): return HttpResponseBadRequest( "La variable GET 'location' doit contenir un entier") return render(request, 'descriptions.html', {'shows': shows_qs.all()}) + +def catalogue(request,request_type): + """ + Vue destinée à communiquer avec un client AJAX, fournissant soit : + - la liste des tirages + - les catégories et salles d'un tirage + - les descriptions d'un tirage (filtrées selon la catégorie et la salle) + """ + if request_type == "list": + # Dans ce cas on retourne la liste des tirages et de leur id sous forme de JSON + data_return = [{'id':tirage.id, 'title':tirage.title} for tirage in Tirage.objects.all()] + return HttpResponse(json.dumps(data_return)) + if request_type == "details": + # Dans ce cas on retourne une liste des catégories et des salles du tirage + try : + tirage_id = request.GET.get('id', '') + tirage = get_object_or_404(Tirage, id=tirage_id) + except: + return HttpResponseBadRequest() + categories = list(unique_everseen([str(spectacle.category) for spectacle in tirage.spectacle_set.all()])) + categories.remove('None') + locations = list(unique_everseen([str(spectacle.location) for spectacle in tirage.spectacle_set.all()])) + data_return = [{'categories':categories, 'locations':locations}] + return HttpResponse(json.dumps(data_return)) + if request_type == "descriptions": + # Ici on retourne les descriptions correspondant à la catégorie et à la salle spécifiées + locations = {} + for salle in Salle.objects.all(): + locations[salle.name] = salle.id + try: + tirage_id = request.GET.get('id', '') + category_name = request.GET.get('category', '') + location_name = request.GET.get('location', '') + tirage = get_object_or_404(Tirage, id=tirage_id) + + shows_qs = tirage.spectacle_set + if category_name: + shows_qs = shows_qs.filter(category__name=category_name) + if location_name: + shows_qs = shows_qs.filter(location__id=locations[location_name]) + except: + return HttpResponseBadRequest("Impossible de trouver des résultats correspondant à ces caractéristiques") + + def getImgUrl(obj): + """ Cette fonction permet de gérer les cas où il n'y a pas d'image """ + try: + return obj.image.url + except: + return '' + + # On convertit les descriptions à envoyer en une liste facilement JSONifiable (il devrait y avoir un moyen plus efficace en redéfinissant le serializer de JSON) + data_return = [{'title': spectacle.title, 'category': str(spectacle.category), 'date': str(spectacle.date), 'location': str(spectacle.location), 'vips': spectacle.vips, 'description': spectacle.description, 'slots_description': spectacle.slots_description, 'image': getImgUrl(spectacle), 'ext_link': spectacle.ext_link, 'price': spectacle.price, 'slots': spectacle.slots} for spectacle in shows_qs.all()] + return HttpResponse(json.dumps(data_return)) + return HttpResponseBadRequest() # Si la requête n'est pas de la forme attendue, on quitte avec une erreur diff --git a/provisioning/bootstrap.sh b/provisioning/bootstrap.sh index 269e4f25..a08d9282 100644 --- a/provisioning/bootstrap.sh +++ b/provisioning/bootstrap.sh @@ -11,6 +11,7 @@ DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4" apt-get update && apt-get install -y python3-pip python3-dev python3-venv \ libmysqlclient-dev libjpeg-dev git redis-server pip install -U pip +pip install more_itertools # Configuration et installation de mysql. Le mot de passe root est le même que # le mot de passe pour l'utilisateur local - pour rappel, ceci est une instance From 68334e15d72abb9bec96d1a08928752dbcc3767f Mon Sep 17 00:00:00 2001 From: Hugo Roussille Date: Thu, 16 Mar 2017 05:50:02 +0100 Subject: [PATCH 02/60] Gestion des citations --- bda/views.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bda/views.py b/bda/views.py index cdc7a296..31ac3296 100644 --- a/bda/views.py +++ b/bda/views.py @@ -25,7 +25,7 @@ from django.views.generic.list import ListView from gestioncof.decorators import cof_required, buro_required from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\ - Tirage, SpectacleRevente, Salle + Tirage, SpectacleRevente, Salle, Quote from bda.algorithm import Algorithm from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\ InscriptionReventeForm, SoldForm @@ -691,7 +691,9 @@ def catalogue(request,request_type): except: return '' + specquotes = lambda spectacle: [{'author': str(quote.author), 'text': str(quote.text)} for quote in Quote.objects.filter(spectacle = spectacle).all()] + # On convertit les descriptions à envoyer en une liste facilement JSONifiable (il devrait y avoir un moyen plus efficace en redéfinissant le serializer de JSON) - data_return = [{'title': spectacle.title, 'category': str(spectacle.category), 'date': str(spectacle.date), 'location': str(spectacle.location), 'vips': spectacle.vips, 'description': spectacle.description, 'slots_description': spectacle.slots_description, 'image': getImgUrl(spectacle), 'ext_link': spectacle.ext_link, 'price': spectacle.price, 'slots': spectacle.slots} for spectacle in shows_qs.all()] + data_return = [{'title': spectacle.title, 'category': str(spectacle.category), 'date': str(spectacle.date), 'location': str(spectacle.location), 'vips': spectacle.vips, 'description': spectacle.description, 'slots_description': spectacle.slots_description, 'quotes': specquotes(spectacle),'image': getImgUrl(spectacle), 'ext_link': spectacle.ext_link, 'price': spectacle.price, 'slots': spectacle.slots} for spectacle in shows_qs.all()] return HttpResponse(json.dumps(data_return)) return HttpResponseBadRequest() # Si la requête n'est pas de la forme attendue, on quitte avec une erreur From 7ecea0e391b5a2ee0b5c54289b66dae7237d0552 Mon Sep 17 00:00:00 2001 From: Hugo Roussille Date: Thu, 16 Mar 2017 06:22:01 +0100 Subject: [PATCH 03/60] Meilleure gestion des dates et timezones --- bda/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bda/views.py b/bda/views.py index 31ac3296..bcbaaecb 100644 --- a/bda/views.py +++ b/bda/views.py @@ -694,6 +694,6 @@ def catalogue(request,request_type): specquotes = lambda spectacle: [{'author': str(quote.author), 'text': str(quote.text)} for quote in Quote.objects.filter(spectacle = spectacle).all()] # On convertit les descriptions à envoyer en une liste facilement JSONifiable (il devrait y avoir un moyen plus efficace en redéfinissant le serializer de JSON) - data_return = [{'title': spectacle.title, 'category': str(spectacle.category), 'date': str(spectacle.date), 'location': str(spectacle.location), 'vips': spectacle.vips, 'description': spectacle.description, 'slots_description': spectacle.slots_description, 'quotes': specquotes(spectacle),'image': getImgUrl(spectacle), 'ext_link': spectacle.ext_link, 'price': spectacle.price, 'slots': spectacle.slots} for spectacle in shows_qs.all()] + data_return = [{'title': spectacle.title, 'category': str(spectacle.category), 'date': str(formats.date_format(timezone.localtime(spectacle.date), "SHORT_DATETIME_FORMAT")), 'location': str(spectacle.location), 'vips': spectacle.vips, 'description': spectacle.description, 'slots_description': spectacle.slots_description, 'quotes': specquotes(spectacle),'image': getImgUrl(spectacle), 'ext_link': spectacle.ext_link, 'price': spectacle.price, 'slots': spectacle.slots} for spectacle in shows_qs.all()] return HttpResponse(json.dumps(data_return)) return HttpResponseBadRequest() # Si la requête n'est pas de la forme attendue, on quitte avec une erreur From a8428f1b41544c1c2a1979de986240affd026dd5 Mon Sep 17 00:00:00 2001 From: Hugo Roussille Date: Thu, 16 Mar 2017 06:34:49 +0100 Subject: [PATCH 04/60] =?UTF-8?q?Pr=C3=A9cision=20des=20tirages=20qui=20do?= =?UTF-8?q?ivent=20appara=C3=AEtre=20dans=20le=20catalogue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bda/models.py | 1 + bda/views.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bda/models.py b/bda/models.py index a405a665..d60c3c94 100644 --- a/bda/models.py +++ b/bda/models.py @@ -18,6 +18,7 @@ class Tirage(models.Model): fermeture = models.DateTimeField("Date et heure de fermerture du tirage") tokens = models.TextField("Graine(s) du tirage", blank=True) active = models.BooleanField("Tirage actif", default=False) + appear_catalogue = models.BooleanField("Tirage à afficher dans le catalogue", default=False) enable_do_tirage = models.BooleanField("Le tirage peut être lancé", default=False) diff --git a/bda/views.py b/bda/views.py index bcbaaecb..34733bec 100644 --- a/bda/views.py +++ b/bda/views.py @@ -651,7 +651,7 @@ def catalogue(request,request_type): """ if request_type == "list": # Dans ce cas on retourne la liste des tirages et de leur id sous forme de JSON - data_return = [{'id':tirage.id, 'title':tirage.title} for tirage in Tirage.objects.all()] + data_return = [{'id':tirage.id, 'title':tirage.title} for tirage in Tirage.objects.filter(appear_catalogue = True).all()] return HttpResponse(json.dumps(data_return)) if request_type == "details": # Dans ce cas on retourne une liste des catégories et des salles du tirage From 0356ec34ae3b4f81f71185c158faa0919bd80cc8 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sun, 19 Mar 2017 16:03:19 -0300 Subject: [PATCH 05/60] Add createopes command --- kfet/management/commands/createopes.py | 173 +++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 kfet/management/commands/createopes.py diff --git a/kfet/management/commands/createopes.py b/kfet/management/commands/createopes.py new file mode 100644 index 00000000..aaca84c7 --- /dev/null +++ b/kfet/management/commands/createopes.py @@ -0,0 +1,173 @@ + +""" +Crée des opérations aléatoires réparties sur une période de temps spécifiée +""" + +import random +from datetime import timedelta +from decimal import Decimal +from django.utils import timezone +from django.core.management.base import BaseCommand + +from kfet.models import (Account, Article, OperationGroup, Operation, + Checkout, Transfer, TransferGroup) + + +class Command(BaseCommand): + help = "Crée des opérations réparties uniformément sur une période de temps" + + def add_arguments(self, parser): + # Nombre d'opérations à créer + parser.add_argument('opes', type=int, + help='Number of opegroups to create') + + # Période sur laquelle créer (depuis num_days avant maintenant) + parser.add_argument('days', type=int, + help='Period in which to create opegroups') + + # Optionnel : nombre de transfert à créer (défaut 0) + parser.add_argument('--transfers', type=int, default=0, + help='Number of transfers to create (default 0)') + + + def handle(self, *args, **options): + + self.stdout.write("Génération d'opérations") + + # Output log vars + opes_created = 0 + purchases = 0 + transfers = 0 + + num_ops = options['opes'] + num_transfers = options['transfers'] + # Convert to seconds + time = options['days'] * 24 * 3600 + + checkout = Checkout.objects.first() + articles = Article.objects.all() + accounts = Account.objects.exclude(trigramme='LIQ') + liq_account = Account.objects.get(trigramme='LIQ') + try: + con_account = Account.objects.get( + cofprofile__user__first_name='Assurancetourix' + ) + except Account.DoesNotExist: + con_account = random.choice(accounts) + + for i in range(num_ops): + + # Randomly pick account + if random.random() > 0.25: + account = random.choice(accounts) + else: + account = liq_account + + # Randomly pick time + at = timezone.now() - timedelta( + seconds=random.randint(0, time)) + + # Majoration sur compte 'concert' + if random.random() < 0.2: + addcost = True + addcost_for = con_account + addcost_amount = Decimal('0.5') + else: + addcost = False + + # Initialize opegroup amount + amount = Decimal('0') + + opegroup = OperationGroup( + on_acc=account, + checkout=checkout, + is_cof=account.cofprofile.is_cof + ) + opegroup.save() + + # Generating operations + for j in range(random.randint(1, 4)): + # Operation type + typevar = random.random() + + # 0.1 probability to have a charge + if typevar > 0.9 and account != liq_account: + ope = Operation( + group=opegroup, + type=Operation.DEPOSIT, + is_checkout=(random.random() > 0.2), + amount=Decimal(random.randint(1, 99)/10) + ) + # 0.1 probability to have a withdrawal + elif typevar > 0.8 and account != liq_account: + ope = Operation( + group=opegroup, + type=Operation.WITHDRAW, + is_checkout=(random.random() > 0.2), + amount=-Decimal(random.randint(1, 99)/10) + ) + else: + article = random.choice(articles) + nb = random.randint(1, 5) + + ope = Operation( + group=opegroup, + type=Operation.PURCHASE, + amount=-article.price*nb, + article=article, + article_nb=nb + ) + + purchases += 1 + + if addcost: + ope.addcost_for = addcost_for + ope.addcost_amount = addcost_amount * nb + ope.amount -= ope.addcost_amount + + opes_created += 1 + ope.save() + amount += ope.amount + + opegroup.amount = amount + opegroup.at = at + opegroup.save() + + # Transfer generation + for i in range(num_transfers): + + # Randomly pick time + at = timezone.now() - timedelta( + seconds=random.randint(0, time)) + + # Choose whether to have a comment + if random.random() > 0.5: + comment = "placeholder comment" + else: + comment = "" + + transfergroup = TransferGroup( + at=at, + comment=comment, + valid_by=random.choice(accounts) + ) + transfergroup.save() + + # Randomly generate transfer + for i in range(random.randint(1, 4)): + transfer = Transfer( + group=transfergroup, + from_acc=random.choice(accounts), + to_acc=random.choice(accounts), + amount=Decimal(random.randint(1, 99)/10) + ) + transfer.save() + transfers += 1 + + self.stdout.write( + "- {:d} opérations créées dont {:d} commandes d'articles" + .format(opes_created, purchases)) + + if transfers: + self.stdout.write("- {:d} transferts créés" + .format(transfers)) From 36dc6439a80d5673e518715ff0bbe8a4cc8d2b16 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sun, 19 Mar 2017 16:03:39 -0300 Subject: [PATCH 06/60] Plug createopes into loadkfetdevdata --- kfet/management/commands/loadkfetdevdata.py | 64 +-------------------- 1 file changed, 3 insertions(+), 61 deletions(-) diff --git a/kfet/management/commands/loadkfetdevdata.py b/kfet/management/commands/loadkfetdevdata.py index 705f6f27..8eee85a9 100644 --- a/kfet/management/commands/loadkfetdevdata.py +++ b/kfet/management/commands/loadkfetdevdata.py @@ -5,15 +5,14 @@ Crée des utilisateurs, des articles et des opérations aléatoires import os import random from datetime import timedelta -from decimal import Decimal from django.utils import timezone from django.contrib.auth.models import User, Group, Permission, ContentType +from django.core.management import call_command from gestioncof.management.base import MyBaseCommand from gestioncof.models import CofProfile -from kfet.models import (Account, Article, OperationGroup, Operation, - Checkout, CheckoutStatement) +from kfet.models import (Account, Checkout, CheckoutStatement) # Où sont stockés les fichiers json DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), @@ -130,61 +129,4 @@ class Command(MyBaseCommand): # Opérations # --- - self.stdout.write("Génération d'opérations") - - articles = Article.objects.all() - accounts = Account.objects.exclude(trigramme='LIQ') - - num_op = 100 - # Operations are put uniformly over the span of a week - past_date = 3600*24*7 - - for i in range(num_op): - if random.random() > 0.25: - account = random.choice(accounts) - else: - account = liq_account - - amount = Decimal('0') - at = timezone.now() - timedelta( - seconds=random.randint(0, past_date)) - - opegroup = OperationGroup( - on_acc=account, - checkout=checkout, - at=at, - is_cof=account.cofprofile.is_cof) - - opegroup.save() - - for j in range(random.randint(1, 4)): - typevar = random.random() - if typevar > 0.9 and account != liq_account: - ope = Operation( - group=opegroup, - type=Operation.DEPOSIT, - amount=Decimal(random.randint(1, 99)/10,) - ) - elif typevar > 0.8 and account != liq_account: - ope = Operation( - group=opegroup, - type=Operation.WITHDRAW, - amount=-Decimal(random.randint(1, 99)/10,) - ) - else: - article = random.choice(articles) - nb = random.randint(1, 5) - - ope = Operation( - group=opegroup, - type=Operation.PURCHASE, - amount=-article.price*nb, - article=article, - article_nb=nb - ) - - ope.save() - amount += ope.amount - - opegroup.amount = amount - opegroup.save() + call_command('createopes','100', '7') From 6d36d50e9ab5146bfeb3451aa9625156d8273744 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 20 Mar 2017 01:56:59 -0300 Subject: [PATCH 07/60] Fix 'at' property for transfergroups --- kfet/management/commands/createopes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kfet/management/commands/createopes.py b/kfet/management/commands/createopes.py index aaca84c7..75bb23ae 100644 --- a/kfet/management/commands/createopes.py +++ b/kfet/management/commands/createopes.py @@ -147,7 +147,6 @@ class Command(BaseCommand): comment = "" transfergroup = TransferGroup( - at=at, comment=comment, valid_by=random.choice(accounts) ) @@ -164,6 +163,9 @@ class Command(BaseCommand): transfer.save() transfers += 1 + transfergroup.at = at + transfergroup.save() + self.stdout.write( "- {:d} opérations créées dont {:d} commandes d'articles" .format(opes_created, purchases)) From dd4d1f3061f08680a120ab7a00775e0804e4238c Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 20 Mar 2017 20:19:04 -0300 Subject: [PATCH 08/60] Improves creation efficiency --- kfet/management/commands/createopes.py | 26 +++++++++++++------------- kfet/models.py | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/kfet/management/commands/createopes.py b/kfet/management/commands/createopes.py index 75bb23ae..77663b2b 100644 --- a/kfet/management/commands/createopes.py +++ b/kfet/management/commands/createopes.py @@ -78,14 +78,15 @@ class Command(BaseCommand): # Initialize opegroup amount amount = Decimal('0') - opegroup = OperationGroup( + opegroup = OperationGroup.objects.create( on_acc=account, checkout=checkout, + at=at, is_cof=account.cofprofile.is_cof ) - opegroup.save() # Generating operations + ope_list = [] for j in range(random.randint(1, 4)): # Operation type typevar = random.random() @@ -125,12 +126,12 @@ class Command(BaseCommand): ope.addcost_amount = addcost_amount * nb ope.amount -= ope.addcost_amount - opes_created += 1 - ope.save() + ope_list.append(ope) amount += ope.amount + Operation.objects.bulk_create(ope_list) + opes_created += len(ope_list) opegroup.amount = amount - opegroup.at = at opegroup.save() # Transfer generation @@ -146,25 +147,24 @@ class Command(BaseCommand): else: comment = "" - transfergroup = TransferGroup( + transfergroup = TransferGroup.objects.create( + at=at, comment=comment, valid_by=random.choice(accounts) ) - transfergroup.save() # Randomly generate transfer + transfer_list = [] for i in range(random.randint(1, 4)): - transfer = Transfer( + transfer_list.append(Transfer( group=transfergroup, from_acc=random.choice(accounts), to_acc=random.choice(accounts), amount=Decimal(random.randint(1, 99)/10) - ) - transfer.save() - transfers += 1 + )) - transfergroup.at = at - transfergroup.save() + Transfer.objects.bulk_create(transfer_list) + transfers += len(transfer_list) self.stdout.write( "- {:d} opérations créées dont {:d} commandes d'articles" diff --git a/kfet/models.py b/kfet/models.py index 419cd0a0..4e98c3c8 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -455,7 +455,7 @@ class OrderArticle(models.Model): quantity_received = models.IntegerField(default = 0) class TransferGroup(models.Model): - at = models.DateTimeField(auto_now_add = True) + at = models.DateTimeField(default=timezone.now) # Optional comment = models.CharField( max_length = 255, @@ -491,7 +491,7 @@ class OperationGroup(models.Model): checkout = models.ForeignKey( Checkout, on_delete = models.PROTECT, related_name = "opesgroup") - at = models.DateTimeField(auto_now_add = True) + at = models.DateTimeField(default=timezone.now) amount = models.DecimalField( max_digits = 6, decimal_places = 2, default = 0) From cadaf4313128c9b7d88be24ef5a8163afe0af447 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 24 Mar 2017 18:50:57 -0300 Subject: [PATCH 09/60] Remove is_checkout field from Operation model --- kfet/models.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/kfet/models.py b/kfet/models.py index 419cd0a0..e54ca0cd 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -510,12 +510,14 @@ class Operation(models.Model): DEPOSIT = 'deposit' WITHDRAW = 'withdraw' INITIAL = 'initial' + EDIT = 'edit' TYPE_ORDER_CHOICES = ( (PURCHASE, 'Achat'), (DEPOSIT, 'Charge'), (WITHDRAW, 'Retrait'), (INITIAL, 'Initial'), + (EDIT, 'Édition'), ) group = models.ForeignKey( @@ -527,7 +529,6 @@ class Operation(models.Model): amount = models.DecimalField( max_digits = 6, decimal_places = 2, blank = True, default = 0) - is_checkout = models.BooleanField(default = True) # Optional article = models.ForeignKey( Article, on_delete = models.PROTECT, @@ -549,6 +550,14 @@ class Operation(models.Model): max_digits = 6, decimal_places = 2, blank = True, null = True, default = None) + @property + def is_checkout(self): + return (self.type == DEPOSIT or + self.type == WITHDRAW or + (self.type == PURCHASE and self.group and + self.group.on_acc.is_cash) + ) + class GlobalPermissions(models.Model): class Meta: managed = False From d7740e66fe9397797a110b66a30aa38eabec5d1d Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 24 Mar 2017 20:52:49 -0300 Subject: [PATCH 10/60] adapt code to is_checkout removal --- kfet/forms.py | 4 +- kfet/migrations/0048_auto_20170324_2313.py | 23 +++++++ kfet/models.py | 6 +- kfet/static/kfet/js/history.js | 19 ++++-- kfet/templates/kfet/kpsul.html | 73 +++++++++++++++++----- kfet/views.py | 20 +++--- 6 files changed, 105 insertions(+), 40 deletions(-) create mode 100644 kfet/migrations/0048_auto_20170324_2313.py diff --git a/kfet/forms.py b/kfet/forms.py index 0c563b04..d00dfc46 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -313,11 +313,10 @@ class KPsulOperationForm(forms.ModelForm): widget = forms.HiddenInput()) class Meta: model = Operation - fields = ['type', 'amount', 'is_checkout', 'article', 'article_nb'] + fields = ['type', 'amount', 'article', 'article_nb'] widgets = { 'type': forms.HiddenInput(), 'amount': forms.HiddenInput(), - 'is_checkout': forms.HiddenInput(), 'article_nb': forms.HiddenInput(), } @@ -333,7 +332,6 @@ class KPsulOperationForm(forms.ModelForm): "Un achat nécessite un article et une quantité") if article_nb < 1: raise ValidationError("Impossible d'acheter moins de 1 article") - self.cleaned_data['is_checkout'] = True elif type_ope and type_ope in [Operation.DEPOSIT, Operation.WITHDRAW]: if not amount or article or article_nb: raise ValidationError("Bad request") diff --git a/kfet/migrations/0048_auto_20170324_2313.py b/kfet/migrations/0048_auto_20170324_2313.py new file mode 100644 index 00000000..636fda65 --- /dev/null +++ b/kfet/migrations/0048_auto_20170324_2313.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0047_auto_20170104_1528'), + ] + + operations = [ + migrations.RemoveField( + model_name='operation', + name='is_checkout', + ), + migrations.AlterField( + model_name='operation', + name='type', + field=models.CharField(max_length=8, choices=[('purchase', 'Achat'), ('deposit', 'Charge'), ('withdraw', 'Retrait'), ('initial', 'Initial'), ('edit', 'Édition')]), + ), + ] diff --git a/kfet/models.py b/kfet/models.py index e54ca0cd..c902c5d8 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -552,9 +552,9 @@ class Operation(models.Model): @property def is_checkout(self): - return (self.type == DEPOSIT or - self.type == WITHDRAW or - (self.type == PURCHASE and self.group and + return (self.type == Operation.DEPOSIT or + self.type == Operation.WITHDRAW or + (self.type == Operation.PURCHASE and self.group and self.group.on_acc.is_cash) ) diff --git a/kfet/static/kfet/js/history.js b/kfet/static/kfet/js/history.js index 291c106d..8559f050 100644 --- a/kfet/static/kfet/js/history.js +++ b/kfet/static/kfet/js/history.js @@ -31,13 +31,22 @@ function KHistory(options={}) { if (ope['type'] == 'purchase') { infos1 = ope['article_nb']; infos2 = ope['article__name']; - } else if (ope['type'] == 'initial') { - infos1 = parsed_amount.toFixed(2)+'€'; - infos2 = 'Initial'; } else { infos1 = parsed_amount.toFixed(2)+'€'; - infos2 = (ope['type'] == 'deposit') ? 'Charge' : 'Retrait'; - infos2 = ope['is_checkout'] ? infos2 : 'Édition'; + switch (ope['type']) { + case 'initial': + infos2 = 'Initial'; + break; + case 'withdraw': + infos2 = 'Retrait'; + break; + case 'deposit': + infos2 = 'Charge'; + break; + case 'edit': + infos2 = 'Édition'; + break; + } } $ope_html diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 7c0437fa..3d11ddec 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -794,15 +794,27 @@ $(document).ready(function() { updateBasketRel(); } - function addDeposit(amount, is_checkout=1) { + function addDeposit(amount) { var deposit_basket_html = $(item_basket_default_html); var amount = parseFloat(amount).toFixed(2); - var index = addDepositToFormset(amount, is_checkout); - var text = is_checkout ? 'Charge' : 'Édition'; + var index = addDepositToFormset(amount); deposit_basket_html .attr('data-opeindex', index) .find('.number').text(amount+"€").end() - .find('.name').text(text).end() + .find('.name').text('Charge').end() + .find('.amount').text(amountToUKF(amount, account_data['is_cof'])); + basket_container.prepend(deposit_basket_html); + updateBasketRel(); + } + + function addEdit(amount) { + var deposit_basket_html = $(item_basket_default_html); + var amount = parseFloat(amount).toFixed(2); + var index = addEditToFormset(amount); + deposit_basket_html + .attr('data-opeindex', index) + .find('.number').text(amount+"€").end() + .find('.name').text('Édition').end() .find('.amount').text(amountToUKF(amount, account_data['is_cof'])); basket_container.prepend(deposit_basket_html); updateBasketRel(); @@ -910,10 +922,9 @@ $(document).ready(function() { // Ask deposit or withdraw // ----- - function askDeposit(is_checkout=1) { - var title = is_checkout ? 'Montant de la charge' : "Montant de l'édition"; + function askDeposit() { $.confirm({ - title: title, + title: 'Montant de la charge', content: '', backgroundDismiss: true, animation:'top', @@ -923,7 +934,34 @@ $(document).ready(function() { var amount = this.$content.find('input').val(); if (!$.isNumeric(amount) || amount <= 0) return false; - addDeposit(amount, is_checkout); + addDeposit(amount); + }, + onOpen: function() { + var that = this + this.$content.find('input').on('keydown', function(e) { + if (e.keyCode == 13) { + e.preventDefault(); + that.$confirmButton.click(); + } + }); + }, + onClose: function() { this._lastFocused = (articleSelect.val() ? articleNb : articleSelect) ; } + }); + } + + function askEdit() { + $.confirm({ + title: "Montant de l'édition", + content: '', + backgroundDismiss: true, + animation:'top', + closeAnimation:'bottom', + keyboardEnabled: true, + confirm: function() { + var amount = this.$content.find('input').val(); + if (!$.isNumeric(amount)) + return false; + addEdit(amount); }, onOpen: function() { var that = this @@ -988,7 +1026,7 @@ $(document).ready(function() { var mngmt_total_forms = 1; var prefix_regex = /__prefix__/; - function addOperationToFormset(type, amount, article='', article_nb='', is_checkout=1) { + function addOperationToFormset(type, amount, article='', article_nb='') { var operation_html = operation_empty_html.clone(); var index = mngmt_total_forms; @@ -998,8 +1036,7 @@ $(document).ready(function() { .find('#id_form-__prefix__-type').val(type).end() .find('#id_form-__prefix__-amount').val((parseFloat(amount)).toFixed(2)).end() .find('#id_form-__prefix__-article').val(article).end() - .find('#id_form-__prefix__-article_nb').val(article_nb).end() - .find('#id_form-__prefix__-is_checkout').val(is_checkout); + .find('#id_form-__prefix__-article_nb').val(article_nb).end(); mngmt_total_forms_input.val(index+1); mngmt_total_forms++; @@ -1014,12 +1051,16 @@ $(document).ready(function() { return index; } - function addDepositToFormset(amount, is_checkout=1) { - return addOperationToFormset('deposit', amount, '', '', is_checkout); + function addDepositToFormset(amount) { + return addOperationToFormset('deposit', amount, '', ''); } - function addWithdrawToFormset(amount, is_checkout=1) { - return addOperationToFormset('withdraw', amount, '', '', is_checkout); + function addEditToFormset(amount) { + return addOperationToFormset('edit', amount, '', ''); + } + + function addWithdrawToFormset(amount) { + return addOperationToFormset('withdraw', amount, '', ''); } function addPurchaseToFormset(article_id, article_nb, amount=0) { @@ -1295,7 +1336,7 @@ $(document).ready(function() { return false; case 119: // F8 - Edition - askDeposit(0); + askEdit(); return false; case 120: // F9 - Addcost diff --git a/kfet/views.py b/kfet/views.py index 4622f5d8..971e4ba5 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -138,8 +138,7 @@ def account_create_special(request): ope = Operation.objects.create( group = opegroup, type = Operation.INITIAL, - amount = amount, - is_checkout = False) + amount = amount) messages.success(request, 'Compte créé : %s' % account.trigramme) return redirect('kfet.account.create') except Account.UserHasAccount as e: @@ -960,10 +959,7 @@ def kpsul_perform_operations(request): operation.amount -= operation.addcost_amount to_addcost_for_balance += operation.addcost_amount if operationgroup.on_acc.is_cash: - operation.is_checkout = True to_checkout_balance += -operation.amount - else: - operation.is_checkout = False if operationgroup.on_acc.is_cof: if is_addcost: operation.addcost_amount = operation.addcost_amount / cof_grant_divisor @@ -972,12 +968,12 @@ def kpsul_perform_operations(request): else: if operationgroup.on_acc.is_cash: data['errors']['account'] = 'Charge et retrait impossible sur LIQ' - to_checkout_balance += operation.amount + if operation.type != Operation.EDIT: + to_checkout_balance += operation.amount operationgroup.amount += operation.amount if operation.type == Operation.DEPOSIT: required_perms.add('kfet.perform_deposit') - if (not operation.is_checkout - and operation.type in [Operation.DEPOSIT, Operation.WITHDRAW]): + if operation.type == Operation.EDIT: required_perms.add('kfet.edit_balance_account') need_comment = True if operationgroup.on_acc.is_cof: @@ -1073,7 +1069,6 @@ def kpsul_perform_operations(request): 'id': operation.pk, 'type': operation.type, 'amount': operation.amount, 'addcost_amount': operation.addcost_amount, 'addcost_for__trigramme': is_addcost and addcost_for.trigramme or None, - 'is_checkout': operation.is_checkout, 'article__name': operation.article and operation.article.name or None, 'article_nb': operation.article_nb, 'group_id': operationgroup.pk, @@ -1163,11 +1158,11 @@ def kpsul_cancel_operations(request): .order_by('at') .last()) if not last_statement or last_statement.at < ope.group.at: - if ope.type == Operation.PURCHASE: + if ope.is_checkout: if ope.group.on_acc.is_cash: to_checkouts_balances[ope.group.checkout] -= - ope.amount - else: - to_checkouts_balances[ope.group.checkout] -= ope.amount + else: + to_checkouts_balances[ope.group.checkout] -= ope.amount # Pour les stocks d'articles # Les stocks d'articles dont il y a eu un inventaire depuis la date @@ -1328,7 +1323,6 @@ def history_json(request): 'type' : ope.type, 'amount' : ope.amount, 'article_nb' : ope.article_nb, - 'is_checkout' : ope.is_checkout, 'addcost_amount': ope.addcost_amount, 'canceled_at' : ope.canceled_at, 'article__name': From 608e67fe6ac96d9254280ba7421ef98bc3696fd0 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 24 Mar 2017 21:23:04 -0300 Subject: [PATCH 11/60] Add RunPython script to migration --- kfet/migrations/0049_auto_20170325_0110.py | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 kfet/migrations/0049_auto_20170325_0110.py diff --git a/kfet/migrations/0049_auto_20170325_0110.py b/kfet/migrations/0049_auto_20170325_0110.py new file mode 100644 index 00000000..0620dd7a --- /dev/null +++ b/kfet/migrations/0049_auto_20170325_0110.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +def adapt_operation_types(apps, schema_editor): + Operation = apps.get_model("kfet", "Operation") + + for ope in Operation.objects.all(): + if (not ope.is_checkout and + ope.type in ['deposit', 'withdraw']): + ope.type = 'edit' + ope.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0048_article_hidden'), + ] + + operations = [ + migrations.AlterField( + model_name='operation', + name='type', + field=models.CharField(choices=[('purchase', 'Achat'), ('deposit', 'Charge'), ('withdraw', 'Retrait'), ('initial', 'Initial'), ('edit', 'Édition')], max_length=8), + ), + migrations.RunPython(adapt_operation_types), + migrations.RemoveField( + model_name='operation', + name='is_checkout', + ), + ] From 5f3f0440840839de8d8ba72768d3671f6f6c1b6a Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 24 Mar 2017 23:27:55 -0300 Subject: [PATCH 12/60] Add revert function to migration --- kfet/migrations/0049_auto_20170325_0110.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/kfet/migrations/0049_auto_20170325_0110.py b/kfet/migrations/0049_auto_20170325_0110.py index 0620dd7a..29d15676 100644 --- a/kfet/migrations/0049_auto_20170325_0110.py +++ b/kfet/migrations/0049_auto_20170325_0110.py @@ -6,12 +6,22 @@ from django.db import migrations, models def adapt_operation_types(apps, schema_editor): Operation = apps.get_model("kfet", "Operation") + edits = Operation.objects.filter( + is_checkout=False, + type__in=['withdraw', 'deposit']) - for ope in Operation.objects.all(): - if (not ope.is_checkout and - ope.type in ['deposit', 'withdraw']): - ope.type = 'edit' - ope.save() + for ope in edits: + ope.type = 'edit' + ope.save() + + +def revert_operation_types(apps, schema_editor): + Operation = apps.get_model("kfet", "Operation") + + for ope in Operation.objects.filter(type='edit'): + ope.type = 'deposit' if ope.amount > 0 else 'withdraw' + ope.is_checkout = False + ope.save() class Migration(migrations.Migration): @@ -26,7 +36,7 @@ class Migration(migrations.Migration): name='type', field=models.CharField(choices=[('purchase', 'Achat'), ('deposit', 'Charge'), ('withdraw', 'Retrait'), ('initial', 'Initial'), ('edit', 'Édition')], max_length=8), ), - migrations.RunPython(adapt_operation_types), + migrations.RunPython(adapt_operation_types, revert_operation_types), migrations.RemoveField( model_name='operation', name='is_checkout', From 794527772fdece6a479705f4d5ec307c0911d72a Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 24 Mar 2017 23:41:33 -0300 Subject: [PATCH 13/60] Add more explicit migration name --- .../{0049_auto_20170325_0110.py => 0049_remove_checkout.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename kfet/migrations/{0049_auto_20170325_0110.py => 0049_remove_checkout.py} (100%) diff --git a/kfet/migrations/0049_auto_20170325_0110.py b/kfet/migrations/0049_remove_checkout.py similarity index 100% rename from kfet/migrations/0049_auto_20170325_0110.py rename to kfet/migrations/0049_remove_checkout.py From ae82c340230a8204e24580dc9d5bcb1d23fc3e85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Delobelle?= Date: Sat, 25 Mar 2017 11:21:21 +0100 Subject: [PATCH 14/60] add missing migration --- kfet/migrations/0048_default_datetime.py | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 kfet/migrations/0048_default_datetime.py diff --git a/kfet/migrations/0048_default_datetime.py b/kfet/migrations/0048_default_datetime.py new file mode 100644 index 00000000..c9bacf1e --- /dev/null +++ b/kfet/migrations/0048_default_datetime.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0047_auto_20170104_1528'), + ] + + operations = [ + migrations.AlterField( + model_name='operationgroup', + name='at', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + migrations.AlterField( + model_name='transfergroup', + name='at', + field=models.DateTimeField(default=django.utils.timezone.now), + ), + ] From bc0affc038e36d5ce820de22cb9393e0ee3db72e Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 25 Mar 2017 09:56:36 -0300 Subject: [PATCH 15/60] Remove autocomplete leftovers --- kfet/templates/kfet/kpsul.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 55caac13..5e5ce87d 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -1062,7 +1062,7 @@ $(document).ready(function() { function askDeposit() { $.confirm({ title: 'Montant de la charge', - content: '', + content: '', backgroundDismiss: true, animation:'top', closeAnimation:'bottom', @@ -1089,7 +1089,7 @@ $(document).ready(function() { function askEdit() { $.confirm({ title: "Montant de l'édition", - content: '', + content: '', backgroundDismiss: true, animation:'top', closeAnimation:'bottom', @@ -1116,7 +1116,7 @@ $(document).ready(function() { function askWithdraw() { $.confirm({ title: 'Montant du retrait', - content: '', + content: '', backgroundDismiss: true, animation:'top', closeAnimation:'bottom', From d8f572bb01d7537e8d0c6bacb7399c9e4b28562d Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 25 Mar 2017 09:57:44 -0300 Subject: [PATCH 16/60] Remove group check in is_checkout --- kfet/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kfet/models.py b/kfet/models.py index db7381e5..f9cc321b 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -569,8 +569,7 @@ class Operation(models.Model): def is_checkout(self): return (self.type == Operation.DEPOSIT or self.type == Operation.WITHDRAW or - (self.type == Operation.PURCHASE and self.group and - self.group.on_acc.is_cash) + (self.type == Operation.PURCHASE and self.group.on_acc.is_cash) ) def __str__(self): From 14e0d8090f4f352a95214c12892beed4c2105111 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 25 Mar 2017 10:01:35 -0300 Subject: [PATCH 17/60] Add euros symbols --- kfet/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kfet/models.py b/kfet/models.py index f9cc321b..77d37317 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -575,10 +575,10 @@ class Operation(models.Model): def __str__(self): templates = { self.PURCHASE: "{nb} {article.name} ({amount}€)", - self.DEPOSIT: "charge ({amount})", - self.WITHDRAW: "retrait ({amount})", - self.INITIAL: "initial ({amount})", - self.EDIT: "édition ({amount})", + self.DEPOSIT: "charge ({amount}€)", + self.WITHDRAW: "retrait ({amount}€)", + self.INITIAL: "initial ({amount}€)", + self.EDIT: "édition ({amount}€)", } return templates[self.type].format(nb=self.article_nb, article=self.article, From f645121fb1c7d6876d6ca0ca2e8d0360ce089525 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 25 Mar 2017 10:39:53 -0300 Subject: [PATCH 18/60] Add error when editing LIQ --- kfet/static/kfet/js/kfet.js | 6 ++++++ kfet/views.py | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index f0e7a316..8236b3e9 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -86,6 +86,12 @@ function getErrorsHtml(data) { content += '
  • Montant invalide
  • '; content += ''; } + if ('account' in data['errors']) { + content += 'Général'; + content += '
      '; + content += '
    • Opération invalide sur le compte '+data['errors']['account']+'
    • '; + content += '
    '; + } return content; } diff --git a/kfet/views.py b/kfet/views.py index 23d3c703..e87dde9b 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1017,7 +1017,8 @@ def kpsul_perform_operations(request): to_articles_stocks[operation.article] -= operation.article_nb else: if operationgroup.on_acc.is_cash: - data['errors']['account'] = 'Charge et retrait impossible sur LIQ' + data['errors']['account'] = 'LIQ' + return JsonResponse(data, status=400) if operation.type != Operation.EDIT: to_checkout_balance += operation.amount operationgroup.amount += operation.amount From 9a081ddae07fb1cb9d711fdb1730fd00cb3a8e93 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 25 Mar 2017 10:43:02 -0300 Subject: [PATCH 19/60] PEP8 on Operation model --- kfet/models.py | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/kfet/models.py b/kfet/models.py index 77d37317..9d4a9536 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -522,7 +522,7 @@ class OperationGroup(models.Model): class Operation(models.Model): PURCHASE = 'purchase' - DEPOSIT = 'deposit' + DEPOSIT = 'deposit' WITHDRAW = 'withdraw' INITIAL = 'initial' EDIT = 'edit' @@ -536,34 +536,34 @@ class Operation(models.Model): ) group = models.ForeignKey( - OperationGroup, on_delete = models.PROTECT, - related_name = "opes") + OperationGroup, on_delete=models.PROTECT, + related_name="opes") type = models.CharField( - choices = TYPE_ORDER_CHOICES, - max_length = choices_length(TYPE_ORDER_CHOICES)) + choices=TYPE_ORDER_CHOICES, + max_length=choices_length(TYPE_ORDER_CHOICES)) amount = models.DecimalField( - max_digits = 6, decimal_places = 2, - blank = True, default = 0) + max_digits=6, decimal_places=2, + blank=True, default=0) # Optional article = models.ForeignKey( - Article, on_delete = models.PROTECT, - related_name = "operations", - blank = True, null = True, default = None) + Article, on_delete=models.PROTECT, + related_name="operations", + blank=True, null=True, default=None) article_nb = models.PositiveSmallIntegerField( - blank = True, null = True, default = None) + blank=True, null=True, default=None) canceled_by = models.ForeignKey( - Account, on_delete = models.PROTECT, - related_name = "+", - blank = True, null = True, default = None) + Account, on_delete=models.PROTECT, + related_name="+", + blank=True, null=True, default=None) canceled_at = models.DateTimeField( - blank = True, null = True, default = None) + blank=True, null=True, default=None) addcost_for = models.ForeignKey( - Account, on_delete = models.PROTECT, - related_name = "addcosts", - blank = True, null = True, default = None) + Account, on_delete=models.PROTECT, + related_name="addcosts", + blank=True, null=True, default=None) addcost_amount = models.DecimalField( - max_digits = 6, decimal_places = 2, - blank = True, null = True, default = None) + max_digits=6, decimal_places=2, + blank=True, null=True, default=None) @property def is_checkout(self): @@ -584,6 +584,7 @@ class Operation(models.Model): article=self.article, amount=self.amount) + class GlobalPermissions(models.Model): class Meta: managed = False From 06a89055c4ed951eb78b8ece725a08efca5387a3 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 25 Mar 2017 10:46:18 -0300 Subject: [PATCH 20/60] Simpler migration --- kfet/migrations/0049_remove_checkout.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/kfet/migrations/0049_remove_checkout.py b/kfet/migrations/0049_remove_checkout.py index 29d15676..8328c9c4 100644 --- a/kfet/migrations/0049_remove_checkout.py +++ b/kfet/migrations/0049_remove_checkout.py @@ -6,13 +6,9 @@ from django.db import migrations, models def adapt_operation_types(apps, schema_editor): Operation = apps.get_model("kfet", "Operation") - edits = Operation.objects.filter( + Operation.objects.filter( is_checkout=False, - type__in=['withdraw', 'deposit']) - - for ope in edits: - ope.type = 'edit' - ope.save() + type__in=['withdraw', 'deposit']).update(type='edit') def revert_operation_types(apps, schema_editor): From 946182f1fef54f72f3498900acfc659765f60ad2 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sun, 26 Mar 2017 15:06:45 -0300 Subject: [PATCH 21/60] Simpler migration revert --- kfet/migrations/0049_remove_checkout.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/kfet/migrations/0049_remove_checkout.py b/kfet/migrations/0049_remove_checkout.py index 8328c9c4..d2eae10c 100644 --- a/kfet/migrations/0049_remove_checkout.py +++ b/kfet/migrations/0049_remove_checkout.py @@ -13,11 +13,9 @@ def adapt_operation_types(apps, schema_editor): def revert_operation_types(apps, schema_editor): Operation = apps.get_model("kfet", "Operation") - - for ope in Operation.objects.filter(type='edit'): - ope.type = 'deposit' if ope.amount > 0 else 'withdraw' - ope.is_checkout = False - ope.save() + edits = Operation.objects.filter(type='edit') + edits.filter(amount__gt=0).update(type='deposit') + edits.filter(amount__lte=0).update(type='withdraw') class Migration(migrations.Migration): From 579711ed2dbf7c4902437d4ba1105b99bf2d38ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Wed, 29 Mar 2017 11:17:34 +0100 Subject: [PATCH 22/60] Hotfix: typo in template name --- gestioncof/petits_cours_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gestioncof/petits_cours_views.py b/gestioncof/petits_cours_views.py index 95fdf3b7..332e156c 100644 --- a/gestioncof/petits_cours_views.py +++ b/gestioncof/petits_cours_views.py @@ -215,7 +215,7 @@ def _traitement_other(request, demande, redo): proposals = proposals.items() proposed_for = proposed_for.items() return render(request, - "gestiocof/traitement_demande_petit_cours_autre_niveau.html", + "gestioncof/traitement_demande_petit_cours_autre_niveau.html", {"demande": demande, "unsatisfied": unsatisfied, "proposals": proposals, From ca6e120a38b817907bdefb824c3e1d66585bc0a0 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Wed, 29 Mar 2017 09:43:10 -0300 Subject: [PATCH 23/60] Fix ws stock update --- kfet/templates/kfet/kpsul.html | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index d3b148c9..b6ebbf45 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -1350,10 +1350,19 @@ $(document).ready(function() { } for (var i=0; i= -5) { + articles_container.find('#data-article-'+article['id']) + .addClass('low-stock'); + } else { + articles_container.find('#data-article-'+article['id']) + .removeClass('low-stock'); + } articles_container.find('#data-article-'+article['id']+' .stock') .text(article['stock']); + + var i = 0; + while (i < articlesList.length && articlesList[i][1] != article.id) i++ ; + articlesList[i][4] = article.stock ; } if (data['addcost']) { settings['addcost_for'] = data['addcost']['for']; From 2d22c202e9af4c53b2d2de18ae6736621c2bde64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Wed, 29 Mar 2017 18:17:20 +0100 Subject: [PATCH 24/60] Prevent side effects while sending emails --- bda/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bda/models.py b/bda/models.py index a405a665..df73a209 100644 --- a/bda/models.py +++ b/bda/models.py @@ -315,10 +315,11 @@ class SpectacleRevente(models.Model): # Envoie un mail aux perdants for inscrit in inscrits: if inscrit != winner: - context['acheteur'] = inscrit.user + new_context = dict(context) + new_context['acheteur'] = inscrit.user datatuple.append(( 'bda-revente-loser', - context, + new_context, settings.MAIL_DATA['revente']['FROM'], [inscrit.user.email] )) From 95c59de4fcf1f5793c4e0c21ea1565e81db4d58e Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Wed, 29 Mar 2017 23:16:40 -0300 Subject: [PATCH 25/60] Add supplier to dev data --- kfet/management/commands/loadkfetdevdata.py | 23 +++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/kfet/management/commands/loadkfetdevdata.py b/kfet/management/commands/loadkfetdevdata.py index 8eee85a9..5c2533b8 100644 --- a/kfet/management/commands/loadkfetdevdata.py +++ b/kfet/management/commands/loadkfetdevdata.py @@ -12,7 +12,8 @@ from django.core.management import call_command from gestioncof.management.base import MyBaseCommand from gestioncof.models import CofProfile -from kfet.models import (Account, Checkout, CheckoutStatement) +from kfet.models import (Account, Checkout, CheckoutStatement, Supplier, + SupplierArticle, Article) # Où sont stockés les fichiers json DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), @@ -125,8 +126,26 @@ class Command(MyBaseCommand): amount_error=0 ) + # --- + # Fournisseur + # --- + + supplier, created = Supplier.objects.get_or_create(name="Panoramix") + if created: + articles = Article.objects.filter(category__name="Autres Bieres") + if not articles.exists(): + articles = random.sample(Article.objects.all(), 30) + to_create = [] + for article in articles: + to_create.append(SupplierArticle( + supplier=supplier, + article=article + )) + + SupplierArticle.objects.bulk_create(to_create) + # --- # Opérations # --- - call_command('createopes','100', '7') + call_command('createopes', '100', '7', '--transfers=20') From 56b5fd627971deaff5698f8334bb119d3dfce472 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Wed, 29 Mar 2017 23:22:18 -0300 Subject: [PATCH 26/60] Always randomize supplier articles --- kfet/management/commands/loadkfetdevdata.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/kfet/management/commands/loadkfetdevdata.py b/kfet/management/commands/loadkfetdevdata.py index 5c2533b8..7f2ec9a3 100644 --- a/kfet/management/commands/loadkfetdevdata.py +++ b/kfet/management/commands/loadkfetdevdata.py @@ -132,9 +132,7 @@ class Command(MyBaseCommand): supplier, created = Supplier.objects.get_or_create(name="Panoramix") if created: - articles = Article.objects.filter(category__name="Autres Bieres") - if not articles.exists(): - articles = random.sample(Article.objects.all(), 30) + articles = random.sample(list(Article.objects.all()), 40) to_create = [] for article in articles: to_create.append(SupplierArticle( From 079666c0db3854ca0ade03290804aaf784da0053 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Thu, 30 Mar 2017 19:32:11 -0300 Subject: [PATCH 27/60] Use verbose names and form snippets for article_create --- kfet/forms.py | 4 ++++ kfet/models.py | 12 ++++++---- kfet/templates/kfet/article_create.html | 26 ++++++++++++++------- kfet/templates/kfet/form_field_snippet.html | 1 + 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/kfet/forms.py b/kfet/forms.py index 2b59e1b3..8bf9fbcf 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -235,16 +235,20 @@ class CheckoutStatementUpdateForm(forms.ModelForm): class ArticleForm(forms.ModelForm): category_new = forms.CharField( + label="Créer une catégorie", max_length=45, required = False) category = forms.ModelChoiceField( + label="Catégorie", queryset = ArticleCategory.objects.all(), required = False) suppliers = forms.ModelMultipleChoiceField( + label="Fournisseurs", queryset = Supplier.objects.all(), required = False) supplier_new = forms.CharField( + label="Créer un fournisseur", max_length = 45, required = False) diff --git a/kfet/models.py b/kfet/models.py index b4af61c1..92a547c1 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -336,19 +336,21 @@ class ArticleCategory(models.Model): @python_2_unicode_compatible class Article(models.Model): - name = models.CharField(max_length = 45) - is_sold = models.BooleanField(default = True) - hidden = models.BooleanField(default=False, + name = models.CharField("nom", max_length = 45) + is_sold = models.BooleanField("en vente", default = True) + hidden = models.BooleanField("caché", + default=False, help_text="Si oui, ne sera pas affiché " "au public ; par exemple " "sur la carte.") price = models.DecimalField( + "prix", max_digits = 6, decimal_places = 2, default = 0) stock = models.IntegerField(default = 0) category = models.ForeignKey( ArticleCategory, on_delete = models.PROTECT, - related_name = "articles") + related_name = "articles", verbose_name='catégorie') BOX_TYPE_CHOICES = ( ("caisse", "caisse"), ("carton", "carton"), @@ -356,10 +358,12 @@ class Article(models.Model): ("fût", "fût"), ) box_type = models.CharField( + "type de contenant", choices = BOX_TYPE_CHOICES, max_length = choices_length(BOX_TYPE_CHOICES), blank = True, null = True, default = None) box_capacity = models.PositiveSmallIntegerField( + "capacité du contenant", blank = True, null = True, default = None) def __str__(self): diff --git a/kfet/templates/kfet/article_create.html b/kfet/templates/kfet/article_create.html index 742756b2..71672f8c 100644 --- a/kfet/templates/kfet/article_create.html +++ b/kfet/templates/kfet/article_create.html @@ -1,17 +1,27 @@ {% extends 'kfet/base.html' %} +{% load widget_tweaks %} +{% load staticfiles %} {% block title %}Nouvel article{% endblock %} {% block content-header-title %}Création d'un article{% endblock %} {% block content %} -
    - {% csrf_token %} - {{ form.as_p }} - {% if not perms.kfet.add_article %} - - {% endif %} - -
    +{% include "kfet/base_messages.html" %} + +
    +
    +
    +
    + {% csrf_token %} + {% include 'kfet/form_snippet.html' with form=form %} + {% if not perms.kfet.add_article %} + {% include 'kfet/form_authentication_snippet.html' %} + {% endif %} + {% include 'kfet/form_submit_snippet.html' with value="Enregistrer" %} +
    +
    +
    +
    {% endblock %} diff --git a/kfet/templates/kfet/form_field_snippet.html b/kfet/templates/kfet/form_field_snippet.html index 92ec385d..705427fe 100644 --- a/kfet/templates/kfet/form_field_snippet.html +++ b/kfet/templates/kfet/form_field_snippet.html @@ -4,6 +4,7 @@
    {{ field|add_class:'form-control' }} + {{field.help_text}} {{ field.errors }}
    From 8e65298c7cd90db23014af1245bf363da44e90f1 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Thu, 30 Mar 2017 19:34:22 -0300 Subject: [PATCH 28/60] Idem for article_update --- kfet/templates/kfet/article_update.html | 26 +++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/kfet/templates/kfet/article_update.html b/kfet/templates/kfet/article_update.html index db0107cc..f4260a97 100644 --- a/kfet/templates/kfet/article_update.html +++ b/kfet/templates/kfet/article_update.html @@ -1,17 +1,27 @@ {% extends 'kfet/base.html' %} +{% load widget_tweaks %} +{% load staticfiles %} {% block title %}Édition de l'article {{ article.name }}{% endblock %} {% block content-header-title %}Article {{ article.name }} - Édition{% endblock %} {% block content %} -
    - {% csrf_token %} - {{ form.as_p }} - {% if not perms.kfet.change_article %} - - {% endif %} - -
    +{% include "kfet/base_messages.html" %} + +
    +
    +
    +
    + {% csrf_token %} + {% include 'kfet/form_snippet.html' with form=form %} + {% if not perms.kfet.add_article %} + {% include 'kfet/form_authentication_snippet.html' %} + {% endif %} + {% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %} +
    +
    +
    +
    {% endblock %} From 91d5fe234c9172a6e3702ea017ffdbe314529dd7 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Thu, 30 Mar 2017 21:10:50 -0300 Subject: [PATCH 29/60] Idem for checkoutstatements --- kfet/models.py | 48 +++++++++++-------- kfet/templates/kfet/article_update.html | 2 +- .../kfet/checkoutstatement_create.html | 2 +- .../kfet/checkoutstatement_update.html | 12 ++--- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/kfet/models.py b/kfet/models.py index 92a547c1..624f9deb 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -273,29 +273,35 @@ class CheckoutStatement(models.Model): checkout = models.ForeignKey( Checkout, on_delete = models.PROTECT, related_name = "statements") - balance_old = models.DecimalField(max_digits = 6, decimal_places = 2) - balance_new = models.DecimalField(max_digits = 6, decimal_places = 2) - amount_taken = models.DecimalField(max_digits = 6, decimal_places = 2) - amount_error = models.DecimalField(max_digits = 6, decimal_places = 2) + balance_old = models.DecimalField("Ancienne balance", + max_digits = 6, decimal_places = 2) + balance_new = models.DecimalField("Nouvelle balance", + max_digits = 6, decimal_places = 2) + amount_taken = models.DecimalField("Montant pris", + max_digits = 6, decimal_places = 2) + amount_error = models.DecimalField("Montant de l'erreur", + max_digits = 6, decimal_places = 2) at = models.DateTimeField(auto_now_add = True) - not_count = models.BooleanField(default=False) + not_count = models.BooleanField("Caisse non comptée", default=False) - taken_001 = models.PositiveSmallIntegerField(default=0) - taken_002 = models.PositiveSmallIntegerField(default=0) - taken_005 = models.PositiveSmallIntegerField(default=0) - taken_01 = models.PositiveSmallIntegerField(default=0) - taken_02 = models.PositiveSmallIntegerField(default=0) - taken_05 = models.PositiveSmallIntegerField(default=0) - taken_1 = models.PositiveSmallIntegerField(default=0) - taken_2 = models.PositiveSmallIntegerField(default=0) - taken_5 = models.PositiveSmallIntegerField(default=0) - taken_10 = models.PositiveSmallIntegerField(default=0) - taken_20 = models.PositiveSmallIntegerField(default=0) - taken_50 = models.PositiveSmallIntegerField(default=0) - taken_100 = models.PositiveSmallIntegerField(default=0) - taken_200 = models.PositiveSmallIntegerField(default=0) - taken_500 = models.PositiveSmallIntegerField(default=0) - taken_cheque = models.DecimalField(default=0, max_digits=6, decimal_places=2) + taken_001 = models.PositiveSmallIntegerField("Pièces de 1¢", default=0) + taken_002 = models.PositiveSmallIntegerField("Pièces de 2¢", default=0) + taken_005 = models.PositiveSmallIntegerField("Pièces de 5¢", default=0) + taken_01 = models.PositiveSmallIntegerField("Pièces de 10¢", default=0) + taken_02 = models.PositiveSmallIntegerField("Pièces de 20¢", default=0) + taken_05 = models.PositiveSmallIntegerField("Pièces de 50¢", default=0) + taken_1 = models.PositiveSmallIntegerField("Pièces de 1€", default=0) + taken_2 = models.PositiveSmallIntegerField("Pièces de 2€", default=0) + taken_5 = models.PositiveSmallIntegerField("Billets de 5€", default=0) + taken_10 = models.PositiveSmallIntegerField("Billets de 10€", default=0) + taken_20 = models.PositiveSmallIntegerField("Billets de 20€", default=0) + taken_50 = models.PositiveSmallIntegerField("Billets de 50€", default=0) + taken_100 = models.PositiveSmallIntegerField("Billets de 100€", default=0) + taken_200 = models.PositiveSmallIntegerField("Billets de 200€", default=0) + taken_500 = models.PositiveSmallIntegerField("Billets de 500€", default=0) + taken_cheque = models.DecimalField( + "Montant des chèques", + default=0, max_digits=6, decimal_places=2) def __str__(self): return '%s %s' % (self.checkout, self.at) diff --git a/kfet/templates/kfet/article_update.html b/kfet/templates/kfet/article_update.html index f4260a97..85a29f6b 100644 --- a/kfet/templates/kfet/article_update.html +++ b/kfet/templates/kfet/article_update.html @@ -15,7 +15,7 @@
    {% csrf_token %} {% include 'kfet/form_snippet.html' with form=form %} - {% if not perms.kfet.add_article %} + {% if not perms.kfet.change_article %} {% include 'kfet/form_authentication_snippet.html' %} {% endif %} {% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %} diff --git a/kfet/templates/kfet/checkoutstatement_create.html b/kfet/templates/kfet/checkoutstatement_create.html index 0edb66ad..8ed57017 100644 --- a/kfet/templates/kfet/checkoutstatement_create.html +++ b/kfet/templates/kfet/checkoutstatement_create.html @@ -80,7 +80,7 @@ - Chèque: +

    Chèque:

    diff --git a/kfet/templates/kfet/checkoutstatement_update.html b/kfet/templates/kfet/checkoutstatement_update.html index 18d8938f..fd35b959 100644 --- a/kfet/templates/kfet/checkoutstatement_update.html +++ b/kfet/templates/kfet/checkoutstatement_update.html @@ -15,15 +15,15 @@ Caisse {{ checkout.name }} - Modification relevé {{ checkoutstatement.at }}
    {% include 'kfet/base_messages.html' %} -
    -
    - +
    +
    + {% csrf_token %} - {{ form.as_p }} + {% include 'kfet/form_snippet.html' with form=form %} {% if not perms.kfet.change_checkoutstatement %} - + {% include 'kfet/form_authentication_snippet.html' %} {% endif %} - + {% include 'kfet/form_submit_snippet.html' with value="Enregistrer" %}
    From c1b8849cb899eca3203143af6d98180d7c310576 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Thu, 30 Mar 2017 21:16:47 -0300 Subject: [PATCH 30/60] Idem for supplier_form --- kfet/models.py | 10 +++++----- kfet/templates/kfet/supplier_form.html | 24 ++++++++++++++++-------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/kfet/models.py b/kfet/models.py index 624f9deb..1fec62a0 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -427,11 +427,11 @@ class Supplier(models.Model): Article, through = 'SupplierArticle', related_name = "suppliers") - name = models.CharField(max_length = 45) - address = models.TextField() - email = models.EmailField() - phone = models.CharField(max_length = 10) - comment = models.TextField() + name = models.CharField("nom", max_length = 45) + address = models.TextField("adresse") + email = models.EmailField("adresse mail") + phone = models.CharField("téléphone", max_length = 10) + comment = models.TextField("commentaire") def __str__(self): return self.name diff --git a/kfet/templates/kfet/supplier_form.html b/kfet/templates/kfet/supplier_form.html index d1e10a5c..168f74d9 100644 --- a/kfet/templates/kfet/supplier_form.html +++ b/kfet/templates/kfet/supplier_form.html @@ -1,4 +1,6 @@ {% extends 'kfet/base.html' %} +{% load widget_tweaks %} +{% load staticfiles %} {% block title %}Fournisseur - Modification{% endblock %} {% block content-header-title %}Fournisseur - Modification{% endblock %} @@ -7,13 +9,19 @@ {% include 'kfet/base_messages.html' %} -
    - {% csrf_token %} - {{ form.as_p }} - {% if not perms.kfet.change_supplier %} - - {% endif %} - -
    +
    +
    +
    +
    + {% csrf_token %} + {% include 'kfet/form_snippet.html' with form=form %} + {% if not perms.kfet.change_supplier %} + {% include 'kfet/form_authentication_snippet.html' %} + {% endif %} + {% include 'kfet/form_submit_snippet.html' with value="Mettre à jour" %} +
    +
    +
    +
    {% endblock %} From 20561eb515a6f78a52e1b00fe4e24ab904d12539 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Thu, 30 Mar 2017 21:31:16 -0300 Subject: [PATCH 31/60] Idem for account_update --- kfet/forms.py | 3 +++ kfet/models.py | 9 +++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/kfet/forms.py b/kfet/forms.py index 8bf9fbcf..600367f7 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -74,8 +74,10 @@ class AccountRestrictForm(AccountForm): class AccountPwdForm(forms.Form): pwd1 = forms.CharField( + label="Mot de passe K-Fêt", widget=forms.PasswordInput) pwd2 = forms.CharField( + label="Confirmer le mot de passe", widget=forms.PasswordInput) def clean(self): @@ -128,6 +130,7 @@ class UserRestrictTeamForm(UserForm): class UserGroupForm(forms.ModelForm): groups = forms.ModelMultipleChoiceField( Group.objects.filter(name__icontains='K-Fêt'), + label='Statut équipe', required=False) def clean_groups(self): diff --git a/kfet/models.py b/kfet/models.py index 1fec62a0..907bf047 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -40,7 +40,7 @@ class Account(models.Model): balance = models.DecimalField( max_digits = 6, decimal_places = 2, default = 0) - is_frozen = models.BooleanField(default = False) + is_frozen = models.BooleanField("est gelé", default = False) created_at = models.DateTimeField(auto_now_add = True, null = True) # Optional PROMO_CHOICES = [(r,r) for r in range(1980, date.today().year+1)] @@ -48,6 +48,7 @@ class Account(models.Model): choices = PROMO_CHOICES, blank = True, null = True, default = default_promo()) nickname = models.CharField( + "surnom(s)", max_length = 255, blank = True, default = "") password = models.CharField( @@ -224,14 +225,18 @@ class AccountNegative(models.Model): start = models.DateTimeField( blank = True, null = True, default = None) balance_offset = models.DecimalField( + "décalage de balance", + help_text="Montant non compris dans l'autorisation de négatif", max_digits = 6, decimal_places = 2, blank = True, null = True, default = None) authz_overdraft_amount = models.DecimalField( + "négatif autorisé", max_digits = 6, decimal_places = 2, blank = True, null = True, default = None) authz_overdraft_until = models.DateTimeField( + "expiration du négatif", blank = True, null = True, default = None) - comment = models.CharField(max_length = 255, blank = True) + comment = models.CharField("commentaire", max_length = 255, blank = True) @python_2_unicode_compatible class Checkout(models.Model): From ebf133d9af276dde656bb2241a52745c4947d737 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Thu, 30 Mar 2017 21:38:16 -0300 Subject: [PATCH 32/60] Lowercase model verbose names --- kfet/models.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/kfet/models.py b/kfet/models.py index 907bf047..29ae57b6 100644 --- a/kfet/models.py +++ b/kfet/models.py @@ -278,34 +278,34 @@ class CheckoutStatement(models.Model): checkout = models.ForeignKey( Checkout, on_delete = models.PROTECT, related_name = "statements") - balance_old = models.DecimalField("Ancienne balance", + balance_old = models.DecimalField("ancienne balance", max_digits = 6, decimal_places = 2) - balance_new = models.DecimalField("Nouvelle balance", + balance_new = models.DecimalField("nouvelle balance", max_digits = 6, decimal_places = 2) - amount_taken = models.DecimalField("Montant pris", + amount_taken = models.DecimalField("montant pris", max_digits = 6, decimal_places = 2) - amount_error = models.DecimalField("Montant de l'erreur", + amount_error = models.DecimalField("montant de l'erreur", max_digits = 6, decimal_places = 2) at = models.DateTimeField(auto_now_add = True) - not_count = models.BooleanField("Caisse non comptée", default=False) + not_count = models.BooleanField("caisse non comptée", default=False) - taken_001 = models.PositiveSmallIntegerField("Pièces de 1¢", default=0) - taken_002 = models.PositiveSmallIntegerField("Pièces de 2¢", default=0) - taken_005 = models.PositiveSmallIntegerField("Pièces de 5¢", default=0) - taken_01 = models.PositiveSmallIntegerField("Pièces de 10¢", default=0) - taken_02 = models.PositiveSmallIntegerField("Pièces de 20¢", default=0) - taken_05 = models.PositiveSmallIntegerField("Pièces de 50¢", default=0) - taken_1 = models.PositiveSmallIntegerField("Pièces de 1€", default=0) - taken_2 = models.PositiveSmallIntegerField("Pièces de 2€", default=0) - taken_5 = models.PositiveSmallIntegerField("Billets de 5€", default=0) - taken_10 = models.PositiveSmallIntegerField("Billets de 10€", default=0) - taken_20 = models.PositiveSmallIntegerField("Billets de 20€", default=0) - taken_50 = models.PositiveSmallIntegerField("Billets de 50€", default=0) - taken_100 = models.PositiveSmallIntegerField("Billets de 100€", default=0) - taken_200 = models.PositiveSmallIntegerField("Billets de 200€", default=0) - taken_500 = models.PositiveSmallIntegerField("Billets de 500€", default=0) + taken_001 = models.PositiveSmallIntegerField("pièces de 1¢", default=0) + taken_002 = models.PositiveSmallIntegerField("pièces de 2¢", default=0) + taken_005 = models.PositiveSmallIntegerField("pièces de 5¢", default=0) + taken_01 = models.PositiveSmallIntegerField("pièces de 10¢", default=0) + taken_02 = models.PositiveSmallIntegerField("pièces de 20¢", default=0) + taken_05 = models.PositiveSmallIntegerField("pièces de 50¢", default=0) + taken_1 = models.PositiveSmallIntegerField("pièces de 1€", default=0) + taken_2 = models.PositiveSmallIntegerField("pièces de 2€", default=0) + taken_5 = models.PositiveSmallIntegerField("billets de 5€", default=0) + taken_10 = models.PositiveSmallIntegerField("billets de 10€", default=0) + taken_20 = models.PositiveSmallIntegerField("billets de 20€", default=0) + taken_50 = models.PositiveSmallIntegerField("billets de 50€", default=0) + taken_100 = models.PositiveSmallIntegerField("billets de 100€", default=0) + taken_200 = models.PositiveSmallIntegerField("billets de 200€", default=0) + taken_500 = models.PositiveSmallIntegerField("billets de 500€", default=0) taken_cheque = models.DecimalField( - "Montant des chèques", + "montant des chèques", default=0, max_digits=6, decimal_places=2) def __str__(self): From c4a3c1a92ae27e1f4be6d70790cd515b4a163bbc Mon Sep 17 00:00:00 2001 From: Hugo Roussille Date: Fri, 31 Mar 2017 02:51:58 +0200 Subject: [PATCH 33/60] Formatage du code selon PEP-8 --- bda/views.py | 77 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/bda/views.py b/bda/views.py index 34733bec..ab12dfa9 100644 --- a/bda/views.py +++ b/bda/views.py @@ -5,11 +5,10 @@ import hashlib import time import json from datetime import timedelta -from more_itertools import unique_everseen from custommail.shortcuts import ( send_mass_custom_mail, send_custom_mail, render_custom_mail ) - +from more_itertools import unique_everseen from django.shortcuts import render, get_object_or_404 from django.contrib.auth.decorators import login_required from django.contrib import messages @@ -17,7 +16,8 @@ from django.db import models, transaction from django.core import serializers from django.db.models import Count, Q, Sum from django.forms.models import inlineformset_factory -from django.http import HttpResponseBadRequest, HttpResponseRedirect, HttpResponse +from django.http import HttpResponseBadRequest, HttpResponseRedirect,\ + HttpResponse from django.core.urlresolvers import reverse from django.conf import settings from django.utils import timezone, formats @@ -642,7 +642,8 @@ def descriptions_spectacles(request, tirage_id): "La variable GET 'location' doit contenir un entier") return render(request, 'descriptions.html', {'shows': shows_qs.all()}) -def catalogue(request,request_type): + +def catalogue(request, request_type): """ Vue destinée à communiquer avec un client AJAX, fournissant soit : - la liste des tirages @@ -650,23 +651,33 @@ def catalogue(request,request_type): - les descriptions d'un tirage (filtrées selon la catégorie et la salle) """ if request_type == "list": - # Dans ce cas on retourne la liste des tirages et de leur id sous forme de JSON - data_return = [{'id':tirage.id, 'title':tirage.title} for tirage in Tirage.objects.filter(appear_catalogue = True).all()] + # Dans ce cas on retourne la liste des tirages et de leur id en JSON + data_return = [ + {'id': tirage.id, 'title': tirage.title} + for tirage in Tirage.objects.filter(appear_catalogue=True).all() + ] return HttpResponse(json.dumps(data_return)) if request_type == "details": - # Dans ce cas on retourne une liste des catégories et des salles du tirage - try : + # Dans ce cas on retourne une liste des catégories et des salles + try: tirage_id = request.GET.get('id', '') tirage = get_object_or_404(Tirage, id=tirage_id) except: return HttpResponseBadRequest() - categories = list(unique_everseen([str(spectacle.category) for spectacle in tirage.spectacle_set.all()])) + categories = list(unique_everseen([ + str(spectacle.category) + for spectacle in tirage.spectacle_set.all() + ])) categories.remove('None') - locations = list(unique_everseen([str(spectacle.location) for spectacle in tirage.spectacle_set.all()])) - data_return = [{'categories':categories, 'locations':locations}] + locations = list(unique_everseen([ + str(spectacle.location) + for spectacle in tirage.spectacle_set.all() + ])) + data_return = [{'categories': categories, 'locations': locations}] return HttpResponse(json.dumps(data_return)) if request_type == "descriptions": - # Ici on retourne les descriptions correspondant à la catégorie et à la salle spécifiées + # Ici on retourne les descriptions correspondant à la catégorie et + # à la salle spécifiées locations = {} for salle in Salle.objects.all(): locations[salle.name] = salle.id @@ -680,20 +691,48 @@ def catalogue(request,request_type): if category_name: shows_qs = shows_qs.filter(category__name=category_name) if location_name: - shows_qs = shows_qs.filter(location__id=locations[location_name]) + shows_qs = shows_qs.filter( + location__id=locations[location_name]) except: - return HttpResponseBadRequest("Impossible de trouver des résultats correspondant à ces caractéristiques") + return HttpResponseBadRequest( + "Impossible de trouver des résultats correspondant \ + à ces caractéristiques") def getImgUrl(obj): - """ Cette fonction permet de gérer les cas où il n'y a pas d'image """ + """ + Cette fonction permet de gérer les cas où il n'y a pas d'image + """ try: return obj.image.url except: return '' - specquotes = lambda spectacle: [{'author': str(quote.author), 'text': str(quote.text)} for quote in Quote.objects.filter(spectacle = spectacle).all()] + def specquotes(spectacle): [ + {'author': str(quote.author), 'text': str(quote.text)} + for quote in Quote.objects.filter(spectacle = spectacle).all() + ] - # On convertit les descriptions à envoyer en une liste facilement JSONifiable (il devrait y avoir un moyen plus efficace en redéfinissant le serializer de JSON) - data_return = [{'title': spectacle.title, 'category': str(spectacle.category), 'date': str(formats.date_format(timezone.localtime(spectacle.date), "SHORT_DATETIME_FORMAT")), 'location': str(spectacle.location), 'vips': spectacle.vips, 'description': spectacle.description, 'slots_description': spectacle.slots_description, 'quotes': specquotes(spectacle),'image': getImgUrl(spectacle), 'ext_link': spectacle.ext_link, 'price': spectacle.price, 'slots': spectacle.slots} for spectacle in shows_qs.all()] + # On convertit les descriptions à envoyer en une liste facilement + # JSONifiable (il devrait y avoir un moyen plus efficace en + # redéfinissant le serializer de JSON) + data_return = [{ + 'title': spectacle.title, + 'category': str(spectacle.category), + 'date': str(formats.date_format( + timezone.localtime(spectacle.date), + "SHORT_DATETIME_FORMAT")), + 'location': str(spectacle.location), + 'vips': spectacle.vips, + 'description': spectacle.description, + 'slots_description': spectacle.slots_description, + 'quotes': specquotes(spectacle), + 'image': getImgUrl(spectacle), + 'ext_link': spectacle.ext_link, + 'price': spectacle.price, + 'slots': spectacle.slots + } + for spectacle in shows_qs.all() + ] return HttpResponse(json.dumps(data_return)) - return HttpResponseBadRequest() # Si la requête n'est pas de la forme attendue, on quitte avec une erreur + # Si la requête n'est pas de la forme attendue, on quitte avec une erreur + return HttpResponseBadRequest() From a9c8de7544740f222bea039b5af6ead2cfa70f39 Mon Sep 17 00:00:00 2001 From: Hugo Roussille Date: Fri, 31 Mar 2017 03:15:40 +0200 Subject: [PATCH 34/60] Utilisation des JsonResponse --- bda/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bda/views.py b/bda/views.py index ab12dfa9..0f9447ff 100644 --- a/bda/views.py +++ b/bda/views.py @@ -17,7 +17,7 @@ from django.core import serializers from django.db.models import Count, Q, Sum from django.forms.models import inlineformset_factory from django.http import HttpResponseBadRequest, HttpResponseRedirect,\ - HttpResponse + JsonResponse, HttpResponse from django.core.urlresolvers import reverse from django.conf import settings from django.utils import timezone, formats @@ -656,7 +656,7 @@ def catalogue(request, request_type): {'id': tirage.id, 'title': tirage.title} for tirage in Tirage.objects.filter(appear_catalogue=True).all() ] - return HttpResponse(json.dumps(data_return)) + return JsonResponse(data_return, safe=False) if request_type == "details": # Dans ce cas on retourne une liste des catégories et des salles try: @@ -674,7 +674,7 @@ def catalogue(request, request_type): for spectacle in tirage.spectacle_set.all() ])) data_return = [{'categories': categories, 'locations': locations}] - return HttpResponse(json.dumps(data_return)) + return JsonResponse(data_return, safe=False) if request_type == "descriptions": # Ici on retourne les descriptions correspondant à la catégorie et # à la salle spécifiées @@ -733,6 +733,6 @@ def catalogue(request, request_type): } for spectacle in shows_qs.all() ] - return HttpResponse(json.dumps(data_return)) + return JsonResponse(data_return, safe=False) # Si la requête n'est pas de la forme attendue, on quitte avec une erreur return HttpResponseBadRequest() From f3b9266e359d5a39a656ec4e2fce1a4f5907e6c3 Mon Sep 17 00:00:00 2001 From: Hugo Roussille Date: Fri, 31 Mar 2017 03:35:09 +0200 Subject: [PATCH 35/60] =?UTF-8?q?Simplification=20du=20code=20avec=20des?= =?UTF-8?q?=20m=C3=A9thodes=20de=20Django?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bda/views.py | 37 ++++++++++++++----------------------- provisioning/bootstrap.sh | 1 - 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/bda/views.py b/bda/views.py index 0f9447ff..95429a25 100644 --- a/bda/views.py +++ b/bda/views.py @@ -3,12 +3,10 @@ import random import hashlib import time -import json from datetime import timedelta from custommail.shortcuts import ( send_mass_custom_mail, send_custom_mail, render_custom_mail ) -from more_itertools import unique_everseen from django.shortcuts import render, get_object_or_404 from django.contrib.auth.decorators import login_required from django.contrib import messages @@ -17,7 +15,7 @@ from django.core import serializers from django.db.models import Count, Q, Sum from django.forms.models import inlineformset_factory from django.http import HttpResponseBadRequest, HttpResponseRedirect,\ - JsonResponse, HttpResponse + JsonResponse from django.core.urlresolvers import reverse from django.conf import settings from django.utils import timezone, formats @@ -25,7 +23,7 @@ from django.views.generic.list import ListView from gestioncof.decorators import cof_required, buro_required from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\ - Tirage, SpectacleRevente, Salle, Quote + Tirage, SpectacleRevente, Salle, Quote, CategorieSpectacle from bda.algorithm import Algorithm from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\ InscriptionReventeForm, SoldForm @@ -652,10 +650,8 @@ def catalogue(request, request_type): """ if request_type == "list": # Dans ce cas on retourne la liste des tirages et de leur id en JSON - data_return = [ - {'id': tirage.id, 'title': tirage.title} - for tirage in Tirage.objects.filter(appear_catalogue=True).all() - ] + data_return = list( + Tirage.objects.filter(appear_catalogue=True).values('id', 'title')) return JsonResponse(data_return, safe=False) if request_type == "details": # Dans ce cas on retourne une liste des catégories et des salles @@ -664,15 +660,14 @@ def catalogue(request, request_type): tirage = get_object_or_404(Tirage, id=tirage_id) except: return HttpResponseBadRequest() - categories = list(unique_everseen([ - str(spectacle.category) - for spectacle in tirage.spectacle_set.all() - ])) - categories.remove('None') - locations = list(unique_everseen([ - str(spectacle.location) - for spectacle in tirage.spectacle_set.all() - ])) + categories = list( + CategorieSpectacle.objects.filter( + spectacle__in=tirage.spectacle_set.all()) + .distinct()) + locations = list( + Salle.objects.filter( + spectacle__in=tirage.spectacle_set.all()) + .distinct().values_list('name', flat=True)) data_return = [{'categories': categories, 'locations': locations}] return JsonResponse(data_return, safe=False) if request_type == "descriptions": @@ -707,11 +702,6 @@ def catalogue(request, request_type): except: return '' - def specquotes(spectacle): [ - {'author': str(quote.author), 'text': str(quote.text)} - for quote in Quote.objects.filter(spectacle = spectacle).all() - ] - # On convertit les descriptions à envoyer en une liste facilement # JSONifiable (il devrait y avoir un moyen plus efficace en # redéfinissant le serializer de JSON) @@ -725,7 +715,8 @@ def catalogue(request, request_type): 'vips': spectacle.vips, 'description': spectacle.description, 'slots_description': spectacle.slots_description, - 'quotes': specquotes(spectacle), + 'quotes': list(Quote.objects.filter(spectacle=spectacle).values( + 'author', 'text')), 'image': getImgUrl(spectacle), 'ext_link': spectacle.ext_link, 'price': spectacle.price, diff --git a/provisioning/bootstrap.sh b/provisioning/bootstrap.sh index a08d9282..269e4f25 100644 --- a/provisioning/bootstrap.sh +++ b/provisioning/bootstrap.sh @@ -11,7 +11,6 @@ DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4" apt-get update && apt-get install -y python3-pip python3-dev python3-venv \ libmysqlclient-dev libjpeg-dev git redis-server pip install -U pip -pip install more_itertools # Configuration et installation de mysql. Le mot de passe root est le même que # le mot de passe pour l'utilisateur local - pour rappel, ceci est une instance From 73c21d83ee91507c9c3ba30086314310bd60af4b Mon Sep 17 00:00:00 2001 From: Hugo Roussille Date: Fri, 31 Mar 2017 03:47:32 +0200 Subject: [PATCH 36/60] Correction des exceptions et de l'URL des images --- bda/models.py | 9 +++++++++ bda/views.py | 16 ++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/bda/models.py b/bda/models.py index d60c3c94..04b6d877 100644 --- a/bda/models.py +++ b/bda/models.py @@ -79,6 +79,15 @@ class Spectacle(models.Model): self.price ) + def getImgUrl(self): + """ + Cette fonction permet d'obtenir l'URL de l'image, si elle existe + """ + try: + return self.image.url + except: + return None + def send_rappel(self): """ Envoie un mail de rappel à toutes les personnes qui ont une place pour diff --git a/bda/views.py b/bda/views.py index 95429a25..486de3f2 100644 --- a/bda/views.py +++ b/bda/views.py @@ -658,8 +658,8 @@ def catalogue(request, request_type): try: tirage_id = request.GET.get('id', '') tirage = get_object_or_404(Tirage, id=tirage_id) - except: - return HttpResponseBadRequest() + except ValueError: + return HttpResponseBadRequest("Pas de tirage pour cet id") categories = list( CategorieSpectacle.objects.filter( spectacle__in=tirage.spectacle_set.all()) @@ -688,19 +688,11 @@ def catalogue(request, request_type): if location_name: shows_qs = shows_qs.filter( location__id=locations[location_name]) - except: + except ValueError: return HttpResponseBadRequest( "Impossible de trouver des résultats correspondant \ à ces caractéristiques") - def getImgUrl(obj): - """ - Cette fonction permet de gérer les cas où il n'y a pas d'image - """ - try: - return obj.image.url - except: - return '' # On convertit les descriptions à envoyer en une liste facilement # JSONifiable (il devrait y avoir un moyen plus efficace en @@ -717,7 +709,7 @@ def catalogue(request, request_type): 'slots_description': spectacle.slots_description, 'quotes': list(Quote.objects.filter(spectacle=spectacle).values( 'author', 'text')), - 'image': getImgUrl(spectacle), + 'image': spectacle.getImgUrl(), 'ext_link': spectacle.ext_link, 'price': spectacle.price, 'slots': spectacle.slots From 8cf14d3f6b4e835f0c899c00ee640caa417cc5cf Mon Sep 17 00:00:00 2001 From: Hugo Roussille Date: Fri, 31 Mar 2017 04:33:13 +0200 Subject: [PATCH 37/60] =?UTF-8?q?Possibilit=C3=A9=20de=20filtrer=20sur=20p?= =?UTF-8?q?lusieurs=20salles/cat=C3=A9gories?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bda/views.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/bda/views.py b/bda/views.py index 486de3f2..cee53085 100644 --- a/bda/views.py +++ b/bda/views.py @@ -3,6 +3,7 @@ import random import hashlib import time +import json from datetime import timedelta from custommail.shortcuts import ( send_mass_custom_mail, send_custom_mail, render_custom_mail @@ -663,31 +664,30 @@ def catalogue(request, request_type): categories = list( CategorieSpectacle.objects.filter( spectacle__in=tirage.spectacle_set.all()) - .distinct()) + .distinct().values('id', 'name')) locations = list( Salle.objects.filter( spectacle__in=tirage.spectacle_set.all()) - .distinct().values_list('name', flat=True)) - data_return = [{'categories': categories, 'locations': locations}] + .distinct().values('id', 'name')) + data_return = {'categories': categories, 'locations': locations} return JsonResponse(data_return, safe=False) if request_type == "descriptions": # Ici on retourne les descriptions correspondant à la catégorie et # à la salle spécifiées - locations = {} - for salle in Salle.objects.all(): - locations[salle.name] = salle.id + try: tirage_id = request.GET.get('id', '') - category_name = request.GET.get('category', '') - location_name = request.GET.get('location', '') + category_id = json.loads(request.GET.get('category', '[0]')) + location_id = json.loads(request.GET.get('location', '[0]')) tirage = get_object_or_404(Tirage, id=tirage_id) shows_qs = tirage.spectacle_set - if category_name: - shows_qs = shows_qs.filter(category__name=category_name) - if location_name: + if not(0 in category_id): shows_qs = shows_qs.filter( - location__id=locations[location_name]) + category__id__in=category_id) + if not(0 in location_id): + shows_qs = shows_qs.filter( + location__id__in=location_id) except ValueError: return HttpResponseBadRequest( "Impossible de trouver des résultats correspondant \ From fdcd2e864ce8a7ecc055a04709e648eb765e52f3 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 31 Mar 2017 11:24:38 -0300 Subject: [PATCH 38/60] Use widget_tweaks everywhere --- kfet/forms.py | 16 +++++----------- kfet/templates/kfet/order_create.html | 3 ++- kfet/templates/kfet/order_to_inventory.html | 9 +++++---- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/kfet/forms.py b/kfet/forms.py index 600367f7..5b56c63a 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -485,9 +485,7 @@ class OrderArticleForm(forms.Form): queryset=Article.objects.all(), widget=forms.HiddenInput(), ) - quantity_ordered = forms.IntegerField( - required=False, - widget=forms.NumberInput(attrs={'class': 'form-control'})) + quantity_ordered = forms.IntegerField(required=False) def __init__(self, *args, **kwargs): super(OrderArticleForm, self).__init__(*args, **kwargs) @@ -514,18 +512,14 @@ class OrderArticleToInventoryForm(forms.Form): ) price_HT = forms.DecimalField( max_digits = 7, decimal_places = 4, - required = False, - widget=forms.NumberInput(attrs={'class': 'form-control'})) + required = False) TVA = forms.DecimalField( max_digits = 7, decimal_places = 2, - required = False, - widget=forms.NumberInput(attrs={'class': 'form-control'})) + required = False) rights = forms.DecimalField( max_digits = 7, decimal_places = 4, - required = False, - widget=forms.NumberInput(attrs={'class': 'form-control'})) - quantity_received = forms.IntegerField( - widget=forms.NumberInput(attrs={'class': 'form-control'})) + required = False) + quantity_received = forms.IntegerField() def __init__(self, *args, **kwargs): super(OrderArticleToInventoryForm, self).__init__(*args, **kwargs) diff --git a/kfet/templates/kfet/order_create.html b/kfet/templates/kfet/order_create.html index cbd84ba8..1bd6d54f 100644 --- a/kfet/templates/kfet/order_create.html +++ b/kfet/templates/kfet/order_create.html @@ -1,4 +1,5 @@ {% extends 'kfet/base.html' %} +{% load widget_tweaks %} {% block title %}Nouvelle commande{% endblock %} {% block content-header-title %}Nouvelle commande {{ supplier.name }}{% endblock %} @@ -60,7 +61,7 @@ {{ form.v_prev }} {{ form.stock }} {{ form.c_rec }} - {{ form.quantity_ordered }} + {{ form.quantity_ordered | add_class:"form-control" }} {% endfor %} diff --git a/kfet/templates/kfet/order_to_inventory.html b/kfet/templates/kfet/order_to_inventory.html index ab107065..5fe920e9 100644 --- a/kfet/templates/kfet/order_to_inventory.html +++ b/kfet/templates/kfet/order_to_inventory.html @@ -1,4 +1,5 @@ {% extends 'kfet/base.html' %} +{% load widget_tweaks %} {% block title %}{% endblock %} {% block content-header-title %}{% endblock %} @@ -33,11 +34,11 @@ {{ form.article }} {{ form.name }} - {{ form.price_HT }} - {{ form.TVA }} - {{ form.rights }} + {{ form.price_HT | add_class:"form-control" }} + {{ form.TVA | add_class:"form-control" }} + {{ form.rights | add_class:"form-control" }} {{ form.quantity_ordered }} - {{ form.quantity_received }} + {{ form.quantity_received | add_class:"form-control" }} {% endfor %} From 499c08156577f2e94894d8e9dbfab242d42062c1 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 31 Mar 2017 12:23:27 -0300 Subject: [PATCH 39/60] Websocket class and instance --- kfet/static/kfet/js/kfet.js | 33 +++++++++++++++++++++++++++++++++ kfet/templates/kfet/kpsul.html | 13 ++----------- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index f0e7a316..aa4b7739 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -27,6 +27,39 @@ $(document).ready(function() { } }); +/* + * Generic Websocket class and k-psul ws instanciation + */ + +class Websocket { + + static get default_data() { + return {"relative_url": "", "default_msg": {}}; + } + + constructor(data) { + $.extend(this, this.constructor.default_data, data); + } + + listen(handle_func) { + var that = this; + var websocket_protocol = window.location.protocol == 'https:' ? 'wss' : 'ws'; + var location_host = window.location.host; + var location_url = window.location.pathname.startsWith('/gestion/') ? location_host + '/gestion' : location_host; + var socket = new ReconnectingWebSocket(websocket_protocol+"://" + location_url + this.relative_url); + + socket.onmessage = function(e) { + var data = $.extend({}, that.default_msg, JSON.parse(e.data)); + handle_func(data); + } + } +} + +var OperationWebSocket = new Websocket({ + 'relative_url': '/ws/k-fet/k-psul/', + 'default_msg': {'opegroups':[],'opes':[],'checkouts':[],'articles':[]}, +}); + function dateUTCToParis(date) { return moment.tz(date, 'UTC').tz('Europe/Paris'); } diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index d3b148c9..cf558647 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -12,7 +12,6 @@ - {% endblock %} @@ -1321,15 +1320,7 @@ $(document).ready(function() { // Synchronization // ----- - websocket_msg_default = {'opegroups':[],'opes':[],'checkouts':[],'articles':[]} - - var websocket_protocol = window.location.protocol == 'https:' ? 'wss' : 'ws'; - var location_host = window.location.host; - var location_url = window.location.pathname.startsWith('/gestion/') ? location_host + '/gestion' : location_host; - socket = new ReconnectingWebSocket(websocket_protocol+"://" + location_url + "/ws/k-fet/k-psul/"); - socket.onmessage = function(e) { - data = $.extend({}, websocket_msg_default, JSON.parse(e.data)); - + OperationWebSocket.listen(function(data) { for (var i=0; i Date: Fri, 31 Mar 2017 17:53:35 +0100 Subject: [PATCH 40/60] A migration for the new field `appear_catalogue` --- .../0011_tirage_appear_catalogue.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 bda/migrations/0011_tirage_appear_catalogue.py diff --git a/bda/migrations/0011_tirage_appear_catalogue.py b/bda/migrations/0011_tirage_appear_catalogue.py new file mode 100644 index 00000000..c2a2479d --- /dev/null +++ b/bda/migrations/0011_tirage_appear_catalogue.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('bda', '0010_spectaclerevente_shotgun'), + ] + + operations = [ + migrations.AddField( + model_name='tirage', + name='appear_catalogue', + field=models.BooleanField( + default=False, + verbose_name='Tirage à afficher dans le catalogue' + ), + ), + ] From 67b44219339d034aa40d8d336f223fd5e63bf8d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Fri, 31 Mar 2017 17:54:31 +0100 Subject: [PATCH 41/60] PEP8 does not like `\` --- bda/views.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/bda/views.py b/bda/views.py index cee53085..dbb3f3c0 100644 --- a/bda/views.py +++ b/bda/views.py @@ -15,19 +15,24 @@ from django.db import models, transaction from django.core import serializers from django.db.models import Count, Q, Sum from django.forms.models import inlineformset_factory -from django.http import HttpResponseBadRequest, HttpResponseRedirect,\ - JsonResponse +from django.http import ( + HttpResponseBadRequest, HttpResponseRedirect, JsonResponse +) from django.core.urlresolvers import reverse from django.conf import settings from django.utils import timezone, formats from django.views.generic.list import ListView from gestioncof.decorators import cof_required, buro_required -from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\ - Tirage, SpectacleRevente, Salle, Quote, CategorieSpectacle +from bda.models import ( + Spectacle, Participant, ChoixSpectacle, Attribution, Tirage, + SpectacleRevente, Salle, Quote, CategorieSpectacle +) from bda.algorithm import Algorithm -from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\ - InscriptionReventeForm, SoldForm +from bda.forms import ( + BaseBdaFormSet, TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, + SoldForm +) @cof_required @@ -690,9 +695,8 @@ def catalogue(request, request_type): location__id__in=location_id) except ValueError: return HttpResponseBadRequest( - "Impossible de trouver des résultats correspondant \ - à ces caractéristiques") - + "Impossible de trouver des résultats correspondant " + "à ces caractéristiques") # On convertit les descriptions à envoyer en une liste facilement # JSONifiable (il devrait y avoir un moyen plus efficace en From 1d8e084a196c393d0da7c7753a8378ba06d36b83 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 31 Mar 2017 14:37:00 -0300 Subject: [PATCH 42/60] websocket update when addcost --- kfet/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kfet/views.py b/kfet/views.py index e87dde9b..3c78601f 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1119,7 +1119,7 @@ def kpsul_perform_operations(request): ope_data = { 'id': operation.pk, 'type': operation.type, 'amount': operation.amount, 'addcost_amount': operation.addcost_amount, - 'addcost_for__trigramme': is_addcost and addcost_for.trigramme or None, + 'addcost_for__trigramme': operation.addcost_for and addcost_for.trigramme or None, 'article__name': operation.article and operation.article.name or None, 'article_nb': operation.article_nb, 'group_id': operationgroup.pk, From 49c84076deb79ea21a069b2858047963703b6d29 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 31 Mar 2017 15:01:31 -0300 Subject: [PATCH 43/60] Manage help_text and errors --- kfet/templates/kfet/form_field_snippet.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kfet/templates/kfet/form_field_snippet.html b/kfet/templates/kfet/form_field_snippet.html index 705427fe..34ebe58e 100644 --- a/kfet/templates/kfet/form_field_snippet.html +++ b/kfet/templates/kfet/form_field_snippet.html @@ -4,7 +4,10 @@
    {{ field|add_class:'form-control' }} + {% if field.errors %} + {{field.errors}} + {% else %} {{field.help_text}} - {{ field.errors }} + {% endif %}
    From 413df0806dd170f82c6b33b46179160b7572bfc5 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 31 Mar 2017 15:10:41 -0300 Subject: [PATCH 44/60] Add url get method --- kfet/static/kfet/js/kfet.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index aa4b7739..1398fedb 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -40,13 +40,18 @@ class Websocket { constructor(data) { $.extend(this, this.constructor.default_data, data); } - - listen(handle_func) { - var that = this; + + get url() { var websocket_protocol = window.location.protocol == 'https:' ? 'wss' : 'ws'; var location_host = window.location.host; var location_url = window.location.pathname.startsWith('/gestion/') ? location_host + '/gestion' : location_host; - var socket = new ReconnectingWebSocket(websocket_protocol+"://" + location_url + this.relative_url); + + return websocket_protocol+"://" + location_url + this.relative_url ; + } + + listen(handle_func) { + var that = this; + var socket = new ReconnectingWebSocket(this.url); socket.onmessage = function(e) { var data = $.extend({}, that.default_msg, JSON.parse(e.data)); From 8bf1bd53439919d2792bd82eabc7aef7a9d33e71 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 31 Mar 2017 15:45:15 -0300 Subject: [PATCH 45/60] Websocket as member --- kfet/static/kfet/js/kfet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index 1398fedb..aa6cfa90 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -51,9 +51,9 @@ class Websocket { listen(handle_func) { var that = this; - var socket = new ReconnectingWebSocket(this.url); + this.socket = new ReconnectingWebSocket(this.url); - socket.onmessage = function(e) { + this.socket.onmessage = function(e) { var data = $.extend({}, that.default_msg, JSON.parse(e.data)); handle_func(data); } From 38bfccf3313abb84447c91a17180c7f7abd6779c Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 31 Mar 2017 15:57:15 -0300 Subject: [PATCH 46/60] Change class name --- kfet/static/kfet/js/kfet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index aa6cfa90..b8a44244 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -31,7 +31,7 @@ $(document).ready(function() { * Generic Websocket class and k-psul ws instanciation */ -class Websocket { +class KfetWebsocket { static get default_data() { return {"relative_url": "", "default_msg": {}}; @@ -60,7 +60,7 @@ class Websocket { } } -var OperationWebSocket = new Websocket({ +var OperationWebSocket = new KfetWebsocket({ 'relative_url': '/ws/k-fet/k-psul/', 'default_msg': {'opegroups':[],'opes':[],'checkouts':[],'articles':[]}, }); From ab15dbae76abb6fb9bfb596efdfb5588abcb2586 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 31 Mar 2017 17:37:39 -0300 Subject: [PATCH 47/60] Add handler management --- kfet/static/kfet/js/kfet.js | 19 ++++++++++++++----- kfet/templates/kfet/kpsul.html | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index b8a44244..bdf5b69b 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -33,12 +33,12 @@ $(document).ready(function() { class KfetWebsocket { - static get default_data() { - return {"relative_url": "", "default_msg": {}}; + static get defaults() { + return {"relative_url": "", "default_msg": {}, "handlers": []}; } constructor(data) { - $.extend(this, this.constructor.default_data, data); + $.extend(this, this.constructor.defaults, data); } get url() { @@ -49,13 +49,22 @@ class KfetWebsocket { return websocket_protocol+"://" + location_url + this.relative_url ; } - listen(handle_func) { + add_handler(handler) { + if (!this.socket) + this.listen(); + + this.handlers.push(handler); + } + + listen() { var that = this; this.socket = new ReconnectingWebSocket(this.url); this.socket.onmessage = function(e) { var data = $.extend({}, that.default_msg, JSON.parse(e.data)); - handle_func(data); + for (let handler of that.handlers) { + handler(data); + } } } } diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index cf558647..4ac1354c 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -1320,7 +1320,7 @@ $(document).ready(function() { // Synchronization // ----- - OperationWebSocket.listen(function(data) { + OperationWebSocket.add_handler(function(data) { for (var i=0; i Date: Fri, 31 Mar 2017 18:28:03 -0300 Subject: [PATCH 48/60] Prevents special opes on LIQ --- kfet/static/kfet/js/kfet.js | 3 +++ kfet/views.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/kfet/static/kfet/js/kfet.js b/kfet/static/kfet/js/kfet.js index f0e7a316..6691fef7 100644 --- a/kfet/static/kfet/js/kfet.js +++ b/kfet/static/kfet/js/kfet.js @@ -86,6 +86,9 @@ function getErrorsHtml(data) { content += '
  • Montant invalide
  • '; content += ''; } + if ('account' in data['errors']) { + content += data['errors']['account']; + } return content; } diff --git a/kfet/views.py b/kfet/views.py index b6a3338a..823a205d 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1041,7 +1041,9 @@ def kpsul_perform_operations(request): operationgroup.comment = operationgroup.comment.strip() if not operationgroup.comment: data['errors']['need_comment'] = True - return JsonResponse(data, status=400) + + if data['errors']: + return JsonResponse(data, status=400) if stop or not request.user.has_perms(required_perms): missing_perms = get_missing_perms(required_perms, request.user) From 9c9ad21d735dd71a132054d1c247a5135473476e Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 31 Mar 2017 23:46:10 -0300 Subject: [PATCH 49/60] Add variable for article line --- kfet/templates/kfet/kpsul.html | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html index 9b45f1bf..b4a44492 100644 --- a/kfet/templates/kfet/kpsul.html +++ b/kfet/templates/kfet/kpsul.html @@ -1340,15 +1340,14 @@ $(document).ready(function() { } } for (var i=0; i= -5) { - articles_container.find('#data-article-'+article['id']) - .addClass('low-stock'); + article_line.addClass('low-stock'); } else { - articles_container.find('#data-article-'+article['id']) - .removeClass('low-stock'); + article_line.removeClass('low-stock'); } - articles_container.find('#data-article-'+article['id']+' .stock') + article_line.find('.stock') .text(article['stock']); var i = 0; From cf03fba1cc482a2ee0c8164e4e6d5b7e1544fc28 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 31 Mar 2017 23:54:38 -0300 Subject: [PATCH 50/60] Remove whitespace by using elif --- kfet/templates/kfet/form_field_snippet.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kfet/templates/kfet/form_field_snippet.html b/kfet/templates/kfet/form_field_snippet.html index 34ebe58e..771e4185 100644 --- a/kfet/templates/kfet/form_field_snippet.html +++ b/kfet/templates/kfet/form_field_snippet.html @@ -6,7 +6,7 @@ {{ field|add_class:'form-control' }} {% if field.errors %} {{field.errors}} - {% else %} + {% elif field.help_text %} {{field.help_text}} {% endif %}
    From 271654b44792266fa0fe5950e6795051534bbb6b Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 1 Apr 2017 08:47:09 -0300 Subject: [PATCH 51/60] No need for intermediate error reporting --- kfet/views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/kfet/views.py b/kfet/views.py index f2ea1599..b4d1328b 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1018,7 +1018,6 @@ def kpsul_perform_operations(request): else: if operationgroup.on_acc.is_cash: data['errors']['account'] = 'LIQ' - return JsonResponse(data, status=400) if operation.type != Operation.EDIT: to_checkout_balance += operation.amount operationgroup.amount += operation.amount From 6797c92a1f86e88f33f0128cf05d731fee7b8704 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 1 Apr 2017 09:35:37 -0300 Subject: [PATCH 52/60] Ok ok je me rends --- kfet/templates/kfet/form_field_snippet.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kfet/templates/kfet/form_field_snippet.html b/kfet/templates/kfet/form_field_snippet.html index 771e4185..0873a481 100644 --- a/kfet/templates/kfet/form_field_snippet.html +++ b/kfet/templates/kfet/form_field_snippet.html @@ -6,7 +6,8 @@ {{ field|add_class:'form-control' }} {% if field.errors %} {{field.errors}} - {% elif field.help_text %} + {% endif %} + {% if field.help_text %} {{field.help_text}} {% endif %}
    From bbb517fbd3323ecc97c1936105d65174d2d92cc0 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 1 Apr 2017 10:37:40 -0300 Subject: [PATCH 53/60] Fix article stat display --- kfet/templates/kfet/article_read.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/kfet/templates/kfet/article_read.html b/kfet/templates/kfet/article_read.html index d4219128..35b484a5 100644 --- a/kfet/templates/kfet/article_read.html +++ b/kfet/templates/kfet/article_read.html @@ -101,9 +101,8 @@ {% endblock %} From 6b8001db56e3dc209793a843e1eaf2c3e339ceb4 Mon Sep 17 00:00:00 2001 From: Hugo Roussille Date: Sat, 1 Apr 2017 16:34:17 +0200 Subject: [PATCH 54/60] Correction des messages d'erreur et de la gestion des exceptions --- bda/views.py | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/bda/views.py b/bda/views.py index dbb3f3c0..41e5d08b 100644 --- a/bda/views.py +++ b/bda/views.py @@ -22,7 +22,7 @@ from django.core.urlresolvers import reverse from django.conf import settings from django.utils import timezone, formats from django.views.generic.list import ListView - +from django.core.exceptions import ObjectDoesNotExist from gestioncof.decorators import cof_required, buro_required from bda.models import ( Spectacle, Participant, ChoixSpectacle, Attribution, Tirage, @@ -661,11 +661,17 @@ def catalogue(request, request_type): return JsonResponse(data_return, safe=False) if request_type == "details": # Dans ce cas on retourne une liste des catégories et des salles + tirage_id = request.GET.get('id', '') try: - tirage_id = request.GET.get('id', '') - tirage = get_object_or_404(Tirage, id=tirage_id) + tirage = Tirage.objects.get(id=tirage_id) + except ObjectDoesNotExist: + return HttpResponseBadRequest( + "Aucun tirage correspondant à l'id " + + tirage_id) except ValueError: - return HttpResponseBadRequest("Pas de tirage pour cet id") + return HttpResponseBadRequest( + "Mauvais format d'identifiant : " + + tirage_id) categories = list( CategorieSpectacle.objects.filter( spectacle__in=tirage.spectacle_set.all()) @@ -680,11 +686,13 @@ def catalogue(request, request_type): # Ici on retourne les descriptions correspondant à la catégorie et # à la salle spécifiées + tirage_id = request.GET.get('id', '') + categories = request.GET.get('category', '[0]') + locations = request.GET.get('location', '[0]') try: - tirage_id = request.GET.get('id', '') - category_id = json.loads(request.GET.get('category', '[0]')) - location_id = json.loads(request.GET.get('location', '[0]')) - tirage = get_object_or_404(Tirage, id=tirage_id) + category_id = json.loads(categories) + location_id = json.loads(locations) + tirage = Tirage.objects.get(id=tirage_id) shows_qs = tirage.spectacle_set if not(0 in category_id): @@ -693,10 +701,19 @@ def catalogue(request, request_type): if not(0 in location_id): shows_qs = shows_qs.filter( location__id__in=location_id) - except ValueError: + except ObjectDoesNotExist: return HttpResponseBadRequest( "Impossible de trouver des résultats correspondant " - "à ces caractéristiques") + "à ces caractéristiques : " + + "id = " + tirage_id + + ", catégories = " + categories + + ", salles = " + locations) + except ValueError: # Contient JSONDecodeError + return HttpResponseBadRequest( + "Impossible de parser les paramètres donnés : " + + "id = " + request.GET.get('id', '') + + ", catégories = " + request.GET.get('category', '[0]') + + ", salles = " + request.GET.get('location', '[0]')) # On convertit les descriptions à envoyer en une liste facilement # JSONifiable (il devrait y avoir un moyen plus efficace en From 66dd7848b855d04a1e8e8bd97a844caf5baec5ac Mon Sep 17 00:00:00 2001 From: Hugo Roussille Date: Sat, 1 Apr 2017 16:48:18 +0200 Subject: [PATCH 55/60] Correction du format de l'URL --- bda/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bda/urls.py b/bda/urls.py index 0af14b53..df660740 100644 --- a/bda/urls.py +++ b/bda/urls.py @@ -47,6 +47,6 @@ urlpatterns = [ url(r'^mails-rappel/(?P\d+)$', views.send_rappel), url(r'^descriptions/(?P\d+)$', views.descriptions_spectacles, name='bda-descriptions'), - url(r'^catalogue/(?P[a-z]*)$', views.catalogue, + url(r'^catalogue/(?P[a-z]+)$', views.catalogue, name='bda-catalogue'), ] From cce0411ee935fd0322003a2601192021be7f66a8 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 1 Apr 2017 17:37:29 -0300 Subject: [PATCH 56/60] Merge both 0048 migrations --- kfet/migrations/0049_merge.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 kfet/migrations/0049_merge.py diff --git a/kfet/migrations/0049_merge.py b/kfet/migrations/0049_merge.py new file mode 100644 index 00000000..0ce9a525 --- /dev/null +++ b/kfet/migrations/0049_merge.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0048_article_hidden'), + ('kfet', '0048_default_datetime'), + ] + + operations = [ + ] From 9cdf0640054dc0bf983aab276e5aaa98a2ec6f8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 1 Apr 2017 21:45:05 +0100 Subject: [PATCH 57/60] Handle incomplete values from the LDAP Sometime `uid` is not set in the objects fetched from the LDAP. This case has to be handled. Also, the `.uid` and `.cn` attributes of these objects in the python abstractions have a `.value` method which we should use. --- gestioncof/autocomplete.py | 14 ++++++++++---- kfet/autocomplete.py | 14 ++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/gestioncof/autocomplete.py b/gestioncof/autocomplete.py index a9abbad7..7a9c37d2 100644 --- a/gestioncof/autocomplete.py +++ b/gestioncof/autocomplete.py @@ -14,6 +14,10 @@ from gestioncof.decorators import buro_required class Clipper(object): def __init__(self, clipper, fullname): + if fullname is None: + fullname = "" + assert isinstance(clipper, str) + assert isinstance(fullname, str) self.clipper = clipper self.fullname = fullname @@ -62,17 +66,19 @@ def autocomplete(request): )) if ldap_query != "(&)": # If none of the bits were legal, we do not perform the query + entries = None with Connection(settings.LDAP_SERVER_URL) as conn: conn.search( 'dc=spi,dc=ens,dc=fr', ldap_query, attributes=['uid', 'cn'] ) - queries['clippers'] = conn.entries + entries = conn.entries # Clearing redundancies queries['clippers'] = [ - Clipper(clipper.uid, clipper.cn) - for clipper in queries['clippers'] - if str(clipper.uid) not in usernames + Clipper(entry.uid.value, entry.cn.value) + for entry in entries + if entry.uid.value is not None + and entry.uid.value not in usernames ] # Resulting data diff --git a/kfet/autocomplete.py b/kfet/autocomplete.py index bee99517..0ab2c8aa 100644 --- a/kfet/autocomplete.py +++ b/kfet/autocomplete.py @@ -13,6 +13,10 @@ from kfet.models import Account class Clipper(object): def __init__(self, clipper, fullname): + if fullname is None: + fullname = "" + assert isinstance(clipper, str) + assert isinstance(fullname, str) self.clipper = clipper self.fullname = fullname @@ -80,17 +84,19 @@ def account_create(request): )) if ldap_query != "(&)": # If none of the bits were legal, we do not perform the query + entries = None with Connection(settings.LDAP_SERVER_URL) as conn: conn.search( 'dc=spi,dc=ens,dc=fr', ldap_query, attributes=['uid', 'cn'] ) - queries['clippers'] = conn.entries + entries = conn.entries # Clearing redundancies queries['clippers'] = [ - Clipper(clipper.uid, clipper.cn) - for clipper in queries['clippers'] - if str(clipper.uid) not in usernames + Clipper(entry.uid.value, entry.cn.value) + for entry in entries + if entry.uid.value is not None + and entry.uid.value not in usernames ] # Resulting data From a793e9a2e754d5b6294f9cfdea3d80430c7ca8d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20P=C3=A9pin?= Date: Sat, 1 Apr 2017 22:07:32 +0100 Subject: [PATCH 58/60] exclude empty strings from ldap results The uid attribute in a LDAP's entry cannot be an empty string. We need to get an actual identifier. --- gestioncof/autocomplete.py | 2 +- kfet/autocomplete.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gestioncof/autocomplete.py b/gestioncof/autocomplete.py index 7a9c37d2..3532525d 100644 --- a/gestioncof/autocomplete.py +++ b/gestioncof/autocomplete.py @@ -77,7 +77,7 @@ def autocomplete(request): queries['clippers'] = [ Clipper(entry.uid.value, entry.cn.value) for entry in entries - if entry.uid.value is not None + if entry.uid.value and entry.uid.value not in usernames ] diff --git a/kfet/autocomplete.py b/kfet/autocomplete.py index 0ab2c8aa..09057d4a 100644 --- a/kfet/autocomplete.py +++ b/kfet/autocomplete.py @@ -95,7 +95,7 @@ def account_create(request): queries['clippers'] = [ Clipper(entry.uid.value, entry.cn.value) for entry in entries - if entry.uid.value is not None + if entry.uid.value and entry.uid.value not in usernames ] From 91a057873d2f8f51ab939fc0d63ee94b45890de9 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 1 Apr 2017 18:10:51 -0300 Subject: [PATCH 59/60] Merge migrations --- kfet/migrations/0049_remove_checkout.py | 38 ------------------------- kfet/migrations/0050_remove_checkout.py | 23 +++++++++++++++ 2 files changed, 23 insertions(+), 38 deletions(-) delete mode 100644 kfet/migrations/0049_remove_checkout.py create mode 100644 kfet/migrations/0050_remove_checkout.py diff --git a/kfet/migrations/0049_remove_checkout.py b/kfet/migrations/0049_remove_checkout.py deleted file mode 100644 index d2eae10c..00000000 --- a/kfet/migrations/0049_remove_checkout.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -def adapt_operation_types(apps, schema_editor): - Operation = apps.get_model("kfet", "Operation") - Operation.objects.filter( - is_checkout=False, - type__in=['withdraw', 'deposit']).update(type='edit') - - -def revert_operation_types(apps, schema_editor): - Operation = apps.get_model("kfet", "Operation") - edits = Operation.objects.filter(type='edit') - edits.filter(amount__gt=0).update(type='deposit') - edits.filter(amount__lte=0).update(type='withdraw') - - -class Migration(migrations.Migration): - - dependencies = [ - ('kfet', '0048_article_hidden'), - ] - - operations = [ - migrations.AlterField( - model_name='operation', - name='type', - field=models.CharField(choices=[('purchase', 'Achat'), ('deposit', 'Charge'), ('withdraw', 'Retrait'), ('initial', 'Initial'), ('edit', 'Édition')], max_length=8), - ), - migrations.RunPython(adapt_operation_types, revert_operation_types), - migrations.RemoveField( - model_name='operation', - name='is_checkout', - ), - ] diff --git a/kfet/migrations/0050_remove_checkout.py b/kfet/migrations/0050_remove_checkout.py new file mode 100644 index 00000000..f9c374ca --- /dev/null +++ b/kfet/migrations/0050_remove_checkout.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kfet', '0049_merge'), + ] + + operations = [ + migrations.RemoveField( + model_name='operation', + name='is_checkout', + ), + migrations.AlterField( + model_name='operation', + name='type', + field=models.CharField(choices=[('purchase', 'Achat'), ('deposit', 'Charge'), ('withdraw', 'Retrait'), ('initial', 'Initial'), ('edit', 'Édition')], max_length=8), + ), + ] From c9973cde75d9d33caf0a218b6e7aefe2706bf1e9 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 1 Apr 2017 23:25:47 -0300 Subject: [PATCH 60/60] Help text for password length --- kfet/forms.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kfet/forms.py b/kfet/forms.py index 5b56c63a..2418c840 100644 --- a/kfet/forms.py +++ b/kfet/forms.py @@ -75,6 +75,7 @@ class AccountRestrictForm(AccountForm): class AccountPwdForm(forms.Form): pwd1 = forms.CharField( label="Mot de passe K-Fêt", + help_text="Le mot de passe doit contenir au moins huit caractères", widget=forms.PasswordInput) pwd2 = forms.CharField( label="Confirmer le mot de passe",