diff --git a/bda/admin.py b/bda/admin.py index a9b3c51f..0e9b683b 100644 --- a/bda/admin.py +++ b/bda/admin.py @@ -225,7 +225,7 @@ class SpectacleReventeAdmin(admin.ModelAdmin): list_display = ("spectacle", "seller", "date", "soldTo") raw_id_fields = ("attribution",) - readonly_fields = ("shotgun", "expiration_time") + readonly_fields = ("date_tirage",) search_fields = ['attribution__spectacle__title', 'seller__user__username', 'seller__user__first_name', diff --git a/bda/forms.py b/bda/forms.py index fe20e565..352914e4 100644 --- a/bda/forms.py +++ b/bda/forms.py @@ -68,9 +68,8 @@ class AnnulForm(forms.Form): self.fields['attributions'].queryset = participant.attribution_set\ .filter(spectacle__date__gte=timezone.now(), revente__isnull=False, - revente__date__gt=timezone.now()-timedelta(hours=1))\ - .filter(Q(revente__soldTo__isnull=True) | - Q(revente__soldTo=participant)) + revente__date__gt=timezone.now()-timedelta(hours=1), + revente__soldTo__isnull=True) class InscriptionReventeForm(forms.Form): diff --git a/bda/management/commands/manage_reventes.py b/bda/management/commands/manage_reventes.py index 4b90bc57..0302ec4b 100644 --- a/bda/management/commands/manage_reventes.py +++ b/bda/management/commands/manage_reventes.py @@ -36,7 +36,7 @@ class Command(BaseCommand): revente.send_notif() self.stdout.write("Mail d'inscription à une revente envoyé") # Check si tirage à faire - elif (now >= revente.expiration_time and + elif (now >= revente.date_tirage and not revente.tirage_done): self.stdout.write(str(now)) revente.tirage() diff --git a/bda/migrations/0010_spectaclerevente_shotgun.py b/bda/migrations/0010_spectaclerevente_shotgun.py new file mode 100644 index 00000000..35b4da8a --- /dev/null +++ b/bda/migrations/0010_spectaclerevente_shotgun.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +from django.utils import timezone +from datetime import timedelta + + +def forwards_func(apps, schema_editor): + SpectacleRevente = apps.get_model("bda", "SpectacleRevente") + + for revente in SpectacleRevente.objects.all(): + is_expired = timezone.now() > revente.date_tirage() + is_direct = (revente.attribution.spectacle.date >= revente.date and + timezone.now() > revente.date + timedelta(minutes=15)) + revente.shotgun = is_expired or is_direct + revente.save() + + +class Migration(migrations.Migration): + + dependencies = [ + ('bda', '0009_revente'), + ] + + operations = [ + migrations.AddField( + model_name='spectaclerevente', + name='shotgun', + field=models.BooleanField(default=False, verbose_name='Disponible imm\xe9diatement'), + ), + migrations.RunPython(forwards_func, migrations.RunPython.noop), + ] diff --git a/bda/models.py b/bda/models.py index b2dc1145..f67807c7 100644 --- a/bda/models.py +++ b/bda/models.py @@ -225,32 +225,20 @@ class SpectacleRevente(models.Model): default=False) tirage_done = models.BooleanField("Tirage effectué", default=False) + shotgun = models.BooleanField("Disponible immédiatement", + default=False) @property - def expiration_time(self): + 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 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)) - # On a aussi 1h pour changer d'avis + # Le vendeur a aussi 1h pour changer d'avis return self.date + delay + timedelta(hours=1) - def expiration_time_str(self): - return self.expiration_time \ - .astimezone(timezone.get_current_timezone()) \ - .strftime('%d/%m/%y à %H:%M') - - @property - def shotgun(self): - # Soit on a dépassé le délai du tirage, soit il reste peu de - # temps avant le spectacle - # On se laisse 5min de marge pour cron - return (timezone.now() > self.expiration_time + timedelta(minutes=5) or - (self.attribution.spectacle.date <= timezone.now() + - timedelta(days=1))) and (timezone.now() >= self.date + - timedelta(minutes=15)) - def __str__(self): return "%s -- %s" % (self.seller, self.attribution.spectacle.title) @@ -307,6 +295,9 @@ class SpectacleRevente(models.Model): from_email=settings.MAIL_DATA['revente']['FROM'] ) self.notif_sent = True + # Flag inutile, sauf si l'horloge interne merde + self.tirage_done = True + self.shotgun = True self.save() def tirage(self): @@ -358,5 +349,8 @@ class SpectacleRevente(models.Model): mails_data, from_email=settings.MAIL_DATA['revente']['FROM'] ) + # Si personne ne veut de la place, elle part au shotgun + else: + self.shotgun = True self.tirage_done = True self.save() diff --git a/bda/templates/bda-interested.html b/bda/templates/bda-interested.html index acfb1d1e..780330bd 100644 --- a/bda/templates/bda-interested.html +++ b/bda/templates/bda-interested.html @@ -3,7 +3,7 @@ {% block realcontent %}

