Merge branch 'k-fet' of git.eleves.ens.fr:cof-geek/gestioCOF into Aufinal/arrondi_ukf
This commit is contained in:
commit
a2dc92d799
52 changed files with 1109 additions and 211 deletions
40
.gitlab-ci.yml
Normal file
40
.gitlab-ci.yml
Normal file
|
@ -0,0 +1,40 @@
|
|||
services:
|
||||
- mysql:latest
|
||||
- redis:latest
|
||||
|
||||
variables:
|
||||
# GestioCOF settings
|
||||
DJANGO_SETTINGS_MODULE: "cof.settings_dev"
|
||||
DBNAME: "cof_gestion"
|
||||
DBUSER: "cof_gestion"
|
||||
DBPASSWD: "cof_password"
|
||||
DBHOST: "mysql"
|
||||
REDIS_HOST: "redis"
|
||||
|
||||
# Cached packages
|
||||
PYTHONPATH: "$CI_PROJECT_DIR/vendor/python"
|
||||
|
||||
# mysql service configuration
|
||||
MYSQL_DATABASE: "$DBNAME"
|
||||
MYSQL_USER: "$DBUSER"
|
||||
MYSQL_PASSWORD: "$DBPASSWD"
|
||||
MYSQL_ROOT_PASSWORD: "root_password"
|
||||
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- vendor/python
|
||||
- vendor/pip
|
||||
- vendor/apt
|
||||
|
||||
before_script:
|
||||
- mkdir -p vendor/{python,pip,apt}
|
||||
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq mysql-client
|
||||
- mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DBHOST"
|
||||
-e "GRANT ALL ON test_$DBNAME.* TO '$DBUSER'@'%'"
|
||||
- pip install --cache-dir vendor/pip -t vendor/python -r requirements-devel.txt
|
||||
|
||||
test:
|
||||
stage: test
|
||||
script:
|
||||
- python manage.py test
|
2
Vagrantfile
vendored
2
Vagrantfile
vendored
|
@ -10,7 +10,7 @@ Vagrant.configure(2) do |config|
|
|||
# For a complete reference, please see the online documentation at
|
||||
# https://docs.vagrantup.com.
|
||||
|
||||
config.vm.box = "ubuntu/trusty64"
|
||||
config.vm.box = "ubuntu/xenial64"
|
||||
|
||||
# On associe le port 80 dans la machine virtuelle avec le port 8080 de notre
|
||||
# ordinateur, et le port 8000 avec le port 8000.
|
||||
|
|
72
bda/admin.py
72
bda/admin.py
|
@ -5,12 +5,13 @@ from __future__ import print_function
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.core.mail import send_mail
|
||||
|
||||
from django.contrib import admin
|
||||
from django.db.models import Sum, Count
|
||||
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
||||
Attribution, Tirage, Quote, CategorieSpectacle
|
||||
from django.template.defaultfilters import pluralize
|
||||
from django.utils import timezone
|
||||
from django import forms
|
||||
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
||||
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
|
@ -210,6 +211,70 @@ class SalleAdmin(admin.ModelAdmin):
|
|||
search_fields = ('name', 'address')
|
||||
|
||||
|
||||
class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
Administration des reventes de spectacles
|
||||
"""
|
||||
model = SpectacleRevente
|
||||
|
||||
def spectacle(self, obj):
|
||||
"""
|
||||
Raccourci vers le spectacle associé à la revente.
|
||||
"""
|
||||
return obj.attribution.spectacle
|
||||
|
||||
list_display = ("spectacle", "seller", "date", "soldTo")
|
||||
raw_id_fields = ("attribution",)
|
||||
readonly_fields = ("shotgun", "expiration_time")
|
||||
search_fields = ['attribution__spectacle__title',
|
||||
'seller__user__username',
|
||||
'seller__user__first_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.filter(
|
||||
attribution__spectacle__date__gte=timezone.now()):
|
||||
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(Spectacle, SpectacleAdmin)
|
||||
admin.site.register(Salle, SalleAdmin)
|
||||
|
@ -217,3 +282,4 @@ admin.site.register(Participant, ParticipantAdmin)
|
|||
admin.site.register(Attribution, AttributionAdmin)
|
||||
admin.site.register(ChoixSpectacle, ChoixSpectacleAdmin)
|
||||
admin.site.register(Tirage, TirageAdmin)
|
||||
admin.site.register(SpectacleRevente, SpectacleReventeAdmin)
|
||||
|
|
50
bda/forms.py
50
bda/forms.py
|
@ -4,9 +4,13 @@ from __future__ import division
|
|||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from django import forms
|
||||
from django.forms.models import BaseInlineFormSet
|
||||
from bda.models import Spectacle
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
from bda.models import Attribution, Spectacle
|
||||
|
||||
|
||||
class BaseBdaFormSet(BaseInlineFormSet):
|
||||
|
@ -35,17 +39,47 @@ class TokenForm(forms.Form):
|
|||
token = forms.CharField(widget=forms.widgets.Textarea())
|
||||
|
||||
|
||||
class SpectacleModelChoiceField(forms.ModelChoiceField):
|
||||
class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
||||
def label_from_instance(self, obj):
|
||||
return "%s le %s (%s) à %.02f€" % (obj.title, obj.date_no_seconds(),
|
||||
obj.location, obj.price)
|
||||
return "%s" % obj.spectacle
|
||||
|
||||
|
||||
class ResellForm(forms.Form):
|
||||
count = forms.ChoiceField(choices=(("1", "1"), ("2", "2"),))
|
||||
spectacle = SpectacleModelChoiceField(queryset=Spectacle.objects.none())
|
||||
attributions = AttributionModelMultipleChoiceField(
|
||||
queryset=Attribution.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
|
||||
def __init__(self, participant, *args, **kwargs):
|
||||
super(ResellForm, self).__init__(*args, **kwargs)
|
||||
self.fields['spectacle'].queryset = participant.attributions.all() \
|
||||
.distinct()
|
||||
self.fields['attributions'].queryset = participant.attribution_set\
|
||||
.filter(spectacle__date__gte=timezone.now())\
|
||||
.exclude(revente__seller=participant)
|
||||
|
||||
|
||||
class AnnulForm(forms.Form):
|
||||
attributions = AttributionModelMultipleChoiceField(
|
||||
queryset=Attribution.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
|
||||
def __init__(self, participant, *args, **kwargs):
|
||||
super(AnnulForm, self).__init__(*args, **kwargs)
|
||||
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))
|
||||
|
||||
|
||||
class InscriptionReventeForm(forms.Form):
|
||||
spectacles = forms.ModelMultipleChoiceField(
|
||||
queryset=Spectacle.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
|
||||
def __init__(self, tirage, *args, **kwargs):
|
||||
super(InscriptionReventeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['spectacles'].queryset = tirage.spectacle_set.filter(
|
||||
date__gte=timezone.now())
|
||||
|
|
43
bda/management/commands/manage_reventes.py
Normal file
43
bda/management/commands/manage_reventes.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Gestion en ligne de commande des reventes.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import timedelta
|
||||
from django.core.management import BaseCommand
|
||||
from django.utils import timezone
|
||||
from bda.models import SpectacleRevente
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Envoie les mails de notification et effectue " \
|
||||
"les tirages au sort des reventes"
|
||||
leave_locale_alone = True
|
||||
|
||||
def handle(self, *args, **options):
|
||||
now = timezone.now()
|
||||
reventes = SpectacleRevente.objects.all()
|
||||
for revente in reventes:
|
||||
# Check si < 24h
|
||||
if (revente.attribution.spectacle.date <=
|
||||
revente.date + timedelta(days=1)) and \
|
||||
now >= revente.date + timedelta(minutes=15) and \
|
||||
not revente.notif_sent:
|
||||
self.stdout.write(str(now))
|
||||
revente.mail_shotgun()
|
||||
self.stdout.write("Mail de disponibilité immédiate envoyé")
|
||||
# Check si délai de retrait dépassé
|
||||
elif (now >= revente.date + timedelta(hours=1) and
|
||||
not revente.notif_sent):
|
||||
self.stdout.write(str(now))
|
||||
revente.send_notif()
|
||||
self.stdout.write("Mail d'inscription à une revente envoyé")
|
||||
# Check si tirage à faire
|
||||
elif (now >= revente.expiration_time and
|
||||
not revente.tirage_done):
|
||||
self.stdout.write(str(now))
|
||||
revente.tirage()
|
||||
self.stdout.write("Tirage effectué, mails envoyés")
|
|
@ -1,16 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Gestion en ligne de commande des mails de rappel.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import timedelta
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
from bda.models import Spectacle
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Envoie les mails de rappel des spectacles dont la date ' \
|
||||
'approche.\nNe renvoie pas les mails déjà envoyés.'
|
||||
leave_locale_alone = True
|
||||
|
||||
def handle(self, *args, **options):
|
||||
now = timezone.now()
|
||||
|
|
66
bda/migrations/0009_revente.py
Normal file
66
bda/migrations/0009_revente.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0008_py3'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SpectacleRevente',
|
||||
fields=[
|
||||
('id', models.AutoField(serialize=False, primary_key=True,
|
||||
auto_created=True, verbose_name='ID')),
|
||||
('date', models.DateTimeField(
|
||||
verbose_name='Date de mise en vente',
|
||||
default=django.utils.timezone.now)),
|
||||
('notif_sent', models.BooleanField(
|
||||
verbose_name='Notification envoyée', default=False)),
|
||||
('tirage_done', models.BooleanField(
|
||||
verbose_name='Tirage effectué', default=False)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Revente',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='participant',
|
||||
name='choicesrevente',
|
||||
field=models.ManyToManyField(to='bda.Spectacle',
|
||||
related_name='subscribed',
|
||||
blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='answered_mail',
|
||||
field=models.ManyToManyField(to='bda.Participant',
|
||||
related_name='wanted',
|
||||
blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='attribution',
|
||||
field=models.OneToOneField(to='bda.Attribution',
|
||||
related_name='revente'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='seller',
|
||||
field=models.ForeignKey(to='bda.Participant',
|
||||
verbose_name='Vendeur',
|
||||
related_name='original_shows'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='soldTo',
|
||||
field=models.ForeignKey(to='bda.Participant',
|
||||
verbose_name='Vendue à', null=True,
|
||||
blank=True),
|
||||
),
|
||||
]
|
231
bda/models.py
231
bda/models.py
|
@ -5,22 +5,19 @@ from __future__ import print_function
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import calendar
|
||||
import random
|
||||
from datetime import timedelta
|
||||
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.template import loader, Context
|
||||
from django.template import loader
|
||||
from django.core import mail
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.utils import timezone, formats
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
|
||||
|
||||
def render_template(template_name, data):
|
||||
tmpl = loader.get_template(template_name)
|
||||
ctxt = Context(data)
|
||||
return tmpl.render(ctxt)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Tirage(models.Model):
|
||||
title = models.CharField("Titre", max_length=300)
|
||||
|
@ -31,12 +28,9 @@ class Tirage(models.Model):
|
|||
enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
|
||||
default=False)
|
||||
|
||||
def date_no_seconds(self):
|
||||
return self.fermeture.astimezone(timezone.get_current_timezone()) \
|
||||
.strftime('%d %b %Y %H:%M')
|
||||
|
||||
def __str__(self):
|
||||
return "%s - %s" % (self.title, self.date_no_seconds())
|
||||
return "%s - %s" % (self.title, formats.localize(
|
||||
timezone.template_localtime(self.fermeture)))
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
|
@ -83,42 +77,46 @@ class Spectacle(models.Model):
|
|||
verbose_name = "Spectacle"
|
||||
ordering = ("date", "title",)
|
||||
|
||||
def __repr__(self):
|
||||
return "[%s]" % self
|
||||
|
||||
def timestamp(self):
|
||||
return "%d" % calendar.timegm(self.date.utctimetuple())
|
||||
|
||||
def date_no_seconds(self):
|
||||
return self.date.astimezone(timezone.get_current_timezone()) \
|
||||
.strftime('%d %b %Y %H:%M')
|
||||
|
||||
def __str__(self):
|
||||
return "%s - %s, %s, %.02f€" % (self.title, self.date_no_seconds(),
|
||||
self.location, self.price)
|
||||
return "%s - %s, %s, %.02f€" % (
|
||||
self.title,
|
||||
formats.localize(timezone.template_localtime(self.date)),
|
||||
self.location,
|
||||
self.price
|
||||
)
|
||||
|
||||
def send_rappel(self):
|
||||
"""
|
||||
Envoie un mail de rappel à toutes les personnes qui ont une place pour
|
||||
ce spectacle.
|
||||
"""
|
||||
# On récupère la liste des participants
|
||||
members = {}
|
||||
for attr in Attribution.objects.filter(spectacle=self).all():
|
||||
member = attr.participant.user
|
||||
if member.id in members:
|
||||
members[member.id].nb_attr = 2
|
||||
members[member.id][1] = 2
|
||||
else:
|
||||
member.nb_attr = 1
|
||||
members[member.id] = member
|
||||
members[member.id] = [member.first_name, 1, member.email]
|
||||
# Pour le BdA
|
||||
members[0] = ['BdA', 1, 'bda@ens.fr']
|
||||
members[-1] = ['BdA', 2, 'bda@ens.fr']
|
||||
# On écrit un mail personnalisé à chaque participant
|
||||
mails_to_send = []
|
||||
mail_object = "%s - %s - %s" % (self.title, self.date_no_seconds(),
|
||||
self.location)
|
||||
mail_object = str(self)
|
||||
for member in members.values():
|
||||
mail_body = render_template('mail-rappel.txt', {
|
||||
'member': member,
|
||||
mail_body = loader.render_to_string('bda/mails/rappel.txt', {
|
||||
'name': member[0],
|
||||
'nb_attr': member[1],
|
||||
'show': self})
|
||||
mail_tot = mail.EmailMessage(
|
||||
mail_object, mail_body,
|
||||
settings.RAPPEL_FROM, [member.email],
|
||||
[], headers={'Reply-To': settings.RAPPEL_REPLY_TO})
|
||||
settings.MAIL_DATA['rappels']['FROM'], [member[2]],
|
||||
[], headers={
|
||||
'Reply-To': settings.MAIL_DATA['rappels']['REPLYTO']})
|
||||
mails_to_send.append(mail_tot)
|
||||
# On envoie les mails
|
||||
connection = mail.get_connection()
|
||||
|
@ -158,6 +156,9 @@ class Participant(models.Model):
|
|||
max_length=6, choices=PAYMENT_TYPES,
|
||||
blank=True)
|
||||
tirage = models.ForeignKey(Tirage)
|
||||
choicesrevente = models.ManyToManyField(Spectacle,
|
||||
related_name="subscribed",
|
||||
blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return "%s - %s" % (self.user, self.tirage.title)
|
||||
|
@ -205,4 +206,170 @@ class Attribution(models.Model):
|
|||
given = models.BooleanField("Donnée", default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "%s -- %s" % (self.participant, self.spectacle)
|
||||
return "%s -- %s, %s" % (self.participant.user, self.spectacle.title,
|
||||
self.spectacle.date)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SpectacleRevente(models.Model):
|
||||
attribution = models.OneToOneField(Attribution,
|
||||
related_name="revente")
|
||||
date = models.DateTimeField("Date de mise en vente",
|
||||
default=timezone.now)
|
||||
answered_mail = models.ManyToManyField(Participant,
|
||||
related_name="wanted",
|
||||
blank=True)
|
||||
seller = models.ForeignKey(Participant,
|
||||
related_name="original_shows",
|
||||
verbose_name="Vendeur")
|
||||
soldTo = models.ForeignKey(Participant, blank=True, null=True,
|
||||
verbose_name="Vendue à")
|
||||
|
||||
notif_sent = models.BooleanField("Notification envoyée",
|
||||
default=False)
|
||||
tirage_done = models.BooleanField("Tirage effectué",
|
||||
default=False)
|
||||
|
||||
@property
|
||||
def expiration_time(self):
|
||||
# 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
|
||||
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)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Revente"
|
||||
|
||||
def send_notif(self):
|
||||
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
||||
|
||||
mails_to_send = []
|
||||
mail_object = "%s" % (self.attribution.spectacle)
|
||||
for participant in inscrits:
|
||||
mail_body = loader.render_to_string('bda/mails/revente.txt', {
|
||||
'user': participant.user,
|
||||
'spectacle': self.attribution.spectacle,
|
||||
'revente': self,
|
||||
'domain': Site.objects.get_current().domain})
|
||||
mail_tot = mail.EmailMessage(
|
||||
mail_object, mail_body,
|
||||
settings.MAIL_DATA['revente']['FROM'],
|
||||
[participant.user.email],
|
||||
[], headers={
|
||||
'Reply-To': settings.MAIL_DATA['revente']['REPLYTO']})
|
||||
mails_to_send.append(mail_tot)
|
||||
|
||||
connection = mail.get_connection()
|
||||
connection.send_messages(mails_to_send)
|
||||
self.notif_sent = True
|
||||
self.save()
|
||||
|
||||
def mail_shotgun(self):
|
||||
"""
|
||||
Envoie un mail à toutes les personnes intéréssées par le spectacle pour
|
||||
leur indiquer qu'il est désormais disponible au shotgun.
|
||||
"""
|
||||
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
||||
|
||||
mails_to_send = []
|
||||
mail_object = "%s" % (self.attribution.spectacle)
|
||||
for participant in inscrits:
|
||||
mail_body = loader.render_to_string('bda/mails/shotgun.txt', {
|
||||
'user': participant.user,
|
||||
'spectacle': self.attribution.spectacle,
|
||||
'domain': Site.objects.get_current(),
|
||||
'mail': self.attribution.participant.user.email})
|
||||
mail_tot = mail.EmailMessage(
|
||||
mail_object, mail_body,
|
||||
settings.MAIL_DATA['revente']['FROM'],
|
||||
[participant.user.email],
|
||||
[], headers={
|
||||
'Reply-To': settings.MAIL_DATA['revente']['REPLYTO']})
|
||||
mails_to_send.append(mail_tot)
|
||||
|
||||
connection = mail.get_connection()
|
||||
connection.send_messages(mails_to_send)
|
||||
self.notif_sent = True
|
||||
self.save()
|
||||
|
||||
def tirage(self):
|
||||
"""
|
||||
Lance le tirage au sort associé à la revente. Un gagnant est choisi
|
||||
parmis les personnes intéressées par le spectacle. Les personnes sont
|
||||
ensuites prévenues par mail du résultat du tirage.
|
||||
"""
|
||||
inscrits = list(self.answered_mail.all())
|
||||
spectacle = self.attribution.spectacle
|
||||
seller = self.seller
|
||||
|
||||
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
|
||||
context = {
|
||||
'acheteur': winner.user,
|
||||
'vendeur': seller.user,
|
||||
'spectacle': spectacle,
|
||||
}
|
||||
mails.append(mail.EmailMessage(
|
||||
mail_subject,
|
||||
loader.render_to_string('bda/mails/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('bda/mails/revente-seller.txt',
|
||||
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:
|
||||
continue
|
||||
|
||||
mail_body = loader.render_to_string(
|
||||
'bda/mails/revente-loser.txt',
|
||||
{'acheteur': inscrit.user,
|
||||
'vendeur': seller.user,
|
||||
'spectacle': spectacle}
|
||||
)
|
||||
mails.append(mail.EmailMessage(
|
||||
mail_subject, mail_body,
|
||||
from_email=settings.MAIL_DATA['revente']['FROM'],
|
||||
to=[inscrit.user.email],
|
||||
reply_to=[settings.MAIL_DATA['revente']['REPLYTO']],
|
||||
))
|
||||
mail.get_connection().send_messages(mails)
|
||||
self.tirage_done = True
|
||||
self.save()
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
{% for show, members, losers in results %}
|
||||
<div class="attribresult">
|
||||
<h3 class="horizontal-title">{{ show.title }} - {{ show.date_no_seconds }} @ {{ show.location }}</h3>
|
||||
<h3 class="horizontal-title">{{ show.title }} - {{ show.date }} @ {{ show.location }}</h3>
|
||||
<p>
|
||||
<strong>{{ show.nrequests }} demandes pour {{ show.slots }} places</strong>
|
||||
{{ show.price }}€ par place{% if user.profile.is_buro and show.nrequests < show.slots %}, {{ show.deficit }}€ de déficit{% endif %}
|
||||
|
|
9
bda/templates/bda-interested.html
Normal file
9
bda/templates/bda-interested.html
Normal file
|
@ -0,0 +1,9 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Inscription à une revente</h2>
|
||||
<p class="success"> Votre inscription pour a bien été enregistrée !</p>
|
||||
<p>Le tirage au sort pour cette revente ({{spectacle}}) sera effectué le {{date}}.
|
||||
|
||||
{% endblock %}
|
6
bda/templates/bda-no-revente.html
Normal file
6
bda/templates/bda-no-revente.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>BdA-Revente</h2>
|
||||
<p>Il n'y a plus de places en revente pour ce spectacle, désolé !</p>
|
||||
{% endblock %}
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2><strong>Nope</strong></h1>
|
||||
<h2><strong>Nope</strong></h2>
|
||||
<p>Avant de revendre des places, il faut aller les payer !</p>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,26 +1,73 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
|
||||
{% endblock %}
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block realcontent %}
|
||||
|
||||
<h2>Revente de place</h1>
|
||||
<form action="" method="post" id="resellform">
|
||||
<h2>Revente de place</h2>
|
||||
<h3>Places non revendues</h3>
|
||||
<form class="form-horizontal" action="" method="post">
|
||||
{% csrf_token %}
|
||||
{% if form.spectacle.errors %}<ul class="errorlist"><li>Sélectionnez un spetacle</li></ul>{% endif %}
|
||||
<p>
|
||||
<pre>
|
||||
Bonjour,<br />
|
||||
<br />
|
||||
Je souhaite revendre {{ form.count }} place(s) pour {{ form.spectacle }}.<br />
|
||||
Contactez-moi par email si vous êtes intéressé !<br />
|
||||
<br />
|
||||
{{ user.get_full_name }} ({{ user.email }})
|
||||
</pre>
|
||||
</p>
|
||||
<input class="btn btn-primary" type="submit" value="Envoyer" />
|
||||
<div class="form-group">
|
||||
<div class="multiple-checkbox">
|
||||
<ul>
|
||||
{% for box in resellform.attributions %}
|
||||
<li>
|
||||
{{box.tag}}
|
||||
{{box.choice_label}}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
|
||||
</div>
|
||||
</form>
|
||||
<br>
|
||||
{% if annulform.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 box in annulform.attributions %}
|
||||
<li>
|
||||
{{box.tag}}
|
||||
{{box.choice_label}}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% for attrib in overdue %}
|
||||
<li>
|
||||
<input type="checkbox" style="visibility:hidden">
|
||||
{{attrib.spectacle}}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% if annulform.attributions %}
|
||||
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endif %}
|
||||
<br>
|
||||
{% if sold %}
|
||||
<h3>Places revendues</h3>
|
||||
<table class="table">
|
||||
{% for attrib in sold %}
|
||||
<tr>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<td>{{attrib.spectacle}}</td>
|
||||
<td>{{attrib.revente.soldTo.user.get_full_name}}</td>
|
||||
<td><button type="submit" class="btn btn-primary" name="transfer"
|
||||
value="{{attrib.revente.id}}">Transférer</button></td>
|
||||
<td><button type="submit" class="btn btn-primary" name="reinit"
|
||||
value="{{attrib.revente.id}}">Réinitialiser</button></td>
|
||||
</form>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
14
bda/templates/bda-shotgun.html
Normal file
14
bda/templates/bda-shotgun.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Places disponibles immédiatement</h2>
|
||||
{% if shotgun %}
|
||||
<ul class="list-unstyled">
|
||||
{% for spectacle in shotgun %}
|
||||
<li><a href="{% url "bda-buy-revente" spectacle.id %}">{{spectacle}}</a></li>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p> Pas de places disponibles immédiatement, désolé !</p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,12 +1,8 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
|
||||
{% endblock %}
|
||||
|
||||
{% block realcontent %}
|
||||
|
||||
<h1>Revente de place</h1>
|
||||
<p class="success">Votre offre de revente de {{ places }} pour {{ show.title }} le {{ show.date_no_seconds }} ({{ show.location }}) à {{ show.price }}€ a bien été envoyée.</p>
|
||||
<h2>Revente de place</h2>
|
||||
<p class="success">Un mail a bien été envoyé à {{seller.get_full_name}} ({{seller.email}}), pour racheter une place pour {{spectacle.title}} !</p>
|
||||
{% endblock %}
|
||||
|
|
6
bda/templates/bda-wrongtime.html
Normal file
6
bda/templates/bda-wrongtime.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Nope</h2>
|
||||
<p>Cette revente n'est pas disponible actuellement, désolé !</p>
|
||||
{% endblock %}
|
6
bda/templates/bda/mails/buy-shotgun.txt
Normal file
6
bda/templates/bda/mails/buy-shotgun.txt
Normal file
|
@ -0,0 +1,6 @@
|
|||
Bonjour {{ vendeur.first_name }} !
|
||||
|
||||
Je souhaiterais racheter ta place pour {{ spectacle.title }} le {{ spectacle.date }} ({{ spectacle.location }}) à {{ spectacle.price|floatformat:2 }}€.
|
||||
Contacte-moi si tu es toujours intéressé·e !
|
||||
|
||||
{{ acheteur.get_full_name }} ({{ acheteur.email }})
|
|
@ -1,14 +1,14 @@
|
|||
Bonjour {{ member.get_full_name }},
|
||||
Bonjour {{ name }},
|
||||
|
||||
Nous te rappellons que tu as eu la chance d'obtenir {{ member.nb_attr|pluralize:"une place,deux places" }}
|
||||
pour {{ show.title }}, le {{ show.date_no_seconds }} au {{ show.location }}. N'oublie pas de t'y rendre !
|
||||
{% if member.nb_attr == 2 %}
|
||||
Nous te rappellons que tu as eu la chance d'obtenir {{ nb_attr|pluralize:"une place,deux places" }}
|
||||
pour {{ show.title }}, le {{ show.date }} au {{ show.location }}. N'oublie pas de t'y rendre !
|
||||
{% if nb_attr == 2 %}
|
||||
Tu as obtenu deux places pour ce spectacle. Nous te rappelons que
|
||||
ces places sont strictement réservées aux personnes de moins de 28 ans.
|
||||
{% endif %}
|
||||
{% if show.listing %}Pour ce spectacle, tu as reçu des places sur
|
||||
listing. Il te faudra donc te rendre 15 minutes en avance sur les lieux de la représentation
|
||||
pour retirer {{ member.nb_attr|pluralize:"ta place,tes places" }}.
|
||||
pour retirer {{ nb_attr|pluralize:"ta place,tes places" }}.
|
||||
{% else %}Pour assister à ce spectacle, tu dois présenter les billets qui ont
|
||||
été distribués au burô.
|
||||
{% endif %}
|
9
bda/templates/bda/mails/revente-loser.txt
Normal file
9
bda/templates/bda/mails/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/bda/mails/revente-new.txt
Normal file
13
bda/templates/bda/mails/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/bda/mails/revente-seller.txt
Normal file
7
bda/templates/bda/mails/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/bda/mails/revente-winner.txt
Normal file
7
bda/templates/bda/mails/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 }} ({{ 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
|
12
bda/templates/bda/mails/revente.txt
Normal file
12
bda/templates/bda/mails/revente.txt
Normal file
|
@ -0,0 +1,12 @@
|
|||
Bonjour {{ user.first_name }}
|
||||
|
||||
Une place pour le spectacle {{ spectacle.title }} ({{ spectacle.date }})
|
||||
a été postée sur BdA-Revente.
|
||||
|
||||
Si ce spectacle t'intéresse toujours, merci de nous le signaler en cliquant
|
||||
sur ce lien : http://{{ domain }}{% url "bda-revente-interested" revente.id %}.
|
||||
Dans le cas où plusieurs personnes seraient intéressées, nous procèderons à
|
||||
un tirage au sort le {{ revente.expiration_time_str }}.
|
||||
|
||||
Chaleureusement,
|
||||
Le BdA
|
11
bda/templates/bda/mails/shotgun.txt
Normal file
11
bda/templates/bda/mails/shotgun.txt
Normal file
|
@ -0,0 +1,11 @@
|
|||
Bonjour {{ user.first_name }}
|
||||
|
||||
Une place pour le spectacle {{ spectacle.title }} ({{ spectacle.date }})
|
||||
a été postée sur BdA-Revente.
|
||||
|
||||
Puisque ce spectacle a lieu dans moins de 24h, il n'y a pas de tirage au sort pour
|
||||
cette place : elle est disponible immédiatement à l'adresse
|
||||
http://{{ domain }}{% url "bda-buy-revente" spectacle.id %}, à la disposition de tous.
|
||||
|
||||
Chaleureusement,
|
||||
Le BdA
|
|
@ -18,7 +18,7 @@
|
|||
{% for spectacle in spectacles %}
|
||||
<tr>
|
||||
<td>{{ spectacle.title }}</td>
|
||||
<td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date_no_seconds }}</td>
|
||||
<td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date }}</td>
|
||||
<td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td>
|
||||
<td data-sort-value="{{ spectacle.slots }}">{{ spectacle.slots }} places</td>
|
||||
<td data-sort-value="{{ spectacle.total }}">{{ spectacle.total }} demandes</td>
|
||||
|
|
39
bda/templates/liste-reventes.html
Normal file
39
bda/templates/liste-reventes.html
Normal file
|
@ -0,0 +1,39 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Inscriptions pour BdA-Revente</h2>
|
||||
{% if success %}
|
||||
<p class="success">Ton inscription a bien été prise en compte !</p>
|
||||
{% endif %}
|
||||
{% if deja_revente %}
|
||||
<p class="success">Des reventes existent déjà pour certains de ces spectacles ; vérifie les places disponibles sans tirage !</p>
|
||||
{% endif %}
|
||||
<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 %}
|
|
@ -11,7 +11,7 @@
|
|||
<tr>
|
||||
<td>{{place.spectacle.title}}</td>
|
||||
<td>{{place.spectacle.location}}</td>
|
||||
<td>{{place.spectacle.date_no_seconds}}</td>
|
||||
<td>{{place.spectacle.date}}</td>
|
||||
<td>{% if place.double %}deux places{%else%}une place{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
20
bda/templates/revente-confirm.html
Normal file
20
bda/templates/revente-confirm.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
|
||||
{%block realcontent %}
|
||||
<h2>Rachat d'une place</h2>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<pre>
|
||||
Bonjour !
|
||||
|
||||
Je souhaiterais racheter ta place pour {{spectacle.title}} le {{spectacle.date}} ({{spectacle.location}}) à {{spectacle.price}}€.
|
||||
Contacte-moi si tu es toujours intéressé-e !
|
||||
|
||||
{{user.get_full_name}} ({{user.email}})
|
||||
</pre>
|
||||
<input type="submit" class="btn btn-primary pull-right" value="Envoyer">
|
||||
</form>
|
||||
<p class="bda-prix">Note : ce mail sera envoyé à une personne au hasard revendant sa place.</p>
|
||||
{%endblock%}
|
|
@ -19,7 +19,7 @@
|
|||
{% for spectacle in object_list %}
|
||||
<tr class="clickable-row" data-href="{% url 'bda-spectacle' tirage_id spectacle.id %}">
|
||||
<td><a href="{% url 'bda-spectacle' tirage_id spectacle.id %}">{{ spectacle.title }} <span style="font-size:small;" class="glyphicon glyphicon-link" aria-hidden="true"></span></a></td>
|
||||
<td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date_no_seconds }}</td>
|
||||
<td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date }}</td>
|
||||
<td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td>
|
||||
<td data-sort-value="{{ spectacle.price |stringformat:".3f" }}">
|
||||
{{ spectacle.price |floatformat }}€
|
||||
|
|
12
bda/urls.py
12
bda/urls.py
|
@ -32,6 +32,18 @@ urlpatterns = [
|
|||
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
|
||||
views.unpaid,
|
||||
name="bda-unpaid"),
|
||||
url(r'^liste-revente/(?P<tirage_id>\d+)$',
|
||||
views.list_revente,
|
||||
name="bda-liste-revente"),
|
||||
url(r'^buy-revente/(?P<spectacle_id>\d+)$',
|
||||
views.buy_revente,
|
||||
name="bda-buy-revente"),
|
||||
url(r'^revente-interested/(?P<revente_id>\d+)$',
|
||||
views.revente_interested,
|
||||
name='bda-revente-interested'),
|
||||
url(r'^revente-immediat/(?P<tirage_id>\d+)$',
|
||||
views.revente_shotgun,
|
||||
name="bda-shotgun"),
|
||||
url(r'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel),
|
||||
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
||||
name='bda-descriptions'),
|
||||
|
|
271
bda/views.py
271
bda/views.py
|
@ -4,27 +4,34 @@ from __future__ import division
|
|||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import random
|
||||
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db import models
|
||||
from django.db.models import Count
|
||||
from django.core import serializers
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Count, Q
|
||||
from django.core import serializers, mail
|
||||
from django.forms.models import inlineformset_factory
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.http import HttpResponseBadRequest, HttpResponseRedirect
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
import hashlib
|
||||
|
||||
from django.core.mail import send_mail
|
||||
from django.template import loader
|
||||
from django.utils import timezone
|
||||
from django.views.generic.list import ListView
|
||||
|
||||
import time
|
||||
from datetime import timedelta
|
||||
|
||||
from gestioncof.decorators import cof_required, buro_required
|
||||
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
|
||||
Tirage, render_template
|
||||
Tirage, SpectacleRevente
|
||||
from bda.algorithm import Algorithm
|
||||
|
||||
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm
|
||||
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\
|
||||
InscriptionReventeForm
|
||||
|
||||
|
||||
@cof_required
|
||||
|
@ -231,6 +238,11 @@ def do_tirage(request, tirage_id):
|
|||
Attribution(spectacle=show, participant=member)
|
||||
for show, members, _ in results
|
||||
for member, _, _, _ in members])
|
||||
# On inscrit à BdA-Revente ceux qui n'ont pas eu les places voulues
|
||||
for (show, _, losers) in results:
|
||||
for (loser, _, _, _) in losers:
|
||||
loser.choicesrevente.add(show)
|
||||
loser.save()
|
||||
return render(request, "bda-attrib-extra.html", data)
|
||||
else:
|
||||
return render(request, "bda-attrib.html", data)
|
||||
|
@ -251,25 +263,6 @@ def tirage(request, tirage_id):
|
|||
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 = """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)
|
||||
|
@ -277,14 +270,226 @@ def revente(request, tirage_id):
|
|||
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)
|
||||
if request.method == 'POST':
|
||||
if 'resell' in request.POST:
|
||||
resellform = ResellForm(participant, request.POST, prefix='resell')
|
||||
annulform = AnnulForm(participant, prefix='annul')
|
||||
if resellform.is_valid():
|
||||
mails = []
|
||||
attributions = resellform.cleaned_data["attributions"]
|
||||
with transaction.atomic():
|
||||
for attribution in attributions:
|
||||
revente, created = SpectacleRevente.objects.get_or_create(
|
||||
attribution=attribution,
|
||||
defaults={'seller': participant})
|
||||
if not created:
|
||||
revente.seller = participant
|
||||
revente.date = timezone.now()
|
||||
mail_subject = "BdA-Revente : {:s}".format(attribution.spectacle.title)
|
||||
mail_body = loader.render_to_string('bda/mails/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:
|
||||
annulform = AnnulForm(participant, request.POST, prefix='annul')
|
||||
resellform = ResellForm(participant, prefix='resell')
|
||||
if annulform.is_valid():
|
||||
attributions = annulform.cleaned_data["attributions"]
|
||||
for attribution in attributions:
|
||||
attribution.revente.delete()
|
||||
|
||||
elif 'transfer' in request.POST:
|
||||
resellform = ResellForm(participant, prefix='resell')
|
||||
annulform = AnnulForm(participant, prefix='annul')
|
||||
|
||||
revente_id = request.POST['transfer'][0]
|
||||
rev = SpectacleRevente.objects.filter(soldTo__isnull=False,
|
||||
id=revente_id)
|
||||
if rev.exists():
|
||||
revente = rev.get()
|
||||
attrib = revente.attribution
|
||||
attrib.participant = revente.soldTo
|
||||
attrib.save()
|
||||
|
||||
elif 'reinit' in request.POST:
|
||||
resellform = ResellForm(participant, prefix='resell')
|
||||
annulform = AnnulForm(participant, prefix='annul')
|
||||
revente_id = request.POST['reinit'][0]
|
||||
rev = SpectacleRevente.objects.filter(soldTo__isnull=False,
|
||||
id=revente_id)
|
||||
if rev.exists():
|
||||
revente = rev.get()
|
||||
if revente.attribution.spectacle.date > timezone.now():
|
||||
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()
|
||||
|
||||
else:
|
||||
form = ResellForm(participant)
|
||||
resellform = ResellForm(participant, prefix='resell')
|
||||
annulform = AnnulForm(participant, prefix='annul')
|
||||
else:
|
||||
resellform = ResellForm(participant, prefix='resell')
|
||||
annulform = AnnulForm(participant, prefix='annul')
|
||||
|
||||
overdue = participant.attribution_set.filter(
|
||||
spectacle__date__gte=timezone.now(),
|
||||
revente__isnull=False,
|
||||
revente__seller=participant,
|
||||
revente__date__lte=timezone.now()-timedelta(hours=1)).filter(
|
||||
Q(revente__soldTo__isnull=True) | Q(revente__soldTo=participant))
|
||||
sold = participant.attribution_set.filter(
|
||||
spectacle__date__gte=timezone.now(),
|
||||
revente__isnull=False,
|
||||
revente__soldTo__isnull=False).exclude(
|
||||
revente__soldTo=participant)
|
||||
|
||||
return render(request, "bda-revente.html",
|
||||
{"form": form, 'tirage': tirage})
|
||||
{'tirage': tirage, 'overdue': overdue, "sold": sold,
|
||||
"annulform": annulform, "resellform": resellform})
|
||||
|
||||
|
||||
@login_required
|
||||
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", {})
|
||||
|
||||
revente.answered_mail.add(participant)
|
||||
return render(request, "bda-interested.html",
|
||||
{"spectacle": revente.attribution.spectacle,
|
||||
"date": revente.expiration_time})
|
||||
|
||||
|
||||
@login_required
|
||||
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)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = InscriptionReventeForm(tirage, request.POST)
|
||||
if form.is_valid():
|
||||
choices = form.cleaned_data['spectacles']
|
||||
participant.choicesrevente = choices
|
||||
participant.save()
|
||||
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
|
||||
success = True
|
||||
else:
|
||||
form = InscriptionReventeForm(
|
||||
tirage,
|
||||
initial={'spectacles': participant.choicesrevente.all()})
|
||||
|
||||
return render(request, "liste-reventes.html",
|
||||
{"form": form, 'shotgun': shotgun,
|
||||
"deja_revente": deja_revente, "success": success})
|
||||
|
||||
|
||||
@login_required
|
||||
def buy_revente(request, spectacle_id):
|
||||
spectacle = get_object_or_404(Spectacle, id=spectacle_id)
|
||||
tirage = spectacle.tirage
|
||||
participant, created = Participant.objects.get_or_create(
|
||||
user=request.user, tirage=tirage)
|
||||
reventes = SpectacleRevente.objects.filter(
|
||||
attribution__spectacle=spectacle,
|
||||
soldTo__isnull=True)
|
||||
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)
|
||||
|
||||
if not reventes_shotgun:
|
||||
return render(request, "bda-no-revente.html", {})
|
||||
|
||||
if request.POST:
|
||||
revente = random.choice(reventes_shotgun)
|
||||
revente.soldTo = participant
|
||||
revente.save()
|
||||
mail = loader.render_to_string('bda/mails/buy-shotgun.txt', {
|
||||
'spectacle': spectacle,
|
||||
'acheteur': request.user,
|
||||
'vendeur': revente.seller.user,
|
||||
})
|
||||
send_mail("BdA-Revente : %s" % spectacle.title, mail,
|
||||
request.user.email,
|
||||
[revente.seller.user.email],
|
||||
fail_silently=False)
|
||||
return render(request, "bda-success.html",
|
||||
{"seller": revente.attribution.participant.user,
|
||||
"spectacle": spectacle})
|
||||
|
||||
return render(request, "revente-confirm.html",
|
||||
{"spectacle": spectacle,
|
||||
"user": request.user})
|
||||
|
||||
|
||||
@login_required
|
||||
def revente_shotgun(request, tirage_id):
|
||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||
spectacles = tirage.spectacle_set.filter(
|
||||
date__gte=timezone.now())
|
||||
shotgun = []
|
||||
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:
|
||||
shotgun.append(spectacle)
|
||||
|
||||
return render(request, "bda-shotgun.html",
|
||||
{"shotgun": shotgun})
|
||||
|
||||
|
||||
@buro_required
|
||||
|
@ -345,11 +550,11 @@ def send_rappel(request, spectacle_id):
|
|||
# Mails d'exemples
|
||||
fake_member = request.user
|
||||
fake_member.nb_attr = 1
|
||||
exemple_mail_1place = render_template('mail-rappel.txt', {
|
||||
exemple_mail_1place = loader.render_to_string('bda/mails/rappel.txt', {
|
||||
'member': fake_member,
|
||||
'show': show})
|
||||
fake_member.nb_attr = 2
|
||||
exemple_mail_2places = render_template('mail-rappel.txt', {
|
||||
exemple_mail_2places = loader.render_to_string('bda/mails/rappel.txt', {
|
||||
'member': fake_member,
|
||||
'show': show})
|
||||
# Contexte
|
||||
|
|
0
cof/locale/__init__.py
Normal file
0
cof/locale/__init__.py
Normal file
0
cof/locale/fr/__init__.py
Normal file
0
cof/locale/fr/__init__.py
Normal file
9
cof/locale/fr/formats.py
Normal file
9
cof/locale/fr/formats.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Formats français.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
DATETIME_FORMAT = r'l j F Y \à H:i'
|
|
@ -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!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['127.0.0.1']
|
||||
|
||||
|
||||
# Application definition
|
||||
INSTALLED_APPS = (
|
||||
'gestioncof',
|
||||
|
@ -56,6 +53,7 @@ INSTALLED_APPS = (
|
|||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
|
@ -101,6 +99,7 @@ DATABASES = {
|
|||
'NAME': os.environ['DBNAME'],
|
||||
'USER': os.environ['DBUSER'],
|
||||
'PASSWORD': os.environ['DBPASSWD'],
|
||||
'HOST': os.environ.get('DBHOST', 'localhost'),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,26 +137,28 @@ MEDIA_URL = '/media/'
|
|||
# Various additional settings
|
||||
SITE_ID = 1
|
||||
|
||||
# URL prefix for admin static files -- CSS, JavaScript and images.
|
||||
# Make sure to use a trailing slash.
|
||||
# Examples: "http://foo.com/static/admin/", "/static/admin/".
|
||||
ADMIN_MEDIA_PREFIX = '/static/grappelli/'
|
||||
GRAPPELLI_ADMIN_HEADLINE = "GestioCOF"
|
||||
GRAPPELLI_ADMIN_TITLE = "<a href=\"/\">GestioCOF</a>"
|
||||
|
||||
PETITS_COURS_FROM = "Le COF <cof@ens.fr>"
|
||||
PETITS_COURS_BCC = "archivescof@gmail.com"
|
||||
PETITS_COURS_REPLYTO = "cof@ens.fr"
|
||||
MAIL_DATA = {
|
||||
'petits_cours': {
|
||||
'FROM': "Le COF <cof@ens.fr>",
|
||||
'BCC': "archivescof@gmail.com",
|
||||
'REPLYTO': "cof@ens.fr"},
|
||||
'rappels': {
|
||||
'FROM': 'Le BdA <bda@ens.fr>',
|
||||
'REPLYTO': 'Le BdA <bda@ens.fr>'},
|
||||
'revente': {
|
||||
'FROM': 'BdA-Revente <bda-revente@ens.fr>',
|
||||
'REPLYTO': 'BdA-Revente <bda-revente@ens.fr>'},
|
||||
}
|
||||
|
||||
RAPPEL_FROM = 'Le BdA <bda@ens.fr>'
|
||||
RAPPEL_REPLY_TO = RAPPEL_FROM
|
||||
|
||||
LOGIN_URL = "/gestion/login"
|
||||
LOGIN_REDIRECT_URL = "/gestion/"
|
||||
LOGIN_URL = "cof-login"
|
||||
LOGIN_REDIRECT_URL = "home"
|
||||
|
||||
CAS_SERVER_URL = 'https://cas.eleves.ens.fr/'
|
||||
CAS_IGNORE_REFERER = True
|
||||
CAS_REDIRECT_URL = '/gestion/'
|
||||
CAS_REDIRECT_URL = '/'
|
||||
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
|
@ -177,7 +178,7 @@ CHANNEL_LAYERS = {
|
|||
"default": {
|
||||
"BACKEND": "asgi_redis.RedisChannelLayer",
|
||||
"CONFIG": {
|
||||
"hosts": [("localhost", 6379)],
|
||||
"hosts": [(os.environ.get("REDIS_HOST", "localhost"), 6379)],
|
||||
},
|
||||
"ROUTING": "cof.routing.channel_routing",
|
||||
}
|
||||
|
@ -200,3 +201,5 @@ def show_toolbar(request):
|
|||
DEBUG_TOOLBAR_CONFIG = {
|
||||
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
|
||||
}
|
||||
|
||||
FORMAT_MODULE_PATH = 'cof.locale'
|
||||
|
|
37
cof/urls.py
37
cof/urls.py
|
@ -1,30 +1,33 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Fichier principal de configuration des urls du projet GestioCOF
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import autocomplete_light
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include, url
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.views.generic.base import TemplateView
|
||||
|
||||
import autocomplete_light
|
||||
|
||||
from django.contrib.auth import views as django_views
|
||||
from django_cas_ng import views as django_cas_views
|
||||
|
||||
from gestioncof import views as gestioncof_views, csv_views
|
||||
from gestioncof.urls import export_patterns, petitcours_patterns, \
|
||||
surveys_patterns, events_patterns, calendar_patterns, \
|
||||
clubs_patterns
|
||||
|
||||
from gestioncof.autocomplete import autocomplete
|
||||
|
||||
autocomplete_light.autodiscover()
|
||||
admin.autodiscover()
|
||||
|
||||
my_urlpatterns = [
|
||||
urlpatterns = [
|
||||
# Page d'accueil
|
||||
url(r'^$', gestioncof_views.home, name='home'),
|
||||
# Le BdA
|
||||
|
@ -48,7 +51,7 @@ my_urlpatterns = [
|
|||
url(r'^cas/logout$', django_cas_views.logout),
|
||||
url(r'^outsider/login$', gestioncof_views.login_ext),
|
||||
url(r'^outsider/logout$', django_views.logout, {'next_page': 'home'}),
|
||||
url(r'^login$', gestioncof_views.login),
|
||||
url(r'^login$', gestioncof_views.login, name="cof-login"),
|
||||
url(r'^logout$', gestioncof_views.logout),
|
||||
# Infos persos
|
||||
url(r'^profile$', gestioncof_views.profile),
|
||||
|
@ -81,14 +84,16 @@ my_urlpatterns = [
|
|||
url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff),
|
||||
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof),
|
||||
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente),
|
||||
url(r'^k-fet/', include('kfet.urls'))
|
||||
] + \
|
||||
(static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
if settings.DEBUG
|
||||
else [])
|
||||
# Si on est en production, MEDIA_ROOT est servi par Apache.
|
||||
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^gestion/', include(my_urlpatterns))
|
||||
url(r'^k-fet/', include('kfet.urls')),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
import debug_toolbar
|
||||
urlpatterns += [
|
||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
||||
]
|
||||
|
||||
# Si on est en production, MEDIA_ROOT est servi par Apache.
|
||||
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.
|
||||
urlpatterns += static(settings.MEDIA_URL,
|
||||
document_root=settings.MEDIA_ROOT)
|
||||
|
|
|
@ -144,9 +144,9 @@ User.profile_phone = ProfileInfo("phone", "Téléphone")
|
|||
User.profile_occupation = ProfileInfo("occupation", "Occupation")
|
||||
User.profile_departement = ProfileInfo("departement", "Departement")
|
||||
User.profile_mailing_cof = ProfileInfo("mailing_cof", "ML COF", True)
|
||||
User.profile_mailing_bda = ProfileInfo("mailing_bda", "ML BDA", True)
|
||||
User.profile_mailing_bda = ProfileInfo("mailing_bda", "ML BdA", True)
|
||||
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente",
|
||||
"ML BDA-R", True)
|
||||
"ML BdA-R", True)
|
||||
|
||||
|
||||
class UserProfileAdmin(UserAdmin):
|
||||
|
|
|
@ -14,7 +14,7 @@ from django.contrib.auth.models import User
|
|||
from django.views.generic import ListView
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.template import loader, Context
|
||||
from django.template import loader
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Min
|
||||
|
@ -30,13 +30,7 @@ from captcha.fields import ReCaptchaField
|
|||
|
||||
from datetime import datetime
|
||||
import base64
|
||||
import simplejson
|
||||
|
||||
|
||||
def render_template(template_path, data):
|
||||
tmpl = loader.get_template(template_path)
|
||||
context = Context(data)
|
||||
return tmpl.render(context)
|
||||
import json
|
||||
|
||||
|
||||
class DemandeListView(ListView):
|
||||
|
@ -137,8 +131,8 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
|
|||
proposed_for = proposed_for.items()
|
||||
attribdata = list(attribdata.items())
|
||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||
mainmail = render_template("petits-cours-mail-demandeur.txt",
|
||||
{"proposals": proposals,
|
||||
mainmail = loader.render_to_string("petits-cours-mail-demandeur.txt", {
|
||||
"proposals": proposals,
|
||||
"unsatisfied": unsatisfied,
|
||||
"extra":
|
||||
'<textarea name="extra" '
|
||||
|
@ -153,7 +147,7 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
|
|||
"proposed_mails": proposed_mails,
|
||||
"mainmail": mainmail,
|
||||
"attribdata":
|
||||
base64.b64encode(simplejson.dumps(attribdata)
|
||||
base64.b64encode(json.dumps(attribdata)
|
||||
.encode('utf_8')),
|
||||
"redo": redo,
|
||||
"errors": errors,
|
||||
|
@ -163,8 +157,10 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
|
|||
def _generate_eleve_email(demande, proposed_for):
|
||||
proposed_mails = []
|
||||
for user, matieres in proposed_for:
|
||||
msg = render_template("petits-cours-mail-eleve.txt",
|
||||
{"demande": demande, "matieres": matieres})
|
||||
msg = loader.render_to_string("petits-cours-mail-eleve.txt", {
|
||||
"demande": demande,
|
||||
"matieres": matieres
|
||||
})
|
||||
proposed_mails.append((user, msg))
|
||||
return proposed_mails
|
||||
|
||||
|
@ -262,7 +258,7 @@ def _traitement_post(request, demande):
|
|||
extra = request.POST["extra"].strip()
|
||||
redo = "redo" in request.POST
|
||||
attribdata = request.POST["attribdata"]
|
||||
attribdata = dict(simplejson.loads(base64.b64decode(attribdata)))
|
||||
attribdata = dict(json.loads(base64.b64decode(attribdata)))
|
||||
for matiere in demande.matieres.all():
|
||||
if matiere.id not in attribdata:
|
||||
unsatisfied.append(matiere)
|
||||
|
@ -278,13 +274,14 @@ def _traitement_post(request, demande):
|
|||
proposals_list = proposals.items()
|
||||
proposed_for = proposed_for.items()
|
||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||
mainmail = render_template("petits-cours-mail-demandeur.txt",
|
||||
{"proposals": proposals_list,
|
||||
mainmail = loader.render_to_string("petits-cours-mail-demandeur.txt", {
|
||||
"proposals": proposals_list,
|
||||
"unsatisfied": unsatisfied,
|
||||
"extra": extra})
|
||||
frommail = settings.PETITS_COURS_FROM
|
||||
bccaddress = settings.PETITS_COURS_BCC
|
||||
replyto = settings.PETITS_COURS_REPLYTO
|
||||
"extra": extra,
|
||||
})
|
||||
frommail = settings.MAIL_DATA['petits_cours']['FROM']
|
||||
bccaddress = settings.MAIL_DATA['petits_cours']['BCC']
|
||||
replyto = settings.MAIL_DATA['petits_cours']['REPLYTO']
|
||||
mails_to_send = []
|
||||
for (user, msg) in proposed_mails:
|
||||
msg = EmailMessage("Petits cours ENS par le COF", msg,
|
||||
|
|
|
@ -29,6 +29,12 @@ class COFCASBackend(CASBackend):
|
|||
request.session['attributes'] = attributes
|
||||
if not username:
|
||||
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)
|
||||
if len(profiles) > 0:
|
||||
profile = profiles.order_by('-is_cof')[0]
|
||||
|
|
|
@ -43,6 +43,8 @@
|
|||
{% else %}
|
||||
<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-liste-revente" tirage.id %}">S'inscrire à BdA-Revente</a></li>
|
||||
<li><a href="{% url "bda-shotgun" tirage.id %}">Places disponibles immédiatement</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
|
@ -82,7 +84,7 @@
|
|||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<h3 class="block-title">Gestion tirages BDA<span class="pull-right glyphicon glyphicon-list"></span></h3>
|
||||
<h3 class="block-title">Gestion tirages BdA<span class="pull-right glyphicon glyphicon-list"></span></h3>
|
||||
<div class="hm-block">
|
||||
{% if active_tirages %}
|
||||
{% for tirage in active_tirages %}
|
||||
|
|
|
@ -14,6 +14,7 @@ from django.http import Http404, HttpResponse, HttpResponseForbidden
|
|||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.views import login as django_login_view
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.utils import timezone
|
||||
import django.utils.six as six
|
||||
|
||||
|
@ -710,12 +711,15 @@ def calendar_ics(request, token):
|
|||
tirage__active=True)
|
||||
shows = shows.distinct()
|
||||
vcal = Calendar()
|
||||
site = Site.objects.get_current()
|
||||
for show in shows:
|
||||
vevent = Vevent()
|
||||
vevent.add('dtstart', show.date)
|
||||
vevent.add('dtend', show.date + timedelta(seconds=7200))
|
||||
vevent.add('summary', show.title)
|
||||
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)
|
||||
if subscription.subscribe_to_events:
|
||||
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('location', event.location)
|
||||
vevent.add('description', event.description)
|
||||
vevent.add('uid', 'event-{:d}@{:s}'.format(
|
||||
event.pk, site.domain))
|
||||
vcal.add_component(vevent)
|
||||
response = HttpResponse(content=vcal.to_ical())
|
||||
response['Content-Type'] = "text/calendar"
|
||||
|
|
|
@ -8,7 +8,5 @@ from channels.routing import route, route_class
|
|||
from kfet import consumers
|
||||
|
||||
channel_routing = [
|
||||
route_class(consumers.KPsul, path=r"^/gestion/ws/k-fet/k-psul/$"),
|
||||
#route("websocket.connect", ws_kpsul_history_connect),
|
||||
#route('websocket.receive', ws_message)
|
||||
route_class(consumers.KPsul, path=r"^ws/k-fet/k-psul/$"),
|
||||
]
|
||||
|
|
|
@ -924,7 +924,7 @@ $(document).ready(function() {
|
|||
}
|
||||
});
|
||||
},
|
||||
onClose: function() { this._lastFocused = articleSelect; }
|
||||
onClose: function() { this._lastFocused = (articleSelect.val() ? articleNb : articleSelect) ; }
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -34,12 +34,12 @@
|
|||
<tr class="transfer_form" id="{{ form.prefix }}">
|
||||
<td class="from_acc_data"></td>
|
||||
<td class="from_acc">
|
||||
<input type="text" name="from_acc" class="input_from_acc" autocomplete="off" spellcheck="false">
|
||||
<input type="text" name="from_acc" class="input_from_acc" maxlength="3" autocomplete="off" spellcheck="false">
|
||||
{{ form.from_acc }}
|
||||
</td>
|
||||
<td class="amount">{{ form.amount }}</td>
|
||||
<td class="to_acc">
|
||||
<input type="text" name="to_acc" class="input_to_acc" autocomplete="off" spellcheck="false">
|
||||
<input type="text" name="to_acc" class="input_to_acc" maxlength="3" autocomplete="off" spellcheck="false">
|
||||
{{ form.to_acc }}
|
||||
</td>
|
||||
<td class="to_acc_data"></td>
|
||||
|
@ -71,18 +71,21 @@ $(document).ready(function () {
|
|||
var $next = $form.next('.transfer_form').find('.from_acc input');
|
||||
}
|
||||
var $input_id = $input.next('input');
|
||||
if (isValidTrigramme(trigramme)) {
|
||||
getAccountData(trigramme, function(data) {
|
||||
$input_id.val(data.id);
|
||||
$data.text(data.name);
|
||||
$next.focus();
|
||||
});
|
||||
} else {
|
||||
$input_id.val('');
|
||||
$data.text('');
|
||||
}
|
||||
}
|
||||
|
||||
$('.input_from_acc, .input_to_acc').on('input', function() {
|
||||
var tri = $(this).val().toUpperCase();
|
||||
if (isValidTrigramme(tri)) {
|
||||
updateAccountData(tri, $(this));
|
||||
}
|
||||
});
|
||||
|
||||
$('#transfers_form').on('submit', function(e) {
|
||||
|
|
|
@ -6,6 +6,16 @@
|
|||
ProxyRequests Off
|
||||
ProxyPass /static/ !
|
||||
ProxyPass /media/ !
|
||||
# Pour utiliser un sous-dossier (typiquement /gestion/), il faut faire a la
|
||||
# place des lignes suivantes:
|
||||
#
|
||||
# RequestHeader set Daphne-Root-Path /gestion
|
||||
# ProxyPass /gestion/ws/ ws://127.0.0.1:8001/gestion/ws/
|
||||
# ProxyPass /gestion http://127.0.0.1:8001/gestion
|
||||
# ProxyPassReverse /gestion http://127.0.0.1:8001/gestion
|
||||
#
|
||||
# Penser egalement a changer les /static/ et /media/ dans la config apache
|
||||
# ainsi que dans les settings django.
|
||||
ProxyPass /ws/ ws://127.0.0.1:8001/ws/
|
||||
ProxyPass / http://127.0.0.1:8001/
|
||||
ProxyPassReverse / http://127.0.0.1:8001/
|
||||
|
|
|
@ -8,8 +8,9 @@ DBNAME="cof_gestion"
|
|||
DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
||||
|
||||
# Installation de paquets utiles
|
||||
apt-get update && apt-get install -y mercurial python-pip python-dev \
|
||||
apt-get update && apt-get install -y python3-pip python3-dev python3-venv \
|
||||
libmysqlclient-dev libjpeg-dev git redis-server
|
||||
pip install -U pip
|
||||
|
||||
# Configuration et installation de mysql. Le mot de passe root est le même que
|
||||
# le mot de passe pour l'utilisateur local - pour rappel, ceci est une instance
|
||||
|
@ -23,16 +24,16 @@ mysql -uroot -p$DBPASSWD -e "CREATE DATABASE $DBNAME; GRANT ALL PRIVILEGES ON $D
|
|||
|
||||
# Installation et configuration d'Apache
|
||||
apt-get install -y apache2
|
||||
a2enmod proxy proxy_http
|
||||
a2enmod proxy proxy_http proxy_wstunnel headers
|
||||
cp /vagrant/provisioning/apache.conf /etc/apache2/sites-available/gestiocof.conf
|
||||
a2ensite gestiocof
|
||||
a2dissite 000-default
|
||||
service apache2 restart
|
||||
mkdir /var/www/static
|
||||
chown -R vagrant:www-data /var/www/static
|
||||
chown -R ubuntu:www-data /var/www/static
|
||||
|
||||
# Mise en place du .bash_profile pour tout configurer lors du `vagrant ssh`
|
||||
cat > ~vagrant/.bash_profile <<EOF
|
||||
cat >> ~ubuntu/.bashrc <<EOF
|
||||
# On utilise la version de développement de GestioCOF
|
||||
export DJANGO_SETTINGS_MODULE='cof.settings_dev'
|
||||
|
||||
|
@ -44,25 +45,29 @@ export DBPASSWD="$DBPASSWD"
|
|||
# Permet d'utiliser les utilitaires pythons locaux
|
||||
export PATH="\$PATH:\$HOME/.local/bin"
|
||||
|
||||
# Charge le virtualenv
|
||||
source ~/venv/bin/activate
|
||||
|
||||
# On va dans /vagrant où se trouve le code de gestioCOF
|
||||
cd /vagrant
|
||||
EOF
|
||||
chown vagrant: ~vagrant/.bash_profile
|
||||
|
||||
# On va dans /vagrant où se trouve gestioCOF
|
||||
cd /vagrant
|
||||
|
||||
# Installation des dépendances python
|
||||
sudo -H -u vagrant pip install --user -r requirements.txt -r requirements-devel.txt
|
||||
# Installation du virtualenv, on utilise désormais python3
|
||||
sudo -H -u ubuntu python3 -m venv ~ubuntu/venv
|
||||
sudo -H -u ubuntu ~ubuntu/venv/bin/pip install -U pip
|
||||
sudo -H -u ubuntu ~ubuntu/venv/bin/pip install -r requirements.txt -r requirements-devel.txt
|
||||
|
||||
# Préparation de Django
|
||||
sudo -H -u vagrant DJANGO_SETTINGS_MODULE='cof.settings_dev' DBUSER=$DBUSER DBNAME=$DBNAME DBPASSWD=$DBPASSWD sh provisioning/prepare_django.sh
|
||||
sudo -H -u ubuntu DJANGO_SETTINGS_MODULE='cof.settings_dev' DBUSER=$DBUSER DBNAME=$DBNAME DBPASSWD=$DBPASSWD bash provisioning/prepare_django.sh
|
||||
|
||||
# Installation du cron pour les mails de rappels
|
||||
sudo -H -u vagrant crontab provisioning/cron.dev
|
||||
sudo -H -u ubuntu crontab provisioning/cron.dev
|
||||
|
||||
# On installe Daphne et on demande à supervisor de le lancer
|
||||
pip install daphne
|
||||
sudo -H -u ubuntu ~ubuntu/venv/bin/pip install daphne
|
||||
apt-get install -y supervisor
|
||||
cp /vagrant/provisioning/supervisor.conf /etc/supervisor/conf.d/gestiocof.conf
|
||||
sed "s/{DBUSER}/$DBUSER/" -i /etc/supervisor/conf.d/gestiocof.conf
|
||||
|
|
|
@ -7,3 +7,4 @@ DBNAME="cof_gestion"
|
|||
DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
||||
|
||||
19 */12 * * * date >> /vagrant/rappels.log ; python /vagrant/manage.py sendrappels >> /vagrant/rappels.log 2>&1
|
||||
*/5 * * * * python /vagrant/manage.py manage_revente >> /vagrant/reventes.log 2>&1
|
||||
|
|
|
@ -14,3 +14,14 @@ envoyés).
|
|||
- Garde les logs peut être une bonne idée.
|
||||
|
||||
Exemple : voir le fichier `provisioning/cron.dev`.
|
||||
|
||||
## Gestion des mails de revente
|
||||
|
||||
Il faut effectuer très régulièrement la commande `manage_reventes` de GestioCOF,
|
||||
qui gère toutes les actions associées à BdA-Revente : envoi des mails de notification,
|
||||
tirages.
|
||||
|
||||
- Pour l'instant un délai de 5 min est hardcodé
|
||||
- Garde des logs ; ils vont finir par être assez lourds si on a beaucoup de reventes.
|
||||
|
||||
Exemple : provisioning/cron.dev
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
#!/bin/bash
|
||||
# Doit être lancé par bootstrap.sh
|
||||
|
||||
source ~/venv/bin/activate
|
||||
python manage.py migrate
|
||||
python manage.py loaddata users root bda gestion sites
|
||||
python manage.py collectstatic --noinput
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
[program:worker]
|
||||
command=/usr/bin/python /vagrant/manage.py runworker
|
||||
command=/home/ubuntu/venv/bin/python /vagrant/manage.py runworker
|
||||
directory=/vagrant/
|
||||
user=vagrant
|
||||
user=ubuntu
|
||||
environment=DBUSER={DBUSER},DBNAME={DBNAME},DBPASSWD={DBPASSWD},DJANGO_SETTINGS_MODULE="cof.settings_dev"
|
||||
autostart=true
|
||||
autorestart=true
|
||||
|
@ -10,11 +10,11 @@ stopasgroup=true
|
|||
redirect_stderr=true
|
||||
|
||||
[program:interface]
|
||||
command=/usr/local/bin/daphne -b 127.0.0.1 -p 8001 cof.asgi:channel_layer
|
||||
command=/home/ubuntu/venv/bin/daphne -b 127.0.0.1 -p 8001 cof.asgi:channel_layer
|
||||
environment=DBUSER={DBUSER},DBNAME={DBNAME},DBPASSWD={DBPASSWD},DJANGO_SETTINGS_MODULE="cof.settings_dev"
|
||||
directory=/vagrant/
|
||||
redirect_stderr=true
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stopasgroup=true
|
||||
user=vagrant
|
||||
user=ubuntu
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
-r requirements.txt
|
||||
django-debug-toolbar
|
||||
ipython
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
configparser==3.5.0
|
||||
Django==1.8
|
||||
Django==1.8.*
|
||||
django-autocomplete-light==2.3.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-recaptcha==1.0.5
|
||||
mysqlclient==1.3.7
|
||||
Pillow==3.3.0
|
||||
simplejson==3.8.2
|
||||
six==1.10.0
|
||||
unicodecsv==0.14.1
|
||||
icalendar==3.10
|
||||
|
@ -15,7 +14,7 @@ django-bootstrap-form==3.2.1
|
|||
asgiref==0.14.0
|
||||
daphne==0.14.3
|
||||
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
|
||||
future==0.15.2
|
||||
django-widget-tweaks==1.4.1
|
||||
|
|
Loading…
Reference in a new issue