Merge branch 'master' into qwann/k-fet/kfet_open
This commit is contained in:
commit
a5671fdf4c
135 changed files with 5387 additions and 17546 deletions
|
@ -32,6 +32,9 @@ before_script:
|
|||
- 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'@'%'"
|
||||
# Remove the old test database if it has not been done yet
|
||||
- mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DBHOST"
|
||||
-e "DROP DATABASE test_$DBNAME" || true
|
||||
- pip install --cache-dir vendor/pip -t vendor/python -r requirements-devel.txt
|
||||
|
||||
test:
|
||||
|
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 cof-geek
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
29
README.md
29
README.md
|
@ -84,29 +84,30 @@ visualiser la dernière version du code.
|
|||
Si vous optez pour une installation manuelle plutôt que d'utiliser Vagrant, il
|
||||
est fortement conseillé d'utiliser un environnement virtuel pour Python.
|
||||
|
||||
Il vous faudra installer mercurial, pip, les librairies de développement de
|
||||
python, un client et un serveur MySQL ainsi qu'un serveur redis ; sous Debian et
|
||||
dérivées (Ubuntu, ...) :
|
||||
Il vous faudra installer pip, les librairies de développement de python, un
|
||||
client et un serveur MySQL ainsi qu'un serveur redis ; sous Debian et dérivées
|
||||
(Ubuntu, ...) :
|
||||
|
||||
sudo apt-get install mercurial python-pip python-dev libmysqlclient-dev
|
||||
redis-server
|
||||
sudo apt-get install python-pip python-dev libmysqlclient-dev redis-server
|
||||
|
||||
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
|
||||
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
|
||||
(le dossier où se trouve ce README), et créez-le maintenant :
|
||||
|
||||
virtualenv env
|
||||
virtualenv env -p $(which python3)
|
||||
|
||||
Pour l'activer, il faut faire
|
||||
L'option `-p` sert à préciser l'exécutable python à utiliser. Vous devez choisir
|
||||
python3, si c'est la version de python par défaut sur votre système, ceci n'est
|
||||
pas nécessaire. Pour l'activer, il faut faire
|
||||
|
||||
. env/bin/activate
|
||||
|
||||
dans le même dossier.
|
||||
|
||||
Vous pouvez maintenant installer les dépendances Python depuis les fichiers
|
||||
`requirements.txt` et `requirements-devel.txt` :
|
||||
Vous pouvez maintenant installer les dépendances Python depuis le fichier
|
||||
`requirements-devel.txt` :
|
||||
|
||||
pip install -r requirements.txt -r requirements-devel.txt
|
||||
pip install -r requirements-devel.txt
|
||||
|
||||
Copiez le fichier `cof/settings_dev.py` dans `cof/settings.py`.
|
||||
|
||||
|
@ -170,10 +171,14 @@ Il ne vous reste plus qu'à initialiser les modèles de Django avec la commande
|
|||
|
||||
python manage.py migrate
|
||||
|
||||
Charger les mails indispensables au bon fonctionnement de GestioCOF :
|
||||
|
||||
Une base de donnée pré-remplie est disponible en lançant la commande :
|
||||
python manage.py syncmails
|
||||
|
||||
python manage.py loaddata users root bda gestion sites accounts groups articles
|
||||
Une base de donnée pré-remplie est disponible en lançant les commandes :
|
||||
|
||||
python manage.py loaddata gestion sites accounts groups articles
|
||||
python manage.py loaddevdata
|
||||
|
||||
Vous êtes prêts à développer ! Lancer GestioCOF en faisant
|
||||
|
||||
|
|
77
bda/admin.py
77
bda/admin.py
|
@ -1,10 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
import autocomplete_light
|
||||
from datetime import timedelta
|
||||
from custommail.shortcuts import send_mass_custom_mail
|
||||
|
||||
from django.core.mail import send_mail
|
||||
from django.contrib import admin
|
||||
from django.db.models import Sum, Count
|
||||
from django.template.defaultfilters import pluralize
|
||||
|
@ -13,10 +12,6 @@ from django import forms
|
|||
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
||||
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
import autocomplete_light
|
||||
|
||||
|
||||
class ChoixSpectacleInline(admin.TabularInline):
|
||||
model = ChoixSpectacle
|
||||
|
@ -72,66 +67,20 @@ class ParticipantAdmin(admin.ModelAdmin):
|
|||
readonly_fields = ("total",)
|
||||
|
||||
def send_attribs(self, request, queryset):
|
||||
datatuple = []
|
||||
for member in queryset.all():
|
||||
attribs = member.attributions.all()
|
||||
context = {'member': member.user}
|
||||
shortname = ""
|
||||
if len(attribs) == 0:
|
||||
mail = """Cher-e %s,
|
||||
|
||||
Tu t'es inscrit-e pour le tirage au sort du BdA. Malheureusement, tu n'as
|
||||
obtenu aucune place.
|
||||
|
||||
Nous proposons cependant de nombreuses offres hors-tirage tout au long de
|
||||
l'année, et nous t'invitons à nous contacter si l'une d'entre elles
|
||||
t'intéresse !
|
||||
--
|
||||
Le Bureau des Arts
|
||||
|
||||
"""
|
||||
name = member.user.get_full_name()
|
||||
mail = mail % name
|
||||
shortname = "bda-attributions-decus"
|
||||
else:
|
||||
mail = """Cher-e %s,
|
||||
|
||||
Tu t'es inscrit-e pour le tirage au sort du BdA. Tu as été sélectionné-e
|
||||
pour les spectacles suivants :
|
||||
|
||||
%s
|
||||
|
||||
*Paiement*
|
||||
L'intégralité de ces places de spectacles est à régler dès maintenant et AVANT
|
||||
le %s, au bureau du COF pendant les heures de permanences (du lundi au vendredi
|
||||
entre 12h et 14h, et entre 18h et 20h). Des facilités de paiement sont bien
|
||||
évidemment possibles : nous pouvons ne pas encaisser le chèque immédiatement,
|
||||
ou bien découper votre paiement en deux fois. Pour ceux qui ne pourraient pas
|
||||
venir payer au bureau, merci de nous contacter par mail.
|
||||
|
||||
*Mode de retrait des places*
|
||||
Au moment du paiement, certaines places vous seront remises directement,
|
||||
d'autres seront à récupérer au cours de l'année, d'autres encore seront
|
||||
nominatives et à retirer le soir même dans les theâtres correspondants.
|
||||
Pour chaque spectacle, vous recevrez un mail quelques jours avant la
|
||||
représentation vous indiquant le mode de retrait.
|
||||
|
||||
Nous vous rappelons que l'obtention de places du BdA vous engage à
|
||||
respecter les règles de fonctionnement :
|
||||
http://www.cof.ens.fr/bda/?page_id=1370
|
||||
Le système de revente des places via les mails BdA-revente sera très
|
||||
prochainement disponible, directement sur votre compte GestioCOF.
|
||||
|
||||
En vous souhaitant de très beaux spectacles tout au long de l'année,
|
||||
--
|
||||
Le Bureau des Arts
|
||||
"""
|
||||
attribs_text = ""
|
||||
name = member.user.get_full_name()
|
||||
for attrib in attribs:
|
||||
attribs_text += "- 1 place pour %s\n" % attrib
|
||||
deadline = member.tirage.fermeture + timedelta(days=7)
|
||||
mail = mail % (name, attribs_text,
|
||||
deadline.strftime('%d %b %Y'))
|
||||
send_mail("Résultats du tirage au sort", mail,
|
||||
"bda@ens.fr", [member.user.email],
|
||||
fail_silently=True)
|
||||
shortname = "bda-attributions"
|
||||
context['places'] = attribs
|
||||
print(context)
|
||||
datatuple.append((shortname, context, "bda@ens.fr",
|
||||
[member.user.email]))
|
||||
send_mass_custom_mail(datatuple)
|
||||
count = len(queryset.all())
|
||||
if count == 1:
|
||||
message_bit = "1 membre a"
|
||||
|
|
File diff suppressed because it is too large
Load diff
23
bda/forms.py
23
bda/forms.py
|
@ -4,11 +4,8 @@ from __future__ import division
|
|||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from django import forms
|
||||
from django.forms.models import BaseInlineFormSet
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
from bda.models import Attribution, Spectacle
|
||||
|
||||
|
@ -46,6 +43,7 @@ class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
|||
|
||||
class ResellForm(forms.Form):
|
||||
attributions = AttributionModelMultipleChoiceField(
|
||||
label='',
|
||||
queryset=Attribution.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
|
@ -59,6 +57,7 @@ class ResellForm(forms.Form):
|
|||
|
||||
class AnnulForm(forms.Form):
|
||||
attributions = AttributionModelMultipleChoiceField(
|
||||
label='',
|
||||
queryset=Attribution.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
|
@ -68,7 +67,7 @@ class AnnulForm(forms.Form):
|
|||
self.fields['attributions'].queryset = participant.attribution_set\
|
||||
.filter(spectacle__date__gte=timezone.now(),
|
||||
revente__isnull=False,
|
||||
revente__date__gt=timezone.now()-timedelta(hours=1),
|
||||
revente__notif_sent=False,
|
||||
revente__soldTo__isnull=True)
|
||||
|
||||
|
||||
|
@ -82,3 +81,19 @@ class InscriptionReventeForm(forms.Form):
|
|||
super(InscriptionReventeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['spectacles'].queryset = tirage.spectacle_set.filter(
|
||||
date__gte=timezone.now())
|
||||
|
||||
|
||||
class SoldForm(forms.Form):
|
||||
attributions = AttributionModelMultipleChoiceField(
|
||||
label='',
|
||||
queryset=Attribution.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple)
|
||||
|
||||
def __init__(self, participant, *args, **kwargs):
|
||||
super(SoldForm, self).__init__(*args, **kwargs)
|
||||
self.fields['attributions'].queryset = (
|
||||
participant.attribution_set
|
||||
.filter(revente__isnull=False,
|
||||
revente__soldTo__isnull=False)
|
||||
.exclude(revente__soldTo=participant)
|
||||
)
|
||||
|
|
107
bda/management/commands/loadbdadevdata.py
Normal file
107
bda/management/commands/loadbdadevdata.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
"""
|
||||
Crée deux tirages de test et y inscrit les utilisateurs
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from gestioncof.management.base import MyBaseCommand
|
||||
from bda.models import Tirage, Spectacle, Salle, Participant, ChoixSpectacle
|
||||
from bda.views import do_tirage
|
||||
|
||||
|
||||
# Où sont stockés les fichiers json
|
||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
||||
'data')
|
||||
|
||||
|
||||
class Command(MyBaseCommand):
|
||||
help = "Crée deux tirages de test et y inscrit les utilisateurs."
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# ---
|
||||
# Tirages
|
||||
# ---
|
||||
|
||||
Tirage.objects.all().delete()
|
||||
Tirage.objects.bulk_create([
|
||||
Tirage(
|
||||
title="Tirage de test 1",
|
||||
ouverture=timezone.now()-timezone.timedelta(days=7),
|
||||
fermeture=timezone.now(),
|
||||
active=True
|
||||
),
|
||||
Tirage(
|
||||
title="Tirage de test 2",
|
||||
ouverture=timezone.now(),
|
||||
fermeture=timezone.now()+timezone.timedelta(days=60),
|
||||
active=True
|
||||
)
|
||||
])
|
||||
tirages = Tirage.objects.all()
|
||||
|
||||
# ---
|
||||
# Salles
|
||||
# ---
|
||||
|
||||
locations = self.from_json('locations.json', DATA_DIR, Salle)
|
||||
|
||||
# ---
|
||||
# Spectacles
|
||||
# ---
|
||||
|
||||
def show_callback(show):
|
||||
"""
|
||||
Assigne un tirage, une date et un lieu à un spectacle et décide si
|
||||
les places sont sur listing.
|
||||
"""
|
||||
show.tirage = random.choice(tirages)
|
||||
show.listing = bool(random.randint(0, 1))
|
||||
show.date = (
|
||||
show.tirage.fermeture
|
||||
+ timezone.timedelta(days=random.randint(60, 90))
|
||||
)
|
||||
show.location = random.choice(locations)
|
||||
return show
|
||||
shows = self.from_json(
|
||||
'shows.json', DATA_DIR, Spectacle, show_callback
|
||||
)
|
||||
|
||||
# ---
|
||||
# Inscriptions
|
||||
# ---
|
||||
|
||||
self.stdout.write("Inscription des utilisateurs aux tirages")
|
||||
ChoixSpectacle.objects.all().delete()
|
||||
choices = []
|
||||
for user in User.objects.filter(profile__is_cof=True):
|
||||
for tirage in tirages:
|
||||
part, _ = Participant.objects.get_or_create(
|
||||
user=user,
|
||||
tirage=tirage
|
||||
)
|
||||
shows = random.sample(
|
||||
list(tirage.spectacle_set.all()),
|
||||
tirage.spectacle_set.count() // 2
|
||||
)
|
||||
for (rank, show) in enumerate(shows):
|
||||
choices.append(ChoixSpectacle(
|
||||
participant=part,
|
||||
spectacle=show,
|
||||
priority=rank + 1,
|
||||
double_choice=random.choice(
|
||||
['1', 'double', 'autoquit']
|
||||
)
|
||||
))
|
||||
ChoixSpectacle.objects.bulk_create(choices)
|
||||
self.stdout.write("- {:d} inscriptions générées".format(len(choices)))
|
||||
|
||||
# ---
|
||||
# On lance le premier tirage
|
||||
# ---
|
||||
|
||||
self.stdout.write("Lancement du premier tirage")
|
||||
do_tirage(tirages[0], "dummy_token")
|
26
bda/management/data/locations.json
Normal file
26
bda/management/data/locations.json
Normal file
|
@ -0,0 +1,26 @@
|
|||
[
|
||||
{
|
||||
"name": "Cour\u00f4",
|
||||
"address": "45 rue d'Ulm, cour\u00f4"
|
||||
},
|
||||
{
|
||||
"name": "K-F\u00eat",
|
||||
"address": "45 rue d'Ulm, escalier C, niveau -1"
|
||||
},
|
||||
{
|
||||
"name": "Th\u00e9\u00e2tre",
|
||||
"address": "45 rue d'Ulm, escalier C, niveau -1"
|
||||
},
|
||||
{
|
||||
"name": "Cours Pasteur",
|
||||
"address": "45 rue d'Ulm, cours pasteur"
|
||||
},
|
||||
{
|
||||
"name": "Salle des actes",
|
||||
"address": "45 rue d'Ulm, escalier A, niveau 1"
|
||||
},
|
||||
{
|
||||
"name": "Amphi Rataud",
|
||||
"address": "45 rue d'Ulm, NIR, niveau PB"
|
||||
}
|
||||
]
|
100
bda/management/data/shows.json
Normal file
100
bda/management/data/shows.json
Normal file
|
@ -0,0 +1,100 @@
|
|||
[
|
||||
{
|
||||
"description": "Jazz / Funk",
|
||||
"title": "Un super concert",
|
||||
"price": 10.0,
|
||||
"slots_description": "Debout",
|
||||
"slots": 5
|
||||
},
|
||||
{
|
||||
"description": "Homemade",
|
||||
"title": "Une super pi\u00e8ce",
|
||||
"price": 10.0,
|
||||
"slots_description": "Assises",
|
||||
"slots": 60
|
||||
},
|
||||
{
|
||||
"description": "Plein air, soleil, bonne musique",
|
||||
"title": "Concert pour la f\u00eate de la musique",
|
||||
"price": 5.0,
|
||||
"slots_description": "Debout, attention \u00e0 la fontaine",
|
||||
"slots": 30
|
||||
},
|
||||
{
|
||||
"description": "Sous le regard s\u00e9v\u00e8re de Louis Pasteur",
|
||||
"title": "Op\u00e9ra sans d\u00e9cors",
|
||||
"price": 5.0,
|
||||
"slots_description": "Assis sur l'herbe",
|
||||
"slots": 20
|
||||
},
|
||||
{
|
||||
"description": "Buffet \u00e0 la fin",
|
||||
"title": "Concert Trouv\u00e8re",
|
||||
"price": 20.0,
|
||||
"slots_description": "Assises",
|
||||
"slots": 15
|
||||
},
|
||||
{
|
||||
"description": "Vive les maths",
|
||||
"title": "Dessin \u00e0 la craie sur tableau noir",
|
||||
"price": 10.0,
|
||||
"slots_description": "Assises, tablette pour prendre des notes",
|
||||
"slots": 30
|
||||
},
|
||||
{
|
||||
"description": "Une pi\u00e8ce \u00e0 un personnage",
|
||||
"title": "D\u00e9cors, d\u00e9montage en musique",
|
||||
"price": 0.0,
|
||||
"slots_description": "Assises",
|
||||
"slots": 20
|
||||
},
|
||||
{
|
||||
"description": "Annulera, annulera pas\u00a0?",
|
||||
"title": "La Nuit",
|
||||
"price": 27.0,
|
||||
"slots_description": "",
|
||||
"slots": 1000
|
||||
},
|
||||
{
|
||||
"description": "Le boum fait sa carte blanche",
|
||||
"title": "Turbomix",
|
||||
"price": 10.0,
|
||||
"slots_description": "Debout les mains en l'air",
|
||||
"slots": 20
|
||||
},
|
||||
{
|
||||
"description": "Unique repr\u00e9sentation",
|
||||
"title": "Carinettes et trombone",
|
||||
"price": 15.0,
|
||||
"slots_description": "Chaises ikea",
|
||||
"slots": 10
|
||||
},
|
||||
{
|
||||
"description": "Suivi d'une jam session",
|
||||
"title": "Percussion sur rondins",
|
||||
"price": 5.0,
|
||||
"slots_description": "B\u00fbches",
|
||||
"slots": 14
|
||||
},
|
||||
{
|
||||
"description": "\u00c9preuve sportive et artistique",
|
||||
"title": "Bassin aux ernests, nage libre",
|
||||
"price": 5.0,
|
||||
"slots_description": "Humides",
|
||||
"slots": 10
|
||||
},
|
||||
{
|
||||
"description": "Sonore",
|
||||
"title": "Chant du barde",
|
||||
"price": 13.0,
|
||||
"slots_description": "Ne venez pas",
|
||||
"slots": 20
|
||||
},
|
||||
{
|
||||
"description": "Cocorico",
|
||||
"title": "Chant du coq",
|
||||
"price": 4.0,
|
||||
"slots_description": "bancs",
|
||||
"slots": 15
|
||||
}
|
||||
]
|
22
bda/migrations/0011_tirage_appear_catalogue.py
Normal file
22
bda/migrations/0011_tirage_appear_catalogue.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0010_spectaclerevente_shotgun'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='tirage',
|
||||
name='appear_catalogue',
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='Tirage à afficher dans le catalogue'
|
||||
),
|
||||
),
|
||||
]
|
182
bda/models.py
182
bda/models.py
|
@ -1,30 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import calendar
|
||||
import random
|
||||
from datetime import timedelta
|
||||
from custommail.shortcuts import send_mass_custom_mail
|
||||
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.template import loader
|
||||
from django.core import mail
|
||||
from django.conf import settings
|
||||
from django.utils import timezone, formats
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Tirage(models.Model):
|
||||
title = models.CharField("Titre", max_length=300)
|
||||
ouverture = models.DateTimeField("Date et heure d'ouverture du tirage")
|
||||
fermeture = models.DateTimeField("Date et heure de fermerture du tirage")
|
||||
tokens = models.TextField("Graine(s) du tirage", blank=True)
|
||||
active = models.BooleanField("Tirage actif", default=False)
|
||||
appear_catalogue = models.BooleanField("Tirage à afficher dans le catalogue", default=False)
|
||||
enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
|
||||
default=False)
|
||||
|
||||
|
@ -33,7 +27,6 @@ class Tirage(models.Model):
|
|||
timezone.template_localtime(self.fermeture)))
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Salle(models.Model):
|
||||
name = models.CharField("Nom", max_length=300)
|
||||
address = models.TextField("Adresse")
|
||||
|
@ -42,7 +35,6 @@ class Salle(models.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CategorieSpectacle(models.Model):
|
||||
name = models.CharField('Nom', max_length=100, unique=True)
|
||||
|
||||
|
@ -53,7 +45,6 @@ class CategorieSpectacle(models.Model):
|
|||
verbose_name = "Catégorie"
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Spectacle(models.Model):
|
||||
title = models.CharField("Titre", max_length=300)
|
||||
category = models.ForeignKey(CategorieSpectacle, blank=True, null=True)
|
||||
|
@ -88,6 +79,15 @@ class Spectacle(models.Model):
|
|||
self.price
|
||||
)
|
||||
|
||||
def getImgUrl(self):
|
||||
"""
|
||||
Cette fonction permet d'obtenir l'URL de l'image, si elle existe
|
||||
"""
|
||||
try:
|
||||
return self.image.url
|
||||
except:
|
||||
return None
|
||||
|
||||
def send_rappel(self):
|
||||
"""
|
||||
Envoie un mail de rappel à toutes les personnes qui ont une place pour
|
||||
|
@ -100,33 +100,30 @@ class Spectacle(models.Model):
|
|||
if member.id in members:
|
||||
members[member.id][1] = 2
|
||||
else:
|
||||
members[member.id] = [member.first_name, 1, member.email]
|
||||
# Pour le BdA
|
||||
members[0] = ['BdA', 1, 'bda@ens.fr']
|
||||
members[-1] = ['BdA', 2, 'bda@ens.fr']
|
||||
members[member.id] = [member, 1]
|
||||
# FIXME : faire quelque chose de ça, un utilisateur bda_generic ?
|
||||
# # Pour le BdA
|
||||
# members[0] = ['BdA', 1, 'bda@ens.fr']
|
||||
# members[-1] = ['BdA', 2, 'bda@ens.fr']
|
||||
# On écrit un mail personnalisé à chaque participant
|
||||
mails_to_send = []
|
||||
mail_object = str(self)
|
||||
for member in members.values():
|
||||
mail_body = loader.render_to_string('bda/mails/rappel.txt', {
|
||||
'name': member[0],
|
||||
'nb_attr': member[1],
|
||||
'show': self})
|
||||
mail_tot = mail.EmailMessage(
|
||||
mail_object, mail_body,
|
||||
settings.MAIL_DATA['rappels']['FROM'], [member[2]],
|
||||
[], headers={
|
||||
'Reply-To': settings.MAIL_DATA['rappels']['REPLYTO']})
|
||||
mails_to_send.append(mail_tot)
|
||||
# On envoie les mails
|
||||
connection = mail.get_connection()
|
||||
connection.send_messages(mails_to_send)
|
||||
datatuple = [(
|
||||
'bda-rappel',
|
||||
{'member': member[0], 'nb_attr': member[1], 'show': self},
|
||||
settings.MAIL_DATA['rappels']['FROM'],
|
||||
[member[0].email])
|
||||
for member in members.values()
|
||||
]
|
||||
send_mass_custom_mail(datatuple)
|
||||
# On enregistre le fait que l'envoi a bien eu lieu
|
||||
self.rappel_sent = timezone.now()
|
||||
self.save()
|
||||
# On renvoie la liste des destinataires
|
||||
return members.values()
|
||||
|
||||
@property
|
||||
def is_past(self):
|
||||
return self.date < timezone.now()
|
||||
|
||||
|
||||
class Quote(models.Model):
|
||||
spectacle = models.ForeignKey(Spectacle)
|
||||
|
@ -142,7 +139,6 @@ PAYMENT_TYPES = (
|
|||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Participant(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
choices = models.ManyToManyField(Spectacle,
|
||||
|
@ -170,7 +166,6 @@ DOUBLE_CHOICES = (
|
|||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ChoixSpectacle(models.Model):
|
||||
participant = models.ForeignKey(Participant)
|
||||
spectacle = models.ForeignKey(Spectacle, related_name="participants")
|
||||
|
@ -189,7 +184,7 @@ class ChoixSpectacle(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return "Vœux de %s pour %s" % (
|
||||
self.participant.user.get_full_name,
|
||||
self.participant.user.get_full_name(),
|
||||
self.spectacle.title)
|
||||
|
||||
class Meta:
|
||||
|
@ -199,7 +194,6 @@ class ChoixSpectacle(models.Model):
|
|||
verbose_name_plural = "voeux"
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Attribution(models.Model):
|
||||
participant = models.ForeignKey(Participant)
|
||||
spectacle = models.ForeignKey(Spectacle, related_name="attribues")
|
||||
|
@ -210,7 +204,6 @@ class Attribution(models.Model):
|
|||
self.spectacle.date)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class SpectacleRevente(models.Model):
|
||||
attribution = models.OneToOneField(Attribution,
|
||||
related_name="revente")
|
||||
|
@ -251,26 +244,24 @@ class SpectacleRevente(models.Model):
|
|||
verbose_name = "Revente"
|
||||
|
||||
def send_notif(self):
|
||||
"""
|
||||
Envoie une notification pour indiquer la mise en vente d'une place sur
|
||||
BdA-Revente à tous les intéressés.
|
||||
"""
|
||||
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,
|
||||
datatuple = [(
|
||||
'bda-revente',
|
||||
{
|
||||
'member': participant.user,
|
||||
'show': self.attribution.spectacle,
|
||||
'revente': self,
|
||||
'domain': Site.objects.get_current().domain})
|
||||
mail_tot = mail.EmailMessage(
|
||||
mail_object, mail_body,
|
||||
'site': Site.objects.get_current()
|
||||
},
|
||||
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)
|
||||
[participant.user.email])
|
||||
for participant in inscrits
|
||||
]
|
||||
send_mass_custom_mail(datatuple)
|
||||
self.notif_sent = True
|
||||
self.save()
|
||||
|
||||
|
@ -280,25 +271,18 @@ class SpectacleRevente(models.Model):
|
|||
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,
|
||||
datatuple = [(
|
||||
'bda-shotgun',
|
||||
{
|
||||
'member': participant.user,
|
||||
'show': self.attribution.spectacle,
|
||||
'site': Site.objects.get_current(),
|
||||
},
|
||||
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)
|
||||
[participant.user.email])
|
||||
for participant in inscrits
|
||||
]
|
||||
send_mass_custom_mail(datatuple)
|
||||
self.notif_sent = True
|
||||
# Flag inutile, sauf si l'horloge interne merde
|
||||
self.tirage_done = True
|
||||
|
@ -316,56 +300,42 @@ class SpectacleRevente(models.Model):
|
|||
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
|
||||
datatuple = []
|
||||
context = {
|
||||
'acheteur': winner.user,
|
||||
'vendeur': seller.user,
|
||||
'spectacle': spectacle,
|
||||
'show': 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],
|
||||
datatuple.append((
|
||||
'bda-revente-winner',
|
||||
context,
|
||||
settings.MAIL_DATA['revente']['FROM'],
|
||||
[winner.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],
|
||||
datatuple.append((
|
||||
'bda-revente-seller',
|
||||
context,
|
||||
settings.MAIL_DATA['revente']['FROM'],
|
||||
[seller.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']],
|
||||
if inscrit != winner:
|
||||
new_context = dict(context)
|
||||
new_context['acheteur'] = inscrit.user
|
||||
datatuple.append((
|
||||
'bda-revente-loser',
|
||||
new_context,
|
||||
settings.MAIL_DATA['revente']['FROM'],
|
||||
[inscrit.user.email]
|
||||
))
|
||||
mail.get_connection().send_messages(mails)
|
||||
|
||||
send_mass_custom_mail(datatuple)
|
||||
# Si personne ne veut de la place, elle part au shotgun
|
||||
else:
|
||||
self.shotgun = True
|
||||
|
||||
self.tirage_done = True
|
||||
self.save()
|
||||
|
|
|
@ -43,3 +43,6 @@ td {
|
|||
margin: 10px 0px;
|
||||
}
|
||||
|
||||
.spectacle-passe {
|
||||
opacity:0.5;
|
||||
}
|
||||
|
|
|
@ -47,9 +47,6 @@
|
|||
{% for participant in participants %}{{participant.name}} : {{participant.nb_places}} places
|
||||
{% endfor %}
|
||||
</pre>
|
||||
|
||||
<script type="text/javascript"
|
||||
src="{% static "js/jquery.min.js" %}"></script>
|
||||
<script type="text/javascript"
|
||||
src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}"></script>
|
||||
<script>
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block realcontent %}
|
||||
|
||||
<h2>Revente de place</h2>
|
||||
<h3>Places non revendues</h3>
|
||||
<form class="form-horizontal" action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<div class="multiple-checkbox">
|
||||
<ul>
|
||||
{% for box in resellform.attributions %}
|
||||
<li>
|
||||
{{box.tag}}
|
||||
{{box.choice_label}}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
|
||||
</div>
|
||||
</form>
|
||||
<br>
|
||||
{% if annulform.attributions or overdue %}
|
||||
<h3>Places en cours de revente</h3>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<div class="multiple-checkbox">
|
||||
<ul>
|
||||
{% for box in annulform.attributions %}
|
||||
<li>
|
||||
{{box.tag}}
|
||||
{{box.choice_label}}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% for attrib in overdue %}
|
||||
<li>
|
||||
<input type="checkbox" style="visibility:hidden">
|
||||
{{attrib.spectacle}}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% if annulform.attributions %}
|
||||
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endif %}
|
||||
<br>
|
||||
{% if sold %}
|
||||
<h3>Places revendues</h3>
|
||||
<table class="table">
|
||||
{% for attrib in sold %}
|
||||
<tr>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<td>{{attrib.spectacle}}</td>
|
||||
<td>{{attrib.revente.soldTo.user.get_full_name}}</td>
|
||||
<td><button type="submit" class="btn btn-primary" name="transfer"
|
||||
value="{{attrib.revente.id}}">Transférer</button></td>
|
||||
<td><button type="submit" class="btn btn-primary" name="reinit"
|
||||
value="{{attrib.revente.id}}">Réinitialiser</button></td>
|
||||
</form>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -38,11 +38,13 @@
|
|||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<span class="bda-prix">Total : {{ total }} demandes</span>
|
||||
<span class="bda-prix">
|
||||
Total : {{ total }} place{{ total|pluralize }} demandée{{ total|pluralize }}
|
||||
sur {{ proposed }} place{{ proposed|pluralize }} proposée{{ proposed|pluralize }}
|
||||
</span>
|
||||
<script type="text/javascript"
|
||||
src="{% static "js/jquery.min.js" %}"></script>
|
||||
<script type="text/javascript"
|
||||
src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}"></script>
|
||||
src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("table.etat-bda").stupidtable();
|
|
@ -2,11 +2,9 @@
|
|||
{% load staticfiles %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link href="{{ STATIC_URL }}grappelli/jquery/ui/css/custom-theme/jquery-ui-1.8.custom.css" rel="stylesheet" type="text/css" media="screen" title="no title" charset="utf-8" />
|
||||
<script src="{{ STATIC_URL }}grappelli/jquery/jquery-1.6.2.min.js" type="text/javascript"></script>
|
||||
<script src="{{ STATIC_URL }}grappelli/jquery/ui/js/jquery-ui-1.8.15.custom.min.js" type="text/javascript"></script>
|
||||
<link href="{{ STATIC_URL }}grappelli/css/tools.css" rel="stylesheet" type="text/css" />
|
||||
<link href="{{ STATIC_URL }}grappelli/css/jquery-ui-grappelli-extensions.css" rel="stylesheet" type="text/css" />
|
||||
<script src="{% static 'js/jquery-ui.min.js' %}" type="text/javascript"></script>
|
||||
<script src="{% static "js/jquery.ui.touch-punch.min.js" %}" type="text/javascript"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/jquery-ui.min.css" %}" />
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
|
||||
{% endblock %}
|
||||
|
||||
|
@ -94,15 +92,9 @@ var django = {
|
|||
</script>
|
||||
|
||||
<h2 class="no-bottom-margin">Inscription au tirage au sort du BdA</h2>
|
||||
{% if success %}
|
||||
<p class="success table-top">Votre inscription a été mise à jour avec succès !</p>
|
||||
{% endif %}
|
||||
{% if stateerror %}
|
||||
<p class="error table-top">Impossible d'enregistrer vos modifications: vous avez apporté d'autres modifications entre temps</p>
|
||||
{% endif %}
|
||||
<form class="form-horizontal" id="bda_form" method="post" action="{% url 'bda-tirage-inscription' tirage.id %}">
|
||||
{% csrf_token %}
|
||||
{% include "inscription-formset.html" %}
|
||||
{% include "bda/inscription-formset.html" %}
|
||||
<div class="inscription-bottom">
|
||||
<span class="bda-prix">Prix total actuel : {{ total_price }}€</span>
|
||||
<div class="pull-right">
|
||||
|
@ -116,7 +108,7 @@ var django = {
|
|||
<input type="submit" class="btn btn-primary" value="Enregistrer" />
|
||||
</div>
|
||||
<p class="footnotes">
|
||||
<sup>1</sup>: cette liste de vœu est ordonnée (du plus important au moins important), pour ajuster la priorité vous pouvez déplacer chaque vœu.<br />
|
||||
<sup>1</sup>: cette liste de vœux est ordonnée (du plus important au moins important), pour ajuster la priorité vous pouvez déplacer chaque vœu.<br />
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
|
@ -3,15 +3,6 @@
|
|||
|
||||
{% 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>
|
||||
{% elif inscrit_revente %}
|
||||
<p class="success">Tu as été inscrit à une revente en cours pour ce spectacle !</p>
|
||||
{% endif %}
|
||||
|
||||
<form action="" class="form-horizontal" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
43
bda/templates/bda/mails-rappel.html
Normal file
43
bda/templates/bda/mails-rappel.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Mails de rappels</h2>
|
||||
{% if sent %}
|
||||
<h3>Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes</h3>
|
||||
<ul>
|
||||
{% for member in members %}
|
||||
<li>{{ member.get_full_name }} ({{ member.email }})</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<h3>Voulez vous envoyer les mails de rappel pour le spectacle
|
||||
{{ show.title }} ?</h3>
|
||||
{% if show.rappel_sent %}
|
||||
<p class="error">Attention, les mails ont déjà été envoyés le
|
||||
{{ show.rappel_sent }}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if not sent %}
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="pull-right">
|
||||
<input class="btn btn-primary" type="submit" value="Envoyer" />
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<br/>
|
||||
<hr/>
|
||||
<h3>Forme des mails</h3>
|
||||
|
||||
<h4>Une seule place</h4>
|
||||
{% for part in exemple_mail_1place %}
|
||||
<pre>{{ part }}</pre>
|
||||
{% endfor %}
|
||||
|
||||
<h4>Deux places</h4>
|
||||
{% for part in exemple_mail_2places %}
|
||||
<pre>{{ part }}</pre>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
|
@ -1,6 +0,0 @@
|
|||
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,23 +0,0 @@
|
|||
Bonjour {{ name }},
|
||||
|
||||
Nous te rappellons que tu as eu la chance d'obtenir {{ nb_attr|pluralize:"une place,deux places" }}
|
||||
pour {{ show.title }}, le {{ show.date }} au {{ show.location }}. N'oublie pas de t'y rendre !
|
||||
{% if nb_attr == 2 %}
|
||||
Tu as obtenu deux places pour ce spectacle. Nous te rappelons que
|
||||
ces places sont strictement réservées aux personnes de moins de 28 ans.
|
||||
{% endif %}
|
||||
{% if show.listing %}Pour ce spectacle, tu as reçu des places sur
|
||||
listing. Il te faudra donc te rendre 15 minutes en avance sur les lieux de la représentation
|
||||
pour retirer {{ nb_attr|pluralize:"ta place,tes places" }}.
|
||||
{% else %}Pour assister à ce spectacle, tu dois présenter les billets qui ont
|
||||
été distribués au burô.
|
||||
{% endif %}
|
||||
|
||||
Si tu ne peux plus assister à cette représentation, tu peux
|
||||
revendre ta place via BdA-revente, accessible directement sur
|
||||
GestioCOF (lien "revendre une place du premier tirage" sur la page
|
||||
d'accueil https://www.cof.ens.fr/gestion/).
|
||||
|
||||
En te souhaitant un excellent spectacle,
|
||||
|
||||
Le Bureau des Arts
|
|
@ -1,9 +0,0 @@
|
|||
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
|
|
@ -1,13 +0,0 @@
|
|||
Bonjour {{ vendeur.first_name }},
|
||||
|
||||
Tu t’es bien inscrit-e pour la revente de {{ spectacle.title }}.
|
||||
|
||||
{% with revente.date_tirage 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
|
|
@ -1,7 +0,0 @@
|
|||
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
|
|
@ -1,7 +0,0 @@
|
|||
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
|
|
@ -1,14 +0,0 @@
|
|||
Bonjour {{ user.first_name }}
|
||||
|
||||
Une place pour le spectacle {{ spectacle.title }} ({{ spectacle.date }})
|
||||
a été postée sur BdA-Revente.
|
||||
|
||||
{% with revente.date_tirage as time %}
|
||||
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 {{ time|date:"DATE_FORMAT" }} à {{ time|time:"TIME_FORMAT" }} (dans {{time|timeuntil}}).
|
||||
{% endwith %}
|
||||
|
||||
Chaleureusement,
|
||||
Le BdA
|
|
@ -1,11 +0,0 @@
|
|||
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
|
|
@ -1,8 +1,6 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h1><strong>{{ error_title }}</strong></h1>
|
||||
<p>{{ error_description }}</p>
|
||||
{% if choices %}
|
||||
<h3>Vos vœux:</h3>
|
||||
<ol>
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
{% block realcontent %}
|
||||
<h2><strong>Places attribuées</strong></h3>
|
||||
{% if warning %}
|
||||
<h3 class="error">Attention, vous avez reçu plusieurs places pour des spectacles différents à la même date !</h3>
|
||||
{% endif %}
|
||||
{% if places %}
|
||||
<table class="table table-striped">
|
||||
{% for place in places %}
|
53
bda/templates/bda/reventes.html
Normal file
53
bda/templates/bda/reventes.html
Normal file
|
@ -0,0 +1,53 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block realcontent %}
|
||||
|
||||
<h2>Revente de place</h2>
|
||||
{% if resellform.attributions %}
|
||||
<h3>Places non revendues</h3>
|
||||
<form class="form-horizontal" action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{resellform|bootstrap}}
|
||||
<div class="form-actions">
|
||||
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
<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 attrib in annulform.attributions %}
|
||||
<li>{{attrib.tag}} {{attrib.choice_label}}</li>
|
||||
{% endfor %}
|
||||
{% for attrib in overdue %}
|
||||
<li>
|
||||
<input type="checkbox" style="visibility:hidden">
|
||||
{{attrib.spectacle}}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if annulform.attributions %}
|
||||
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endif %}
|
||||
<br>
|
||||
{% if soldform.attributions %}
|
||||
<h3>Places revendues</h3>
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{soldform|bootstrap}}
|
||||
<button type="submit" class="btn btn-primary" name="transfer">Transférer</button>
|
||||
<button type="submit" class="btn btn-primary" name="reinit">Réinitialiser</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if not resellform.attributions and not soldform.attributions and not overdue and not annulform.attributions %}
|
||||
<p>Plus de reventes possibles !</p>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -1,35 +0,0 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
{% if sent %}
|
||||
<h3>Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes</h3>
|
||||
<ul>
|
||||
{% for member in members %}
|
||||
<li>{{ member.get_full_name }} ({{ member.email }})</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<h3>Voulez vous envoyer les mails de rappel pour le spectacle
|
||||
{{ show.title }} ?</h3>
|
||||
{% if show.rappel_sent %}
|
||||
<p class="error">Attention, les mails ont déjà été envoyés le
|
||||
{{ show.rappel_sent }}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if not sent %}
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
<br />
|
||||
<input type="submit" value="Envoyer" />
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<h3>Forme des mails</h3>
|
||||
|
||||
<br />Une seule place<br /><br />
|
||||
<pre>{{ exemple_mail_1place }}</pre>
|
||||
|
||||
<br />Deux places<br /><br />
|
||||
<pre>{{ exemple_mail_2places }}</pre>
|
||||
{% endblock %}
|
|
@ -1,6 +1,10 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/bda.css" %}" />
|
||||
{% endblock %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2><strong>{{tirage_name}}</strong></h2>
|
||||
<h3>Liste des spectacles</h3>
|
||||
|
@ -17,9 +21,9 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
{% for spectacle in object_list %}
|
||||
<tr class="clickable-row" data-href="{% url 'bda-spectacle' tirage_id spectacle.id %}">
|
||||
<tr class="clickable-row {% if spectacle.is_past %}spectacle-passe{% endif %}" data-href="{% url 'bda-spectacle' tirage_id spectacle.id %}">
|
||||
<td><a href="{% url 'bda-spectacle' tirage_id spectacle.id %}">{{ spectacle.title }} <span style="font-size:small;" class="glyphicon glyphicon-link" aria-hidden="true"></span></a></td>
|
||||
<td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date }}</td>
|
||||
<td data-sort-value="{{ spectacle.timestamp }}"">{{ spectacle.date }}</td>
|
||||
<td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td>
|
||||
<td data-sort-value="{{ spectacle.price |stringformat:".3f" }}">
|
||||
{{ spectacle.price |floatformat }}€
|
||||
|
@ -29,9 +33,8 @@
|
|||
</tbody>
|
||||
</table>
|
||||
<script type="text/javascript"
|
||||
src="{% static "js/jquery.min.js" %}"></script>
|
||||
<script type="text/javascript"
|
||||
src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}"></script>
|
||||
src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
$(function(){
|
||||
$("table.etat-bda").stupidtable();
|
||||
|
|
|
@ -47,4 +47,6 @@ urlpatterns = [
|
|||
url(r'^mails-rappel/(?P<spectacle_id>\d+)$', views.send_rappel),
|
||||
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
||||
name='bda-descriptions'),
|
||||
url(r'^catalogue/(?P<request_type>[a-z]+)$', views.catalogue,
|
||||
name='bda-catalogue'),
|
||||
]
|
||||
|
|
468
bda/views.py
468
bda/views.py
|
@ -1,41 +1,49 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import random
|
||||
|
||||
import hashlib
|
||||
import time
|
||||
import json
|
||||
from datetime import timedelta
|
||||
from custommail.shortcuts import (
|
||||
send_mass_custom_mail, send_custom_mail, render_custom_mail
|
||||
)
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Count, Q
|
||||
from django.core import serializers, mail
|
||||
from django.core import serializers
|
||||
from django.db.models import Count, Q, Sum
|
||||
from django.forms.models import inlineformset_factory
|
||||
from django.http import HttpResponseBadRequest, HttpResponseRedirect
|
||||
from django.http import (
|
||||
HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
|
||||
)
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
import hashlib
|
||||
|
||||
from django.core.mail import send_mail
|
||||
from django.template import loader
|
||||
from django.utils import timezone
|
||||
from django.utils import timezone, formats
|
||||
from django.views.generic.list import ListView
|
||||
|
||||
import time
|
||||
from datetime import timedelta
|
||||
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from gestioncof.decorators import cof_required, buro_required
|
||||
from bda.models import Spectacle, Participant, ChoixSpectacle, Attribution,\
|
||||
Tirage, SpectacleRevente
|
||||
from bda.models import (
|
||||
Spectacle, Participant, ChoixSpectacle, Attribution, Tirage,
|
||||
SpectacleRevente, Salle, Quote, CategorieSpectacle
|
||||
)
|
||||
from bda.algorithm import Algorithm
|
||||
|
||||
from bda.forms import BaseBdaFormSet, TokenForm, ResellForm, AnnulForm,\
|
||||
InscriptionReventeForm
|
||||
from bda.forms import (
|
||||
BaseBdaFormSet, TokenForm, ResellForm, AnnulForm, InscriptionReventeForm,
|
||||
SoldForm
|
||||
)
|
||||
|
||||
|
||||
@cof_required
|
||||
def etat_places(request, tirage_id):
|
||||
"""
|
||||
Résumé des spectacles d'un tirage avec pour chaque spectacle :
|
||||
- Le nombre de places en jeu
|
||||
- Le nombre de demandes
|
||||
- Le ratio demandes/places
|
||||
Et le total de toutes les demandes
|
||||
"""
|
||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||
spectacles1 = ChoixSpectacle.objects \
|
||||
.filter(spectacle__tirage=tirage) \
|
||||
|
@ -67,9 +75,14 @@ def etat_places(request, tirage_id):
|
|||
spectacles_dict[spectacle["spectacle"]].ratio = \
|
||||
spectacles_dict[spectacle["spectacle"]].total / \
|
||||
spectacles_dict[spectacle["spectacle"]].slots
|
||||
total += spectacle["total"]
|
||||
return render(request, "etat-places.html",
|
||||
{"spectacles": spectacles, "total": total, 'tirage': tirage})
|
||||
total += 2*spectacle["total"]
|
||||
context = {
|
||||
"proposed": tirage.spectacle_set.aggregate(Sum('slots'))['slots__sum'],
|
||||
"spectacles": spectacles,
|
||||
"total": total,
|
||||
'tirage': tirage
|
||||
}
|
||||
return render(request, "bda/etat-places.html", context)
|
||||
|
||||
|
||||
def _hash_queryset(queryset):
|
||||
|
@ -105,33 +118,50 @@ def places(request, tirage_id):
|
|||
warning = True
|
||||
else:
|
||||
dates.append(date)
|
||||
return render(request, "resume_places.html",
|
||||
# On prévient l'utilisateur s'il a deux places à la même date
|
||||
if warning:
|
||||
messages.warning(request, "Attention, vous avez reçu des places pour "
|
||||
"des spectacles différents à la même date.")
|
||||
return render(request, "bda/resume_places.html",
|
||||
{"participant": participant,
|
||||
"places": filtered_places,
|
||||
"tirage": tirage,
|
||||
"total": total,
|
||||
"warning": warning})
|
||||
"total": total})
|
||||
|
||||
|
||||
@cof_required
|
||||
def inscription(request, tirage_id):
|
||||
"""
|
||||
Vue d'inscription à un tirage BdA.
|
||||
- On vérifie qu'on se situe bien entre la date d'ouverture et la date de
|
||||
fermeture des inscriptions.
|
||||
- On vérifie que l'inscription n'a pas été modifiée entre le moment où le
|
||||
client demande le formulaire et le moment où il soumet son inscription
|
||||
(autre session par exemple).
|
||||
"""
|
||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||
if timezone.now() < tirage.ouverture:
|
||||
error_desc = tirage.ouverture.strftime('Ouverture le %d %b %Y à %H:%M')
|
||||
return render(request, 'resume_inscription.html',
|
||||
{"error_title": "Le tirage n'est pas encore ouvert !",
|
||||
"error_description": error_desc})
|
||||
# Le tirage n'est pas encore ouvert.
|
||||
opening = formats.localize(
|
||||
timezone.template_localtime(tirage.ouverture))
|
||||
messages.error(request, "Le tirage n'est pas encore ouvert : "
|
||||
"ouverture le {:s}".format(opening))
|
||||
return render(request, 'bda/resume-inscription-tirage.html', {})
|
||||
if timezone.now() > tirage.fermeture:
|
||||
# Le tirage est fermé.
|
||||
participant, created = Participant.objects.get_or_create(
|
||||
user=request.user, tirage=tirage)
|
||||
choices = participant.choixspectacle_set.order_by("priority").all()
|
||||
return render(request, "resume_inscription.html",
|
||||
{"error_title": "C'est fini !",
|
||||
"error_description":
|
||||
"Tirage au sort dans la journée !",
|
||||
"choices": choices})
|
||||
messages.error(request,
|
||||
" C'est fini : tirage au sort dans la journée !")
|
||||
return render(request, "bda/resume-inscription-tirage.html",
|
||||
{"choices": choices})
|
||||
|
||||
def formfield_callback(f, **kwargs):
|
||||
"""
|
||||
Fonction utilisée par inlineformset_factory ci dessous.
|
||||
Restreint les spectacles proposés aux spectacles du bo tirage.
|
||||
"""
|
||||
if f.name == "spectacle":
|
||||
kwargs['queryset'] = tirage.spectacle_set
|
||||
return f.formfield(**kwargs)
|
||||
|
@ -164,58 +194,70 @@ def inscription(request, tirage_id):
|
|||
total_price += choice.spectacle.price
|
||||
if choice.double:
|
||||
total_price += choice.spectacle.price
|
||||
return render(request, "inscription-bda.html",
|
||||
# Messages
|
||||
if success:
|
||||
messages.success(request, "Votre inscription a été mise à jour avec "
|
||||
"succès !")
|
||||
if stateerror:
|
||||
messages.error(request, "Impossible d'enregistrer vos modifications "
|
||||
": vous avez apporté d'autres modifications "
|
||||
"entre temps.")
|
||||
return render(request, "bda/inscription-tirage.html",
|
||||
{"formset": formset,
|
||||
"success": success,
|
||||
"total_price": total_price,
|
||||
"dbstate": dbstate,
|
||||
'tirage': tirage,
|
||||
"stateerror": stateerror})
|
||||
'tirage': tirage})
|
||||
|
||||
|
||||
def do_tirage(request, tirage_id):
|
||||
tirage_elt = get_object_or_404(Tirage, id=tirage_id)
|
||||
form = TokenForm(request.POST)
|
||||
if not form.is_valid():
|
||||
return tirage(request, tirage_id)
|
||||
def do_tirage(tirage_elt, token):
|
||||
"""
|
||||
Fonction auxiliaire à la vue ``tirage`` qui lance effectivement le tirage
|
||||
après qu'on a vérifié que c'est légitime et que le token donné en argument
|
||||
est correct.
|
||||
Rend les résultats
|
||||
"""
|
||||
# Initialisation du dictionnaire data qui va contenir les résultats
|
||||
start = time.time()
|
||||
data = {}
|
||||
shows = tirage_elt.spectacle_set.select_related().all()
|
||||
members = tirage_elt.participant_set.all()
|
||||
choices = ChoixSpectacle.objects.filter(spectacle__tirage=tirage_elt) \
|
||||
.order_by('participant', 'priority').select_related().all()
|
||||
algo = Algorithm(shows, members, choices)
|
||||
results = algo(form.cleaned_data["token"])
|
||||
total_slots = 0
|
||||
total_losers = 0
|
||||
data = {
|
||||
'shows': tirage_elt.spectacle_set.select_related().all(),
|
||||
'token': token,
|
||||
'members': tirage_elt.participant_set.all(),
|
||||
'total_slots': 0,
|
||||
'total_losers': 0,
|
||||
'total_sold': 0,
|
||||
'total_deficit': 0,
|
||||
'opera_deficit': 0,
|
||||
}
|
||||
|
||||
# On lance le tirage
|
||||
choices = (
|
||||
ChoixSpectacle.objects
|
||||
.filter(spectacle__tirage=tirage_elt)
|
||||
.order_by('participant', 'priority')
|
||||
.select_related().all()
|
||||
)
|
||||
results = Algorithm(data['shows'], data['members'], choices)(token)
|
||||
|
||||
# On compte les places attribuées et les déçus
|
||||
for (_, members, losers) in results:
|
||||
total_slots += len(members)
|
||||
total_losers += len(losers)
|
||||
data["total_slots"] = total_slots
|
||||
data["total_losers"] = total_losers
|
||||
data["shows"] = shows
|
||||
data["token"] = form.cleaned_data["token"]
|
||||
data["members"] = members
|
||||
data["results"] = results
|
||||
total_sold = 0
|
||||
total_deficit = 0
|
||||
opera_deficit = 0
|
||||
data['total_slots'] += len(members)
|
||||
data['total_losers'] += len(losers)
|
||||
|
||||
# On calcule le déficit et les bénéfices pour le BdA
|
||||
# FIXME: le traitement de l'opéra est sale
|
||||
for (show, members, _) in results:
|
||||
deficit = (show.slots - len(members)) * show.price
|
||||
total_sold += show.slots * show.price
|
||||
data['total_sold'] += show.slots * show.price
|
||||
if deficit >= 0:
|
||||
if "Opéra" in show.location.name:
|
||||
opera_deficit += deficit
|
||||
total_deficit += deficit
|
||||
data["total_sold"] = total_sold - total_deficit
|
||||
data["total_deficit"] = total_deficit
|
||||
data["opera_deficit"] = opera_deficit
|
||||
data["duration"] = time.time() - start
|
||||
if request.user.is_authenticated():
|
||||
members2 = {}
|
||||
data['opera_deficit'] += deficit
|
||||
data['total_deficit'] += deficit
|
||||
data["total_sold"] -= data['total_deficit']
|
||||
|
||||
# Participant objects are not shared accross spectacle results,
|
||||
# so assign a single object for each Participant id
|
||||
members_uniq = {}
|
||||
members2 = {}
|
||||
for (show, members, _) in results:
|
||||
for (member, _, _, _) in members:
|
||||
if member.id not in members_uniq:
|
||||
|
@ -227,25 +269,36 @@ def do_tirage(request, tirage_id):
|
|||
member.total += show.price
|
||||
members2 = members2.items()
|
||||
data["members2"] = sorted(members2, key=lambda m: m[0].user.last_name)
|
||||
|
||||
# ---
|
||||
# À partir d'ici, le tirage devient effectif
|
||||
# ---
|
||||
|
||||
# On suppression les vieilles attributions, on sauvegarde le token et on
|
||||
# désactive le tirage
|
||||
Attribution.objects.filter(spectacle__tirage=tirage_elt).delete()
|
||||
tirage_elt.tokens += "%s\n\"\"\"%s\"\"\"\n" % (
|
||||
tirage_elt.tokens += '{:s}\n"""{:s}"""\n'.format(
|
||||
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
|
||||
form.cleaned_data['token'])
|
||||
token)
|
||||
tirage_elt.enable_do_tirage = False
|
||||
tirage_elt.save()
|
||||
|
||||
# On enregistre les nouvelles attributions
|
||||
Attribution.objects.bulk_create([
|
||||
Attribution(spectacle=show, participant=member)
|
||||
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)
|
||||
else:
|
||||
return render(request, "bda-attrib.html", data)
|
||||
|
||||
data["duration"] = time.time() - start
|
||||
data["results"] = results
|
||||
return data
|
||||
|
||||
|
||||
@buro_required
|
||||
|
@ -257,7 +310,8 @@ def tirage(request, tirage_id):
|
|||
if request.POST:
|
||||
form = TokenForm(request.POST)
|
||||
if form.is_valid():
|
||||
return do_tirage(request, tirage_id)
|
||||
results = do_tirage(tirage_elt, form.cleaned_data['token'])
|
||||
return render(request, "bda-attrib-extra.html", results)
|
||||
else:
|
||||
form = TokenForm()
|
||||
return render(request, "bda-token.html", {"form": form})
|
||||
|
@ -268,15 +322,20 @@ def revente(request, tirage_id):
|
|||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||
participant, created = Participant.objects.get_or_create(
|
||||
user=request.user, tirage=tirage)
|
||||
|
||||
if not participant.paid:
|
||||
return render(request, "bda-notpaid.html", {})
|
||||
|
||||
resellform = ResellForm(participant, prefix='resell')
|
||||
annulform = AnnulForm(participant, prefix='annul')
|
||||
soldform = SoldForm(participant, prefix='sold')
|
||||
|
||||
if request.method == 'POST':
|
||||
# On met en vente une place
|
||||
if 'resell' in request.POST:
|
||||
resellform = ResellForm(participant, request.POST, prefix='resell')
|
||||
annulform = AnnulForm(participant, prefix='annul')
|
||||
if resellform.is_valid():
|
||||
mails = []
|
||||
datatuple = []
|
||||
attributions = resellform.cleaned_data["attributions"]
|
||||
with transaction.atomic():
|
||||
for attribution in attributions:
|
||||
|
@ -291,28 +350,21 @@ def revente(request, tirage_id):
|
|||
revente.notif_sent = False
|
||||
revente.tirage_done = False
|
||||
revente.shotgun = False
|
||||
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']
|
||||
],
|
||||
context = {
|
||||
'vendeur': participant.user,
|
||||
'show': attribution.spectacle,
|
||||
'revente': revente
|
||||
}
|
||||
datatuple.append((
|
||||
'bda-revente-new', context,
|
||||
settings.MAIL_DATA['revente']['FROM'],
|
||||
[participant.user.email]
|
||||
))
|
||||
revente.save()
|
||||
mail.get_connection().send_messages(mails)
|
||||
send_mass_custom_mail(datatuple)
|
||||
# On annule une revente
|
||||
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:
|
||||
|
@ -320,30 +372,24 @@ def revente(request, tirage_id):
|
|||
# On confirme une vente en transférant la place à la personne qui a
|
||||
# gagné le tirage
|
||||
elif 'transfer' in request.POST:
|
||||
resellform = ResellForm(participant, prefix='resell')
|
||||
annulform = AnnulForm(participant, prefix='annul')
|
||||
soldform = SoldForm(participant, request.POST, prefix='sold')
|
||||
if soldform.is_valid():
|
||||
attributions = soldform.cleaned_data['attributions']
|
||||
for attribution in attributions:
|
||||
attribution.participant = attribution.revente.soldTo
|
||||
attribution.save()
|
||||
|
||||
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()
|
||||
# On annule la revente après le tirage au sort (par exemple si
|
||||
# la personne qui a gagné le tirage ne se manifeste pas). La place est
|
||||
# alors remise en vente
|
||||
elif 'reinit' in request.POST:
|
||||
resellform = ResellForm(participant, prefix='resell')
|
||||
annulform = AnnulForm(participant, prefix='annul')
|
||||
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)
|
||||
soldform = SoldForm(participant, request.POST, prefix='sold')
|
||||
if soldform.is_valid():
|
||||
attributions = soldform.cleaned_data['attributions']
|
||||
for attribution in attributions:
|
||||
if attribution.spectacle.date > timezone.now():
|
||||
revente = attribution.revente
|
||||
revente.date = timezone.now() - timedelta(minutes=65)
|
||||
revente.soldTo = None
|
||||
revente.notif_sent = False
|
||||
revente.tirage_done = False
|
||||
|
@ -352,33 +398,23 @@ def revente(request, tirage_id):
|
|||
revente.answered_mail.clear()
|
||||
revente.save()
|
||||
|
||||
else:
|
||||
resellform = ResellForm(participant, prefix='resell')
|
||||
annulform = AnnulForm(participant, prefix='annul')
|
||||
else:
|
||||
resellform = ResellForm(participant, prefix='resell')
|
||||
annulform = AnnulForm(participant, prefix='annul')
|
||||
|
||||
overdue = participant.attribution_set.filter(
|
||||
spectacle__date__gte=timezone.now(),
|
||||
revente__isnull=False,
|
||||
revente__seller=participant,
|
||||
revente__date__lte=timezone.now()-timedelta(hours=1)).filter(
|
||||
revente__notif_sent=True)\
|
||||
.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)
|
||||
|
||||
return render(request, "bda-revente.html",
|
||||
{'tirage': tirage, 'overdue': overdue, "sold": sold,
|
||||
return render(request, "bda/reventes.html",
|
||||
{'tirage': tirage, 'overdue': overdue, "soldform": soldform,
|
||||
"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(
|
||||
participant, _ = 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",
|
||||
|
@ -393,11 +429,11 @@ def revente_interested(request, revente_id):
|
|||
@login_required
|
||||
def list_revente(request, tirage_id):
|
||||
tirage = get_object_or_404(Tirage, id=tirage_id)
|
||||
participant, created = Participant.objects.get_or_create(
|
||||
participant, _ = Participant.objects.get_or_create(
|
||||
user=request.user, tirage=tirage)
|
||||
deja_revente = False
|
||||
success = False
|
||||
inscrit_revente = False
|
||||
inscrit_revente = []
|
||||
if request.method == 'POST':
|
||||
form = InscriptionReventeForm(tirage, request.POST)
|
||||
if form.is_valid():
|
||||
|
@ -424,24 +460,36 @@ def list_revente(request, tirage_id):
|
|||
if min_resell is not None:
|
||||
min_resell.answered_mail.add(participant)
|
||||
min_resell.save()
|
||||
inscrit_revente = True
|
||||
inscrit_revente.append(spectacle)
|
||||
success = True
|
||||
else:
|
||||
form = InscriptionReventeForm(
|
||||
tirage,
|
||||
initial={'spectacles': participant.choicesrevente.all()})
|
||||
initial={'spectacles': participant.choicesrevente.all()}
|
||||
)
|
||||
# Messages
|
||||
if success:
|
||||
messages.success(request, "Ton inscription a bien été prise en compte")
|
||||
if deja_revente:
|
||||
messages.info(request, "Des reventes existent déjà pour certains de "
|
||||
"ces spectacles, vérifie les places "
|
||||
"disponibles sans tirage !")
|
||||
if inscrit_revente:
|
||||
shows = map("<li>{!s}</li>".format, inscrit_revente)
|
||||
msg = (
|
||||
"Tu as été inscrit à des reventes en cours pour les spectacles "
|
||||
"<ul>{:s}</ul>".format('\n'.join(shows))
|
||||
)
|
||||
messages.info(request, msg, extra_tags="safe")
|
||||
|
||||
return render(request, "liste-reventes.html",
|
||||
{"form": form,
|
||||
"deja_revente": deja_revente, "success": success,
|
||||
"inscrit_revente": inscrit_revente})
|
||||
return render(request, "bda/liste-reventes.html", {"form": form})
|
||||
|
||||
|
||||
@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(
|
||||
participant, _ = Participant.objects.get_or_create(
|
||||
user=request.user, tirage=tirage)
|
||||
reventes = SpectacleRevente.objects.filter(
|
||||
attribution__spectacle=spectacle,
|
||||
|
@ -464,15 +512,17 @@ def buy_revente(request, spectacle_id):
|
|||
revente = random.choice(reventes_shotgun)
|
||||
revente.soldTo = participant
|
||||
revente.save()
|
||||
mail = loader.render_to_string('bda/mails/buy-shotgun.txt', {
|
||||
'spectacle': spectacle,
|
||||
context = {
|
||||
'show': spectacle,
|
||||
'acheteur': request.user,
|
||||
'vendeur': revente.seller.user,
|
||||
})
|
||||
send_mail("BdA-Revente : %s" % spectacle.title, mail,
|
||||
request.user.email,
|
||||
'vendeur': revente.seller.user
|
||||
}
|
||||
send_custom_mail(
|
||||
'bda-buy-shotgun',
|
||||
'bda@ens.fr',
|
||||
[revente.seller.user.email],
|
||||
fail_silently=False)
|
||||
context=context,
|
||||
)
|
||||
return render(request, "bda-success.html",
|
||||
{"seller": revente.attribution.participant.user,
|
||||
"spectacle": spectacle})
|
||||
|
@ -556,15 +606,16 @@ def unpaid(request, tirage_id):
|
|||
def send_rappel(request, spectacle_id):
|
||||
show = get_object_or_404(Spectacle, id=spectacle_id)
|
||||
# Mails d'exemples
|
||||
fake_member = request.user
|
||||
fake_member.nb_attr = 1
|
||||
exemple_mail_1place = loader.render_to_string('bda/mails/rappel.txt', {
|
||||
'member': fake_member,
|
||||
'show': show})
|
||||
fake_member.nb_attr = 2
|
||||
exemple_mail_2places = loader.render_to_string('bda/mails/rappel.txt', {
|
||||
'member': fake_member,
|
||||
'show': show})
|
||||
exemple_mail_1place = render_custom_mail('bda-rappel', {
|
||||
'member': request.user,
|
||||
'show': show,
|
||||
'nb_attr': 1
|
||||
})
|
||||
exemple_mail_2places = render_custom_mail('bda-rappel', {
|
||||
'member': request.user,
|
||||
'show': show,
|
||||
'nb_attr': 2
|
||||
})
|
||||
# Contexte
|
||||
ctxt = {'show': show,
|
||||
'exemple_mail_1place': exemple_mail_1place,
|
||||
|
@ -577,7 +628,7 @@ def send_rappel(request, spectacle_id):
|
|||
# Demande de confirmation
|
||||
else:
|
||||
ctxt['sent'] = False
|
||||
return render(request, "mails-rappel.html", ctxt)
|
||||
return render(request, "bda/mails-rappel.html", ctxt)
|
||||
|
||||
|
||||
def descriptions_spectacles(request, tirage_id):
|
||||
|
@ -594,3 +645,98 @@ def descriptions_spectacles(request, tirage_id):
|
|||
return HttpResponseBadRequest(
|
||||
"La variable GET 'location' doit contenir un entier")
|
||||
return render(request, 'descriptions.html', {'shows': shows_qs.all()})
|
||||
|
||||
|
||||
def catalogue(request, request_type):
|
||||
"""
|
||||
Vue destinée à communiquer avec un client AJAX, fournissant soit :
|
||||
- la liste des tirages
|
||||
- les catégories et salles d'un tirage
|
||||
- les descriptions d'un tirage (filtrées selon la catégorie et la salle)
|
||||
"""
|
||||
if request_type == "list":
|
||||
# Dans ce cas on retourne la liste des tirages et de leur id en JSON
|
||||
data_return = list(
|
||||
Tirage.objects.filter(appear_catalogue=True).values('id', 'title'))
|
||||
return JsonResponse(data_return, safe=False)
|
||||
if request_type == "details":
|
||||
# Dans ce cas on retourne une liste des catégories et des salles
|
||||
tirage_id = request.GET.get('id', '')
|
||||
try:
|
||||
tirage = Tirage.objects.get(id=tirage_id)
|
||||
except ObjectDoesNotExist:
|
||||
return HttpResponseBadRequest(
|
||||
"Aucun tirage correspondant à l'id "
|
||||
+ tirage_id)
|
||||
except ValueError:
|
||||
return HttpResponseBadRequest(
|
||||
"Mauvais format d'identifiant : "
|
||||
+ tirage_id)
|
||||
categories = list(
|
||||
CategorieSpectacle.objects.filter(
|
||||
spectacle__in=tirage.spectacle_set.all())
|
||||
.distinct().values('id', 'name'))
|
||||
locations = list(
|
||||
Salle.objects.filter(
|
||||
spectacle__in=tirage.spectacle_set.all())
|
||||
.distinct().values('id', 'name'))
|
||||
data_return = {'categories': categories, 'locations': locations}
|
||||
return JsonResponse(data_return, safe=False)
|
||||
if request_type == "descriptions":
|
||||
# Ici on retourne les descriptions correspondant à la catégorie et
|
||||
# à la salle spécifiées
|
||||
|
||||
tirage_id = request.GET.get('id', '')
|
||||
categories = request.GET.get('category', '[0]')
|
||||
locations = request.GET.get('location', '[0]')
|
||||
try:
|
||||
category_id = json.loads(categories)
|
||||
location_id = json.loads(locations)
|
||||
tirage = Tirage.objects.get(id=tirage_id)
|
||||
|
||||
shows_qs = tirage.spectacle_set
|
||||
if not(0 in category_id):
|
||||
shows_qs = shows_qs.filter(
|
||||
category__id__in=category_id)
|
||||
if not(0 in location_id):
|
||||
shows_qs = shows_qs.filter(
|
||||
location__id__in=location_id)
|
||||
except ObjectDoesNotExist:
|
||||
return HttpResponseBadRequest(
|
||||
"Impossible de trouver des résultats correspondant "
|
||||
"à ces caractéristiques : "
|
||||
+ "id = " + tirage_id
|
||||
+ ", catégories = " + categories
|
||||
+ ", salles = " + locations)
|
||||
except ValueError: # Contient JSONDecodeError
|
||||
return HttpResponseBadRequest(
|
||||
"Impossible de parser les paramètres donnés : "
|
||||
+ "id = " + request.GET.get('id', '')
|
||||
+ ", catégories = " + request.GET.get('category', '[0]')
|
||||
+ ", salles = " + request.GET.get('location', '[0]'))
|
||||
|
||||
# On convertit les descriptions à envoyer en une liste facilement
|
||||
# JSONifiable (il devrait y avoir un moyen plus efficace en
|
||||
# redéfinissant le serializer de JSON)
|
||||
data_return = [{
|
||||
'title': spectacle.title,
|
||||
'category': str(spectacle.category),
|
||||
'date': str(formats.date_format(
|
||||
timezone.localtime(spectacle.date),
|
||||
"SHORT_DATETIME_FORMAT")),
|
||||
'location': str(spectacle.location),
|
||||
'vips': spectacle.vips,
|
||||
'description': spectacle.description,
|
||||
'slots_description': spectacle.slots_description,
|
||||
'quotes': list(Quote.objects.filter(spectacle=spectacle).values(
|
||||
'author', 'text')),
|
||||
'image': spectacle.getImgUrl(),
|
||||
'ext_link': spectacle.ext_link,
|
||||
'price': spectacle.price,
|
||||
'slots': spectacle.slots
|
||||
}
|
||||
for spectacle in shows_qs.all()
|
||||
]
|
||||
return JsonResponse(data_return, safe=False)
|
||||
# Si la requête n'est pas de la forme attendue, on quitte avec une erreur
|
||||
return HttpResponseBadRequest()
|
||||
|
|
|
@ -9,10 +9,6 @@ For the full list of settings and their values, see
|
|||
https://docs.djangoproject.com/en/1.8/ref/settings/
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
import os
|
||||
|
||||
|
@ -50,6 +46,7 @@ INSTALLED_APPS = (
|
|||
'kfet',
|
||||
'channels',
|
||||
'widget_tweaks',
|
||||
'custommail',
|
||||
)
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
|
@ -163,6 +160,8 @@ AUTHENTICATION_BACKENDS = (
|
|||
'kfet.backends.GenericTeamBackend',
|
||||
)
|
||||
|
||||
# LDAP_SERVER_URL = 'ldaps://ldap.spi.ens.fr:636'
|
||||
|
||||
# EMAIL_HOST="nef.ens.fr"
|
||||
|
||||
RECAPTCHA_PUBLIC_KEY = "DUMMY"
|
||||
|
|
|
@ -4,10 +4,6 @@
|
|||
Fichier principal de configuration des urls du projet GestioCOF
|
||||
"""
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import autocomplete_light
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -61,7 +57,8 @@ urlpatterns = [
|
|||
name='password_change_done'),
|
||||
# Inscription d'un nouveau membre
|
||||
url(r'^registration$', gestioncof_views.registration),
|
||||
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)$',
|
||||
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)/'
|
||||
r'(?P<fullname>.*)$',
|
||||
gestioncof_views.registration_form2, name="clipper-registration"),
|
||||
url(r'^registration/user/(?P<username>.+)$',
|
||||
gestioncof_views.registration_form2, name="user-registration"),
|
||||
|
@ -87,7 +84,7 @@ urlpatterns = [
|
|||
url(r'^k-fet/', include('kfet.urls')),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
if 'debug_toolbar' in settings.INSTALLED_APPS:
|
||||
import debug_toolbar
|
||||
urlpatterns += [
|
||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
||||
|
|
|
@ -8,7 +8,7 @@ from django import forms
|
|||
from django.contrib import admin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from gestioncof.models import SurveyQuestionAnswer, SurveyQuestion, \
|
||||
CofProfile, EventOption, EventOptionChoice, Event, Club, CustomMail, \
|
||||
CofProfile, EventOption, EventOptionChoice, Event, Club, \
|
||||
Survey, EventCommentField, EventRegistration
|
||||
from gestioncof.petits_cours_models import PetitCoursDemande, \
|
||||
PetitCoursSubject, PetitCoursAbility, PetitCoursAttribution, \
|
||||
|
@ -267,10 +267,6 @@ class PetitCoursDemandeAdmin(admin.ModelAdmin):
|
|||
search_fields = ('name', 'email', 'phone', 'lieu', 'remarques')
|
||||
|
||||
|
||||
class CustomMailAdmin(admin.ModelAdmin):
|
||||
search_fields = ('shortname', 'title')
|
||||
|
||||
|
||||
class ClubAdminForm(forms.ModelForm):
|
||||
def clean(self):
|
||||
cleaned_data = super(ClubAdminForm, self).clean()
|
||||
|
@ -297,7 +293,6 @@ admin.site.unregister(User)
|
|||
admin.site.register(User, UserProfileAdmin)
|
||||
admin.site.register(CofProfile)
|
||||
admin.site.register(Club, ClubAdmin)
|
||||
admin.site.register(CustomMail)
|
||||
admin.site.register(PetitCoursSubject)
|
||||
admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin)
|
||||
admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin)
|
||||
|
|
|
@ -1,18 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from ldap3 import Connection
|
||||
|
||||
from django import shortcuts
|
||||
from django.http import Http404
|
||||
from django.db.models import Q
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from gestioncof.models import CofProfile, Clipper
|
||||
from django.conf import settings
|
||||
|
||||
from gestioncof.models import CofProfile
|
||||
from gestioncof.decorators import buro_required
|
||||
|
||||
|
||||
class Clipper(object):
|
||||
def __init__(self, clipper, fullname):
|
||||
if fullname is None:
|
||||
fullname = ""
|
||||
assert isinstance(clipper, str)
|
||||
assert isinstance(fullname, str)
|
||||
self.clipper = clipper
|
||||
self.fullname = fullname
|
||||
|
||||
|
||||
@buro_required
|
||||
def autocomplete(request):
|
||||
if "q" not in request.GET:
|
||||
|
@ -25,9 +34,9 @@ def autocomplete(request):
|
|||
queries = {}
|
||||
bits = q.split()
|
||||
|
||||
queries['members'] = CofProfile.objects.filter(Q(is_cof=True))
|
||||
queries['users'] = User.objects.filter(Q(profile__is_cof=False))
|
||||
queries['clippers'] = Clipper.objects
|
||||
# Fetching data from User and CofProfile tables
|
||||
queries['members'] = CofProfile.objects.filter(is_cof=True)
|
||||
queries['users'] = User.objects.filter(profile__is_cof=False)
|
||||
for bit in bits:
|
||||
queries['members'] = queries['members'].filter(
|
||||
Q(user__first_name__icontains=bit)
|
||||
|
@ -38,24 +47,42 @@ def autocomplete(request):
|
|||
Q(first_name__icontains=bit)
|
||||
| Q(last_name__icontains=bit)
|
||||
| Q(username__icontains=bit))
|
||||
queries['clippers'] = queries['clippers'].filter(
|
||||
Q(fullname__icontains=bit)
|
||||
| Q(username__icontains=bit))
|
||||
queries['members'] = queries['members'].distinct()
|
||||
queries['users'] = queries['users'].distinct()
|
||||
usernames = list(queries['members'].values_list('login_clipper',
|
||||
flat='True')) \
|
||||
+ list(queries['users'].values_list('profile__login_clipper',
|
||||
|
||||
# Clearing redundancies
|
||||
usernames = (
|
||||
set(queries['members'].values_list('login_clipper', flat='True'))
|
||||
| set(queries['users'].values_list('profile__login_clipper',
|
||||
flat='True'))
|
||||
queries['clippers'] = queries['clippers'] \
|
||||
.exclude(username__in=usernames).distinct()
|
||||
# add clippers
|
||||
)
|
||||
|
||||
# Fetching data from the SPI
|
||||
if hasattr(settings, 'LDAP_SERVER_URL'):
|
||||
# Fetching
|
||||
ldap_query = '(&{:s})'.format(''.join(
|
||||
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=bit)
|
||||
for bit in bits if bit.isalnum()
|
||||
))
|
||||
if ldap_query != "(&)":
|
||||
# If none of the bits were legal, we do not perform the query
|
||||
entries = None
|
||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
||||
conn.search(
|
||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
||||
attributes=['uid', 'cn']
|
||||
)
|
||||
entries = conn.entries
|
||||
# Clearing redundancies
|
||||
queries['clippers'] = [
|
||||
Clipper(entry.uid.value, entry.cn.value)
|
||||
for entry in entries
|
||||
if entry.uid.value
|
||||
and entry.uid.value not in usernames
|
||||
]
|
||||
|
||||
# Resulting data
|
||||
data.update(queries)
|
||||
|
||||
options = 0
|
||||
for query in queries.values():
|
||||
options += len(query)
|
||||
data['options'] = options
|
||||
data['options'] = sum(len(query) for query in queries)
|
||||
|
||||
return shortcuts.render(request, "autocomplete_user.html", data)
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import autocomplete_light
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
autocomplete_light.register(
|
||||
User, search_fields=('username', 'first_name', 'last_name'),
|
||||
autocomplete_js_attributes={'placeholder': 'membre...'})
|
||||
attrs={'placeholder': 'membre...'}
|
||||
)
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django_cas_ng.decorators import user_passes_test
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
|
||||
|
||||
def is_cof(user):
|
||||
|
@ -14,9 +10,7 @@ def is_cof(user):
|
|||
except:
|
||||
return False
|
||||
|
||||
cof_required = user_passes_test(lambda u: is_cof(u))
|
||||
cof_required_customdenied = user_passes_test(lambda u: is_cof(u),
|
||||
login_url="cof-denied")
|
||||
cof_required = user_passes_test(is_cof)
|
||||
|
||||
|
||||
def is_buro(user):
|
||||
|
@ -26,4 +20,4 @@ def is_buro(user):
|
|||
except:
|
||||
return False
|
||||
|
||||
buro_required = user_passes_test(lambda u: is_buro(u))
|
||||
buro_required = user_passes_test(is_buro)
|
||||
|
|
|
@ -152,156 +152,6 @@
|
|||
"model": "gestioncof.petitcourssubject",
|
||||
"pk": 4
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "college",
|
||||
"user": 11,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 1
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "lycee",
|
||||
"user": 11,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 2
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "prepa1styear",
|
||||
"user": 11,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 3
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "prepa2ndyear",
|
||||
"user": 11,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 4
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "licence3",
|
||||
"user": 11,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 5
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "prepa1styear",
|
||||
"user": 43,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 6
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "prepa2ndyear",
|
||||
"user": 43,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 7
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 1,
|
||||
"niveau": "licence3",
|
||||
"user": 43,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 8
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 2,
|
||||
"niveau": "college",
|
||||
"user": 43,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 9
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 2,
|
||||
"niveau": "lycee",
|
||||
"user": 43,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 10
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 3,
|
||||
"niveau": "lycee",
|
||||
"user": 48,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 11
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 3,
|
||||
"niveau": "prepa1styear",
|
||||
"user": 48,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 12
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 3,
|
||||
"niveau": "prepa2ndyear",
|
||||
"user": 48,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 13
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 3,
|
||||
"niveau": "licence3",
|
||||
"user": 48,
|
||||
"agrege": true
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 14
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"matiere": 4,
|
||||
"niveau": "college",
|
||||
"user": 10,
|
||||
"agrege": false
|
||||
},
|
||||
"model": "gestioncof.petitcoursability",
|
||||
"pk": 15
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"traitee": false,
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
[
|
||||
{
|
||||
"fields": {
|
||||
"username": "root",
|
||||
"first_name": "super",
|
||||
"last_name": "user",
|
||||
"is_active": true,
|
||||
"is_superuser": true,
|
||||
"is_staff": true,
|
||||
"last_login": null,
|
||||
"groups": [],
|
||||
"user_permissions": [],
|
||||
"password": "pbkdf2_sha256$12000$yRpkPuayQ8De$h6bDe+Q4kMikzwEbLNw2I9/V/1/v3F3yLIjEZIFSHrY=",
|
||||
"email": "root@localhost",
|
||||
"date_joined": "2016-06-15T17:50:57Z"
|
||||
},
|
||||
"model": "auth.user",
|
||||
"pk": 62
|
||||
},
|
||||
{
|
||||
"fields": {
|
||||
"departement": "",
|
||||
"type_cotiz": "normalien",
|
||||
"petits_cours_remarques": "",
|
||||
"is_buro": true,
|
||||
"is_cof": true,
|
||||
"mailing_cof": true,
|
||||
"comments": "Super utilisateur",
|
||||
"login_clipper": "",
|
||||
"phone": "",
|
||||
"num": 62,
|
||||
"mailing_bda_revente": true,
|
||||
"user": 62,
|
||||
"petits_cours_accept": false,
|
||||
"mailing_bda": true,
|
||||
"occupation": "1A"
|
||||
},
|
||||
"model": "gestioncof.cofprofile",
|
||||
"pk": 62
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load diff
|
@ -378,12 +378,12 @@ EventFormset = formset_factory(AdminEventForm, BaseEventRegistrationFormset)
|
|||
class CalendarForm(forms.ModelForm):
|
||||
subscribe_to_events = forms.BooleanField(
|
||||
initial=True,
|
||||
label="Événements du COF.")
|
||||
label="Événements du COF")
|
||||
subscribe_to_my_shows = forms.BooleanField(
|
||||
initial=True,
|
||||
label="Les spectacles pour lesquels j'ai obtenu une place.")
|
||||
label="Les spectacles pour lesquels j'ai obtenu une place")
|
||||
other_shows = forms.ModelMultipleChoiceField(
|
||||
label="Spectacles supplémentaires.",
|
||||
label="Spectacles supplémentaires",
|
||||
queryset=Spectacle.objects.filter(tirage__active=True),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
|
|
0
gestioncof/management/__init__.py
Normal file
0
gestioncof/management/__init__.py
Normal file
41
gestioncof/management/base.py
Normal file
41
gestioncof/management/base.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
"""
|
||||
Un mixin à utiliser avec BaseCommand pour charger des objets depuis un json
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
class MyBaseCommand(BaseCommand):
|
||||
"""
|
||||
Ajoute une méthode ``from_json`` qui charge des objets à partir d'un json.
|
||||
"""
|
||||
|
||||
def from_json(self, filename, data_dir, klass,
|
||||
callback=lambda obj: obj):
|
||||
"""
|
||||
Charge les objets contenus dans le fichier json référencé par
|
||||
``filename`` dans la base de donnée. La fonction callback est appelées
|
||||
sur chaque objet avant enregistrement.
|
||||
"""
|
||||
self.stdout.write("Chargement de {:s}".format(filename))
|
||||
with open(os.path.join(data_dir, filename), 'r') as file:
|
||||
descriptions = json.load(file)
|
||||
objects = []
|
||||
nb_new = 0
|
||||
for description in descriptions:
|
||||
qset = klass.objects.filter(**description)
|
||||
try:
|
||||
objects.append(qset.get())
|
||||
except klass.DoesNotExist:
|
||||
obj = klass(**description)
|
||||
obj = callback(obj)
|
||||
obj.save()
|
||||
objects.append(obj)
|
||||
nb_new += 1
|
||||
self.stdout.write("- {:d} objets créés".format(nb_new))
|
||||
self.stdout.write("- {:d} objets gardés en l'état"
|
||||
.format(len(objects)-nb_new))
|
||||
return objects
|
0
gestioncof/management/commands/__init__.py
Normal file
0
gestioncof/management/commands/__init__.py
Normal file
115
gestioncof/management/commands/loaddevdata.py
Normal file
115
gestioncof/management/commands/loaddevdata.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
"""
|
||||
Charge des données de test dans la BDD
|
||||
- Utilisateurs
|
||||
- Sondage
|
||||
- Événement
|
||||
- Petits cours
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import random
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.management import call_command
|
||||
|
||||
from gestioncof.management.base import MyBaseCommand
|
||||
from gestioncof.petits_cours_models import (
|
||||
PetitCoursAbility, PetitCoursSubject, LEVELS_CHOICES,
|
||||
PetitCoursAttributionCounter
|
||||
)
|
||||
|
||||
# Où sont stockés les fichiers json
|
||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
||||
'data')
|
||||
|
||||
|
||||
class Command(MyBaseCommand):
|
||||
help = "Charge des données de test dans la BDD"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""
|
||||
Permet de ne pas créer l'utilisateur "root".
|
||||
"""
|
||||
parser.add_argument(
|
||||
'--no-root',
|
||||
action='store_true',
|
||||
dest='no-root',
|
||||
default=False,
|
||||
help='Ne crée pas l\'utilisateur "root"'
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# ---
|
||||
# Utilisateurs
|
||||
# ---
|
||||
|
||||
# Gaulois
|
||||
gaulois = self.from_json('gaulois.json', DATA_DIR, User)
|
||||
for user in gaulois:
|
||||
user.profile.is_cof = True
|
||||
user.profile.save()
|
||||
|
||||
# Romains
|
||||
self.from_json('romains.json', DATA_DIR, User)
|
||||
|
||||
# Root
|
||||
no_root = options.get('no-root', False)
|
||||
if not no_root:
|
||||
self.stdout.write("Création de l'utilisateur root")
|
||||
root, _ = User.objects.get_or_create(
|
||||
username='root',
|
||||
first_name='super',
|
||||
last_name='user',
|
||||
email='root@localhost')
|
||||
root.set_password('root')
|
||||
root.is_staff = True
|
||||
root.is_superuser = True
|
||||
root.profile.is_cof = True
|
||||
root.profile.is_buro = True
|
||||
root.profile.save()
|
||||
root.save()
|
||||
|
||||
# ---
|
||||
# Petits cours
|
||||
# ---
|
||||
|
||||
self.stdout.write("Inscriptions au système des petits cours")
|
||||
levels = [id for (id, verbose) in LEVELS_CHOICES]
|
||||
subjects = list(PetitCoursSubject.objects.all())
|
||||
nb_of_teachers = 0
|
||||
for user in gaulois:
|
||||
if random.randint(0, 1):
|
||||
nb_of_teachers += 1
|
||||
# L'utilisateur reçoit les demandes de petits cours
|
||||
user.profile.petits_cours_accept = True
|
||||
user.save()
|
||||
# L'utilisateur est compétent dans une matière
|
||||
subject = random.choice(subjects)
|
||||
if not PetitCoursAbility.objects.filter(
|
||||
user=user,
|
||||
matiere=subject).exists():
|
||||
PetitCoursAbility.objects.create(
|
||||
user=user,
|
||||
matiere=subject,
|
||||
niveau=random.choice(levels),
|
||||
agrege=bool(random.randint(0, 1))
|
||||
)
|
||||
# On initialise son compteur d'attributions
|
||||
PetitCoursAttributionCounter.objects.get_or_create(
|
||||
user=user,
|
||||
matiere=subject
|
||||
)
|
||||
self.stdout.write("- {:d} inscriptions".format(nb_of_teachers))
|
||||
|
||||
# ---
|
||||
# Le BdA
|
||||
# ---
|
||||
|
||||
call_command('loadbdadevdata')
|
||||
|
||||
# ---
|
||||
# La K-Fêt
|
||||
# ---
|
||||
|
||||
call_command('loadkfetdevdata')
|
86
gestioncof/management/commands/syncmails.py
Normal file
86
gestioncof/management/commands/syncmails.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Import des mails de GestioCOF dans la base de donnée
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from custommail.models import Type, CustomMail, Variable
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = ("Va chercher les données mails de GestioCOF stocké au format json "
|
||||
"dans /gestioncof/management/data/custommails.json. Le format des "
|
||||
"données est celui donné par la commande :"
|
||||
" `python manage.py dumpdata custommail --natural-foreign` "
|
||||
"La bonne façon de mettre à jour ce fichier est donc de le "
|
||||
"charger à l'aide de syncmails, le faire les modifications à "
|
||||
"l'aide de l'interface administration et/ou du shell puis de le "
|
||||
"remplacer par le nouveau résultat de la commande précédente.")
|
||||
|
||||
def handle(self, *args, **options):
|
||||
path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(__file__)),
|
||||
'data', 'custommail.json')
|
||||
with open(path, 'r') as jsonfile:
|
||||
mail_data = json.load(jsonfile)
|
||||
|
||||
# On se souvient à quel objet correspond quel pk du json
|
||||
assoc = {'types': {}, 'mails': {}}
|
||||
status = {'synced': 0, 'unchanged': 0}
|
||||
|
||||
for obj in mail_data:
|
||||
fields = obj['fields']
|
||||
|
||||
# Pour les trois types d'objets :
|
||||
# - On récupère les objets référencés par les clefs étrangères
|
||||
# - On crée l'objet si nécessaire
|
||||
# - On le stocke éventuellement dans les deux dictionnaires définis
|
||||
# plus haut
|
||||
|
||||
# Variable types
|
||||
if obj['model'] == 'custommail.variabletype':
|
||||
fields['inner1'] = assoc['types'].get(fields['inner1'])
|
||||
fields['inner2'] = assoc['types'].get(fields['inner2'])
|
||||
if fields['kind'] == 'model':
|
||||
fields['content_type'] = (
|
||||
ContentType.objects
|
||||
.get_by_natural_key(*fields['content_type'])
|
||||
)
|
||||
var_type, _ = Type.objects.get_or_create(**fields)
|
||||
assoc['types'][obj['pk']] = var_type
|
||||
|
||||
# Custom mails
|
||||
if obj['model'] == 'custommail.custommail':
|
||||
mail = None
|
||||
try:
|
||||
mail = CustomMail.objects.get(
|
||||
shortname=fields['shortname'])
|
||||
status['unchanged'] += 1
|
||||
except CustomMail.DoesNotExist:
|
||||
mail = CustomMail.objects.create(**fields)
|
||||
status['synced'] += 1
|
||||
self.stdout.write(
|
||||
'SYNCED {:s}'.format(fields['shortname']))
|
||||
assoc['mails'][obj['pk']] = mail
|
||||
|
||||
# Variables
|
||||
if obj['model'] == 'custommail.custommailvariable':
|
||||
fields['custommail'] = assoc['mails'].get(fields['custommail'])
|
||||
fields['type'] = assoc['types'].get(fields['type'])
|
||||
try:
|
||||
Variable.objects.get(
|
||||
custommail=fields['custommail'],
|
||||
name=fields['name']
|
||||
)
|
||||
except Variable.DoesNotExist:
|
||||
Variable.objects.create(**fields)
|
||||
|
||||
# C'est agréable d'avoir le résultat affiché
|
||||
self.stdout.write(
|
||||
'{synced:d} mails synchronized {unchanged:d} unchanged'
|
||||
.format(**status)
|
||||
)
|
587
gestioncof/management/data/custommail.json
Normal file
587
gestioncof/management/data/custommail.json
Normal file
|
@ -0,0 +1,587 @@
|
|||
[
|
||||
{
|
||||
"model": "custommail.variabletype",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"content_type": [
|
||||
"auth",
|
||||
"user"
|
||||
],
|
||||
"inner1": null,
|
||||
"kind": "model",
|
||||
"inner2": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.variabletype",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"content_type": null,
|
||||
"inner1": null,
|
||||
"kind": "int",
|
||||
"inner2": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.variabletype",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"content_type": [
|
||||
"bda",
|
||||
"spectacle"
|
||||
],
|
||||
"inner1": null,
|
||||
"kind": "model",
|
||||
"inner2": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.variabletype",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"content_type": [
|
||||
"bda",
|
||||
"spectaclerevente"
|
||||
],
|
||||
"inner1": null,
|
||||
"kind": "model",
|
||||
"inner2": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.variabletype",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"content_type": [
|
||||
"sites",
|
||||
"site"
|
||||
],
|
||||
"inner1": null,
|
||||
"kind": "model",
|
||||
"inner2": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.variabletype",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"content_type": [
|
||||
"gestioncof",
|
||||
"petitcoursdemande"
|
||||
],
|
||||
"inner1": null,
|
||||
"kind": "model",
|
||||
"inner2": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.variabletype",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"content_type": null,
|
||||
"inner1": null,
|
||||
"kind": "list",
|
||||
"inner2": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.variabletype",
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"content_type": null,
|
||||
"inner1": 1,
|
||||
"kind": "list",
|
||||
"inner2": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.variabletype",
|
||||
"pk": 9,
|
||||
"fields": {
|
||||
"content_type": null,
|
||||
"inner1": null,
|
||||
"kind": "pair",
|
||||
"inner2": 8
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.variabletype",
|
||||
"pk": 10,
|
||||
"fields": {
|
||||
"content_type": null,
|
||||
"inner1": 9,
|
||||
"kind": "list",
|
||||
"inner2": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.variabletype",
|
||||
"pk": 11,
|
||||
"fields": {
|
||||
"content_type": null,
|
||||
"inner1": 3,
|
||||
"kind": "list",
|
||||
"inner2": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"shortname": "welcome",
|
||||
"subject": "Bienvenue au COF",
|
||||
"description": "Mail de bienvenue au COF envoy\u00e9 automatiquement \u00e0 l'inscription d'un nouveau membre",
|
||||
"body": "Bonjour {{ member.first_name }} et bienvenue au COF !\r\n\r\nTu trouveras plein de trucs cool sur le site du COF : https://www.cof.ens.fr/ et notre page Facebook : https://www.facebook.com/cof.ulm\r\nEt n'oublie pas d'aller d\u00e9couvrir GestioCOF, la plateforme de gestion du COF !\r\nSi tu as des questions, tu peux nous envoyer un mail \u00e0 cof@ens.fr (on aime le spam), ou passer nous voir au Bur\u00f4 pr\u00e8s de la Cour\u00f4 du lundi au vendredi de 12h \u00e0 14h et de 18h \u00e0 20h.\r\n\r\nRetrouvez les \u00e9v\u00e8nements de rentr\u00e9e pour les conscrit.e.s et les vieux/vieilles organis\u00e9s par le COF et ses clubs ici : http://www.cof.ens.fr/depot/Rentree.pdf \r\n\r\nAmicalement,\r\n\r\nTon COF qui t'aime."
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"shortname": "bda-rappel",
|
||||
"subject": "{{ show }}",
|
||||
"description": "Mail de rappel pour les spectacles BdA",
|
||||
"body": "Bonjour {{ member.first_name }},\r\n\r\nNous te rappellons que tu as eu la chance d'obtenir {{ nb_attr|pluralize:\"une place,deux places\" }}\r\npour {{ show.title }}, le {{ show.date }} au {{ show.location }}. N'oublie pas de t'y rendre !\r\n{% if nb_attr == 2 %}\r\nTu as obtenu deux places pour ce spectacle. Nous te rappelons que\r\nces places sont strictement r\u00e9serv\u00e9es aux personnes de moins de 28 ans.\r\n{% endif %}\r\n{% if show.listing %}Pour ce spectacle, tu as re\u00e7u des places sur\r\nlisting. Il te faudra donc te rendre 15 minutes en avance sur les lieux de la repr\u00e9sentation\r\npour retirer {{ nb_attr|pluralize:\"ta place,tes places\" }}.\r\n{% else %}Pour assister \u00e0 ce spectacle, tu dois pr\u00e9senter les billets qui ont\r\n\u00e9t\u00e9 distribu\u00e9s au bur\u00f4.\r\n{% endif %}\r\n\r\nSi tu ne peux plus assister \u00e0 cette repr\u00e9sentation, tu peux\r\nrevendre ta place via BdA-revente, accessible directement sur\r\nGestioCOF (lien \"revendre une place du premier tirage\" sur la page\r\nd'accueil https://www.cof.ens.fr/gestion/).\r\n\r\nEn te souhaitant un excellent spectacle,\r\n\r\nLe Bureau des Arts"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"shortname": "bda-revente",
|
||||
"subject": "{{ show }}",
|
||||
"description": "Notification envoy\u00e9e \u00e0 toutes les personnes int\u00e9ress\u00e9es par un spectacle pour le signaler qu'une place vient d'\u00eatre mise en vente.",
|
||||
"body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nSi ce spectacle t'int\u00e9resse toujours, merci de nous le signaler en cliquant\r\nsur ce lien : http://{{ site }}{% url \"bda-revente-interested\" revente.id %}.\r\nDans le cas o\u00f9 plusieurs personnes seraient int\u00e9ress\u00e9es, nous proc\u00e8derons \u00e0\r\nun tirage au sort le {{ revente.date_tirage|date:\"DATE_FORMAT\" }}.\r\n\r\nChaleureusement,\r\nLe BdA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"shortname": "bda-shotgun",
|
||||
"subject": "{{ show }}",
|
||||
"description": "Notification signalant qu'une place est au shotgun aux personnes int\u00e9ress\u00e9es.",
|
||||
"body": "Bonjour {{ member.first_name }}\r\n\r\nUne place pour le spectacle {{ show.title }} ({{ show.date }})\r\na \u00e9t\u00e9 post\u00e9e sur BdA-Revente.\r\n\r\nPuisque ce spectacle a lieu dans moins de 24h, il n'y a pas de tirage au sort pour\r\ncette place : elle est disponible imm\u00e9diatement \u00e0 l'adresse\r\nhttp://{{ site }}{% url \"bda-buy-revente\" show.id %}, \u00e0 la disposition de tous.\r\n\r\nChaleureusement,\r\nLe BdA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"shortname": "bda-revente-winner",
|
||||
"subject": "BdA-Revente : {{ show.title }}",
|
||||
"description": "Mail envoy\u00e9 au gagnant d'un tirage BdA-Revente",
|
||||
"body": "Bonjour {{ acheteur.first_name }},\r\n\r\nTu as \u00e9t\u00e9 tir\u00e9-e au sort pour racheter une place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) \u00e0 {{ show.price|floatformat:2 }}\u20ac.\r\nTu peux contacter le/la vendeur-se \u00e0 l'adresse {{ vendeur.email }}.\r\n\r\nChaleureusement,\r\nLe BdA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"shortname": "bda-revente-loser",
|
||||
"subject": "BdA-Revente : {{ show.title }}",
|
||||
"description": "Notification envoy\u00e9e aux perdants d'un tirage de revente.",
|
||||
"body": "Bonjour {{ acheteur.first_name }},\r\n\r\nTu t'\u00e9tais inscrit-e pour la revente de la place de {{ vendeur.get_full_name }}\r\npour {{ show.title }}.\r\nMalheureusement, une autre personne a \u00e9t\u00e9 tir\u00e9e au sort pour racheter la place.\r\nTu pourras certainement retenter ta chance pour une autre revente !\r\n\r\n\u00c0 tr\u00e8s bient\u00f4t,\r\nLe Bureau des Arts"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"shortname": "bda-revente-seller",
|
||||
"subject": "BdA-Revente : {{ show.title }}",
|
||||
"description": "Notification envoy\u00e9e au vendeur d'une place pour lui indiquer qu'elle vient d'\u00eatre attribu\u00e9e",
|
||||
"body": "Bonjour {{ vendeur.first_name }},\r\n\r\nLa personne tir\u00e9e au sort pour racheter ta place pour {{ show.title }} est {{ acheteur.get_full_name }}.\r\nTu peux le/la contacter \u00e0 l'adresse {{ acheteur.email }}, ou en r\u00e9pondant \u00e0 ce mail.\r\n\r\nChaleureusement,\r\nLe BdA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"shortname": "bda-revente-new",
|
||||
"subject": "BdA-Revente : {{ show.title }}",
|
||||
"description": "Notification signalant au vendeur d'une place que sa mise en vente a bien eu lieu et lui donnant quelques informations compl\u00e9mentaires.",
|
||||
"body": "Bonjour {{ vendeur.first_name }},\r\n\r\nTu t\u2019es bien inscrit-e pour la revente de {{ show.title }}.\r\n\r\n{% with revente.date_tirage as time %}\r\nLe tirage au sort entre tout-e-s les racheteuse-eur-s potentiel-le-s aura lieu\r\nle {{ time|date:\"DATE_FORMAT\" }} \u00e0 {{ time|time:\"TIME_FORMAT\" }} (dans {{time|timeuntil }}).\r\nSi personne ne s\u2019est inscrit pour racheter la place, celle-ci apparaitra parmi\r\nles \u00ab Places disponibles imm\u00e9diatement \u00e0 la revente \u00bb sur GestioCOF.\r\n{% endwith %}\r\n\r\nBonne revente !\r\nLe Bureau des Arts"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"pk": 9,
|
||||
"fields": {
|
||||
"shortname": "bda-buy-shotgun",
|
||||
"subject": "BdA-Revente : {{ show.title }}",
|
||||
"description": "Mail envoy\u00e9 au revendeur lors d'un achat au shotgun.",
|
||||
"body": "Bonjour {{ vendeur.first_name }} !\r\n\r\nJe souhaiterais racheter ta place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) \u00e0 {{ show.price|floatformat:2 }}\u20ac.\r\nContacte-moi si tu es toujours int\u00e9ress\u00e9\u00b7e !\r\n\r\n{{ acheteur.get_full_name }} ({{ acheteur.email }})"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"pk": 10,
|
||||
"fields": {
|
||||
"shortname": "petit-cours-mail-eleve",
|
||||
"subject": "Petits cours ENS par le COF",
|
||||
"description": "Mail envoy\u00e9 aux personnes dont ont a donn\u00e9 les contacts \u00e0 des demandeurs de petits cours",
|
||||
"body": "Salut,\r\n\r\nLe COF a re\u00e7u une demande de petit cours qui te correspond. Tu es en haut de la liste d'attente donc on a transmis tes coordonn\u00e9es, ainsi que celles de 2 autres qui correspondaient aussi (c'est la vie, on donne les num\u00e9ros 3 par 3 pour que ce soit plus souple). Voici quelques infos sur l'annonce en question :\r\n\r\n\u00a4 Nom : {{ demande.name }}\r\n\r\n\u00a4 P\u00e9riode : {{ demande.quand }}\r\n\r\n\u00a4 Fr\u00e9quence : {{ demande.freq }}\r\n\r\n\u00a4 Lieu (si pr\u00e9f\u00e9r\u00e9) : {{ demande.lieu }}\r\n\r\n\u00a4 Niveau : {{ demande.get_niveau_display }}\r\n\r\n\u00a4 Remarques diverses (d\u00e9sol\u00e9 pour les balises HTML) : {{ demande.remarques }}\r\n\r\n{% if matieres|length > 1 %}\u00a4 Mati\u00e8res :\r\n{% for matiere in matieres %} \u00a4 {{ matiere }}\r\n{% endfor %}{% else %}\u00a4 Mati\u00e8re : {% for matiere in matieres %}{{ matiere }}\r\n{% endfor %}{% endif %}\r\nVoil\u00e0, cette personne te contactera peut-\u00eatre sous peu, tu pourras voir les d\u00e9tails directement avec elle (prix, modalit\u00e9s, ...). Pour indication, 30 Euro/h semble \u00eatre la moyenne.\r\n\r\nSi tu te rends compte qu'en fait tu ne peux pas/plus donner de cours en ce moment, \u00e7a serait cool que tu d\u00e9coches la case \"Recevoir des propositions de petits cours\" sur GestioCOF. Ensuite d\u00e8s que tu voudras r\u00e9appara\u00eetre tu pourras recocher la case et tu seras \u00e0 nouveau sur la liste.\r\n\r\n\u00c0 bient\u00f4t,\r\n\r\n--\r\nLe COF, pour les petits cours"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"pk": 11,
|
||||
"fields": {
|
||||
"shortname": "petits-cours-mail-demandeur",
|
||||
"subject": "Cours particuliers ENS",
|
||||
"description": "Mail envoy\u00e9 aux personnent qui demandent des petits cours lorsque leur demande est trait\u00e9e.\r\n\r\n(Ne pas toucher \u00e0 {{ extra|safe }})",
|
||||
"body": "Bonjour,\r\n\r\nJe vous contacte au sujet de votre annonce pass\u00e9e sur le site du COF pour rentrer en contact avec un \u00e9l\u00e8ve normalien pour des cours particuliers. Voici les coordonn\u00e9es d'\u00e9l\u00e8ves qui sont motiv\u00e9s par de tels cours et correspondent aux crit\u00e8res que vous nous aviez transmis :\r\n\r\n{% for matiere, proposed in proposals %}\u00a4 {{ matiere }} :{% for user in proposed %}\r\n \u00a4 {{ user.get_full_name }}{% if user.profile.phone %}, {{ user.profile.phone }}{% endif %}{% if user.email %}, {{ user.email }}{% endif %}{% endfor %}\r\n\r\n{% endfor %}{% if unsatisfied %}Nous n'avons cependant pas pu trouver d'\u00e9l\u00e8ve disponible pour des cours de {% for matiere in unsatisfied %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}.\r\n\r\n{% endif %}Si pour une raison ou une autre ces num\u00e9ros ne suffisaient pas, n'h\u00e9sitez pas \u00e0 r\u00e9pondre \u00e0 cet e-mail et je vous en ferai parvenir d'autres sans probl\u00e8me.\r\n{% if extra|length > 0 %}\r\n{{ extra|safe }}\r\n{% endif %}\r\nCordialement,\r\n\r\n--\r\nLe COF, BdE de l'ENS"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"pk": 12,
|
||||
"fields": {
|
||||
"shortname": "bda-attributions",
|
||||
"subject": "R\u00e9sultats du tirage au sort",
|
||||
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux gagnants d'une ou plusieurs places",
|
||||
"body": "Cher-e {{ member.first_name }},\r\n\r\nTu t'es inscrit-e pour le tirage au sort du BdA. Tu as \u00e9t\u00e9 s\u00e9lectionn\u00e9-e\r\npour les spectacles suivants :\r\n{% for place in places %}\r\n- 1 place pour {{ place }}{% endfor %}\r\n\r\n*Paiement*\r\nL'int\u00e9gralit\u00e9 de ces places de spectacles est \u00e0 r\u00e9gler d\u00e8s maintenant et AVANT\r\nvendredi prochain, au bureau du COF pendant les heures de permanences (du lundi au vendredi\r\nentre 12h et 14h, et entre 18h et 20h). Des facilit\u00e9s de paiement sont bien\r\n\u00e9videmment possibles : nous pouvons ne pas encaisser le ch\u00e8que imm\u00e9diatement,\r\nou bien d\u00e9couper votre paiement en deux fois. Pour ceux qui ne pourraient pas\r\nvenir payer au bureau, merci de nous contacter par mail.\r\n\r\n*Mode de retrait des places*\r\nAu moment du paiement, certaines places vous seront remises directement,\r\nd'autres seront \u00e0 r\u00e9cup\u00e9rer au cours de l'ann\u00e9e, d'autres encore seront\r\nnominatives et \u00e0 retirer le soir m\u00eame dans les the\u00e2tres correspondants.\r\nPour chaque spectacle, vous recevrez un mail quelques jours avant la\r\nrepr\u00e9sentation vous indiquant le mode de retrait.\r\n\r\nNous vous rappelons que l'obtention de places du BdA vous engage \u00e0\r\nrespecter les r\u00e8gles de fonctionnement :\r\nhttp://www.cof.ens.fr/bda/?page_id=1370\r\nUn syst\u00e8me de revente des places via les mails BdA-revente disponible\r\ndirectement sur votre compte GestioCOF.\r\n\r\nEn vous souhaitant de tr\u00e8s beaux spectacles tout au long de l'ann\u00e9e,\r\n--\r\nLe Bureau des Arts"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"pk": 13,
|
||||
"fields": {
|
||||
"shortname": "bda-attributions-decus",
|
||||
"subject": "R\u00e9sultats du tirage au sort",
|
||||
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux personnes n'ayant pas obtenu de place",
|
||||
"body": "Cher-e {{ member.first_name }},\r\n\r\nTu t'es inscrit-e pour le tirage au sort du BdA. Malheureusement, tu n'as\r\nobtenu aucune place.\r\n\r\nNous proposons cependant de nombreuses offres hors-tirage tout au long de\r\nl'ann\u00e9e, et nous t'invitons \u00e0 nous contacter si l'une d'entre elles\r\nt'int\u00e9resse !\r\n--\r\nLe Bureau des Arts"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "member",
|
||||
"description": "Utilisateur de GestioCOF",
|
||||
"custommail": 1,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "member",
|
||||
"description": "Utilisateur ayant eu une place pour ce spectacle",
|
||||
"custommail": 2,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"name": "show",
|
||||
"description": "Spectacle",
|
||||
"custommail": 2,
|
||||
"type": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"name": "nb_attr",
|
||||
"description": "Nombre de places obtenues",
|
||||
"custommail": 2,
|
||||
"type": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"name": "revente",
|
||||
"description": "Revente mentionn\u00e9e dans le mail",
|
||||
"custommail": 3,
|
||||
"type": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"name": "member",
|
||||
"description": "Personne int\u00e9ress\u00e9e par la place",
|
||||
"custommail": 3,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"name": "show",
|
||||
"description": "Spectacle",
|
||||
"custommail": 3,
|
||||
"type": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"name": "site",
|
||||
"description": "Site web (gestioCOF)",
|
||||
"custommail": 3,
|
||||
"type": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 9,
|
||||
"fields": {
|
||||
"name": "site",
|
||||
"description": "Site web (gestioCOF)",
|
||||
"custommail": 4,
|
||||
"type": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 10,
|
||||
"fields": {
|
||||
"name": "show",
|
||||
"description": "Spectacle",
|
||||
"custommail": 4,
|
||||
"type": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 11,
|
||||
"fields": {
|
||||
"name": "member",
|
||||
"description": "Personne int\u00e9ress\u00e9e par la place",
|
||||
"custommail": 4,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 12,
|
||||
"fields": {
|
||||
"name": "acheteur",
|
||||
"description": "Gagnant-e du tirage",
|
||||
"custommail": 5,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 13,
|
||||
"fields": {
|
||||
"name": "vendeur",
|
||||
"description": "Personne qui vend une place",
|
||||
"custommail": 5,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 14,
|
||||
"fields": {
|
||||
"name": "show",
|
||||
"description": "Spectacle",
|
||||
"custommail": 5,
|
||||
"type": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 15,
|
||||
"fields": {
|
||||
"name": "show",
|
||||
"description": "Spectacle",
|
||||
"custommail": 6,
|
||||
"type": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 16,
|
||||
"fields": {
|
||||
"name": "vendeur",
|
||||
"description": "Personne qui vend une place",
|
||||
"custommail": 6,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 17,
|
||||
"fields": {
|
||||
"name": "acheteur",
|
||||
"description": "Personne inscrite au tirage qui n'a pas eu la place",
|
||||
"custommail": 6,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 18,
|
||||
"fields": {
|
||||
"name": "acheteur",
|
||||
"description": "Gagnant-e du tirage",
|
||||
"custommail": 7,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 19,
|
||||
"fields": {
|
||||
"name": "vendeur",
|
||||
"description": "Personne qui vend une place",
|
||||
"custommail": 7,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 20,
|
||||
"fields": {
|
||||
"name": "show",
|
||||
"description": "Spectacle",
|
||||
"custommail": 7,
|
||||
"type": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 21,
|
||||
"fields": {
|
||||
"name": "show",
|
||||
"description": "Spectacle",
|
||||
"custommail": 8,
|
||||
"type": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 22,
|
||||
"fields": {
|
||||
"name": "vendeur",
|
||||
"description": "Personne qui vend la place",
|
||||
"custommail": 8,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 23,
|
||||
"fields": {
|
||||
"name": "revente",
|
||||
"description": "Revente mentionn\u00e9e dans le mail",
|
||||
"custommail": 8,
|
||||
"type": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 24,
|
||||
"fields": {
|
||||
"name": "vendeur",
|
||||
"description": "Personne qui vend la place",
|
||||
"custommail": 9,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 25,
|
||||
"fields": {
|
||||
"name": "show",
|
||||
"description": "Spectacle",
|
||||
"custommail": 9,
|
||||
"type": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 26,
|
||||
"fields": {
|
||||
"name": "acheteur",
|
||||
"description": "Personne qui prend la place au shotgun",
|
||||
"custommail": 9,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 27,
|
||||
"fields": {
|
||||
"name": "demande",
|
||||
"description": "Demande de petit cours",
|
||||
"custommail": 10,
|
||||
"type": 6
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 28,
|
||||
"fields": {
|
||||
"name": "matieres",
|
||||
"description": "Liste des mati\u00e8res concern\u00e9es par la demande",
|
||||
"custommail": 10,
|
||||
"type": 7
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 29,
|
||||
"fields": {
|
||||
"name": "proposals",
|
||||
"description": "Liste associant une liste d'enseignants \u00e0 chaque mati\u00e8re",
|
||||
"custommail": 11,
|
||||
"type": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 30,
|
||||
"fields": {
|
||||
"name": "unsatisfied",
|
||||
"description": "Liste des mati\u00e8res pour lesquelles on n'a pas d'enseigant \u00e0 proposer",
|
||||
"custommail": 11,
|
||||
"type": 7
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 31,
|
||||
"fields": {
|
||||
"name": "places",
|
||||
"description": "Places de spectacle du participant",
|
||||
"custommail": 12,
|
||||
"type": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 32,
|
||||
"fields": {
|
||||
"name": "member",
|
||||
"description": "Participant du tirage au sort",
|
||||
"custommail": 12,
|
||||
"type": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommailvariable",
|
||||
"pk": 33,
|
||||
"fields": {
|
||||
"name": "member",
|
||||
"description": "Participant du tirage au sort",
|
||||
"custommail": 13,
|
||||
"type": 1
|
||||
}
|
||||
}
|
||||
]
|
368
gestioncof/management/data/gaulois.json
Normal file
368
gestioncof/management/data/gaulois.json
Normal file
|
@ -0,0 +1,368 @@
|
|||
[
|
||||
{
|
||||
"username": "Abraracourcix",
|
||||
"email": "Abraracourcix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Abraracourcix"
|
||||
},
|
||||
{
|
||||
"username": "Acidenitrix",
|
||||
"email": "Acidenitrix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Acidenitrix"
|
||||
},
|
||||
{
|
||||
"username": "Agecanonix",
|
||||
"email": "Agecanonix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Agecanonix"
|
||||
},
|
||||
{
|
||||
"username": "Alambix",
|
||||
"email": "Alambix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Alambix"
|
||||
},
|
||||
{
|
||||
"username": "Amerix",
|
||||
"email": "Amerix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Amerix"
|
||||
},
|
||||
{
|
||||
"username": "Amnesix",
|
||||
"email": "Amnesix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Amnesix"
|
||||
},
|
||||
{
|
||||
"username": "Aniline",
|
||||
"email": "Aniline.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Aniline"
|
||||
},
|
||||
{
|
||||
"username": "Aplusbegalix",
|
||||
"email": "Aplusbegalix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Aplusbegalix"
|
||||
},
|
||||
{
|
||||
"username": "Archeopterix",
|
||||
"email": "Archeopterix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Archeopterix"
|
||||
},
|
||||
{
|
||||
"username": "Assurancetourix",
|
||||
"email": "Assurancetourix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Assurancetourix"
|
||||
},
|
||||
{
|
||||
"username": "Asterix",
|
||||
"email": "Asterix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Asterix"
|
||||
},
|
||||
{
|
||||
"username": "Astronomix",
|
||||
"email": "Astronomix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Astronomix"
|
||||
},
|
||||
{
|
||||
"username": "Avoranfix",
|
||||
"email": "Avoranfix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Avoranfix"
|
||||
},
|
||||
{
|
||||
"username": "Barometrix",
|
||||
"email": "Barometrix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Barometrix"
|
||||
},
|
||||
{
|
||||
"username": "Beaufix",
|
||||
"email": "Beaufix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Beaufix"
|
||||
},
|
||||
{
|
||||
"username": "Berlix",
|
||||
"email": "Berlix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Berlix"
|
||||
},
|
||||
{
|
||||
"username": "Bonemine",
|
||||
"email": "Bonemine.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Bonemine"
|
||||
},
|
||||
{
|
||||
"username": "Boufiltre",
|
||||
"email": "Boufiltre.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Boufiltre"
|
||||
},
|
||||
{
|
||||
"username": "Catedralgotix",
|
||||
"email": "Catedralgotix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Catedralgotix"
|
||||
},
|
||||
{
|
||||
"username": "CesarLabeldecadix",
|
||||
"email": "CesarLabeldecadix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "CesarLabeldecadix"
|
||||
},
|
||||
{
|
||||
"username": "Cetautomatix",
|
||||
"email": "Cetautomatix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Cetautomatix"
|
||||
},
|
||||
{
|
||||
"username": "Cetyounix",
|
||||
"email": "Cetyounix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Cetyounix"
|
||||
},
|
||||
{
|
||||
"username": "Changeledix",
|
||||
"email": "Changeledix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Changeledix"
|
||||
},
|
||||
{
|
||||
"username": "Chanteclairix",
|
||||
"email": "Chanteclairix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Chanteclairix"
|
||||
},
|
||||
{
|
||||
"username": "Cicatrix",
|
||||
"email": "Cicatrix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Cicatrix"
|
||||
},
|
||||
{
|
||||
"username": "Comix",
|
||||
"email": "Comix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Comix"
|
||||
},
|
||||
{
|
||||
"username": "Diagnostix",
|
||||
"email": "Diagnostix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Diagnostix"
|
||||
},
|
||||
{
|
||||
"username": "Doublepolemix",
|
||||
"email": "Doublepolemix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Doublepolemix"
|
||||
},
|
||||
{
|
||||
"username": "Eponine",
|
||||
"email": "Eponine.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Eponine"
|
||||
},
|
||||
{
|
||||
"username": "Falbala",
|
||||
"email": "Falbala.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Falbala"
|
||||
},
|
||||
{
|
||||
"username": "Fanzine",
|
||||
"email": "Fanzine.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Fanzine"
|
||||
},
|
||||
{
|
||||
"username": "Gelatine",
|
||||
"email": "Gelatine.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Gelatine"
|
||||
},
|
||||
{
|
||||
"username": "Goudurix",
|
||||
"email": "Goudurix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Goudurix"
|
||||
},
|
||||
{
|
||||
"username": "Homeopatix",
|
||||
"email": "Homeopatix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Homeopatix"
|
||||
},
|
||||
{
|
||||
"username": "Idefix",
|
||||
"email": "Idefix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Idefix"
|
||||
},
|
||||
{
|
||||
"username": "Ielosubmarine",
|
||||
"email": "Ielosubmarine.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Ielosubmarine"
|
||||
},
|
||||
{
|
||||
"username": "Keskonrix",
|
||||
"email": "Keskonrix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Keskonrix"
|
||||
},
|
||||
{
|
||||
"username": "Lentix",
|
||||
"email": "Lentix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Lentix"
|
||||
},
|
||||
{
|
||||
"username": "Maestria",
|
||||
"email": "Maestria.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Maestria"
|
||||
},
|
||||
{
|
||||
"username": "MaitrePanix",
|
||||
"email": "MaitrePanix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "MaitrePanix"
|
||||
},
|
||||
{
|
||||
"username": "MmeAgecanonix",
|
||||
"email": "MmeAgecanonix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "MmeAgecanonix"
|
||||
},
|
||||
{
|
||||
"username": "Moralelastix",
|
||||
"email": "Moralelastix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Moralelastix"
|
||||
},
|
||||
{
|
||||
"username": "Obelix",
|
||||
"email": "Obelix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Obelix"
|
||||
},
|
||||
{
|
||||
"username": "Obelodalix",
|
||||
"email": "Obelodalix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Obelodalix"
|
||||
},
|
||||
{
|
||||
"username": "Odalix",
|
||||
"email": "Odalix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Odalix"
|
||||
},
|
||||
{
|
||||
"username": "Ordralfabetix",
|
||||
"email": "Ordralfabetix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Ordralfabetix"
|
||||
},
|
||||
{
|
||||
"username": "Orthopedix",
|
||||
"email": "Orthopedix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Orthopedix"
|
||||
},
|
||||
{
|
||||
"username": "Panoramix",
|
||||
"email": "Panoramix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Panoramix"
|
||||
},
|
||||
{
|
||||
"username": "Plaintcontrix",
|
||||
"email": "Plaintcontrix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Plaintcontrix"
|
||||
},
|
||||
{
|
||||
"username": "Praline",
|
||||
"email": "Praline.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Praline"
|
||||
},
|
||||
{
|
||||
"username": "Prefix",
|
||||
"email": "Prefix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Prefix"
|
||||
},
|
||||
{
|
||||
"username": "Prolix",
|
||||
"email": "Prolix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Prolix"
|
||||
},
|
||||
{
|
||||
"username": "Pronostix",
|
||||
"email": "Pronostix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Pronostix"
|
||||
},
|
||||
{
|
||||
"username": "Quatredeusix",
|
||||
"email": "Quatredeusix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Quatredeusix"
|
||||
},
|
||||
{
|
||||
"username": "Saingesix",
|
||||
"email": "Saingesix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Saingesix"
|
||||
},
|
||||
{
|
||||
"username": "Segregationnix",
|
||||
"email": "Segregationnix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Segregationnix"
|
||||
},
|
||||
{
|
||||
"username": "Septantesix",
|
||||
"email": "Septantesix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Septantesix"
|
||||
},
|
||||
{
|
||||
"username": "Tournedix",
|
||||
"email": "Tournedix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Tournedix"
|
||||
},
|
||||
{
|
||||
"username": "Tragicomix",
|
||||
"email": "Tragicomix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Tragicomix"
|
||||
},
|
||||
{
|
||||
"username": "Coriza",
|
||||
"email": "Coriza.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Coriza"
|
||||
},
|
||||
{
|
||||
"username": "Zerozerosix",
|
||||
"email": "Zerozerosix.gaulois@ens.fr",
|
||||
"last_name": "Gaulois",
|
||||
"first_name": "Zerozerosix"
|
||||
}
|
||||
]
|
614
gestioncof/management/data/romains.json
Normal file
614
gestioncof/management/data/romains.json
Normal file
|
@ -0,0 +1,614 @@
|
|||
[
|
||||
{
|
||||
"username": "Abel",
|
||||
"email": "Abel.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Abel"
|
||||
},
|
||||
{
|
||||
"username": "Abelardus",
|
||||
"email": "Abelardus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Abelardus"
|
||||
},
|
||||
{
|
||||
"username": "Abrahamus",
|
||||
"email": "Abrahamus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Abrahamus"
|
||||
},
|
||||
{
|
||||
"username": "Acacius",
|
||||
"email": "Acacius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Acacius"
|
||||
},
|
||||
{
|
||||
"username": "Accius",
|
||||
"email": "Accius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Accius"
|
||||
},
|
||||
{
|
||||
"username": "Achaicus",
|
||||
"email": "Achaicus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Achaicus"
|
||||
},
|
||||
{
|
||||
"username": "Achill",
|
||||
"email": "Achill.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Achill"
|
||||
},
|
||||
{
|
||||
"username": "Achilles",
|
||||
"email": "Achilles.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Achilles"
|
||||
},
|
||||
{
|
||||
"username": "Achilleus",
|
||||
"email": "Achilleus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Achilleus"
|
||||
},
|
||||
{
|
||||
"username": "Acrisius",
|
||||
"email": "Acrisius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Acrisius"
|
||||
},
|
||||
{
|
||||
"username": "Actaeon",
|
||||
"email": "Actaeon.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Actaeon"
|
||||
},
|
||||
{
|
||||
"username": "Acteon",
|
||||
"email": "Acteon.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Acteon"
|
||||
},
|
||||
{
|
||||
"username": "Adalricus",
|
||||
"email": "Adalricus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adalricus"
|
||||
},
|
||||
{
|
||||
"username": "Adelfonsus",
|
||||
"email": "Adelfonsus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adelfonsus"
|
||||
},
|
||||
{
|
||||
"username": "Adelphus",
|
||||
"email": "Adelphus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adelphus"
|
||||
},
|
||||
{
|
||||
"username": "Adeodatus",
|
||||
"email": "Adeodatus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adeodatus"
|
||||
},
|
||||
{
|
||||
"username": "Adolfus",
|
||||
"email": "Adolfus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adolfus"
|
||||
},
|
||||
{
|
||||
"username": "Adolphus",
|
||||
"email": "Adolphus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adolphus"
|
||||
},
|
||||
{
|
||||
"username": "Adrastus",
|
||||
"email": "Adrastus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adrastus"
|
||||
},
|
||||
{
|
||||
"username": "Adrianus",
|
||||
"email": "Adrianus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Adrianus"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6gidius",
|
||||
"email": "\u00c6gidius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6gidius"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6lia",
|
||||
"email": "\u00c6lia.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6lia"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6lianus",
|
||||
"email": "\u00c6lianus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6lianus"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6milianus",
|
||||
"email": "\u00c6milianus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6milianus"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6milius",
|
||||
"email": "\u00c6milius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6milius"
|
||||
},
|
||||
{
|
||||
"username": "Aeneas",
|
||||
"email": "Aeneas.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Aeneas"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6olus",
|
||||
"email": "\u00c6olus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6olus"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6schylus",
|
||||
"email": "\u00c6schylus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6schylus"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6son",
|
||||
"email": "\u00c6son.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6son"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6sop",
|
||||
"email": "\u00c6sop.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6sop"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6ther",
|
||||
"email": "\u00c6ther.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6ther"
|
||||
},
|
||||
{
|
||||
"username": "\u00c6tius",
|
||||
"email": "\u00c6tius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "\u00c6tius"
|
||||
},
|
||||
{
|
||||
"username": "Agapetus",
|
||||
"email": "Agapetus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Agapetus"
|
||||
},
|
||||
{
|
||||
"username": "Agapitus",
|
||||
"email": "Agapitus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Agapitus"
|
||||
},
|
||||
{
|
||||
"username": "Agapius",
|
||||
"email": "Agapius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Agapius"
|
||||
},
|
||||
{
|
||||
"username": "Agathangelus",
|
||||
"email": "Agathangelus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Agathangelus"
|
||||
},
|
||||
{
|
||||
"username": "Aigidius",
|
||||
"email": "Aigidius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Aigidius"
|
||||
},
|
||||
{
|
||||
"username": "Aiolus",
|
||||
"email": "Aiolus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Aiolus"
|
||||
},
|
||||
{
|
||||
"username": "Ajax",
|
||||
"email": "Ajax.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Ajax"
|
||||
},
|
||||
{
|
||||
"username": "Alair",
|
||||
"email": "Alair.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alair"
|
||||
},
|
||||
{
|
||||
"username": "Alaricus",
|
||||
"email": "Alaricus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alaricus"
|
||||
},
|
||||
{
|
||||
"username": "Albanus",
|
||||
"email": "Albanus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Albanus"
|
||||
},
|
||||
{
|
||||
"username": "Alberic",
|
||||
"email": "Alberic.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alberic"
|
||||
},
|
||||
{
|
||||
"username": "Albericus",
|
||||
"email": "Albericus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Albericus"
|
||||
},
|
||||
{
|
||||
"username": "Albertus",
|
||||
"email": "Albertus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Albertus"
|
||||
},
|
||||
{
|
||||
"username": "Albinus",
|
||||
"email": "Albinus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Albinus"
|
||||
},
|
||||
{
|
||||
"username": "Albus",
|
||||
"email": "Albus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Albus"
|
||||
},
|
||||
{
|
||||
"username": "Alcaeus",
|
||||
"email": "Alcaeus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alcaeus"
|
||||
},
|
||||
{
|
||||
"username": "Alcander",
|
||||
"email": "Alcander.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alcander"
|
||||
},
|
||||
{
|
||||
"username": "Alcimus",
|
||||
"email": "Alcimus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alcimus"
|
||||
},
|
||||
{
|
||||
"username": "Alcinder",
|
||||
"email": "Alcinder.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alcinder"
|
||||
},
|
||||
{
|
||||
"username": "Alerio",
|
||||
"email": "Alerio.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alerio"
|
||||
},
|
||||
{
|
||||
"username": "Alexandrus",
|
||||
"email": "Alexandrus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alexandrus"
|
||||
},
|
||||
{
|
||||
"username": "Alexis",
|
||||
"email": "Alexis.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alexis"
|
||||
},
|
||||
{
|
||||
"username": "Alexius",
|
||||
"email": "Alexius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alexius"
|
||||
},
|
||||
{
|
||||
"username": "Alexus",
|
||||
"email": "Alexus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alexus"
|
||||
},
|
||||
{
|
||||
"username": "Alfonsus",
|
||||
"email": "Alfonsus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alfonsus"
|
||||
},
|
||||
{
|
||||
"username": "Alfredus",
|
||||
"email": "Alfredus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alfredus"
|
||||
},
|
||||
{
|
||||
"username": "Almericus",
|
||||
"email": "Almericus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Almericus"
|
||||
},
|
||||
{
|
||||
"username": "Aloisius",
|
||||
"email": "Aloisius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Aloisius"
|
||||
},
|
||||
{
|
||||
"username": "Aloysius",
|
||||
"email": "Aloysius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Aloysius"
|
||||
},
|
||||
{
|
||||
"username": "Alphaeus",
|
||||
"email": "Alphaeus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alphaeus"
|
||||
},
|
||||
{
|
||||
"username": "Alpheaus",
|
||||
"email": "Alpheaus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alpheaus"
|
||||
},
|
||||
{
|
||||
"username": "Alpheus",
|
||||
"email": "Alpheus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alpheus"
|
||||
},
|
||||
{
|
||||
"username": "Alphoeus",
|
||||
"email": "Alphoeus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alphoeus"
|
||||
},
|
||||
{
|
||||
"username": "Alphonsus",
|
||||
"email": "Alphonsus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alphonsus"
|
||||
},
|
||||
{
|
||||
"username": "Alphonzus",
|
||||
"email": "Alphonzus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alphonzus"
|
||||
},
|
||||
{
|
||||
"username": "Alvinius",
|
||||
"email": "Alvinius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alvinius"
|
||||
},
|
||||
{
|
||||
"username": "Alvredus",
|
||||
"email": "Alvredus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Alvredus"
|
||||
},
|
||||
{
|
||||
"username": "Amadeus",
|
||||
"email": "Amadeus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amadeus"
|
||||
},
|
||||
{
|
||||
"username": "Amaliricus",
|
||||
"email": "Amaliricus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amaliricus"
|
||||
},
|
||||
{
|
||||
"username": "Amandus",
|
||||
"email": "Amandus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amandus"
|
||||
},
|
||||
{
|
||||
"username": "Amantius",
|
||||
"email": "Amantius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amantius"
|
||||
},
|
||||
{
|
||||
"username": "Amarandus",
|
||||
"email": "Amarandus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amarandus"
|
||||
},
|
||||
{
|
||||
"username": "Amaranthus",
|
||||
"email": "Amaranthus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amaranthus"
|
||||
},
|
||||
{
|
||||
"username": "Amatus",
|
||||
"email": "Amatus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amatus"
|
||||
},
|
||||
{
|
||||
"username": "Ambrosianus",
|
||||
"email": "Ambrosianus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Ambrosianus"
|
||||
},
|
||||
{
|
||||
"username": "Ambrosius",
|
||||
"email": "Ambrosius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Ambrosius"
|
||||
},
|
||||
{
|
||||
"username": "Amedeus",
|
||||
"email": "Amedeus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amedeus"
|
||||
},
|
||||
{
|
||||
"username": "Americus",
|
||||
"email": "Americus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Americus"
|
||||
},
|
||||
{
|
||||
"username": "Amlethus",
|
||||
"email": "Amlethus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amlethus"
|
||||
},
|
||||
{
|
||||
"username": "Amletus",
|
||||
"email": "Amletus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amletus"
|
||||
},
|
||||
{
|
||||
"username": "Amor",
|
||||
"email": "Amor.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amor"
|
||||
},
|
||||
{
|
||||
"username": "Ampelius",
|
||||
"email": "Ampelius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Ampelius"
|
||||
},
|
||||
{
|
||||
"username": "Amphion",
|
||||
"email": "Amphion.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Amphion"
|
||||
},
|
||||
{
|
||||
"username": "Anacletus",
|
||||
"email": "Anacletus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anacletus"
|
||||
},
|
||||
{
|
||||
"username": "Anastasius",
|
||||
"email": "Anastasius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anastasius"
|
||||
},
|
||||
{
|
||||
"username": "Anastatius",
|
||||
"email": "Anastatius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anastatius"
|
||||
},
|
||||
{
|
||||
"username": "Anastius",
|
||||
"email": "Anastius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anastius"
|
||||
},
|
||||
{
|
||||
"username": "Anatolius",
|
||||
"email": "Anatolius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anatolius"
|
||||
},
|
||||
{
|
||||
"username": "Androcles",
|
||||
"email": "Androcles.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Androcles"
|
||||
},
|
||||
{
|
||||
"username": "Andronicus",
|
||||
"email": "Andronicus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Andronicus"
|
||||
},
|
||||
{
|
||||
"username": "Anencletus",
|
||||
"email": "Anencletus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anencletus"
|
||||
},
|
||||
{
|
||||
"username": "Angelicus",
|
||||
"email": "Angelicus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Angelicus"
|
||||
},
|
||||
{
|
||||
"username": "Angelus",
|
||||
"email": "Angelus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Angelus"
|
||||
},
|
||||
{
|
||||
"username": "Anicetus",
|
||||
"email": "Anicetus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Anicetus"
|
||||
},
|
||||
{
|
||||
"username": "Antigonus",
|
||||
"email": "Antigonus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Antigonus"
|
||||
},
|
||||
{
|
||||
"username": "Antipater",
|
||||
"email": "Antipater.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Antipater"
|
||||
},
|
||||
{
|
||||
"username": "Antoninus",
|
||||
"email": "Antoninus.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Antoninus"
|
||||
},
|
||||
{
|
||||
"username": "Antonius",
|
||||
"email": "Antonius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Antonius"
|
||||
},
|
||||
{
|
||||
"username": "Aphrodisius",
|
||||
"email": "Aphrodisius.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Aphrodisius"
|
||||
},
|
||||
{
|
||||
"username": "Apollinaris",
|
||||
"email": "Apollinaris.Romain@ens.fr",
|
||||
"last_name": "Romain",
|
||||
"first_name": "Apollinaris"
|
||||
}
|
||||
]
|
17
gestioncof/migrations/0009_delete_clipper.py
Normal file
17
gestioncof/migrations/0009_delete_clipper.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0008_py3'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='Clipper',
|
||||
),
|
||||
]
|
16
gestioncof/migrations/0010_delete_custommail.py
Normal file
16
gestioncof/migrations/0010_delete_custommail.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('gestioncof', '0009_delete_clipper'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.DeleteModel(
|
||||
name='CustomMail',
|
||||
),
|
||||
]
|
|
@ -1,9 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models
|
||||
from django.dispatch import receiver
|
||||
from django.contrib.auth.models import User
|
||||
|
@ -102,22 +98,6 @@ class Club(models.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CustomMail(models.Model):
|
||||
shortname = models.SlugField(max_length=50, blank=False)
|
||||
title = models.CharField("Titre", max_length=200, blank=False)
|
||||
content = models.TextField("Contenu", blank=False)
|
||||
comments = models.TextField("Informations contextuelles sur le mail",
|
||||
blank=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Mail personnalisable"
|
||||
verbose_name_plural = "Mails personnalisables"
|
||||
|
||||
def __str__(self):
|
||||
return "%s: %s" % (self.shortname, self.title)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Event(models.Model):
|
||||
title = models.CharField("Titre", max_length=200)
|
||||
|
@ -264,15 +244,6 @@ class SurveyAnswer(models.Model):
|
|||
self.survey.title)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class Clipper(models.Model):
|
||||
username = models.CharField("Identifiant", max_length=20)
|
||||
fullname = models.CharField("Nom complet", max_length=200)
|
||||
|
||||
def __str__(self):
|
||||
return "Clipper %s" % self.username
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CalendarSubscription(models.Model):
|
||||
token = models.UUIDField()
|
||||
|
|
54
gestioncof/petits_cours_forms.py
Normal file
54
gestioncof/petits_cours_forms.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from captcha.fields import ReCaptchaField
|
||||
|
||||
from django import forms
|
||||
from django.forms import ModelForm
|
||||
from django.forms.models import inlineformset_factory, BaseInlineFormSet
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from gestioncof.petits_cours_models import PetitCoursDemande, PetitCoursAbility
|
||||
|
||||
|
||||
class BaseMatieresFormSet(BaseInlineFormSet):
|
||||
def clean(self):
|
||||
super(BaseMatieresFormSet, self).clean()
|
||||
if any(self.errors):
|
||||
# Don't bother validating the formset unless each form is
|
||||
# valid on its own
|
||||
return
|
||||
matieres = []
|
||||
for i in range(0, self.total_form_count()):
|
||||
form = self.forms[i]
|
||||
if not form.cleaned_data:
|
||||
continue
|
||||
matiere = form.cleaned_data['matiere']
|
||||
niveau = form.cleaned_data['niveau']
|
||||
delete = form.cleaned_data['DELETE']
|
||||
if not delete and (matiere, niveau) in matieres:
|
||||
raise forms.ValidationError(
|
||||
"Vous ne pouvez pas vous inscrire deux fois pour la "
|
||||
"même matiere avec le même niveau.")
|
||||
matieres.append((matiere, niveau))
|
||||
|
||||
|
||||
class DemandeForm(ModelForm):
|
||||
captcha = ReCaptchaField(attrs={'theme': 'clean', 'lang': 'fr'})
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DemandeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['matieres'].help_text = ''
|
||||
|
||||
class Meta:
|
||||
model = PetitCoursDemande
|
||||
fields = ('name', 'email', 'phone', 'quand', 'freq', 'lieu',
|
||||
'matieres', 'agrege_requis', 'niveau', 'remarques')
|
||||
widgets = {'matieres': forms.CheckboxSelectMultiple}
|
||||
|
||||
|
||||
MatieresFormSet = inlineformset_factory(
|
||||
User,
|
||||
PetitCoursAbility,
|
||||
fields=("matiere", "niveau", "agrege"),
|
||||
formset=BaseMatieresFormSet
|
||||
)
|
|
@ -1,14 +1,11 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from functools import reduce
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Min
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from django.utils.six.moves import reduce
|
||||
|
||||
|
||||
def choices_length(choices):
|
||||
|
@ -24,7 +21,6 @@ LEVELS_CHOICES = (
|
|||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class PetitCoursSubject(models.Model):
|
||||
name = models.CharField(_("Matière"), max_length=30)
|
||||
users = models.ManyToManyField(User, related_name="petits_cours_matieres",
|
||||
|
@ -38,7 +34,6 @@ class PetitCoursSubject(models.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class PetitCoursAbility(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matière"))
|
||||
|
@ -52,11 +47,11 @@ class PetitCoursAbility(models.Model):
|
|||
verbose_name_plural = "Compétences des petits cours"
|
||||
|
||||
def __str__(self):
|
||||
return "%s - %s - %s" % (self.user.username,
|
||||
self.matiere, self.niveau)
|
||||
return "{:s} - {!s} - {:s}".format(
|
||||
self.user.username, self.matiere, self.niveau
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class PetitCoursDemande(models.Model):
|
||||
name = models.CharField(_("Nom/prénom"), max_length=200)
|
||||
email = models.CharField(_("Adresse email"), max_length=300)
|
||||
|
@ -70,7 +65,7 @@ class PetitCoursDemande(models.Model):
|
|||
freq = models.CharField(
|
||||
_("Fréquence"),
|
||||
help_text=_("Indiquez ici la fréquence envisagée "
|
||||
+ "(hebdomadaire, 2 fois par semaine, ...)"),
|
||||
"(hebdomadaire, 2 fois par semaine, ...)"),
|
||||
max_length=300, blank=True)
|
||||
lieu = models.CharField(
|
||||
_("Lieu (si préférence)"),
|
||||
|
@ -94,16 +89,42 @@ class PetitCoursDemande(models.Model):
|
|||
blank=True, null=True)
|
||||
created = models.DateTimeField(_("Date de création"), auto_now_add=True)
|
||||
|
||||
def get_candidates(self, redo=False):
|
||||
"""
|
||||
Donne la liste des profs disponibles pour chaque matière de la demande.
|
||||
- On ne donne que les agrégés si c'est demandé
|
||||
- Si ``redo`` vaut ``True``, cela signifie qu'on retraite la demande et
|
||||
il ne faut pas proposer à nouveau des noms qui ont déjà été proposés
|
||||
"""
|
||||
for matiere in self.matieres.all():
|
||||
candidates = PetitCoursAbility.objects.filter(
|
||||
matiere=matiere,
|
||||
niveau=self.niveau,
|
||||
user__profile__is_cof=True,
|
||||
user__profile__petits_cours_accept=True
|
||||
)
|
||||
if self.agrege_requis:
|
||||
candidates = candidates.filter(agrege=True)
|
||||
if redo:
|
||||
attrs = self.petitcoursattribution_set.filter(matiere=matiere)
|
||||
already_proposed = [
|
||||
attr.user
|
||||
for attr in attrs
|
||||
]
|
||||
candidates = candidates.exclude(user__in=already_proposed)
|
||||
candidates = candidates.order_by('?').select_related().all()
|
||||
yield (matiere, candidates)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Demande de petits cours"
|
||||
verbose_name_plural = "Demandes de petits cours"
|
||||
|
||||
def __str__(self):
|
||||
return "Demande %d du %s" % (self.id,
|
||||
self.created.strftime("%d %b %Y"))
|
||||
return "Demande {:d} du {:s}".format(
|
||||
self.id, self.created.strftime("%d %b %Y")
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class PetitCoursAttribution(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
demande = models.ForeignKey(PetitCoursDemande, verbose_name=_("Demande"))
|
||||
|
@ -118,20 +139,40 @@ class PetitCoursAttribution(models.Model):
|
|||
verbose_name_plural = "Attributions de petits cours"
|
||||
|
||||
def __str__(self):
|
||||
return "Attribution de la demande %d à %s pour %s" \
|
||||
% (self.demande.id, self.user.username, self.matiere)
|
||||
return "Attribution de la demande {:d} à {:s} pour {!s}".format(
|
||||
self.demande.id, self.user.username, self.matiere
|
||||
)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class PetitCoursAttributionCounter(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
matiere = models.ForeignKey(PetitCoursSubject, verbose_name=_("Matiere"))
|
||||
count = models.IntegerField("Nombre d'envois", default=0)
|
||||
|
||||
@classmethod
|
||||
def get_uptodate(cls, user, matiere):
|
||||
"""
|
||||
Donne le compteur de l'utilisateur pour cette matière. Si le compteur
|
||||
n'existe pas encore, il est initialisé avec le minimum des valeurs des
|
||||
compteurs de tout le monde.
|
||||
"""
|
||||
counter, created = cls.objects.get_or_create(
|
||||
user=user, matiere=matiere)
|
||||
if created:
|
||||
mincount = (
|
||||
cls.objects.filter(matiere=matiere).exclude(user=user)
|
||||
.aggregate(Min('count'))
|
||||
['count__min']
|
||||
)
|
||||
counter.count = mincount
|
||||
counter.save()
|
||||
return counter
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Compteur d'attribution de petits cours"
|
||||
verbose_name_plural = "Compteurs d'attributions de petits cours"
|
||||
|
||||
def __str__(self):
|
||||
return "%d demandes envoyées à %s pour %s" \
|
||||
% (self.count, self.user.username, self.matiere)
|
||||
return "{:d} demandes envoyées à {:s} pour {!s}".format(
|
||||
self.count, self.user.username, self.matiere
|
||||
)
|
||||
|
|
|
@ -1,37 +1,27 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
import json
|
||||
from datetime import datetime
|
||||
from custommail.shortcuts import render_custom_mail
|
||||
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.core import mail
|
||||
from django.core.mail import EmailMessage
|
||||
from django.forms import ModelForm
|
||||
from django import forms
|
||||
from django.forms.models import inlineformset_factory, BaseInlineFormSet
|
||||
from django.contrib.auth.models import User
|
||||
from django.views.generic import ListView
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.generic import ListView, DetailView
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.template import loader
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Min
|
||||
from django.contrib import messages
|
||||
|
||||
from gestioncof.models import CofProfile
|
||||
from gestioncof.petits_cours_models import PetitCoursDemande, \
|
||||
PetitCoursAttribution, PetitCoursAttributionCounter, PetitCoursAbility, \
|
||||
PetitCoursSubject
|
||||
from gestioncof.petits_cours_models import (
|
||||
PetitCoursDemande, PetitCoursAttribution, PetitCoursAttributionCounter,
|
||||
PetitCoursAbility, PetitCoursSubject
|
||||
)
|
||||
from gestioncof.petits_cours_forms import DemandeForm, MatieresFormSet
|
||||
from gestioncof.decorators import buro_required
|
||||
from gestioncof.shared import lock_table, unlock_tables
|
||||
|
||||
from captcha.fields import ReCaptchaField
|
||||
|
||||
from datetime import datetime
|
||||
import base64
|
||||
import json
|
||||
|
||||
|
||||
class DemandeListView(ListView):
|
||||
model = PetitCoursDemande
|
||||
|
@ -41,47 +31,17 @@ class DemandeListView(ListView):
|
|||
def get_queryset(self):
|
||||
return PetitCoursDemande.objects.order_by('traitee', '-id').all()
|
||||
|
||||
@method_decorator(buro_required)
|
||||
def dispatch(self, *args, **kwargs):
|
||||
return super(DemandeListView, self).dispatch(*args, **kwargs)
|
||||
|
||||
class DemandeDetailView(DetailView):
|
||||
model = PetitCoursDemande
|
||||
template_name = "gestioncof/details_demande_petit_cours.html"
|
||||
context_object_name = "demande"
|
||||
|
||||
@buro_required
|
||||
def details(request, demande_id):
|
||||
demande = get_object_or_404(PetitCoursDemande, id=demande_id)
|
||||
attributions = PetitCoursAttribution.objects.filter(demande=demande).all()
|
||||
return render(request, "details_demande_petit_cours.html",
|
||||
{"demande": demande,
|
||||
"attributions": attributions})
|
||||
|
||||
|
||||
def _get_attrib_counter(user, matiere):
|
||||
counter, created = PetitCoursAttributionCounter \
|
||||
.objects.get_or_create(user=user, matiere=matiere)
|
||||
if created:
|
||||
mincount = PetitCoursAttributionCounter.objects \
|
||||
.filter(matiere=matiere).exclude(user=user).all() \
|
||||
.aggregate(Min('count'))
|
||||
counter.count = mincount['count__min']
|
||||
counter.save()
|
||||
return counter
|
||||
|
||||
|
||||
def _get_demande_candidates(demande, redo=False):
|
||||
for matiere in demande.matieres.all():
|
||||
candidates = PetitCoursAbility.objects.filter(matiere=matiere,
|
||||
niveau=demande.niveau)
|
||||
candidates = candidates.filter(user__profile__is_cof=True,
|
||||
user__profile__petits_cours_accept=True)
|
||||
if demande.agrege_requis:
|
||||
candidates = candidates.filter(agrege=True)
|
||||
if redo:
|
||||
attributions = PetitCoursAttribution.objects \
|
||||
.filter(demande=demande, matiere=matiere).all()
|
||||
for attrib in attributions:
|
||||
candidates = candidates.exclude(user=attrib.user)
|
||||
candidates = candidates.order_by('?').select_related().all()
|
||||
yield (matiere, candidates)
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DemandeDetailView, self).get_context_data(**kwargs)
|
||||
obj = self.object
|
||||
context['attributions'] = obj.petitcoursattribution_set.all()
|
||||
return context
|
||||
|
||||
|
||||
@buro_required
|
||||
|
@ -95,12 +55,15 @@ def traitement(request, demande_id, redo=False):
|
|||
proposed_for = {}
|
||||
unsatisfied = []
|
||||
attribdata = {}
|
||||
for matiere, candidates in _get_demande_candidates(demande, redo):
|
||||
for matiere, candidates in demande.get_candidates(redo):
|
||||
if candidates:
|
||||
tuples = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
tuples.append((candidate, _get_attrib_counter(user, matiere)))
|
||||
tuples.append((
|
||||
candidate,
|
||||
PetitCoursAttributionCounter.get_uptodate(user, matiere)
|
||||
))
|
||||
tuples = sorted(tuples, key=lambda c: c[1].count)
|
||||
candidates, _ = zip(*tuples)
|
||||
candidates = candidates[0:min(3, len(candidates))]
|
||||
|
@ -131,7 +94,7 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
|
|||
proposed_for = proposed_for.items()
|
||||
attribdata = list(attribdata.items())
|
||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||
mainmail = loader.render_to_string("petits-cours-mail-demandeur.txt", {
|
||||
mainmail = render_custom_mail("petits-cours-mail-demandeur", {
|
||||
"proposals": proposals,
|
||||
"unsatisfied": unsatisfied,
|
||||
"extra":
|
||||
|
@ -139,7 +102,10 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
|
|||
'style="width:99%; height: 90px;">'
|
||||
'</textarea>'
|
||||
})
|
||||
return render(request, "traitement_demande_petit_cours.html",
|
||||
if errors is not None:
|
||||
for error in errors:
|
||||
messages.error(request, error)
|
||||
return render(request, "gestioncof/traitement_demande_petit_cours.html",
|
||||
{"demande": demande,
|
||||
"unsatisfied": unsatisfied,
|
||||
"proposals": proposals,
|
||||
|
@ -148,19 +114,20 @@ def _finalize_traitement(request, demande, proposals, proposed_for,
|
|||
"mainmail": mainmail,
|
||||
"attribdata": json.dumps(attribdata),
|
||||
"redo": redo,
|
||||
"errors": errors,
|
||||
})
|
||||
|
||||
|
||||
def _generate_eleve_email(demande, proposed_for):
|
||||
proposed_mails = []
|
||||
for user, matieres in proposed_for:
|
||||
msg = loader.render_to_string("petits-cours-mail-eleve.txt", {
|
||||
return [
|
||||
(
|
||||
user,
|
||||
render_custom_mail('petit-cours-mail-eleve', {
|
||||
"demande": demande,
|
||||
"matieres": matieres
|
||||
})
|
||||
proposed_mails.append((user, msg))
|
||||
return proposed_mails
|
||||
)
|
||||
for user, matieres in proposed_for
|
||||
]
|
||||
|
||||
|
||||
def _traitement_other_preparing(request, demande):
|
||||
|
@ -170,7 +137,7 @@ def _traitement_other_preparing(request, demande):
|
|||
proposed_for = {}
|
||||
attribdata = {}
|
||||
errors = []
|
||||
for matiere, candidates in _get_demande_candidates(demande, redo):
|
||||
for matiere, candidates in demande.get_candidates(redo):
|
||||
if candidates:
|
||||
candidates = dict([(candidate.user.id, candidate.user)
|
||||
for candidate in candidates])
|
||||
|
@ -178,17 +145,19 @@ def _traitement_other_preparing(request, demande):
|
|||
proposals[matiere] = []
|
||||
for choice_id in range(min(3, len(candidates))):
|
||||
choice = int(
|
||||
request.POST["proposal-%d-%d" % (matiere.id, choice_id)])
|
||||
request.POST["proposal-{:d}-{:d}"
|
||||
.format(matiere.id, choice_id)]
|
||||
)
|
||||
if choice == -1:
|
||||
continue
|
||||
if choice not in candidates:
|
||||
errors.append("Choix invalide pour la proposition %d"
|
||||
"en %s" % (choice_id + 1, matiere))
|
||||
errors.append("Choix invalide pour la proposition {:d}"
|
||||
"en {!s}".format(choice_id + 1, matiere))
|
||||
continue
|
||||
user = candidates[choice]
|
||||
if user in proposals[matiere]:
|
||||
errors.append("La proposition %d en %s est un doublon"
|
||||
% (choice_id + 1, matiere))
|
||||
errors.append("La proposition {:d} en {!s} est un doublon"
|
||||
.format(choice_id + 1, matiere))
|
||||
continue
|
||||
proposals[matiere].append(user)
|
||||
attribdata[matiere.id].append(user.id)
|
||||
|
@ -197,10 +166,11 @@ def _traitement_other_preparing(request, demande):
|
|||
else:
|
||||
proposed_for[user].append(matiere)
|
||||
if not proposals[matiere]:
|
||||
errors.append("Aucune proposition pour %s" % (matiere,))
|
||||
errors.append("Aucune proposition pour {!s}".format(matiere))
|
||||
elif len(proposals[matiere]) < 3:
|
||||
errors.append("Seulement %d proposition%s pour %s"
|
||||
% (len(proposals[matiere]),
|
||||
errors.append("Seulement {:d} proposition{:s} pour {!s}"
|
||||
.format(
|
||||
len(proposals[matiere]),
|
||||
"s" if len(proposals[matiere]) > 1 else "",
|
||||
matiere))
|
||||
else:
|
||||
|
@ -219,12 +189,15 @@ def _traitement_other(request, demande, redo):
|
|||
proposed_for = {}
|
||||
unsatisfied = []
|
||||
attribdata = {}
|
||||
for matiere, candidates in _get_demande_candidates(demande, redo):
|
||||
for matiere, candidates in demande.get_candidates(redo):
|
||||
if candidates:
|
||||
tuples = []
|
||||
for candidate in candidates:
|
||||
user = candidate.user
|
||||
tuples.append((candidate, _get_attrib_counter(user, matiere)))
|
||||
tuples.append((
|
||||
candidate,
|
||||
PetitCoursAttributionCounter.get_uptodate(user, matiere)
|
||||
))
|
||||
tuples = sorted(tuples, key=lambda c: c[1].count)
|
||||
candidates, _ = zip(*tuples)
|
||||
attribdata[matiere.id] = []
|
||||
|
@ -241,7 +214,8 @@ def _traitement_other(request, demande, redo):
|
|||
unsatisfied.append(matiere)
|
||||
proposals = proposals.items()
|
||||
proposed_for = proposed_for.items()
|
||||
return render(request, "traitement_demande_petit_cours_autre_niveau.html",
|
||||
return render(request,
|
||||
"gestioncof/traitement_demande_petit_cours_autre_niveau.html",
|
||||
{"demande": demande,
|
||||
"unsatisfied": unsatisfied,
|
||||
"proposals": proposals,
|
||||
|
@ -272,25 +246,27 @@ def _traitement_post(request, demande):
|
|||
proposals_list = proposals.items()
|
||||
proposed_for = proposed_for.items()
|
||||
proposed_mails = _generate_eleve_email(demande, proposed_for)
|
||||
mainmail = loader.render_to_string("petits-cours-mail-demandeur.txt", {
|
||||
mainmail_object, mainmail_body = render_custom_mail(
|
||||
"petits-cours-mail-demandeur",
|
||||
{
|
||||
"proposals": proposals_list,
|
||||
"unsatisfied": unsatisfied,
|
||||
"extra": extra,
|
||||
})
|
||||
"extra": extra
|
||||
}
|
||||
)
|
||||
frommail = settings.MAIL_DATA['petits_cours']['FROM']
|
||||
bccaddress = settings.MAIL_DATA['petits_cours']['BCC']
|
||||
replyto = settings.MAIL_DATA['petits_cours']['REPLYTO']
|
||||
mails_to_send = []
|
||||
for (user, msg) in proposed_mails:
|
||||
msg = EmailMessage("Petits cours ENS par le COF", msg,
|
||||
frommail, [user.email],
|
||||
for (user, (mail_object, body)) in proposed_mails:
|
||||
msg = mail.EmailMessage(mail_object, body, frommail, [user.email],
|
||||
[bccaddress], headers={'Reply-To': replyto})
|
||||
mails_to_send.append(msg)
|
||||
mails_to_send.append(EmailMessage("Cours particuliers ENS", mainmail,
|
||||
mails_to_send.append(mail.EmailMessage(mainmail_object, mainmail_body,
|
||||
frommail, [demande.email],
|
||||
[bccaddress],
|
||||
headers={'Reply-To': replyto}))
|
||||
connection = mail.get_connection(fail_silently=True)
|
||||
connection = mail.get_connection(fail_silently=False)
|
||||
connection.send_messages(mails_to_send)
|
||||
lock_table(PetitCoursAttributionCounter, PetitCoursAttribution, User)
|
||||
for matiere in proposals:
|
||||
|
@ -307,43 +283,18 @@ def _traitement_post(request, demande):
|
|||
demande.traitee_par = request.user
|
||||
demande.processed = datetime.now()
|
||||
demande.save()
|
||||
return render(request, "traitement_demande_petit_cours_success.html",
|
||||
return render(request,
|
||||
"gestioncof/traitement_demande_petit_cours_success.html",
|
||||
{"demande": demande,
|
||||
"redo": redo,
|
||||
})
|
||||
|
||||
|
||||
class BaseMatieresFormSet(BaseInlineFormSet):
|
||||
def clean(self):
|
||||
super(BaseMatieresFormSet, self).clean()
|
||||
if any(self.errors):
|
||||
# Don't bother validating the formset unless each form is
|
||||
# valid on its own
|
||||
return
|
||||
matieres = []
|
||||
for i in range(0, self.total_form_count()):
|
||||
form = self.forms[i]
|
||||
if not form.cleaned_data:
|
||||
continue
|
||||
matiere = form.cleaned_data['matiere']
|
||||
niveau = form.cleaned_data['niveau']
|
||||
delete = form.cleaned_data['DELETE']
|
||||
if not delete and (matiere, niveau) in matieres:
|
||||
raise forms.ValidationError(
|
||||
"Vous ne pouvez pas vous inscrire deux fois pour la "
|
||||
"même matiere avec le même niveau.")
|
||||
matieres.append((matiere, niveau))
|
||||
|
||||
|
||||
@login_required
|
||||
def inscription(request):
|
||||
profile, created = CofProfile.objects.get_or_create(user=request.user)
|
||||
if not profile.is_cof:
|
||||
return redirect("cof-denied")
|
||||
MatieresFormSet = inlineformset_factory(User, PetitCoursAbility,
|
||||
fields=("matiere", "niveau",
|
||||
"agrege",),
|
||||
formset=BaseMatieresFormSet)
|
||||
success = False
|
||||
if request.method == "POST":
|
||||
formset = MatieresFormSet(request.POST, instance=request.user)
|
||||
|
@ -354,10 +305,14 @@ def inscription(request):
|
|||
profile.save()
|
||||
lock_table(PetitCoursAttributionCounter, PetitCoursAbility, User,
|
||||
PetitCoursSubject)
|
||||
abilities = PetitCoursAbility.objects \
|
||||
.filter(user=request.user).all()
|
||||
abilities = (
|
||||
PetitCoursAbility.objects.filter(user=request.user).all()
|
||||
)
|
||||
for ability in abilities:
|
||||
_get_attrib_counter(ability.user, ability.matiere)
|
||||
PetitCoursAttributionCounter.get_uptodate(
|
||||
ability.user,
|
||||
ability.matiere
|
||||
)
|
||||
unlock_tables()
|
||||
success = True
|
||||
formset = MatieresFormSet(instance=request.user)
|
||||
|
@ -369,20 +324,6 @@ def inscription(request):
|
|||
"remarques": profile.petits_cours_remarques})
|
||||
|
||||
|
||||
class DemandeForm(ModelForm):
|
||||
captcha = ReCaptchaField(attrs={'theme': 'clean', 'lang': 'fr'})
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DemandeForm, self).__init__(*args, **kwargs)
|
||||
self.fields['matieres'].help_text = ''
|
||||
|
||||
class Meta:
|
||||
model = PetitCoursDemande
|
||||
fields = ('name', 'email', 'phone', 'quand', 'freq', 'lieu',
|
||||
'matieres', 'agrege_requis', 'niveau', 'remarques')
|
||||
widgets = {'matieres': forms.CheckboxSelectMultiple}
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
def demande(request):
|
||||
success = False
|
||||
|
|
|
@ -9,12 +9,9 @@ from django.conf import settings
|
|||
from django_cas_ng.backends import CASBackend
|
||||
from django_cas_ng.utils import get_cas_client
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import User as DjangoUser
|
||||
from django.db import connection
|
||||
from django.core.mail import send_mail
|
||||
from django.template import Template, Context
|
||||
|
||||
from gestioncof.models import CofProfile, CustomMail
|
||||
from gestioncof.models import CofProfile
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
@ -99,18 +96,3 @@ def unlock_tables(*models):
|
|||
return row
|
||||
|
||||
unlock_table = unlock_tables
|
||||
|
||||
|
||||
def send_custom_mail(to, shortname, context=None, from_email="cof@ens.fr"):
|
||||
if context is None:
|
||||
context = {}
|
||||
if isinstance(to, DjangoUser):
|
||||
context["nom"] = to.get_full_name()
|
||||
context["prenom"] = to.first_name
|
||||
to = to.email
|
||||
mail = CustomMail.objects.get(shortname=shortname)
|
||||
template = Template(mail.content)
|
||||
message = template.render(Context(context))
|
||||
send_mail(mail.title, message,
|
||||
from_email, [to],
|
||||
fail_silently=True)
|
||||
|
|
|
@ -520,22 +520,6 @@ h4.block-title {
|
|||
list-style: none;
|
||||
}
|
||||
|
||||
.success {
|
||||
font-weight: bold;
|
||||
color: #7AB85F;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.success a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.error {
|
||||
font-weight: bold;
|
||||
color: #F6BEBE;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#main form ul.errorlist li {
|
||||
font-weight: bold;
|
||||
color: #B00000;
|
||||
|
@ -816,7 +800,7 @@ input#search_autocomplete {
|
|||
height: 40px;
|
||||
padding: 10px 8px;
|
||||
margin: 0 auto;
|
||||
margin-top: 20px;
|
||||
margin-top: 0px;
|
||||
display: block;
|
||||
color: #aaa;
|
||||
}
|
||||
|
@ -1088,3 +1072,57 @@ tr.awesome{
|
|||
color: white;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.petitcours-raw {
|
||||
padding:20px;
|
||||
background:#fff;
|
||||
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
|
||||
.messages .alert .close {
|
||||
top:0;
|
||||
right:0;
|
||||
}
|
||||
|
||||
.messages .alert {
|
||||
padding:10px 15px;
|
||||
margin:0;
|
||||
border:0;
|
||||
border-radius:0;
|
||||
}
|
||||
|
||||
div.messages div.alert-info {
|
||||
background-color: #659C94;
|
||||
}
|
||||
|
||||
div.messages div.alert-success {
|
||||
background-color: #41C342;
|
||||
}
|
||||
|
||||
div.messages div.alert-warning {
|
||||
background-color: #efa50f;
|
||||
}
|
||||
|
||||
div.messages div.alert-error {
|
||||
background-color: #e14b4b;
|
||||
}
|
||||
|
||||
div.messages div.alert-info div.container,
|
||||
div.messages div.alert-error div.container,
|
||||
div.messages div.alert-warning div.container,
|
||||
div.messages div.alert-success div.container {
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.messages div.alert div.container a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* Help text */
|
||||
|
||||
p.help-block {
|
||||
margin: 5px auto;
|
||||
width: 90%;
|
||||
}
|
||||
|
|
7
gestioncof/static/css/jquery-ui.min.css
vendored
Normal file
7
gestioncof/static/css/jquery-ui.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
13
gestioncof/static/js/jquery-ui.min.js
vendored
Normal file
13
gestioncof/static/js/jquery-ui.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
11
gestioncof/static/js/jquery.ui.touch-punch.min.js
vendored
Normal file
11
gestioncof/static/js/jquery.ui.touch-punch.min.js
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
/*!
|
||||
* jQuery UI Touch Punch 0.2.3
|
||||
*
|
||||
* Copyright 2011–2014, Dave Furfero
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
*
|
||||
* Depends:
|
||||
* jquery.ui.widget.js
|
||||
* jquery.ui.mouse.js
|
||||
*/
|
||||
!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);
|
|
@ -15,7 +15,7 @@
|
|||
{% if clippers %}
|
||||
<li class="autocomplete-header">Utilisateurs <tt>clipper</tt></li>
|
||||
{% for clipper in clippers %}{% if forloop.counter < 5 %}
|
||||
<li class="autocomplete-value"><a href="{% url 'clipper-registration' clipper.username %}">{{ clipper|highlight_clipper:q }}</a></li>
|
||||
<li class="autocomplete-value"><a href="{% url 'clipper-registration' clipper.clipper clipper.fullname %}">{{ clipper|highlight_clipper:q }}</a></li>
|
||||
{% elif forloop.counter == 5 %}<li class="autocomplete-more">...</a>{% endif %}{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -4,13 +4,19 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml" lang="fr">
|
||||
<head>
|
||||
<title>{{ site.name }}</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
{# CSS #}
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/bootstrap.min.css" %}" />
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/cof.css" %}" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Dosis|Dosis:700|Raleway|Roboto:300,300i,700" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{% static "font-awesome/css/font-awesome.min.css" %}">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
{# JS #}
|
||||
<script src="https://code.jquery.com/jquery-3.1.0.min.js" integrity="sha256-cCueBR6CsyA4/9szpPfrX3s49M9vUU5BgtiJj06wt/s=" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
{% block extra_head %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base_header.html" %}
|
||||
{% extends "gestioncof/base_header.html" %}
|
||||
|
||||
{% block interm_content %}
|
||||
<div class="container hidden-xs espace"></div>
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block content %}
|
||||
<div class="petitcours-raw">
|
||||
{% if success %}
|
||||
<p class="success">Votre demande a été enregistrée avec succès !</p>
|
||||
{% else %}
|
||||
<form id="demandecours" method="post" action="{% url "gestioncof.petits_cours_views.demande_raw" %}">
|
||||
{% csrf_token %}
|
||||
<table>
|
||||
{{ form.as_table }}
|
||||
{{ form | bootstrap }}
|
||||
</table>
|
||||
<input type="submit" class="btn-submit" value="Enregistrer" />
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -16,5 +16,21 @@
|
|||
<h2 class="member-status">{% if user.first_name %}{{ user.first_name }}{% else %}<tt>{{ user.username }}</tt>{% endif %}, {% if user.profile.is_cof %}<tt class="user-is-cof">au COF{% else %}<tt class="user-is-not-cof">non-COF{% endif %}</tt></h2>
|
||||
</div><!-- /.container -->
|
||||
</header>
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="messages">
|
||||
<div class="alert alert-{{ message.level_tag }} alert-dismissible fade in{% if message.tags %} {{ message.tags }}{% endif %}">
|
||||
<div class="container">
|
||||
<button class="close" data-dismiss="alert">×</button>
|
||||
{% if 'safe' in message.tags %}
|
||||
{{ message|safe }}
|
||||
{% else %}
|
||||
{{ message }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% block interm_content %}{% endblock %}
|
||||
{% endblock %}
|
|
@ -1,17 +1,10 @@
|
|||
{% extends "base_title.html" %}
|
||||
{% load bootstrap %}
|
||||
|
||||
{% block realcontent %}
|
||||
|
||||
<h2>Calendrier dynamique</h2>
|
||||
|
||||
{% if success %}
|
||||
<p class="success">Calendrier mis à jour avec succès</p>
|
||||
{% endif %}
|
||||
|
||||
{% if error %}
|
||||
<p class="error">{{ error }}</p>
|
||||
{% endif %}
|
||||
|
||||
<p>Ce formulaire vous permet de définir un calendrier dynamique compatible avec
|
||||
n'importe quel logiciel ou application d'agenda. Vous pouvez choisir de
|
||||
souscrire aux événements du COF et/ou aux spectacles BdA.
|
||||
|
@ -36,8 +29,21 @@ souscrire aux événements du COF et/ou aux spectacles BdA.
|
|||
|
||||
<form action="" method="post">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p }}
|
||||
<input type="submit" value="Enregistrer" class="btn btn-primary" />
|
||||
{{ form | bootstrap }}
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" onClick="select(true)">Tout sélectionner</button>
|
||||
<button type="button" class="btn btn-default" onClick="select(false)">Tout désélectionner</button>
|
||||
</p>
|
||||
|
||||
<input type="submit" value="Enregistrer" class="btn btn-primary center-block" />
|
||||
</form>
|
||||
|
||||
<script language="JavaScript">
|
||||
function select(check) {
|
||||
checkboxes = document.getElementsByName("other_shows");
|
||||
for(var i=0, n=checkboxes.length;i<n;i++) {
|
||||
checkboxes[i].checked = check;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -2,9 +2,6 @@
|
|||
|
||||
{% block realcontent %}
|
||||
<h2>Événement: {{ event.title }}</h2>
|
||||
{% if success %}
|
||||
<p class="success">Votre inscription a bien été enregistrée ! Vous pouvez cependant la modifier jusqu'à la fin des inscriptions.</p>
|
||||
{% endif %}
|
||||
{% if event.details %}
|
||||
<p>{{ event.details }}</p>
|
||||
{% endif %}
|
|
@ -5,9 +5,6 @@
|
|||
|
||||
{% block realcontent %}
|
||||
<h2>Modifier mon profil</h2>
|
||||
{% if success %}
|
||||
<p class="success">Votre profil a été mis à jour avec succès !</p>
|
||||
{% endif %}
|
||||
<form id="profile form-horizontal" method="post" action="{% url 'gestioncof.views.profile' %}">
|
||||
<div class="row" style="margin: 0 15%;">
|
||||
{% csrf_token %}
|
8
gestioncof/templates/gestioncof/registration_post.html
Normal file
8
gestioncof/templates/gestioncof/registration_post.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Inscription d'un nouveau membre</h2>
|
||||
<div id="form-placeholder">
|
||||
{% include "gestioncof/registration_form.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -5,13 +5,6 @@
|
|||
|
||||
{% block realcontent %}
|
||||
<h2>Sondage: {{ survey.title }}</h2>
|
||||
{% if success %}
|
||||
{% if deleted %}
|
||||
<p class="success">Votre réponse a bien été supprimée !</p>
|
||||
{% else %}
|
||||
<p class="success">Votre réponse a bien été enregistrée ! Vous pouvez cependant la modifier jusqu'à la fin du sondage.</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if survey.details %}
|
||||
<p>{{ survey.details }}</p>
|
||||
{% endif %}
|
|
@ -30,10 +30,16 @@
|
|||
<h4>Mails pour les membres proposés :</h4>
|
||||
{% for proposeduser, mail in proposed_mails %}
|
||||
<h5>Pour {{ proposeduser }}:</h5>
|
||||
<pre>{{ mail }}</pre>
|
||||
{% with object=mail.0 content=mail.1 %}
|
||||
<pre>{{ object }}</pre>
|
||||
<pre>{{ content }}</pre>
|
||||
{% endwith %}
|
||||
{% endfor %}
|
||||
<h4>Mail pour l'auteur de la demande :</h4>
|
||||
<pre style="margin-top: 15px;">{{ mainmail|safe }}</pre>
|
||||
{% with object=mainmail.0 content=mainmail.1 %}
|
||||
<pre style="margin-top: 15px;">{{ object }}</pre>
|
||||
<pre style="margin-top: 15px;">{{ content|safe }}</pre>
|
||||
{% endwith %}
|
||||
<input type="hidden" name="attribdata" value="{{ attribdata }}" />
|
||||
{% if redo %}<input type="hidden" name="redo" value="1" />{% endif %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="Valider le {% if redo %}re{% endif %}traitement de la demande" />
|
|
@ -16,7 +16,6 @@
|
|||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<script src="{% static "js/jquery.min.js" %}" type="text/javascript"></script>
|
||||
{% if proposals %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
|
@ -17,4 +17,8 @@
|
|||
<li><a href="{% url 'gestioncof.views.export_mega_orgas' %}">Export des orgas uniquement</a></li>
|
||||
<li><a href="{% url 'gestioncof.views.export_mega' %}">Export de tout le monde</a></li>
|
||||
</ul>
|
||||
|
||||
<p>Note : pour ouvrir les fichiers .csv avec Excel, il faut
|
||||
passer par <tt>Fichier > Importer</tt> et sélectionner la
|
||||
virgule comme séparateur.</p>
|
||||
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
|||
{% extends "base_header.html" %}
|
||||
{% extends "gestioncof/base_header.html" %}
|
||||
|
||||
{% block homelink %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
{% load staticfiles %}
|
||||
|
||||
{% block extra_head %}
|
||||
<link href="{% static "grappelli/jquery/ui/css/custom-theme/jquery-ui-1.8.custom.css" %}" rel="stylesheet" type="text/css" media="screen" title="no title" charset="utf-8" />
|
||||
<script src="{% static "grappelli/jquery/jquery-1.6.2.min.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "grappelli/jquery/ui/js/jquery-ui-1.8.15.custom.min.js" %}" type="text/javascript"></script>
|
||||
<link href="{% static "grappelli/css/tools.css" %}" rel="stylesheet" type="text/css" />
|
||||
<link href="{% static "grappelli/css/jquery-ui-grappelli-extensions.css" %}" rel="stylesheet" type="text/css" />
|
||||
<script src="{% static "js/jquery-ui.min.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "js/jquery.ui.touch-punch.min.js" %}" type="text/javascript"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{% static "css/jquery-ui.min.css" %}" />
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block realcontent %}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
Bonjour,
|
||||
|
||||
Je vous contacte au sujet de votre annonce passée sur le site du COF pour rentrer en contact avec un élève normalien pour des cours particuliers. Voici les coordonnées d'élèves qui sont motivés par de tels cours et correspondent aux critères que vous nous aviez transmis :
|
||||
|
||||
{% for matiere, proposed in proposals %}¤ {{ matiere }} :{% for user in proposed %}
|
||||
¤ {{ user.get_full_name }}{% if user.profile.phone %}, {{ user.profile.phone }}{% endif %}{% if user.email %}, {{ user.email }}{% endif %}{% endfor %}
|
||||
|
||||
{% endfor %}{% if unsatisfied %}Nous n'avons cependant pas pu trouver d'élève disponible pour des cours de {% for matiere in unsatisfied %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}.
|
||||
|
||||
{% endif %}Si pour une raison ou une autre ces numéros ne suffisaient pas, n'hésitez pas à répondre à cet e-mail et je vous en ferai parvenir d'autres sans problème.
|
||||
{% if extra|length > 0 %}
|
||||
{{ extra|safe }}
|
||||
{% endif %}
|
||||
Cordialement,
|
||||
|
||||
--
|
||||
Le COF, BdE de l'ENS
|
|
@ -1,28 +0,0 @@
|
|||
Salut,
|
||||
|
||||
Le COF a reçu une demande de petit cours qui te correspond. Tu es en haut de la liste d'attente donc on a transmis tes coordonnées, ainsi que celles de 2 autres qui correspondaient aussi (c'est la vie, on donne les numéros 3 par 3 pour que ce soit plus souple). Voici quelques infos sur l'annonce en question :
|
||||
|
||||
¤ Nom : {{ demande.name }}
|
||||
|
||||
¤ Période : {{ demande.quand }}
|
||||
|
||||
¤ Fréquence : {{ demande.freq }}
|
||||
|
||||
¤ Lieu (si préféré) : {{ demande.lieu }}
|
||||
|
||||
¤ Niveau : {{ demande.get_niveau_display }}
|
||||
|
||||
¤ Remarques diverses (désolé pour les balises HTML) : {{ demande.remarques }}
|
||||
|
||||
{% if matieres|length > 1 %}¤ Matières :
|
||||
{% for matiere in matieres %} ¤ {{ matiere }}
|
||||
{% endfor %}{% else %}¤ Matière : {% for matiere in matieres %}{{ matiere }}
|
||||
{% endfor %}{% endif %}
|
||||
Voilà, cette personne te contactera peut-être sous peu, tu pourras voir les détails directement avec elle (prix, modalités, ...). Pour indication, 30 Euro/h semble être la moyenne.
|
||||
|
||||
Si tu te rends compte qu'en fait tu ne peux pas/plus donner de cours en ce moment, ça serait cool que tu décoches la case "Recevoir des propositions de petits cours" sur GestioCOF. Ensuite dès que tu voudras réapparaître tu pourras recocher la case et tu seras à nouveau sur la liste.
|
||||
|
||||
À bientôt,
|
||||
|
||||
--
|
||||
Le COF, pour les petits cours
|
|
@ -4,13 +4,14 @@
|
|||
{% block page_size %}col-sm-8{% endblock %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script src="{% static "js/jquery.min.js" %}" type="text/javascript"></script>
|
||||
<script src="{% static "autocomplete_light/autocomplete.js" %}" type="text/javascript"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Inscription d'un nouveau membre</h2>
|
||||
<input type="text" name="q" id="search_autocomplete" spellcheck="false" />
|
||||
<p class="help-block">Les mots contenant des caractères non alphanumériques seront ignorés</p>
|
||||
<input type="text" name="q" id="search_autocomplete" spellcheck="false"
|
||||
placeholder="Chercher un utilisateur par nom, prénom ou identifiant clipper" />
|
||||
<div id="form-placeholder"></div>
|
||||
<div class="yourlabs-autocomplete"></div>
|
||||
<script type="text/javascript">
|
||||
|
@ -18,10 +19,9 @@
|
|||
$(document).ready(function() {
|
||||
$('input#search_autocomplete').yourlabsAutocomplete({
|
||||
url: '{% url 'gestioncof.autocomplete.autocomplete' %}',
|
||||
minimumCharacters: 1,
|
||||
minimumCharacters: 3,
|
||||
id: 'search_autocomplete',
|
||||
choiceSelector: 'li:has(a)',
|
||||
placeholder: "Chercher un utilisateur par nom, prénom ou identifiant clipper",
|
||||
box: $(".yourlabs-autocomplete"),
|
||||
});
|
||||
$('input#search_autocomplete').bind(
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2>Inscription d'un nouveau membre</h2>
|
||||
{% if success %}
|
||||
<p class="success">L'inscription de {{ member.first_name }} {{ member.last_name }} (<tt>{{ member.username }}</tt>) a été enregistrée avec succès.
|
||||
{% if member.profile.is_cof %}Il est désormais membre du COF n°{{ member.profile.num }} !{% endif %}</p>
|
||||
{% endif %}
|
||||
<div id="form-placeholder">
|
||||
{% include "registration_form.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -23,7 +23,7 @@ def key(d, key_name):
|
|||
|
||||
|
||||
def highlight_text(text, q):
|
||||
q2 = "|".join(q.split())
|
||||
q2 = "|".join(re.escape(word) for word in q.split())
|
||||
pattern = re.compile(r"(?P<filter>%s)" % q2, re.IGNORECASE)
|
||||
return mark_safe(re.sub(pattern,
|
||||
r"<span class='highlight'>\g<filter></span>",
|
||||
|
@ -43,7 +43,7 @@ def highlight_user(user, q):
|
|||
@register.filter
|
||||
def highlight_clipper(clipper, q):
|
||||
if clipper.fullname:
|
||||
text = "%s (<tt>%s</tt>)" % (clipper.fullname, clipper.username)
|
||||
text = "%s (<tt>%s</tt>)" % (clipper.fullname, clipper.clipper)
|
||||
else:
|
||||
text = clipper.username
|
||||
text = clipper.clipper
|
||||
return highlight_text(text, q)
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf.urls import url
|
||||
from gestioncof.petits_cours_views import DemandeListView
|
||||
from gestioncof.petits_cours_views import DemandeListView, DemandeDetailView
|
||||
from gestioncof import views, petits_cours_views
|
||||
from gestioncof.decorators import buro_required
|
||||
|
||||
export_patterns = [
|
||||
url(r'^members$', views.export_members),
|
||||
|
@ -24,10 +21,11 @@ petitcours_patterns = [
|
|||
name='petits-cours-demande'),
|
||||
url(r'^demande-raw$', petits_cours_views.demande_raw,
|
||||
name='petits-cours-demande-raw'),
|
||||
url(r'^demandes$', DemandeListView.as_view(),
|
||||
url(r'^demandes$',
|
||||
buro_required(DemandeListView.as_view()),
|
||||
name='petits-cours-demandes-list'),
|
||||
url(r'^demandes/(?P<demande_id>\d+)$',
|
||||
petits_cours_views.details,
|
||||
url(r'^demandes/(?P<pk>\d+)$',
|
||||
buro_required(DemandeDetailView.as_view()),
|
||||
name='petits-cours-demande-details'),
|
||||
url(r'^demandes/(?P<demande_id>\d+)/traitement$',
|
||||
petits_cours_views.traitement,
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import unicodecsv
|
||||
import uuid
|
||||
from datetime import timedelta
|
||||
from icalendar import Calendar, Event as Vevent
|
||||
from custommail.shortcuts import send_custom_mail
|
||||
|
||||
from django.shortcuts import redirect, get_object_or_404, render
|
||||
from django.http import Http404, HttpResponse, HttpResponseForbidden
|
||||
|
@ -16,6 +13,7 @@ from django.contrib.auth.views import login as django_login_view
|
|||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.utils import timezone
|
||||
from django.contrib import messages
|
||||
import django.utils.six as six
|
||||
|
||||
from gestioncof.models import Survey, SurveyAnswer, SurveyQuestion, \
|
||||
|
@ -24,8 +22,7 @@ from gestioncof.models import Event, EventRegistration, EventOption, \
|
|||
EventOptionChoice
|
||||
from gestioncof.models import EventCommentField, EventCommentValue, \
|
||||
CalendarSubscription
|
||||
from gestioncof.shared import send_custom_mail
|
||||
from gestioncof.models import CofProfile, Clipper, Club
|
||||
from gestioncof.models import CofProfile, Club
|
||||
from gestioncof.decorators import buro_required, cof_required
|
||||
from gestioncof.forms import UserProfileForm, EventStatusFilterForm, \
|
||||
SurveyForm, SurveyStatusFilterForm, RegistrationUserForm, \
|
||||
|
@ -153,10 +150,21 @@ def survey(request, survey_id):
|
|||
except SurveyAnswer.DoesNotExist:
|
||||
current_answer = None
|
||||
form = SurveyForm(survey=survey)
|
||||
return render(request, "survey.html", {"survey": survey, "form": form,
|
||||
"success": success,
|
||||
"deleted": deleted,
|
||||
"current_answer": current_answer})
|
||||
# Messages
|
||||
if success:
|
||||
if deleted:
|
||||
messages.success(request,
|
||||
"Votre réponse a bien été supprimée")
|
||||
else:
|
||||
messages.success(request,
|
||||
"Votre réponse a bien été enregistrée ! Vous "
|
||||
"pouvez cependant la modifier jusqu'à la fin "
|
||||
"du sondage.")
|
||||
return render(request, "gestioncof/survey.html", {
|
||||
"survey": survey,
|
||||
"form": form,
|
||||
"current_answer": current_answer
|
||||
})
|
||||
|
||||
|
||||
def get_event_form_choices(event, form):
|
||||
|
@ -216,8 +224,13 @@ def event(request, event_id):
|
|||
current_choices=current_registration.options)
|
||||
except EventRegistration.DoesNotExist:
|
||||
form = EventForm(event=event)
|
||||
return render(request, "event.html",
|
||||
{"event": event, "form": form, "success": success})
|
||||
# Messages
|
||||
if success:
|
||||
messages.success(request, "Votre inscription a bien été enregistrée ! "
|
||||
"Vous pouvez cependant la modifier jusqu'à "
|
||||
"la fin des inscriptions.")
|
||||
return render(request, "gestioncof/event.html",
|
||||
{"event": event, "form": form})
|
||||
|
||||
|
||||
def clean_post_for_status(initial):
|
||||
|
@ -304,15 +317,15 @@ def survey_status(request, survey_id):
|
|||
|
||||
@cof_required
|
||||
def profile(request):
|
||||
success = False
|
||||
if request.method == "POST":
|
||||
form = UserProfileForm(request.POST, instance=request.user.profile)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
success = True
|
||||
messages.success(request,
|
||||
"Votre profil a été mis à jour avec succès !")
|
||||
else:
|
||||
form = UserProfileForm(instance=request.user.profile)
|
||||
return render(request, "profile.html", {"form": form, "success": success})
|
||||
return render(request, "gestioncof/profile.html", {"form": form})
|
||||
|
||||
|
||||
def registration_set_ro_fields(user_form, profile_form):
|
||||
|
@ -321,11 +334,11 @@ def registration_set_ro_fields(user_form, profile_form):
|
|||
|
||||
|
||||
@buro_required
|
||||
def registration_form2(request, login_clipper=None, username=None):
|
||||
def registration_form2(request, login_clipper=None, username=None,
|
||||
fullname=None):
|
||||
events = Event.objects.filter(old=False).all()
|
||||
member = None
|
||||
if login_clipper:
|
||||
clipper = get_object_or_404(Clipper, username=login_clipper)
|
||||
try: # check if the given user is already registered
|
||||
member = User.objects.get(username=login_clipper)
|
||||
username = member.username
|
||||
|
@ -336,8 +349,8 @@ def registration_form2(request, login_clipper=None, username=None):
|
|||
user_form = RegistrationUserForm(initial={
|
||||
'username': login_clipper,
|
||||
'email': "%s@clipper.ens.fr" % login_clipper})
|
||||
if clipper.fullname:
|
||||
bits = clipper.fullname.split(" ")
|
||||
if fullname:
|
||||
bits = fullname.split(" ")
|
||||
user_form.fields['first_name'].initial = bits[0]
|
||||
if len(bits) > 1:
|
||||
user_form.fields['last_name'].initial = " ".join(bits[1:])
|
||||
|
@ -375,7 +388,7 @@ def registration_form2(request, login_clipper=None, username=None):
|
|||
profile_form = RegistrationProfileForm()
|
||||
event_formset = EventFormset(events=events, prefix='events')
|
||||
clubs_form = ClubsForm()
|
||||
return render(request, "registration_form.html",
|
||||
return render(request, "gestioncof/registration_form.html",
|
||||
{"member": member, "login_clipper": login_clipper,
|
||||
"user_form": user_form,
|
||||
"profile_form": profile_form,
|
||||
|
@ -412,11 +425,11 @@ def registration(request):
|
|||
try:
|
||||
member = User.objects.get(username=username)
|
||||
user_form = RegistrationUserForm(request_dict, instance=member)
|
||||
if member.profile.login_clipper:
|
||||
login_clipper = member.profile.login_clipper
|
||||
else:
|
||||
user_form.force_long_username()
|
||||
except User.DoesNotExist:
|
||||
try:
|
||||
clipper = Clipper.objects.get(username=username)
|
||||
login_clipper = clipper.username
|
||||
except Clipper.DoesNotExist:
|
||||
user_form.force_long_username()
|
||||
else:
|
||||
user_form.force_long_username()
|
||||
|
@ -438,7 +451,10 @@ def registration(request):
|
|||
# Enregistrement du profil
|
||||
profile = profile_form.save()
|
||||
if profile.is_cof and not was_cof:
|
||||
send_custom_mail(member, "bienvenue")
|
||||
send_custom_mail(
|
||||
"welcome", "cof@ens.fr", [member.email],
|
||||
context={'member': member},
|
||||
)
|
||||
# Enregistrement des inscriptions aux événements
|
||||
for form in event_formset:
|
||||
if 'status' not in form.cleaned_data:
|
||||
|
@ -461,26 +477,37 @@ def registration(request):
|
|||
current_registration.paid = \
|
||||
(form.cleaned_data['status'] == 'paid')
|
||||
current_registration.save()
|
||||
if form.event.title == "Mega 15" and created_reg:
|
||||
field = EventCommentField.objects.get(
|
||||
event=form.event, name="Commentaires")
|
||||
try:
|
||||
comments = EventCommentValue.objects.get(
|
||||
commentfield=field,
|
||||
registration=current_registration).content
|
||||
except EventCommentValue.DoesNotExist:
|
||||
comments = field.default
|
||||
send_custom_mail(member, "mega",
|
||||
{"remarques": comments})
|
||||
# if form.event.title == "Mega 15" and created_reg:
|
||||
# field = EventCommentField.objects.get(
|
||||
# event=form.event, name="Commentaires")
|
||||
# try:
|
||||
# comments = EventCommentValue.objects.get(
|
||||
# commentfield=field,
|
||||
# registration=current_registration).content
|
||||
# except EventCommentValue.DoesNotExist:
|
||||
# comments = field.default
|
||||
# FIXME : il faut faire quelque chose de propre ici,
|
||||
# par exemple écrire un mail générique pour
|
||||
# l'inscription aux événements et/ou donner la
|
||||
# possibilité d'associer un mail aux événements
|
||||
# send_custom_mail(...)
|
||||
# Enregistrement des inscriptions aux clubs
|
||||
member.clubs.clear()
|
||||
for club in clubs_form.cleaned_data['clubs']:
|
||||
club.membres.add(member)
|
||||
club.save()
|
||||
success = True
|
||||
return render(request, "registration_post.html",
|
||||
{"success": success,
|
||||
"user_form": user_form,
|
||||
# Messages
|
||||
if success:
|
||||
msg = ("L'inscription de {:s} (<tt>{:s}</tt>) a été "
|
||||
"enregistrée avec succès"
|
||||
.format(member.get_full_name(), member.email))
|
||||
if member.profile.is_cof:
|
||||
msg += "Il est désormais membre du COF n°{:d} !".format(
|
||||
member.profile.num)
|
||||
messages.success(request, msg, extra_tags='safe')
|
||||
return render(request, "gestioncof/registration_post.html",
|
||||
{"user_form": user_form,
|
||||
"profile_form": profile_form,
|
||||
"member": member,
|
||||
"login_clipper": login_clipper,
|
||||
|
@ -637,7 +664,7 @@ def export_mega(request):
|
|||
|
||||
@buro_required
|
||||
def utile_cof(request):
|
||||
return render(request, "utile_cof.html", {})
|
||||
return render(request, "gestioncof/utile_cof.html", {})
|
||||
|
||||
|
||||
@buro_required
|
||||
|
@ -686,15 +713,17 @@ def calendar(request):
|
|||
subscription.token = uuid.uuid4()
|
||||
subscription.save()
|
||||
form.save_m2m()
|
||||
return render(request, "calendar_subscription.html",
|
||||
messages.success(request,
|
||||
"Calendrier mis à jour avec succès.")
|
||||
return render(request, "gestioncof/calendar_subscription.html",
|
||||
{'form': form,
|
||||
'success': True,
|
||||
'token': str(subscription.token)})
|
||||
else:
|
||||
return render(request, "calendar_subscription.html",
|
||||
{'form': form, 'error': "Formulaire incorrect"})
|
||||
messages.error(request, "Formulaire incorrect.")
|
||||
return render(request, "gestioncof/calendar_subscription.html",
|
||||
{'form': form})
|
||||
else:
|
||||
return render(request, "calendar_subscription.html",
|
||||
return render(request, "gestioncof/calendar_subscription.html",
|
||||
{'form': CalendarForm(instance=instance),
|
||||
'token': instance.token if instance else None})
|
||||
|
||||
|
|
|
@ -1,13 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from ldap3 import Connection
|
||||
from django.shortcuts import render
|
||||
from django.http import Http404
|
||||
from django.db.models import Q
|
||||
from gestioncof.models import User, Clipper
|
||||
from django.conf import settings
|
||||
|
||||
from gestioncof.models import User
|
||||
from kfet.decorators import teamkfet_required
|
||||
from kfet.models import Account
|
||||
|
||||
|
||||
class Clipper(object):
|
||||
def __init__(self, clipper, fullname):
|
||||
if fullname is None:
|
||||
fullname = ""
|
||||
assert isinstance(clipper, str)
|
||||
assert isinstance(fullname, str)
|
||||
self.clipper = clipper
|
||||
self.fullname = fullname
|
||||
|
||||
|
||||
@teamkfet_required
|
||||
def account_create(request):
|
||||
if "q" not in request.GET:
|
||||
|
@ -22,59 +35,73 @@ def account_create(request):
|
|||
queries = {}
|
||||
search_words = q.split()
|
||||
|
||||
# Fetching data from User, CofProfile and Account tables
|
||||
queries['kfet'] = Account.objects
|
||||
queries['users_cof'] = User.objects.filter(Q(profile__is_cof=True))
|
||||
queries['users_notcof'] = User.objects.filter(Q(profile__is_cof=False))
|
||||
queries['clippers'] = Clipper.objects
|
||||
queries['users_cof'] = User.objects.filter(profile__is_cof=True)
|
||||
queries['users_notcof'] = User.objects.filter(profile__is_cof=False)
|
||||
|
||||
for word in search_words:
|
||||
queries['kfet'] = queries['kfet'].filter(
|
||||
Q(cofprofile__user__username__icontains=word) |
|
||||
Q(cofprofile__user__first_name__icontains=word) |
|
||||
Q(cofprofile__user__last_name__icontains=word)
|
||||
Q(cofprofile__user__username__icontains=word)
|
||||
| Q(cofprofile__user__first_name__icontains=word)
|
||||
| Q(cofprofile__user__last_name__icontains=word)
|
||||
)
|
||||
queries['users_cof'] = queries['users_cof'].filter(
|
||||
Q(username__icontains=word) |
|
||||
Q(first_name__icontains=word) |
|
||||
Q(last_name__icontains=word)
|
||||
Q(username__icontains=word)
|
||||
| Q(first_name__icontains=word)
|
||||
| Q(last_name__icontains=word)
|
||||
)
|
||||
queries['users_notcof'] = queries['users_notcof'].filter(
|
||||
Q(username__icontains=word) |
|
||||
Q(first_name__icontains=word) |
|
||||
Q(last_name__icontains=word)
|
||||
)
|
||||
queries['clippers'] = queries['clippers'].filter(
|
||||
Q(username__icontains=word) |
|
||||
Q(fullname__icontains=word)
|
||||
Q(username__icontains=word)
|
||||
| Q(first_name__icontains=word)
|
||||
| Q(last_name__icontains=word)
|
||||
)
|
||||
|
||||
# Clearing redundancies
|
||||
queries['kfet'] = queries['kfet'].distinct()
|
||||
|
||||
usernames = list(
|
||||
usernames = set(
|
||||
queries['kfet'].values_list('cofprofile__user__username', flat=True))
|
||||
|
||||
queries['kfet'] = [(account, account.cofprofile.user)
|
||||
for account in queries['kfet']]
|
||||
queries['kfet'] = [
|
||||
(account, account.cofprofile.user)
|
||||
for account in queries['kfet']
|
||||
]
|
||||
|
||||
queries['users_cof'] = \
|
||||
queries['users_cof'].exclude(username__in=usernames).distinct()
|
||||
queries['users_notcof'] = \
|
||||
queries['users_notcof'].exclude(username__in=usernames).distinct()
|
||||
|
||||
usernames += list(
|
||||
usernames |= set(
|
||||
queries['users_cof'].values_list('username', flat=True))
|
||||
usernames += list(
|
||||
usernames |= set(
|
||||
queries['users_notcof'].values_list('username', flat=True))
|
||||
|
||||
queries['clippers'] = \
|
||||
queries['clippers'].exclude(username__in=usernames).distinct()
|
||||
# Fetching data from the SPI
|
||||
if hasattr(settings, 'LDAP_SERVER_URL'):
|
||||
# Fetching
|
||||
ldap_query = '(&{:s})'.format(''.join(
|
||||
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=word)
|
||||
for word in search_words if word.isalnum()
|
||||
))
|
||||
if ldap_query != "(&)":
|
||||
# If none of the bits were legal, we do not perform the query
|
||||
entries = None
|
||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
||||
conn.search(
|
||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
||||
attributes=['uid', 'cn']
|
||||
)
|
||||
entries = conn.entries
|
||||
# Clearing redundancies
|
||||
queries['clippers'] = [
|
||||
Clipper(entry.uid.value, entry.cn.value)
|
||||
for entry in entries
|
||||
if entry.uid.value
|
||||
and entry.uid.value not in usernames
|
||||
]
|
||||
|
||||
# Resulting data
|
||||
data.update(queries)
|
||||
|
||||
options = 0
|
||||
for query in queries.values():
|
||||
options += len(query)
|
||||
data['options'] = options
|
||||
data['options'] = sum([len(query) for query in queries])
|
||||
|
||||
return render(request, "kfet/account_create_autocomplete.html", data)
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (absolute_import, division,
|
||||
print_function, unicode_literals)
|
||||
from builtins import *
|
||||
from django.contrib.auth.decorators import user_passes_test
|
||||
|
||||
from django_cas_ng.decorators import user_passes_test
|
||||
|
||||
def kfet_is_team(user):
|
||||
return user.has_perm('kfet.is_team')
|
||||
|
@ -14,3 +11,4 @@ def can_force_close(user):
|
|||
|
||||
teamkfet_required = user_passes_test(lambda u: kfet_is_team(u))
|
||||
force_close_required = user_passes_test(lambda u: can_force_close(u))
|
||||
teamkfet_required = user_passes_test(kfet_is_team)
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,98 +0,0 @@
|
|||
[
|
||||
{
|
||||
"model": "auth.group",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "K-F\u00eat chef",
|
||||
"permissions": [
|
||||
115,
|
||||
116,
|
||||
117,
|
||||
118,
|
||||
119,
|
||||
120,
|
||||
133,
|
||||
134,
|
||||
135,
|
||||
130,
|
||||
131,
|
||||
132,
|
||||
136,
|
||||
137,
|
||||
138,
|
||||
121,
|
||||
122,
|
||||
123,
|
||||
127,
|
||||
128,
|
||||
129,
|
||||
124,
|
||||
125,
|
||||
126,
|
||||
188,
|
||||
189,
|
||||
190,
|
||||
169,
|
||||
176,
|
||||
183,
|
||||
170,
|
||||
171,
|
||||
182,
|
||||
172,
|
||||
178,
|
||||
177,
|
||||
181,
|
||||
175,
|
||||
179,
|
||||
173,
|
||||
174,
|
||||
184,
|
||||
180,
|
||||
139,
|
||||
140,
|
||||
141,
|
||||
142,
|
||||
143,
|
||||
144,
|
||||
166,
|
||||
167,
|
||||
168,
|
||||
163,
|
||||
164,
|
||||
165,
|
||||
151,
|
||||
152,
|
||||
153,
|
||||
154,
|
||||
155,
|
||||
156,
|
||||
185,
|
||||
186,
|
||||
187,
|
||||
145,
|
||||
146,
|
||||
147,
|
||||
148,
|
||||
149,
|
||||
150,
|
||||
160,
|
||||
161,
|
||||
162,
|
||||
157,
|
||||
158,
|
||||
159
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "auth.group",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "K-F\u00eat girl",
|
||||
"permissions": [
|
||||
172,
|
||||
173
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
|
@ -74,8 +74,11 @@ class AccountRestrictForm(AccountForm):
|
|||
|
||||
class AccountPwdForm(forms.Form):
|
||||
pwd1 = forms.CharField(
|
||||
label="Mot de passe K-Fêt",
|
||||
help_text="Le mot de passe doit contenir au moins huit caractères",
|
||||
widget=forms.PasswordInput)
|
||||
pwd2 = forms.CharField(
|
||||
label="Confirmer le mot de passe",
|
||||
widget=forms.PasswordInput)
|
||||
|
||||
def clean(self):
|
||||
|
@ -128,6 +131,7 @@ class UserRestrictTeamForm(UserForm):
|
|||
class UserGroupForm(forms.ModelForm):
|
||||
groups = forms.ModelMultipleChoiceField(
|
||||
Group.objects.filter(name__icontains='K-Fêt'),
|
||||
label='Statut équipe',
|
||||
required=False)
|
||||
|
||||
def clean_groups(self):
|
||||
|
@ -229,22 +233,36 @@ class CheckoutStatementUpdateForm(forms.ModelForm):
|
|||
model = CheckoutStatement
|
||||
exclude = ['by', 'at', 'checkout', 'amount_error', 'amount_taken']
|
||||
|
||||
|
||||
# -----
|
||||
# Category
|
||||
# -----
|
||||
|
||||
class CategoryForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = ArticleCategory
|
||||
fields = ['name', 'has_addcost']
|
||||
|
||||
# -----
|
||||
# Article forms
|
||||
# -----
|
||||
|
||||
class ArticleForm(forms.ModelForm):
|
||||
category_new = forms.CharField(
|
||||
label="Créer une catégorie",
|
||||
max_length=45,
|
||||
required = False)
|
||||
category = forms.ModelChoiceField(
|
||||
label="Catégorie",
|
||||
queryset = ArticleCategory.objects.all(),
|
||||
required = False)
|
||||
|
||||
suppliers = forms.ModelMultipleChoiceField(
|
||||
label="Fournisseurs",
|
||||
queryset = Supplier.objects.all(),
|
||||
required = False)
|
||||
supplier_new = forms.CharField(
|
||||
label="Créer un fournisseur",
|
||||
max_length = 45,
|
||||
required = False)
|
||||
|
||||
|
@ -318,11 +336,10 @@ class KPsulOperationForm(forms.ModelForm):
|
|||
widget = forms.HiddenInput())
|
||||
class Meta:
|
||||
model = Operation
|
||||
fields = ['type', 'amount', 'is_checkout', 'article', 'article_nb']
|
||||
fields = ['type', 'amount', 'article', 'article_nb']
|
||||
widgets = {
|
||||
'type': forms.HiddenInput(),
|
||||
'amount': forms.HiddenInput(),
|
||||
'is_checkout': forms.HiddenInput(),
|
||||
'article_nb': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
|
@ -338,7 +355,6 @@ class KPsulOperationForm(forms.ModelForm):
|
|||
"Un achat nécessite un article et une quantité")
|
||||
if article_nb < 1:
|
||||
raise ValidationError("Impossible d'acheter moins de 1 article")
|
||||
self.cleaned_data['is_checkout'] = True
|
||||
elif type_ope and type_ope in [Operation.DEPOSIT, Operation.WITHDRAW]:
|
||||
if not amount or article or article_nb:
|
||||
raise ValidationError("Bad request")
|
||||
|
@ -466,6 +482,7 @@ class InventoryArticleForm(forms.Form):
|
|||
self.stock_old = kwargs['initial']['stock_old']
|
||||
self.category = kwargs['initial']['category']
|
||||
self.category_name = kwargs['initial']['category__name']
|
||||
self.box_capacity = kwargs['initial']['box_capacity']
|
||||
|
||||
# -----
|
||||
# Order forms
|
||||
|
@ -478,9 +495,7 @@ class OrderArticleForm(forms.Form):
|
|||
queryset=Article.objects.all(),
|
||||
widget=forms.HiddenInput(),
|
||||
)
|
||||
quantity_ordered = forms.IntegerField(
|
||||
required=False,
|
||||
widget=forms.NumberInput(attrs={'class': 'form-control'}))
|
||||
quantity_ordered = forms.IntegerField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OrderArticleForm, self).__init__(*args, **kwargs)
|
||||
|
@ -507,18 +522,14 @@ class OrderArticleToInventoryForm(forms.Form):
|
|||
)
|
||||
price_HT = forms.DecimalField(
|
||||
max_digits = 7, decimal_places = 4,
|
||||
required = False,
|
||||
widget=forms.NumberInput(attrs={'class': 'form-control'}))
|
||||
required = False)
|
||||
TVA = forms.DecimalField(
|
||||
max_digits = 7, decimal_places = 2,
|
||||
required = False,
|
||||
widget=forms.NumberInput(attrs={'class': 'form-control'}))
|
||||
required = False)
|
||||
rights = forms.DecimalField(
|
||||
max_digits = 7, decimal_places = 4,
|
||||
required = False,
|
||||
widget=forms.NumberInput(attrs={'class': 'form-control'}))
|
||||
quantity_received = forms.IntegerField(
|
||||
widget=forms.NumberInput(attrs={'class': 'form-control'}))
|
||||
required = False)
|
||||
quantity_received = forms.IntegerField()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OrderArticleToInventoryForm, self).__init__(*args, **kwargs)
|
||||
|
|
175
kfet/management/commands/createopes.py
Normal file
175
kfet/management/commands/createopes.py
Normal file
|
@ -0,0 +1,175 @@
|
|||
|
||||
"""
|
||||
Crée des opérations aléatoires réparties sur une période de temps spécifiée
|
||||
"""
|
||||
|
||||
import random
|
||||
from datetime import timedelta
|
||||
from decimal import Decimal
|
||||
from django.utils import timezone
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from kfet.models import (Account, Article, OperationGroup, Operation,
|
||||
Checkout, Transfer, TransferGroup)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Crée des opérations réparties uniformément sur une période de temps"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
# Nombre d'opérations à créer
|
||||
parser.add_argument('opes', type=int,
|
||||
help='Number of opegroups to create')
|
||||
|
||||
# Période sur laquelle créer (depuis num_days avant maintenant)
|
||||
parser.add_argument('days', type=int,
|
||||
help='Period in which to create opegroups')
|
||||
|
||||
# Optionnel : nombre de transfert à créer (défaut 0)
|
||||
parser.add_argument('--transfers', type=int, default=0,
|
||||
help='Number of transfers to create (default 0)')
|
||||
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
self.stdout.write("Génération d'opérations")
|
||||
|
||||
# Output log vars
|
||||
opes_created = 0
|
||||
purchases = 0
|
||||
transfers = 0
|
||||
|
||||
num_ops = options['opes']
|
||||
num_transfers = options['transfers']
|
||||
# Convert to seconds
|
||||
time = options['days'] * 24 * 3600
|
||||
|
||||
checkout = Checkout.objects.first()
|
||||
articles = Article.objects.all()
|
||||
accounts = Account.objects.exclude(trigramme='LIQ')
|
||||
liq_account = Account.objects.get(trigramme='LIQ')
|
||||
try:
|
||||
con_account = Account.objects.get(
|
||||
cofprofile__user__first_name='Assurancetourix'
|
||||
)
|
||||
except Account.DoesNotExist:
|
||||
con_account = random.choice(accounts)
|
||||
|
||||
for i in range(num_ops):
|
||||
|
||||
# Randomly pick account
|
||||
if random.random() > 0.25:
|
||||
account = random.choice(accounts)
|
||||
else:
|
||||
account = liq_account
|
||||
|
||||
# Randomly pick time
|
||||
at = timezone.now() - timedelta(
|
||||
seconds=random.randint(0, time))
|
||||
|
||||
# Majoration sur compte 'concert'
|
||||
if random.random() < 0.2:
|
||||
addcost = True
|
||||
addcost_for = con_account
|
||||
addcost_amount = Decimal('0.5')
|
||||
else:
|
||||
addcost = False
|
||||
|
||||
# Initialize opegroup amount
|
||||
amount = Decimal('0')
|
||||
|
||||
opegroup = OperationGroup.objects.create(
|
||||
on_acc=account,
|
||||
checkout=checkout,
|
||||
at=at,
|
||||
is_cof=account.cofprofile.is_cof
|
||||
)
|
||||
|
||||
# Generating operations
|
||||
ope_list = []
|
||||
for j in range(random.randint(1, 4)):
|
||||
# Operation type
|
||||
typevar = random.random()
|
||||
|
||||
# 0.1 probability to have a charge
|
||||
if typevar > 0.9 and account != liq_account:
|
||||
ope = Operation(
|
||||
group=opegroup,
|
||||
type=Operation.DEPOSIT,
|
||||
is_checkout=(random.random() > 0.2),
|
||||
amount=Decimal(random.randint(1, 99)/10)
|
||||
)
|
||||
# 0.1 probability to have a withdrawal
|
||||
elif typevar > 0.8 and account != liq_account:
|
||||
ope = Operation(
|
||||
group=opegroup,
|
||||
type=Operation.WITHDRAW,
|
||||
is_checkout=(random.random() > 0.2),
|
||||
amount=-Decimal(random.randint(1, 99)/10)
|
||||
)
|
||||
else:
|
||||
article = random.choice(articles)
|
||||
nb = random.randint(1, 5)
|
||||
|
||||
ope = Operation(
|
||||
group=opegroup,
|
||||
type=Operation.PURCHASE,
|
||||
amount=-article.price*nb,
|
||||
article=article,
|
||||
article_nb=nb
|
||||
)
|
||||
|
||||
purchases += 1
|
||||
|
||||
if addcost:
|
||||
ope.addcost_for = addcost_for
|
||||
ope.addcost_amount = addcost_amount * nb
|
||||
ope.amount -= ope.addcost_amount
|
||||
|
||||
ope_list.append(ope)
|
||||
amount += ope.amount
|
||||
|
||||
Operation.objects.bulk_create(ope_list)
|
||||
opes_created += len(ope_list)
|
||||
opegroup.amount = amount
|
||||
opegroup.save()
|
||||
|
||||
# Transfer generation
|
||||
for i in range(num_transfers):
|
||||
|
||||
# Randomly pick time
|
||||
at = timezone.now() - timedelta(
|
||||
seconds=random.randint(0, time))
|
||||
|
||||
# Choose whether to have a comment
|
||||
if random.random() > 0.5:
|
||||
comment = "placeholder comment"
|
||||
else:
|
||||
comment = ""
|
||||
|
||||
transfergroup = TransferGroup.objects.create(
|
||||
at=at,
|
||||
comment=comment,
|
||||
valid_by=random.choice(accounts)
|
||||
)
|
||||
|
||||
# Randomly generate transfer
|
||||
transfer_list = []
|
||||
for i in range(random.randint(1, 4)):
|
||||
transfer_list.append(Transfer(
|
||||
group=transfergroup,
|
||||
from_acc=random.choice(accounts),
|
||||
to_acc=random.choice(accounts),
|
||||
amount=Decimal(random.randint(1, 99)/10)
|
||||
))
|
||||
|
||||
Transfer.objects.bulk_create(transfer_list)
|
||||
transfers += len(transfer_list)
|
||||
|
||||
self.stdout.write(
|
||||
"- {:d} opérations créées dont {:d} commandes d'articles"
|
||||
.format(opes_created, purchases))
|
||||
|
||||
if transfers:
|
||||
self.stdout.write("- {:d} transferts créés"
|
||||
.format(transfers))
|
149
kfet/management/commands/loadkfetdevdata.py
Normal file
149
kfet/management/commands/loadkfetdevdata.py
Normal file
|
@ -0,0 +1,149 @@
|
|||
"""
|
||||
Crée des utilisateurs, des articles et des opérations aléatoires
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
from datetime import timedelta
|
||||
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.models import User, Group, Permission, ContentType
|
||||
from django.core.management import call_command
|
||||
|
||||
from gestioncof.management.base import MyBaseCommand
|
||||
from gestioncof.models import CofProfile
|
||||
from kfet.models import (Account, Checkout, CheckoutStatement, Supplier,
|
||||
SupplierArticle, Article)
|
||||
|
||||
# Où sont stockés les fichiers json
|
||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
||||
'data')
|
||||
|
||||
|
||||
class Command(MyBaseCommand):
|
||||
help = "Crée des utilisateurs, des articles et des opérations aléatoires"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# ---
|
||||
# Groupes
|
||||
# ---
|
||||
|
||||
Group.objects.filter(name__icontains='K-Fêt').delete()
|
||||
|
||||
group_chef = Group(name="K-Fêt César")
|
||||
group_boy = Group(name="K-Fêt Légionnaire")
|
||||
|
||||
group_chef.save()
|
||||
group_boy.save()
|
||||
|
||||
permissions_chef = Permission.objects.filter(
|
||||
content_type__in=ContentType.objects.filter(
|
||||
app_label='kfet'))
|
||||
permissions_boy = Permission.objects.filter(
|
||||
codename__in=['is_team', 'perform_deposit'])
|
||||
|
||||
group_chef.permissions.add(*permissions_chef)
|
||||
group_boy.permissions.add(*permissions_boy)
|
||||
|
||||
# ---
|
||||
# Comptes
|
||||
# ---
|
||||
|
||||
self.stdout.write("Création des comptes K-Fêt")
|
||||
|
||||
gaulois = CofProfile.objects.filter(user__last_name='Gaulois')
|
||||
gaulois_trigramme = map('{:03d}'.format, range(50))
|
||||
|
||||
romains = CofProfile.objects.filter(user__last_name='Romain')
|
||||
romains_trigramme = map(lambda x: str(100+x), range(99))
|
||||
|
||||
created_accounts = 0
|
||||
team_accounts = 0
|
||||
|
||||
for (profile, trigramme) in zip(gaulois, gaulois_trigramme):
|
||||
account, created = Account.objects.get_or_create(
|
||||
trigramme=trigramme,
|
||||
cofprofile=profile,
|
||||
defaults={'balance': random.randint(1, 999)/10}
|
||||
)
|
||||
created_accounts += int(created)
|
||||
|
||||
if profile.user.first_name == 'Abraracourcix':
|
||||
profile.user.groups.add(group_chef)
|
||||
|
||||
for (profile, trigramme) in zip(romains, romains_trigramme):
|
||||
account, created = Account.objects.get_or_create(
|
||||
trigramme=trigramme,
|
||||
cofprofile=profile,
|
||||
defaults={'balance': random.randint(1, 999)/10}
|
||||
)
|
||||
created_accounts += int(created)
|
||||
|
||||
if random.random() > 0.75 and created:
|
||||
profile.user.groups.add(group_boy)
|
||||
team_accounts += 1
|
||||
|
||||
self.stdout.write("- {:d} comptes créés, {:d} dans l'équipe K-Fêt"
|
||||
.format(created_accounts, team_accounts))
|
||||
|
||||
# Compte liquide
|
||||
|
||||
self.stdout.write("Création du compte liquide")
|
||||
liq_user, _ = User.objects.get_or_create(username='liquide')
|
||||
liq_profile, _ = CofProfile.objects.get_or_create(user=liq_user)
|
||||
liq_account, _ = Account.objects.get_or_create(cofprofile=liq_profile,
|
||||
trigramme='LIQ')
|
||||
|
||||
# Root account if existing
|
||||
|
||||
root_profile = CofProfile.objects.filter(user__username='root')
|
||||
if root_profile.exists():
|
||||
self.stdout.write("Création du compte K-Fêt root")
|
||||
root_profile = root_profile.get()
|
||||
Account.objects.get_or_create(cofprofile=root_profile,
|
||||
trigramme='AAA')
|
||||
|
||||
# ---
|
||||
# Caisse
|
||||
# ---
|
||||
|
||||
checkout, created = Checkout.objects.get_or_create(
|
||||
created_by=Account.objects.get(trigramme='000'),
|
||||
name='Chaudron',
|
||||
defaults={
|
||||
'valid_from': timezone.now(),
|
||||
'valid_to': timezone.now() + timedelta(days=730)
|
||||
},
|
||||
)
|
||||
|
||||
if created:
|
||||
CheckoutStatement.objects.create(
|
||||
by=Account.objects.get(trigramme='000'),
|
||||
checkout=checkout,
|
||||
balance_old=0,
|
||||
balance_new=0,
|
||||
amount_taken=0,
|
||||
amount_error=0
|
||||
)
|
||||
|
||||
# ---
|
||||
# Fournisseur
|
||||
# ---
|
||||
|
||||
supplier, created = Supplier.objects.get_or_create(name="Panoramix")
|
||||
if created:
|
||||
articles = random.sample(list(Article.objects.all()), 40)
|
||||
to_create = []
|
||||
for article in articles:
|
||||
to_create.append(SupplierArticle(
|
||||
supplier=supplier,
|
||||
article=article
|
||||
))
|
||||
|
||||
SupplierArticle.objects.bulk_create(to_create)
|
||||
|
||||
# ---
|
||||
# Opérations
|
||||
# ---
|
||||
|
||||
call_command('createopes', '100', '7', '--transfers=20')
|
25
kfet/migrations/0048_default_datetime.py
Normal file
25
kfet/migrations/0048_default_datetime.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kfet', '0047_auto_20170104_1528'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='operationgroup',
|
||||
name='at',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='transfergroup',
|
||||
name='at',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now),
|
||||
),
|
||||
]
|
|
@ -7,8 +7,9 @@ from django.db import migrations, models
|
|||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kfet', '00048_kfet_open_cache'),
|
||||
('kfet', '0048_kfet_open_cache'),
|
||||
('kfet', '0048_article_hidden'),
|
||||
('kfet', '0048_default_datetime'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
|
38
kfet/migrations/0050_remove_checkout.py
Normal file
38
kfet/migrations/0050_remove_checkout.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def adapt_operation_types(apps, schema_editor):
|
||||
Operation = apps.get_model("kfet", "Operation")
|
||||
Operation.objects.filter(
|
||||
is_checkout=False,
|
||||
type__in=['withdraw', 'deposit']).update(type='edit')
|
||||
|
||||
|
||||
def revert_operation_types(apps, schema_editor):
|
||||
Operation = apps.get_model("kfet", "Operation")
|
||||
edits = Operation.objects.filter(type='edit')
|
||||
edits.filter(amount__gt=0).update(type='deposit')
|
||||
edits.filter(amount__lte=0).update(type='withdraw')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kfet', '0049_merge'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='operation',
|
||||
name='type',
|
||||
field=models.CharField(choices=[('purchase', 'Achat'), ('deposit', 'Charge'), ('withdraw', 'Retrait'), ('initial', 'Initial'), ('edit', 'Édition')], max_length=8),
|
||||
),
|
||||
migrations.RunPython(adapt_operation_types, revert_operation_types),
|
||||
migrations.RemoveField(
|
||||
model_name='operation',
|
||||
name='is_checkout',
|
||||
),
|
||||
]
|
210
kfet/migrations/0051_verbose_names.py
Normal file
210
kfet/migrations/0051_verbose_names.py
Normal file
|
@ -0,0 +1,210 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('kfet', '0050_remove_checkout'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='account',
|
||||
name='is_frozen',
|
||||
field=models.BooleanField(default=False, verbose_name='est gelé'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='account',
|
||||
name='nickname',
|
||||
field=models.CharField(default='', max_length=255, verbose_name='surnom(s)', blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='accountnegative',
|
||||
name='authz_overdraft_amount',
|
||||
field=models.DecimalField(max_digits=6, blank=True, default=None, null=True, verbose_name='négatif autorisé', decimal_places=2),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='accountnegative',
|
||||
name='authz_overdraft_until',
|
||||
field=models.DateTimeField(default=None, null=True, verbose_name='expiration du négatif', blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='accountnegative',
|
||||
name='balance_offset',
|
||||
field=models.DecimalField(blank=True, max_digits=6, help_text="Montant non compris dans l'autorisation de négatif", default=None, null=True, verbose_name='décalage de balance', decimal_places=2),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='accountnegative',
|
||||
name='comment',
|
||||
field=models.CharField(blank=True, max_length=255, verbose_name='commentaire'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='box_capacity',
|
||||
field=models.PositiveSmallIntegerField(default=None, null=True, verbose_name='capacité du contenant', blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='box_type',
|
||||
field=models.CharField(blank=True, max_length=7, choices=[('caisse', 'caisse'), ('carton', 'carton'), ('palette', 'palette'), ('fût', 'fût')], default=None, null=True, verbose_name='type de contenant'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='category',
|
||||
field=models.ForeignKey(related_name='articles', to='kfet.ArticleCategory', on_delete=django.db.models.deletion.PROTECT, verbose_name='catégorie'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='hidden',
|
||||
field=models.BooleanField(default=False, verbose_name='caché', help_text='Si oui, ne sera pas affiché au public ; par exemple sur la carte.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='is_sold',
|
||||
field=models.BooleanField(default=True, verbose_name='en vente'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='name',
|
||||
field=models.CharField(max_length=45, verbose_name='nom'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='article',
|
||||
name='price',
|
||||
field=models.DecimalField(default=0, verbose_name='prix', decimal_places=2, max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='amount_error',
|
||||
field=models.DecimalField(max_digits=6, verbose_name="montant de l'erreur", decimal_places=2),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='amount_taken',
|
||||
field=models.DecimalField(max_digits=6, verbose_name='montant pris', decimal_places=2),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='balance_new',
|
||||
field=models.DecimalField(max_digits=6, verbose_name='nouvelle balance', decimal_places=2),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='balance_old',
|
||||
field=models.DecimalField(max_digits=6, verbose_name='ancienne balance', decimal_places=2),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='not_count',
|
||||
field=models.BooleanField(default=False, verbose_name='caisse non comptée'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_001',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='pièces de 1¢'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_002',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='pièces de 2¢'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_005',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='pièces de 5¢'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_01',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='pièces de 10¢'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_02',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='pièces de 20¢'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_05',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='pièces de 50¢'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_1',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='pièces de 1€'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_10',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='billets de 10€'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_100',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='billets de 100€'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_2',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='pièces de 2€'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_20',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='billets de 20€'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_200',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='billets de 200€'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_5',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='billets de 5€'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_50',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='billets de 50€'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_500',
|
||||
field=models.PositiveSmallIntegerField(default=0, verbose_name='billets de 500€'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='checkoutstatement',
|
||||
name='taken_cheque',
|
||||
field=models.DecimalField(default=0, verbose_name='montant des chèques', decimal_places=2, max_digits=6),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplier',
|
||||
name='address',
|
||||
field=models.TextField(verbose_name='adresse'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplier',
|
||||
name='comment',
|
||||
field=models.TextField(verbose_name='commentaire'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplier',
|
||||
name='email',
|
||||
field=models.EmailField(max_length=254, verbose_name='adresse mail'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplier',
|
||||
name='name',
|
||||
field=models.CharField(max_length=45, verbose_name='nom'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='supplier',
|
||||
name='phone',
|
||||
field=models.CharField(max_length=10, verbose_name='téléphone'),
|
||||
),
|
||||
]
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue