forked from DGNum/gestioCOF
Merge branch 'master' of https://git.eleves.ens.fr/cof-geek/gestioCOF into Aufinal/fix_reinit_past
This commit is contained in:
commit
a9a4bf6b4a
12 changed files with 175 additions and 44 deletions
53
bda/admin.py
53
bda/admin.py
|
@ -5,12 +5,13 @@ from __future__ import print_function
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db.models import Sum, Count
|
from django.db.models import Sum, Count
|
||||||
|
from django.template.defaultfilters import pluralize
|
||||||
|
from django.utils import timezone
|
||||||
|
from django import forms
|
||||||
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
||||||
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
||||||
from django import forms
|
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
@ -211,9 +212,15 @@ class SalleAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
class SpectacleReventeAdmin(admin.ModelAdmin):
|
class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Administration des reventes de spectacles
|
||||||
|
"""
|
||||||
model = SpectacleRevente
|
model = SpectacleRevente
|
||||||
|
|
||||||
def spectacle(self, obj):
|
def spectacle(self, obj):
|
||||||
|
"""
|
||||||
|
Raccourci vers le spectacle associé à la revente.
|
||||||
|
"""
|
||||||
return obj.attribution.spectacle
|
return obj.attribution.spectacle
|
||||||
|
|
||||||
list_display = ("spectacle", "seller", "date", "soldTo")
|
list_display = ("spectacle", "seller", "date", "soldTo")
|
||||||
|
@ -224,6 +231,48 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||||
'seller__user__first_name',
|
'seller__user__first_name',
|
||||||
'seller__user__last_name']
|
'seller__user__last_name']
|
||||||
|
|
||||||
|
actions = ['transfer', 'reinit']
|
||||||
|
actions_on_bottom = True
|
||||||
|
|
||||||
|
def transfer(self, request, queryset):
|
||||||
|
"""
|
||||||
|
Effectue le transfert des reventes pour lesquels on connaît l'acheteur.
|
||||||
|
"""
|
||||||
|
reventes = queryset.exclude(soldTo__isnull=True).all()
|
||||||
|
count = reventes.count()
|
||||||
|
for revente in reventes:
|
||||||
|
attrib = revente.attribution
|
||||||
|
attrib.participant = revente.soldTo
|
||||||
|
attrib.save()
|
||||||
|
self.message_user(
|
||||||
|
request,
|
||||||
|
"%d attribution%s %s été transférée%s avec succès." % (
|
||||||
|
count, pluralize(count),
|
||||||
|
pluralize(count, "a,ont"), pluralize(count))
|
||||||
|
)
|
||||||
|
transfer.short_description = "Transférer les reventes sélectionnées"
|
||||||
|
|
||||||
|
def reinit(self, request, queryset):
|
||||||
|
"""
|
||||||
|
Réinitialise les reventes.
|
||||||
|
"""
|
||||||
|
count = queryset.count()
|
||||||
|
for revente in queryset.all():
|
||||||
|
revente.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(
|
||||||
|
request,
|
||||||
|
"%d attribution%s %s été réinitialisée%s avec succès." % (
|
||||||
|
count, pluralize(count),
|
||||||
|
pluralize(count, "a,ont"), pluralize(count))
|
||||||
|
)
|
||||||
|
reinit.short_description = "Réinitialiser les reventes sélectionnées"
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(CategorieSpectacle)
|
admin.site.register(CategorieSpectacle)
|
||||||
admin.site.register(Spectacle, SpectacleAdmin)
|
admin.site.register(Spectacle, SpectacleAdmin)
|
||||||
|
|
|
@ -320,33 +320,51 @@ class SpectacleRevente(models.Model):
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def tirage(self):
|
def tirage(self):
|
||||||
inscrits = self.answered_mail
|
inscrits = list(self.answered_mail.all())
|
||||||
spectacle = self.attribution.spectacle
|
spectacle = self.attribution.spectacle
|
||||||
seller = self.seller
|
seller = self.seller
|
||||||
if inscrits.exists():
|
|
||||||
winner = random.choice(inscrits.all())
|
if inscrits:
|
||||||
|
mails = []
|
||||||
|
mail_subject = "BdA-Revente : {:s}".format(spectacle.title)
|
||||||
|
|
||||||
|
# Envoie un mail au gagnant et au vendeur
|
||||||
|
winner = random.choice(inscrits)
|
||||||
self.soldTo = winner
|
self.soldTo = winner
|
||||||
mail_buyer = """Bonjour,
|
context = {
|
||||||
|
'acheteur': winner.user,
|
||||||
|
'vendeur': seller.user,
|
||||||
|
'spectacle': spectacle,
|
||||||
|
}
|
||||||
|
mails.append(mail.EmailMessage(
|
||||||
|
mail_subject, loader.render_to_string('mail-revente-winner.txt', context),
|
||||||
|
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||||
|
to=[winner.user.email],
|
||||||
|
reply_to=[seller.user.email],
|
||||||
|
))
|
||||||
|
mails.append(mail.EmailMessage(
|
||||||
|
mail_subject, loader.render_to_string('mail-revente-seller.txt', context),
|
||||||
|
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||||
|
to=[seller.user.email],
|
||||||
|
reply_to=[winner.user.email],
|
||||||
|
))
|
||||||
|
|
||||||
Tu as été tiré-e au sort pour racheter une place pour %s le %s (%s) à %0.02f€.
|
# Envoie un mail aux perdants
|
||||||
Tu peux contacter le/la vendeur-se à l'adresse %s.
|
for inscrit in inscrits:
|
||||||
|
if inscrit == winner:
|
||||||
|
continue
|
||||||
|
|
||||||
Chaleureusement,
|
mail_body = loader.render_to_string('mail-revente-loser.txt', {
|
||||||
Le BdA""" % (spectacle.title, spectacle.date_no_seconds(),
|
'acheteur': inscrit.user,
|
||||||
spectacle.location, spectacle.price, seller.user.email)
|
'vendeur': seller.user,
|
||||||
|
'spectacle': spectacle,
|
||||||
mail.send_mail("BdA-Revente : %s" % spectacle.title,
|
})
|
||||||
mail_buyer, "bda@ens.fr", [winner.user.email],
|
mails.append(mail.EmailMessage(
|
||||||
fail_silently=False)
|
mail_subject, mail_body,
|
||||||
mail_seller = """Bonjour,
|
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||||
La personne tirée au sort pour racheter ta place pour %s est %s.
|
to=[inscrit.user.email],
|
||||||
Tu peux le/la contacter à l'adresse %s.
|
reply_to=[settings.MAIL_DATA['revente']['REPLYTO']],
|
||||||
|
))
|
||||||
Chaleureusement,
|
mail.get_connection().send_messages(mails)
|
||||||
Le BdA""" % (spectacle.title, winner.user.get_full_name(), winner.user.email)
|
|
||||||
|
|
||||||
mail.send_mail("BdA-Revente : %s" % spectacle.title,
|
|
||||||
mail_seller, "bda@ens.fr", [seller.user.email],
|
|
||||||
fail_silently=False)
|
|
||||||
self.tirage_done = True
|
self.tirage_done = True
|
||||||
self.save()
|
self.save()
|
||||||
|
|
|
@ -62,9 +62,9 @@
|
||||||
<td>{{attrib.spectacle}}</td>
|
<td>{{attrib.spectacle}}</td>
|
||||||
<td>{{attrib.revente.soldTo.user.get_full_name}}</td>
|
<td>{{attrib.revente.soldTo.user.get_full_name}}</td>
|
||||||
<td><button type="submit" class="btn btn-primary" name="transfer"
|
<td><button type="submit" class="btn btn-primary" name="transfer"
|
||||||
value={{attrib.revente.id}}>Transférer</button></td>
|
value="{{attrib.revente.id}}">Transférer</button></td>
|
||||||
<td><button type="submit" class="btn btn-primary" name="reinit"
|
<td><button type="submit" class="btn btn-primary" name="reinit"
|
||||||
value={{attrib.revente.id}}>Réinitialiser</button></td>
|
value="{{attrib.revente.id}}">Réinitialiser</button></td>
|
||||||
</form>
|
</form>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
9
bda/templates/mail-revente-loser.txt
Normal file
9
bda/templates/mail-revente-loser.txt
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
Bonjour {{ acheteur.first_name }},
|
||||||
|
|
||||||
|
Tu t'étais inscrit-e pour la revente de la place de {{ vendeur.get_full_name }}
|
||||||
|
pour {{ spectacle.title }}.
|
||||||
|
Malheureusement, une autre personne a été tirée au sort pour racheter la place.
|
||||||
|
Tu pourras certainement retenter ta chance pour une autre revente !
|
||||||
|
|
||||||
|
À très bientôt,
|
||||||
|
Le Bureau des Arts
|
13
bda/templates/mail-revente-new.txt
Normal file
13
bda/templates/mail-revente-new.txt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Bonjour {{ vendeur.first_name }},
|
||||||
|
|
||||||
|
Tu t’es bien inscrit-e pour la revente de {{ spectacle.title }}.
|
||||||
|
|
||||||
|
{% with revente.expiration_time as time %}
|
||||||
|
Le tirage au sort entre tout-e-s les racheteuse-eur-s potentiel-le-s aura lieu
|
||||||
|
le {{ time|date:"DATE_FORMAT" }} à {{ time|time:"TIME_FORMAT" }} (dans {{time|timeuntil }}).
|
||||||
|
Si personne ne s’est inscrit pour racheter la place, celle-ci apparaitra parmi
|
||||||
|
les « Places disponibles immédiatement à la revente » sur GestioCOF.
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
Bonne revente !
|
||||||
|
Le Bureau des Arts
|
7
bda/templates/mail-revente-seller.txt
Normal file
7
bda/templates/mail-revente-seller.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Bonjour {{ vendeur.first_name }},
|
||||||
|
|
||||||
|
La personne tirée au sort pour racheter ta place pour {{ spectacle.title }} est {{ acheteur.get_full_name }}.
|
||||||
|
Tu peux le/la contacter à l'adresse {{ acheteur.email }}, ou en répondant à ce mail.
|
||||||
|
|
||||||
|
Chaleureusement,
|
||||||
|
Le BdA
|
7
bda/templates/mail-revente-winner.txt
Normal file
7
bda/templates/mail-revente-winner.txt
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Bonjour {{ acheteur.first_name }},
|
||||||
|
|
||||||
|
Tu as été tiré-e au sort pour racheter une place pour {{ spectacle.title }} le {{ spectacle.date_no_seconds }} ({{ spectacle.location }}) à {{ spectacle.price|floatformat:2 }}€.
|
||||||
|
Tu peux contacter le/la vendeur-se à l'adresse {{ vendeur.email }}, ou en répondant à ce mail.
|
||||||
|
|
||||||
|
Chaleureusement,
|
||||||
|
Le BdA
|
39
bda/views.py
39
bda/views.py
|
@ -8,15 +8,17 @@ import random
|
||||||
|
|
||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Count, Q
|
||||||
from django.core import serializers
|
from django.core import serializers, mail
|
||||||
from django.forms.models import inlineformset_factory
|
from django.forms.models import inlineformset_factory
|
||||||
from django.http import HttpResponseBadRequest, HttpResponseRedirect
|
from django.http import HttpResponseBadRequest, HttpResponseRedirect
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.conf import settings
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
|
from django.template import loader
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.views.generic.list import ListView
|
from django.views.generic.list import ListView
|
||||||
|
|
||||||
|
@ -292,15 +294,30 @@ def revente(request, tirage_id):
|
||||||
resellform = ResellForm(participant, request.POST, prefix='resell')
|
resellform = ResellForm(participant, request.POST, prefix='resell')
|
||||||
annulform = AnnulForm(participant, prefix='annul')
|
annulform = AnnulForm(participant, prefix='annul')
|
||||||
if resellform.is_valid():
|
if resellform.is_valid():
|
||||||
|
mails = []
|
||||||
attributions = resellform.cleaned_data["attributions"]
|
attributions = resellform.cleaned_data["attributions"]
|
||||||
for attribution in attributions:
|
with transaction.atomic():
|
||||||
revente, created = SpectacleRevente.objects.get_or_create(
|
for attribution in attributions:
|
||||||
attribution=attribution,
|
revente, created = SpectacleRevente.objects.get_or_create(
|
||||||
defaults={'seller': participant})
|
attribution=attribution,
|
||||||
if not created:
|
defaults={'seller': participant})
|
||||||
revente.seller = participant
|
if not created:
|
||||||
revente.date = timezone.now()
|
revente.seller = participant
|
||||||
revente.save()
|
revente.date = timezone.now()
|
||||||
|
mail_subject = "BdA-Revente : {:s}".format(attribution.spectacle.title)
|
||||||
|
mail_body = loader.render_to_string('mail-revente-new.txt', {
|
||||||
|
'vendeur': participant.user,
|
||||||
|
'spectacle': attribution.spectacle,
|
||||||
|
'revente': revente,
|
||||||
|
})
|
||||||
|
mails.append(mail.EmailMessage(
|
||||||
|
mail_subject, mail_body,
|
||||||
|
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||||
|
to=[participant.user.email],
|
||||||
|
reply_to=[settings.MAIL_DATA['revente']['REPLYTO']],
|
||||||
|
))
|
||||||
|
revente.save()
|
||||||
|
mail.get_connection().send_messages(mails)
|
||||||
|
|
||||||
elif 'annul' in request.POST:
|
elif 'annul' in request.POST:
|
||||||
annulform = AnnulForm(participant, request.POST, prefix='annul')
|
annulform = AnnulForm(participant, request.POST, prefix='annul')
|
||||||
|
@ -334,6 +351,8 @@ def revente(request, tirage_id):
|
||||||
if revente.attribution.spectacle.date > timezone.now():
|
if revente.attribution.spectacle.date > timezone.now():
|
||||||
revente.date = timezone.now() - timedelta(hours=1)
|
revente.date = timezone.now() - timedelta(hours=1)
|
||||||
revente.soldTo = None
|
revente.soldTo = None
|
||||||
|
revente.notif_sent = False
|
||||||
|
revente.tirage_done = False
|
||||||
if revente.answered_mail:
|
if revente.answered_mail:
|
||||||
revente.answered_mail.clear()
|
revente.answered_mail.clear()
|
||||||
revente.save()
|
revente.save()
|
||||||
|
|
|
@ -29,9 +29,6 @@ SECRET_KEY = 'q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah'
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = ['127.0.0.1']
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
'gestioncof',
|
'gestioncof',
|
||||||
|
@ -126,7 +123,7 @@ STATIC_URL = '/static/'
|
||||||
STATIC_ROOT = '/var/www/static/'
|
STATIC_ROOT = '/var/www/static/'
|
||||||
|
|
||||||
STATICFILES_DIRS = (
|
STATICFILES_DIRS = (
|
||||||
os.path.join(BASE_DIR, 'static/'),
|
os.path.join(BASE_DIR, 'static/'),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Media upload (through ImageField, SiteField)
|
# Media upload (through ImageField, SiteField)
|
||||||
|
|
|
@ -29,6 +29,12 @@ class COFCASBackend(CASBackend):
|
||||||
request.session['attributes'] = attributes
|
request.session['attributes'] = attributes
|
||||||
if not username:
|
if not username:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Le CAS de l'ENS accepte les logins avec des espaces au début
|
||||||
|
# et à la fin, ainsi qu’avec une casse variable. On normalise pour
|
||||||
|
# éviter les doublons.
|
||||||
|
username = username.strip().lower()
|
||||||
|
|
||||||
profiles = CofProfile.objects.filter(login_clipper=username)
|
profiles = CofProfile.objects.filter(login_clipper=username)
|
||||||
if len(profiles) > 0:
|
if len(profiles) > 0:
|
||||||
profile = profiles.order_by('-is_cof')[0]
|
profile = profiles.order_by('-is_cof')[0]
|
||||||
|
|
|
@ -14,6 +14,7 @@ from django.http import Http404, HttpResponse, HttpResponseForbidden
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.views import login as django_login_view
|
from django.contrib.auth.views import login as django_login_view
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
import django.utils.six as six
|
import django.utils.six as six
|
||||||
|
|
||||||
|
@ -710,12 +711,15 @@ def calendar_ics(request, token):
|
||||||
tirage__active=True)
|
tirage__active=True)
|
||||||
shows = shows.distinct()
|
shows = shows.distinct()
|
||||||
vcal = Calendar()
|
vcal = Calendar()
|
||||||
|
site = Site.objects.get_current()
|
||||||
for show in shows:
|
for show in shows:
|
||||||
vevent = Vevent()
|
vevent = Vevent()
|
||||||
vevent.add('dtstart', show.date)
|
vevent.add('dtstart', show.date)
|
||||||
vevent.add('dtend', show.date + timedelta(seconds=7200))
|
vevent.add('dtend', show.date + timedelta(seconds=7200))
|
||||||
vevent.add('summary', show.title)
|
vevent.add('summary', show.title)
|
||||||
vevent.add('location', show.location.name)
|
vevent.add('location', show.location.name)
|
||||||
|
vevent.add('uid', 'show-{:d}-{:d}@{:s}'.format(
|
||||||
|
show.pk, show.tirage_id, site.domain))
|
||||||
vcal.add_component(vevent)
|
vcal.add_component(vevent)
|
||||||
if subscription.subscribe_to_events:
|
if subscription.subscribe_to_events:
|
||||||
for event in Event.objects.filter(old=False).all():
|
for event in Event.objects.filter(old=False).all():
|
||||||
|
@ -725,6 +729,8 @@ def calendar_ics(request, token):
|
||||||
vevent.add('summary', event.title)
|
vevent.add('summary', event.title)
|
||||||
vevent.add('location', event.location)
|
vevent.add('location', event.location)
|
||||||
vevent.add('description', event.description)
|
vevent.add('description', event.description)
|
||||||
|
vevent.add('uid', 'event-{:d}@{:s}'.format(
|
||||||
|
event.pk, site.domain))
|
||||||
vcal.add_component(vevent)
|
vcal.add_component(vevent)
|
||||||
response = HttpResponse(content=vcal.to_ical())
|
response = HttpResponse(content=vcal.to_ical())
|
||||||
response['Content-Type'] = "text/calendar"
|
response['Content-Type'] = "text/calendar"
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
configparser==3.5.0
|
configparser==3.5.0
|
||||||
Django==1.8
|
Django==1.8.*
|
||||||
django-autocomplete-light==2.3.3
|
django-autocomplete-light==2.3.3
|
||||||
django-autoslug==1.9.3
|
django-autoslug==1.9.3
|
||||||
git+https://github.com/xapantu/django-cas-ng.git#egg=django-cas-ng
|
django-cas-ng==3.5.5
|
||||||
django-grappelli==2.8.1
|
django-grappelli==2.8.1
|
||||||
django-recaptcha==1.0.5
|
django-recaptcha==1.0.5
|
||||||
mysqlclient==1.3.7
|
mysqlclient==1.3.7
|
||||||
|
@ -15,7 +15,7 @@ django-bootstrap-form==3.2.1
|
||||||
asgiref==0.14.0
|
asgiref==0.14.0
|
||||||
daphne==0.14.3
|
daphne==0.14.3
|
||||||
asgi-redis==0.14.0
|
asgi-redis==0.14.0
|
||||||
-e git+https://github.com/Aureplop/channels.git#egg=channel
|
git+https://github.com/Aureplop/channels.git#egg=channel
|
||||||
statistics==1.0.3.5
|
statistics==1.0.3.5
|
||||||
future==0.15.2
|
future==0.15.2
|
||||||
django-widget-tweaks==1.4.1
|
django-widget-tweaks==1.4.1
|
||||||
|
|
Loading…
Reference in a new issue