Inscription à une revente

-

Votre inscription pour a bien été enregistrée !

+

Votre inscription a bien été enregistrée !

Le tirage au sort pour cette revente ({{spectacle}}) sera effectué le {{date}}. {% endblock %} diff --git a/bda/templates/bda-wrongtime.html b/bda/templates/bda-wrongtime.html index 5e17926b..dfafb05f 100644 --- a/bda/templates/bda-wrongtime.html +++ b/bda/templates/bda-wrongtime.html @@ -1,6 +1,13 @@ {% extends "base_title.html" %} {% block realcontent %} -

Nope

-

Cette revente n'est pas disponible actuellement, désolé !

+

Nope

+ {% if revente.shotgun %} +

Le tirage au sort de cette revente a déjà été effectué !

+ +

Si personne n'était intéressé, elle est maintenant disponible + ici.

+ {% else %} +

Il n'est pas encore possible de s'inscrire à cette revente, réessaie dans quelque temps !

+ {% endif %} {% endblock %} diff --git a/bda/templates/liste-reventes.html b/bda/templates/liste-reventes.html index d08d4010..c0bf8ff0 100644 --- a/bda/templates/liste-reventes.html +++ b/bda/templates/liste-reventes.html @@ -8,7 +8,10 @@ {% endif %} {% if deja_revente %}

Des reventes existent déjà pour certains de ces spectacles ; vérifie les places disponibles sans tirage !

+ {% elif inscrit_revente %} +

Tu as été inscrit à une revente en cours pour ce spectacle !

