Merge branch 'master' into Aufinal/bda_fixes
This commit is contained in:
commit
e1794a654f
|
@ -20,7 +20,6 @@ variables:
|
||||||
# psql password authentication
|
# psql password authentication
|
||||||
PGPASSWORD: $POSTGRES_PASSWORD
|
PGPASSWORD: $POSTGRES_PASSWORD
|
||||||
|
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
paths:
|
paths:
|
||||||
- vendor/python
|
- vendor/python
|
||||||
|
@ -31,12 +30,12 @@ before_script:
|
||||||
- mkdir -p vendor/{python,pip,apt}
|
- mkdir -p vendor/{python,pip,apt}
|
||||||
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client
|
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client
|
||||||
- sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' cof/settings/secret_example.py > cof/settings/secret.py
|
- sed -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
|
# 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"
|
- psql --username=$POSTGRES_USER --host=$DBHOST -c "DROP DATABASE IF EXISTS test_$POSTGRES_DB"
|
||||||
- pip install --cache-dir vendor/pip -t vendor/python -r requirements.txt
|
- pip install --upgrade --cache-dir vendor/pip -t vendor/python -r requirements.txt
|
||||||
- redis-cli config set requirepass $REDIS_PASSWD || true
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- python manage.py test -v3
|
- python manage.py test
|
||||||
|
|
|
@ -5,17 +5,34 @@ from django.db import migrations, models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
def forwards_func(apps, schema_editor):
|
|
||||||
|
def fill_tirage_fields(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Create a `Tirage` to fill new field `tirage` of `Participant`
|
||||||
|
and `Spectacle` already existing.
|
||||||
|
"""
|
||||||
|
Participant = apps.get_model("bda", "Participant")
|
||||||
|
Spectacle = apps.get_model("bda", "Spectacle")
|
||||||
Tirage = apps.get_model("bda", "Tirage")
|
Tirage = apps.get_model("bda", "Tirage")
|
||||||
db_alias = schema_editor.connection.alias
|
|
||||||
Tirage.objects.using(db_alias).bulk_create([
|
# These querysets only contains instances not linked to any `Tirage`.
|
||||||
Tirage(
|
participants = Participant.objects.filter(tirage=None)
|
||||||
id=1,
|
spectacles = Spectacle.objects.filter(tirage=None)
|
||||||
title="Tirage de test (migration)",
|
|
||||||
active=False,
|
if not participants.count() and not spectacles.count():
|
||||||
ouverture=timezone.now(),
|
# No need to create a "trash" tirage.
|
||||||
fermeture=timezone.now()),
|
return
|
||||||
])
|
|
||||||
|
tirage = Tirage.objects.create(
|
||||||
|
title="Tirage de test (migration)",
|
||||||
|
active=False,
|
||||||
|
ouverture=timezone.now(),
|
||||||
|
fermeture=timezone.now(),
|
||||||
|
)
|
||||||
|
|
||||||
|
participants.update(tirage=tirage)
|
||||||
|
spectacles.update(tirage=tirage)
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
@ -35,22 +52,33 @@ class Migration(migrations.Migration):
|
||||||
('active', models.BooleanField(default=True, verbose_name=b'Tirage actif')),
|
('active', models.BooleanField(default=True, verbose_name=b'Tirage actif')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='participant',
|
model_name='participant',
|
||||||
name='user',
|
name='user',
|
||||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
|
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
|
||||||
),
|
),
|
||||||
|
# Create fields `spectacle` for `Participant` and `Spectacle` models.
|
||||||
|
# These fields are not nullable, but we first create them as nullable
|
||||||
|
# to give a default value for existing instances of these models.
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='participant',
|
model_name='participant',
|
||||||
name='tirage',
|
name='tirage',
|
||||||
field=models.ForeignKey(default=1, to='bda.Tirage'),
|
field=models.ForeignKey(to='bda.Tirage', null=True),
|
||||||
preserve_default=False,
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name='spectacle',
|
||||||
name='tirage',
|
name='tirage',
|
||||||
field=models.ForeignKey(default=1, to='bda.Tirage'),
|
field=models.ForeignKey(to='bda.Tirage', null=True),
|
||||||
preserve_default=False,
|
),
|
||||||
|
migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='participant',
|
||||||
|
name='tirage',
|
||||||
|
field=models.ForeignKey(to='bda.Tirage'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='spectacle',
|
||||||
|
name='tirage',
|
||||||
|
field=models.ForeignKey(to='bda.Tirage'),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
<div>
|
<div>
|
||||||
<button class="btn btn-default" type="button" onclick="toggle('export-salle')">Afficher/Cacher liste noms</button>
|
<button class="btn btn-default" type="button" onclick="toggle('export-salle')">Afficher/Cacher liste noms</button>
|
||||||
<pre id="export-salle" style="display:none">{% spaceless %}
|
<pre id="export-salle" style="display:none">{% spaceless %}
|
||||||
{% for participant in participants %}{{participant.name}} : {{participant.nb_places}} places
|
{% for participant in participants %}{{ participant.name }} : {{ participant.nb_places }} place{{ participant.nb_places|pluralize }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endspaceless %}</pre>
|
{% endspaceless %}</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
0
cof/settings/__init__.py
Normal file
0
cof/settings/__init__.py
Normal file
|
@ -31,6 +31,7 @@ def import_secret(name):
|
||||||
SECRET_KEY = import_secret("SECRET_KEY")
|
SECRET_KEY = import_secret("SECRET_KEY")
|
||||||
ADMINS = import_secret("ADMINS")
|
ADMINS = import_secret("ADMINS")
|
||||||
SERVER_EMAIL = import_secret("SERVER_EMAIL")
|
SERVER_EMAIL = import_secret("SERVER_EMAIL")
|
||||||
|
EMAIL_HOST = import_secret("EMAIL_HOST")
|
||||||
|
|
||||||
DBNAME = import_secret("DBNAME")
|
DBNAME = import_secret("DBNAME")
|
||||||
DBUSER = import_secret("DBUSER")
|
DBUSER = import_secret("DBUSER")
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
SECRET_KEY = 'q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah'
|
SECRET_KEY = 'q()(zn4m63i%5cp4)f+ww4-28_w+ly3q9=6imw2ciu&_(5_4ah'
|
||||||
ADMINS = None
|
ADMINS = None
|
||||||
SERVER_EMAIL = "root@vagrant"
|
SERVER_EMAIL = "root@vagrant"
|
||||||
|
EMAIL_HOST = "localhost"
|
||||||
|
|
||||||
DBUSER = "cof_gestion"
|
DBUSER = "cof_gestion"
|
||||||
DBNAME = "cof_gestion"
|
DBNAME = "cof_gestion"
|
||||||
|
|
|
@ -58,7 +58,7 @@ def autocomplete(request):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fetching data from the SPI
|
# Fetching data from the SPI
|
||||||
if hasattr(settings, 'LDAP_SERVER_URL'):
|
if getattr(settings, 'LDAP_SERVER_URL', None):
|
||||||
# Fetching
|
# Fetching
|
||||||
ldap_query = '(&{:s})'.format(''.join(
|
ldap_query = '(&{:s})'.format(''.join(
|
||||||
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=bit)
|
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=bit)
|
||||||
|
|
|
@ -1,148 +1,161 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "model",
|
||||||
"content_type": [
|
"content_type": [
|
||||||
"auth",
|
"auth",
|
||||||
"user"
|
"user"
|
||||||
],
|
],
|
||||||
"inner1": null,
|
"inner1": null,
|
||||||
"kind": "model",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "int",
|
||||||
"content_type": null,
|
"content_type": null,
|
||||||
"inner1": null,
|
"inner1": null,
|
||||||
"kind": "int",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "model",
|
||||||
"content_type": [
|
"content_type": [
|
||||||
"bda",
|
"bda",
|
||||||
"spectacle"
|
"spectacle"
|
||||||
],
|
],
|
||||||
"inner1": null,
|
"inner1": null,
|
||||||
"kind": "model",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "model",
|
||||||
"content_type": [
|
"content_type": [
|
||||||
"bda",
|
"bda",
|
||||||
"spectaclerevente"
|
"spectaclerevente"
|
||||||
],
|
],
|
||||||
"inner1": null,
|
"inner1": null,
|
||||||
"kind": "model",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "model",
|
||||||
"content_type": [
|
"content_type": [
|
||||||
"sites",
|
"sites",
|
||||||
"site"
|
"site"
|
||||||
],
|
],
|
||||||
"inner1": null,
|
"inner1": null,
|
||||||
"kind": "model",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 5
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "model",
|
||||||
"content_type": [
|
"content_type": [
|
||||||
"gestioncof",
|
"gestioncof",
|
||||||
"petitcoursdemande"
|
"petitcoursdemande"
|
||||||
],
|
],
|
||||||
"inner1": null,
|
"inner1": null,
|
||||||
"kind": "model",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 6
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 7,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"content_type": null,
|
|
||||||
"inner1": null,
|
|
||||||
"kind": "list",
|
"kind": "list",
|
||||||
|
"content_type": null,
|
||||||
|
"inner1": 12,
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 7
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 8,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "list",
|
||||||
"content_type": null,
|
"content_type": null,
|
||||||
"inner1": 1,
|
"inner1": 1,
|
||||||
"kind": "list",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 9,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"content_type": null,
|
|
||||||
"inner1": null,
|
|
||||||
"kind": "pair",
|
"kind": "pair",
|
||||||
|
"content_type": null,
|
||||||
|
"inner1": 12,
|
||||||
"inner2": 8
|
"inner2": 8
|
||||||
}
|
},
|
||||||
|
"pk": 9
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 10,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "list",
|
||||||
"content_type": null,
|
"content_type": null,
|
||||||
"inner1": 9,
|
"inner1": 9,
|
||||||
"kind": "list",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.variabletype",
|
"model": "custommail.type",
|
||||||
"pk": 11,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"kind": "list",
|
||||||
"content_type": null,
|
"content_type": null,
|
||||||
"inner1": 3,
|
"inner1": 3,
|
||||||
"kind": "list",
|
|
||||||
"inner2": null
|
"inner2": null
|
||||||
}
|
},
|
||||||
|
"pk": 11
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "custommail.type",
|
||||||
|
"fields": {
|
||||||
|
"kind": "model",
|
||||||
|
"content_type": [
|
||||||
|
"gestioncof",
|
||||||
|
"petitcourssubject"
|
||||||
|
],
|
||||||
|
"inner1": null,
|
||||||
|
"inner2": null
|
||||||
|
},
|
||||||
|
"pk": 12
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "welcome",
|
"shortname": "welcome",
|
||||||
"subject": "Bienvenue au COF",
|
"subject": "Bienvenue au COF",
|
||||||
"description": "Mail de bienvenue au COF envoy\u00e9 automatiquement \u00e0 l'inscription d'un nouveau membre",
|
"body": "Bonjour {{ member.first_name }} et bienvenue au COF !\r\n\r\nTu trouveras plein de trucs cool sur le site du COF : https://www.cof.ens.fr/ et notre page Facebook : https://www.facebook.com/cof.ulm\r\nEt n'oublie pas d'aller d\u00e9couvrir GestioCOF, la plateforme de gestion du COF !\r\nSi tu as des questions, tu peux nous envoyer un mail \u00e0 cof@ens.fr (on aime le spam), ou passer nous voir au Bur\u00f4 pr\u00e8s de la Cour\u00f4 du lundi au vendredi de 12h \u00e0 14h et de 18h \u00e0 20h.\r\n\r\nRetrouvez les \u00e9v\u00e8nements de rentr\u00e9e pour les conscrit.e.s et les vieux/vieilles organis\u00e9s par le COF et ses clubs ici : http://www.cof.ens.fr/depot/Rentree.pdf \r\n\r\nAmicalement,\r\n\r\nTon COF qui t'aime.",
|
||||||
"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",
|
"model": "custommail.custommail",
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-rappel",
|
"shortname": "bda-rappel",
|
||||||
"subject": "{{ show }}",
|
"subject": "{{ show }}",
|
||||||
"description": "Mail de rappel pour les spectacles BdA",
|
"body": "Bonjour {{ member.first_name }},\r\n\r\nNous te rappellons que tu as eu la chance d'obtenir {{ nb_attr|pluralize:\"une place,deux places\" }}\r\npour {{ show.title }}, le {{ show.date }} au {{ show.location }}. N'oublie pas de t'y rendre !\r\n{% if nb_attr == 2 %}\r\nTu as obtenu deux places pour ce spectacle. Nous te rappelons que\r\nces places sont strictement r\u00e9serv\u00e9es aux personnes de moins de 28 ans.\r\n{% endif %}\r\n{% if show.listing %}Pour ce spectacle, tu as re\u00e7u des places sur\r\nlisting. Il te faudra donc te rendre 15 minutes en avance sur les lieux de la repr\u00e9sentation\r\npour retirer {{ nb_attr|pluralize:\"ta place,tes places\" }}.\r\n{% else %}Pour assister \u00e0 ce spectacle, tu dois pr\u00e9senter les billets qui ont\r\n\u00e9t\u00e9 distribu\u00e9s au bur\u00f4.\r\n{% endif %}\r\n\r\nSi tu ne peux plus assister \u00e0 cette repr\u00e9sentation, tu peux\r\nrevendre ta place via BdA-revente, accessible directement sur\r\nGestioCOF (lien \"revendre une place du premier tirage\" sur la page\r\nd'accueil https://www.cof.ens.fr/gestion/).\r\n\r\nEn te souhaitant un excellent spectacle,\r\n\r\nLe Bureau des Arts",
|
||||||
"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",
|
"model": "custommail.custommail",
|
||||||
|
@ -150,7 +163,7 @@
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-revente",
|
"shortname": "bda-revente",
|
||||||
"subject": "{{ show }}",
|
"subject": "{{ show }}",
|
||||||
"description": "Notification envoy\u00e9e \u00e0 toutes les personnes int\u00e9ress\u00e9es par un spectacle pour le signaler qu'une place vient d'\u00eatre mise en vente.",
|
"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"
|
"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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -166,422 +179,422 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommail",
|
"model": "custommail.custommail",
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-revente-winner",
|
"shortname": "bda-revente-winner",
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
"subject": "BdA-Revente : {{ show.title }}",
|
||||||
"description": "Mail envoy\u00e9 au gagnant d'un tirage BdA-Revente",
|
"body": "Bonjour {{ acheteur.first_name }},\r\n\r\nTu as \u00e9t\u00e9 tir\u00e9-e au sort pour racheter une place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) \u00e0 {{ show.price|floatformat:2 }}\u20ac.\r\nTu peux contacter le/la vendeur-se \u00e0 l'adresse {{ vendeur.email }}.\r\n\r\nChaleureusement,\r\nLe BdA",
|
||||||
"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",
|
"model": "custommail.custommail",
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-revente-loser",
|
"shortname": "bda-revente-loser",
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
"subject": "BdA-Revente : {{ show.title }}",
|
||||||
"description": "Notification envoy\u00e9e aux perdants d'un tirage de revente.",
|
"body": "Bonjour {{ acheteur.first_name }},\r\n\r\nTu t'\u00e9tais inscrit-e pour la revente de la place de {{ vendeur.get_full_name }}\r\npour {{ show.title }}.\r\nMalheureusement, une autre personne a \u00e9t\u00e9 tir\u00e9e au sort pour racheter la place.\r\nTu pourras certainement retenter ta chance pour une autre revente !\r\n\r\n\u00c0 tr\u00e8s bient\u00f4t,\r\nLe Bureau des Arts",
|
||||||
"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",
|
"model": "custommail.custommail",
|
||||||
"pk": 7,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-revente-seller",
|
"shortname": "bda-revente-seller",
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
"subject": "BdA-Revente : {{ show.title }}",
|
||||||
"description": "Notification envoy\u00e9e au vendeur d'une place pour lui indiquer qu'elle vient d'\u00eatre attribu\u00e9e",
|
"body": "Bonjour {{ vendeur.first_name }},\r\n\r\nLa personne tir\u00e9e au sort pour racheter ta place pour {{ show.title }} est {{ acheteur.get_full_name }}.\r\nTu peux le/la contacter \u00e0 l'adresse {{ acheteur.email }}, ou en r\u00e9pondant \u00e0 ce mail.\r\n\r\nChaleureusement,\r\nLe BdA",
|
||||||
"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",
|
"model": "custommail.custommail",
|
||||||
"pk": 8,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-revente-new",
|
"shortname": "bda-revente-new",
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
"subject": "BdA-Revente : {{ show.title }}",
|
||||||
"description": "Notification signalant au vendeur d'une place que sa mise en vente a bien eu lieu et lui donnant quelques informations compl\u00e9mentaires.",
|
"body": "Bonjour {{ vendeur.first_name }},\r\n\r\nTu t\u2019es bien inscrit-e pour la revente de {{ show.title }}.\r\n\r\n{% with revente.date_tirage as time %}\r\nLe tirage au sort entre tout-e-s les racheteuse-eur-s potentiel-le-s aura lieu\r\nle {{ time|date:\"DATE_FORMAT\" }} \u00e0 {{ time|time:\"TIME_FORMAT\" }} (dans {{time|timeuntil }}).\r\nSi personne ne s\u2019est inscrit pour racheter la place, celle-ci apparaitra parmi\r\nles \u00ab Places disponibles imm\u00e9diatement \u00e0 la revente \u00bb sur GestioCOF.\r\n{% endwith %}\r\n\r\nBonne revente !\r\nLe Bureau des Arts",
|
||||||
"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",
|
"model": "custommail.custommail",
|
||||||
"pk": 9,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-buy-shotgun",
|
"shortname": "bda-buy-shotgun",
|
||||||
"subject": "BdA-Revente : {{ show.title }}",
|
"subject": "BdA-Revente : {{ show.title }}",
|
||||||
"description": "Mail envoy\u00e9 au revendeur lors d'un achat au shotgun.",
|
"body": "Bonjour {{ vendeur.first_name }} !\r\n\r\nJe souhaiterais racheter ta place pour {{ show.title }} le {{ show.date }} ({{ show.location }}) \u00e0 {{ show.price|floatformat:2 }}\u20ac.\r\nContacte-moi si tu es toujours int\u00e9ress\u00e9\u00b7e !\r\n\r\n{{ acheteur.get_full_name }} ({{ acheteur.email }})",
|
||||||
"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",
|
"model": "custommail.custommail",
|
||||||
"pk": 10,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "petit-cours-mail-eleve",
|
"shortname": "petit-cours-mail-eleve",
|
||||||
"subject": "Petits cours ENS par le COF",
|
"subject": "Petits cours ENS par le COF",
|
||||||
"description": "Mail envoy\u00e9 aux personnes dont ont a donn\u00e9 les contacts \u00e0 des demandeurs de petits cours",
|
"body": "Salut,\r\n\r\nLe COF a re\u00e7u une demande de petit cours qui te correspond. Tu es en haut de la liste d'attente donc on a transmis tes coordonn\u00e9es, ainsi que celles de 2 autres qui correspondaient aussi (c'est la vie, on donne les num\u00e9ros 3 par 3 pour que ce soit plus souple). Voici quelques infos sur l'annonce en question :\r\n\r\n\u00a4 Nom : {{ demande.name }}\r\n\r\n\u00a4 P\u00e9riode : {{ demande.quand }}\r\n\r\n\u00a4 Fr\u00e9quence : {{ demande.freq }}\r\n\r\n\u00a4 Lieu (si pr\u00e9f\u00e9r\u00e9) : {{ demande.lieu }}\r\n\r\n\u00a4 Niveau : {{ demande.get_niveau_display }}\r\n\r\n\u00a4 Remarques diverses (d\u00e9sol\u00e9 pour les balises HTML) : {{ demande.remarques }}\r\n\r\n{% if matieres|length > 1 %}\u00a4 Mati\u00e8res :\r\n{% for matiere in matieres %} \u00a4 {{ matiere }}\r\n{% endfor %}{% else %}\u00a4 Mati\u00e8re : {% for matiere in matieres %}{{ matiere }}\r\n{% endfor %}{% endif %}\r\nVoil\u00e0, cette personne te contactera peut-\u00eatre sous peu, tu pourras voir les d\u00e9tails directement avec elle (prix, modalit\u00e9s, ...). Pour indication, 30 Euro/h semble \u00eatre la moyenne.\r\n\r\nSi tu te rends compte qu'en fait tu ne peux pas/plus donner de cours en ce moment, \u00e7a serait cool que tu d\u00e9coches la case \"Recevoir des propositions de petits cours\" sur GestioCOF. Ensuite d\u00e8s que tu voudras r\u00e9appara\u00eetre tu pourras recocher la case et tu seras \u00e0 nouveau sur la liste.\r\n\r\n\u00c0 bient\u00f4t,\r\n\r\n--\r\nLe COF, pour les petits cours",
|
||||||
"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",
|
"model": "custommail.custommail",
|
||||||
"pk": 11,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "petits-cours-mail-demandeur",
|
"shortname": "petits-cours-mail-demandeur",
|
||||||
"subject": "Cours particuliers ENS",
|
"subject": "Cours particuliers ENS",
|
||||||
"description": "Mail envoy\u00e9 aux personnent qui demandent des petits cours lorsque leur demande est trait\u00e9e.\r\n\r\n(Ne pas toucher \u00e0 {{ extra|safe }})",
|
"body": "Bonjour,\r\n\r\nJe vous contacte au sujet de votre annonce pass\u00e9e sur le site du COF pour rentrer en contact avec un \u00e9l\u00e8ve normalien pour des cours particuliers. Voici les coordonn\u00e9es d'\u00e9l\u00e8ves qui sont motiv\u00e9s par de tels cours et correspondent aux crit\u00e8res que vous nous aviez transmis :\r\n\r\n{% for matiere, proposed in proposals %}\u00a4 {{ matiere }} :{% for user in proposed %}\r\n \u00a4 {{ user.get_full_name }}{% if user.profile.phone %}, {{ user.profile.phone }}{% endif %}{% if user.email %}, {{ user.email }}{% endif %}{% endfor %}\r\n\r\n{% endfor %}{% if unsatisfied %}Nous n'avons cependant pas pu trouver d'\u00e9l\u00e8ve disponible pour des cours de {% for matiere in unsatisfied %}{% if forloop.counter0 > 0 %}, {% endif %}{{ matiere }}{% endfor %}.\r\n\r\n{% endif %}Si pour une raison ou une autre ces num\u00e9ros ne suffisaient pas, n'h\u00e9sitez pas \u00e0 r\u00e9pondre \u00e0 cet e-mail et je vous en ferai parvenir d'autres sans probl\u00e8me.\r\n{% if extra|length > 0 %}\r\n{{ extra|safe }}\r\n{% endif %}\r\nCordialement,\r\n\r\n--\r\nLe COF, BdE de l'ENS",
|
||||||
"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",
|
"model": "custommail.custommail",
|
||||||
"pk": 12,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-attributions",
|
"shortname": "bda-attributions",
|
||||||
"subject": "R\u00e9sultats du tirage au sort",
|
"subject": "R\u00e9sultats du tirage au sort",
|
||||||
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux gagnants d'une ou plusieurs places",
|
"body": "Cher-e {{ member.first_name }},\r\n\r\nTu t'es inscrit-e pour le tirage au sort du BdA. Tu as \u00e9t\u00e9 s\u00e9lectionn\u00e9-e\r\npour les spectacles suivants :\r\n{% for place in places %}\r\n- 1 place pour {{ place }}{% endfor %}\r\n\r\n*Paiement*\r\nL'int\u00e9gralit\u00e9 de ces places de spectacles est \u00e0 r\u00e9gler d\u00e8s maintenant et AVANT\r\nvendredi prochain, au bureau du COF pendant les heures de permanences (du lundi au vendredi\r\nentre 12h et 14h, et entre 18h et 20h). Des facilit\u00e9s de paiement sont bien\r\n\u00e9videmment possibles : nous pouvons ne pas encaisser le ch\u00e8que imm\u00e9diatement,\r\nou bien d\u00e9couper votre paiement en deux fois. Pour ceux qui ne pourraient pas\r\nvenir payer au bureau, merci de nous contacter par mail.\r\n\r\n*Mode de retrait des places*\r\nAu moment du paiement, certaines places vous seront remises directement,\r\nd'autres seront \u00e0 r\u00e9cup\u00e9rer au cours de l'ann\u00e9e, d'autres encore seront\r\nnominatives et \u00e0 retirer le soir m\u00eame dans les the\u00e2tres correspondants.\r\nPour chaque spectacle, vous recevrez un mail quelques jours avant la\r\nrepr\u00e9sentation vous indiquant le mode de retrait.\r\n\r\nNous vous rappelons que l'obtention de places du BdA vous engage \u00e0\r\nrespecter les r\u00e8gles de fonctionnement :\r\nhttp://www.cof.ens.fr/bda/?page_id=1370\r\nUn syst\u00e8me de revente des places via les mails BdA-revente disponible\r\ndirectement sur votre compte GestioCOF.\r\n\r\nEn vous souhaitant de tr\u00e8s beaux spectacles tout au long de l'ann\u00e9e,\r\n--\r\nLe Bureau des Arts",
|
||||||
"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",
|
"model": "custommail.custommail",
|
||||||
"pk": 13,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"shortname": "bda-attributions-decus",
|
"shortname": "bda-attributions-decus",
|
||||||
"subject": "R\u00e9sultats du tirage au sort",
|
"subject": "R\u00e9sultats du tirage au sort",
|
||||||
"description": "Mail annon\u00e7ant les r\u00e9sultats du tirage au sort du BdA aux personnes n'ayant pas obtenu de place",
|
"body": "Cher-e {{ member.first_name }},\r\n\r\nTu t'es inscrit-e pour le tirage au sort du BdA. Malheureusement, tu n'as\r\nobtenu aucune place.\r\n\r\nNous proposons cependant de nombreuses offres hors-tirage tout au long de\r\nl'ann\u00e9e, et nous t'invitons \u00e0 nous contacter si l'une d'entre elles\r\nt'int\u00e9resse !\r\n--\r\nLe Bureau des Arts",
|
||||||
"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.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 1,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "member",
|
|
||||||
"description": "Utilisateur de GestioCOF",
|
|
||||||
"custommail": 1,
|
"custommail": 1,
|
||||||
"type": 1
|
"type": 1,
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 2,
|
|
||||||
"fields": {
|
|
||||||
"name": "member",
|
"name": "member",
|
||||||
"description": "Utilisateur ayant eu une place pour ce spectacle",
|
"description": "Utilisateur de GestioCOF"
|
||||||
"custommail": 2,
|
},
|
||||||
"type": 1
|
"pk": 1
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 3,
|
|
||||||
"fields": {
|
"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",
|
"name": "show",
|
||||||
"description": "Spectacle",
|
"description": "Spectacle"
|
||||||
"custommail": 2,
|
},
|
||||||
"type": 3
|
"pk": 3
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 4,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 2,
|
||||||
|
"type": 2,
|
||||||
"name": "nb_attr",
|
"name": "nb_attr",
|
||||||
"description": "Nombre de places obtenues",
|
"description": "Nombre de places obtenues"
|
||||||
"custommail": 2,
|
},
|
||||||
"type": 2
|
"pk": 4
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 5,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 3,
|
||||||
|
"type": 4,
|
||||||
"name": "revente",
|
"name": "revente",
|
||||||
"description": "Revente mentionn\u00e9e dans le mail",
|
"description": "Revente mentionn\u00e9e dans le mail"
|
||||||
"custommail": 3,
|
},
|
||||||
"type": 4
|
"pk": 5
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 6,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 3,
|
||||||
|
"type": 1,
|
||||||
"name": "member",
|
"name": "member",
|
||||||
"description": "Personne int\u00e9ress\u00e9e par la place",
|
"description": "Personne int\u00e9ress\u00e9e par la place"
|
||||||
"custommail": 3,
|
},
|
||||||
"type": 1
|
"pk": 6
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 7,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 3,
|
||||||
|
"type": 3,
|
||||||
"name": "show",
|
"name": "show",
|
||||||
"description": "Spectacle",
|
"description": "Spectacle"
|
||||||
"custommail": 3,
|
},
|
||||||
"type": 3
|
"pk": 7
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 8,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "site",
|
|
||||||
"description": "Site web (gestioCOF)",
|
|
||||||
"custommail": 3,
|
"custommail": 3,
|
||||||
"type": 5
|
"type": 5,
|
||||||
}
|
"name": "site",
|
||||||
|
"description": "Site web (gestioCOF)"
|
||||||
|
},
|
||||||
|
"pk": 8
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 9,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "site",
|
|
||||||
"description": "Site web (gestioCOF)",
|
|
||||||
"custommail": 4,
|
"custommail": 4,
|
||||||
"type": 5
|
"type": 5,
|
||||||
}
|
"name": "site",
|
||||||
|
"description": "Site web (gestioCOF)"
|
||||||
|
},
|
||||||
|
"pk": 9
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 10,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 4,
|
||||||
|
"type": 3,
|
||||||
"name": "show",
|
"name": "show",
|
||||||
"description": "Spectacle",
|
"description": "Spectacle"
|
||||||
"custommail": 4,
|
},
|
||||||
"type": 3
|
"pk": 10
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 11,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 4,
|
||||||
|
"type": 1,
|
||||||
"name": "member",
|
"name": "member",
|
||||||
"description": "Personne int\u00e9ress\u00e9e par la place",
|
"description": "Personne int\u00e9ress\u00e9e par la place"
|
||||||
"custommail": 4,
|
},
|
||||||
"type": 1
|
"pk": 11
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 12,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "acheteur",
|
|
||||||
"description": "Gagnant-e du tirage",
|
|
||||||
"custommail": 5,
|
"custommail": 5,
|
||||||
"type": 1
|
"type": 1,
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 13,
|
|
||||||
"fields": {
|
|
||||||
"name": "vendeur",
|
|
||||||
"description": "Personne qui vend une place",
|
|
||||||
"custommail": 5,
|
|
||||||
"type": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 14,
|
|
||||||
"fields": {
|
|
||||||
"name": "show",
|
|
||||||
"description": "Spectacle",
|
|
||||||
"custommail": 5,
|
|
||||||
"type": 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 15,
|
|
||||||
"fields": {
|
|
||||||
"name": "show",
|
|
||||||
"description": "Spectacle",
|
|
||||||
"custommail": 6,
|
|
||||||
"type": 3
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 16,
|
|
||||||
"fields": {
|
|
||||||
"name": "vendeur",
|
|
||||||
"description": "Personne qui vend une place",
|
|
||||||
"custommail": 6,
|
|
||||||
"type": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 17,
|
|
||||||
"fields": {
|
|
||||||
"name": "acheteur",
|
"name": "acheteur",
|
||||||
"description": "Personne inscrite au tirage qui n'a pas eu la place",
|
"description": "Gagnant-e du tirage"
|
||||||
"custommail": 6,
|
},
|
||||||
"type": 1
|
"pk": 12
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 18,
|
|
||||||
"fields": {
|
|
||||||
"name": "acheteur",
|
|
||||||
"description": "Gagnant-e du tirage",
|
|
||||||
"custommail": 7,
|
|
||||||
"type": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "custommail.custommailvariable",
|
|
||||||
"pk": 19,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 5,
|
||||||
|
"type": 1,
|
||||||
"name": "vendeur",
|
"name": "vendeur",
|
||||||
"description": "Personne qui vend une place",
|
"description": "Personne qui vend une place"
|
||||||
"custommail": 7,
|
},
|
||||||
"type": 1
|
"pk": 13
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 20,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 5,
|
||||||
|
"type": 3,
|
||||||
"name": "show",
|
"name": "show",
|
||||||
"description": "Spectacle",
|
"description": "Spectacle"
|
||||||
"custommail": 7,
|
},
|
||||||
"type": 3
|
"pk": 14
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 21,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 6,
|
||||||
|
"type": 3,
|
||||||
"name": "show",
|
"name": "show",
|
||||||
"description": "Spectacle",
|
"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,
|
"custommail": 8,
|
||||||
"type": 3
|
"type": 3,
|
||||||
}
|
"name": "show",
|
||||||
|
"description": "Spectacle"
|
||||||
|
},
|
||||||
|
"pk": 21
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 22,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "vendeur",
|
|
||||||
"description": "Personne qui vend la place",
|
|
||||||
"custommail": 8,
|
"custommail": 8,
|
||||||
"type": 1
|
"type": 1,
|
||||||
}
|
"name": "vendeur",
|
||||||
|
"description": "Personne qui vend la place"
|
||||||
|
},
|
||||||
|
"pk": 22
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 23,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 8,
|
||||||
|
"type": 4,
|
||||||
"name": "revente",
|
"name": "revente",
|
||||||
"description": "Revente mentionn\u00e9e dans le mail",
|
"description": "Revente mentionn\u00e9e dans le mail"
|
||||||
"custommail": 8,
|
},
|
||||||
"type": 4
|
"pk": 23
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 24,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 9,
|
||||||
|
"type": 1,
|
||||||
"name": "vendeur",
|
"name": "vendeur",
|
||||||
"description": "Personne qui vend la place",
|
"description": "Personne qui vend la place"
|
||||||
"custommail": 9,
|
},
|
||||||
"type": 1
|
"pk": 24
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 25,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 9,
|
||||||
|
"type": 3,
|
||||||
"name": "show",
|
"name": "show",
|
||||||
"description": "Spectacle",
|
"description": "Spectacle"
|
||||||
"custommail": 9,
|
},
|
||||||
"type": 3
|
"pk": 25
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 26,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 9,
|
||||||
|
"type": 1,
|
||||||
"name": "acheteur",
|
"name": "acheteur",
|
||||||
"description": "Personne qui prend la place au shotgun",
|
"description": "Personne qui prend la place au shotgun"
|
||||||
"custommail": 9,
|
},
|
||||||
"type": 1
|
"pk": 26
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 27,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 10,
|
||||||
|
"type": 6,
|
||||||
"name": "demande",
|
"name": "demande",
|
||||||
"description": "Demande de petit cours",
|
"description": "Demande de petit cours"
|
||||||
"custommail": 10,
|
},
|
||||||
"type": 6
|
"pk": 27
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 28,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 10,
|
||||||
|
"type": 7,
|
||||||
"name": "matieres",
|
"name": "matieres",
|
||||||
"description": "Liste des mati\u00e8res concern\u00e9es par la demande",
|
"description": "Liste des mati\u00e8res concern\u00e9es par la demande"
|
||||||
"custommail": 10,
|
},
|
||||||
"type": 7
|
"pk": 28
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 29,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 11,
|
||||||
|
"type": 10,
|
||||||
"name": "proposals",
|
"name": "proposals",
|
||||||
"description": "Liste associant une liste d'enseignants \u00e0 chaque mati\u00e8re",
|
"description": "Liste associant une liste d'enseignants \u00e0 chaque mati\u00e8re"
|
||||||
"custommail": 11,
|
},
|
||||||
"type": 10
|
"pk": 29
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 30,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 11,
|
||||||
|
"type": 7,
|
||||||
"name": "unsatisfied",
|
"name": "unsatisfied",
|
||||||
"description": "Liste des mati\u00e8res pour lesquelles on n'a pas d'enseigant \u00e0 proposer",
|
"description": "Liste des mati\u00e8res pour lesquelles on n'a pas d'enseigant \u00e0 proposer"
|
||||||
"custommail": 11,
|
},
|
||||||
"type": 7
|
"pk": 30
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 31,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"custommail": 12,
|
||||||
|
"type": 11,
|
||||||
"name": "places",
|
"name": "places",
|
||||||
"description": "Places de spectacle du participant",
|
"description": "Places de spectacle du participant"
|
||||||
"custommail": 12,
|
},
|
||||||
"type": 11
|
"pk": 31
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 32,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "member",
|
|
||||||
"description": "Participant du tirage au sort",
|
|
||||||
"custommail": 12,
|
"custommail": 12,
|
||||||
"type": 1
|
"type": 1,
|
||||||
}
|
"name": "member",
|
||||||
|
"description": "Participant du tirage au sort"
|
||||||
|
},
|
||||||
|
"pk": 32
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "custommail.custommailvariable",
|
"model": "custommail.variable",
|
||||||
"pk": 33,
|
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "member",
|
|
||||||
"description": "Participant du tirage au sort",
|
|
||||||
"custommail": 13,
|
"custommail": 13,
|
||||||
"type": 1
|
"type": 1,
|
||||||
}
|
"name": "member",
|
||||||
|
"description": "Participant du tirage au sort"
|
||||||
|
},
|
||||||
|
"pk": 33
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -157,14 +157,16 @@ class PetitCoursAttributionCounter(models.Model):
|
||||||
compteurs de tout le monde.
|
compteurs de tout le monde.
|
||||||
"""
|
"""
|
||||||
counter, created = cls.objects.get_or_create(
|
counter, created = cls.objects.get_or_create(
|
||||||
user=user, matiere=matiere)
|
user=user,
|
||||||
|
matiere=matiere,
|
||||||
|
)
|
||||||
if created:
|
if created:
|
||||||
mincount = (
|
mincount = (
|
||||||
cls.objects.filter(matiere=matiere).exclude(user=user)
|
cls.objects.filter(matiere=matiere).exclude(user=user)
|
||||||
.aggregate(Min('count'))
|
.aggregate(Min('count'))
|
||||||
['count__min']
|
['count__min']
|
||||||
)
|
)
|
||||||
counter.count = mincount
|
counter.count = mincount or 0
|
||||||
counter.save()
|
counter.save()
|
||||||
return counter
|
return counter
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ class GroupForm(forms.ModelForm):
|
||||||
# other_groups = self.instance.permissions.difference(
|
# other_groups = self.instance.permissions.difference(
|
||||||
# self.fields['permissions'].queryset
|
# self.fields['permissions'].queryset
|
||||||
# )
|
# )
|
||||||
|
if self.instance.pk is None:
|
||||||
|
return kfet_perms
|
||||||
other_perms = self.instance.permissions.exclude(
|
other_perms = self.instance.permissions.exclude(
|
||||||
pk__in=[p.pk for p in self.fields['permissions'].queryset],
|
pk__in=[p.pk for p in self.fields['permissions'].queryset],
|
||||||
)
|
)
|
||||||
|
@ -36,6 +38,8 @@ class UserGroupForm(forms.ModelForm):
|
||||||
|
|
||||||
def clean_groups(self):
|
def clean_groups(self):
|
||||||
kfet_groups = self.cleaned_data.get('groups')
|
kfet_groups = self.cleaned_data.get('groups')
|
||||||
|
if self.instance.pk is None:
|
||||||
|
return kfet_groups
|
||||||
other_groups = self.instance.groups.exclude(name__icontains='K-Fêt')
|
other_groups = self.instance.groups.exclude(name__icontains='K-Fêt')
|
||||||
return list(kfet_groups) + list(other_groups)
|
return list(kfet_groups) + list(other_groups)
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ def account_create(request):
|
||||||
queries['users_notcof'].values_list('username', flat=True))
|
queries['users_notcof'].values_list('username', flat=True))
|
||||||
|
|
||||||
# Fetching data from the SPI
|
# Fetching data from the SPI
|
||||||
if hasattr(settings, 'LDAP_SERVER_URL'):
|
if getattr(settings, 'LDAP_SERVER_URL', None):
|
||||||
# Fetching
|
# Fetching
|
||||||
ldap_query = '(&{:s})'.format(''.join(
|
ldap_query = '(&{:s})'.format(''.join(
|
||||||
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=word)
|
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=word)
|
||||||
|
@ -106,6 +106,7 @@ def account_create(request):
|
||||||
return render(request, "kfet/account_create_autocomplete.html", data)
|
return render(request, "kfet/account_create_autocomplete.html", data)
|
||||||
|
|
||||||
|
|
||||||
|
@teamkfet_required
|
||||||
def account_search(request):
|
def account_search(request):
|
||||||
if "q" not in request.GET:
|
if "q" not in request.GET:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
|
|
@ -129,7 +129,6 @@ class UserRestrictTeamForm(UserForm):
|
||||||
fields = ['first_name', 'last_name', 'email']
|
fields = ['first_name', 'last_name', 'email']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AccountNegativeForm(forms.ModelForm):
|
class AccountNegativeForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AccountNegative
|
model = AccountNegative
|
||||||
|
|
39
kfet/migrations/0060_amend_supplier.py
Normal file
39
kfet/migrations/0060_amend_supplier.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('kfet', '0059_create_generic'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplier',
|
||||||
|
name='address',
|
||||||
|
field=models.TextField(verbose_name='adresse', blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplier',
|
||||||
|
name='articles',
|
||||||
|
field=models.ManyToManyField(verbose_name='articles vendus', through='kfet.SupplierArticle', related_name='suppliers', to='kfet.Article'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplier',
|
||||||
|
name='comment',
|
||||||
|
field=models.TextField(verbose_name='commentaire', blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplier',
|
||||||
|
name='email',
|
||||||
|
field=models.EmailField(max_length=254, verbose_name='adresse mail', blank=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='supplier',
|
||||||
|
name='phone',
|
||||||
|
field=models.CharField(max_length=20, verbose_name='téléphone', blank=True),
|
||||||
|
),
|
||||||
|
]
|
18
kfet/migrations/0061_add_perms_config.py
Normal file
18
kfet/migrations/0061_add_perms_config.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('kfet', '0060_amend_supplier'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='account',
|
||||||
|
options={'permissions': (('is_team', 'Is part of the team'), ('manage_perms', 'Gérer les permissions K-Fêt'), ('manage_addcosts', 'Gérer les majorations'), ('edit_balance_account', "Modifier la balance d'un compte"), ('change_account_password', "Modifier le mot de passe d'une personne de l'équipe"), ('special_add_account', 'Créer un compte avec une balance initiale'), ('can_force_close', 'Fermer manuellement la K-Fêt'), ('see_config', 'Voir la configuration K-Fêt'), ('change_config', 'Modifier la configuration K-Fêt'))},
|
||||||
|
),
|
||||||
|
]
|
14
kfet/migrations/0062_delete_globalpermissions.py
Normal file
14
kfet/migrations/0062_delete_globalpermissions.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('kfet', '0061_add_perms_config'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='GlobalPermissions',
|
||||||
|
),
|
||||||
|
]
|
|
@ -8,6 +8,7 @@ from gestioncof.models import CofProfile
|
||||||
from django.utils.six.moves import reduce
|
from django.utils.six.moves import reduce
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import F
|
from django.db.models import F
|
||||||
from datetime import date
|
from datetime import date
|
||||||
|
@ -94,6 +95,8 @@ class Account(models.Model):
|
||||||
('special_add_account',
|
('special_add_account',
|
||||||
"Créer un compte avec une balance initiale"),
|
"Créer un compte avec une balance initiale"),
|
||||||
('can_force_close', "Fermer manuellement la K-Fêt"),
|
('can_force_close', "Fermer manuellement la K-Fêt"),
|
||||||
|
('see_config', "Voir la configuration K-Fêt"),
|
||||||
|
('change_config', "Modifier la configuration K-Fêt"),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -540,21 +543,24 @@ class InventoryArticle(models.Model):
|
||||||
self.stock_error = self.stock_new - self.stock_old
|
self.stock_error = self.stock_new - self.stock_old
|
||||||
super(InventoryArticle, self).save(*args, **kwargs)
|
super(InventoryArticle, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class Supplier(models.Model):
|
class Supplier(models.Model):
|
||||||
articles = models.ManyToManyField(
|
articles = models.ManyToManyField(
|
||||||
Article,
|
Article,
|
||||||
through = 'SupplierArticle',
|
verbose_name=_("articles vendus"),
|
||||||
related_name = "suppliers")
|
through='SupplierArticle',
|
||||||
name = models.CharField("nom", max_length = 45)
|
related_name='suppliers',
|
||||||
address = models.TextField("adresse")
|
)
|
||||||
email = models.EmailField("adresse mail")
|
name = models.CharField(_("nom"), max_length=45)
|
||||||
phone = models.CharField("téléphone", max_length = 10)
|
address = models.TextField(_("adresse"), blank=True)
|
||||||
comment = models.TextField("commentaire")
|
email = models.EmailField(_("adresse mail"), blank=True)
|
||||||
|
phone = models.CharField(_("téléphone"), max_length=20, blank=True)
|
||||||
|
comment = models.TextField(_("commentaire"), blank=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class SupplierArticle(models.Model):
|
class SupplierArticle(models.Model):
|
||||||
supplier = models.ForeignKey(
|
supplier = models.ForeignKey(
|
||||||
Supplier, on_delete = models.PROTECT)
|
Supplier, on_delete = models.PROTECT)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import json
|
import json
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
from django.contrib.auth.models import AnonymousUser, Permission, User
|
from django.contrib.auth.models import AnonymousUser, Permission, User
|
||||||
from django.test import Client
|
from django.test import Client
|
||||||
|
@ -118,6 +119,11 @@ class OpenKfetViewsTest(ChannelTestCase):
|
||||||
"""OpenKfet views unit-tests suite."""
|
"""OpenKfet views unit-tests suite."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
# Need this (and here) because of '<client>.login' in setUp
|
||||||
|
patcher_messages = mock.patch('gestioncof.signals.messages')
|
||||||
|
patcher_messages.start()
|
||||||
|
self.addCleanup(patcher_messages.stop)
|
||||||
|
|
||||||
# get some permissions
|
# get some permissions
|
||||||
perms = {
|
perms = {
|
||||||
'kfet.is_team': Permission.objects.get(codename='is_team'),
|
'kfet.is_team': Permission.objects.get(codename='is_team'),
|
||||||
|
@ -194,7 +200,8 @@ class OpenKfetConsumerTest(ChannelTestCase):
|
||||||
OpenKfetConsumer.group_send('kfet.open.team', {'test': 'plop'})
|
OpenKfetConsumer.group_send('kfet.open.team', {'test': 'plop'})
|
||||||
self.assertIsNone(c.receive())
|
self.assertIsNone(c.receive())
|
||||||
|
|
||||||
def test_team_user(self):
|
@mock.patch('gestioncof.signals.messages')
|
||||||
|
def test_team_user(self, mock_messages):
|
||||||
"""Team user is added to kfet.open.team group."""
|
"""Team user is added to kfet.open.team group."""
|
||||||
# setup team user and its client
|
# setup team user and its client
|
||||||
t = User.objects.create_user('team', '', 'team')
|
t = User.objects.create_user('team', '', 'team')
|
||||||
|
@ -224,6 +231,11 @@ class OpenKfetScenarioTest(ChannelTestCase):
|
||||||
"""OpenKfet functionnal tests suite."""
|
"""OpenKfet functionnal tests suite."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
# Need this (and here) because of '<client>.login' in setUp
|
||||||
|
patcher_messages = mock.patch('gestioncof.signals.messages')
|
||||||
|
patcher_messages.start()
|
||||||
|
self.addCleanup(patcher_messages.stop)
|
||||||
|
|
||||||
# anonymous client (for views)
|
# anonymous client (for views)
|
||||||
self.c = Client()
|
self.c = Client()
|
||||||
# anonymous client (for websockets)
|
# anonymous client (for websockets)
|
||||||
|
|
95
kfet/tests/test_tests_utils.py
Normal file
95
kfet/tests/test_tests_utils.py
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from gestioncof.models import CofProfile
|
||||||
|
|
||||||
|
from ..models import Account
|
||||||
|
from .testcases import TestCaseMixin
|
||||||
|
from .utils import (
|
||||||
|
create_user, create_team, create_root, get_perms, user_add_perms,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class UserHelpersTests(TestCaseMixin, TestCase):
|
||||||
|
|
||||||
|
def test_create_user(self):
|
||||||
|
"""create_user creates a basic user and its account."""
|
||||||
|
u = create_user()
|
||||||
|
a = u.profile.account_kfet
|
||||||
|
|
||||||
|
self.assertInstanceExpected(u, {
|
||||||
|
'get_full_name': 'first last',
|
||||||
|
'username': 'user',
|
||||||
|
})
|
||||||
|
self.assertFalse(u.user_permissions.exists())
|
||||||
|
|
||||||
|
self.assertEqual('000', a.trigramme)
|
||||||
|
|
||||||
|
def test_create_team(self):
|
||||||
|
u = create_team()
|
||||||
|
a = u.profile.account_kfet
|
||||||
|
|
||||||
|
self.assertInstanceExpected(u, {
|
||||||
|
'get_full_name': 'team member',
|
||||||
|
'username': 'team',
|
||||||
|
})
|
||||||
|
self.assertTrue(u.has_perm('kfet.is_team'))
|
||||||
|
|
||||||
|
self.assertEqual('100', a.trigramme)
|
||||||
|
|
||||||
|
def test_create_root(self):
|
||||||
|
u = create_root()
|
||||||
|
a = u.profile.account_kfet
|
||||||
|
|
||||||
|
self.assertInstanceExpected(u, {
|
||||||
|
'get_full_name': 'super user',
|
||||||
|
'username': 'root',
|
||||||
|
'is_superuser': True,
|
||||||
|
'is_staff': True,
|
||||||
|
})
|
||||||
|
|
||||||
|
self.assertEqual('200', a.trigramme)
|
||||||
|
|
||||||
|
|
||||||
|
class PermHelpersTest(TestCaseMixin, TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
cts = ContentType.objects.get_for_models(Account, CofProfile)
|
||||||
|
self.perm1 = Permission.objects.create(
|
||||||
|
content_type=cts[Account],
|
||||||
|
codename='test_perm',
|
||||||
|
name='Perm for test',
|
||||||
|
)
|
||||||
|
self.perm2 = Permission.objects.create(
|
||||||
|
content_type=cts[CofProfile],
|
||||||
|
codename='another_test_perm',
|
||||||
|
name='Another one',
|
||||||
|
)
|
||||||
|
self.perm_team = Permission.objects.get(
|
||||||
|
content_type__app_label='kfet',
|
||||||
|
codename='is_team',
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_perms(self):
|
||||||
|
perms = get_perms('kfet.test_perm', 'gestioncof.another_test_perm')
|
||||||
|
self.assertDictEqual(perms, {
|
||||||
|
'kfet.test_perm': self.perm1,
|
||||||
|
'gestioncof.another_test_perm': self.perm2,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_user_add_perms(self):
|
||||||
|
user = User.objects.create_user(username='user', password='user')
|
||||||
|
user.user_permissions.add(self.perm1)
|
||||||
|
|
||||||
|
user_add_perms(user, ['kfet.is_team', 'gestioncof.another_test_perm'])
|
||||||
|
|
||||||
|
self.assertQuerysetEqual(
|
||||||
|
user.user_permissions.all(),
|
||||||
|
map(repr, [self.perm1, self.perm2, self.perm_team]),
|
||||||
|
ordered=False,
|
||||||
|
)
|
File diff suppressed because it is too large
Load diff
353
kfet/tests/testcases.py
Normal file
353
kfet/tests/testcases.py
Normal file
|
@ -0,0 +1,353 @@
|
||||||
|
from unittest import mock
|
||||||
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.http import QueryDict
|
||||||
|
from django.test import Client
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
|
from .utils import create_root, create_team, create_user
|
||||||
|
|
||||||
|
|
||||||
|
class TestCaseMixin:
|
||||||
|
"""Extends TestCase for kfet application tests."""
|
||||||
|
|
||||||
|
def assertForbidden(self, response):
|
||||||
|
"""
|
||||||
|
Test that the response (retrieved with a Client) is a denial of access.
|
||||||
|
|
||||||
|
The response should verify one of the following:
|
||||||
|
- its HTTP response code is 403,
|
||||||
|
- it redirects to the login page with a GET parameter named 'next'
|
||||||
|
whose value is the url of the requested page.
|
||||||
|
|
||||||
|
"""
|
||||||
|
request = response.wsgi_request
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
# Is this an HTTP Forbidden response ?
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
except AssertionError:
|
||||||
|
# A redirection to the login view is fine too.
|
||||||
|
|
||||||
|
# Let's build the login url with the 'next' param on current
|
||||||
|
# page.
|
||||||
|
full_path = request.get_full_path()
|
||||||
|
|
||||||
|
querystring = QueryDict(mutable=True)
|
||||||
|
querystring['next'] = full_path
|
||||||
|
|
||||||
|
login_url = '/login?' + querystring.urlencode(safe='/')
|
||||||
|
|
||||||
|
# We don't focus on what the login view does.
|
||||||
|
# So don't fetch the redirect.
|
||||||
|
self.assertRedirects(
|
||||||
|
response, login_url,
|
||||||
|
fetch_redirect_response=False,
|
||||||
|
)
|
||||||
|
except AssertionError:
|
||||||
|
raise AssertionError(
|
||||||
|
"%(http_method)s request at %(path)s should be forbidden for "
|
||||||
|
"%(username)s user.\n"
|
||||||
|
"Response isn't 403, nor a redirect to login view. Instead, "
|
||||||
|
"response code is %(code)d." % {
|
||||||
|
'http_method': request.method,
|
||||||
|
'path': request.get_full_path(),
|
||||||
|
'username': (
|
||||||
|
"'{}'".format(request.user)
|
||||||
|
if request.user.is_authenticated()
|
||||||
|
else 'anonymous'
|
||||||
|
),
|
||||||
|
'code': response.status_code,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def assertForbiddenKfet(self, response, form_ctx='form'):
|
||||||
|
"""
|
||||||
|
Test that a response (retrieved with a Client) contains error due to
|
||||||
|
lack of kfet permissions.
|
||||||
|
|
||||||
|
It checks that 'Permission refusée' is present in the non-field errors
|
||||||
|
of the form of response context at key 'form_ctx', or present in
|
||||||
|
messages.
|
||||||
|
|
||||||
|
This should be used for pages which can be accessed by the kfet team
|
||||||
|
members, but require additionnal permission(s) to make an operation.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
try:
|
||||||
|
form = response.context[form_ctx]
|
||||||
|
self.assertIn("Permission refusée", form.non_field_errors())
|
||||||
|
except (AssertionError, AttributeError, KeyError):
|
||||||
|
messages = [str(msg) for msg in response.context['messages']]
|
||||||
|
self.assertIn("Permission refusée", messages)
|
||||||
|
except AssertionError:
|
||||||
|
request = response.wsgi_request
|
||||||
|
raise AssertionError(
|
||||||
|
"%(http_method)s request at %(path)s should raise an error "
|
||||||
|
"for %(username)s user.\n"
|
||||||
|
"Cannot find any errors in non-field errors of form "
|
||||||
|
"'%(form_ctx)s', nor in messages." % {
|
||||||
|
'http_method': request.method,
|
||||||
|
'path': request.get_full_path(),
|
||||||
|
'username': (
|
||||||
|
"'%s'" % request.user
|
||||||
|
if request.user.is_authenticated()
|
||||||
|
else 'anonymous'
|
||||||
|
),
|
||||||
|
'form_ctx': form_ctx,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def assertInstanceExpected(self, instance, expected):
|
||||||
|
"""
|
||||||
|
Test that the values of the attributes and without-argument methods of
|
||||||
|
'instance' are equal to 'expected' pairs.
|
||||||
|
"""
|
||||||
|
for attr, expected_value in expected.items():
|
||||||
|
value = getattr(instance, attr)
|
||||||
|
if callable(value):
|
||||||
|
value = value()
|
||||||
|
self.assertEqual(value, expected_value)
|
||||||
|
|
||||||
|
def assertUrlsEqual(self, actual, expected):
|
||||||
|
"""
|
||||||
|
Test that the url 'actual' is as 'expected'.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
actual (str): Url to verify.
|
||||||
|
expected: Two forms are accepted.
|
||||||
|
* (str): Expected url. Strings equality is checked.
|
||||||
|
* (dict): Its keys must be attributes of 'urlparse(actual)'.
|
||||||
|
Equality is checked for each present key, except for
|
||||||
|
'query' which must be a dict of the expected query string
|
||||||
|
parameters.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if type(expected) == dict:
|
||||||
|
parsed = urlparse(actual)
|
||||||
|
for part, expected_part in expected.items():
|
||||||
|
if part == 'query':
|
||||||
|
self.assertDictEqual(
|
||||||
|
parse_qs(parsed.query),
|
||||||
|
expected.get('query', {}),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.assertEqual(getattr(parsed, part), expected_part)
|
||||||
|
else:
|
||||||
|
self.assertEqual(actual, expected)
|
||||||
|
|
||||||
|
|
||||||
|
class ViewTestCaseMixin(TestCaseMixin):
|
||||||
|
"""
|
||||||
|
TestCase extension to ease tests of kfet views.
|
||||||
|
|
||||||
|
|
||||||
|
Urls concerns
|
||||||
|
-------------
|
||||||
|
|
||||||
|
# Basic usage
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
url_name (str): Name of view under test, as given to 'reverse'
|
||||||
|
function.
|
||||||
|
url_args (list, optional): Will be given to 'reverse' call.
|
||||||
|
url_kwargs (dict, optional): Same.
|
||||||
|
url_expcted (str): What 'reverse' should return given previous
|
||||||
|
attributes.
|
||||||
|
|
||||||
|
View url can then be accessed at the 'url' attribute.
|
||||||
|
|
||||||
|
# Advanced usage
|
||||||
|
|
||||||
|
If multiple combinations of url name, args, kwargs can be used for a view,
|
||||||
|
it is possible to define 'urls_conf' attribute. It must be a list whose
|
||||||
|
each item is a dict defining arguments for 'reverse' call ('name', 'args',
|
||||||
|
'kwargs' keys) and its expected result ('expected' key).
|
||||||
|
|
||||||
|
The reversed urls can be accessed at the 't_urls' attribute.
|
||||||
|
|
||||||
|
|
||||||
|
Users concerns
|
||||||
|
--------------
|
||||||
|
|
||||||
|
During setup, three users are created with their kfet account:
|
||||||
|
- 'user': a basic user without any permission, account trigramme: 000,
|
||||||
|
- 'team': a user with kfet.is_team permission, account trigramme: 100,
|
||||||
|
- 'root': a superuser, account trigramme: 200.
|
||||||
|
Their password is their username.
|
||||||
|
|
||||||
|
One can create additionnal users with 'get_users_extra' method, or prevent
|
||||||
|
these 3 users to be created with 'get_users_base' method. See these two
|
||||||
|
methods for further informations.
|
||||||
|
|
||||||
|
By using 'register_user' method, these users can then be accessed at
|
||||||
|
'users' attribute by their label. Similarly, their kfet account is
|
||||||
|
registered on 'accounts' attribute.
|
||||||
|
|
||||||
|
A user label can be given to 'auth_user' attribute. The related user is
|
||||||
|
then authenticated on self.client during test setup. Its value defaults to
|
||||||
|
'None', meaning no user is authenticated.
|
||||||
|
|
||||||
|
|
||||||
|
Automated tests
|
||||||
|
---------------
|
||||||
|
|
||||||
|
# Url reverse
|
||||||
|
|
||||||
|
Based on url-related attributes/properties, the test 'test_urls' checks
|
||||||
|
that expected url is returned by 'reverse' (once with basic url usage and
|
||||||
|
each for advanced usage).
|
||||||
|
|
||||||
|
# Forbidden responses
|
||||||
|
|
||||||
|
The 'test_forbidden' test verifies that each user, from labels of
|
||||||
|
'auth_forbidden' attribute, can't access the url(s), i.e. response should
|
||||||
|
be a 403, or a redirect to login view.
|
||||||
|
|
||||||
|
Tested HTTP requests are given by 'http_methods' attribute. Additional data
|
||||||
|
can be given by defining an attribute '<method(lowercase)>_data'.
|
||||||
|
|
||||||
|
"""
|
||||||
|
url_name = None
|
||||||
|
url_expected = None
|
||||||
|
|
||||||
|
http_methods = ['GET']
|
||||||
|
|
||||||
|
auth_user = None
|
||||||
|
auth_forbidden = []
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Warning: Do not forget to call super().setUp() in subclasses.
|
||||||
|
"""
|
||||||
|
# Signals handlers on login/logout send messages.
|
||||||
|
# Due to the way the Django' test Client performs login, this raise an
|
||||||
|
# error. As workaround, we mock the Django' messages module.
|
||||||
|
patcher_messages = mock.patch('gestioncof.signals.messages')
|
||||||
|
patcher_messages.start()
|
||||||
|
self.addCleanup(patcher_messages.stop)
|
||||||
|
|
||||||
|
# A test can mock 'django.utils.timezone.now' and give this as return
|
||||||
|
# value. E.g. it is useful if the test checks values of 'auto_now' or
|
||||||
|
# 'auto_now_add' fields.
|
||||||
|
self.now = timezone.now()
|
||||||
|
|
||||||
|
# These attributes register users and accounts instances.
|
||||||
|
self.users = {}
|
||||||
|
self.accounts = {}
|
||||||
|
|
||||||
|
for label, user in dict(self.users_base, **self.users_extra).items():
|
||||||
|
self.register_user(label, user)
|
||||||
|
|
||||||
|
if self.auth_user:
|
||||||
|
# The wrapper is a sanity check.
|
||||||
|
self.assertTrue(
|
||||||
|
self.client.login(
|
||||||
|
username=self.auth_user,
|
||||||
|
password=self.auth_user,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
del self.users_base
|
||||||
|
del self.users_extra
|
||||||
|
|
||||||
|
def get_users_base(self):
|
||||||
|
"""
|
||||||
|
Dict of <label: user instance>.
|
||||||
|
|
||||||
|
Note: Don't access yourself this property. Use 'users_base' attribute
|
||||||
|
which cache the returned value from here.
|
||||||
|
It allows to give functions calls, which creates users instances, as
|
||||||
|
values here.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Format desc: username, password, trigramme
|
||||||
|
return {
|
||||||
|
# user, user, 000
|
||||||
|
'user': create_user(),
|
||||||
|
# team, team, 100
|
||||||
|
'team': create_team(),
|
||||||
|
# root, root, 200
|
||||||
|
'root': create_root(),
|
||||||
|
}
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def users_base(self):
|
||||||
|
return self.get_users_base()
|
||||||
|
|
||||||
|
def get_users_extra(self):
|
||||||
|
"""
|
||||||
|
Dict of <label: user instance>.
|
||||||
|
|
||||||
|
Note: Don't access yourself this property. Use 'users_base' attribute
|
||||||
|
which cache the returned value from here.
|
||||||
|
It allows to give functions calls, which create users instances, as
|
||||||
|
values here.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return {}
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def users_extra(self):
|
||||||
|
return self.get_users_extra()
|
||||||
|
|
||||||
|
def register_user(self, label, user):
|
||||||
|
self.users[label] = user
|
||||||
|
if hasattr(user.profile, 'account_kfet'):
|
||||||
|
self.accounts[label] = user.profile.account_kfet
|
||||||
|
|
||||||
|
def get_user(self, label):
|
||||||
|
if self.auth_user is not None:
|
||||||
|
return self.auth_user
|
||||||
|
return self.auth_user_mapping.get(label)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def urls_conf(self):
|
||||||
|
return [{
|
||||||
|
'name': self.url_name,
|
||||||
|
'args': getattr(self, 'url_args', []),
|
||||||
|
'kwargs': getattr(self, 'url_kwargs', {}),
|
||||||
|
'expected': self.url_expected,
|
||||||
|
}]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def t_urls(self):
|
||||||
|
return [
|
||||||
|
reverse(
|
||||||
|
url_conf['name'],
|
||||||
|
args=url_conf.get('args', []),
|
||||||
|
kwargs=url_conf.get('kwargs', {}),
|
||||||
|
)
|
||||||
|
for url_conf in self.urls_conf]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url(self):
|
||||||
|
return self.t_urls[0]
|
||||||
|
|
||||||
|
def test_urls(self):
|
||||||
|
for url, conf in zip(self.t_urls, self.urls_conf):
|
||||||
|
self.assertEqual(url, conf['expected'])
|
||||||
|
|
||||||
|
def test_forbidden(self):
|
||||||
|
for method in self.http_methods:
|
||||||
|
for user in self.auth_forbidden:
|
||||||
|
for url in self.t_urls:
|
||||||
|
self.check_forbidden(method, url, user)
|
||||||
|
|
||||||
|
def check_forbidden(self, method, url, user=None):
|
||||||
|
method = method.lower()
|
||||||
|
client = Client()
|
||||||
|
if user is not None:
|
||||||
|
client.login(username=user, password=user)
|
||||||
|
|
||||||
|
send_request = getattr(client, method)
|
||||||
|
data = getattr(self, '{}_data'.format(method), {})
|
||||||
|
|
||||||
|
r = send_request(url, data)
|
||||||
|
self.assertForbidden(r)
|
188
kfet/tests/utils.py
Normal file
188
kfet/tests/utils.py
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.contrib.auth.models import Permission
|
||||||
|
|
||||||
|
from ..models import Account
|
||||||
|
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
def _create_user_and_account(user_attrs, account_attrs, perms=None):
|
||||||
|
"""
|
||||||
|
Create a user and its account, and assign permissions to this user.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
user_attrs (dict): User data (first name, last name, password...).
|
||||||
|
account_attrs (dict): Account data (department, kfet password...).
|
||||||
|
perms (list of str: 'app.perm'): These permissions will be assigned to
|
||||||
|
the created user. No permission are assigned by default.
|
||||||
|
|
||||||
|
If 'password' is not given in 'user_attrs', username is used as password.
|
||||||
|
|
||||||
|
If 'kfet.is_team' is in 'perms' and 'password' is not in 'account_attrs',
|
||||||
|
the account password is 'kfetpwd_<user pwd>'.
|
||||||
|
|
||||||
|
"""
|
||||||
|
user_pwd = user_attrs.pop('password', user_attrs['username'])
|
||||||
|
user = User.objects.create(**user_attrs)
|
||||||
|
user.set_password(user_pwd)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
account_attrs['cofprofile'] = user.profile
|
||||||
|
kfet_pwd = account_attrs.pop('password', 'kfetpwd_{}'.format(user_pwd))
|
||||||
|
|
||||||
|
account = Account.objects.create(**account_attrs)
|
||||||
|
|
||||||
|
if perms is not None:
|
||||||
|
user = user_add_perms(user, perms)
|
||||||
|
|
||||||
|
if 'kfet.is_team' in perms:
|
||||||
|
account.change_pwd(kfet_pwd)
|
||||||
|
account.save()
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def create_user(username='user', trigramme='000', **kwargs):
|
||||||
|
"""
|
||||||
|
Create a user without any permission and its kfet account.
|
||||||
|
|
||||||
|
username and trigramme are accepted as arguments (defaults to 'user' and
|
||||||
|
'000').
|
||||||
|
|
||||||
|
user_attrs, account_attrs and perms can be given as keyword arguments to
|
||||||
|
customize the user and its kfet account.
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
|
||||||
|
User
|
||||||
|
* username: user
|
||||||
|
* password: user
|
||||||
|
* first_name: first
|
||||||
|
* last_name: last
|
||||||
|
* email: mail@user.net
|
||||||
|
Account
|
||||||
|
* trigramme: 000
|
||||||
|
|
||||||
|
"""
|
||||||
|
user_attrs = kwargs.setdefault('user_attrs', {})
|
||||||
|
|
||||||
|
user_attrs.setdefault('username', username)
|
||||||
|
user_attrs.setdefault('first_name', 'first')
|
||||||
|
user_attrs.setdefault('last_name', 'last')
|
||||||
|
user_attrs.setdefault('email', 'mail@user.net')
|
||||||
|
|
||||||
|
account_attrs = kwargs.setdefault('account_attrs', {})
|
||||||
|
account_attrs.setdefault('trigramme', trigramme)
|
||||||
|
|
||||||
|
return _create_user_and_account(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def create_team(username='team', trigramme='100', **kwargs):
|
||||||
|
"""
|
||||||
|
Create a user, member of the kfet team, and its kfet account.
|
||||||
|
|
||||||
|
username and trigramme are accepted as arguments (defaults to 'team' and
|
||||||
|
'100').
|
||||||
|
|
||||||
|
user_attrs, account_attrs and perms can be given as keyword arguments to
|
||||||
|
customize the user and its kfet account.
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
|
||||||
|
User
|
||||||
|
* username: team
|
||||||
|
* password: team
|
||||||
|
* first_name: team
|
||||||
|
* last_name: member
|
||||||
|
* email: mail@team.net
|
||||||
|
Account
|
||||||
|
* trigramme: 100
|
||||||
|
* kfet password: kfetpwd_team
|
||||||
|
|
||||||
|
"""
|
||||||
|
user_attrs = kwargs.setdefault('user_attrs', {})
|
||||||
|
|
||||||
|
user_attrs.setdefault('username', username)
|
||||||
|
user_attrs.setdefault('first_name', 'team')
|
||||||
|
user_attrs.setdefault('last_name', 'member')
|
||||||
|
user_attrs.setdefault('email', 'mail@team.net')
|
||||||
|
|
||||||
|
account_attrs = kwargs.setdefault('account_attrs', {})
|
||||||
|
account_attrs.setdefault('trigramme', trigramme)
|
||||||
|
|
||||||
|
perms = kwargs.setdefault('perms', [])
|
||||||
|
perms.append('kfet.is_team')
|
||||||
|
|
||||||
|
return _create_user_and_account(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def create_root(username='root', trigramme='200', **kwargs):
|
||||||
|
"""
|
||||||
|
Create a superuser and its kfet account.
|
||||||
|
|
||||||
|
username and trigramme are accepted as arguments (defaults to 'root' and
|
||||||
|
'200').
|
||||||
|
|
||||||
|
user_attrs, account_attrs and perms can be given as keyword arguments to
|
||||||
|
customize the user and its kfet account.
|
||||||
|
|
||||||
|
# Default values
|
||||||
|
|
||||||
|
User
|
||||||
|
* username: root
|
||||||
|
* password: root
|
||||||
|
* first_name: super
|
||||||
|
* last_name: user
|
||||||
|
* email: mail@root.net
|
||||||
|
* is_staff, is_superuser: True
|
||||||
|
Account
|
||||||
|
* trigramme: 200
|
||||||
|
* kfet password: kfetpwd_root
|
||||||
|
|
||||||
|
"""
|
||||||
|
user_attrs = kwargs.setdefault('user_attrs', {})
|
||||||
|
|
||||||
|
user_attrs.setdefault('username', username)
|
||||||
|
user_attrs.setdefault('first_name', 'super')
|
||||||
|
user_attrs.setdefault('last_name', 'user')
|
||||||
|
user_attrs.setdefault('email', 'mail@root.net')
|
||||||
|
user_attrs['is_superuser'] = user_attrs['is_staff'] = True
|
||||||
|
|
||||||
|
account_attrs = kwargs.setdefault('account_attrs', {})
|
||||||
|
account_attrs.setdefault('trigramme', trigramme)
|
||||||
|
|
||||||
|
return _create_user_and_account(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def get_perms(*labels):
|
||||||
|
"""Return Permission instances from a list of '<app>.<perm_codename>'."""
|
||||||
|
perms = {}
|
||||||
|
for label in set(labels):
|
||||||
|
app_label, codename = label.split('.', 1)
|
||||||
|
perms[label] = Permission.objects.get(
|
||||||
|
content_type__app_label=app_label,
|
||||||
|
codename=codename,
|
||||||
|
)
|
||||||
|
return perms
|
||||||
|
|
||||||
|
|
||||||
|
def user_add_perms(user, perms_labels):
|
||||||
|
"""
|
||||||
|
Add perms to a user.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (User instance)
|
||||||
|
perms (list of str 'app.perm_name')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The same user (refetched from DB to avoid missing perms)
|
||||||
|
|
||||||
|
"""
|
||||||
|
perms = get_perms(*perms_labels)
|
||||||
|
user.user_permissions.add(*perms.values())
|
||||||
|
|
||||||
|
# If permissions have already been fetched for this user, we need to reload
|
||||||
|
# it to avoid using of the previous permissions cache.
|
||||||
|
# https://docs.djangoproject.com/en/1.11/topics/auth/default/#permission-caching
|
||||||
|
return User.objects.get(pk=user.pk)
|
|
@ -188,13 +188,9 @@ urlpatterns = [
|
||||||
# Settings urls
|
# Settings urls
|
||||||
# -----
|
# -----
|
||||||
|
|
||||||
url(r'^settings/$',
|
url(r'^settings/$', views.config_list,
|
||||||
permission_required('kfet.change_settings')
|
|
||||||
(views.SettingsList.as_view()),
|
|
||||||
name='kfet.settings'),
|
name='kfet.settings'),
|
||||||
url(r'^settings/edit$',
|
url(r'^settings/edit$', views.config_update,
|
||||||
permission_required('kfet.change_settings')
|
|
||||||
(views.SettingsUpdate.as_view()),
|
|
||||||
name='kfet.settings.update'),
|
name='kfet.settings.update'),
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ from django.views.generic.edit import CreateView, UpdateView
|
||||||
from django.core.urlresolvers import reverse, reverse_lazy
|
from django.core.urlresolvers import reverse, reverse_lazy
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
from django.contrib.messages.views import SuccessMessageMixin
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required, permission_required
|
||||||
from django.contrib.auth.models import User, Permission
|
from django.contrib.auth.models import User, Permission
|
||||||
from django.http import JsonResponse, Http404
|
from django.http import JsonResponse, Http404
|
||||||
from django.forms import formset_factory
|
from django.forms import formset_factory
|
||||||
|
@ -1401,6 +1401,9 @@ class SettingsList(TemplateView):
|
||||||
template_name = 'kfet/settings.html'
|
template_name = 'kfet/settings.html'
|
||||||
|
|
||||||
|
|
||||||
|
config_list = permission_required('kfet.see_config')(SettingsList.as_view())
|
||||||
|
|
||||||
|
|
||||||
class SettingsUpdate(SuccessMessageMixin, FormView):
|
class SettingsUpdate(SuccessMessageMixin, FormView):
|
||||||
form_class = KFetConfigForm
|
form_class = KFetConfigForm
|
||||||
template_name = 'kfet/settings_update.html'
|
template_name = 'kfet/settings_update.html'
|
||||||
|
@ -1409,13 +1412,17 @@ class SettingsUpdate(SuccessMessageMixin, FormView):
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
# Checking permission
|
# Checking permission
|
||||||
if not self.request.user.has_perm('kfet.change_settings'):
|
if not self.request.user.has_perm('kfet.change_config'):
|
||||||
form.add_error(None, 'Permission refusée')
|
form.add_error(None, 'Permission refusée')
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
form.save()
|
form.save()
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
config_update = (
|
||||||
|
permission_required('kfet.change_config')(SettingsUpdate.as_view())
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# -----
|
# -----
|
||||||
# Transfer views
|
# Transfer views
|
||||||
|
|
Loading…
Reference in a new issue