Compare commits
1 commit
master
...
kerl/sitec
Author | SHA1 | Date | |
---|---|---|---|
|
e56123e5f7 |
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -5,7 +5,6 @@ cof/settings.py
|
|||
settings.py
|
||||
*~
|
||||
venv/
|
||||
.venv/
|
||||
.vagrant
|
||||
/src
|
||||
media/
|
||||
|
@ -20,4 +19,3 @@ media/
|
|||
|
||||
# VSCode
|
||||
.vscode/
|
||||
.direnv
|
||||
|
|
|
@ -17,15 +17,12 @@ variables:
|
|||
# psql password authentication
|
||||
PGPASSWORD: $POSTGRES_PASSWORD
|
||||
|
||||
# apps to check migrations for
|
||||
MIGRATION_APPS: "bda bds cofcms clubs events gestioncof kfet kfetauth kfetcms open petitscours shared"
|
||||
|
||||
.test_template:
|
||||
before_script:
|
||||
- mkdir -p vendor/{pip,apt}
|
||||
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client libldap2-dev libsasl2-dev
|
||||
- sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' gestioasso/settings/secret_example.py > gestioasso/settings/secret.py
|
||||
- sed -i.bak -E 's;^REDIS_PASSWD = .*$;REDIS_PASSWD = "";' gestioasso/settings/secret.py
|
||||
- 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
|
||||
- psql --username=$POSTGRES_USER --host=$DBHOST -c "DROP DATABASE IF EXISTS test_$POSTGRES_DB"
|
||||
- pip install --upgrade -r requirements-prod.txt coverage tblib
|
||||
|
@ -47,7 +44,7 @@ coftest:
|
|||
stage: test
|
||||
extends: .test_template
|
||||
variables:
|
||||
DJANGO_SETTINGS_MODULE: "gestioasso.settings.cof_prod"
|
||||
DJANGO_SETTINGS_MODULE: "cof.settings.cof_prod"
|
||||
script:
|
||||
- coverage run manage.py test gestioncof bda kfet petitscours shared --parallel
|
||||
|
||||
|
@ -55,7 +52,7 @@ bdstest:
|
|||
stage: test
|
||||
extends: .test_template
|
||||
variables:
|
||||
DJANGO_SETTINGS_MODULE: "gestioasso.settings.bds_prod"
|
||||
DJANGO_SETTINGS_MODULE: "cof.settings.bds_prod"
|
||||
script:
|
||||
- coverage run manage.py test bds clubs events --parallel
|
||||
|
||||
|
@ -68,7 +65,7 @@ linters:
|
|||
- black --check .
|
||||
- isort --check --diff .
|
||||
# Print errors only
|
||||
- flake8 --exit-zero bda bds clubs gestioasso events gestioncof kfet petitscours provisioning shared
|
||||
- flake8 --exit-zero bda bds clubs cof events gestioncof kfet petitscours provisioning shared
|
||||
cache:
|
||||
key: linters
|
||||
paths:
|
||||
|
@ -78,14 +75,14 @@ linters:
|
|||
migration_checks:
|
||||
stage: test
|
||||
variables:
|
||||
DJANGO_SETTINGS_MODULE: "gestioasso.settings.local"
|
||||
DJANGO_SETTINGS_MODULE: "cof.settings.local"
|
||||
before_script:
|
||||
- mkdir -p vendor/{pip,apt}
|
||||
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client libldap2-dev libsasl2-dev
|
||||
- cp gestioasso/settings/secret_example.py gestioasso/settings/secret.py
|
||||
- cp cof/settings/secret_example.py cof/settings/secret.py
|
||||
- pip install --upgrade -r requirements-devel.txt
|
||||
- python --version
|
||||
script: python manage.py makemigrations --dry-run --check $MIGRATION_APPS
|
||||
script: python manage.py makemigrations --dry-run --check
|
||||
services:
|
||||
# this should not be necessary…
|
||||
- postgres:11.7
|
||||
|
|
157
CHANGELOG.md
157
CHANGELOG.md
|
@ -21,165 +21,12 @@ Liste des changements notables dans GestioCOF depuis la version 0.1 (septembre
|
|||
Uniquement un modèle simple de clubs avec des respos. Aucune gestion des
|
||||
adhérents ni des cotisations.
|
||||
|
||||
## TODO Prod
|
||||
## Upcoming
|
||||
|
||||
- Créer un compte hCaptcha (https://www.hcaptcha.com/), au COF, et remplacer les secrets associés
|
||||
|
||||
## Version ??? - ??/??/????
|
||||
|
||||
## Version 0.15.1 - 15/06/2023
|
||||
|
||||
### K-Fêt
|
||||
|
||||
- Rattrape les erreurs d'envoi de mail de négatif
|
||||
- Utilise l'adresse chefs pour les envois de négatifs
|
||||
|
||||
## Version 0.15 - 22/05/2023
|
||||
|
||||
### K-Fêt
|
||||
|
||||
- Rajoute un formulaire de contact
|
||||
- Rajoute un formulaire de demande de soirée
|
||||
- Désactive les mails d'envoi de négatifs sur les comptes gelés
|
||||
|
||||
## Version 0.14 - 19/05/2023
|
||||
|
||||
- Répare les dépendances en spécifiant toutes les versions
|
||||
|
||||
### K-Fêt
|
||||
|
||||
- Répare la gestion des changement d'heure via moment.js
|
||||
|
||||
## Version 0.13 - 19/02/2023
|
||||
|
||||
### K-Fêt
|
||||
|
||||
- Rajoute la valeur des inventaires
|
||||
- Résout les problèmes de négatif ne disparaissant pas
|
||||
- Affiche son surnom s'il y en a un
|
||||
- Bugfixes
|
||||
|
||||
## Version 0.12.1 - 03/10/2022
|
||||
|
||||
### K-Fêt
|
||||
|
||||
- Fixe un problème de rendu causé par l'agrandissement du menu
|
||||
|
||||
## Version 0.12 - 17/06/2022
|
||||
|
||||
### K-Fêt
|
||||
|
||||
- Ajoute une exception à la limite d'historique pour les comptes `LIQ` et `#13`
|
||||
- Répare le problème des étiquettes LIQ/Comptes K-Fêt inversées dans les stats des articles K-Fêt
|
||||
|
||||
## Version 0.11 - 26/10/2021
|
||||
|
||||
### COF
|
||||
|
||||
- Répare un problème de rendu sur le wagtail du COF
|
||||
|
||||
### K-Fêt
|
||||
|
||||
- Ajoute de mails de rappels pour les comptes en négatif
|
||||
- La recherche de comptes sur K-Psul remarche normalement
|
||||
- Le pointeur de la souris change de forme quand on survole un item d'autocomplétion
|
||||
- Modification du gel de compte:
|
||||
- on ne peut plus geler/dégeler son compte soi-même (il faut la permission "Gérer les permissions K-Fêt")
|
||||
- on ne peut rien compter sur un compte gelé (aucune override possible), et les K-Fêteux·ses dont le compte est gelé perdent tout accès à K-Psul
|
||||
- les comptes actuellement gelés (sur l'ancien système) sont dégelés automatiquement
|
||||
- Modification du fonctionnement des négatifs
|
||||
- impossible d'avoir des négatifs inférieurs à `kfet_config.overdraft_amount`
|
||||
- il n'y a plus de limite de temps sur les négatifs
|
||||
- supression des autorisations de négatif
|
||||
- il n'est plus possible de réinitialiser la durée d'un négatif en faisant puis en annulant une charge
|
||||
- La gestion des erreurs passe du client au serveur, ce qui permet d'avoir des messages plus explicites
|
||||
- La supression d'opérations anciennes est réparée
|
||||
|
||||
## Version 0.10 - 18/04/2021
|
||||
|
||||
### K-Fêt
|
||||
|
||||
- On fait sauter la limite qui empêchait de vendre plus de 24 unités d'un item à
|
||||
la fois.
|
||||
- L'interface indique plus clairement quand on fait une erreur en modifiant un
|
||||
compte.
|
||||
- On supprime la fonction "décalage de balance".
|
||||
- L'accès à l'historique est maintenant limité à 7 jours pour raison de
|
||||
confidentialité. Les chefs/trez peuvent disposer d'une permission
|
||||
supplémentaire pour accéder à jusqu'à 30 jours en cas de problème de compta.
|
||||
L'accès à son historique personnel n'est pas limité. Les durées sont
|
||||
configurables dans `settings/cof_prod.py`.
|
||||
|
||||
### COF
|
||||
|
||||
- Le Captcha sur la page de demande de petits cours utilise maintenant hCaptcha
|
||||
au lieu de ReCaptcha, pour mieux respecter la vie privée des utilisateur·ices
|
||||
|
||||
## Version 0.9 - 06/02/2020
|
||||
|
||||
### COF / BdA
|
||||
|
||||
- Le COF peut remettre à zéro la liste de ses adhérents en août (sans passer par
|
||||
KDE).
|
||||
- La page d'accueil affiche la date de fermeture des tirages BdA.
|
||||
- On peut revendre une place dès qu'on l'a payée, plus besoin de payer toutes
|
||||
ses places pour pouvoir revendre.
|
||||
- On s'assure que l'email fourni lors d'une demande de petit cours est valide.
|
||||
|
||||
### BDS
|
||||
|
||||
- Le burô peut maintenant accorder ou révoquer le statut de membre du Burô
|
||||
en modifiant le profil d'un membre du BDS.
|
||||
- Le burô peut exporter la liste de ses membres avec email au format CSV depuis
|
||||
la page d'accueil.
|
||||
|
||||
### K-Fêt
|
||||
|
||||
- On affiche les articles actuellement en vente en premier lors des inventaires
|
||||
et des commandes.
|
||||
- On peut supprimer un inventaire. Seuls les articles dont c'est le dernier
|
||||
inventaire sont affectés.
|
||||
|
||||
## Version 0.8 - 03/12/2020
|
||||
|
||||
### COF
|
||||
|
||||
- La page "Mes places" dans la section BdA indique quelles places sont sur
|
||||
listing.
|
||||
- ergonomie de l'interface admin du BdA : moins d'options inutiles lors de
|
||||
la sélection de participants.
|
||||
- les tirages sont maintenant archivables pour éviter d'avoir encore d'autres
|
||||
options inutiles.
|
||||
- l'autocomplétion dans l'admin BdA est réparée.
|
||||
- Les icones de la page de gestion des petits cours sont (à nouveau) réparées.
|
||||
- On a supprimé la possibilité de modifier les mails automatiques depuis
|
||||
l'interface admin car trop problématique. Faute de mieux, envoyer un mail à
|
||||
KDE pour modifier ces mails.
|
||||
- corrige un crash sporadique sur la page d'inscription au système de petits
|
||||
cours
|
||||
|
||||
### K-Fêt
|
||||
|
||||
- (fix partiel) Empêche la K-Fêt de modifier des données COF (e.g. nom, prénom,
|
||||
username) lors de la création d'un nouveau compte.
|
||||
- Les statistiques de conso globales montrent deux courbes COF / non-COF au
|
||||
lieu de LIQ / sur compte.
|
||||
- Un bug empêchait de fermer manuellement la K-Fêt depuis un compte non
|
||||
privilégié en tapant un mot de passe. C'est corrigé.
|
||||
|
||||
## Version 0.7.2 - 08/09/2020
|
||||
|
||||
- Nouvelle page 404
|
||||
- Correction de bug en K-Fêt : le lien pour créer un nouveau compte exté apparaît
|
||||
à nouveau dans l'autocomplétion
|
||||
|
||||
## Version 0.7.1 - 05/09/2020
|
||||
|
||||
Petits ajustements sur le site du COF :
|
||||
### Site du COF
|
||||
|
||||
- Possibilité d'ajouter des champs d'infos supplémentaires en plus de l'email et
|
||||
de la page web dans les annuaires (clubs et partenaires).
|
||||
- Corrige un bug d'affichage des adresses emails de clubs
|
||||
|
||||
## Version 0.7 - 29/08/2020
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# GestioCOF / GestioBDS
|
||||
# GestioCOF
|
||||
|
||||
[![pipeline status](https://git.eleves.ens.fr/cof-geek/gestioCOF/badges/master/pipeline.svg)](https://git.eleves.ens.fr/cof-geek/gestioCOF/commits/master)
|
||||
[![coverage report](https://git.eleves.ens.fr/cof-geek/gestioCOF/badges/master/coverage.svg)](https://git.eleves.ens.fr/cof-geek/gestioCOF/commits/master)
|
||||
|
@ -38,11 +38,11 @@ Vous pouvez maintenant installer les dépendances Python depuis le fichier
|
|||
pip install -U pip # parfois nécessaire la première fois
|
||||
pip install -r requirements-devel.txt
|
||||
|
||||
Pour terminer, copier le fichier `gestioasso/settings/secret_example.py` vers
|
||||
`gestioasso/settings/secret.py`. Sous Linux ou Mac, préférez plutôt un lien symbolique
|
||||
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:
|
||||
|
||||
ln -s secret_example.py gestioasso/settings/secret.py
|
||||
ln -s secret_example.py cof/settings/secret.py
|
||||
|
||||
Nous avons un git hook de pre-commit pour formatter et vérifier que votre code
|
||||
vérifie nos conventions. Pour bénéficier des mises à jour du hook, préférez
|
||||
|
|
42
Vagrantfile
vendored
42
Vagrantfile
vendored
|
@ -1,19 +1,47 @@
|
|||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
# Configuration de base pour GestioCOF.
|
||||
# Voir https://docs.vagrantup.com pour plus d'informations.
|
||||
# All Vagrant configuration is done below. The "2" in Vagrant.configure
|
||||
# configures the configuration version (we support older styles for
|
||||
# backwards compatibility). Please don't change it unless you know what
|
||||
# you're doing.
|
||||
Vagrant.configure(2) do |config|
|
||||
# On se base sur Debian 10 (Buster) pour avoir le même environnement qu'en
|
||||
# production.
|
||||
config.vm.box = "debian/contrib-buster64"
|
||||
# The most common configuration options are documented and commented below.
|
||||
# For a complete reference, please see the online documentation at
|
||||
# https://docs.vagrantup.com.
|
||||
|
||||
config.vm.box = "ubuntu/xenial64"
|
||||
|
||||
# On associe le port 80 dans la machine virtuelle avec le port 8080 de notre
|
||||
# ordinateur, et le port 8000 avec le port 8000.
|
||||
config.vm.network :forwarded_port, guest: 80, host: 8080
|
||||
config.vm.network :forwarded_port, guest: 8000, host: 8000
|
||||
|
||||
# Le restes de la configuration (installation de paquets, etc) est géré un
|
||||
# script shell.
|
||||
# Create a private network, which allows host-only access to the machine
|
||||
# using a specific IP.
|
||||
# config.vm.network "private_network", ip: "192.168.33.10"
|
||||
|
||||
# Provider-specific configuration so you can fine-tune various
|
||||
# backing providers for Vagrant. These expose provider-specific options.
|
||||
# Example for VirtualBox:
|
||||
#
|
||||
# config.vm.provider "virtualbox" do |vb|
|
||||
# # Display the VirtualBox GUI when booting the machine
|
||||
# vb.gui = true
|
||||
#
|
||||
# # Customize the amount of memory on the VM:
|
||||
# vb.memory = "1024"
|
||||
# end
|
||||
#
|
||||
# View the documentation for the provider you are using for more
|
||||
# information on available options.
|
||||
|
||||
# Enable provisioning with a shell script. Additional provisioners such as
|
||||
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
|
||||
# documentation for more information about their specific syntax and use.
|
||||
# config.vm.provision "shell", inline: <<-SHELL
|
||||
# sudo apt-get update
|
||||
# sudo apt-get install -y apache2
|
||||
# SHELL
|
||||
config.vm.provision :shell, path: "provisioning/bootstrap.sh"
|
||||
end
|
||||
|
|
81
bda/admin.py
81
bda/admin.py
|
@ -1,11 +1,10 @@
|
|||
from datetime import timedelta
|
||||
|
||||
from custommail.shortcuts import send_mass_custom_mail
|
||||
from dal.autocomplete import ModelSelect2
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.core.mail import send_mass_mail
|
||||
from django.db.models import Count, Q, Sum
|
||||
from django.template import loader
|
||||
from django.template.defaultfilters import pluralize
|
||||
from django.utils import timezone
|
||||
|
||||
|
@ -33,6 +32,20 @@ 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"
|
||||
|
||||
|
||||
class AttributionTabularAdminForm(forms.ModelForm):
|
||||
listing = None
|
||||
|
||||
|
@ -80,12 +93,9 @@ class WithoutListingAttributionInline(AttributionInline):
|
|||
class ParticipantAdminForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
queryset = Spectacle.objects.select_related("location")
|
||||
|
||||
if self.instance.pk is not None:
|
||||
queryset = queryset.filter(tirage=self.instance.tirage)
|
||||
|
||||
self.fields["choicesrevente"].queryset = queryset
|
||||
self.fields["choicesrevente"].queryset = Spectacle.objects.select_related(
|
||||
"location"
|
||||
)
|
||||
|
||||
|
||||
class ParticipantPaidFilter(admin.SimpleListFilter):
|
||||
|
@ -159,23 +169,19 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
|||
form = ParticipantAdminForm
|
||||
|
||||
def send_attribs(self, request, queryset):
|
||||
emails = []
|
||||
datatuple = []
|
||||
for member in queryset.all():
|
||||
subject = "Résultats du tirage au sort"
|
||||
attribs = member.attributions.all()
|
||||
context = {"member": member.user}
|
||||
|
||||
template_name = ""
|
||||
shortname = ""
|
||||
if len(attribs) == 0:
|
||||
template_name = "bda/mails/attributions-decus.txt"
|
||||
shortname = "bda-attributions-decus"
|
||||
else:
|
||||
template_name = "bda/mails/attributions.txt"
|
||||
shortname = "bda-attributions"
|
||||
context["places"] = attribs
|
||||
|
||||
message = loader.render_to_string(template_name, context)
|
||||
emails.append((subject, message, "bda@ens.fr", [member.user.email]))
|
||||
|
||||
send_mass_mail(emails)
|
||||
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"
|
||||
|
@ -191,6 +197,17 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
|||
|
||||
|
||||
class AttributionAdminForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if "spectacle" in self.fields:
|
||||
self.fields["spectacle"].queryset = Spectacle.objects.select_related(
|
||||
"location"
|
||||
)
|
||||
if "participant" in self.fields:
|
||||
self.fields["participant"].queryset = Participant.objects.select_related(
|
||||
"user", "tirage"
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
participant = cleaned_data.get("participant")
|
||||
|
@ -203,14 +220,9 @@ class AttributionAdminForm(forms.ModelForm):
|
|||
)
|
||||
return cleaned_data
|
||||
|
||||
class Meta:
|
||||
widgets = {
|
||||
"participant": ModelSelect2(url="bda-participant-autocomplete"),
|
||||
"spectacle": ModelSelect2(url="bda-spectacle-autocomplete"),
|
||||
}
|
||||
|
||||
|
||||
class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||
|
||||
list_display = ("id", "spectacle", "participant", "given", "paid")
|
||||
search_fields = (
|
||||
"spectacle__title",
|
||||
|
@ -223,7 +235,7 @@ class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
|||
|
||||
|
||||
class ChoixSpectacleAdmin(admin.ModelAdmin):
|
||||
autocomplete_fields = ["participant", "spectacle"]
|
||||
form = ChoixSpectacleAdminForm
|
||||
|
||||
def tirage(self, obj):
|
||||
return obj.participant.tirage
|
||||
|
@ -267,14 +279,15 @@ class SalleAdmin(admin.ModelAdmin):
|
|||
class SpectacleReventeAdminForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
qset = Participant.objects.select_related("user", "tirage")
|
||||
|
||||
if self.instance.pk is not None:
|
||||
qset = qset.filter(tirage=self.instance.seller.tirage)
|
||||
|
||||
self.fields["confirmed_entry"].queryset = qset
|
||||
self.fields["seller"].queryset = qset
|
||||
self.fields["soldTo"].queryset = qset
|
||||
self.fields["confirmed_entry"].queryset = Participant.objects.select_related(
|
||||
"user", "tirage"
|
||||
)
|
||||
self.fields["seller"].queryset = Participant.objects.select_related(
|
||||
"user", "tirage"
|
||||
)
|
||||
self.fields["soldTo"].queryset = Participant.objects.select_related(
|
||||
"user", "tirage"
|
||||
)
|
||||
|
||||
|
||||
class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||
|
|
|
@ -2,6 +2,7 @@ import random
|
|||
|
||||
|
||||
class Algorithm(object):
|
||||
|
||||
shows = None
|
||||
ranks = None
|
||||
origranks = None
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.forms.models import BaseInlineFormSet
|
|||
from django.template import loader
|
||||
from django.utils import timezone
|
||||
|
||||
from bda.models import SpectacleRevente
|
||||
from bda.models import Attribution, Spectacle, SpectacleRevente
|
||||
|
||||
|
||||
class InscriptionInlineFormSet(BaseInlineFormSet):
|
||||
|
@ -77,7 +77,7 @@ class ResellForm(forms.Form):
|
|||
super().__init__(*args, **kwargs)
|
||||
self.fields["attributions"] = TemplateLabelField(
|
||||
queryset=participant.attribution_set.filter(
|
||||
spectacle__date__gte=timezone.now(), paid=True
|
||||
spectacle__date__gte=timezone.now()
|
||||
)
|
||||
.exclude(revente__seller=participant)
|
||||
.select_related("spectacle", "spectacle__location", "participant__user"),
|
||||
|
|
|
@ -81,7 +81,7 @@ class Command(MyBaseCommand):
|
|||
shows = random.sample(
|
||||
list(tirage.spectacle_set.all()), tirage.spectacle_set.count() // 2
|
||||
)
|
||||
for rank, show in enumerate(shows):
|
||||
for (rank, show) in enumerate(shows):
|
||||
choices.append(
|
||||
ChoixSpectacle(
|
||||
participant=part,
|
||||
|
|
|
@ -6,6 +6,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -35,6 +35,7 @@ def fill_tirage_fields(apps, schema_editor):
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0001_initial")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0002_add_tirage")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0003_update_tirage_and_spectacle")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0004_mails-rappel")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -18,6 +18,7 @@ def forwards_func(apps, schema_editor):
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0005_encoding")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0006_add_tirage_switch")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0007_extends_spectacle")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -6,6 +6,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0008_py3")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -21,6 +21,7 @@ def forwards_func(apps, schema_editor):
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0009_revente")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0010_spectaclerevente_shotgun")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0011_tirage_appear_catalogue")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -13,6 +13,7 @@ def swap_double_choice(apps, schema_editor):
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0011_tirage_appear_catalogue")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -6,6 +6,7 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0012_notif_time"), ("bda", "0012_swap_double_choice")]
|
||||
|
||||
operations = []
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0013_merge_20180524_2123")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -29,6 +29,7 @@ def set_participant_payment(apps, schema_editor):
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0014_attribution_paid_field")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0015_move_bda_payment")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bda", "0016_delete_participant_paid")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
# Generated by Django 2.2.12 on 2020-10-21 16:18
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("bda", "0017_participant_accepte_charte"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="participant",
|
||||
options={"ordering": ("-tirage", "user__last_name", "user__first_name")},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="tirage",
|
||||
name="archived",
|
||||
field=models.BooleanField(default=False, verbose_name="Archivé"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="participant",
|
||||
name="tirage",
|
||||
field=models.ForeignKey(
|
||||
limit_choices_to={"archived": False},
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="bda.Tirage",
|
||||
),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name="participant",
|
||||
constraint=models.UniqueConstraint(
|
||||
fields=("tirage", "user"), name="unique_tirage"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -2,14 +2,14 @@ import calendar
|
|||
import random
|
||||
from datetime import timedelta
|
||||
|
||||
from custommail.models import CustomMail
|
||||
from custommail.shortcuts import send_mass_custom_mail
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.core import mail
|
||||
from django.core.mail import EmailMessage, send_mass_mail
|
||||
from django.db import models
|
||||
from django.db.models import Count, Exists
|
||||
from django.template import loader
|
||||
from django.utils import formats, timezone
|
||||
|
||||
|
||||
|
@ -31,7 +31,6 @@ class Tirage(models.Model):
|
|||
"Tirage à afficher dans le catalogue", default=False
|
||||
)
|
||||
enable_do_tirage = models.BooleanField("Le tirage peut être lancé", default=False)
|
||||
archived = models.BooleanField("Archivé", default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "%s - %s" % (
|
||||
|
@ -117,19 +116,16 @@ class Spectacle(models.Model):
|
|||
bda_generic.nb_attr = 1
|
||||
members.append(bda_generic)
|
||||
# On écrit un mail personnalisé à chaque participant
|
||||
mails = [
|
||||
datatuple = [
|
||||
(
|
||||
str(self),
|
||||
loader.render_to_string(
|
||||
"bda/mails/rappel.txt",
|
||||
context={"member": member, "nb_attr": member.nb_attr, "show": self},
|
||||
),
|
||||
"bda-rappel",
|
||||
{"member": member, "nb_attr": member.nb_attr, "show": self},
|
||||
settings.MAIL_DATA["rappels"]["FROM"],
|
||||
[member.email],
|
||||
)
|
||||
for member in members
|
||||
]
|
||||
send_mass_mail(mails)
|
||||
send_mass_custom_mail(datatuple)
|
||||
# On enregistre le fait que l'envoi a bien eu lieu
|
||||
self.rappel_sent = timezone.now()
|
||||
self.save()
|
||||
|
@ -198,9 +194,7 @@ class Participant(models.Model):
|
|||
attributions = models.ManyToManyField(
|
||||
Spectacle, through="Attribution", related_name="attributed_to"
|
||||
)
|
||||
tirage = models.ForeignKey(
|
||||
Tirage, on_delete=models.CASCADE, limit_choices_to={"archived": False}
|
||||
)
|
||||
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
|
||||
accepte_charte = models.BooleanField("A accepté la charte BdA", default=False)
|
||||
choicesrevente = models.ManyToManyField(
|
||||
Spectacle, related_name="subscribed", blank=True
|
||||
|
@ -211,12 +205,6 @@ class Participant(models.Model):
|
|||
def __str__(self):
|
||||
return "%s - %s" % (self.user, self.tirage.title)
|
||||
|
||||
class Meta:
|
||||
ordering = ("-tirage", "user__last_name", "user__first_name")
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=("tirage", "user"), name="unique_tirage"),
|
||||
]
|
||||
|
||||
|
||||
DOUBLE_CHOICES = (
|
||||
("1", "1 place"),
|
||||
|
@ -360,24 +348,21 @@ class SpectacleRevente(models.Model):
|
|||
BdA-Revente à tous les intéressés.
|
||||
"""
|
||||
inscrits = self.attribution.spectacle.subscribed.select_related("user")
|
||||
mails = [
|
||||
datatuple = [
|
||||
(
|
||||
"BdA-Revente : {}".format(self.attribution.spectacle.title),
|
||||
loader.render_to_string(
|
||||
"bda/mails/revente-new.txt",
|
||||
context={
|
||||
"bda-revente",
|
||||
{
|
||||
"member": participant.user,
|
||||
"show": self.attribution.spectacle,
|
||||
"revente": self,
|
||||
"site": Site.objects.get_current(),
|
||||
},
|
||||
),
|
||||
settings.MAIL_DATA["revente"]["FROM"],
|
||||
[participant.user.email],
|
||||
)
|
||||
for participant in inscrits
|
||||
]
|
||||
send_mass_mail(mails)
|
||||
send_mass_custom_mail(datatuple)
|
||||
self.notif_sent = True
|
||||
self.notif_time = timezone.now()
|
||||
self.save()
|
||||
|
@ -388,23 +373,20 @@ class SpectacleRevente(models.Model):
|
|||
leur indiquer qu'il est désormais disponible au shotgun.
|
||||
"""
|
||||
inscrits = self.attribution.spectacle.subscribed.select_related("user")
|
||||
mails = [
|
||||
datatuple = [
|
||||
(
|
||||
"BdA-Revente : {}".format(self.attribution.spectacle.title),
|
||||
loader.render_to_string(
|
||||
"bda/mails/revente-shotgun.txt",
|
||||
context={
|
||||
"bda-shotgun",
|
||||
{
|
||||
"member": participant.user,
|
||||
"show": self.attribution.spectacle,
|
||||
"site": Site.objects.get_current(),
|
||||
},
|
||||
),
|
||||
settings.MAIL_DATA["revente"]["FROM"],
|
||||
[participant.user.email],
|
||||
)
|
||||
for participant in inscrits
|
||||
]
|
||||
send_mass_mail(mails)
|
||||
send_mass_custom_mail(datatuple)
|
||||
self.notif_sent = True
|
||||
self.notif_time = timezone.now()
|
||||
# Flag inutile, sauf si l'horloge interne merde
|
||||
|
@ -436,30 +418,31 @@ class SpectacleRevente(models.Model):
|
|||
"show": spectacle,
|
||||
}
|
||||
|
||||
subject = "BdA-Revente : {}".format(spectacle.title)
|
||||
c_mails_qs = CustomMail.objects.filter(
|
||||
shortname__in=[
|
||||
"bda-revente-winner",
|
||||
"bda-revente-loser",
|
||||
"bda-revente-seller",
|
||||
]
|
||||
)
|
||||
|
||||
c_mails = {cm.shortname: cm for cm in c_mails_qs}
|
||||
|
||||
mails.append(
|
||||
EmailMessage(
|
||||
subject=subject,
|
||||
body=loader.render_to_string(
|
||||
"bda/mails/revente-tirage-winner.txt",
|
||||
context=context,
|
||||
),
|
||||
c_mails["bda-revente-winner"].get_message(
|
||||
context,
|
||||
from_email=settings.MAIL_DATA["revente"]["FROM"],
|
||||
to=[winner.user.email],
|
||||
)
|
||||
)
|
||||
|
||||
mails.append(
|
||||
EmailMessage(
|
||||
subject=subject,
|
||||
body=loader.render_to_string(
|
||||
"bda/mails/revente-tirage-seller.txt",
|
||||
context=context,
|
||||
),
|
||||
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
|
||||
|
@ -469,15 +452,11 @@ class SpectacleRevente(models.Model):
|
|||
new_context["acheteur"] = inscrit.user
|
||||
|
||||
mails.append(
|
||||
EmailMessage(
|
||||
subject=subject,
|
||||
body=loader.render_to_string(
|
||||
"bda/mails/revente-tirage-loser.txt",
|
||||
context=new_context,
|
||||
),
|
||||
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()
|
||||
|
|
|
@ -26,6 +26,13 @@
|
|||
|
||||
<hr \>
|
||||
|
||||
<p>
|
||||
<em>Note :</em> le template de ce mail peut être modifié à
|
||||
<a href="{% url 'admin:custommail_custommail_change' custommail.pk %}">cette adresse</a>
|
||||
</p>
|
||||
|
||||
<hr \>
|
||||
|
||||
<h3>Forme des mails</h3>
|
||||
|
||||
<h4>Une seule place</h4>
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
Cher-e {{ member.first_name }},
|
||||
|
||||
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
|
|
@ -1,31 +0,0 @@
|
|||
Cher-e {{ member.first_name }},
|
||||
|
||||
Tu t'es inscrit-e pour le tirage au sort du BdA. Tu as été sélectionné-e
|
||||
pour les spectacles suivants :
|
||||
{% for place in places %}
|
||||
- 1 place pour {{ place }}{% endfor %}
|
||||
|
||||
*Paiement*
|
||||
L'intégralité de ces places de spectacles est à régler dès maintenant et AVANT
|
||||
vendredi prochain, 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 théâ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 :
|
||||
https://bda.ens.fr/lequipe/charte-bda/
|
||||
Un système de revente des places via les mails BdA-revente est 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
|
|
@ -1,23 +0,0 @@
|
|||
Bonjour {{ member.first_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 {{ nb_attr|pluralize:"une place,des places" }} sur
|
||||
listing. Il te faudra donc te rendre 15 minutes en avance sur les lieux de la représentation
|
||||
pour {{ nb_attr|pluralize:"la,les" }} retirer.
|
||||
{% 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,12 +0,0 @@
|
|||
Bonjour {{ member.first_name }}
|
||||
|
||||
Une place pour le spectacle {{ show.title }} ({{ show.date }})
|
||||
a été postée sur BdA-Revente.
|
||||
|
||||
Si ce spectacle t'intéresse toujours, merci de nous le signaler en cliquant
|
||||
sur ce lien : https://{{ site }}{% url "bda-revente-confirm" revente.id %}.
|
||||
Dans le cas où plusieurs personnes seraient intéressées, nous procèderons à
|
||||
un tirage au sort le {{ revente.date_tirage|date:"DATE_FORMAT" }}.
|
||||
|
||||
Chaleureusement,
|
||||
Le BdA
|
|
@ -1,13 +0,0 @@
|
|||
Bonjour {{ vendeur.first_name }},
|
||||
|
||||
Tu t’es bien inscrit·e pour revendre une place pour {{ show.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 apparaîtra parmi
|
||||
les « Places disponibles immédiatement à la revente » sur GestioCOF.
|
||||
{% endwith %}
|
||||
|
||||
Bonne revente !
|
||||
Le Bureau des Arts
|
|
@ -1,6 +0,0 @@
|
|||
Bonjour {{ vendeur.first_name }} !
|
||||
|
||||
Je souhaiterais racheter ta place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) à {{ show.price|floatformat:2 }}€.
|
||||
Contacte-moi si tu es toujours intéressé·e !
|
||||
|
||||
{{ acheteur.get_full_name }} ({{ acheteur.email }})
|
|
@ -1,11 +0,0 @@
|
|||
Bonjour {{ member.first_name }}
|
||||
|
||||
Une place pour le spectacle {{ show.title }} ({{ show.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
|
||||
https://{{ site }}{% url "bda-revente-buy" show.id %}, à la disposition de tous.
|
||||
|
||||
Chaleureusement,
|
||||
Le BdA
|
|
@ -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 {{ show.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,7 +0,0 @@
|
|||
Bonjour {{ vendeur.first_name }},
|
||||
|
||||
La personne tirée au sort pour racheter ta place pour {{ show.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 {{ show.title }} le {{ show.date }} ({{ show.location }}) à {{ show.price|floatformat:2 }}€.
|
||||
Tu peux contacter le/la vendeur·se à l'adresse {{ vendeur.email }}.
|
||||
|
||||
Chaleureusement,
|
||||
Le BdA
|
|
@ -16,7 +16,7 @@
|
|||
<tbody>
|
||||
{% for participant in participants %}
|
||||
<tr>
|
||||
<td data-sort-value="{{ participant.name}}">{{participant.name}}</td>
|
||||
<td data-sort-value="{{ participan.name}}">{{participant.name}}</td>
|
||||
<td data-sort-value="{{participant.nb_places}}">{{participant.nb_places}} place{{participant.nb_places|pluralize}}</td>
|
||||
<td data-sort-value="{{participant.email}}">{{participant.email}}</td>
|
||||
<td data-sort-value="{{ participant.paid}}" class={%if participant.paid %}"greenratio"{%else%}"redratio"{%endif%}>
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
<td>{{place.spectacle.location}}</td>
|
||||
<td>{{place.spectacle.date}}</td>
|
||||
<td>{% if place.double %}deux places{%else%}une place{% endif %}</td>
|
||||
<td>{% if place.spectacle.listing %}sur listing{% else %}place physique{% endif %}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
|
6
bda/templates/bda/revente/notpaid.html
Normal file
6
bda/templates/bda/revente/notpaid.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
{% extends "base_title.html" %}
|
||||
|
||||
{% block realcontent %}
|
||||
<h2><strong>Nope</strong></h2>
|
||||
<p>Avant de revendre des places, il faut aller les payer !</p>
|
||||
{% endblock %}
|
|
@ -1,3 +1,7 @@
|
|||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.utils import timezone
|
||||
|
||||
from shared.tests.mixins import ViewTestCaseMixin
|
||||
|
@ -24,6 +28,12 @@ class BdATestHelpers:
|
|||
if self.bda_testdata:
|
||||
self.load_bda_testdata()
|
||||
|
||||
def require_custommails(self):
|
||||
data_file = os.path.join(
|
||||
settings.BASE_DIR, "gestioncof", "management", "data", "custommail.json"
|
||||
)
|
||||
call_command("syncmails", data_file, verbosity=0)
|
||||
|
||||
def load_bda_testdata(self):
|
||||
self.tirage = Tirage.objects.create(
|
||||
title="Test tirage",
|
||||
|
|
|
@ -19,6 +19,8 @@ User = get_user_model()
|
|||
|
||||
|
||||
class SpectacleReventeTests(TestCase):
|
||||
fixtures = ["gestioncof/management/data/custommail.json"]
|
||||
|
||||
def setUp(self):
|
||||
now = timezone.now()
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ class InscriptionViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
|||
|
||||
@property
|
||||
def url_expected(self):
|
||||
return "/gestion/bda/inscription/{}".format(self.tirage.id)
|
||||
return "/bda/inscription/{}".format(self.tirage.id)
|
||||
|
||||
def test_get_opened(self):
|
||||
self.tirage.ouverture = timezone.now() - timedelta(days=1)
|
||||
|
@ -149,7 +149,7 @@ class PlacesViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
|||
|
||||
@property
|
||||
def url_expected(self):
|
||||
return "/gestion/bda/places/{}".format(self.tirage.id)
|
||||
return "/bda/places/{}".format(self.tirage.id)
|
||||
|
||||
|
||||
class EtatPlacesViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||
|
@ -166,7 +166,7 @@ class EtatPlacesViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
|||
|
||||
@property
|
||||
def url_expected(self):
|
||||
return "/gestion/bda/etat-places/{}".format(self.tirage.id)
|
||||
return "/bda/etat-places/{}".format(self.tirage.id)
|
||||
|
||||
|
||||
class TirageViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||
|
@ -185,7 +185,7 @@ class TirageViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
|||
|
||||
@property
|
||||
def url_expected(self):
|
||||
return "/gestion/bda/tirage/{}".format(self.tirage.id)
|
||||
return "/bda/tirage/{}".format(self.tirage.id)
|
||||
|
||||
def test_perform_tirage_disabled(self):
|
||||
# Cannot be performed if disabled
|
||||
|
@ -225,7 +225,7 @@ class SpectacleListViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
|||
|
||||
@property
|
||||
def url_expected(self):
|
||||
return "/gestion/bda/spectacles/{}".format(self.tirage.id)
|
||||
return "/bda/spectacles/{}".format(self.tirage.id)
|
||||
|
||||
|
||||
class SpectacleViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||
|
@ -242,7 +242,7 @@ class SpectacleViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
|||
|
||||
@property
|
||||
def url_expected(self):
|
||||
return "/gestion/bda/spectacles/{}/{}".format(self.tirage.id, self.show1.id)
|
||||
return "/bda/spectacles/{}/{}".format(self.tirage.id, self.show1.id)
|
||||
|
||||
|
||||
class UnpaidViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||
|
@ -259,7 +259,7 @@ class UnpaidViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
|||
|
||||
@property
|
||||
def url_expected(self):
|
||||
return "/gestion/bda/spectacles/unpaid/{}".format(self.tirage.id)
|
||||
return "/bda/spectacles/unpaid/{}".format(self.tirage.id)
|
||||
|
||||
|
||||
class SendRemindersViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||
|
@ -276,9 +276,10 @@ class SendRemindersViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
|||
|
||||
@property
|
||||
def url_expected(self):
|
||||
return "/gestion/bda/mails-rappel/{}".format(self.show1.id)
|
||||
return "/bda/mails-rappel/{}".format(self.show1.id)
|
||||
|
||||
def test_post(self):
|
||||
self.require_custommails()
|
||||
resp = self.client.post(self.url)
|
||||
self.assertEqual(200, resp.status_code)
|
||||
# TODO: check that emails are sent
|
||||
|
@ -291,7 +292,7 @@ class CatalogueViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
|||
bda_testdata = True
|
||||
|
||||
def test_api_list(self):
|
||||
url_list = "/gestion/bda/catalogue/list"
|
||||
url_list = "/bda/catalogue/list"
|
||||
resp = self.client.get(url_list)
|
||||
self.assertJSONEqual(
|
||||
resp.content.decode("utf-8"),
|
||||
|
@ -299,7 +300,7 @@ class CatalogueViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
|||
)
|
||||
|
||||
def test_api_details(self):
|
||||
url_details = "/gestion/bda/catalogue/details?id={}".format(self.tirage.id)
|
||||
url_details = "/bda/catalogue/details?id={}".format(self.tirage.id)
|
||||
resp = self.client.get(url_details)
|
||||
self.assertJSONEqual(
|
||||
resp.content.decode("utf-8"),
|
||||
|
@ -310,9 +311,7 @@ class CatalogueViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
|||
)
|
||||
|
||||
def test_api_descriptions(self):
|
||||
url_descriptions = "/gestion/bda/catalogue/descriptions?id={}".format(
|
||||
self.tirage.id
|
||||
)
|
||||
url_descriptions = "/bda/catalogue/descriptions?id={}".format(self.tirage.id)
|
||||
resp = self.client.get(url_descriptions)
|
||||
raw = resp.content.decode("utf-8")
|
||||
try:
|
||||
|
@ -356,9 +355,7 @@ class TestReventeManageTest(TestCase):
|
|||
|
||||
def test_can_get(self):
|
||||
client = Client()
|
||||
client.force_login(
|
||||
self.user, backend="django.contrib.auth.backends.ModelBackend"
|
||||
)
|
||||
client.force_login(self.user)
|
||||
r = client.get(self.url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
|
63
bda/views.py
63
bda/views.py
|
@ -4,17 +4,17 @@ import random
|
|||
import time
|
||||
from collections import defaultdict
|
||||
|
||||
from custommail.models import CustomMail
|
||||
from custommail.shortcuts import send_custom_mail, send_mass_custom_mail
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core import serializers
|
||||
from django.core.exceptions import NON_FIELD_ERRORS
|
||||
from django.core.mail import send_mail, send_mass_mail
|
||||
from django.db import transaction
|
||||
from django.db.models import Count, Prefetch
|
||||
from django.forms.models import inlineformset_factory
|
||||
from django.http import HttpResponseBadRequest, HttpResponseRedirect, JsonResponse
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.template import loader
|
||||
from django.template.defaultfilters import pluralize
|
||||
from django.urls import reverse
|
||||
from django.utils import formats, timezone
|
||||
|
@ -274,13 +274,13 @@ def do_tirage(tirage_elt, token):
|
|||
results = Algorithm(data["shows"], data["members"], choices)(token)
|
||||
|
||||
# On compte les places attribuées et les déçus
|
||||
for _, members, losers in results:
|
||||
for (_, members, losers) in results:
|
||||
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:
|
||||
for (show, members, _) in results:
|
||||
deficit = (show.slots - len(members)) * show.price
|
||||
data["total_sold"] += show.slots * show.price
|
||||
if deficit >= 0:
|
||||
|
@ -293,8 +293,8 @@ def do_tirage(tirage_elt, token):
|
|||
# so assign a single object for each Participant id
|
||||
members_uniq = {}
|
||||
members2 = {}
|
||||
for show, members, _ in results:
|
||||
for member, _, _, _ in members:
|
||||
for (show, members, _) in results:
|
||||
for (member, _, _, _) in members:
|
||||
if member.id not in members_uniq:
|
||||
members_uniq[member.id] = member
|
||||
members2[member] = []
|
||||
|
@ -385,6 +385,12 @@ def revente_manage(request, tirage_id):
|
|||
user=request.user, tirage=tirage
|
||||
)
|
||||
|
||||
# If the participant has just been created, the `paid` field is not
|
||||
# automatically added by our custom ObjectManager. Skip the check in this
|
||||
# scenario.
|
||||
if not created and not participant.paid:
|
||||
return render(request, "bda/revente/notpaid.html", {})
|
||||
|
||||
resellform = ResellForm(participant, prefix="resell")
|
||||
annulform = AnnulForm(participant, prefix="annul")
|
||||
soldform = SoldForm(participant, prefix="sold")
|
||||
|
@ -394,7 +400,7 @@ def revente_manage(request, tirage_id):
|
|||
if "resell" in request.POST:
|
||||
resellform = ResellForm(participant, request.POST, prefix="resell")
|
||||
if resellform.is_valid():
|
||||
mails = []
|
||||
datatuple = []
|
||||
attributions = resellform.cleaned_data["attributions"]
|
||||
with transaction.atomic():
|
||||
for attribution in attributions:
|
||||
|
@ -409,17 +415,16 @@ def revente_manage(request, tirage_id):
|
|||
"show": attribution.spectacle,
|
||||
"revente": revente,
|
||||
}
|
||||
mails.append(
|
||||
datatuple.append(
|
||||
(
|
||||
"BdA-Revente : {}".format(attribution.spectacle),
|
||||
loader.render_to_string(
|
||||
"bda/mails/revente-seller.txt", context=context
|
||||
),
|
||||
"bda-revente-new",
|
||||
context,
|
||||
settings.MAIL_DATA["revente"]["FROM"],
|
||||
[participant.user.email],
|
||||
)
|
||||
)
|
||||
send_mass_mail(mails)
|
||||
revente.save()
|
||||
send_mass_custom_mail(datatuple)
|
||||
# On annule une revente
|
||||
elif "annul" in request.POST:
|
||||
annulform = AnnulForm(participant, request.POST, prefix="annul")
|
||||
|
@ -638,16 +643,12 @@ def revente_buy(request, spectacle_id):
|
|||
"acheteur": request.user,
|
||||
"vendeur": revente.seller.user,
|
||||
}
|
||||
|
||||
send_mail(
|
||||
"BdA-Revente : {}".format(spectacle.title),
|
||||
loader.render_to_string(
|
||||
"bda/mails/revente-shotgun-seller.txt", context=context
|
||||
),
|
||||
request.user.email,
|
||||
send_custom_mail(
|
||||
"bda-buy-shotgun",
|
||||
"bda@ens.fr",
|
||||
[revente.seller.user.email],
|
||||
context=context,
|
||||
)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"bda/revente/mail-success.html",
|
||||
|
@ -700,7 +701,7 @@ def spectacle(request, tirage_id, spectacle_id):
|
|||
"username": participant.user.username,
|
||||
"email": participant.user.email,
|
||||
"given": int(attrib.given),
|
||||
"paid": attrib.paid,
|
||||
"paid": True,
|
||||
"nb_places": 1,
|
||||
}
|
||||
if participant.id in participants:
|
||||
|
@ -750,21 +751,19 @@ class UnpaidParticipants(BuroRequiredMixin, ListView):
|
|||
def send_rappel(request, spectacle_id):
|
||||
show = get_object_or_404(Spectacle, id=spectacle_id)
|
||||
# Mails d'exemples
|
||||
subject = show.title
|
||||
body_mail_1place = loader.render_to_string(
|
||||
"bda/mails/rappel.txt",
|
||||
context={"member": request.user, "show": show, "nb_attr": 1},
|
||||
custommail = CustomMail.objects.get(shortname="bda-rappel")
|
||||
exemple_mail_1place = custommail.render(
|
||||
{"member": request.user, "show": show, "nb_attr": 1}
|
||||
)
|
||||
body_mail_2places = loader.render_to_string(
|
||||
"bda/mails/rappel.txt",
|
||||
context={"member": request.user, "show": show, "nb_attr": 2},
|
||||
exemple_mail_2places = custommail.render(
|
||||
{"member": request.user, "show": show, "nb_attr": 2}
|
||||
)
|
||||
|
||||
# Contexte
|
||||
ctxt = {
|
||||
"show": show,
|
||||
"exemple_mail_1place": (subject, body_mail_1place),
|
||||
"exemple_mail_2places": (subject, body_mail_2places),
|
||||
"exemple_mail_1place": exemple_mail_1place,
|
||||
"exemple_mail_2places": exemple_mail_2places,
|
||||
"custommail": custommail,
|
||||
}
|
||||
# Envoi confirmé
|
||||
if request.method == "POST":
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from django import forms
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from bds.models import BDSProfile
|
||||
|
||||
|
@ -9,8 +8,6 @@ User = get_user_model()
|
|||
|
||||
|
||||
class UserForm(forms.ModelForm):
|
||||
is_buro = forms.BooleanField(label=_("Membre du Burô"), required=False)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ["email", "first_name", "last_name"]
|
||||
|
@ -36,6 +33,4 @@ class ProfileForm(forms.ModelForm):
|
|||
class Meta:
|
||||
model = BDSProfile
|
||||
exclude = ["user"]
|
||||
widgets = {
|
||||
"birthdate": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d")
|
||||
}
|
||||
widgets = {"birthdate": forms.DateInput(attrs={"type": "date"})}
|
||||
|
|
|
@ -8,6 +8,7 @@ import bds.models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||
|
|
|
@ -9,6 +9,7 @@ def create_bds_buro_group(apps, schema_editor):
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("bds", "0001_initial")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bds", "0002_bds_group"),
|
||||
]
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bds", "0003_staff_permission"),
|
||||
]
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bds", "0004_is_member_cotiz_type"),
|
||||
]
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bds", "0005_remove_bdsprofile_certificate_file"),
|
||||
]
|
||||
|
|
|
@ -3,7 +3,6 @@ from os.path import splitext
|
|||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from shared.utils import choices_length
|
||||
|
@ -94,16 +93,6 @@ class BDSProfile(models.Model):
|
|||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def expired_members(cls):
|
||||
now = timezone.now()
|
||||
qs = cls.objects.filter(is_member=True)
|
||||
if now.month > 1 and now.month < 7:
|
||||
return qs.filter(cotisation_period="SE1")
|
||||
elif now.month < 2 or now.month > 8:
|
||||
return qs.none()
|
||||
return qs
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Profil BDS")
|
||||
verbose_name_plural = _("Profils BDS")
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
{% extends "bds/base.html" %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="title">Liste des adhésions expirées</h1>
|
||||
|
||||
{% if object_list %}
|
||||
<div class="content">
|
||||
<ul>
|
||||
{% for p in object_list %}
|
||||
<li>{{ p.user.first_name }} {{ p.user.last_name }} ({{ p.user.username }}), {{ p.get_cotisation_period_display }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="buttons is-centered">
|
||||
<a class="button is-danger" href="{% url 'bds:members.reset' %}">Réinitialiser les adhésions expirées</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
|
@ -34,13 +34,6 @@
|
|||
<br>
|
||||
<br>
|
||||
|
||||
<a class=button href="{% url 'bds:export.members' %}">Télécharger la liste des membres (CSV)</a>
|
||||
|
||||
<a class=button href="{% url 'bds:members.expired' %}">Liste des adhésions expirées ({{ nb_expired }})</a>
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
Le site est encore en développement.
|
||||
<br>
|
||||
Suivez notre avancement sur
|
||||
|
|
|
@ -22,18 +22,6 @@ def login_url(next=None):
|
|||
return "{}?next={}".format(login_url, next)
|
||||
|
||||
|
||||
class TestHomeView(TestCase):
|
||||
@mock.patch("gestioncof.signals.messages")
|
||||
def test_get(self, mock_messages):
|
||||
user = User.objects.create_user(username="random_user")
|
||||
give_bds_buro_permissions(user)
|
||||
self.client.force_login(
|
||||
user, backend="django.contrib.auth.backends.ModelBackend"
|
||||
)
|
||||
resp = self.client.get(reverse("bds:home"))
|
||||
self.assertEquals(resp.status_code, 200)
|
||||
|
||||
|
||||
class TestRegistrationView(TestCase):
|
||||
@mock.patch("gestioncof.signals.messages")
|
||||
def test_get_autocomplete(self, mock_messages):
|
||||
|
@ -46,7 +34,7 @@ class TestRegistrationView(TestCase):
|
|||
self.assertRedirects(resp, login_url(next=url))
|
||||
|
||||
# Logged-in but unprivileged GET
|
||||
client.force_login(user, backend="django.contrib.auth.backends.ModelBackend")
|
||||
client.force_login(user)
|
||||
resp = client.get(url)
|
||||
self.assertEquals(resp.status_code, 403)
|
||||
|
||||
|
@ -66,7 +54,7 @@ class TestRegistrationView(TestCase):
|
|||
self.assertRedirects(resp, login_url(next=url))
|
||||
|
||||
# Logged-in but unprivileged GET
|
||||
client.force_login(user, backend="django.contrib.auth.backends.ModelBackend")
|
||||
client.force_login(user)
|
||||
resp = client.get(url)
|
||||
self.assertEquals(resp.status_code, 403)
|
||||
|
||||
|
|
|
@ -14,11 +14,4 @@ urlpatterns = [
|
|||
name="user.create.fromclipper",
|
||||
),
|
||||
path("user/delete/<int:pk>", views.UserDeleteView.as_view(), name="user.delete"),
|
||||
path("members", views.export_members, name="export.members"),
|
||||
path(
|
||||
"members/expired",
|
||||
views.ResetMembershipListView.as_view(),
|
||||
name="members.expired",
|
||||
),
|
||||
path("members/reset", views.ResetMembershipView.as_view(), name="members.reset"),
|
||||
]
|
||||
|
|
56
bds/views.py
56
bds/views.py
|
@ -1,14 +1,9 @@
|
|||
import csv
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import DeleteView, ListView, RedirectView, TemplateView
|
||||
from django.views.generic import DeleteView, TemplateView
|
||||
|
||||
from bds.autocomplete import bds_search
|
||||
from bds.forms import ProfileForm, UserForm, UserFromClipperForm, UserFromScratchForm
|
||||
|
@ -30,7 +25,6 @@ class Home(StaffRequiredMixin, TemplateView):
|
|||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["member_count"] = BDSProfile.objects.filter(is_member=True).count()
|
||||
context["nb_expired"] = BDSProfile.expired_members().count()
|
||||
return context
|
||||
|
||||
|
||||
|
@ -42,9 +36,6 @@ class UserUpdateView(StaffRequiredMixin, MultipleFormView):
|
|||
"profile": ProfileForm,
|
||||
}
|
||||
|
||||
def get_user_initial(self):
|
||||
return {"is_buro": self.get_user_instance().has_perm("bds.is_team")}
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
self.user = get_object_or_404(User, pk=self.kwargs["pk"])
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
@ -61,11 +52,6 @@ class UserUpdateView(StaffRequiredMixin, MultipleFormView):
|
|||
def form_valid(self, forms):
|
||||
user = forms["user"].save()
|
||||
profile = forms["profile"].save(commit=False)
|
||||
perm = Permission.objects.get(content_type__app_label="bds", codename="is_team")
|
||||
if forms["user"].cleaned_data["is_buro"]:
|
||||
user.user_permissions.add(perm)
|
||||
else:
|
||||
user.user_permissions.remove(perm)
|
||||
profile.user = user
|
||||
profile.save()
|
||||
messages.success(self.request, _("Profil mis à jour avec succès !"))
|
||||
|
@ -142,43 +128,3 @@ class UserDeleteView(StaffRequiredMixin, DeleteView):
|
|||
messages.success(request, self.success_message)
|
||||
|
||||
return super().delete(request, *args, **kwargs)
|
||||
|
||||
|
||||
class ResetMembershipListView(StaffRequiredMixin, ListView):
|
||||
model = BDSProfile
|
||||
template_name = "bds/expired_members.html"
|
||||
|
||||
def get_queryset(self):
|
||||
return BDSProfile.expired_members()
|
||||
|
||||
|
||||
class ResetMembershipView(StaffRequiredMixin, RedirectView):
|
||||
url = reverse_lazy("bds:members.expired")
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
qs = BDSProfile.expired_members()
|
||||
nb = qs.count()
|
||||
|
||||
qs.update(cotisation_period="NO", is_member=False, mails_bds=False)
|
||||
|
||||
messages.success(request, f"{nb} adhésions réinitialisées")
|
||||
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
|
||||
@permission_required("bds.is_team")
|
||||
def export_members(request):
|
||||
response = HttpResponse(content_type="text/csv")
|
||||
response["Content-Disposition"] = "attachment; filename=membres_bds.csv"
|
||||
|
||||
writer = csv.writer(response)
|
||||
for profile in BDSProfile.objects.filter(is_member=True).all():
|
||||
user = profile.user
|
||||
bits = [
|
||||
user.username,
|
||||
user.get_full_name(),
|
||||
user.email,
|
||||
]
|
||||
writer.writerow([str(bit) for bit in bits])
|
||||
|
||||
return response
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||
|
|
|
@ -3,6 +3,6 @@ import os
|
|||
from channels.asgi import get_channel_layer
|
||||
|
||||
if "DJANGO_SETTINGS_MODULE" not in os.environ:
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cof.settings")
|
||||
|
||||
channel_layer = get_channel_layer()
|
|
@ -1,9 +1,7 @@
|
|||
"""
|
||||
Settings de production de GestioBDS.
|
||||
|
||||
Surcharge les settings définis dans common.py
|
||||
Django development settings for the cof project.
|
||||
The settings that are not listed here are imported from .common
|
||||
"""
|
||||
|
||||
from .common import * # NOQA
|
||||
from .common import INSTALLED_APPS
|
||||
|
|
@ -1,13 +1,8 @@
|
|||
"""
|
||||
Settings de production de GestioCOF.
|
||||
|
||||
Surcharge les settings définis dans common.py
|
||||
Django development settings for the cof project.
|
||||
The settings that are not listed here are imported from .common
|
||||
"""
|
||||
|
||||
import os
|
||||
from datetime import timedelta
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from .common import * # NOQA
|
||||
from .common import (
|
||||
|
@ -28,8 +23,8 @@ REDIS_DB = import_secret("REDIS_DB")
|
|||
REDIS_HOST = import_secret("REDIS_HOST")
|
||||
REDIS_PORT = import_secret("REDIS_PORT")
|
||||
|
||||
HCAPTCHA_SITEKEY = import_secret("HCAPTCHA_SITEKEY")
|
||||
HCAPTCHA_SECRET = import_secret("HCAPTCHA_SECRET")
|
||||
RECAPTCHA_PUBLIC_KEY = import_secret("RECAPTCHA_PUBLIC_KEY")
|
||||
RECAPTCHA_PRIVATE_KEY = import_secret("RECAPTCHA_PRIVATE_KEY")
|
||||
KFETOPEN_TOKEN = import_secret("KFETOPEN_TOKEN")
|
||||
|
||||
# ---
|
||||
|
@ -52,10 +47,11 @@ INSTALLED_APPS = (
|
|||
+ [
|
||||
"bda",
|
||||
"petitscours",
|
||||
"hcaptcha",
|
||||
"captcha",
|
||||
"kfet",
|
||||
"kfet.open",
|
||||
"channels",
|
||||
"custommail",
|
||||
"djconfig",
|
||||
"wagtail.contrib.forms",
|
||||
"wagtail.contrib.redirects",
|
||||
|
@ -113,17 +109,11 @@ CORS_ORIGIN_WHITELIST = ("bda.ens.fr", "www.bda.ens.fr" "cof.ens.fr", "www.cof.e
|
|||
# Auth-related stuff
|
||||
# ---
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
[
|
||||
# Must be in first
|
||||
"kfet.auth.backends.BlockFrozenAccountBackend"
|
||||
]
|
||||
+ AUTHENTICATION_BACKENDS
|
||||
+ [
|
||||
AUTHENTICATION_BACKENDS += [
|
||||
"gestioncof.shared.COFCASBackend",
|
||||
"kfet.auth.backends.GenericBackend",
|
||||
]
|
||||
)
|
||||
|
||||
LOGIN_URL = "cof-login"
|
||||
LOGIN_REDIRECT_URL = "home"
|
||||
|
||||
|
@ -160,7 +150,7 @@ CHANNEL_LAYERS = {
|
|||
)
|
||||
]
|
||||
},
|
||||
"ROUTING": "gestioasso.routing.routing",
|
||||
"ROUTING": "cof.routing.routing",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,29 +196,8 @@ MAIL_DATA = {
|
|||
"REPLYTO": "cof@ens.fr",
|
||||
},
|
||||
"rappels": {"FROM": "Le BdA <bda@ens.fr>", "REPLYTO": "Le BdA <bda@ens.fr>"},
|
||||
"kfet": {
|
||||
"FROM": "La K-Fêt <chefs-k-fet@ens.fr>",
|
||||
"REPLYTO": "La K-Fêt <chefs-k-fet@ens.fr>",
|
||||
},
|
||||
"revente": {
|
||||
"FROM": "BdA-Revente <bda-revente@ens.fr>",
|
||||
"REPLYTO": "BdA-Revente <bda-revente@ens.fr>",
|
||||
},
|
||||
}
|
||||
|
||||
# ---
|
||||
# kfet history limits
|
||||
# ---
|
||||
|
||||
# L'historique n'est accesible que d'aujourd'hui
|
||||
# à aujourd'hui - KFET_HISTORY_DATE_LIMIT
|
||||
KFET_HISTORY_DATE_LIMIT = timedelta(days=7)
|
||||
|
||||
# Limite plus longue pour les chefs/trez
|
||||
# (qui ont la permission kfet.access_old_history)
|
||||
KFET_HISTORY_LONG_DATE_LIMIT = timedelta(days=30)
|
||||
|
||||
# These accounts don't represent actual people and can be freely accessed
|
||||
# Identification based on trigrammes
|
||||
KFET_HISTORY_NO_DATE_LIMIT_TRIGRAMMES = ["LIQ", "#13"]
|
||||
KFET_HISTORY_NO_DATE_LIMIT = timezone.datetime(1794, 10, 30) # AKA the distant past
|
|
@ -1,5 +1,8 @@
|
|||
"""
|
||||
Settings par défaut et settings communs à GestioCOF et GestioBDS.
|
||||
Django common settings for cof project.
|
||||
|
||||
Everything which is supposed to be identical between the production server and
|
||||
the local development server should be here.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
@ -62,7 +65,7 @@ INSTALLED_APPS = [
|
|||
"django.contrib.messages",
|
||||
"django.contrib.admin",
|
||||
"django.contrib.admindocs",
|
||||
"gestioasso.apps.IgnoreSrcStaticFilesConfig",
|
||||
"cof.apps.IgnoreSrcStaticFilesConfig",
|
||||
"django_cas_ng",
|
||||
"bootstrapform",
|
||||
"widget_tweaks",
|
||||
|
@ -79,7 +82,7 @@ MIDDLEWARE = [
|
|||
"django.middleware.locale.LocaleMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "gestioasso.urls"
|
||||
ROOT_URLCONF = "cof.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
|
@ -123,7 +126,7 @@ USE_I18N = True
|
|||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
LANGUAGES = (("fr", "Français"), ("en", "English"))
|
||||
FORMAT_MODULE_PATH = "gestioasso.locale"
|
||||
FORMAT_MODULE_PATH = "cof.locale"
|
||||
|
||||
|
||||
# ---
|
|
@ -1,9 +1,4 @@
|
|||
"""
|
||||
Settings utilisés lors d'un développement en local (dans un virtualenv).
|
||||
Active toutes les applications (de GestioCOF et de GestioBDS).
|
||||
|
||||
Surcharge les settings définis dans common.py
|
||||
"""
|
||||
"""Django local development settings."""
|
||||
import os
|
||||
|
||||
from . import bds_prod
|
||||
|
@ -48,7 +43,7 @@ CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"
|
|||
CHANNEL_LAYERS = {
|
||||
"default": {
|
||||
"BACKEND": "asgiref.inmemory.ChannelLayer",
|
||||
"ROUTING": "gestioasso.routing.routing",
|
||||
"ROUTING": "cof.routing.routing",
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,3 @@
|
|||
"""
|
||||
Secrets à re-définir en production.
|
||||
"""
|
||||
|
||||
SECRET_KEY = "q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah"
|
||||
ADMINS = None
|
||||
SERVER_EMAIL = "root@vagrant"
|
||||
|
@ -16,8 +12,8 @@ REDIS_PORT = 6379
|
|||
REDIS_DB = 0
|
||||
REDIS_HOST = "127.0.0.1"
|
||||
|
||||
HCAPTCHA_SITEKEY = "10000000-ffff-ffff-ffff-000000000001"
|
||||
HCAPTCHA_SECRET = "0x0000000000000000000000000000000000000000"
|
||||
RECAPTCHA_PUBLIC_KEY = "DUMMY"
|
||||
RECAPTCHA_PRIVATE_KEY = "DUMMY"
|
||||
|
||||
EMAIL_HOST = None
|
||||
|
166
cof/urls.py
Normal file
166
cof/urls.py
Normal file
|
@ -0,0 +1,166 @@
|
|||
"""
|
||||
Fichier principal de configuration des urls du projet GestioCOF
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth import views as django_auth_views
|
||||
from django.urls import include, path
|
||||
from django.views.generic.base import TemplateView
|
||||
from django_cas_ng import views as django_cas_views
|
||||
|
||||
admin.autodiscover()
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/doc/", include("django.contrib.admindocs.urls")),
|
||||
path("admin/", admin.site.urls),
|
||||
]
|
||||
|
||||
if "gestioncof" in settings.INSTALLED_APPS:
|
||||
from django_js_reverse.views import urls_js
|
||||
from wagtail.admin import urls as wagtailadmin_urls
|
||||
from wagtail.documents import urls as wagtaildocs_urls
|
||||
|
||||
from gestioncof import csv_views, views as gestioncof_views
|
||||
from gestioncof.urls import (
|
||||
calendar_patterns,
|
||||
clubs_patterns,
|
||||
events_patterns,
|
||||
export_patterns,
|
||||
surveys_patterns,
|
||||
)
|
||||
|
||||
# Also includes BdA, K-Fêt, etc.
|
||||
urlpatterns += [
|
||||
path("admin/logout/", gestioncof_views.logout),
|
||||
path(
|
||||
"admin/<slug:app_label>/<slug:model_name>/csv/",
|
||||
csv_views.admin_list_export,
|
||||
{"fields": ["username"]},
|
||||
),
|
||||
# Page d'accueil
|
||||
path("", gestioncof_views.HomeView.as_view(), name="home"),
|
||||
# Le BdA
|
||||
path("bda/", include("bda.urls")),
|
||||
# Les exports
|
||||
path("export/", include(export_patterns)),
|
||||
# Les petits cours
|
||||
path("petitcours/", include("petitscours.urls")),
|
||||
# Les sondages
|
||||
path("survey/", include(surveys_patterns)),
|
||||
# Evenements
|
||||
path("event/", include(events_patterns)),
|
||||
# Calendrier
|
||||
path("calendar/", include(calendar_patterns)),
|
||||
# Clubs
|
||||
path("clubs/", include(clubs_patterns)),
|
||||
# Authentification
|
||||
path(
|
||||
"cof/denied",
|
||||
TemplateView.as_view(template_name="cof-denied.html"),
|
||||
name="cof-denied",
|
||||
),
|
||||
path("cas/login", django_cas_views.LoginView.as_view(), name="cas_login_view"),
|
||||
path("cas/logout", django_cas_views.LogoutView.as_view()),
|
||||
path(
|
||||
"outsider/login",
|
||||
gestioncof_views.LoginExtView.as_view(),
|
||||
name="ext_login_view",
|
||||
),
|
||||
path(
|
||||
"outsider/logout",
|
||||
django_auth_views.LogoutView.as_view(),
|
||||
{"next_page": "home"},
|
||||
),
|
||||
path("login", gestioncof_views.login, name="cof-login"),
|
||||
path("logout", gestioncof_views.logout, name="cof-logout"),
|
||||
# Infos persos
|
||||
path("profile", gestioncof_views.profile, name="profile"),
|
||||
path(
|
||||
"outsider/password-change",
|
||||
django_auth_views.PasswordChangeView.as_view(),
|
||||
name="password_change",
|
||||
),
|
||||
path(
|
||||
"outsider/password-change-done",
|
||||
django_auth_views.PasswordChangeDoneView.as_view(),
|
||||
name="password_change_done",
|
||||
),
|
||||
# Inscription d'un nouveau membre
|
||||
path("registration", gestioncof_views.registration, name="registration"),
|
||||
path(
|
||||
"registration/clipper/<slug:login_clipper>/<fullname>",
|
||||
gestioncof_views.registration_form2,
|
||||
name="clipper-registration",
|
||||
),
|
||||
path(
|
||||
"registration/user/<username>",
|
||||
gestioncof_views.registration_form2,
|
||||
name="user-registration",
|
||||
),
|
||||
path(
|
||||
"registration/empty",
|
||||
gestioncof_views.registration_form2,
|
||||
name="empty-registration",
|
||||
),
|
||||
# Autocompletion
|
||||
path(
|
||||
"autocomplete/registration",
|
||||
gestioncof_views.RegistrationAutocompleteView.as_view(),
|
||||
name="cof.registration.autocomplete",
|
||||
),
|
||||
path(
|
||||
"user/autocomplete",
|
||||
gestioncof_views.UserAutocompleteView.as_view(),
|
||||
name="cof-user-autocomplete",
|
||||
),
|
||||
# Liens utiles du COF et du BdA
|
||||
path("utile_cof", gestioncof_views.utile_cof, name="utile_cof"),
|
||||
path("utile_bda", gestioncof_views.utile_bda, name="utile_bda"),
|
||||
path("utile_bda/bda_diff", gestioncof_views.liste_bdadiff, name="ml_diffbda"),
|
||||
path("utile_cof/diff_cof", gestioncof_views.liste_diffcof, name="ml_diffcof"),
|
||||
path(
|
||||
"utile_bda/bda_revente",
|
||||
gestioncof_views.liste_bdarevente,
|
||||
name="ml_bda_revente",
|
||||
),
|
||||
path("k-fet/", include("kfet.urls")),
|
||||
path("cms/", include(wagtailadmin_urls)),
|
||||
path("documents/", include(wagtaildocs_urls)),
|
||||
# djconfig
|
||||
path("config", gestioncof_views.ConfigUpdate.as_view(), name="config.edit"),
|
||||
# js-reverse
|
||||
path("jsreverse/", urls_js, name="js_reverse"),
|
||||
]
|
||||
|
||||
if "bds" in settings.INSTALLED_APPS:
|
||||
urlpatterns.append(path("bds/", include("bds.urls")))
|
||||
|
||||
if "events" in settings.INSTALLED_APPS:
|
||||
# The new event application is still in development
|
||||
# → for now it is namespaced below events_v2
|
||||
# → rename this when the old events system is out
|
||||
urlpatterns += [path("event_v2/", include("events.urls"))]
|
||||
|
||||
if "authens" in settings.INSTALLED_APPS:
|
||||
urlpatterns.append(path("authens/", include("authens.urls")))
|
||||
|
||||
if "debug_toolbar" in settings.INSTALLED_APPS:
|
||||
import debug_toolbar
|
||||
|
||||
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]
|
||||
|
||||
if settings.DEBUG:
|
||||
# Si on est en production, MEDIA_ROOT est servi par Apache.
|
||||
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
# Wagtail for uncatched
|
||||
if "wagtail.core" in settings.INSTALLED_APPS:
|
||||
from wagtail.core import urls as wagtail_urls
|
||||
|
||||
urlpatterns += i18n_patterns(
|
||||
path("", include(wagtail_urls)), prefix_default_language=False
|
||||
)
|
|
@ -2,5 +2,5 @@ import os
|
|||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings.bds_prod")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "cof.settings.bds_prod")
|
||||
application = get_wsgi_application()
|
|
@ -4,6 +4,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("events", "0001_event"),
|
||||
|
|
|
@ -6,6 +6,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("events", "0002_event_subscribers"),
|
||||
|
|
|
@ -5,6 +5,7 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("events", "0003_options_and_extra_fields"),
|
||||
|
|
|
@ -54,9 +54,7 @@ class CSVExportAccessTest(MessagePatch, TestCase):
|
|||
|
||||
def test_get(self):
|
||||
client = Client()
|
||||
client.force_login(
|
||||
self.staff, backend="django.contrib.auth.backends.ModelBackend"
|
||||
)
|
||||
client.force_login(self.staff)
|
||||
r = client.get(self.url)
|
||||
self.assertEqual(r.status_code, 200)
|
||||
|
||||
|
@ -68,7 +66,7 @@ class CSVExportAccessTest(MessagePatch, TestCase):
|
|||
|
||||
def test_unauthorised(self):
|
||||
client = Client()
|
||||
client.force_login(self.u1, backend="django.contrib.auth.backends.ModelBackend")
|
||||
client.force_login(self.u1)
|
||||
r = client.get(self.url)
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
|
@ -88,9 +86,7 @@ class CSVExportContentTest(MessagePatch, CSVResponseMixin, TestCase):
|
|||
)
|
||||
self.staff = make_staff_user("staff")
|
||||
self.client = Client()
|
||||
self.client.force_login(
|
||||
self.staff, backend="django.contrib.auth.backends.ModelBackend"
|
||||
)
|
||||
self.client.force_login(self.staff)
|
||||
|
||||
def test_simple_event(self):
|
||||
self.event.subscribers.set([self.u1, self.u2])
|
||||
|
|
|
@ -1,64 +0,0 @@
|
|||
"""
|
||||
Settings utilisés dans la VM Vagrant.
|
||||
Active toutes les applications (de GestioCOF et de GestioBDS).
|
||||
|
||||
Surcharge les settings définis dans common.py
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from . import bds_prod
|
||||
from .cof_prod import * # NOQA
|
||||
from .cof_prod import INSTALLED_APPS, MIDDLEWARE, TESTING
|
||||
|
||||
# ---
|
||||
# Merge COF and BDS configs
|
||||
# ---
|
||||
|
||||
for app in bds_prod.INSTALLED_APPS:
|
||||
if app not in INSTALLED_APPS:
|
||||
INSTALLED_APPS.append(app)
|
||||
|
||||
# ---
|
||||
# Tweaks for debug/local development
|
||||
# ---
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
DEBUG = True
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
if TESTING:
|
||||
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
||||
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = "/srv/gestiocof/static"
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = "/srv/gestiocof/media"
|
||||
|
||||
|
||||
# ---
|
||||
# Debug tool bar
|
||||
# ---
|
||||
|
||||
|
||||
def show_toolbar(request):
|
||||
"""
|
||||
On active la debug-toolbar en mode développement local sauf :
|
||||
- dans l'admin où ça ne sert pas à grand chose;
|
||||
- si la variable d'environnement DJANGO_NO_DDT est à 1 → ça permet de la désactiver
|
||||
sans modifier ce fichier en exécutant `export DJANGO_NO_DDT=1` dans le terminal
|
||||
qui lance `./manage.py runserver`.
|
||||
|
||||
Autre side effect de cette fonction : on ne fait pas la vérification de INTERNAL_IPS
|
||||
que ferait la debug-toolbar par défaut, ce qui la fait fonctionner aussi à
|
||||
l'intérieur de Vagrant (comportement non testé depuis un moment…)
|
||||
"""
|
||||
env_no_ddt = bool(os.environ.get("DJANGO_NO_DDT", None))
|
||||
return DEBUG and not env_no_ddt and not request.path.startswith("/admin/")
|
||||
|
||||
|
||||
if not TESTING:
|
||||
INSTALLED_APPS += ["debug_toolbar"]
|
||||
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE
|
||||
DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": show_toolbar}
|
|
@ -1,73 +0,0 @@
|
|||
"""
|
||||
Fichier principal de configuration des urls du projet GestioCOF
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
from django.views.generic.base import RedirectView
|
||||
|
||||
bds_is_alone = (
|
||||
"bds" in settings.INSTALLED_APPS and "gestioncof" not in settings.INSTALLED_APPS
|
||||
)
|
||||
|
||||
admin.autodiscover()
|
||||
urlpatterns = [
|
||||
# Website administration (independent from installed apps)
|
||||
path("admin/doc/", include("django.contrib.admindocs.urls")),
|
||||
path("admin/", admin.site.urls),
|
||||
]
|
||||
|
||||
if not bds_is_alone:
|
||||
# Redirection / → /gestion, only useful for developpers.
|
||||
urlpatterns.append(path("", RedirectView.as_view(url="gestion/")))
|
||||
|
||||
# App-specific urls
|
||||
|
||||
app_dict = {
|
||||
"bds": "" if bds_is_alone else "bds/",
|
||||
"kfet": "k-fet/",
|
||||
# Below = GestioCOF → goes under gestion/
|
||||
"gestioncof": "gestion/",
|
||||
"bda": "gestion/bda/",
|
||||
"petitscours": "gestion/petitcours/",
|
||||
"events": "gestion/event_v2/", # the events module is still experimental !
|
||||
"authens": "gestion/authens/",
|
||||
}
|
||||
for app_name, url_prefix in app_dict.items():
|
||||
if app_name in settings.INSTALLED_APPS:
|
||||
urlpatterns += [path(url_prefix, include("{}.urls".format(app_name)))]
|
||||
|
||||
|
||||
if "django_js_reverse" in settings.INSTALLED_APPS:
|
||||
from django_js_reverse.views import urls_js
|
||||
|
||||
urlpatterns += [
|
||||
path("jsreverse/", urls_js, name="js_reverse"),
|
||||
]
|
||||
|
||||
if "debug_toolbar" in settings.INSTALLED_APPS:
|
||||
import debug_toolbar
|
||||
|
||||
urlpatterns += [path("__debug__/", include(debug_toolbar.urls))]
|
||||
|
||||
if settings.DEBUG:
|
||||
# Si on est en production, MEDIA_ROOT est servi par Apache.
|
||||
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
|
||||
# Wagtail URLs (wagtail.core urls must be last, as catch-all)
|
||||
if "wagtail.core" in settings.INSTALLED_APPS:
|
||||
from wagtail.admin import urls as wagtailadmin_urls
|
||||
from wagtail.core import urls as wagtail_urls
|
||||
from wagtail.documents import urls as wagtaildocs_urls
|
||||
|
||||
urlpatterns += [
|
||||
path("cms/", include(wagtailadmin_urls)),
|
||||
path("documents/", include(wagtaildocs_urls)),
|
||||
]
|
||||
urlpatterns += i18n_patterns(
|
||||
path("", include(wagtail_urls)), prefix_default_language=False
|
||||
)
|
|
@ -100,6 +100,28 @@ class CofProfileInline(admin.StackedInline):
|
|||
inline_classes = ("collapse open",)
|
||||
|
||||
|
||||
class FkeyLookup(object):
|
||||
def __init__(self, fkeydecl, short_description=None, admin_order_field=None):
|
||||
self.fk, fkattrs = fkeydecl.split("__", 1)
|
||||
self.fkattrs = fkattrs.split("__")
|
||||
|
||||
self.short_description = short_description or self.fkattrs[-1]
|
||||
self.admin_order_field = admin_order_field or fkeydecl
|
||||
|
||||
def __get__(self, obj, klass):
|
||||
if obj is None:
|
||||
"""
|
||||
hack required to make Django validate (if obj is
|
||||
None, then we're a class, and classes are callable
|
||||
<wink>)
|
||||
"""
|
||||
return self
|
||||
item = getattr(obj, self.fk)
|
||||
for attr in self.fkattrs:
|
||||
item = getattr(item, attr)
|
||||
return item
|
||||
|
||||
|
||||
def ProfileInfo(field, short_description, boolean=False):
|
||||
def getter(self):
|
||||
try:
|
||||
|
@ -112,6 +134,7 @@ def ProfileInfo(field, short_description, boolean=False):
|
|||
return getter
|
||||
|
||||
|
||||
User.profile_login_clipper = FkeyLookup("profile__login_clipper", "Login clipper")
|
||||
User.profile_phone = ProfileInfo("phone", "Téléphone")
|
||||
User.profile_occupation = ProfileInfo("occupation", "Occupation")
|
||||
User.profile_departement = ProfileInfo("departement", "Departement")
|
||||
|
@ -140,6 +163,7 @@ class UserProfileAdmin(UserAdmin):
|
|||
is_cof.boolean = True
|
||||
|
||||
list_display = UserAdmin.list_display + (
|
||||
"profile_login_clipper",
|
||||
"profile_phone",
|
||||
"profile_occupation",
|
||||
"profile_mailing_cof",
|
||||
|
|
|
@ -13,6 +13,7 @@ import gestioncof.cms.models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
|
|
|
@ -4,6 +4,7 @@ from django.db import migrations, models
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("cofcms", "0001_initial")]
|
||||
|
||||
operations = [
|
||||
|
|
|
@ -6,6 +6,7 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("cofcms", "0002_auto_20190523_1521"),
|
||||
]
|
||||
|
|
|
@ -6,6 +6,7 @@ from django.db import migrations
|
|||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("cofcms", "0003_directory_entry_optional_links"),
|
||||
]
|
||||
|
|
|
@ -9,6 +9,12 @@ from wagtail.images.blocks import ImageChooserBlock
|
|||
from wagtail.images.edit_handlers import ImageChooserPanel
|
||||
|
||||
|
||||
# Récriture des URL publiques par surcharge de get_url
|
||||
class AbsoluteURLMixin:
|
||||
def get_url(self, request=None, current_site=None):
|
||||
return self.get_full_url(request=request)
|
||||
|
||||
|
||||
# Page pouvant afficher des actualités
|
||||
class COFActuIndexMixin:
|
||||
@property
|
||||
|
@ -18,7 +24,7 @@ class COFActuIndexMixin:
|
|||
|
||||
|
||||
# Racine du site du COF
|
||||
class COFRootPage(RoutablePageMixin, Page, COFActuIndexMixin):
|
||||
class COFRootPage(AbsoluteURLMixin, RoutablePageMixin, Page, COFActuIndexMixin):
|
||||
introduction = RichTextField("Introduction")
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
|
@ -62,7 +68,7 @@ class IFrameBlock(blocks.StructBlock):
|
|||
|
||||
|
||||
# Page lambda du site
|
||||
class COFPage(Page):
|
||||
class COFPage(AbsoluteURLMixin, Page):
|
||||
body = StreamField(
|
||||
[
|
||||
("heading", blocks.CharBlock(classname="full title")),
|
||||
|
@ -83,7 +89,7 @@ class COFPage(Page):
|
|||
|
||||
|
||||
# Actualités
|
||||
class COFActuIndexPage(Page, COFActuIndexMixin):
|
||||
class COFActuIndexPage(AbsoluteURLMixin, Page, COFActuIndexMixin):
|
||||
subpage_types = ["COFActuPage"]
|
||||
parent_page_types = ["COFRootPage"]
|
||||
|
||||
|
@ -108,7 +114,7 @@ class COFActuIndexPage(Page, COFActuIndexMixin):
|
|||
return context
|
||||
|
||||
|
||||
class COFActuPage(RoutablePageMixin, Page):
|
||||
class COFActuPage(AbsoluteURLMixin, RoutablePageMixin, Page):
|
||||
chapo = models.TextField("Description rapide", blank=True)
|
||||
body = RichTextField("Contenu")
|
||||
image = models.ForeignKey(
|
||||
|
@ -145,7 +151,7 @@ class COFActuPage(RoutablePageMixin, Page):
|
|||
|
||||
|
||||
# Annuaires (Clubs, partenaires, bonnes adresses)
|
||||
class COFDirectoryPage(Page):
|
||||
class COFDirectoryPage(AbsoluteURLMixin, Page):
|
||||
introduction = RichTextField("Introduction")
|
||||
alphabetique = models.BooleanField(
|
||||
"Tri par ordre alphabétique ?", default=True, blank=True
|
||||
|
@ -171,7 +177,7 @@ class COFDirectoryPage(Page):
|
|||
verbose_name_plural = "Annuaires"
|
||||
|
||||
|
||||
class COFDirectoryEntryPage(Page):
|
||||
class COFDirectoryEntryPage(AbsoluteURLMixin, Page):
|
||||
body = RichTextField("Description")
|
||||
links = StreamField(
|
||||
[
|
||||
|
|
|
@ -2,8 +2,8 @@ $(function() {
|
|||
$(".facteur").on("click", function(){
|
||||
var $this = $(this);
|
||||
var sticker = $this.attr('data-mref')
|
||||
.replace(/pont/g, '.')
|
||||
.replace(/arbre/g, '@')
|
||||
.replace('pont', '.')
|
||||
.replace('arbre', '@')
|
||||
.replace(/(.)-/g, '$1');
|
||||
|
||||
var boite = $("<a>", {href:"ma"+"il"+"to:"+sticker}).text(sticker);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
{% block content %}
|
||||
<section class="intro">
|
||||
<h1>{{ page.title }}</h1>
|
||||
<div>{{ page.introduction|richtext }}</div>
|
||||
<div>{{ page.introduction|safe }}</div>
|
||||
</section>
|
||||
|
||||
<section class="actulist">
|
||||
|
@ -36,7 +36,7 @@
|
|||
{% if actu.is_event %}
|
||||
<p><span class="actu-dates">{{ actu|dates|capfirst }}</span><br />{{ actu.chapo }}</p>
|
||||
{% else %}
|
||||
{{ actu.body|richtext|truncatewords_html:15 }}
|
||||
{{ actu.body|safe|truncatewords_html:15 }}
|
||||
{% endif %}
|
||||
<a href="{% pageurl actu %}">{% trans "Lire plus" %} ></a>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "cofcms/base.html" %}
|
||||
{% load wagtailcore_tags wagtailimages_tags cofcms_tags i18n %}
|
||||
{% load wagtailimages_tags cofcms_tags i18n %}
|
||||
|
||||
{% block content %}
|
||||
<section class="intro">
|
||||
|
@ -11,7 +11,7 @@
|
|||
<section class="pagecontent">
|
||||
<div class="image">{% image page.image width-700 %}</div>
|
||||
<article>
|
||||
{{ page.body|richtext }}
|
||||
{{ page.body|safe }}
|
||||
</article>
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "cofcms/base_aside.html" %}
|
||||
{% load wagtailcore_tags wagtailimages_tags cofcms_tags static i18n %}
|
||||
{% load wagtailimages_tags cofcms_tags static i18n %}
|
||||
|
||||
{% block extra_head %}
|
||||
{{ block.super }}
|
||||
|
@ -18,7 +18,7 @@
|
|||
{% block content %}
|
||||
<section class="intro">
|
||||
<h1>{{ page.title }}</h1>
|
||||
<div>{{ page.introduction|richtext }}</div>
|
||||
<div>{{ page.introduction|safe }}</div>
|
||||
</section>
|
||||
|
||||
<section class="directory">
|
||||
|
@ -28,7 +28,7 @@
|
|||
<div class="entry-image">{% image entry.image width-150 class="entry-img" %}</div>
|
||||
{% endif %}
|
||||
<h2>{{ entry.title }}</h2>
|
||||
<div class="desc">{{ entry.body|richtext }}</div>
|
||||
<div class="desc">{{ entry.body|safe }}</div>
|
||||
{% if entry.links %}
|
||||
<ul class="links">
|
||||
{% for block in entry.links %}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
{% extends "cofcms/base.html" %}
|
||||
{% load wagtailcore_tags wagtailimages_tags cofcms_tags %}
|
||||
{% load wagtailimages_tags cofcms_tags %}
|
||||
|
||||
{% block content %}
|
||||
<section class="intro">
|
||||
<h1>{{ page.title }}</h1>
|
||||
<div>{{ page.introduction|richtext }}</div>
|
||||
<div>{{ page.introduction|safe }}</div>
|
||||
</section>
|
||||
|
||||
<section class="pagecontent">
|
||||
|
@ -13,7 +13,7 @@
|
|||
<h2>{{ block.value }}</h2>
|
||||
{% elif block.block_type == "paragraph" %}
|
||||
<article class="paragraph">
|
||||
{{ block.value|richtext }}
|
||||
{{ block.value|safe }}
|
||||
</article>
|
||||
{% elif block.block_type == "image" %}
|
||||
<div class="image">
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
{% block content %}
|
||||
<section class="intro">
|
||||
<h1>{{ page.title }}</h1>
|
||||
<div>{{ page.introduction|richtext }}</div>
|
||||
<div>{{ page.introduction|safe }}</div>
|
||||
</section>
|
||||
|
||||
<section class="actuhome">
|
||||
|
@ -31,7 +31,7 @@
|
|||
{% if actu.is_event %}
|
||||
<span class="actu-minical">{% mini_calendar actu %}</span><span class="actu-dates">{{ actu|dates }}</span>
|
||||
{% else %}
|
||||
{{ actu.body|richtext|truncatewords_html:10 }}
|
||||
{{ actu.body|safe|truncatewords_html:10 }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<a href="{% pageurl actu %}" class="actu-overlay"></a>
|
||||
|
|
600
gestioncof/management/data/custommail.json
Normal file
600
gestioncof/management/data/custommail.json
Normal file
|
@ -0,0 +1,600 @@
|
|||
[
|
||||
{
|
||||
"model": "custommail.type",
|
||||
"fields": {
|
||||
"kind": "model",
|
||||
"content_type": [
|
||||
"auth",
|
||||
"user"
|
||||
],
|
||||
"inner1": null,
|
||||
"inner2": null
|
||||
},
|
||||
"pk": 1
|
||||
},
|
||||
{
|
||||
"model": "custommail.type",
|
||||
"fields": {
|
||||
"kind": "int",
|
||||
"content_type": null,
|
||||
"inner1": null,
|
||||
"inner2": null
|
||||
},
|
||||
"pk": 2
|
||||
},
|
||||
{
|
||||
"model": "custommail.type",
|
||||
"fields": {
|
||||
"kind": "model",
|
||||
"content_type": [
|
||||
"bda",
|
||||
"spectacle"
|
||||
],
|
||||
"inner1": null,
|
||||
"inner2": null
|
||||
},
|
||||
"pk": 3
|
||||
},
|
||||
{
|
||||
"model": "custommail.type",
|
||||
"fields": {
|
||||
"kind": "model",
|
||||
"content_type": [
|
||||
"bda",
|
||||
"spectaclerevente"
|
||||
],
|
||||
"inner1": null,
|
||||
"inner2": null
|
||||
},
|
||||
"pk": 4
|
||||
},
|
||||
{
|
||||
"model": "custommail.type",
|
||||
"fields": {
|
||||
"kind": "model",
|
||||
"content_type": [
|
||||
"sites",
|
||||
"site"
|
||||
],
|
||||
"inner1": null,
|
||||
"inner2": null
|
||||
},
|
||||
"pk": 5
|
||||
},
|
||||
{
|
||||
"model": "custommail.type",
|
||||
"fields": {
|
||||
"kind": "model",
|
||||
"content_type": [
|
||||
"gestioncof",
|
||||
"petitcoursdemande"
|
||||
],
|
||||
"inner1": null,
|
||||
"inner2": null
|
||||
},
|
||||
"pk": 6
|
||||
},
|
||||
{
|
||||
"model": "custommail.type",
|
||||
"fields": {
|
||||
"kind": "list",
|
||||
"content_type": null,
|
||||
"inner1": 12,
|
||||
"inner2": null
|
||||
},
|
||||
"pk": 7
|
||||
},
|
||||
{
|
||||
"model": "custommail.type",
|
||||
"fields": {
|
||||
"kind": "list",
|
||||
"content_type": null,
|
||||
"inner1": 1,
|
||||
"inner2": null
|
||||
},
|
||||
"pk": 8
|
||||
},
|
||||
{
|
||||
"model": "custommail.type",
|
||||
"fields": {
|
||||
"kind": "pair",
|
||||
"content_type": null,
|
||||
"inner1": 12,
|
||||
"inner2": 8
|
||||
},
|
||||
"pk": 9
|
||||
},
|
||||
{
|
||||
"model": "custommail.type",
|
||||
"fields": {
|
||||
"kind": "list",
|
||||
"content_type": null,
|
||||
"inner1": 9,
|
||||
"inner2": null
|
||||
},
|
||||
"pk": 10
|
||||
},
|
||||
{
|
||||
"model": "custommail.type",
|
||||
"fields": {
|
||||
"kind": "list",
|
||||
"content_type": null,
|
||||
"inner1": 3,
|
||||
"inner2": null
|
||||
},
|
||||
"pk": 11
|
||||
},
|
||||
{
|
||||
"model": "custommail.type",
|
||||
"fields": {
|
||||
"kind": "model",
|
||||
"content_type": [
|
||||
"gestioncof",
|
||||
"petitcourssubject"
|
||||
],
|
||||
"inner1": null,
|
||||
"inner2": null
|
||||
},
|
||||
"pk": 12
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"fields": {
|
||||
"shortname": "welcome",
|
||||
"subject": "Bienvenue au COF",
|
||||
"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.",
|
||||
"description": "Mail de bienvenue au COF envoy\u00e9 automatiquement \u00e0 l'inscription d'un nouveau membre"
|
||||
},
|
||||
"pk": 1
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"fields": {
|
||||
"shortname": "bda-rappel",
|
||||
"subject": "{{ show }}",
|
||||
"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",
|
||||
"description": "Mail de rappel pour les spectacles BdA"
|
||||
},
|
||||
"pk": 2
|
||||
},
|
||||
{
|
||||
"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 leur 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-confirm\" 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-revente-buy\" show.id %}, \u00e0 la disposition de tous.\r\n\r\nChaleureusement,\r\nLe BdA"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"fields": {
|
||||
"shortname": "bda-revente-winner",
|
||||
"subject": "BdA-Revente : {{ show.title }}",
|
||||
"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",
|
||||
"description": "Mail envoy\u00e9 au gagnant d'un tirage BdA-Revente"
|
||||
},
|
||||
"pk": 5
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"fields": {
|
||||
"shortname": "bda-revente-loser",
|
||||
"subject": "BdA-Revente : {{ show.title }}",
|
||||
"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",
|
||||
"description": "Notification envoy\u00e9e aux perdants d'un tirage de revente."
|
||||
},
|
||||
"pk": 6
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"fields": {
|
||||
"shortname": "bda-revente-seller",
|
||||
"subject": "BdA-Revente : {{ show.title }}",
|
||||
"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",
|
||||
"description": "Notification envoy\u00e9e au vendeur d'une place pour lui indiquer qu'elle vient d'\u00eatre attribu\u00e9e"
|
||||
},
|
||||
"pk": 7
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"fields": {
|
||||
"shortname": "bda-revente-new",
|
||||
"subject": "BdA-Revente : {{ show.title }}",
|
||||
"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",
|
||||
"description": "Notification signalant au vendeur d'une place que sa mise en vente a bien eu lieu et lui donnant quelques informations compl\u00e9mentaires."
|
||||
},
|
||||
"pk": 8
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"fields": {
|
||||
"shortname": "bda-buy-shotgun",
|
||||
"subject": "BdA-Revente : {{ show.title }}",
|
||||
"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 }})",
|
||||
"description": "Mail envoy\u00e9 au revendeur lors d'un achat au shotgun."
|
||||
},
|
||||
"pk": 9
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"fields": {
|
||||
"shortname": "petit-cours-mail-eleve",
|
||||
"subject": "Petits cours ENS par le COF",
|
||||
"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",
|
||||
"description": "Mail envoy\u00e9 aux personnes dont ont a donn\u00e9 les contacts \u00e0 des demandeurs de petits cours"
|
||||
},
|
||||
"pk": 10
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"fields": {
|
||||
"shortname": "petits-cours-mail-demandeur",
|
||||
"subject": "Cours particuliers ENS",
|
||||
"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",
|
||||
"description": "Mail envoy\u00e9 aux personnes qui demandent des petits cours lorsque leur demande est trait\u00e9e.\r\n\r\n(Ne pas toucher \u00e0 {{ extra|safe }})"
|
||||
},
|
||||
"pk": 11
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"fields": {
|
||||
"shortname": "bda-attributions",
|
||||
"subject": "R\u00e9sultats du tirage au sort",
|
||||
"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",
|
||||
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux gagnants d'une ou plusieurs places"
|
||||
},
|
||||
"pk": 12
|
||||
},
|
||||
{
|
||||
"model": "custommail.custommail",
|
||||
"fields": {
|
||||
"shortname": "bda-attributions-decus",
|
||||
"subject": "R\u00e9sultats du tirage au sort",
|
||||
"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",
|
||||
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux personnes n'ayant pas obtenu de place"
|
||||
},
|
||||
"pk": 13
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 1,
|
||||
"type": 1,
|
||||
"name": "member",
|
||||
"description": "Utilisateur de GestioCOF"
|
||||
},
|
||||
"pk": 1
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 2,
|
||||
"type": 1,
|
||||
"name": "member",
|
||||
"description": "Utilisateur ayant eu une place pour ce spectacle"
|
||||
},
|
||||
"pk": 2
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 2,
|
||||
"type": 3,
|
||||
"name": "show",
|
||||
"description": "Spectacle"
|
||||
},
|
||||
"pk": 3
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 2,
|
||||
"type": 2,
|
||||
"name": "nb_attr",
|
||||
"description": "Nombre de places obtenues"
|
||||
},
|
||||
"pk": 4
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 3,
|
||||
"type": 4,
|
||||
"name": "revente",
|
||||
"description": "Revente mentionn\u00e9e dans le mail"
|
||||
},
|
||||
"pk": 5
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 3,
|
||||
"type": 1,
|
||||
"name": "member",
|
||||
"description": "Personne int\u00e9ress\u00e9e par la place"
|
||||
},
|
||||
"pk": 6
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 3,
|
||||
"type": 3,
|
||||
"name": "show",
|
||||
"description": "Spectacle"
|
||||
},
|
||||
"pk": 7
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 3,
|
||||
"type": 5,
|
||||
"name": "site",
|
||||
"description": "Site web (gestioCOF)"
|
||||
},
|
||||
"pk": 8
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 4,
|
||||
"type": 5,
|
||||
"name": "site",
|
||||
"description": "Site web (gestioCOF)"
|
||||
},
|
||||
"pk": 9
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 4,
|
||||
"type": 3,
|
||||
"name": "show",
|
||||
"description": "Spectacle"
|
||||
},
|
||||
"pk": 10
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 4,
|
||||
"type": 1,
|
||||
"name": "member",
|
||||
"description": "Personne int\u00e9ress\u00e9e par la place"
|
||||
},
|
||||
"pk": 11
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 5,
|
||||
"type": 1,
|
||||
"name": "acheteur",
|
||||
"description": "Gagnant-e du tirage"
|
||||
},
|
||||
"pk": 12
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 5,
|
||||
"type": 1,
|
||||
"name": "vendeur",
|
||||
"description": "Personne qui vend une place"
|
||||
},
|
||||
"pk": 13
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 5,
|
||||
"type": 3,
|
||||
"name": "show",
|
||||
"description": "Spectacle"
|
||||
},
|
||||
"pk": 14
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 6,
|
||||
"type": 3,
|
||||
"name": "show",
|
||||
"description": "Spectacle"
|
||||
},
|
||||
"pk": 15
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 6,
|
||||
"type": 1,
|
||||
"name": "vendeur",
|
||||
"description": "Personne qui vend une place"
|
||||
},
|
||||
"pk": 16
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 6,
|
||||
"type": 1,
|
||||
"name": "acheteur",
|
||||
"description": "Personne inscrite au tirage qui n'a pas eu la place"
|
||||
},
|
||||
"pk": 17
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 7,
|
||||
"type": 1,
|
||||
"name": "acheteur",
|
||||
"description": "Gagnant-e du tirage"
|
||||
},
|
||||
"pk": 18
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 7,
|
||||
"type": 1,
|
||||
"name": "vendeur",
|
||||
"description": "Personne qui vend une place"
|
||||
},
|
||||
"pk": 19
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 7,
|
||||
"type": 3,
|
||||
"name": "show",
|
||||
"description": "Spectacle"
|
||||
},
|
||||
"pk": 20
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 8,
|
||||
"type": 3,
|
||||
"name": "show",
|
||||
"description": "Spectacle"
|
||||
},
|
||||
"pk": 21
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 8,
|
||||
"type": 1,
|
||||
"name": "vendeur",
|
||||
"description": "Personne qui vend la place"
|
||||
},
|
||||
"pk": 22
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 8,
|
||||
"type": 4,
|
||||
"name": "revente",
|
||||
"description": "Revente mentionn\u00e9e dans le mail"
|
||||
},
|
||||
"pk": 23
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 9,
|
||||
"type": 1,
|
||||
"name": "vendeur",
|
||||
"description": "Personne qui vend la place"
|
||||
},
|
||||
"pk": 24
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 9,
|
||||
"type": 3,
|
||||
"name": "show",
|
||||
"description": "Spectacle"
|
||||
},
|
||||
"pk": 25
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 9,
|
||||
"type": 1,
|
||||
"name": "acheteur",
|
||||
"description": "Personne qui prend la place au shotgun"
|
||||
},
|
||||
"pk": 26
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 10,
|
||||
"type": 6,
|
||||
"name": "demande",
|
||||
"description": "Demande de petit cours"
|
||||
},
|
||||
"pk": 27
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 10,
|
||||
"type": 7,
|
||||
"name": "matieres",
|
||||
"description": "Liste des mati\u00e8res concern\u00e9es par la demande"
|
||||
},
|
||||
"pk": 28
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 11,
|
||||
"type": 10,
|
||||
"name": "proposals",
|
||||
"description": "Liste associant une liste d'enseignants \u00e0 chaque mati\u00e8re"
|
||||
},
|
||||
"pk": 29
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 11,
|
||||
"type": 7,
|
||||
"name": "unsatisfied",
|
||||
"description": "Liste des mati\u00e8res pour lesquelles on n'a pas d'enseigant \u00e0 proposer"
|
||||
},
|
||||
"pk": 30
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 12,
|
||||
"type": 11,
|
||||
"name": "places",
|
||||
"description": "Places de spectacle du participant"
|
||||
},
|
||||
"pk": 31
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 12,
|
||||
"type": 1,
|
||||
"name": "member",
|
||||
"description": "Participant du tirage au sort"
|
||||
},
|
||||
"pk": 32
|
||||
},
|
||||
{
|
||||
"model": "custommail.variable",
|
||||
"fields": {
|
||||
"custommail": 13,
|
||||
"type": 1,
|
||||
"name": "member",
|
||||
"description": "Participant du tirage au sort"
|
||||
},
|
||||
"pk": 33
|
||||
}
|
||||
]
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue