From 732e47707e96bb7047438edb1791511e89918fa1 Mon Sep 17 00:00:00 2001
From: Ludovic Stephan
Date: Mon, 23 Oct 2017 17:25:58 +0200
Subject: [PATCH 01/15] Add unsubscribe option + list of current draws
---
bda/forms.py | 41 +++++++++++++++++++--
bda/templates/bda/revente-tirages.html | 28 +++++++++++++++
bda/urls.py | 11 +++---
bda/views.py | 50 +++++++++++++++++++++++++-
gestioncof/templates/home.html | 7 ++--
5 files changed, 126 insertions(+), 11 deletions(-)
create mode 100644 bda/templates/bda/revente-tirages.html
diff --git a/bda/forms.py b/bda/forms.py
index c0417d1e..139ef45d 100644
--- a/bda/forms.py
+++ b/bda/forms.py
@@ -4,7 +4,7 @@ from django import forms
from django.forms.models import BaseInlineFormSet
from django.utils import timezone
-from bda.models import Attribution, Spectacle
+from bda.models import Attribution, Spectacle, SpectacleRevente
class InscriptionInlineFormSet(BaseInlineFormSet):
@@ -45,6 +45,9 @@ class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
return "%s" % str(obj.spectacle)
+class ReventeModelMultipleChoiceField(forms.ModelMultipleChoiceField):
+ def label_from_instance(self, obj):
+ return "%s" % str(obj.attribution.spectacle)
class ResellForm(forms.Form):
attributions = AttributionModelMultipleChoiceField(
@@ -63,7 +66,6 @@ class ResellForm(forms.Form):
'participant__user')
)
-
class AnnulForm(forms.Form):
attributions = AttributionModelMultipleChoiceField(
label='',
@@ -83,7 +85,6 @@ class AnnulForm(forms.Form):
'participant__user')
)
-
class InscriptionReventeForm(forms.Form):
spectacles = forms.ModelMultipleChoiceField(
queryset=Spectacle.objects.none(),
@@ -98,6 +99,40 @@ class InscriptionReventeForm(forms.Form):
.filter(date__gte=timezone.now())
)
+class ReventeTirageAnnulForm(forms.Form):
+ reventes = ReventeModelMultipleChoiceField(
+ label='',
+ queryset=SpectacleRevente.objects.none(),
+ widget=forms.CheckboxSelectMultiple,
+ required=False
+ )
+
+ def __init__(self, participant, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields['reventes'].queryset = (
+ participant.wanted.filter(soldTo__isnull=True)
+ .select_related('attribution__spectacle')
+ )
+
+
+class ReventeTirageForm(forms.Form):
+ reventes = ReventeModelMultipleChoiceField(
+ label='',
+ queryset=SpectacleRevente.objects.none(),
+ widget=forms.CheckboxSelectMultiple,
+ required=False
+ )
+
+ def __init__(self, participant, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.fields['reventes'].queryset = (
+ SpectacleRevente.objects.filter(
+ notif_sent=True,
+ shotgun=False,
+ tirage_done=False
+ ).exclude(answered_mail=participant)
+ .select_related('attribution__spectacle')
+ )
class SoldForm(forms.Form):
attributions = AttributionModelMultipleChoiceField(
diff --git a/bda/templates/bda/revente-tirages.html b/bda/templates/bda/revente-tirages.html
new file mode 100644
index 00000000..bd738673
--- /dev/null
+++ b/bda/templates/bda/revente-tirages.html
@@ -0,0 +1,28 @@
+{% extends "base_title.html" %}
+{% load bootstrap %}
+
+{% block realcontent %}
+
+Tirages au sort de reventes
+{% if annulform.reventes %}
+Mes inscriptions
+
+{% endif %}
+
+{% if subform.reventes %}
+Tirages en cours
+
+{% endif %}
+{% endblock %}
diff --git a/bda/urls.py b/bda/urls.py
index 876c84ea..51dd4235 100644
--- a/bda/urls.py
+++ b/bda/urls.py
@@ -32,16 +32,19 @@ urlpatterns = [
url(r'^spectacles/unpaid/(?P\d+)$',
views.unpaid,
name="bda-unpaid"),
- url(r'^liste-revente/(?P\d+)$',
+ url(r'^revente/(?P\d+)/list$',
views.list_revente,
name="bda-liste-revente"),
- url(r'^buy-revente/(?P\d+)$',
+ url(r'^revente/(?P\d+)/tirages$',
+ views.revente_tirages,
+ name="bda-revente-tirages"),
+ url(r'^revente/(?P\d+)/buy$',
views.buy_revente,
name="bda-buy-revente"),
- url(r'^revente-interested/(?P\d+)$',
+ url(r'^revente/(?P\d+)/interested$',
views.revente_interested,
name='bda-revente-interested'),
- url(r'^revente-immediat/(?P\d+)$',
+ url(r'^revente/(?P\d+)/immediat$',
views.revente_shotgun,
name="bda-shotgun"),
url(r'^mails-rappel/(?P\d+)$',
diff --git a/bda/views.py b/bda/views.py
index 84b6c9d3..4b75c116 100644
--- a/bda/views.py
+++ b/bda/views.py
@@ -30,7 +30,7 @@ from bda.models import (
from bda.algorithm import Algorithm
from bda.forms import (
TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, SoldForm,
- InscriptionInlineFormSet,
+ InscriptionInlineFormSet, ReventeTirageForm, ReventeTirageAnnulForm
)
@@ -377,6 +377,7 @@ def revente(request, tirage_id):
if not created:
revente.seller = participant
revente.date = timezone.now()
+ revente.wanted = Participant.objects.none()
revente.soldTo = None
revente.notif_sent = False
revente.tirage_done = False
@@ -442,6 +443,53 @@ def revente(request, tirage_id):
"annulform": annulform, "resellform": resellform})
+@login_required
+def revente_tirages(request, tirage_id):
+ tirage = get_object_or_404(Tirage, id=tirage_id)
+ participant, _ = Participant.objects.get_or_create(
+ user=request.user, tirage=tirage)
+ unsub = 0
+ subform = ReventeTirageForm(participant, prefix="subscribe")
+ annulform = ReventeTirageAnnulForm(participant, prefix="annul")
+
+ if request.method == 'POST':
+ if "subscribe" in request.POST:
+ subform = ReventeTirageForm(participant, request.POST,
+ prefix="subscribe")
+ if subform.is_valid():
+ sub = 0
+ reventes = subform.cleaned_data['reventes']
+ for revente in reventes:
+ revente.answered_mail.add(participant)
+ sub += 1
+ if sub > 0:
+ plural = "s" if sub > 1 else ""
+ messages.success(
+ request,
+ "Tu as bien été inscrit à {} revente{}"
+ .format(sub, plural)
+ )
+ elif "annul" in request.POST:
+ annulform = ReventeTirageAnnulForm(participant, request.POST,
+ prefix="annul")
+ if annulform.is_valid():
+ unsub = 0
+ reventes = annulform.cleaned_data['reventes']
+ for revente in reventes:
+ revente.answered_mail.remove(participant)
+ unsub += 1
+ if unsub > 0:
+ plural = "s" if unsub > 1 else ""
+ messages.success(
+ request,
+ "Tu as bien été désinscrit de {} revente{}"
+ .format(unsub, plural)
+ )
+
+ return render(request, "bda/revente-tirages.html",
+ {"annulform": annulform, "subform": subform})
+
+
@login_required
def revente_interested(request, revente_id):
revente = get_object_or_404(SpectacleRevente, id=revente_id)
diff --git a/gestioncof/templates/home.html b/gestioncof/templates/home.html
index acc04f30..f7ca57b5 100644
--- a/gestioncof/templates/home.html
+++ b/gestioncof/templates/home.html
@@ -43,9 +43,10 @@
État des demandes
{% else %}
Mes places
- Revendre une place
- S'inscrire à BdA-Revente
- Places disponibles immédiatement
+ Gestion de mes reventes
+ Reventes en cours
+ S'inscrire à BdA-Revente
+ Places disponibles immédiatement
{% endif %}
{% endfor %}
From e74dbb11f1556a4ffb3cc42f83686a3a27cf5f45 Mon Sep 17 00:00:00 2001
From: Ludovic Stephan
Date: Mon, 23 Oct 2017 18:39:45 +0200
Subject: [PATCH 02/15] Organize revente files and function names
---
.../revente/confirm-shotgun.html} | 0
.../revente/confirmed.html} | 0
.../revente/mail-success.html} | 0
.../{reventes.html => revente/manage.html} | 0
.../revente/none.html} | 0
.../revente/notpaid.html} | 0
.../revente/shotgun.html} | 2 +-
.../subscribe.html} | 0
.../tirages.html} | 0
.../revente/wrongtime.html} | 2 +-
bda/urls.py | 30 +++++++++++--------
bda/views.py | 30 +++++++++----------
gestioncof/management/data/custommail.json | 2 +-
gestioncof/templates/home.html | 6 ++--
14 files changed, 38 insertions(+), 34 deletions(-)
rename bda/templates/{revente-confirm.html => bda/revente/confirm-shotgun.html} (100%)
rename bda/templates/{bda-interested.html => bda/revente/confirmed.html} (100%)
rename bda/templates/{bda-success.html => bda/revente/mail-success.html} (100%)
rename bda/templates/bda/{reventes.html => revente/manage.html} (100%)
rename bda/templates/{bda-no-revente.html => bda/revente/none.html} (100%)
rename bda/templates/{bda-notpaid.html => bda/revente/notpaid.html} (100%)
rename bda/templates/{bda-shotgun.html => bda/revente/shotgun.html} (83%)
rename bda/templates/bda/{liste-reventes.html => revente/subscribe.html} (100%)
rename bda/templates/bda/{revente-tirages.html => revente/tirages.html} (100%)
rename bda/templates/{bda-wrongtime.html => bda/revente/wrongtime.html} (86%)
diff --git a/bda/templates/revente-confirm.html b/bda/templates/bda/revente/confirm-shotgun.html
similarity index 100%
rename from bda/templates/revente-confirm.html
rename to bda/templates/bda/revente/confirm-shotgun.html
diff --git a/bda/templates/bda-interested.html b/bda/templates/bda/revente/confirmed.html
similarity index 100%
rename from bda/templates/bda-interested.html
rename to bda/templates/bda/revente/confirmed.html
diff --git a/bda/templates/bda-success.html b/bda/templates/bda/revente/mail-success.html
similarity index 100%
rename from bda/templates/bda-success.html
rename to bda/templates/bda/revente/mail-success.html
diff --git a/bda/templates/bda/reventes.html b/bda/templates/bda/revente/manage.html
similarity index 100%
rename from bda/templates/bda/reventes.html
rename to bda/templates/bda/revente/manage.html
diff --git a/bda/templates/bda-no-revente.html b/bda/templates/bda/revente/none.html
similarity index 100%
rename from bda/templates/bda-no-revente.html
rename to bda/templates/bda/revente/none.html
diff --git a/bda/templates/bda-notpaid.html b/bda/templates/bda/revente/notpaid.html
similarity index 100%
rename from bda/templates/bda-notpaid.html
rename to bda/templates/bda/revente/notpaid.html
diff --git a/bda/templates/bda-shotgun.html b/bda/templates/bda/revente/shotgun.html
similarity index 83%
rename from bda/templates/bda-shotgun.html
rename to bda/templates/bda/revente/shotgun.html
index e10fae00..fae36c04 100644
--- a/bda/templates/bda-shotgun.html
+++ b/bda/templates/bda/revente/shotgun.html
@@ -5,7 +5,7 @@
{% if shotgun %}
{% for spectacle in shotgun %}
- - {{spectacle}}
+ - {{spectacle}}
{% endfor %}
{% else %}
Pas de places disponibles immédiatement, désolé !
diff --git a/bda/templates/bda/liste-reventes.html b/bda/templates/bda/revente/subscribe.html
similarity index 100%
rename from bda/templates/bda/liste-reventes.html
rename to bda/templates/bda/revente/subscribe.html
diff --git a/bda/templates/bda/revente-tirages.html b/bda/templates/bda/revente/tirages.html
similarity index 100%
rename from bda/templates/bda/revente-tirages.html
rename to bda/templates/bda/revente/tirages.html
diff --git a/bda/templates/bda-wrongtime.html b/bda/templates/bda/revente/wrongtime.html
similarity index 86%
rename from bda/templates/bda-wrongtime.html
rename to bda/templates/bda/revente/wrongtime.html
index dfafb05f..18c417a2 100644
--- a/bda/templates/bda-wrongtime.html
+++ b/bda/templates/bda/revente/wrongtime.html
@@ -6,7 +6,7 @@
Le tirage au sort de cette revente a déjà été effectué !
Si personne n'était intéressé, elle est maintenant disponible
- ici.
+ ici.
{% else %}
Il n'est pas encore possible de s'inscrire à cette revente, réessaie dans quelque temps !
{% endif %}
diff --git a/bda/urls.py b/bda/urls.py
index 51dd4235..7588187c 100644
--- a/bda/urls.py
+++ b/bda/urls.py
@@ -16,9 +16,6 @@ urlpatterns = [
url(r'^places/(?P\d+)$',
views.places,
name="bda-places-attribuees"),
- url(r'^revente/(?P\d+)$',
- views.revente,
- name='bda-revente'),
url(r'^etat-places/(?P\d+)$',
views.etat_places,
name='bda-etat-places'),
@@ -32,21 +29,28 @@ urlpatterns = [
url(r'^spectacles/unpaid/(?P\d+)$',
views.unpaid,
name="bda-unpaid"),
- url(r'^revente/(?P\d+)/list$',
- views.list_revente,
- name="bda-liste-revente"),
+
+ # Urls BdA-Revente
+
+ url(r'^revente/(?P\d+)/manage$',
+ views.revente_manage,
+ name='bda-revente-manage'),
+ url(r'^revente/(?P\d+)/subscribe$',
+ views.revente_subscribe,
+ name="bda-revente-subscribe"),
url(r'^revente/(?P\d+)/tirages$',
views.revente_tirages,
name="bda-revente-tirages"),
url(r'^revente/(?P\d+)/buy$',
- views.buy_revente,
- name="bda-buy-revente"),
- url(r'^revente/(?P\d+)/interested$',
- views.revente_interested,
- name='bda-revente-interested'),
- url(r'^revente/(?P\d+)/immediat$',
+ views.revente_buy,
+ name="bda-revente-buy"),
+ url(r'^revente/(?P\d+)/confirm$',
+ views.revente_confirm,
+ name='bda-revente-confirm'),
+ url(r'^revente/(?P\d+)/shotgun$',
views.revente_shotgun,
- name="bda-shotgun"),
+ name="bda-revente-shotgun"),
+
url(r'^mails-rappel/(?P\d+)$',
views.send_rappel,
name="bda-rappels"
diff --git a/bda/views.py b/bda/views.py
index 4b75c116..c0e64230 100644
--- a/bda/views.py
+++ b/bda/views.py
@@ -349,13 +349,13 @@ def tirage(request, tirage_id):
@login_required
-def revente(request, tirage_id):
+def revente_manage(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
participant, created = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
if not participant.paid:
- return render(request, "bda-notpaid.html", {})
+ return render(request, "bda/revente/notpaid.html", {})
resellform = ResellForm(participant, prefix='resell')
annulform = AnnulForm(participant, prefix='annul')
@@ -438,7 +438,7 @@ def revente(request, tirage_id):
.filter(
Q(revente__soldTo__isnull=True) | Q(revente__soldTo=participant))
- return render(request, "bda/reventes.html",
+ return render(request, "bda/revente/manage.html",
{'tirage': tirage, 'overdue': overdue, "soldform": soldform,
"annulform": annulform, "resellform": resellform})
@@ -486,27 +486,27 @@ def revente_tirages(request, tirage_id):
.format(unsub, plural)
)
- return render(request, "bda/revente-tirages.html",
+ return render(request, "bda/revente/tirages.html",
{"annulform": annulform, "subform": subform})
@login_required
-def revente_interested(request, revente_id):
+def revente_confirm(request, revente_id):
revente = get_object_or_404(SpectacleRevente, id=revente_id)
participant, _ = Participant.objects.get_or_create(
user=request.user, tirage=revente.attribution.spectacle.tirage)
if (timezone.now() < revente.date + timedelta(hours=1)) or revente.shotgun:
- return render(request, "bda-wrongtime.html",
+ return render(request, "bda/revente/wrongtime.html",
{"revente": revente})
revente.answered_mail.add(participant)
- return render(request, "bda-interested.html",
+ return render(request, "bda/revente/confirmed.html",
{"spectacle": revente.attribution.spectacle,
"date": revente.date_tirage})
@login_required
-def list_revente(request, tirage_id):
+def revente_subscribe(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
participant, _ = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
@@ -560,11 +560,11 @@ def list_revente(request, tirage_id):
)
messages.info(request, msg, extra_tags="safe")
- return render(request, "bda/liste-reventes.html", {"form": form})
+ return render(request, "bda/revente/subscribe.html", {"form": form})
@login_required
-def buy_revente(request, spectacle_id):
+def revente_buy(request, spectacle_id):
spectacle = get_object_or_404(Spectacle, id=spectacle_id)
tirage = spectacle.tirage
participant, _ = Participant.objects.get_or_create(
@@ -578,13 +578,13 @@ def buy_revente(request, spectacle_id):
own_reventes = reventes.filter(seller=participant)
if len(own_reventes) > 0:
own_reventes[0].delete()
- return HttpResponseRedirect(reverse("bda-shotgun",
+ return HttpResponseRedirect(reverse("bda-revente-shotgun",
args=[tirage.id]))
reventes_shotgun = reventes.filter(shotgun=True)
if not reventes_shotgun:
- return render(request, "bda-no-revente.html", {})
+ return render(request, "bda/revente/none.html", {})
if request.POST:
revente = random.choice(reventes_shotgun)
@@ -601,11 +601,11 @@ def buy_revente(request, spectacle_id):
[revente.seller.user.email],
context=context,
)
- return render(request, "bda-success.html",
+ return render(request, "bda/revente/mail-success.html",
{"seller": revente.attribution.participant.user,
"spectacle": spectacle})
- return render(request, "revente-confirm.html",
+ return render(request, "bda/revente/confirm-shotgun.html",
{"spectacle": spectacle,
"user": request.user})
@@ -629,7 +629,7 @@ def revente_shotgun(request, tirage_id):
)
shotgun = [sp for sp in spectacles if len(sp.shotguns) > 0]
- return render(request, "bda-shotgun.html",
+ return render(request, "bda/revente/shotgun.html",
{"shotgun": shotgun})
diff --git a/gestioncof/management/data/custommail.json b/gestioncof/management/data/custommail.json
index 9ee9b1ea..bf59e5f6 100644
--- a/gestioncof/management/data/custommail.json
+++ b/gestioncof/management/data/custommail.json
@@ -151,7 +151,7 @@
"shortname": "bda-revente",
"subject": "{{ show }}",
"description": "Notification envoy\u00e9e \u00e0 toutes les personnes int\u00e9ress\u00e9es par un spectacle pour le signaler qu'une place vient d'\u00eatre mise en vente.",
- "body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nSi ce spectacle t'int\u00e9resse toujours, merci de nous le signaler en cliquant\r\nsur ce lien : http://{{ site }}{% url \"bda-revente-interested\" revente.id %}.\r\nDans le cas o\u00f9 plusieurs personnes seraient int\u00e9ress\u00e9es, nous proc\u00e8derons \u00e0\r\nun tirage au sort le {{ revente.date_tirage|date:\"DATE_FORMAT\" }}.\r\n\r\nChaleureusement,\r\nLe BdA"
+ "body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nSi ce spectacle t'int\u00e9resse toujours, merci de nous le signaler en cliquant\r\nsur ce lien : http://{{ site }}{% url \"bda-revente-confirm\" revente.id %}.\r\nDans le cas o\u00f9 plusieurs personnes seraient int\u00e9ress\u00e9es, nous proc\u00e8derons \u00e0\r\nun tirage au sort le {{ revente.date_tirage|date:\"DATE_FORMAT\" }}.\r\n\r\nChaleureusement,\r\nLe BdA"
}
},
{
diff --git a/gestioncof/templates/home.html b/gestioncof/templates/home.html
index f7ca57b5..943ef780 100644
--- a/gestioncof/templates/home.html
+++ b/gestioncof/templates/home.html
@@ -43,10 +43,10 @@
État des demandes
{% else %}
Mes places
- Gestion de mes reventes
+ Gestion de mes reventes
Reventes en cours
- S'inscrire à BdA-Revente
- Places disponibles immédiatement
+ S'inscrire à BdA-Revente
+ Places disponibles immédiatement
{% endif %}
{% endfor %}
From 919bcd197d077767bdc07355a70a84d39f8ebecf Mon Sep 17 00:00:00 2001
From: Ludovic Stephan
Date: Mon, 23 Oct 2017 18:59:30 +0200
Subject: [PATCH 03/15] Small code QoL improvements
---
bda/models.py | 10 ++++++++++
bda/views.py | 10 ++--------
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/bda/models.py b/bda/models.py
index 41462d70..5533e3bb 100644
--- a/bda/models.py
+++ b/bda/models.py
@@ -252,6 +252,16 @@ class SpectacleRevente(models.Model):
class Meta:
verbose_name = "Revente"
+ def reset(self):
+ """Réinitialise la revente pour permettre une remise sur le marché"""
+ self.seller = self.attribution.participant
+ self.date = timezone.now()
+ self.answered_mail.clear()
+ self.soldTo = None
+ self.notif_sent = False
+ self.tirage_done = False
+ self.shotgun = False
+
def send_notif(self):
"""
Envoie une notification pour indiquer la mise en vente d'une place sur
diff --git a/bda/views.py b/bda/views.py
index c0e64230..311d530a 100644
--- a/bda/views.py
+++ b/bda/views.py
@@ -375,13 +375,7 @@ def revente_manage(request, tirage_id):
attribution=attribution,
defaults={'seller': participant})
if not created:
- revente.seller = participant
- revente.date = timezone.now()
- revente.wanted = Participant.objects.none()
- revente.soldTo = None
- revente.notif_sent = False
- revente.tirage_done = False
- revente.shotgun = False
+ revente.reset()
context = {
'vendeur': participant.user,
'show': attribution.spectacle,
@@ -495,7 +489,7 @@ def revente_confirm(request, revente_id):
revente = get_object_or_404(SpectacleRevente, id=revente_id)
participant, _ = Participant.objects.get_or_create(
user=request.user, tirage=revente.attribution.spectacle.tirage)
- if (timezone.now() < revente.date + timedelta(hours=1)) or revente.shotgun:
+ if not revente.notif_sent or revente.shotgun:
return render(request, "bda/revente/wrongtime.html",
{"revente": revente})
From 1b0e4285ecbc7ea224cb7fad9c725365d4a9ba01 Mon Sep 17 00:00:00 2001
From: Ludovic Stephan
Date: Mon, 23 Oct 2017 20:26:07 +0200
Subject: [PATCH 04/15] Reverse match fix
---
gestioncof/management/data/custommail.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/gestioncof/management/data/custommail.json b/gestioncof/management/data/custommail.json
index bf59e5f6..029c03e0 100644
--- a/gestioncof/management/data/custommail.json
+++ b/gestioncof/management/data/custommail.json
@@ -161,7 +161,7 @@
"shortname": "bda-shotgun",
"subject": "{{ show }}",
"description": "Notification signalant qu'une place est au shotgun aux personnes int\u00e9ress\u00e9es.",
- "body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nPuisque ce spectacle a lieu dans moins de 24h, il n'y a pas de tirage au sort pour\r\ncette place : elle est disponible imm\u00e9diatement \u00e0 l'adresse\r\nhttp://{{ site }}{% url \"bda-buy-revente\" show.id %}, \u00e0 la disposition de tous.\r\n\r\nChaleureusement,\r\nLe BdA"
+ "body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nPuisque ce spectacle a lieu dans moins de 24h, il n'y a pas de tirage au sort pour\r\ncette place : elle est disponible imm\u00e9diatement \u00e0 l'adresse\r\nhttp://{{ site }}{% url \"bda-revente-buy\" show.id %}, \u00e0 la disposition de tous.\r\n\r\nChaleureusement,\r\nLe BdA"
}
},
{
From 684603709e90e471f5528ab35f1efc2011145fb5 Mon Sep 17 00:00:00 2001
From: Ludovic Stephan
Date: Mon, 23 Oct 2017 20:30:11 +0200
Subject: [PATCH 05/15] Class attributes and properties + more verbose log
SpectacleRevente gets brand new properties and attributes to simplify
code ; also, manage_reventes command output is more verbose
---
bda/management/commands/manage_reventes.py | 48 ++++++++++++++--------
bda/models.py | 45 +++++++++++++++++---
2 files changed, 69 insertions(+), 24 deletions(-)
diff --git a/bda/management/commands/manage_reventes.py b/bda/management/commands/manage_reventes.py
index 0302ec4b..5a767604 100644
--- a/bda/management/commands/manage_reventes.py
+++ b/bda/management/commands/manage_reventes.py
@@ -6,7 +6,6 @@ Gestion en ligne de commande des reventes.
from __future__ import unicode_literals
-from datetime import timedelta
from django.core.management import BaseCommand
from django.utils import timezone
from bda.models import SpectacleRevente
@@ -21,23 +20,36 @@ class Command(BaseCommand):
now = timezone.now()
reventes = SpectacleRevente.objects.all()
for revente in reventes:
- # Check si < 24h
- if (revente.attribution.spectacle.date <=
- revente.date + timedelta(days=1)) and \
- now >= revente.date + timedelta(minutes=15) and \
- not revente.notif_sent:
- self.stdout.write(str(now))
- revente.mail_shotgun()
- self.stdout.write("Mail de disponibilité immédiate envoyé")
- # Check si délai de retrait dépassé
- elif (now >= revente.date + timedelta(hours=1) and
- not revente.notif_sent):
+ # Le spectacle est bientôt et on a pas encore envoyé de mail :
+ # on met la place au shotgun et on prévient.
+ if revente.is_urgent and not revente.notif_sent:
+ if revente.can_notif:
+ self.stdout.write(str(now))
+ revente.mail_shotgun()
+ self.stdout.write(
+ "Mails de disponibilité immédiate envoyés "
+ "pour la revente [%s]" % revente
+ )
+
+ # Le spectacle est dans plus longtemps : on prévient
+ elif (revente.can_notif and not revente.notif_sent):
self.stdout.write(str(now))
revente.send_notif()
- self.stdout.write("Mail d'inscription à une revente envoyé")
- # Check si tirage à faire
- elif (now >= revente.date_tirage and
- not revente.tirage_done):
+ self.stdout.write(
+ "Mails d'inscription à la revente [%s] envoyés"
+ % revente
+ )
+
+ # On fait le tirage
+ elif (now >= revente.date_tirage and not revente.tirage_done):
self.stdout.write(str(now))
- revente.tirage()
- self.stdout.write("Tirage effectué, mails envoyés")
+ winner = revente.tirage()
+ self.stdout.write(
+ "Tirage effectué pour la revente [%s]"
+ % revente
+ )
+
+ if winner:
+ self.stdout.write("Gagnant : %s" % winner.user)
+ else:
+ self.stdout.write("Pas de gagnant ; place au shotgun")
diff --git a/bda/models.py b/bda/models.py
index 5533e3bb..b2882900 100644
--- a/bda/models.py
+++ b/bda/models.py
@@ -233,17 +233,46 @@ class SpectacleRevente(models.Model):
default=False)
shotgun = models.BooleanField("Disponible immédiatement",
default=False)
+ ####
+ # Some class attributes
+ ###
+ # TODO : settings ?
+
+ # Temps minimum entre le tirage et le spectacle
+ min_margin = timedelta(days=5)
+
+ # Temps entre la création d'une revente et l'envoi du mail
+ remorse_time = timedelta(hours=1)
+
+ # Temps min/max d'attente avant le tirage
+ max_wait_time = timedelta(days=3)
+ min_wait_time = timedelta(days=1)
@property
def date_tirage(self):
"""Renvoie la date du tirage au sort de la revente."""
- # L'acheteur doit être connu au plus 12h avant le spectacle
+ notif_time = self.date + self.remorse_time
+
remaining_time = (self.attribution.spectacle.date
- - self.date - timedelta(hours=13))
- # Au minimum, on attend 2 jours avant le tirage
- delay = min(remaining_time, timedelta(days=2))
- # Le vendeur a aussi 1h pour changer d'avis
- return self.date + delay + timedelta(hours=1)
+ - notif_time - self.min_margin)
+
+ delay = min(remaining_time, self.max_wait_time)
+
+ return notif_time + delay
+
+ @property
+ def is_urgent(self):
+ """
+ Renvoie True iff la revente doit être mise au shotgun directement.
+ Plus précisément, on doit avoir min_margin + min_wait_time de marge.
+ """
+ spectacle_date = self.attribution.spectacle.date
+ return (spectacle_date <= timezone.now() + self.min_margin
+ + self.min_wait_time)
+
+ @property
+ def can_notif(self):
+ return (timezone.now() >= self.date + self.remorse_time)
def __str__(self):
return "%s -- %s" % (self.seller,
@@ -353,8 +382,12 @@ class SpectacleRevente(models.Model):
[inscrit.user.email]
))
send_mass_custom_mail(datatuple)
+
+ return winner
+
# Si personne ne veut de la place, elle part au shotgun
else:
self.shotgun = True
+ return None
self.tirage_done = True
self.save()
From 6a6549e0d72937d9f5adaf7bf43ff090150b4891 Mon Sep 17 00:00:00 2001
From: Ludovic Stephan
Date: Mon, 23 Oct 2017 20:52:25 +0200
Subject: [PATCH 06/15] Add notif time
In case of a gestioCOF bug, we keep the notification time in memory to
still do the drawing 1-3 days after.
---
bda/admin.py | 6 +++---
bda/forms.py | 4 ++--
bda/migrations/0012_notif_time.py | 28 ++++++++++++++++++++++++++++
bda/models.py | 29 +++++++++++++++++++++--------
bda/views.py | 14 +++++++-------
5 files changed, 61 insertions(+), 20 deletions(-)
create mode 100644 bda/migrations/0012_notif_time.py
diff --git a/bda/admin.py b/bda/admin.py
index 60d3c1ba..4f5d821a 100644
--- a/bda/admin.py
+++ b/bda/admin.py
@@ -225,7 +225,7 @@ class SpectacleReventeAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- self.fields['answered_mail'].queryset = (
+ self.fields['confirmed_entry'].queryset = (
Participant.objects
.select_related('user', 'tirage')
)
@@ -292,8 +292,8 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
revente.soldTo = None
revente.notif_sent = False
revente.tirage_done = False
- if revente.answered_mail:
- revente.answered_mail.clear()
+ if revente.confirmed_entry:
+ revente.confirmed_entry.clear()
revente.save()
self.message_user(
request,
diff --git a/bda/forms.py b/bda/forms.py
index 139ef45d..11d05b0e 100644
--- a/bda/forms.py
+++ b/bda/forms.py
@@ -110,7 +110,7 @@ class ReventeTirageAnnulForm(forms.Form):
def __init__(self, participant, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['reventes'].queryset = (
- participant.wanted.filter(soldTo__isnull=True)
+ participant.entered.filter(soldTo__isnull=True)
.select_related('attribution__spectacle')
)
@@ -130,7 +130,7 @@ class ReventeTirageForm(forms.Form):
notif_sent=True,
shotgun=False,
tirage_done=False
- ).exclude(answered_mail=participant)
+ ).exclude(confirmed_entry=participant)
.select_related('attribution__spectacle')
)
diff --git a/bda/migrations/0012_notif_time.py b/bda/migrations/0012_notif_time.py
new file mode 100644
index 00000000..be66efd1
--- /dev/null
+++ b/bda/migrations/0012_notif_time.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('bda', '0011_tirage_appear_catalogue'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='spectaclerevente',
+ name='answered_mail',
+ ),
+ migrations.AddField(
+ model_name='spectaclerevente',
+ name='confirmed_entry',
+ field=models.ManyToManyField(blank=True, related_name='entered', to='bda.Participant'),
+ ),
+ migrations.AddField(
+ model_name='spectaclerevente',
+ name='notif_time',
+ field=models.DateTimeField(blank=True, verbose_name="Moment d'envoi de la notification", null=True),
+ ),
+ ]
diff --git a/bda/models.py b/bda/models.py
index b2882900..2ad47dbf 100644
--- a/bda/models.py
+++ b/bda/models.py
@@ -218,9 +218,9 @@ class SpectacleRevente(models.Model):
related_name="revente")
date = models.DateTimeField("Date de mise en vente",
default=timezone.now)
- answered_mail = models.ManyToManyField(Participant,
- related_name="wanted",
- blank=True)
+ confirmed_entry = models.ManyToManyField(Participant,
+ related_name="entered",
+ blank=True)
seller = models.ForeignKey(Participant,
related_name="original_shows",
verbose_name="Vendeur")
@@ -229,8 +229,13 @@ class SpectacleRevente(models.Model):
notif_sent = models.BooleanField("Notification envoyée",
default=False)
+
+ notif_time = models.DateTimeField("Moment d'envoi de la notification",
+ blank=True, null=True)
+
tirage_done = models.BooleanField("Tirage effectué",
default=False)
+
shotgun = models.BooleanField("Disponible immédiatement",
default=False)
####
@@ -248,17 +253,23 @@ class SpectacleRevente(models.Model):
max_wait_time = timedelta(days=3)
min_wait_time = timedelta(days=1)
+ @property
+ def real_notif_time(self):
+ if self.notif_time:
+ return self.notif_time
+ else:
+ return self.date + self.remorse_time
+
@property
def date_tirage(self):
"""Renvoie la date du tirage au sort de la revente."""
- notif_time = self.date + self.remorse_time
remaining_time = (self.attribution.spectacle.date
- - notif_time - self.min_margin)
+ - self.real_notif_time - self.min_margin)
delay = min(remaining_time, self.max_wait_time)
- return notif_time + delay
+ return self.real_notif_time + delay
@property
def is_urgent(self):
@@ -285,7 +296,7 @@ class SpectacleRevente(models.Model):
"""Réinitialise la revente pour permettre une remise sur le marché"""
self.seller = self.attribution.participant
self.date = timezone.now()
- self.answered_mail.clear()
+ self.confirmed_entry.clear()
self.soldTo = None
self.notif_sent = False
self.tirage_done = False
@@ -311,6 +322,7 @@ class SpectacleRevente(models.Model):
]
send_mass_custom_mail(datatuple)
self.notif_sent = True
+ self.notif_time = timezone.now()
self.save()
def mail_shotgun(self):
@@ -332,6 +344,7 @@ class SpectacleRevente(models.Model):
]
send_mass_custom_mail(datatuple)
self.notif_sent = True
+ self.notif_time = timezone.now()
# Flag inutile, sauf si l'horloge interne merde
self.tirage_done = True
self.shotgun = True
@@ -343,7 +356,7 @@ class SpectacleRevente(models.Model):
parmis les personnes intéressées par le spectacle. Les personnes sont
ensuites prévenues par mail du résultat du tirage.
"""
- inscrits = list(self.answered_mail.all())
+ inscrits = list(self.confirmed_entry.all())
spectacle = self.attribution.spectacle
seller = self.seller
diff --git a/bda/views.py b/bda/views.py
index 311d530a..6ed22b21 100644
--- a/bda/views.py
+++ b/bda/views.py
@@ -420,8 +420,8 @@ def revente_manage(request, tirage_id):
revente.notif_sent = False
revente.tirage_done = False
revente.shotgun = False
- if revente.answered_mail:
- revente.answered_mail.clear()
+ if revente.confirmed_entry:
+ revente.confirmed_entry.clear()
revente.save()
overdue = participant.attribution_set.filter(
@@ -454,7 +454,7 @@ def revente_tirages(request, tirage_id):
sub = 0
reventes = subform.cleaned_data['reventes']
for revente in reventes:
- revente.answered_mail.add(participant)
+ revente.confirmed_entry.add(participant)
sub += 1
if sub > 0:
plural = "s" if sub > 1 else ""
@@ -470,7 +470,7 @@ def revente_tirages(request, tirage_id):
unsub = 0
reventes = annulform.cleaned_data['reventes']
for revente in reventes:
- revente.answered_mail.remove(participant)
+ revente.confirmed_entry.remove(participant)
unsub += 1
if unsub > 0:
plural = "s" if unsub > 1 else ""
@@ -493,7 +493,7 @@ def revente_confirm(request, revente_id):
return render(request, "bda/revente/wrongtime.html",
{"revente": revente})
- revente.answered_mail.add(participant)
+ revente.confirmed_entry.add(participant)
return render(request, "bda/revente/confirmed.html",
{"spectacle": revente.attribution.spectacle,
"date": revente.date_tirage})
@@ -526,12 +526,12 @@ def revente_subscribe(request, tirage_id):
# la revente ayant le moins d'inscrits
min_resell = (
qset.filter(shotgun=False)
- .annotate(nb_subscribers=Count('answered_mail'))
+ .annotate(nb_subscribers=Count('confirmed_entry'))
.order_by('nb_subscribers')
.first()
)
if min_resell is not None:
- min_resell.answered_mail.add(participant)
+ min_resell.confirmed_entry.add(participant)
inscrit_revente.append(spectacle)
success = True
else:
From 785555c05cc874dfc2a9542608c0e94baffccc2e Mon Sep 17 00:00:00 2001
From: Ludovic Stephan
Date: Thu, 26 Oct 2017 12:40:11 +0200
Subject: [PATCH 07/15] Misc fixes
---
bda/forms.py | 10 ++++++++--
bda/migrations/0012_notif_time.py | 7 ++++---
bda/models.py | 11 ++++++-----
bda/views.py | 33 +++++++++++--------------------
4 files changed, 30 insertions(+), 31 deletions(-)
diff --git a/bda/forms.py b/bda/forms.py
index 11d05b0e..2929f771 100644
--- a/bda/forms.py
+++ b/bda/forms.py
@@ -43,11 +43,13 @@ class TokenForm(forms.Form):
class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
- return "%s" % str(obj.spectacle)
+ return str(obj.spectacle)
+
class ReventeModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
- return "%s" % str(obj.attribution.spectacle)
+ return str(obj.attribution.spectacle)
+
class ResellForm(forms.Form):
attributions = AttributionModelMultipleChoiceField(
@@ -66,6 +68,7 @@ class ResellForm(forms.Form):
'participant__user')
)
+
class AnnulForm(forms.Form):
attributions = AttributionModelMultipleChoiceField(
label='',
@@ -85,6 +88,7 @@ class AnnulForm(forms.Form):
'participant__user')
)
+
class InscriptionReventeForm(forms.Form):
spectacles = forms.ModelMultipleChoiceField(
queryset=Spectacle.objects.none(),
@@ -99,6 +103,7 @@ class InscriptionReventeForm(forms.Form):
.filter(date__gte=timezone.now())
)
+
class ReventeTirageAnnulForm(forms.Form):
reventes = ReventeModelMultipleChoiceField(
label='',
@@ -134,6 +139,7 @@ class ReventeTirageForm(forms.Form):
.select_related('attribution__spectacle')
)
+
class SoldForm(forms.Form):
attributions = AttributionModelMultipleChoiceField(
label='',
diff --git a/bda/migrations/0012_notif_time.py b/bda/migrations/0012_notif_time.py
index be66efd1..ee777e35 100644
--- a/bda/migrations/0012_notif_time.py
+++ b/bda/migrations/0012_notif_time.py
@@ -11,11 +11,12 @@ class Migration(migrations.Migration):
]
operations = [
- migrations.RemoveField(
+ migrations.RenameField(
model_name='spectaclerevente',
- name='answered_mail',
+ old_name='answered_mail',
+ new_name='confirmed_entry',
),
- migrations.AddField(
+ migrations.AlterField(
model_name='spectaclerevente',
name='confirmed_entry',
field=models.ManyToManyField(blank=True, related_name='entered', to='bda.Participant'),
diff --git a/bda/models.py b/bda/models.py
index 2ad47dbf..59827621 100644
--- a/bda/models.py
+++ b/bda/models.py
@@ -168,6 +168,7 @@ class Participant(models.Model):
def __str__(self):
return "%s - %s" % (self.user, self.tirage.title)
+
DOUBLE_CHOICES = (
("1", "1 place"),
("autoquit", "2 places si possible, 1 sinon"),
@@ -292,15 +293,16 @@ class SpectacleRevente(models.Model):
class Meta:
verbose_name = "Revente"
- def reset(self):
+ def reset(self, new_date=timezone.now()):
"""Réinitialise la revente pour permettre une remise sur le marché"""
self.seller = self.attribution.participant
- self.date = timezone.now()
+ self.date = new_date
self.confirmed_entry.clear()
self.soldTo = None
self.notif_sent = False
self.tirage_done = False
self.shotgun = False
+ self.save()
def send_notif(self):
"""
@@ -396,11 +398,10 @@ class SpectacleRevente(models.Model):
))
send_mass_custom_mail(datatuple)
- return winner
-
# Si personne ne veut de la place, elle part au shotgun
else:
+ winner = None
self.shotgun = True
- return None
self.tirage_done = True
self.save()
+ return winner
diff --git a/bda/views.py b/bda/views.py
index 6ed22b21..fb1a2e82 100644
--- a/bda/views.py
+++ b/bda/views.py
@@ -5,7 +5,6 @@ import random
import hashlib
import time
import json
-from datetime import timedelta
from custommail.shortcuts import send_mass_custom_mail, send_custom_mail
from custommail.models import CustomMail
from django.shortcuts import render, get_object_or_404
@@ -14,6 +13,7 @@ from django.contrib import messages
from django.db import transaction
from django.core import serializers
from django.db.models import Count, Q, Prefetch
+from django.template.defaultfilters import pluralize
from django.forms.models import inlineformset_factory
from django.http import (
HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
@@ -376,6 +376,7 @@ def revente_manage(request, tirage_id):
defaults={'seller': participant})
if not created:
revente.reset()
+
context = {
'vendeur': participant.user,
'show': attribution.spectacle,
@@ -414,15 +415,10 @@ def revente_manage(request, tirage_id):
attributions = soldform.cleaned_data['attributions']
for attribution in attributions:
if attribution.spectacle.date > timezone.now():
- revente = attribution.revente
- revente.date = timezone.now() - timedelta(minutes=65)
- revente.soldTo = None
- revente.notif_sent = False
- revente.tirage_done = False
- revente.shotgun = False
- if revente.confirmed_entry:
- revente.confirmed_entry.clear()
- revente.save()
+ # On antidate pour envoyer le mail plus vite
+ new_date = (timezone.now()
+ - SpectacleRevente.remorse_time)
+ revente.reset(new_date=new_date)
overdue = participant.attribution_set.filter(
spectacle__date__gte=timezone.now(),
@@ -442,7 +438,6 @@ def revente_tirages(request, tirage_id):
tirage = get_object_or_404(Tirage, id=tirage_id)
participant, _ = Participant.objects.get_or_create(
user=request.user, tirage=tirage)
- unsub = 0
subform = ReventeTirageForm(participant, prefix="subscribe")
annulform = ReventeTirageAnnulForm(participant, prefix="annul")
@@ -451,33 +446,29 @@ def revente_tirages(request, tirage_id):
subform = ReventeTirageForm(participant, request.POST,
prefix="subscribe")
if subform.is_valid():
- sub = 0
reventes = subform.cleaned_data['reventes']
+ count = reventes.count()
for revente in reventes:
revente.confirmed_entry.add(participant)
- sub += 1
- if sub > 0:
- plural = "s" if sub > 1 else ""
+ if count > 0:
messages.success(
request,
"Tu as bien été inscrit à {} revente{}"
- .format(sub, plural)
+ .format(count, pluralize(count))
)
elif "annul" in request.POST:
annulform = ReventeTirageAnnulForm(participant, request.POST,
prefix="annul")
if annulform.is_valid():
- unsub = 0
reventes = annulform.cleaned_data['reventes']
+ count = reventes.count()
for revente in reventes:
revente.confirmed_entry.remove(participant)
- unsub += 1
- if unsub > 0:
- plural = "s" if unsub > 1 else ""
+ if count > 0:
messages.success(
request,
"Tu as bien été désinscrit de {} revente{}"
- .format(unsub, plural)
+ .format(count, pluralize(count))
)
return render(request, "bda/revente/tirages.html",
From f18959c0a1d643fab3b08921a004f158d4ba4720 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20P=C3=A9pin?=
Date: Wed, 1 Nov 2017 17:26:40 +0100
Subject: [PATCH 08/15] BdA-Revente: meaningful names, some help tests
---
bda/templates/bda/revente/manage.html | 58 +++++++++++++++++++-----
bda/templates/bda/revente/subscribe.html | 39 ++++++++++------
bda/templates/bda/revente/tirages.html | 40 ++++++++++++----
bda/views.py | 18 ++++++++
gestioncof/static/css/cof.css | 11 +++++
gestioncof/templates/home.html | 6 +--
6 files changed, 136 insertions(+), 36 deletions(-)
diff --git a/bda/templates/bda/revente/manage.html b/bda/templates/bda/revente/manage.html
index 0912babb..8162d55d 100644
--- a/bda/templates/bda/revente/manage.html
+++ b/bda/templates/bda/revente/manage.html
@@ -3,50 +3,84 @@
{% block realcontent %}
-Revente de place
+Gestion des places que je revends
{% with resell_attributions=resellform.attributions annul_attributions=annulform.attributions sold_attributions=soldform.attributions %}
{% if resellform.attributions %}
+
+
Places non revendues
+
+
{% endif %}
-
+
{% if annul_attributions or overdue %}
Places en cours de revente
+
+
{% endif %}
-
+
{% if sold_attributions %}
Places revendues
-
{% endif %}
{% if not resell_attributions and not annul_attributions and not overdue and not sold_attributions %}
Plus de reventes possibles !
diff --git a/bda/templates/bda/revente/subscribe.html b/bda/templates/bda/revente/subscribe.html
index fcf57345..9a193908 100644
--- a/bda/templates/bda/revente/subscribe.html
+++ b/bda/templates/bda/revente/subscribe.html
@@ -4,28 +4,41 @@
{% block realcontent %}
Inscriptions pour BdA-Revente