diff --git a/.gitignore b/.gitignore
index f12190af..ab791b2e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,8 @@ venv/
/src
media/
*.log
+*.sqlite3
+
+# PyCharm
+.idea
+.cache
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f2635b7b..19bcc736 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,25 +1,24 @@
services:
- - mysql:latest
+ - postgres:latest
- redis:latest
variables:
# GestioCOF settings
- DJANGO_SETTINGS_MODULE: "cof.settings_dev"
- DBNAME: "cof_gestion"
- DBUSER: "cof_gestion"
- DBPASSWD: "cof_password"
- DBHOST: "mysql"
+ DJANGO_SETTINGS_MODULE: "cof.settings.prod"
+ DBHOST: "postgres"
REDIS_HOST: "redis"
+ REDIS_PASSWD: "dummy"
# Cached packages
PYTHONPATH: "$CI_PROJECT_DIR/vendor/python"
- # mysql service configuration
- MYSQL_DATABASE: "$DBNAME"
- MYSQL_USER: "$DBUSER"
- MYSQL_PASSWORD: "$DBPASSWD"
- MYSQL_ROOT_PASSWORD: "root_password"
+ # postgres service configuration
+ POSTGRES_PASSWORD: "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
+ POSTGRES_USER: "cof_gestion"
+ POSTGRES_DB: "cof_gestion"
+ # psql password authentication
+ PGPASSWORD: $POSTGRES_PASSWORD
cache:
paths:
@@ -29,13 +28,12 @@ cache:
before_script:
- mkdir -p vendor/{python,pip,apt}
- - apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq mysql-client
- - mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DBHOST"
- -e "GRANT ALL ON test_$DBNAME.* TO '$DBUSER'@'%'"
+ - apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client
+ - sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' cof/settings/secret_example.py > cof/settings/secret.py
+ - sed -i.bak -E 's;^REDIS_PASSWD = .*$;REDIS_PASSWD = "";' cof/settings/secret.py
# 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
+ - psql --username=$POSTGRES_USER --host=$DBHOST -c "DROP DATABASE IF EXISTS test_$POSTGRES_DB"
+ - pip install --upgrade --cache-dir vendor/pip -t vendor/python -r requirements.txt
test:
stage: test
diff --git a/README.md b/README.md
index 1a3d575e..01f4ead2 100644
--- a/README.md
+++ b/README.md
@@ -66,119 +66,65 @@ car par défaut Django n'écoute que sur l'adresse locale de la machine virtuell
or vous voudrez accéder à GestioCOF depuis votre machine physique. L'url à
entrer dans le navigateur est `localhost:8000`.
+
#### Serveur de développement type production
-Sur la VM Vagrant, un serveur apache est configuré pour servir GestioCOF de
-façon similaire à la version en production : on utilise
+Juste histoire de jouer, pas indispensable pour développer :
+
+La VM Vagrant héberge en plus un serveur nginx configuré pour servir GestioCOF
+comme en production : on utilise
[Daphne](https://github.com/django/daphne/) et `python manage.py runworker`
-derrière un reverse-proxy apache. Le tout est monitoré par
-[supervisor](http://supervisord.org/).
+derrière un reverse-proxy nginx.
Ce serveur se lance tout seul et est accessible en dehors de la VM à l'url
-`localhost:8080`. Toutefois il ne se recharge pas tout seul lorsque le code
-change, il faut relancer le worker avec `sudo supervisorctl restart worker` pour
-visualiser la dernière version du code.
+`localhost:8080/gestion/`. Toutefois il ne se recharge pas tout seul lorsque le
+code change, il faut relancer le worker avec `sudo systemctl restart
+worker.service` pour visualiser la dernière version du code.
+
### Installation manuelle
-Si vous optez pour une installation manuelle plutôt que d'utiliser Vagrant, il
-est fortement conseillé d'utiliser un environnement virtuel pour Python.
+Vous pouvez opter 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 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 ainsi
+que sqlite3, un moteur de base de données léger et simple d'utilisation. Sous
+Debian et dérivées (Ubuntu, ...) :
- sudo apt-get install python-pip python-dev libmysqlclient-dev redis-server
+ sudo apt-get install python3-pip python3-dev sqlite3
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 -p $(which python3)
+ python3 -m venv venv
-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
+Pour l'activer, il faut faire
- . env/bin/activate
+ . venv/bin/activate
dans le même dossier.
Vous pouvez maintenant installer les dépendances Python depuis le fichier
`requirements-devel.txt` :
+ pip install -U pip
pip install -r requirements-devel.txt
-Copiez le fichier `cof/settings_dev.py` dans `cof/settings.py`.
+Pour terminer, copier le fichier `cof/settings/secret_example.py` vers
+`cof/settings/secret.py`. Sous Linux ou Mac, préférez plutôt un lien symbolique
+pour profiter de façon transparente des mises à jour du fichier:
-#### Installation avec MySQL
+ ln -s secret_example.py cof/settings/secret.py
-Il faut maintenant installer MySQL. Si vous n'avez pas déjà MySQL installé sur
-votre serveur, il faut l'installer ; sous Debian et dérivées (Ubuntu, ...) :
-
- sudo apt-get install mysql-server
-
-Il vous demandera un mot de passe pour le compte d'administration MySQL,
-notez-le quelque part (ou n'en mettez pas, le serveur n'est accessible que
-localement par défaut). Si vous utilisez une autre distribution, consultez la
-documentation de votre distribution pour savoir comment changer ce mot de passe
-et démarrer le serveur MySQL (c'est automatique sous Ubuntu).
-
-Vous devez alors créer un utilisateur local et une base `cof_gestion`, avec le
-mot de passe de votre choix (remplacez `mot_de_passe`) :
-
- mysql -uroot -e "CREATE DATABASE cof_gestion; GRANT ALL PRIVILEGES ON cof_gestion.* TO 'cof_gestion'@'localhost' IDENTIFIER BY 'mot_de_passe'"
-
-Éditez maintenant le fichier `cof/settings.py` pour y intégrer ces changements ;
-la définition de `DATABASES` doit ressembler à (à nouveau, remplacez
-`mot_de_passe` de façon appropriée) :
-
- DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.mysql',
- 'NAME': 'cof_gestion',
- 'USER': 'cof_gestion',
- 'PASSWORD': 'mot_de_passe',
- }
- }
-
-#### Installation avec SQLite
-
-GestioCOF est installé avec MySQL sur la VM COF, et afin d'avoir un
-environnement de développement aussi proche que possible de ce qui tourne en
-vrai pour éviter les mauvaises surprises, il est conseillé d'utiliser MySQL sur
-votre machine de développement également. Toutefois, GestioCOF devrait
-fonctionner avec d'autres moteurs SQL, et certains préfèrent utiliser SQLite
-pour sa légèreté et facilité d'installation.
-
-Si vous décidez d'utiliser SQLite, il faut l'installer ; sous Debian et dérivées :
-
- sudo apt-get install sqlite3
-
-puis éditer le fichier `cof/settings.py` pour que la définition de `DATABASES`
-ressemble à :
-
- DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
- }
- }
#### Fin d'installation
-Il ne vous reste plus qu'à initialiser les modèles de Django avec la commande suivante :
+Il ne vous reste plus qu'à initialiser les modèles de Django et peupler la base
+de donnée avec les données nécessaires au bon fonctionnement de GestioCOF + des
+données bidons bien pratiques pour développer avec la commande suivante :
- python manage.py migrate
-
-Charger les mails indispensables au bon fonctionnement de GestioCOF :
-
- python manage.py syncmails
-
-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
+ bash provisioning/prepare_django.sh
Vous êtes prêts à développer ! Lancer GestioCOF en faisant
@@ -188,7 +134,7 @@ Vous êtes prêts à développer ! Lancer GestioCOF en faisant
Pour mettre à jour les paquets Python, utiliser la commande suivante :
- pip install --upgrade -r requirements.txt -r requirements-devel.txt
+ pip install --upgrade -r requirements-devel.txt
Pour mettre à jour les modèles après une migration, il faut ensuite faire :
@@ -197,6 +143,6 @@ Pour mettre à jour les modèles après une migration, il faut ensuite faire :
## Documentation utilisateur
-Une brève documentation utilisateur pour se familiariser plus vite avec l'outil
-est accessible sur le
-[wiki](https://git.eleves.ens.fr/cof-geek/gestioCOF/wikis/home).
+Une brève documentation utilisateur est accessible sur le
+[wiki](https://git.eleves.ens.fr/cof-geek/gestioCOF/wikis/home) pour avoir une
+idée de la façon dont le COF utilise GestioCOF.
diff --git a/bda/__init__.py b/bda/__init__.py
index e69de29b..8b137891 100644
--- a/bda/__init__.py
+++ b/bda/__init__.py
@@ -0,0 +1 @@
+
diff --git a/bda/admin.py b/bda/admin.py
index 0cc66d43..6638ad45 100644
--- a/bda/admin.py
+++ b/bda/admin.py
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
-import autocomplete_light
from datetime import timedelta
from custommail.shortcuts import send_mass_custom_mail
@@ -9,6 +8,9 @@ from django.db.models import Sum, Count
from django.template.defaultfilters import pluralize
from django.utils import timezone
from django import forms
+
+from dal.autocomplete import ModelSelect2
+
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
@@ -24,8 +26,17 @@ class ReadOnlyMixin(object):
return readonly_fields + self.readonly_fields_update
+class ChoixSpectacleAdminForm(forms.ModelForm):
+ class Meta:
+ widgets = {
+ 'participant': ModelSelect2(url='bda-participant-autocomplete'),
+ 'spectacle': ModelSelect2(url='bda-spectacle-autocomplete'),
+ }
+
+
class ChoixSpectacleInline(admin.TabularInline):
model = ChoixSpectacle
+ form = ChoixSpectacleAdminForm
sortable_field_name = "priority"
@@ -56,17 +67,17 @@ class AttributionInline(admin.TabularInline):
def get_queryset(self, request):
qs = super().get_queryset(request)
if self.listing is not None:
- qs.filter(spectacle__listing=self.listing)
+ qs = qs.filter(spectacle__listing=self.listing)
return qs
class WithListingAttributionInline(AttributionInline):
+ exclude = ('given', )
form = WithListingAttributionTabularAdminForm
listing = True
class WithoutListingAttributionInline(AttributionInline):
- exclude = ('given', )
form = WithoutListingAttributionTabularAdminForm
listing = False
@@ -180,7 +191,7 @@ class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin):
class ChoixSpectacleAdmin(admin.ModelAdmin):
- form = autocomplete_light.modelform_factory(ChoixSpectacle, exclude=[])
+ form = ChoixSpectacleAdminForm
def tirage(self, obj):
return obj.participant.tirage
diff --git a/bda/autocomplete_light_registry.py b/bda/autocomplete_light_registry.py
deleted file mode 100644
index 6c2f3ea6..00000000
--- a/bda/autocomplete_light_registry.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# -*- coding: utf-8 -*-
-
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import autocomplete_light
-
-from bda.models import Participant, Spectacle
-
-autocomplete_light.register(
- Participant, search_fields=('user__username', 'user__first_name',
- 'user__last_name'),
- autocomplete_js_attributes={'placeholder': 'participant...'})
-
-autocomplete_light.register(
- Spectacle, search_fields=('title', ),
- autocomplete_js_attributes={'placeholder': 'spectacle...'})
diff --git a/bda/migrations/0001_initial.py b/bda/migrations/0001_initial.py
index aa2cb252..c4494413 100644
--- a/bda/migrations/0001_initial.py
+++ b/bda/migrations/0001_initial.py
@@ -59,7 +59,7 @@ class Migration(migrations.Migration):
('price', models.FloatField(verbose_name=b"Prix d'une place", blank=True)),
('slots', models.IntegerField(verbose_name=b'Places')),
('priority', models.IntegerField(default=1000, verbose_name=b'Priorit\xc3\xa9')),
- ('location', models.ForeignKey(to='bda.Salle')),
+ ('location', models.ForeignKey(to='bda.Salle', on_delete=models.CASCADE)),
],
options={
'ordering': ('priority', 'date', 'title'),
@@ -79,27 +79,27 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='participant',
name='user',
- field=models.OneToOneField(to=settings.AUTH_USER_MODEL),
+ field=models.OneToOneField(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
),
migrations.AddField(
model_name='choixspectacle',
name='participant',
- field=models.ForeignKey(to='bda.Participant'),
+ field=models.ForeignKey(to='bda.Participant', on_delete=models.CASCADE),
),
migrations.AddField(
model_name='choixspectacle',
name='spectacle',
- field=models.ForeignKey(related_name='participants', to='bda.Spectacle'),
+ field=models.ForeignKey(related_name='participants', to='bda.Spectacle', on_delete=models.CASCADE),
),
migrations.AddField(
model_name='attribution',
name='participant',
- field=models.ForeignKey(to='bda.Participant'),
+ field=models.ForeignKey(to='bda.Participant', on_delete=models.CASCADE),
),
migrations.AddField(
model_name='attribution',
name='spectacle',
- field=models.ForeignKey(related_name='attribues', to='bda.Spectacle'),
+ field=models.ForeignKey(related_name='attribues', to='bda.Spectacle', on_delete=models.CASCADE),
),
migrations.AlterUniqueTogether(
name='choixspectacle',
diff --git a/bda/migrations/0002_add_tirage.py b/bda/migrations/0002_add_tirage.py
index 1956a4a4..79f79a57 100644
--- a/bda/migrations/0002_add_tirage.py
+++ b/bda/migrations/0002_add_tirage.py
@@ -5,17 +5,34 @@ from django.db import migrations, models
from django.conf import settings
from django.utils import timezone
-def forwards_func(apps, schema_editor):
+
+def fill_tirage_fields(apps, schema_editor):
+ """
+ Create a `Tirage` to fill new field `tirage` of `Participant`
+ and `Spectacle` already existing.
+ """
+ Participant = apps.get_model("bda", "Participant")
+ Spectacle = apps.get_model("bda", "Spectacle")
Tirage = apps.get_model("bda", "Tirage")
- db_alias = schema_editor.connection.alias
- Tirage.objects.using(db_alias).bulk_create([
- Tirage(
- id=1,
- title="Tirage de test (migration)",
- active=False,
- ouverture=timezone.now(),
- fermeture=timezone.now()),
- ])
+
+ # These querysets only contains instances not linked to any `Tirage`.
+ participants = Participant.objects.filter(tirage=None)
+ spectacles = Spectacle.objects.filter(tirage=None)
+
+ if not participants.count() and not spectacles.count():
+ # No need to create a "trash" tirage.
+ return
+
+ tirage = Tirage.objects.create(
+ title="Tirage de test (migration)",
+ active=False,
+ ouverture=timezone.now(),
+ fermeture=timezone.now(),
+ )
+
+ participants.update(tirage=tirage)
+ spectacles.update(tirage=tirage)
+
class Migration(migrations.Migration):
@@ -35,22 +52,33 @@ class Migration(migrations.Migration):
('active', models.BooleanField(default=True, verbose_name=b'Tirage actif')),
],
),
- migrations.RunPython(forwards_func, migrations.RunPython.noop),
migrations.AlterField(
model_name='participant',
name='user',
- field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
+ field=models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE),
),
+ # Create fields `spectacle` for `Participant` and `Spectacle` models.
+ # These fields are not nullable, but we first create them as nullable
+ # to give a default value for existing instances of these models.
migrations.AddField(
model_name='participant',
name='tirage',
- field=models.ForeignKey(default=1, to='bda.Tirage'),
- preserve_default=False,
+ field=models.ForeignKey(to='bda.Tirage', null=True, on_delete=models.CASCADE),
),
migrations.AddField(
model_name='spectacle',
name='tirage',
- field=models.ForeignKey(default=1, to='bda.Tirage'),
- preserve_default=False,
+ field=models.ForeignKey(to='bda.Tirage', null=True, on_delete=models.CASCADE),
+ ),
+ migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop),
+ migrations.AlterField(
+ model_name='participant',
+ name='tirage',
+ field=models.ForeignKey(to='bda.Tirage', on_delete=models.CASCADE),
+ ),
+ migrations.AlterField(
+ model_name='spectacle',
+ name='tirage',
+ field=models.ForeignKey(to='bda.Tirage', on_delete=models.CASCADE),
),
]
diff --git a/bda/migrations/0007_extends_spectacle.py b/bda/migrations/0007_extends_spectacle.py
index b95c18de..6ea11dc0 100644
--- a/bda/migrations/0007_extends_spectacle.py
+++ b/bda/migrations/0007_extends_spectacle.py
@@ -73,6 +73,7 @@ class Migration(migrations.Migration):
model_name='spectacle',
name='category',
field=models.ForeignKey(blank=True, to='bda.CategorieSpectacle',
+ on_delete=models.CASCADE,
null=True),
),
migrations.AddField(
@@ -84,6 +85,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='quote',
name='spectacle',
- field=models.ForeignKey(to='bda.Spectacle'),
+ field=models.ForeignKey(to='bda.Spectacle',
+ on_delete=models.CASCADE),
),
]
diff --git a/bda/migrations/0009_revente.py b/bda/migrations/0009_revente.py
index 1cca4e86..70d6f338 100644
--- a/bda/migrations/0009_revente.py
+++ b/bda/migrations/0009_revente.py
@@ -47,12 +47,14 @@ class Migration(migrations.Migration):
model_name='spectaclerevente',
name='attribution',
field=models.OneToOneField(to='bda.Attribution',
+ on_delete=models.CASCADE,
related_name='revente'),
),
migrations.AddField(
model_name='spectaclerevente',
name='seller',
field=models.ForeignKey(to='bda.Participant',
+ on_delete=models.CASCADE,
verbose_name='Vendeur',
related_name='original_shows'),
),
@@ -60,6 +62,7 @@ class Migration(migrations.Migration):
model_name='spectaclerevente',
name='soldTo',
field=models.ForeignKey(to='bda.Participant',
+ on_delete=models.CASCADE,
verbose_name='Vendue à', null=True,
blank=True),
),
diff --git a/bda/models.py b/bda/models.py
index 0228b4c0..73356038 100644
--- a/bda/models.py
+++ b/bda/models.py
@@ -6,11 +6,23 @@ from datetime import timedelta
from custommail.shortcuts import send_mass_custom_mail
from django.contrib.sites.models import Site
+from django.core import mail
from django.db import models
+from django.db.models import Count
from django.contrib.auth.models import User
from django.conf import settings
from django.utils import timezone, formats
+from custommail.models import CustomMail
+
+
+def get_generic_user():
+ generic, _ = User.objects.get_or_create(
+ username="bda_generic",
+ defaults={"email": "bda@ens.fr", "first_name": "Bureau des arts"}
+ )
+ return generic
+
class Tirage(models.Model):
title = models.CharField("Titre", max_length=300)
@@ -50,9 +62,12 @@ class CategorieSpectacle(models.Model):
class Spectacle(models.Model):
title = models.CharField("Titre", max_length=300)
- category = models.ForeignKey(CategorieSpectacle, blank=True, null=True)
+ category = models.ForeignKey(
+ CategorieSpectacle, on_delete=models.CASCADE,
+ blank=True, null=True,
+ )
date = models.DateTimeField("Date & heure")
- location = models.ForeignKey(Salle)
+ location = models.ForeignKey(Salle, on_delete=models.CASCADE)
vips = models.TextField('Personnalités', blank=True)
description = models.TextField("Description", blank=True)
slots_description = models.TextField("Description des places", blank=True)
@@ -62,7 +77,7 @@ class Spectacle(models.Model):
max_length=500)
price = models.FloatField("Prix d'une place")
slots = models.IntegerField("Places")
- tirage = models.ForeignKey(Tirage)
+ tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
listing = models.BooleanField("Les places sont sur listing")
rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True,
null=True)
@@ -96,32 +111,29 @@ class Spectacle(models.Model):
Envoie un mail de rappel à toutes les personnes qui ont une place pour
ce spectacle.
"""
- # On récupère la liste des participants
- members = {}
- for attr in Attribution.objects.filter(spectacle=self).all():
- member = attr.participant.user
- if member.id in members:
- members[member.id][1] = 2
- else:
- 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 récupère la liste des participants + le BdA
+ members = list(
+ User.objects
+ .filter(participant__attributions=self)
+ .annotate(nb_attr=Count("id")).order_by()
+ )
+ bda_generic = get_generic_user()
+ bda_generic.nb_attr = 1
+ members.append(bda_generic)
# On écrit un mail personnalisé à chaque participant
datatuple = [(
'bda-rappel',
- {'member': member[0], 'nb_attr': member[1], 'show': self},
+ {'member': member, "nb_attr": member.nb_attr, 'show': self},
settings.MAIL_DATA['rappels']['FROM'],
- [member[0].email])
- for member in members.values()
+ [member.email])
+ for member in members
]
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()
+ return members
@property
def is_past(self):
@@ -129,7 +141,7 @@ class Spectacle(models.Model):
class Quote(models.Model):
- spectacle = models.ForeignKey(Spectacle)
+ spectacle = models.ForeignKey(Spectacle, on_delete=models.CASCADE)
text = models.TextField('Citation')
author = models.CharField('Auteur', max_length=200)
@@ -143,7 +155,7 @@ PAYMENT_TYPES = (
class Participant(models.Model):
- user = models.ForeignKey(User)
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
choices = models.ManyToManyField(Spectacle,
through="ChoixSpectacle",
related_name="chosen_by")
@@ -154,7 +166,7 @@ class Participant(models.Model):
paymenttype = models.CharField("Moyen de paiement",
max_length=6, choices=PAYMENT_TYPES,
blank=True)
- tirage = models.ForeignKey(Tirage)
+ tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
choicesrevente = models.ManyToManyField(Spectacle,
related_name="subscribed",
blank=True)
@@ -170,8 +182,11 @@ DOUBLE_CHOICES = (
class ChoixSpectacle(models.Model):
- participant = models.ForeignKey(Participant)
- spectacle = models.ForeignKey(Spectacle, related_name="participants")
+ participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
+ spectacle = models.ForeignKey(
+ Spectacle, on_delete=models.CASCADE,
+ related_name="participants",
+ )
priority = models.PositiveIntegerField("Priorité")
double_choice = models.CharField("Nombre de places",
default="1", choices=DOUBLE_CHOICES,
@@ -198,8 +213,11 @@ class ChoixSpectacle(models.Model):
class Attribution(models.Model):
- participant = models.ForeignKey(Participant)
- spectacle = models.ForeignKey(Spectacle, related_name="attribues")
+ participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
+ spectacle = models.ForeignKey(
+ Spectacle, on_delete=models.CASCADE,
+ related_name="attribues",
+ )
given = models.BooleanField("Donnée", default=False)
def __str__(self):
@@ -208,18 +226,25 @@ class Attribution(models.Model):
class SpectacleRevente(models.Model):
- attribution = models.OneToOneField(Attribution,
- related_name="revente")
+ attribution = models.OneToOneField(
+ Attribution, on_delete=models.CASCADE,
+ related_name="revente",
+ )
date = models.DateTimeField("Date de mise en vente",
default=timezone.now)
answered_mail = models.ManyToManyField(Participant,
related_name="wanted",
blank=True)
- seller = models.ForeignKey(Participant,
- related_name="original_shows",
- verbose_name="Vendeur")
- soldTo = models.ForeignKey(Participant, blank=True, null=True,
- verbose_name="Vendue à")
+ seller = models.ForeignKey(
+ Participant, on_delete=models.CASCADE,
+ verbose_name="Vendeur",
+ related_name="original_shows",
+ )
+ soldTo = models.ForeignKey(
+ Participant, on_delete=models.CASCADE,
+ verbose_name="Vendue à",
+ blank=True, null=True,
+ )
notif_sent = models.BooleanField("Notification envoyée",
default=False)
@@ -306,37 +331,55 @@ class SpectacleRevente(models.Model):
# Envoie un mail au gagnant et au vendeur
winner = random.choice(inscrits)
self.soldTo = winner
- datatuple = []
+
+ mails = []
+
context = {
'acheteur': winner.user,
'vendeur': seller.user,
'show': spectacle,
}
- datatuple.append((
- 'bda-revente-winner',
- context,
- settings.MAIL_DATA['revente']['FROM'],
- [winner.user.email],
- ))
- datatuple.append((
+
+ c_mails_qs = CustomMail.objects.filter(shortname__in=[
+ 'bda-revente-winner', 'bda-revente-loser',
'bda-revente-seller',
- context,
- settings.MAIL_DATA['revente']['FROM'],
- [seller.user.email]
- ))
+ ])
+
+ c_mails = {cm.shortname: cm for cm in c_mails_qs}
+
+ mails.append(
+ c_mails['bda-revente-winner'].get_message(
+ context,
+ from_email=settings.MAIL_DATA['revente']['FROM'],
+ to=[winner.user.email],
+ )
+ )
+
+ mails.append(
+ c_mails['bda-revente-seller'].get_message(
+ context,
+ from_email=settings.MAIL_DATA['revente']['FROM'],
+ to=[seller.user.email],
+ reply_to=[winner.user.email],
+ )
+ )
# Envoie un mail aux perdants
for inscrit in inscrits:
if inscrit != winner:
new_context = dict(context)
new_context['acheteur'] = inscrit.user
- datatuple.append((
- 'bda-revente-loser',
- new_context,
- settings.MAIL_DATA['revente']['FROM'],
- [inscrit.user.email]
- ))
- send_mass_custom_mail(datatuple)
+
+ mails.append(
+ c_mails['bda-revente-loser'].get_message(
+ new_context,
+ from_email=settings.MAIL_DATA['revente']['FROM'],
+ to=[inscrit.user.email],
+ )
+ )
+
+ mail_conn = mail.get_connection()
+ mail_conn.send_messages(mails)
# Si personne ne veut de la place, elle part au shotgun
else:
self.shotgun = True
diff --git a/bda/templates/bda/mails-rappel.html b/bda/templates/bda/mails-rappel.html
index 73625d1c..c10503b0 100644
--- a/bda/templates/bda/mails-rappel.html
+++ b/bda/templates/bda/mails-rappel.html
@@ -3,41 +3,46 @@
{% block realcontent %}
Mails de rappels
{% if sent %}
-
Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes
-
- {% for member in members %}
-
{{ member.get_full_name }} ({{ member.email }})
- {% endfor %}
-
+
Les mails de rappel pour le spectacle {{ show.title }} ont bien été envoyés aux personnes suivantes
+
+ {% for member in members %}
+
{{ member.get_full_name }} ({{ member.email }})
+ {% endfor %}
+
{% else %}
-
Voulez vous envoyer les mails de rappel pour le spectacle
- {{ show.title }} ?
- {% if show.rappel_sent %}
-
Attention, les mails ont déjà été envoyés le
- {{ show.rappel_sent }}
- {% endif %}
+
Voulez vous envoyer les mails de rappel pour le spectacle {{ show.title }} ?
{% endif %}
- {% if not sent %}
-
- {% endif %}
+
+ {% if not sent %}
+
+ {% endif %}
+
+
+
+
+
+ Note : le template de ce mail peut être modifié à
+ cette adresse
+
+
+
-
-
Forme des mails
Une seule place
- {% for part in exemple_mail_1place %}
-
{{ part }}
- {% endfor %}
+ {% for part in exemple_mail_1place %}
+
{{ part }}
+ {% endfor %}
Deux places
{% for part in exemple_mail_2places %}
-
{{ part }}
+
{{ part }}
{% endfor %}
+
{% endblock %}
diff --git a/bda/templates/bda-participants.html b/bda/templates/bda/participants.html
similarity index 73%
rename from bda/templates/bda-participants.html
rename to bda/templates/bda/participants.html
index 289d1761..c3ff31d6 100644
--- a/bda/templates/bda-participants.html
+++ b/bda/templates/bda/participants.html
@@ -36,17 +36,26 @@