forked from DGNum/gestioCOF
Merge branch 'Aufinal/bda_fixes' into 'master'
Fix pour BdA-Revente See merge request cof-geek/gestioCOF!263
This commit is contained in:
commit
25f4c64835
25 changed files with 657 additions and 261 deletions
1
TODO_PROD.md
Normal file
1
TODO_PROD.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
- Changer les urls dans les mails "bda-revente" et "bda-shotgun"
|
10
bda/admin.py
10
bda/admin.py
|
@ -236,7 +236,7 @@ class SpectacleReventeAdminForm(forms.ModelForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['answered_mail'].queryset = (
|
self.fields['confirmed_entry'].queryset = (
|
||||||
Participant.objects
|
Participant.objects
|
||||||
.select_related('user', 'tirage')
|
.select_related('user', 'tirage')
|
||||||
)
|
)
|
||||||
|
@ -299,13 +299,7 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||||
count = queryset.count()
|
count = queryset.count()
|
||||||
for revente in queryset.filter(
|
for revente in queryset.filter(
|
||||||
attribution__spectacle__date__gte=timezone.now()):
|
attribution__spectacle__date__gte=timezone.now()):
|
||||||
revente.date = timezone.now() - timedelta(hours=1)
|
revente.reset(new_date=timezone.now() - timedelta(hours=1))
|
||||||
revente.soldTo = None
|
|
||||||
revente.notif_sent = False
|
|
||||||
revente.tirage_done = False
|
|
||||||
if revente.answered_mail:
|
|
||||||
revente.answered_mail.clear()
|
|
||||||
revente.save()
|
|
||||||
self.message_user(
|
self.message_user(
|
||||||
request,
|
request,
|
||||||
"%d attribution%s %s été réinitialisée%s avec succès." % (
|
"%d attribution%s %s été réinitialisée%s avec succès." % (
|
||||||
|
|
103
bda/forms.py
103
bda/forms.py
|
@ -4,7 +4,7 @@ from django import forms
|
||||||
from django.forms.models import BaseInlineFormSet
|
from django.forms.models import BaseInlineFormSet
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from bda.models import Attribution, Spectacle
|
from bda.models import Attribution, Spectacle, SpectacleRevente
|
||||||
|
|
||||||
|
|
||||||
class InscriptionInlineFormSet(BaseInlineFormSet):
|
class InscriptionInlineFormSet(BaseInlineFormSet):
|
||||||
|
@ -43,7 +43,33 @@ class TokenForm(forms.Form):
|
||||||
|
|
||||||
class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
||||||
def label_from_instance(self, obj):
|
def label_from_instance(self, obj):
|
||||||
return "%s" % str(obj.spectacle)
|
return str(obj.spectacle)
|
||||||
|
|
||||||
|
|
||||||
|
class ReventeModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
||||||
|
def __init__(self, *args, own=True, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.own = own
|
||||||
|
|
||||||
|
def label_from_instance(self, obj):
|
||||||
|
label = "{show}{suffix}"
|
||||||
|
suffix = ""
|
||||||
|
if self.own:
|
||||||
|
# C'est notre propre revente : pas besoin de spécifier le vendeur
|
||||||
|
if obj.soldTo is not None:
|
||||||
|
suffix = " -- Vendue à {firstname} {lastname}".format(
|
||||||
|
firstname=obj.soldTo.user.first_name,
|
||||||
|
lastname=obj.soldTo.user.last_name,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Ce n'est pas à nous : on ne voit jamais l'acheteur
|
||||||
|
suffix = " -- Vendue par {firstname} {lastname}".format(
|
||||||
|
firstname=obj.seller.user.first_name,
|
||||||
|
lastname=obj.seller.user.last_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
return label.format(show=str(obj.attribution.spectacle),
|
||||||
|
suffix=suffix)
|
||||||
|
|
||||||
|
|
||||||
class ResellForm(forms.Form):
|
class ResellForm(forms.Form):
|
||||||
|
@ -65,7 +91,8 @@ class ResellForm(forms.Form):
|
||||||
|
|
||||||
|
|
||||||
class AnnulForm(forms.Form):
|
class AnnulForm(forms.Form):
|
||||||
attributions = AttributionModelMultipleChoiceField(
|
reventes = ReventeModelMultipleChoiceField(
|
||||||
|
own=True,
|
||||||
label='',
|
label='',
|
||||||
queryset=Attribution.objects.none(),
|
queryset=Attribution.objects.none(),
|
||||||
widget=forms.CheckboxSelectMultiple,
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
@ -73,14 +100,13 @@ class AnnulForm(forms.Form):
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
def __init__(self, participant, *args, **kwargs):
|
||||||
super(AnnulForm, self).__init__(*args, **kwargs)
|
super(AnnulForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['attributions'].queryset = (
|
self.fields['reventes'].queryset = (
|
||||||
participant.attribution_set
|
participant.original_shows
|
||||||
.filter(spectacle__date__gte=timezone.now(),
|
.filter(attribution__spectacle__date__gte=timezone.now(),
|
||||||
revente__isnull=False,
|
notif_sent=False,
|
||||||
revente__notif_sent=False,
|
soldTo__isnull=True)
|
||||||
revente__soldTo__isnull=True)
|
.select_related('attribution__spectacle',
|
||||||
.select_related('spectacle', 'spectacle__location',
|
'attribution__spectacle__location')
|
||||||
'participant__user')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,19 +125,58 @@ class InscriptionReventeForm(forms.Form):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReventeTirageAnnulForm(forms.Form):
|
||||||
|
reventes = ReventeModelMultipleChoiceField(
|
||||||
|
own=False,
|
||||||
|
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.entered.filter(soldTo__isnull=True)
|
||||||
|
.select_related('attribution__spectacle',
|
||||||
|
'seller__user')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReventeTirageForm(forms.Form):
|
||||||
|
reventes = ReventeModelMultipleChoiceField(
|
||||||
|
own=False,
|
||||||
|
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(confirmed_entry=participant)
|
||||||
|
.select_related('attribution__spectacle')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SoldForm(forms.Form):
|
class SoldForm(forms.Form):
|
||||||
attributions = AttributionModelMultipleChoiceField(
|
reventes = ReventeModelMultipleChoiceField(
|
||||||
|
own=True,
|
||||||
label='',
|
label='',
|
||||||
queryset=Attribution.objects.none(),
|
queryset=Attribution.objects.none(),
|
||||||
widget=forms.CheckboxSelectMultiple)
|
widget=forms.CheckboxSelectMultiple)
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
def __init__(self, participant, *args, **kwargs):
|
||||||
super(SoldForm, self).__init__(*args, **kwargs)
|
super(SoldForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['attributions'].queryset = (
|
self.fields['reventes'].queryset = (
|
||||||
participant.attribution_set
|
participant.original_shows
|
||||||
.filter(revente__isnull=False,
|
.filter(soldTo__isnull=False)
|
||||||
revente__soldTo__isnull=False)
|
.exclude(soldTo=participant)
|
||||||
.exclude(revente__soldTo=participant)
|
.select_related('attribution__spectacle',
|
||||||
.select_related('spectacle', 'spectacle__location',
|
'attribution__spectacle__location')
|
||||||
'participant__user')
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,7 +6,6 @@ Gestion en ligne de commande des reventes.
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
from django.core.management import BaseCommand
|
from django.core.management import BaseCommand
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from bda.models import SpectacleRevente
|
from bda.models import SpectacleRevente
|
||||||
|
@ -21,23 +20,36 @@ class Command(BaseCommand):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
reventes = SpectacleRevente.objects.all()
|
reventes = SpectacleRevente.objects.all()
|
||||||
for revente in reventes:
|
for revente in reventes:
|
||||||
# Check si < 24h
|
# Le spectacle est bientôt et on a pas encore envoyé de mail :
|
||||||
if (revente.attribution.spectacle.date <=
|
# on met la place au shotgun et on prévient.
|
||||||
revente.date + timedelta(days=1)) and \
|
if revente.is_urgent and not revente.notif_sent:
|
||||||
now >= revente.date + timedelta(minutes=15) and \
|
if revente.can_notif:
|
||||||
not revente.notif_sent:
|
self.stdout.write(str(now))
|
||||||
self.stdout.write(str(now))
|
revente.mail_shotgun()
|
||||||
revente.mail_shotgun()
|
self.stdout.write(
|
||||||
self.stdout.write("Mail de disponibilité immédiate envoyé")
|
"Mails de disponibilité immédiate envoyés "
|
||||||
# Check si délai de retrait dépassé
|
"pour la revente [%s]" % revente
|
||||||
elif (now >= revente.date + timedelta(hours=1) and
|
)
|
||||||
not revente.notif_sent):
|
|
||||||
|
# Le spectacle est dans plus longtemps : on prévient
|
||||||
|
elif (revente.can_notif and not revente.notif_sent):
|
||||||
self.stdout.write(str(now))
|
self.stdout.write(str(now))
|
||||||
revente.send_notif()
|
revente.send_notif()
|
||||||
self.stdout.write("Mail d'inscription à une revente envoyé")
|
self.stdout.write(
|
||||||
# Check si tirage à faire
|
"Mails d'inscription à la revente [%s] envoyés"
|
||||||
elif (now >= revente.date_tirage and
|
% revente
|
||||||
not revente.tirage_done):
|
)
|
||||||
|
|
||||||
|
# On fait le tirage
|
||||||
|
elif (now >= revente.date_tirage and not revente.tirage_done):
|
||||||
self.stdout.write(str(now))
|
self.stdout.write(str(now))
|
||||||
revente.tirage()
|
winner = revente.tirage()
|
||||||
self.stdout.write("Tirage effectué, mails envoyés")
|
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")
|
||||||
|
|
29
bda/migrations/0012_notif_time.py
Normal file
29
bda/migrations/0012_notif_time.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# -*- 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.RenameField(
|
||||||
|
model_name='spectaclerevente',
|
||||||
|
old_name='answered_mail',
|
||||||
|
new_name='confirmed_entry',
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
]
|
165
bda/models.py
165
bda/models.py
|
@ -174,6 +174,7 @@ class Participant(models.Model):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s - %s" % (self.user, self.tirage.title)
|
return "%s - %s" % (self.user, self.tirage.title)
|
||||||
|
|
||||||
|
|
||||||
DOUBLE_CHOICES = (
|
DOUBLE_CHOICES = (
|
||||||
("1", "1 place"),
|
("1", "1 place"),
|
||||||
("autoquit", "2 places si possible, 1 sinon"),
|
("autoquit", "2 places si possible, 1 sinon"),
|
||||||
|
@ -232,9 +233,9 @@ class SpectacleRevente(models.Model):
|
||||||
)
|
)
|
||||||
date = models.DateTimeField("Date de mise en vente",
|
date = models.DateTimeField("Date de mise en vente",
|
||||||
default=timezone.now)
|
default=timezone.now)
|
||||||
answered_mail = models.ManyToManyField(Participant,
|
confirmed_entry = models.ManyToManyField(Participant,
|
||||||
related_name="wanted",
|
related_name="entered",
|
||||||
blank=True)
|
blank=True)
|
||||||
seller = models.ForeignKey(
|
seller = models.ForeignKey(
|
||||||
Participant, on_delete=models.CASCADE,
|
Participant, on_delete=models.CASCADE,
|
||||||
verbose_name="Vendeur",
|
verbose_name="Vendeur",
|
||||||
|
@ -248,21 +249,61 @@ class SpectacleRevente(models.Model):
|
||||||
|
|
||||||
notif_sent = models.BooleanField("Notification envoyée",
|
notif_sent = models.BooleanField("Notification envoyée",
|
||||||
default=False)
|
default=False)
|
||||||
|
|
||||||
|
notif_time = models.DateTimeField("Moment d'envoi de la notification",
|
||||||
|
blank=True, null=True)
|
||||||
|
|
||||||
tirage_done = models.BooleanField("Tirage effectué",
|
tirage_done = models.BooleanField("Tirage effectué",
|
||||||
default=False)
|
default=False)
|
||||||
|
|
||||||
shotgun = models.BooleanField("Disponible immédiatement",
|
shotgun = models.BooleanField("Disponible immédiatement",
|
||||||
default=False)
|
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 real_notif_time(self):
|
||||||
|
if self.notif_time:
|
||||||
|
return self.notif_time
|
||||||
|
else:
|
||||||
|
return self.date + self.remorse_time
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date_tirage(self):
|
def date_tirage(self):
|
||||||
"""Renvoie la date du tirage au sort de la revente."""
|
"""Renvoie la date du tirage au sort de la revente."""
|
||||||
# L'acheteur doit être connu au plus 12h avant le spectacle
|
|
||||||
remaining_time = (self.attribution.spectacle.date
|
remaining_time = (self.attribution.spectacle.date
|
||||||
- self.date - timedelta(hours=13))
|
- self.real_notif_time - self.min_margin)
|
||||||
# Au minimum, on attend 2 jours avant le tirage
|
|
||||||
delay = min(remaining_time, timedelta(days=2))
|
delay = min(remaining_time, self.max_wait_time)
|
||||||
# Le vendeur a aussi 1h pour changer d'avis
|
|
||||||
return self.date + delay + timedelta(hours=1)
|
return self.real_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):
|
def __str__(self):
|
||||||
return "%s -- %s" % (self.seller,
|
return "%s -- %s" % (self.seller,
|
||||||
|
@ -271,6 +312,18 @@ class SpectacleRevente(models.Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Revente"
|
verbose_name = "Revente"
|
||||||
|
|
||||||
|
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 = new_date
|
||||||
|
self.confirmed_entry.clear()
|
||||||
|
self.soldTo = None
|
||||||
|
self.notif_sent = False
|
||||||
|
self.notif_time = None
|
||||||
|
self.tirage_done = False
|
||||||
|
self.shotgun = False
|
||||||
|
self.save()
|
||||||
|
|
||||||
def send_notif(self):
|
def send_notif(self):
|
||||||
"""
|
"""
|
||||||
Envoie une notification pour indiquer la mise en vente d'une place sur
|
Envoie une notification pour indiquer la mise en vente d'une place sur
|
||||||
|
@ -291,6 +344,7 @@ class SpectacleRevente(models.Model):
|
||||||
]
|
]
|
||||||
send_mass_custom_mail(datatuple)
|
send_mass_custom_mail(datatuple)
|
||||||
self.notif_sent = True
|
self.notif_sent = True
|
||||||
|
self.notif_time = timezone.now()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def mail_shotgun(self):
|
def mail_shotgun(self):
|
||||||
|
@ -312,76 +366,79 @@ class SpectacleRevente(models.Model):
|
||||||
]
|
]
|
||||||
send_mass_custom_mail(datatuple)
|
send_mass_custom_mail(datatuple)
|
||||||
self.notif_sent = True
|
self.notif_sent = True
|
||||||
|
self.notif_time = timezone.now()
|
||||||
# Flag inutile, sauf si l'horloge interne merde
|
# Flag inutile, sauf si l'horloge interne merde
|
||||||
self.tirage_done = True
|
self.tirage_done = True
|
||||||
self.shotgun = True
|
self.shotgun = True
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def tirage(self):
|
def tirage(self, send_mails=True):
|
||||||
"""
|
"""
|
||||||
Lance le tirage au sort associé à la revente. Un gagnant est choisi
|
Lance le tirage au sort associé à la revente. Un gagnant est choisi
|
||||||
parmis les personnes intéressées par le spectacle. Les personnes sont
|
parmis les personnes intéressées par le spectacle. Les personnes sont
|
||||||
ensuites prévenues par mail du résultat du tirage.
|
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
|
spectacle = self.attribution.spectacle
|
||||||
seller = self.seller
|
seller = self.seller
|
||||||
|
winner = None
|
||||||
|
|
||||||
if inscrits:
|
if inscrits:
|
||||||
# Envoie un mail au gagnant et au vendeur
|
# Envoie un mail au gagnant et au vendeur
|
||||||
winner = random.choice(inscrits)
|
winner = random.choice(inscrits)
|
||||||
self.soldTo = winner
|
self.soldTo = winner
|
||||||
|
if send_mails:
|
||||||
|
mails = []
|
||||||
|
|
||||||
mails = []
|
context = {
|
||||||
|
'acheteur': winner.user,
|
||||||
|
'vendeur': seller.user,
|
||||||
|
'show': spectacle,
|
||||||
|
}
|
||||||
|
|
||||||
context = {
|
c_mails_qs = CustomMail.objects.filter(shortname__in=[
|
||||||
'acheteur': winner.user,
|
'bda-revente-winner', 'bda-revente-loser',
|
||||||
'vendeur': seller.user,
|
'bda-revente-seller',
|
||||||
'show': spectacle,
|
])
|
||||||
}
|
|
||||||
|
|
||||||
c_mails_qs = CustomMail.objects.filter(shortname__in=[
|
c_mails = {cm.shortname: cm for cm in c_mails_qs}
|
||||||
'bda-revente-winner', 'bda-revente-loser',
|
|
||||||
'bda-revente-seller',
|
|
||||||
])
|
|
||||||
|
|
||||||
c_mails = {cm.shortname: cm for cm in c_mails_qs}
|
mails.append(
|
||||||
|
c_mails['bda-revente-winner'].get_message(
|
||||||
mails.append(
|
context,
|
||||||
c_mails['bda-revente-winner'].get_message(
|
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||||
context,
|
to=[winner.user.email],
|
||||||
from_email=settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
to=[winner.user.email],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
mails.append(
|
|
||||||
c_mails['bda-revente-seller'].get_message(
|
|
||||||
context,
|
|
||||||
from_email=settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
to=[seller.user.email],
|
|
||||||
reply_to=[winner.user.email],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Envoie un mail aux perdants
|
|
||||||
for inscrit in inscrits:
|
|
||||||
if inscrit != winner:
|
|
||||||
new_context = dict(context)
|
|
||||||
new_context['acheteur'] = inscrit.user
|
|
||||||
|
|
||||||
mails.append(
|
|
||||||
c_mails['bda-revente-loser'].get_message(
|
|
||||||
new_context,
|
|
||||||
from_email=settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
to=[inscrit.user.email],
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
mail_conn = mail.get_connection()
|
mails.append(
|
||||||
mail_conn.send_messages(mails)
|
c_mails['bda-revente-seller'].get_message(
|
||||||
|
context,
|
||||||
|
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||||
|
to=[seller.user.email],
|
||||||
|
reply_to=[winner.user.email],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Envoie un mail aux perdants
|
||||||
|
for inscrit in inscrits:
|
||||||
|
if inscrit != winner:
|
||||||
|
new_context = dict(context)
|
||||||
|
new_context['acheteur'] = inscrit.user
|
||||||
|
|
||||||
|
mails.append(
|
||||||
|
c_mails['bda-revente-loser'].get_message(
|
||||||
|
new_context,
|
||||||
|
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||||
|
to=[inscrit.user.email],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mail_conn = mail.get_connection()
|
||||||
|
mail_conn.send_messages(mails)
|
||||||
# Si personne ne veut de la place, elle part au shotgun
|
# Si personne ne veut de la place, elle part au shotgun
|
||||||
else:
|
else:
|
||||||
self.shotgun = True
|
self.shotgun = True
|
||||||
self.tirage_done = True
|
self.tirage_done = True
|
||||||
self.save()
|
self.save()
|
||||||
|
return winner
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load bootstrap %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>Inscriptions pour BdA-Revente</h2>
|
|
||||||
<form action="" class="form-horizontal" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="form-group">
|
|
||||||
<h3>Spectacles</h3>
|
|
||||||
<br/>
|
|
||||||
<button type="button" class="btn btn-primary" onClick="select(true)">Tout sélectionner</button>
|
|
||||||
<button type="button" class="btn btn-primary" onClick="select(false)">Tout désélectionner</button>
|
|
||||||
|
|
||||||
<div class="multiple-checkbox">
|
|
||||||
<ul>
|
|
||||||
{% for checkbox in form.spectacles %}
|
|
||||||
<li>{{checkbox}}</li>
|
|
||||||
{%endfor%}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="submit" class="btn btn-primary" value="S'inscrire pour les places sélectionnées">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<script language="JavaScript">
|
|
||||||
function select(check) {
|
|
||||||
checkboxes = document.getElementsByName("spectacles");
|
|
||||||
for(var i=0, n=checkboxes.length;i<n;i++) {
|
|
||||||
checkboxes[i].checked = check;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
90
bda/templates/bda/revente/manage.html
Normal file
90
bda/templates/bda/revente/manage.html
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
{% load bootstrap %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
|
||||||
|
<h2>Gestion des places que je revends</h2>
|
||||||
|
{% with resell_attributions=resellform.attributions annul_reventes=annulform.reventes sold_reventes=soldform.reventes %}
|
||||||
|
|
||||||
|
{% if resellform.attributions %}
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<h3>Places non revendues</h3>
|
||||||
|
<form class="form-horizontal" action="" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Cochez les places que vous souhaitez revendre, et validez. Vous aurez
|
||||||
|
ensuite 1h pour changer d'avis avant que la revente soit confirmée et
|
||||||
|
que les notifications soient envoyées aux intéressé·e·s.
|
||||||
|
</div>
|
||||||
|
<div class="bootstrap-form-reduce">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ resellform|bootstrap }}
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if annul_reventes or overdue %}
|
||||||
|
<h3>Places en cours de revente</h3>
|
||||||
|
<form action="" method="post">
|
||||||
|
{% if annul_reventes %}
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Vous pouvez annuler les places mises en vente il y a moins d'une heure.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class='form-group'>
|
||||||
|
<div class='multiple-checkbox'>
|
||||||
|
<ul>
|
||||||
|
{% for revente in annul_reventes %}
|
||||||
|
<li>{{ revente.tag }} {{ revente.choice_label }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% for attrib in overdue %}
|
||||||
|
<li>
|
||||||
|
<input type="checkbox" style="visibility:hidden">
|
||||||
|
{{ attrib.spectacle }}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if annul_reventes %}
|
||||||
|
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
||||||
|
{% endif %}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if sold_reventes %}
|
||||||
|
<h3>Places revendues</h3>
|
||||||
|
<form action="" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Pour chaque revente, vous devez soit l'annuler soit la confirmer pour
|
||||||
|
transférer la place la place à la personne tirée au sort.
|
||||||
|
|
||||||
|
L'annulation sert par exemple à pouvoir remettre la place en jeu si
|
||||||
|
vous ne parvenez pas à entrer en contact avec la personne tirée au
|
||||||
|
sort.
|
||||||
|
</div>
|
||||||
|
<div class="bootstrap-form-reduce">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ soldform|bootstrap }}
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary" name="transfer">Transférer</button>
|
||||||
|
<button type="submit" class="btn btn-primary" name="reinit">Réinitialiser</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% if not resell_attributions and not annul_attributions and not overdue and not sold_reventes %}
|
||||||
|
<p>Plus de reventes possibles !</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock %}
|
|
@ -5,7 +5,7 @@
|
||||||
{% if shotgun %}
|
{% if shotgun %}
|
||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
{% for spectacle in shotgun %}
|
{% for spectacle in shotgun %}
|
||||||
<li><a href="{% url "bda-buy-revente" spectacle.id %}">{{spectacle}}</a></li>
|
<li><a href="{% url "bda-revente-buy" spectacle.id %}">{{spectacle}}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
<p> Pas de places disponibles immédiatement, désolé !</p>
|
<p> Pas de places disponibles immédiatement, désolé !</p>
|
46
bda/templates/bda/revente/subscribe.html
Normal file
46
bda/templates/bda/revente/subscribe.html
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
{% load bootstrap %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
<h2>Inscriptions pour BdA-Revente</h2>
|
||||||
|
<form action="" class="form-horizontal" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Cochez les spectacles pour lesquels vous souhaitez recevoir un
|
||||||
|
notification quand une place est disponible en revente. <br />
|
||||||
|
Lorsque vous validez vos choix, si un tirage au sort est en cours pour
|
||||||
|
un des spectacles que vous avez sélectionné, vous serez automatiquement
|
||||||
|
inscrit à ce tirage.
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
onClick="select(true)">Tout sélectionner</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
onClick="select(false)">Tout désélectionner</button>
|
||||||
|
|
||||||
|
<div class="multiple-checkbox">
|
||||||
|
<ul>
|
||||||
|
{% for checkbox in form.spectacles %}
|
||||||
|
<li>{{ checkbox }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="submit"
|
||||||
|
class="btn btn-primary"
|
||||||
|
value="S'inscrire pour les places sélectionnées">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script language="JavaScript">
|
||||||
|
function select(check) {
|
||||||
|
checkboxes = document.getElementsByName("spectacles");
|
||||||
|
for(var i=0, n=checkboxes.length; i < n; i++) {
|
||||||
|
checkboxes[i].checked = check;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
52
bda/templates/bda/revente/tirages.html
Normal file
52
bda/templates/bda/revente/tirages.html
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
{% load bootstrap %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
|
||||||
|
<h2>Tirages au sort de reventes</h2>
|
||||||
|
|
||||||
|
{% if annulform.reventes %}
|
||||||
|
<h3>Les reventes auxquelles vous êtes inscrit·e</h3>
|
||||||
|
<form class="form-horizontal" action="" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Vous pouvez vous désinscrire des reventes suivantes tant que le tirage n'a
|
||||||
|
pas eu lieu.
|
||||||
|
</div>
|
||||||
|
<div class="bootstrap-form-reduce">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ annulform|bootstrap }}
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<input type="submit"
|
||||||
|
class="btn btn-primary"
|
||||||
|
name="annul"
|
||||||
|
value="Se désinscrire des tirages sélectionnés">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if subform.reventes %}
|
||||||
|
|
||||||
|
<h3>Tirages en cours</h3>
|
||||||
|
<form class="form-horizontal" action="" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Vous pouvez vous inscrire aux tirage en cours suivants.
|
||||||
|
</div>
|
||||||
|
<div class="bootstrap-form-reduce">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ subform|bootstrap }}
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<input type="submit"
|
||||||
|
class="btn btn-primary"
|
||||||
|
name="subscribe"
|
||||||
|
value="S'inscrire aux tirages sélectionnés">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -6,7 +6,7 @@
|
||||||
<p>Le tirage au sort de cette revente a déjà été effectué !</p>
|
<p>Le tirage au sort de cette revente a déjà été effectué !</p>
|
||||||
|
|
||||||
<p>Si personne n'était intéressé, elle est maintenant disponible
|
<p>Si personne n'était intéressé, elle est maintenant disponible
|
||||||
<a href="{% url "bda-buy-revente" revente.attribution.spectacle.id %}">ici</a>.</p>
|
<a href="{% url "bda-revente-buy" revente.attribution.spectacle.id %}">ici</a>.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p> Il n'est pas encore possible de s'inscrire à cette revente, réessaie dans quelque temps !</p>
|
<p> Il n'est pas encore possible de s'inscrire à cette revente, réessaie dans quelque temps !</p>
|
||||||
{% endif %}
|
{% endif %}
|
|
@ -1,56 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load bootstrap %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
|
|
||||||
<h2>Revente de place</h2>
|
|
||||||
{% with resell_attributions=resellform.attributions annul_attributions=annulform.attributions sold_attributions=soldform.attributions %}
|
|
||||||
|
|
||||||
{% if resellform.attributions %}
|
|
||||||
<h3>Places non revendues</h3>
|
|
||||||
<form class="form-horizontal" action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{resellform|bootstrap}}
|
|
||||||
<div class="form-actions">
|
|
||||||
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
|
||||||
{% if annul_attributions or overdue %}
|
|
||||||
<h3>Places en cours de revente</h3>
|
|
||||||
<form action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class='form-group'>
|
|
||||||
<div class='multiple-checkbox'>
|
|
||||||
<ul>
|
|
||||||
{% for attrib in annul_attributions %}
|
|
||||||
<li>{{attrib.tag}} {{attrib.choice_label}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% for attrib in overdue %}
|
|
||||||
<li>
|
|
||||||
<input type="checkbox" style="visibility:hidden">
|
|
||||||
{{attrib.spectacle}}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% if annul_attributions %}
|
|
||||||
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
|
||||||
{% if sold_attributions %}
|
|
||||||
<h3>Places revendues</h3>
|
|
||||||
<form action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{soldform|bootstrap}}
|
|
||||||
<button type="submit" class="btn btn-primary" name="transfer">Transférer</button>
|
|
||||||
<button type="submit" class="btn btn-primary" name="reinit">Réinitialiser</button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
{% if not resell_attributions and not annul_attributions and not overdue and not sold_attributions %}
|
|
||||||
<p>Plus de reventes possibles !</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
|
|
@ -67,7 +67,7 @@ class SpectacleReventeTests(TestCase):
|
||||||
revente = self.rev
|
revente = self.rev
|
||||||
|
|
||||||
wanted_by = [self.p1, self.p2, self.p3]
|
wanted_by = [self.p1, self.p2, self.p3]
|
||||||
revente.answered_mail = wanted_by
|
revente.confirmed_entry = wanted_by
|
||||||
|
|
||||||
with mock.patch('bda.models.random.choice') as mc:
|
with mock.patch('bda.models.random.choice') as mc:
|
||||||
# Set winner to self.p1.
|
# Set winner to self.p1.
|
||||||
|
|
69
bda/tests/test_revente.py
Normal file
69
bda/tests/test_revente.py
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase, Client
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from bda.models import (Tirage, Spectacle, Salle, CategorieSpectacle,
|
||||||
|
SpectacleRevente, Attribution, Participant)
|
||||||
|
|
||||||
|
|
||||||
|
class TestModels(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.tirage = Tirage.objects.create(
|
||||||
|
title="Tirage test",
|
||||||
|
appear_catalogue=True,
|
||||||
|
ouverture=timezone.now(),
|
||||||
|
fermeture=timezone.now()
|
||||||
|
)
|
||||||
|
self.category = CategorieSpectacle.objects.create(name="Category")
|
||||||
|
self.location = Salle.objects.create(name="here")
|
||||||
|
self.spectacle_soon = Spectacle.objects.create(
|
||||||
|
title="foo", date=timezone.now()+timedelta(days=1),
|
||||||
|
location=self.location, price=0, slots=42,
|
||||||
|
tirage=self.tirage, listing=False, category=self.category
|
||||||
|
)
|
||||||
|
self.spectacle_later = Spectacle.objects.create(
|
||||||
|
title="bar", date=timezone.now()+timedelta(days=30),
|
||||||
|
location=self.location, price=0, slots=42,
|
||||||
|
tirage=self.tirage, listing=False, category=self.category
|
||||||
|
)
|
||||||
|
|
||||||
|
user_buyer = User.objects.create_user(
|
||||||
|
username="bda_buyer", password="testbuyer"
|
||||||
|
)
|
||||||
|
user_seller = User.objects.create_user(
|
||||||
|
username="bda_seller", password="testseller"
|
||||||
|
)
|
||||||
|
self.buyer = Participant.objects.create(
|
||||||
|
user=user_buyer, tirage=self.tirage
|
||||||
|
)
|
||||||
|
self.seller = Participant.objects.create(
|
||||||
|
user=user_seller, tirage=self.tirage
|
||||||
|
)
|
||||||
|
|
||||||
|
self.attr_soon = Attribution.objects.create(
|
||||||
|
participant=self.seller, spectacle=self.spectacle_soon
|
||||||
|
)
|
||||||
|
self.attr_later = Attribution.objects.create(
|
||||||
|
participant=self.seller, spectacle=self.spectacle_later
|
||||||
|
)
|
||||||
|
self.revente_soon = SpectacleRevente.objects.create(
|
||||||
|
seller=self.seller,
|
||||||
|
attribution=self.attr_soon
|
||||||
|
)
|
||||||
|
self.revente_later = SpectacleRevente.objects.create(
|
||||||
|
seller=self.seller,
|
||||||
|
attribution=self.attr_later
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_urgent(self):
|
||||||
|
self.assertTrue(self.revente_soon.is_urgent)
|
||||||
|
self.assertFalse(self.revente_later.is_urgent)
|
||||||
|
|
||||||
|
def test_tirage(self):
|
||||||
|
self.revente_soon.confirmed_entry.add(self.buyer)
|
||||||
|
|
||||||
|
self.assertEqual(self.revente_soon.tirage(send_mails=False),
|
||||||
|
self.buyer)
|
||||||
|
self.assertIsNone(self.revente_later.tirage(send_mails=False))
|
35
bda/urls.py
35
bda/urls.py
|
@ -16,9 +16,6 @@ urlpatterns = [
|
||||||
url(r'^places/(?P<tirage_id>\d+)$',
|
url(r'^places/(?P<tirage_id>\d+)$',
|
||||||
views.places,
|
views.places,
|
||||||
name="bda-places-attribuees"),
|
name="bda-places-attribuees"),
|
||||||
url(r'^revente/(?P<tirage_id>\d+)$',
|
|
||||||
views.revente,
|
|
||||||
name='bda-revente'),
|
|
||||||
url(r'^etat-places/(?P<tirage_id>\d+)$',
|
url(r'^etat-places/(?P<tirage_id>\d+)$',
|
||||||
views.etat_places,
|
views.etat_places,
|
||||||
name='bda-etat-places'),
|
name='bda-etat-places'),
|
||||||
|
@ -38,18 +35,28 @@ urlpatterns = [
|
||||||
url(r'^participants/autocomplete$',
|
url(r'^participants/autocomplete$',
|
||||||
views.participant_autocomplete,
|
views.participant_autocomplete,
|
||||||
name="bda-participant-autocomplete"),
|
name="bda-participant-autocomplete"),
|
||||||
url(r'^liste-revente/(?P<tirage_id>\d+)$',
|
|
||||||
views.list_revente,
|
# Urls BdA-Revente
|
||||||
name="bda-liste-revente"),
|
|
||||||
url(r'^buy-revente/(?P<spectacle_id>\d+)$',
|
url(r'^revente/(?P<tirage_id>\d+)/manage$',
|
||||||
views.buy_revente,
|
views.revente_manage,
|
||||||
name="bda-buy-revente"),
|
name='bda-revente-manage'),
|
||||||
url(r'^revente-interested/(?P<revente_id>\d+)$',
|
url(r'^revente/(?P<tirage_id>\d+)/subscribe$',
|
||||||
views.revente_interested,
|
views.revente_subscribe,
|
||||||
name='bda-revente-interested'),
|
name="bda-revente-subscribe"),
|
||||||
url(r'^revente-immediat/(?P<tirage_id>\d+)$',
|
url(r'^revente/(?P<tirage_id>\d+)/tirages$',
|
||||||
|
views.revente_tirages,
|
||||||
|
name="bda-revente-tirages"),
|
||||||
|
url(r'^revente/(?P<spectacle_id>\d+)/buy$',
|
||||||
|
views.revente_buy,
|
||||||
|
name="bda-revente-buy"),
|
||||||
|
url(r'^revente/(?P<revente_id>\d+)/confirm$',
|
||||||
|
views.revente_confirm,
|
||||||
|
name='bda-revente-confirm'),
|
||||||
|
url(r'^revente/(?P<tirage_id>\d+)/shotgun$',
|
||||||
views.revente_shotgun,
|
views.revente_shotgun,
|
||||||
name="bda-shotgun"),
|
name="bda-revente-shotgun"),
|
||||||
|
|
||||||
url(r'^mails-rappel/(?P<spectacle_id>\d+)$',
|
url(r'^mails-rappel/(?P<spectacle_id>\d+)$',
|
||||||
views.send_rappel,
|
views.send_rappel,
|
||||||
name="bda-rappels"
|
name="bda-rappels"
|
||||||
|
|
141
bda/views.py
141
bda/views.py
|
@ -5,7 +5,6 @@ import random
|
||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
from datetime import timedelta
|
|
||||||
from custommail.shortcuts import send_mass_custom_mail, send_custom_mail
|
from custommail.shortcuts import send_mass_custom_mail, send_custom_mail
|
||||||
from custommail.models import CustomMail
|
from custommail.models import CustomMail
|
||||||
from django.shortcuts import render, get_object_or_404
|
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.db import transaction
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
from django.db.models import Count, Q, Prefetch
|
from django.db.models import Count, Q, Prefetch
|
||||||
|
from django.template.defaultfilters import pluralize
|
||||||
from django.forms.models import inlineformset_factory
|
from django.forms.models import inlineformset_factory
|
||||||
from django.http import (
|
from django.http import (
|
||||||
HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
|
HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
|
||||||
|
@ -30,7 +30,7 @@ from bda.models import (
|
||||||
from bda.algorithm import Algorithm
|
from bda.algorithm import Algorithm
|
||||||
from bda.forms import (
|
from bda.forms import (
|
||||||
TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, SoldForm,
|
TokenForm, ResellForm, AnnulForm, InscriptionReventeForm, SoldForm,
|
||||||
InscriptionInlineFormSet,
|
InscriptionInlineFormSet, ReventeTirageForm, ReventeTirageAnnulForm
|
||||||
)
|
)
|
||||||
|
|
||||||
from utils.views.autocomplete import Select2QuerySetView
|
from utils.views.autocomplete import Select2QuerySetView
|
||||||
|
@ -351,13 +351,21 @@ def tirage(request, tirage_id):
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def revente(request, tirage_id):
|
def revente_manage(request, tirage_id):
|
||||||
|
"""
|
||||||
|
Gestion de ses propres reventes :
|
||||||
|
- Création d'une revente
|
||||||
|
- Annulation d'une revente
|
||||||
|
- Confirmation d'une revente = transfert de la place à la personne qui
|
||||||
|
rachète
|
||||||
|
- Annulation d'une revente après que le tirage a eu lieu
|
||||||
|
"""
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||||
participant, created = Participant.objects.get_or_create(
|
participant, created = Participant.objects.get_or_create(
|
||||||
user=request.user, tirage=tirage)
|
user=request.user, tirage=tirage)
|
||||||
|
|
||||||
if not participant.paid:
|
if not participant.paid:
|
||||||
return render(request, "bda-notpaid.html", {})
|
return render(request, "bda/revente/notpaid.html", {})
|
||||||
|
|
||||||
resellform = ResellForm(participant, prefix='resell')
|
resellform = ResellForm(participant, prefix='resell')
|
||||||
annulform = AnnulForm(participant, prefix='annul')
|
annulform = AnnulForm(participant, prefix='annul')
|
||||||
|
@ -377,12 +385,8 @@ def revente(request, tirage_id):
|
||||||
attribution=attribution,
|
attribution=attribution,
|
||||||
defaults={'seller': participant})
|
defaults={'seller': participant})
|
||||||
if not created:
|
if not created:
|
||||||
revente.seller = participant
|
revente.reset()
|
||||||
revente.date = timezone.now()
|
|
||||||
revente.soldTo = None
|
|
||||||
revente.notif_sent = False
|
|
||||||
revente.tirage_done = False
|
|
||||||
revente.shotgun = False
|
|
||||||
context = {
|
context = {
|
||||||
'vendeur': participant.user,
|
'vendeur': participant.user,
|
||||||
'show': attribution.spectacle,
|
'show': attribution.spectacle,
|
||||||
|
@ -399,18 +403,18 @@ def revente(request, tirage_id):
|
||||||
elif 'annul' in request.POST:
|
elif 'annul' in request.POST:
|
||||||
annulform = AnnulForm(participant, request.POST, prefix='annul')
|
annulform = AnnulForm(participant, request.POST, prefix='annul')
|
||||||
if annulform.is_valid():
|
if annulform.is_valid():
|
||||||
attributions = annulform.cleaned_data["attributions"]
|
reventes = annulform.cleaned_data["reventes"]
|
||||||
for attribution in attributions:
|
for revente in reventes:
|
||||||
attribution.revente.delete()
|
revente.delete()
|
||||||
# On confirme une vente en transférant la place à la personne qui a
|
# On confirme une vente en transférant la place à la personne qui a
|
||||||
# gagné le tirage
|
# gagné le tirage
|
||||||
elif 'transfer' in request.POST:
|
elif 'transfer' in request.POST:
|
||||||
soldform = SoldForm(participant, request.POST, prefix='sold')
|
soldform = SoldForm(participant, request.POST, prefix='sold')
|
||||||
if soldform.is_valid():
|
if soldform.is_valid():
|
||||||
attributions = soldform.cleaned_data['attributions']
|
reventes = soldform.cleaned_data['reventes']
|
||||||
for attribution in attributions:
|
for reventes in reventes:
|
||||||
attribution.participant = attribution.revente.soldTo
|
revente.attribution.participant = revente.soldTo
|
||||||
attribution.save()
|
revente.attribution.save()
|
||||||
|
|
||||||
# On annule la revente après le tirage au sort (par exemple si
|
# On annule la revente après le tirage au sort (par exemple si
|
||||||
# la personne qui a gagné le tirage ne se manifeste pas). La place est
|
# la personne qui a gagné le tirage ne se manifeste pas). La place est
|
||||||
|
@ -418,18 +422,13 @@ def revente(request, tirage_id):
|
||||||
elif 'reinit' in request.POST:
|
elif 'reinit' in request.POST:
|
||||||
soldform = SoldForm(participant, request.POST, prefix='sold')
|
soldform = SoldForm(participant, request.POST, prefix='sold')
|
||||||
if soldform.is_valid():
|
if soldform.is_valid():
|
||||||
attributions = soldform.cleaned_data['attributions']
|
reventes = soldform.cleaned_data['reventes']
|
||||||
for attribution in attributions:
|
for revente in reventes:
|
||||||
if attribution.spectacle.date > timezone.now():
|
if revente.attribution.spectacle.date > timezone.now():
|
||||||
revente = attribution.revente
|
# On antidate pour envoyer le mail plus vite
|
||||||
revente.date = timezone.now() - timedelta(minutes=65)
|
new_date = (timezone.now()
|
||||||
revente.soldTo = None
|
- SpectacleRevente.remorse_time)
|
||||||
revente.notif_sent = False
|
revente.reset(new_date=new_date)
|
||||||
revente.tirage_done = False
|
|
||||||
revente.shotgun = False
|
|
||||||
if revente.answered_mail:
|
|
||||||
revente.answered_mail.clear()
|
|
||||||
revente.save()
|
|
||||||
|
|
||||||
overdue = participant.attribution_set.filter(
|
overdue = participant.attribution_set.filter(
|
||||||
spectacle__date__gte=timezone.now(),
|
spectacle__date__gte=timezone.now(),
|
||||||
|
@ -439,28 +438,80 @@ def revente(request, tirage_id):
|
||||||
.filter(
|
.filter(
|
||||||
Q(revente__soldTo__isnull=True) | Q(revente__soldTo=participant))
|
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,
|
{'tirage': tirage, 'overdue': overdue, "soldform": soldform,
|
||||||
"annulform": annulform, "resellform": resellform})
|
"annulform": annulform, "resellform": resellform})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def revente_interested(request, revente_id):
|
def revente_tirages(request, tirage_id):
|
||||||
|
"""
|
||||||
|
Affiche à un participant la liste de toutes les reventes en cours (pour un
|
||||||
|
tirage donné) et lui permet de s'inscrire et se désinscrire à ces reventes.
|
||||||
|
"""
|
||||||
|
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||||
|
participant, _ = Participant.objects.get_or_create(
|
||||||
|
user=request.user, tirage=tirage)
|
||||||
|
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():
|
||||||
|
reventes = subform.cleaned_data['reventes']
|
||||||
|
count = reventes.count()
|
||||||
|
for revente in reventes:
|
||||||
|
revente.confirmed_entry.add(participant)
|
||||||
|
if count > 0:
|
||||||
|
messages.success(
|
||||||
|
request,
|
||||||
|
"Tu as bien été inscrit à {} revente{}"
|
||||||
|
.format(count, pluralize(count))
|
||||||
|
)
|
||||||
|
elif "annul" in request.POST:
|
||||||
|
annulform = ReventeTirageAnnulForm(participant, request.POST,
|
||||||
|
prefix="annul")
|
||||||
|
if annulform.is_valid():
|
||||||
|
reventes = annulform.cleaned_data['reventes']
|
||||||
|
count = reventes.count()
|
||||||
|
for revente in reventes:
|
||||||
|
revente.confirmed_entry.remove(participant)
|
||||||
|
if count > 0:
|
||||||
|
messages.success(
|
||||||
|
request,
|
||||||
|
"Tu as bien été désinscrit de {} revente{}"
|
||||||
|
.format(count, pluralize(count))
|
||||||
|
)
|
||||||
|
|
||||||
|
return render(request, "bda/revente/tirages.html",
|
||||||
|
{"annulform": annulform, "subform": subform})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def revente_confirm(request, revente_id):
|
||||||
revente = get_object_or_404(SpectacleRevente, id=revente_id)
|
revente = get_object_or_404(SpectacleRevente, id=revente_id)
|
||||||
participant, _ = Participant.objects.get_or_create(
|
participant, _ = Participant.objects.get_or_create(
|
||||||
user=request.user, tirage=revente.attribution.spectacle.tirage)
|
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-wrongtime.html",
|
return render(request, "bda/revente/wrongtime.html",
|
||||||
{"revente": revente})
|
{"revente": revente})
|
||||||
|
|
||||||
revente.answered_mail.add(participant)
|
revente.confirmed_entry.add(participant)
|
||||||
return render(request, "bda-interested.html",
|
return render(request, "bda/revente/confirmed.html",
|
||||||
{"spectacle": revente.attribution.spectacle,
|
{"spectacle": revente.attribution.spectacle,
|
||||||
"date": revente.date_tirage})
|
"date": revente.date_tirage})
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def list_revente(request, tirage_id):
|
def revente_subscribe(request, tirage_id):
|
||||||
|
"""
|
||||||
|
Permet à un participant de sélectionner ses préférences pour les reventes.
|
||||||
|
Il recevra des notifications pour les spectacles qui l'intéressent et il
|
||||||
|
est automatiquement inscrit aux reventes en cours au moment où il ajoute un
|
||||||
|
spectacle à la liste des spectacles qui l'intéressent.
|
||||||
|
"""
|
||||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||||
participant, _ = Participant.objects.get_or_create(
|
participant, _ = Participant.objects.get_or_create(
|
||||||
user=request.user, tirage=tirage)
|
user=request.user, tirage=tirage)
|
||||||
|
@ -486,12 +537,12 @@ def list_revente(request, tirage_id):
|
||||||
# la revente ayant le moins d'inscrits
|
# la revente ayant le moins d'inscrits
|
||||||
min_resell = (
|
min_resell = (
|
||||||
qset.filter(shotgun=False)
|
qset.filter(shotgun=False)
|
||||||
.annotate(nb_subscribers=Count('answered_mail'))
|
.annotate(nb_subscribers=Count('confirmed_entry'))
|
||||||
.order_by('nb_subscribers')
|
.order_by('nb_subscribers')
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
if min_resell is not None:
|
if min_resell is not None:
|
||||||
min_resell.answered_mail.add(participant)
|
min_resell.confirmed_entry.add(participant)
|
||||||
inscrit_revente.append(spectacle)
|
inscrit_revente.append(spectacle)
|
||||||
success = True
|
success = True
|
||||||
else:
|
else:
|
||||||
|
@ -514,11 +565,11 @@ def list_revente(request, tirage_id):
|
||||||
)
|
)
|
||||||
messages.info(request, msg, extra_tags="safe")
|
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
|
@login_required
|
||||||
def buy_revente(request, spectacle_id):
|
def revente_buy(request, spectacle_id):
|
||||||
spectacle = get_object_or_404(Spectacle, id=spectacle_id)
|
spectacle = get_object_or_404(Spectacle, id=spectacle_id)
|
||||||
tirage = spectacle.tirage
|
tirage = spectacle.tirage
|
||||||
participant, _ = Participant.objects.get_or_create(
|
participant, _ = Participant.objects.get_or_create(
|
||||||
|
@ -532,13 +583,13 @@ def buy_revente(request, spectacle_id):
|
||||||
own_reventes = reventes.filter(seller=participant)
|
own_reventes = reventes.filter(seller=participant)
|
||||||
if len(own_reventes) > 0:
|
if len(own_reventes) > 0:
|
||||||
own_reventes[0].delete()
|
own_reventes[0].delete()
|
||||||
return HttpResponseRedirect(reverse("bda-shotgun",
|
return HttpResponseRedirect(reverse("bda-revente-shotgun",
|
||||||
args=[tirage.id]))
|
args=[tirage.id]))
|
||||||
|
|
||||||
reventes_shotgun = reventes.filter(shotgun=True)
|
reventes_shotgun = reventes.filter(shotgun=True)
|
||||||
|
|
||||||
if not reventes_shotgun:
|
if not reventes_shotgun:
|
||||||
return render(request, "bda-no-revente.html", {})
|
return render(request, "bda/revente/none.html", {})
|
||||||
|
|
||||||
if request.POST:
|
if request.POST:
|
||||||
revente = random.choice(reventes_shotgun)
|
revente = random.choice(reventes_shotgun)
|
||||||
|
@ -555,11 +606,11 @@ def buy_revente(request, spectacle_id):
|
||||||
[revente.seller.user.email],
|
[revente.seller.user.email],
|
||||||
context=context,
|
context=context,
|
||||||
)
|
)
|
||||||
return render(request, "bda-success.html",
|
return render(request, "bda/revente/mail-success.html",
|
||||||
{"seller": revente.attribution.participant.user,
|
{"seller": revente.attribution.participant.user,
|
||||||
"spectacle": spectacle})
|
"spectacle": spectacle})
|
||||||
|
|
||||||
return render(request, "revente-confirm.html",
|
return render(request, "bda/revente/confirm-shotgun.html",
|
||||||
{"spectacle": spectacle,
|
{"spectacle": spectacle,
|
||||||
"user": request.user})
|
"user": request.user})
|
||||||
|
|
||||||
|
@ -583,7 +634,7 @@ def revente_shotgun(request, tirage_id):
|
||||||
)
|
)
|
||||||
shotgun = [sp for sp in spectacles if len(sp.shotguns) > 0]
|
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})
|
{"shotgun": shotgun})
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -159,23 +159,23 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
|
"pk": 3,
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-revente",
|
"shortname": "bda-revente",
|
||||||
"subject": "{{ show }}",
|
"subject": "{{ show }}",
|
||||||
"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",
|
"description": "Notification envoy\u00e9e \u00e0 toutes les personnes int\u00e9ress\u00e9es par un spectacle pour leur signaler qu'une place vient d'\u00eatre mise en vente.",
|
||||||
"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-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"
|
||||||
},
|
}
|
||||||
"pk": 3
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
|
"pk": 4,
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-shotgun",
|
"shortname": "bda-shotgun",
|
||||||
"subject": "{{ show }}",
|
"subject": "{{ show }}",
|
||||||
"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",
|
"description": "Notification signalant qu'une place est au shotgun aux personnes int\u00e9ress\u00e9es.",
|
||||||
"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-revente-buy\" show.id %}, \u00e0 la disposition de tous.\r\n\r\nChaleureusement,\r\nLe BdA"
|
||||||
},
|
}
|
||||||
"pk": 4
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
|
|
|
@ -1140,3 +1140,14 @@ p.help-block {
|
||||||
margin: 5px auto;
|
margin: 5px auto;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.bg-info {
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 0.3em 1em;
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bootstrap-form-reduce > .form-group {
|
||||||
|
margin-top: -16px;
|
||||||
|
}
|
||||||
|
|
|
@ -43,9 +43,10 @@
|
||||||
<li><a href="{% url "bda-etat-places" tirage.id %}">État des demandes</a></li>
|
<li><a href="{% url "bda-etat-places" tirage.id %}">État des demandes</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{% url "bda-places-attribuees" tirage.id %}">Mes places</a></li>
|
<li><a href="{% url "bda-places-attribuees" tirage.id %}">Mes places</a></li>
|
||||||
<li><a href="{% url "bda-revente" tirage.id %}">Revendre une place</a></li>
|
<li><a href="{% url "bda-revente-manage" tirage.id %}">Gérer les places que je revends</a></li>
|
||||||
<li><a href="{% url "bda-liste-revente" tirage.id %}">S'inscrire à BdA-Revente</a></li>
|
<li><a href="{% url "bda-revente-tirages" tirage.id %}">Voir les reventes en cours</a></li>
|
||||||
<li><a href="{% url "bda-shotgun" tirage.id %}">Places disponibles immédiatement</a></li>
|
<li><a href="{% url "bda-revente-subscribe" tirage.id %}">Indiquer les spectacles qui m'intéressent</a></li>
|
||||||
|
<li><a href="{% url "bda-revente-shotgun" tirage.id %}">Places disponibles immédiatement</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
Loading…
Reference in a new issue