# coding: utf-8 from __future__ import division from django.shortcuts import render, get_object_or_404 from django.contrib.auth.decorators import login_required from django.db import models from django.core import serializers from django.forms.models import inlineformset_factory import hashlib from django.core.mail import send_mail from django.utils import timezone from django.views.generic.list import ListView from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from django.contrib import messages from datetime import timedelta import time from gestioncof.decorators import cof_required, buro_required from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution, Tirage from bda.algorithm import Algorithm from bda.forms import BaseBdaFormSet, TokenForm, ResellForm @cof_required def etat_places(request, tirage_id): tirage = get_object_or_404(Tirage, id=tirage_id) spectacles1 = ChoixSpectacle.objects \ .filter(spectacle__tirage=tirage) \ .filter(double_choice="1") \ .all() \ .values('spectacle','spectacle__title') \ .annotate(total=models.Count('spectacle')) spectacles2 = ChoixSpectacle.objects \ .filter(spectacle__tirage=tirage) \ .exclude(double_choice="1") \ .all() \ .values('spectacle','spectacle__title') \ .annotate(total=models.Count('spectacle')) spectacles = tirage.spectacle_set.all() spectacles_dict = {} total = 0 for spectacle in spectacles: spectacle.total = 0 spectacle.ratio = 0.0 spectacles_dict[spectacle.id] = spectacle for spectacle in spectacles1: spectacles_dict[spectacle["spectacle"]].total += spectacle["total"] spectacles_dict[spectacle["spectacle"]].ratio = \ spectacles_dict[spectacle["spectacle"]].total / \ spectacles_dict[spectacle["spectacle"]].slots total += spectacle["total"] for spectacle in spectacles2: spectacles_dict[spectacle["spectacle"]].total += 2*spectacle["total"] spectacles_dict[spectacle["spectacle"]].ratio = \ spectacles_dict[spectacle["spectacle"]].total / \ spectacles_dict[spectacle["spectacle"]].slots total += spectacle["total"] return render(request, "etat-places.html", {"spectacles": spectacles, "total": total, 'tirage': tirage}) def _hash_queryset(queryset): data = serializers.serialize("json", queryset) hasher = hashlib.sha256() hasher.update(data) return hasher.hexdigest() @cof_required def places(request, tirage_id): tirage = get_object_or_404(Tirage, id=tirage_id) participant, created = Participant.objects.get_or_create( user=request.user, tirage=tirage) places = participant.attribution_set.order_by( "spectacle__date", "spectacle").all() total = sum([place.spectacle.price for place in places]) filtered_places = [] places_dict = {} spectacles = [] dates = [] warning = False for place in places: if place.spectacle in spectacles: places_dict[place.spectacle].double = True else: place.double = False places_dict[place.spectacle] = place spectacles.append(place.spectacle) filtered_places.append(place) date = place.spectacle.date.date() if date in dates: warning = True else: dates.append(date) return render(request, "resume_places.html", {"participant": participant, "places": filtered_places, "tirage": tirage, "total": total, "warning": warning}) @cof_required def places_ics(request, tirage_id): tirage = get_object_or_404(Tirage, id=tirage_id) participant, created = Participant.objects.get_or_create( user=request.user, tirage=tirage) places = participant.attribution_set.order_by( "spectacle__date", "spectacle").all() filtered_places = [] places_dict = {} spectacles = [] for place in places: if place.spectacle in spectacles: places_dict[place.spectacle].double = True else: place.double = False place.spectacle.dtend = place.spectacle.date + timedelta(seconds=7200) places_dict[place.spectacle] = place spectacles.append(place.spectacle) filtered_places.append(place) return render(request, "resume_places.ics", {"participant": participant, "places": filtered_places}, content_type="text/calendar") @cof_required def inscription(request, tirage_id): tirage = get_object_or_404(Tirage, id=tirage_id) if timezone.now() < tirage.ouverture: error_desc = "Ouverture le %s" % ( tirage.ouverture.strftime('%d %b %Y à %H:%M')) return render(request, 'resume_inscription.html', { "error_title": "Le tirage n'est pas encore ouvert !", "error_description": error_desc }) if timezone.now() > tirage.fermeture: participant, created = Participant.objects.get_or_create( user=request.user, tirage=tirage) choices = participant.choixspectacle_set.order_by("priority").all() return render(request, "resume_inscription.html", { "error_title": "C'est fini !", "error_description": u"Tirage au sort dans la journée !", "choices": choices}) def formfield_callback(f, **kwargs): if f.name == "spectacle": kwargs['queryset'] = tirage.spectacle_set return f.formfield(**kwargs) BdaFormSet = inlineformset_factory( Participant, ChoixSpectacle, fields=("spectacle","double_choice","priority"), formset=BaseBdaFormSet, formfield_callback=formfield_callback) participant, created = Participant.objects.get_or_create( user=request.user, tirage=tirage) success = False stateerror = False if request.method == "POST": dbstate = _hash_queryset(participant.choixspectacle_set.all()) if "dbstate" in request.POST and dbstate != request.POST["dbstate"]: stateerror = True formset = BdaFormSet(instance=participant) else: formset = BdaFormSet(request.POST, instance=participant) if formset.is_valid(): formset.save() success = True formset = BdaFormSet(instance=participant) else: formset = BdaFormSet(instance=participant) dbstate = _hash_queryset(participant.choixspectacle_set.all()) total_price = 0 for choice in participant.choixspectacle_set.all(): total_price += choice.spectacle.price if choice.double: total_price += choice.spectacle.price return render(request, "inscription-bda.html", { "formset": formset, "success": success, "total_price": total_price, "dbstate": dbstate, 'tirage': tirage, "stateerror": stateerror}) def do_tirage(request, tirage_id): tirage_elt = get_object_or_404(Tirage, id=tirage_id) form = TokenForm(request.POST) if not form.is_valid(): return tirage(request) tirage_elt.token = form.cleaned_data['token'] tirage_elt.save() start = time.time() data = {} shows = tirage_elt.spectacle_set.select_related().all() members = tirage_elt.participant_set.all() choices = ChoixSpectacle.objects.filter(spectacle__tirage=tirage_elt).order_by( 'participant', 'priority').select_related().all() algo = Algorithm(shows, members, choices) results = algo(form.cleaned_data["token"]) total_slots = 0 total_losers = 0 for (_, members, losers) in results: total_slots += len(members) total_losers += len(losers) data["total_slots"] = total_slots data["total_losers"] = total_losers data["shows"] = shows data["token"] = form.cleaned_data["token"] data["members"] = members data["results"] = results total_sold = 0 total_deficit = 0 opera_deficit = 0 for (show, members, _) in results: deficit = (show.slots - len(members)) * show.price total_sold += show.slots * show.price if deficit >= 0: if u"Opéra" in show.location.name: opera_deficit += deficit total_deficit += deficit data["total_sold"] = total_sold - total_deficit data["total_deficit"] = total_deficit data["opera_deficit"] = opera_deficit data["duration"] = time.time() - start if request.user.is_authenticated(): members2 = {} members_uniq = {} # Participant objects are not shared accross spectacle results, # So assign a single object for each Participant id for (show, members, _) in results: for (member, _, _, _) in members: if member.id not in members_uniq: members_uniq[member.id] = member members2[member] = [] member.total = 0 member = members_uniq[member.id] members2[member].append(show) member.total += show.price members2 = members2.items() data["members2"] = sorted(members2, key=lambda m: m[0].user.last_name) # À partir d'ici, le tirage devient effectif # FIXME: Établir les conditions de validations (formulaire ?) # cf. issue #32 if False: Attribution.objects.all().delete() for (show, members, _) in results: for (member, _, _, _) in members: attrib = Attribution(spectacle=show, participant=member) attrib.save() return render(request, "bda-attrib-extra.html", data) else: return render(request, "bda-attrib.html", data) @buro_required def tirage(request, tirage_id): if request.POST: form = TokenForm(request.POST) if form.is_valid(): return do_tirage(request, tirage_id) else: form = TokenForm() return render(request, "bda-token.html", {"form": form}) def do_resell(request, form): spectacle = form.cleaned_data["spectacle"] count = form.cleaned_data["count"] places = "2 places" if count == "2" else "une place" mail = u"""Bonjour, Je souhaite revendre %s pour %s le %s (%s) à %.02f€. Contactez moi par email si vous êtes intéressé·e·s ! %s (%s)""" % (places, spectacle.title, spectacle.date_no_seconds(), spectacle.location, spectacle.price, request.user.get_full_name(), request.user.email) send_mail("%s" % spectacle, mail, request.user.email, ["bda-revente@lists.ens.fr"], fail_silently = False) return render(request, "bda-success.html", {"show": spectacle, "places": places}) @login_required def 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) if not participant.paid: return render(request, "bda-notpaid.html", {}) if request.POST: form = ResellForm(participant, request.POST) if form.is_valid(): return do_resell(request, form) else: form = ResellForm(participant) return render(request, "bda-revente.html", {"form": form, 'tirage': tirage}) @buro_required def spectacle(request, tirage_id, spectacle_id): tirage = get_object_or_404(Tirage, id=tirage_id) spectacle = get_object_or_404(Spectacle, id=spectacle_id, tirage=tirage) attributions = spectacle.attribues.all() participants = {} for attrib in attributions: participant = attrib.participant participant_info = {'lastname':participant.user.last_name, 'name': participant.user.get_full_name, 'username':participant.user.username, 'email':participant.user.email, 'given':attrib.given, 'paid':participant.paid, 'nb_places':1} if participant.id in participants: participants[participant.id]['nb_places'] = 2 else: participants[participant.id] = participant_info participants_info = sorted(participants.values(), key=lambda part: part['lastname']) return render(request, "bda-participants.html", {"spectacle": spectacle, "participants": participants_info}) @buro_required def add_attrib(request, tirage_id, spectacle_id): tirage = get_object_or_404(Tirage, id=tirage_id) spectacle = get_object_or_404(Spectacle, id = spectacle_id, tirage=tirage) try: part=tirage.participant_set.get(user__username=request.POST['username']) except Participant.DoesNotExist: messages.add_message(request, messages.ERROR, u"Erreur : utilisateur %s non trouvé" % request.POST['username']) return HttpResponseRedirect(reverse('bda-spectacle', args=(tirage_id, spectacle_id,))) places_owned = len(spectacle.attribues.filter(participant=part)) if int(request.POST['nb_places'])+places_owned > 2: messages.add_message(request, messages.ERROR, "Erreur: on ne peut pas avoir plus de deux places") return HttpResponseRedirect(reverse('bda-spectacle', args=(tirage_id, spectacle_id,))) attrib = Attribution(participant=part, spectacle=spectacle, given=('given' in request.POST)) attrib.save() if request.POST['nb_places']==2: attrib.save() messages.add_message(request, messages.SUCCESS, "Attribution réussie !") return HttpResponseRedirect(reverse('bda-spectacle', args=(tirage_id, spectacle_id,))) class SpectacleListView(ListView): model = Spectacle template_name = 'spectacle_list.html' def get_queryset(self): self.tirage = get_object_or_404(Tirage, id=self.kwargs['tirage_id']) categories = self.tirage.spectacle_set.all() return categories def get_context_data(self, **kwargs): context = super(SpectacleListView, self).get_context_data(**kwargs) context['tirage_id'] = self.tirage.id context['tirage_name'] = self.tirage.title return context @buro_required def unpaid(request, tirage_id): tirage = get_object_or_404(Tirage, id=tirage_id) unpaid = tirage.participant_set.filter(paid=False).all() return render(request, "bda-unpaid.html", {"unpaid": unpaid}) @buro_required def liste_spectacles_ics(request, tirage_id): tirage = get_object_or_404(Tirage, id=tirage_id) spectacles = tirage.spectacle_set.order_by("date").all() for spectacle in spectacles: spectacle.dtend = spectacle.date + timedelta(seconds=7200) return render(request, "liste_spectacles.ics", {"spectacles": spectacles, "tirage": tirage}, content_type="text/calendar")