forked from DGNum/gestioCOF
Merge branch 'master' into k-fet
This commit is contained in:
commit
f5ebe3f166
50 changed files with 1095 additions and 200 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
|
# For a complete reference, please see the online documentation at
|
||||||
# https://docs.vagrantup.com.
|
# 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
|
# On associe le port 80 dans la machine virtuelle avec le port 8080 de notre
|
||||||
# ordinateur, et le port 8000 avec le port 8000.
|
# 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 __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 bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
from django.template.defaultfilters import pluralize
|
||||||
Attribution, Tirage, Quote, CategorieSpectacle
|
from django.utils import timezone
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
||||||
|
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
@ -210,6 +211,70 @@ class SalleAdmin(admin.ModelAdmin):
|
||||||
search_fields = ('name', 'address')
|
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(CategorieSpectacle)
|
||||||
admin.site.register(Spectacle, SpectacleAdmin)
|
admin.site.register(Spectacle, SpectacleAdmin)
|
||||||
admin.site.register(Salle, SalleAdmin)
|
admin.site.register(Salle, SalleAdmin)
|
||||||
|
@ -217,3 +282,4 @@ admin.site.register(Participant, ParticipantAdmin)
|
||||||
admin.site.register(Attribution, AttributionAdmin)
|
admin.site.register(Attribution, AttributionAdmin)
|
||||||
admin.site.register(ChoixSpectacle, ChoixSpectacleAdmin)
|
admin.site.register(ChoixSpectacle, ChoixSpectacleAdmin)
|
||||||
admin.site.register(Tirage, TirageAdmin)
|
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 print_function
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms.models import BaseInlineFormSet
|
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):
|
class BaseBdaFormSet(BaseInlineFormSet):
|
||||||
|
@ -35,17 +39,47 @@ class TokenForm(forms.Form):
|
||||||
token = forms.CharField(widget=forms.widgets.Textarea())
|
token = forms.CharField(widget=forms.widgets.Textarea())
|
||||||
|
|
||||||
|
|
||||||
class SpectacleModelChoiceField(forms.ModelChoiceField):
|
class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
||||||
def label_from_instance(self, obj):
|
def label_from_instance(self, obj):
|
||||||
return "%s le %s (%s) à %.02f€" % (obj.title, obj.date_no_seconds(),
|
return "%s" % obj.spectacle
|
||||||
obj.location, obj.price)
|
|
||||||
|
|
||||||
|
|
||||||
class ResellForm(forms.Form):
|
class ResellForm(forms.Form):
|
||||||
count = forms.ChoiceField(choices=(("1", "1"), ("2", "2"),))
|
attributions = AttributionModelMultipleChoiceField(
|
||||||
spectacle = SpectacleModelChoiceField(queryset=Spectacle.objects.none())
|
queryset=Attribution.objects.none(),
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
required=False)
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
def __init__(self, participant, *args, **kwargs):
|
||||||
super(ResellForm, self).__init__(*args, **kwargs)
|
super(ResellForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['spectacle'].queryset = participant.attributions.all() \
|
self.fields['attributions'].queryset = participant.attribution_set\
|
||||||
.distinct()
|
.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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Gestion en ligne de commande des mails de rappel.
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from datetime import timedelta
|
|
||||||
from bda.models import Spectacle
|
from bda.models import Spectacle
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Envoie les mails de rappel des spectacles dont la date ' \
|
help = 'Envoie les mails de rappel des spectacles dont la date ' \
|
||||||
'approche.\nNe renvoie pas les mails déjà envoyés.'
|
'approche.\nNe renvoie pas les mails déjà envoyés.'
|
||||||
|
leave_locale_alone = True
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
now = timezone.now()
|
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),
|
||||||
|
),
|
||||||
|
]
|
235
bda/models.py
235
bda/models.py
|
@ -5,22 +5,19 @@ from __future__ import print_function
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import calendar
|
import calendar
|
||||||
|
import random
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import User
|
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.core import mail
|
||||||
from django.conf import settings
|
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
|
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
|
@python_2_unicode_compatible
|
||||||
class Tirage(models.Model):
|
class Tirage(models.Model):
|
||||||
title = models.CharField("Titre", max_length=300)
|
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é",
|
enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
|
||||||
default=False)
|
default=False)
|
||||||
|
|
||||||
def date_no_seconds(self):
|
|
||||||
return self.fermeture.astimezone(timezone.get_current_timezone()) \
|
|
||||||
.strftime('%d %b %Y %H:%M')
|
|
||||||
|
|
||||||
def __str__(self):
|
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
|
@python_2_unicode_compatible
|
||||||
|
@ -83,42 +77,46 @@ class Spectacle(models.Model):
|
||||||
verbose_name = "Spectacle"
|
verbose_name = "Spectacle"
|
||||||
ordering = ("date", "title",)
|
ordering = ("date", "title",)
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "[%s]" % self
|
|
||||||
|
|
||||||
def timestamp(self):
|
def timestamp(self):
|
||||||
return "%d" % calendar.timegm(self.date.utctimetuple())
|
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):
|
def __str__(self):
|
||||||
return "%s - %s, %s, %.02f€" % (self.title, self.date_no_seconds(),
|
return "%s - %s, %s, %.02f€" % (
|
||||||
self.location, self.price)
|
self.title,
|
||||||
|
formats.localize(timezone.template_localtime(self.date)),
|
||||||
|
self.location,
|
||||||
|
self.price
|
||||||
|
)
|
||||||
|
|
||||||
def send_rappel(self):
|
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
|
# On récupère la liste des participants
|
||||||
members = {}
|
members = {}
|
||||||
for attr in Attribution.objects.filter(spectacle=self).all():
|
for attr in Attribution.objects.filter(spectacle=self).all():
|
||||||
member = attr.participant.user
|
member = attr.participant.user
|
||||||
if member.id in members:
|
if member.id in members:
|
||||||
members[member.id].nb_attr = 2
|
members[member.id][1] = 2
|
||||||
else:
|
else:
|
||||||
member.nb_attr = 1
|
members[member.id] = [member.first_name, 1, member.email]
|
||||||
members[member.id] = member
|
# Pour le BdA
|
||||||
|
members[0] = ['BdA', 1, 'bda@ens.fr']
|
||||||
|
members[-1] = ['BdA', 2, 'bda@ens.fr']
|
||||||
# On écrit un mail personnalisé à chaque participant
|
# On écrit un mail personnalisé à chaque participant
|
||||||
mails_to_send = []
|
mails_to_send = []
|
||||||
mail_object = "%s - %s - %s" % (self.title, self.date_no_seconds(),
|
mail_object = str(self)
|
||||||
self.location)
|
|
||||||
for member in members.values():
|
for member in members.values():
|
||||||
mail_body = render_template('mail-rappel.txt', {
|
mail_body = loader.render_to_string('bda/mails/rappel.txt', {
|
||||||
'member': member,
|
'name': member[0],
|
||||||
'show': self})
|
'nb_attr': member[1],
|
||||||
|
'show': self})
|
||||||
mail_tot = mail.EmailMessage(
|
mail_tot = mail.EmailMessage(
|
||||||
mail_object, mail_body,
|
mail_object, mail_body,
|
||||||
settings.RAPPEL_FROM, [member.email],
|
settings.MAIL_DATA['rappels']['FROM'], [member[2]],
|
||||||
[], headers={'Reply-To': settings.RAPPEL_REPLY_TO})
|
[], headers={
|
||||||
|
'Reply-To': settings.MAIL_DATA['rappels']['REPLYTO']})
|
||||||
mails_to_send.append(mail_tot)
|
mails_to_send.append(mail_tot)
|
||||||
# On envoie les mails
|
# On envoie les mails
|
||||||
connection = mail.get_connection()
|
connection = mail.get_connection()
|
||||||
|
@ -158,6 +156,9 @@ class Participant(models.Model):
|
||||||
max_length=6, choices=PAYMENT_TYPES,
|
max_length=6, choices=PAYMENT_TYPES,
|
||||||
blank=True)
|
blank=True)
|
||||||
tirage = models.ForeignKey(Tirage)
|
tirage = models.ForeignKey(Tirage)
|
||||||
|
choicesrevente = models.ManyToManyField(Spectacle,
|
||||||
|
related_name="subscribed",
|
||||||
|
blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s - %s" % (self.user, self.tirage.title)
|
return "%s - %s" % (self.user, self.tirage.title)
|
||||||
|
@ -205,4 +206,170 @@ class Attribution(models.Model):
|
||||||
given = models.BooleanField("Donnée", default=False)
|
given = models.BooleanField("Donnée", default=False)
|
||||||
|
|
||||||
def __str__(self):
|
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 %}
|
{% for show, members, losers in results %}
|
||||||
<div class="attribresult">
|
<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>
|
<p>
|
||||||
<strong>{{ show.nrequests }} demandes pour {{ show.slots }} places</strong>
|
<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 %}
|
{{ 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" %}
|
{% extends "base_title.html" %}
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
<h2><strong>Nope</strong></h1>
|
<h2><strong>Nope</strong></h2>
|
||||||
<p>Avant de revendre des places, il faut aller les payer !</p>
|
<p>Avant de revendre des places, il faut aller les payer !</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,26 +1,73 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load bootstrap %}
|
||||||
|
|
||||||
{% block extra_head %}
|
|
||||||
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
|
|
||||||
<h2>Revente de place</h1>
|
<h2>Revente de place</h2>
|
||||||
<form action="" method="post" id="resellform">
|
<h3>Places non revendues</h3>
|
||||||
|
<form class="form-horizontal" action="" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% if form.spectacle.errors %}<ul class="errorlist"><li>Sélectionnez un spetacle</li></ul>{% endif %}
|
<div class="form-group">
|
||||||
<p>
|
<div class="multiple-checkbox">
|
||||||
<pre>
|
<ul>
|
||||||
Bonjour,<br />
|
{% for box in resellform.attributions %}
|
||||||
<br />
|
<li>
|
||||||
Je souhaite revendre {{ form.count }} place(s) pour {{ form.spectacle }}.<br />
|
{{box.tag}}
|
||||||
Contactez-moi par email si vous êtes intéressé !<br />
|
{{box.choice_label}}
|
||||||
<br />
|
</li>
|
||||||
{{ user.get_full_name }} ({{ user.email }})
|
{% endfor %}
|
||||||
</pre>
|
</ul>
|
||||||
</p>
|
</div>
|
||||||
<input class="btn btn-primary" type="submit" value="Envoyer" />
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
|
||||||
|
</div>
|
||||||
</form>
|
</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 %}
|
{% 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" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
|
|
||||||
{% block extra_head %}
|
|
||||||
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
|
|
||||||
<h1>Revente de place</h1>
|
<h2>Revente de place</h2>
|
||||||
<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>
|
<p class="success">Un mail a bien été envoyé à {{seller.get_full_name}} ({{seller.email}}), pour racheter une place pour {{spectacle.title}} !</p>
|
||||||
{% endblock %}
|
{% 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" }}
|
Nous te rappellons que tu as eu la chance d'obtenir {{ 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 !
|
pour {{ show.title }}, le {{ show.date }} au {{ show.location }}. N'oublie pas de t'y rendre !
|
||||||
{% if member.nb_attr == 2 %}
|
{% if nb_attr == 2 %}
|
||||||
Tu as obtenu deux places pour ce spectacle. Nous te rappelons que
|
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.
|
ces places sont strictement réservées aux personnes de moins de 28 ans.
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if show.listing %}Pour ce spectacle, tu as reçu des places sur
|
{% 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
|
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
|
{% else %}Pour assister à ce spectacle, tu dois présenter les billets qui ont
|
||||||
été distribués au burô.
|
été distribués au burô.
|
||||||
{% endif %}
|
{% 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 %}
|
{% for spectacle in spectacles %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ spectacle.title }}</td>
|
<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.location }}">{{ spectacle.location }}</td>
|
||||||
<td data-sort-value="{{ spectacle.slots }}">{{ spectacle.slots }} places</td>
|
<td data-sort-value="{{ spectacle.slots }}">{{ spectacle.slots }} places</td>
|
||||||
<td data-sort-value="{{ spectacle.total }}">{{ spectacle.total }} demandes</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>
|
<tr>
|
||||||
<td>{{place.spectacle.title}}</td>
|
<td>{{place.spectacle.title}}</td>
|
||||||
<td>{{place.spectacle.location}}</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>
|
<td>{% if place.double %}deux places{%else%}une place{% endif %}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% 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 %}
|
{% for spectacle in object_list %}
|
||||||
<tr class="clickable-row" data-href="{% url 'bda-spectacle' tirage_id spectacle.id %}">
|
<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><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.location }}">{{ spectacle.location }}</td>
|
||||||
<td data-sort-value="{{ spectacle.price |stringformat:".3f" }}">
|
<td data-sort-value="{{ spectacle.price |stringformat:".3f" }}">
|
||||||
{{ spectacle.price |floatformat }}€
|
{{ spectacle.price |floatformat }}€
|
||||||
|
|
12
bda/urls.py
12
bda/urls.py
|
@ -32,6 +32,18 @@ urlpatterns = [
|
||||||
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
|
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
|
||||||
views.unpaid,
|
views.unpaid,
|
||||||
name="bda-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'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel),
|
||||||
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
||||||
name='bda-descriptions'),
|
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 print_function
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
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
|
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
|
from django.http import HttpResponseBadRequest, HttpResponseRedirect
|
||||||
|
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
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
from gestioncof.decorators import cof_required, buro_required
|
from gestioncof.decorators import cof_required, buro_required
|
||||||
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
|
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
|
||||||
Tirage, render_template
|
Tirage, SpectacleRevente
|
||||||
from bda.algorithm import Algorithm
|
from bda.algorithm import Algorithm
|
||||||
|
|
||||||
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm
|
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\
|
||||||
|
InscriptionReventeForm
|
||||||
|
|
||||||
|
|
||||||
@cof_required
|
@cof_required
|
||||||
|
@ -231,6 +238,11 @@ def do_tirage(request, tirage_id):
|
||||||
Attribution(spectacle=show, participant=member)
|
Attribution(spectacle=show, participant=member)
|
||||||
for show, members, _ in results
|
for show, members, _ in results
|
||||||
for member, _, _, _ in members])
|
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)
|
return render(request, "bda-attrib-extra.html", data)
|
||||||
else:
|
else:
|
||||||
return render(request, "bda-attrib.html", data)
|
return render(request, "bda-attrib.html", data)
|
||||||
|
@ -251,25 +263,6 @@ def tirage(request, tirage_id):
|
||||||
return render(request, "bda-token.html", {"form": form})
|
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
|
@login_required
|
||||||
def revente(request, tirage_id):
|
def revente(request, tirage_id):
|
||||||
tirage = get_object_or_404(Tirage, id=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)
|
user=request.user, tirage=tirage)
|
||||||
if not participant.paid:
|
if not participant.paid:
|
||||||
return render(request, "bda-notpaid.html", {})
|
return render(request, "bda-notpaid.html", {})
|
||||||
if request.POST:
|
if request.method == 'POST':
|
||||||
form = ResellForm(participant, request.POST)
|
if 'resell' in request.POST:
|
||||||
if form.is_valid():
|
resellform = ResellForm(participant, request.POST, prefix='resell')
|
||||||
return do_resell(request, form)
|
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:
|
||||||
|
resellform = ResellForm(participant, prefix='resell')
|
||||||
|
annulform = AnnulForm(participant, prefix='annul')
|
||||||
else:
|
else:
|
||||||
form = ResellForm(participant)
|
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",
|
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
|
@buro_required
|
||||||
|
@ -345,11 +550,11 @@ def send_rappel(request, spectacle_id):
|
||||||
# Mails d'exemples
|
# Mails d'exemples
|
||||||
fake_member = request.user
|
fake_member = request.user
|
||||||
fake_member.nb_attr = 1
|
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,
|
'member': fake_member,
|
||||||
'show': show})
|
'show': show})
|
||||||
fake_member.nb_attr = 2
|
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,
|
'member': fake_member,
|
||||||
'show': show})
|
'show': show})
|
||||||
# Contexte
|
# 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!
|
# 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',
|
||||||
|
@ -56,6 +53,7 @@ INSTALLED_APPS = (
|
||||||
)
|
)
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = (
|
MIDDLEWARE_CLASSES = (
|
||||||
|
'debug_toolbar.middleware.DebugToolbarMiddleware',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.middleware.common.CommonMiddleware',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
@ -101,6 +99,7 @@ DATABASES = {
|
||||||
'NAME': os.environ['DBNAME'],
|
'NAME': os.environ['DBNAME'],
|
||||||
'USER': os.environ['DBUSER'],
|
'USER': os.environ['DBUSER'],
|
||||||
'PASSWORD': os.environ['DBPASSWD'],
|
'PASSWORD': os.environ['DBPASSWD'],
|
||||||
|
'HOST': os.environ.get('DBHOST', 'localhost'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,7 +125,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)
|
||||||
|
@ -138,26 +137,28 @@ MEDIA_URL = '/media/'
|
||||||
# Various additional settings
|
# Various additional settings
|
||||||
SITE_ID = 1
|
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_HEADLINE = "GestioCOF"
|
||||||
GRAPPELLI_ADMIN_TITLE = "<a href=\"/\">GestioCOF</a>"
|
GRAPPELLI_ADMIN_TITLE = "<a href=\"/\">GestioCOF</a>"
|
||||||
|
|
||||||
PETITS_COURS_FROM = "Le COF <cof@ens.fr>"
|
MAIL_DATA = {
|
||||||
PETITS_COURS_BCC = "archivescof@gmail.com"
|
'petits_cours': {
|
||||||
PETITS_COURS_REPLYTO = "cof@ens.fr"
|
'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>'
|
LOGIN_URL = "cof-login"
|
||||||
RAPPEL_REPLY_TO = RAPPEL_FROM
|
LOGIN_REDIRECT_URL = "home"
|
||||||
|
|
||||||
LOGIN_URL = "/gestion/login"
|
|
||||||
LOGIN_REDIRECT_URL = "/gestion/"
|
|
||||||
|
|
||||||
CAS_SERVER_URL = 'https://cas.eleves.ens.fr/'
|
CAS_SERVER_URL = 'https://cas.eleves.ens.fr/'
|
||||||
CAS_IGNORE_REFERER = True
|
CAS_IGNORE_REFERER = True
|
||||||
CAS_REDIRECT_URL = '/gestion/'
|
CAS_REDIRECT_URL = '/'
|
||||||
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
||||||
AUTHENTICATION_BACKENDS = (
|
AUTHENTICATION_BACKENDS = (
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
|
@ -177,7 +178,7 @@ CHANNEL_LAYERS = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "asgi_redis.RedisChannelLayer",
|
"BACKEND": "asgi_redis.RedisChannelLayer",
|
||||||
"CONFIG": {
|
"CONFIG": {
|
||||||
"hosts": [("localhost", 6379)],
|
"hosts": [(os.environ.get("REDIS_HOST", "localhost"), 6379)],
|
||||||
},
|
},
|
||||||
"ROUTING": "cof.routing.channel_routing",
|
"ROUTING": "cof.routing.channel_routing",
|
||||||
}
|
}
|
||||||
|
@ -200,3 +201,5 @@ def show_toolbar(request):
|
||||||
DEBUG_TOOLBAR_CONFIG = {
|
DEBUG_TOOLBAR_CONFIG = {
|
||||||
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
|
'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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
Fichier principal de configuration des urls du projet GestioCOF
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import autocomplete_light
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.views.generic.base import TemplateView
|
from django.views.generic.base import TemplateView
|
||||||
|
|
||||||
import autocomplete_light
|
|
||||||
|
|
||||||
from django.contrib.auth import views as django_views
|
from django.contrib.auth import views as django_views
|
||||||
from django_cas_ng import views as django_cas_views
|
from django_cas_ng import views as django_cas_views
|
||||||
|
|
||||||
from gestioncof import views as gestioncof_views, csv_views
|
from gestioncof import views as gestioncof_views, csv_views
|
||||||
from gestioncof.urls import export_patterns, petitcours_patterns, \
|
from gestioncof.urls import export_patterns, petitcours_patterns, \
|
||||||
surveys_patterns, events_patterns, calendar_patterns, \
|
surveys_patterns, events_patterns, calendar_patterns, \
|
||||||
clubs_patterns
|
clubs_patterns
|
||||||
|
|
||||||
from gestioncof.autocomplete import autocomplete
|
from gestioncof.autocomplete import autocomplete
|
||||||
|
|
||||||
autocomplete_light.autodiscover()
|
autocomplete_light.autodiscover()
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
|
||||||
my_urlpatterns = [
|
urlpatterns = [
|
||||||
# Page d'accueil
|
# Page d'accueil
|
||||||
url(r'^$', gestioncof_views.home, name='home'),
|
url(r'^$', gestioncof_views.home, name='home'),
|
||||||
# Le BdA
|
# Le BdA
|
||||||
|
@ -48,7 +51,7 @@ my_urlpatterns = [
|
||||||
url(r'^cas/logout$', django_cas_views.logout),
|
url(r'^cas/logout$', django_cas_views.logout),
|
||||||
url(r'^outsider/login$', gestioncof_views.login_ext),
|
url(r'^outsider/login$', gestioncof_views.login_ext),
|
||||||
url(r'^outsider/logout$', django_views.logout, {'next_page': 'home'}),
|
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),
|
url(r'^logout$', gestioncof_views.logout),
|
||||||
# Infos persos
|
# Infos persos
|
||||||
url(r'^profile$', gestioncof_views.profile),
|
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_bda/bda_diff$', gestioncof_views.liste_bdadiff),
|
||||||
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof),
|
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof),
|
||||||
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente),
|
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente),
|
||||||
url(r'^k-fet/', include('kfet.urls'))
|
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))
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
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_occupation = ProfileInfo("occupation", "Occupation")
|
||||||
User.profile_departement = ProfileInfo("departement", "Departement")
|
User.profile_departement = ProfileInfo("departement", "Departement")
|
||||||
User.profile_mailing_cof = ProfileInfo("mailing_cof", "ML COF", True)
|
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",
|
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente",
|
||||||
"ML BDA-R", True)
|
"ML BdA-R", True)
|
||||||
|
|
||||||
|
|
||||||
class UserProfileAdmin(UserAdmin):
|
class UserProfileAdmin(UserAdmin):
|
||||||
|
|
|
@ -14,7 +14,7 @@ from django.contrib.auth.models import User
|
||||||
from django.views.generic import ListView
|
from django.views.generic import ListView
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
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.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.db.models import Min
|
from django.db.models import Min
|
||||||
|
@ -30,13 +30,7 @@ from captcha.fields import ReCaptchaField
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import base64
|
import base64
|
||||||
import simplejson
|
import json
|
||||||
|
|
||||||
|
|
||||||
def render_template(template_path, data):
|
|
||||||
tmpl = loader.get_template(template_path)
|
|
||||||
context = Context(data)
|
|
||||||
return tmpl.render(context)
|
|
||||||
|
|
||||||
|
|
||||||
class DemandeListView(ListView):
|
class DemandeListView(ListView):
|
||||||
|
@ -137,14 +131,14 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
|
||||||
proposed_for = proposed_for.items()
|
proposed_for = proposed_for.items()
|
||||||
attribdata = list(attribdata.items())
|
attribdata = list(attribdata.items())
|
||||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||||
mainmail = render_template("petits-cours-mail-demandeur.txt",
|
mainmail = loader.render_to_string("petits-cours-mail-demandeur.txt", {
|
||||||
{"proposals": proposals,
|
"proposals": proposals,
|
||||||
"unsatisfied": unsatisfied,
|
"unsatisfied": unsatisfied,
|
||||||
"extra":
|
"extra":
|
||||||
'<textarea name="extra" '
|
'<textarea name="extra" '
|
||||||
'style="width:99%; height: 90px;">'
|
'style="width:99%; height: 90px;">'
|
||||||
'</textarea>'
|
'</textarea>'
|
||||||
})
|
})
|
||||||
return render(request, "traitement_demande_petit_cours.html",
|
return render(request, "traitement_demande_petit_cours.html",
|
||||||
{"demande": demande,
|
{"demande": demande,
|
||||||
"unsatisfied": unsatisfied,
|
"unsatisfied": unsatisfied,
|
||||||
|
@ -153,7 +147,7 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
|
||||||
"proposed_mails": proposed_mails,
|
"proposed_mails": proposed_mails,
|
||||||
"mainmail": mainmail,
|
"mainmail": mainmail,
|
||||||
"attribdata":
|
"attribdata":
|
||||||
base64.b64encode(simplejson.dumps(attribdata)
|
base64.b64encode(json.dumps(attribdata)
|
||||||
.encode('utf_8')),
|
.encode('utf_8')),
|
||||||
"redo": redo,
|
"redo": redo,
|
||||||
"errors": errors,
|
"errors": errors,
|
||||||
|
@ -163,8 +157,10 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
|
||||||
def _generate_eleve_email(demande, proposed_for):
|
def _generate_eleve_email(demande, proposed_for):
|
||||||
proposed_mails = []
|
proposed_mails = []
|
||||||
for user, matieres in proposed_for:
|
for user, matieres in proposed_for:
|
||||||
msg = render_template("petits-cours-mail-eleve.txt",
|
msg = loader.render_to_string("petits-cours-mail-eleve.txt", {
|
||||||
{"demande": demande, "matieres": matieres})
|
"demande": demande,
|
||||||
|
"matieres": matieres
|
||||||
|
})
|
||||||
proposed_mails.append((user, msg))
|
proposed_mails.append((user, msg))
|
||||||
return proposed_mails
|
return proposed_mails
|
||||||
|
|
||||||
|
@ -262,7 +258,7 @@ def _traitement_post(request, demande):
|
||||||
extra = request.POST["extra"].strip()
|
extra = request.POST["extra"].strip()
|
||||||
redo = "redo" in request.POST
|
redo = "redo" in request.POST
|
||||||
attribdata = request.POST["attribdata"]
|
attribdata = request.POST["attribdata"]
|
||||||
attribdata = dict(simplejson.loads(base64.b64decode(attribdata)))
|
attribdata = dict(json.loads(base64.b64decode(attribdata)))
|
||||||
for matiere in demande.matieres.all():
|
for matiere in demande.matieres.all():
|
||||||
if matiere.id not in attribdata:
|
if matiere.id not in attribdata:
|
||||||
unsatisfied.append(matiere)
|
unsatisfied.append(matiere)
|
||||||
|
@ -278,13 +274,14 @@ def _traitement_post(request, demande):
|
||||||
proposals_list = proposals.items()
|
proposals_list = proposals.items()
|
||||||
proposed_for = proposed_for.items()
|
proposed_for = proposed_for.items()
|
||||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||||
mainmail = render_template("petits-cours-mail-demandeur.txt",
|
mainmail = loader.render_to_string("petits-cours-mail-demandeur.txt", {
|
||||||
{"proposals": proposals_list,
|
"proposals": proposals_list,
|
||||||
"unsatisfied": unsatisfied,
|
"unsatisfied": unsatisfied,
|
||||||
"extra": extra})
|
"extra": extra,
|
||||||
frommail = settings.PETITS_COURS_FROM
|
})
|
||||||
bccaddress = settings.PETITS_COURS_BCC
|
frommail = settings.MAIL_DATA['petits_cours']['FROM']
|
||||||
replyto = settings.PETITS_COURS_REPLYTO
|
bccaddress = settings.MAIL_DATA['petits_cours']['BCC']
|
||||||
|
replyto = settings.MAIL_DATA['petits_cours']['REPLYTO']
|
||||||
mails_to_send = []
|
mails_to_send = []
|
||||||
for (user, msg) in proposed_mails:
|
for (user, msg) in proposed_mails:
|
||||||
msg = EmailMessage("Petits cours ENS par le COF", msg,
|
msg = EmailMessage("Petits cours ENS par le COF", msg,
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -43,6 +43,8 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{% url "bda-places-attribuees" tirage.id %}">Mes places</a></li>
|
<li><a href="{% url "bda-places-attribuees" tirage.id %}">Mes places</a></li>
|
||||||
<li><a href="{% url "bda-revente" tirage.id %}">Revendre une place</a></li>
|
<li><a href="{% url "bda-revente" 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 %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -82,7 +84,7 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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">
|
<div class="hm-block">
|
||||||
{% if active_tirages %}
|
{% if active_tirages %}
|
||||||
{% for tirage in 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.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"
|
||||||
|
|
|
@ -8,7 +8,5 @@ from channels.routing import route, route_class
|
||||||
from kfet import consumers
|
from kfet import consumers
|
||||||
|
|
||||||
channel_routing = [
|
channel_routing = [
|
||||||
route_class(consumers.KPsul, path=r"^/gestion/ws/k-fet/k-psul/$"),
|
route_class(consumers.KPsul, path=r"^ws/k-fet/k-psul/$"),
|
||||||
#route("websocket.connect", ws_kpsul_history_connect),
|
|
||||||
#route('websocket.receive', ws_message)
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,6 +6,16 @@
|
||||||
ProxyRequests Off
|
ProxyRequests Off
|
||||||
ProxyPass /static/ !
|
ProxyPass /static/ !
|
||||||
ProxyPass /media/ !
|
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 /ws/ ws://127.0.0.1:8001/ws/
|
||||||
ProxyPass / http://127.0.0.1:8001/
|
ProxyPass / http://127.0.0.1:8001/
|
||||||
ProxyPassReverse / http://127.0.0.1:8001/
|
ProxyPassReverse / http://127.0.0.1:8001/
|
||||||
|
|
|
@ -8,8 +8,9 @@ DBNAME="cof_gestion"
|
||||||
DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
||||||
|
|
||||||
# Installation de paquets utiles
|
# 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
|
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
|
# 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
|
# 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
|
# Installation et configuration d'Apache
|
||||||
apt-get install -y apache2
|
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
|
cp /vagrant/provisioning/apache.conf /etc/apache2/sites-available/gestiocof.conf
|
||||||
a2ensite gestiocof
|
a2ensite gestiocof
|
||||||
a2dissite 000-default
|
a2dissite 000-default
|
||||||
service apache2 restart
|
service apache2 restart
|
||||||
mkdir /var/www/static
|
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`
|
# 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
|
# On utilise la version de développement de GestioCOF
|
||||||
export DJANGO_SETTINGS_MODULE='cof.settings_dev'
|
export DJANGO_SETTINGS_MODULE='cof.settings_dev'
|
||||||
|
|
||||||
|
@ -44,25 +45,29 @@ export DBPASSWD="$DBPASSWD"
|
||||||
# Permet d'utiliser les utilitaires pythons locaux
|
# Permet d'utiliser les utilitaires pythons locaux
|
||||||
export PATH="\$PATH:\$HOME/.local/bin"
|
export PATH="\$PATH:\$HOME/.local/bin"
|
||||||
|
|
||||||
|
# Charge le virtualenv
|
||||||
|
source ~/venv/bin/activate
|
||||||
|
|
||||||
# On va dans /vagrant où se trouve le code de gestioCOF
|
# On va dans /vagrant où se trouve le code de gestioCOF
|
||||||
cd /vagrant
|
cd /vagrant
|
||||||
EOF
|
EOF
|
||||||
chown vagrant: ~vagrant/.bash_profile
|
|
||||||
|
|
||||||
# On va dans /vagrant où se trouve gestioCOF
|
# On va dans /vagrant où se trouve gestioCOF
|
||||||
cd /vagrant
|
cd /vagrant
|
||||||
|
|
||||||
# Installation des dépendances python
|
# Installation du virtualenv, on utilise désormais python3
|
||||||
sudo -H -u vagrant pip install --user -r requirements.txt -r requirements-devel.txt
|
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
|
# 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
|
# 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
|
# 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
|
apt-get install -y supervisor
|
||||||
cp /vagrant/provisioning/supervisor.conf /etc/supervisor/conf.d/gestiocof.conf
|
cp /vagrant/provisioning/supervisor.conf /etc/supervisor/conf.d/gestiocof.conf
|
||||||
sed "s/{DBUSER}/$DBUSER/" -i /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"
|
DBPASSWD="4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
||||||
|
|
||||||
19 */12 * * * date >> /vagrant/rappels.log ; python /vagrant/manage.py sendrappels >> /vagrant/rappels.log 2>&1
|
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.
|
- Garde les logs peut être une bonne idée.
|
||||||
|
|
||||||
Exemple : voir le fichier `provisioning/cron.dev`.
|
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
|
# Doit être lancé par bootstrap.sh
|
||||||
|
|
||||||
|
source ~/venv/bin/activate
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
python manage.py loaddata users root bda gestion sites
|
python manage.py loaddata users root bda gestion sites
|
||||||
python manage.py collectstatic --noinput
|
python manage.py collectstatic --noinput
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[program:worker]
|
[program:worker]
|
||||||
command=/usr/bin/python /vagrant/manage.py runworker
|
command=/home/ubuntu/venv/bin/python /vagrant/manage.py runworker
|
||||||
directory=/vagrant/
|
directory=/vagrant/
|
||||||
user=vagrant
|
user=ubuntu
|
||||||
environment=DBUSER={DBUSER},DBNAME={DBNAME},DBPASSWD={DBPASSWD},DJANGO_SETTINGS_MODULE="cof.settings_dev"
|
environment=DBUSER={DBUSER},DBNAME={DBNAME},DBPASSWD={DBPASSWD},DJANGO_SETTINGS_MODULE="cof.settings_dev"
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=true
|
autorestart=true
|
||||||
|
@ -10,11 +10,11 @@ stopasgroup=true
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
|
|
||||||
[program:interface]
|
[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"
|
environment=DBUSER={DBUSER},DBNAME={DBNAME},DBPASSWD={DBPASSWD},DJANGO_SETTINGS_MODULE="cof.settings_dev"
|
||||||
directory=/vagrant/
|
directory=/vagrant/
|
||||||
redirect_stderr=true
|
redirect_stderr=true
|
||||||
autostart=true
|
autostart=true
|
||||||
autorestart=true
|
autorestart=true
|
||||||
stopasgroup=true
|
stopasgroup=true
|
||||||
user=vagrant
|
user=ubuntu
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
|
-r requirements.txt
|
||||||
django-debug-toolbar
|
django-debug-toolbar
|
||||||
ipython
|
ipython
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
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
|
||||||
Pillow==3.3.0
|
Pillow==3.3.0
|
||||||
simplejson==3.8.2
|
|
||||||
six==1.10.0
|
six==1.10.0
|
||||||
unicodecsv==0.14.1
|
unicodecsv==0.14.1
|
||||||
icalendar==3.10
|
icalendar==3.10
|
||||||
|
@ -15,7 +14,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