{% endif %} +
{% csrf_token %}
diff --git a/bda/views.py b/bda/views.py index a0767c67..24b83c86 100644 --- a/bda/views.py +++ b/bda/views.py @@ -271,6 +271,7 @@ def revente(request, tirage_id): if not participant.paid: return render(request, "bda-notpaid.html", {}) if request.method == 'POST': + # On met en vente une place if 'resell' in request.POST: resellform = ResellForm(participant, request.POST, prefix='resell') annulform = AnnulForm(participant, prefix='annul') @@ -279,12 +280,17 @@ def revente(request, tirage_id): attributions = resellform.cleaned_data["attributions"] with transaction.atomic(): for attribution in attributions: - revente, created = SpectacleRevente.objects.get_or_create( - attribution=attribution, - defaults={'seller': participant}) + revente, created = \ + SpectacleRevente.objects.get_or_create( + attribution=attribution, + defaults={'seller': participant}) if not created: revente.seller = participant revente.date = timezone.now() + revente.soldTo = None + revente.notif_sent = False + revente.tirage_done = False + revente.shotgun = False context = { 'vendeur': participant.user, 'show': attribution.spectacle, @@ -297,7 +303,7 @@ def revente(request, tirage_id): mails_data, from_email=settings.MAIL_DATA['revente']['FROM'] ) - + # On annule une revente elif 'annul' in request.POST: annulform = AnnulForm(participant, request.POST, prefix='annul') resellform = ResellForm(participant, prefix='resell') @@ -305,7 +311,8 @@ def revente(request, tirage_id): attributions = annulform.cleaned_data["attributions"] for attribution in attributions: attribution.revente.delete() - + # On confirme une vente en transférant la place à la personne qui a + # gagné le tirage elif 'transfer' in request.POST: resellform = ResellForm(participant, prefix='resell') annulform = AnnulForm(participant, prefix='annul') @@ -318,7 +325,9 @@ def revente(request, tirage_id): attrib = revente.attribution attrib.participant = revente.soldTo attrib.save() - + # 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 + # alors remise en vente elif 'reinit' in request.POST: resellform = ResellForm(participant, prefix='resell') annulform = AnnulForm(participant, prefix='annul') @@ -332,6 +341,7 @@ def revente(request, tirage_id): revente.soldTo = None revente.notif_sent = False revente.tirage_done = False + revente.shotgun = False if revente.answered_mail: revente.answered_mail.clear() revente.save() @@ -352,8 +362,7 @@ def revente(request, tirage_id): sold = participant.attribution_set.filter( spectacle__date__gte=timezone.now(), revente__isnull=False, - revente__soldTo__isnull=False).exclude( - revente__soldTo=participant) + revente__soldTo__isnull=False) return render(request, "bda-revente.html", {'tirage': tirage, 'overdue': overdue, "sold": sold, @@ -365,13 +374,14 @@ def revente_interested(request, revente_id): revente = get_object_or_404(SpectacleRevente, id=revente_id) participant, created = 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", {}) + if (timezone.now() < revente.date + timedelta(hours=1)) or revente.shotgun: + return render(request, "bda-wrongtime.html", + {"revente": revente}) revente.answered_mail.add(participant) return render(request, "bda-interested.html", {"spectacle": revente.attribution.spectacle, - "date": revente.expiration_time}) + "date": revente.date_tirage}) @login_required @@ -379,23 +389,9 @@ def list_revente(request, tirage_id): tirage = get_object_or_404(Tirage, id=tirage_id) participant, created = Participant.objects.get_or_create( user=request.user, tirage=tirage) - spectacles = tirage.spectacle_set.filter( - date__gte=timezone.now()) - shotgun = [] deja_revente = False success = False - for spectacle in spectacles: - revente_objects = SpectacleRevente.objects.filter( - attribution__spectacle=spectacle, - soldTo__isnull=True) - revente_count = 0 - for revente in revente_objects: - if revente.shotgun: - revente_count += 1 - if revente_count: - spectacle.revente_count = revente_count - shotgun.append(spectacle) - + inscrit_revente = False if request.method == 'POST': form = InscriptionReventeForm(tirage, request.POST) if form.is_valid(): @@ -405,15 +401,24 @@ def list_revente(request, tirage_id): for spectacle in choices: qset = SpectacleRevente.objects.filter( attribution__spectacle=spectacle) - if qset.exists(): - # On l'inscrit à l'un des tirages au sort - for revente in qset.all(): - if revente.shotgun and not revente.soldTo: - deja_revente = True - else: - revente.answered_mail.add(participant) - revente.save() - break + if qset.filter(shotgun=True, soldTo__isnull=True).exists(): + # Une place est disponible au shotgun, on suggère à + # l'utilisateur d'aller la récupérer + deja_revente = True + else: + # La place n'est pas disponible au shotgun, si des reventes + # pour ce spectacle existent déjà, on inscrit la personne à + # la revente ayant le moins d'inscrits + min_resell = ( + qset.filter(shotgun=False) + .annotate(nb_subscribers=Count('answered_mail')) + .order_by('nb_subscribers') + .first() + ) + if min_resell is not None: + min_resell.answered_mail.add(participant) + min_resell.save() + inscrit_revente = True success = True else: form = InscriptionReventeForm( @@ -421,8 +426,9 @@ def list_revente(request, tirage_id): initial={'spectacles': participant.choicesrevente.all()}) return render(request, "liste-reventes.html", - {"form": form, 'shotgun': shotgun, - "deja_revente": deja_revente, "success": success}) + {"form": form, + "deja_revente": deja_revente, "success": success, + "inscrit_revente": inscrit_revente}) @login_required @@ -434,15 +440,16 @@ def buy_revente(request, spectacle_id): reventes = SpectacleRevente.objects.filter( attribution__spectacle=spectacle, soldTo__isnull=True) + + # Si l'utilisateur veut racheter une place qu'il est en train de revendre, + # on supprime la revente en question. if reventes.filter(seller=participant).exists(): revente = reventes.filter(seller=participant)[0] revente.delete() return HttpResponseRedirect(reverse("bda-shotgun", args=[tirage.id])) - reventes_shotgun = [] - for revente in reventes.all(): - if revente.shotgun: - reventes_shotgun.append(revente) + + reventes_shotgun = list(reventes.filter(shotgun=True).all()) if not reventes_shotgun: return render(request, "bda-no-revente.html", {}) @@ -478,14 +485,11 @@ def revente_shotgun(request, tirage_id): date__gte=timezone.now()) shotgun = [] for spectacle in spectacles: - revente_objects = SpectacleRevente.objects.filter( + reventes = SpectacleRevente.objects.filter( attribution__spectacle=spectacle, + shotgun=True, soldTo__isnull=True) - revente_count = 0 - for revente in revente_objects: - if revente.shotgun: - revente_count += 1 - if revente_count: + if reventes.exists(): shotgun.append(spectacle) return render(request, "bda-shotgun.html",