Merge branch 'master' into aureplop/kfet-auth
This commit is contained in:
commit
fcf4a25745
350 changed files with 29746 additions and 8840 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -9,8 +9,13 @@ venv/
|
||||||
/src
|
/src
|
||||||
media/
|
media/
|
||||||
*.log
|
*.log
|
||||||
|
.sass-cache/
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
.coverage
|
||||||
|
|
||||||
# PyCharm
|
# PyCharm
|
||||||
.idea
|
.idea
|
||||||
.cache
|
.cache
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/
|
|
@ -1,6 +1,4 @@
|
||||||
services:
|
image: "python:3.5"
|
||||||
- postgres:latest
|
|
||||||
- redis:latest
|
|
||||||
|
|
||||||
variables:
|
variables:
|
||||||
# GestioCOF settings
|
# GestioCOF settings
|
||||||
|
@ -10,7 +8,7 @@ variables:
|
||||||
REDIS_PASSWD: "dummy"
|
REDIS_PASSWD: "dummy"
|
||||||
|
|
||||||
# Cached packages
|
# Cached packages
|
||||||
PYTHONPATH: "$CI_PROJECT_DIR/vendor/python"
|
PIP_CACHE_DIR: "$CI_PROJECT_DIR/vendor/pip"
|
||||||
|
|
||||||
# postgres service configuration
|
# postgres service configuration
|
||||||
POSTGRES_PASSWORD: "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
POSTGRES_PASSWORD: "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
||||||
|
@ -20,22 +18,44 @@ variables:
|
||||||
# psql password authentication
|
# psql password authentication
|
||||||
PGPASSWORD: $POSTGRES_PASSWORD
|
PGPASSWORD: $POSTGRES_PASSWORD
|
||||||
|
|
||||||
cache:
|
|
||||||
paths:
|
|
||||||
- vendor/python
|
|
||||||
- vendor/pip
|
|
||||||
- vendor/apt
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- mkdir -p vendor/{python,pip,apt}
|
|
||||||
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client
|
|
||||||
- sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' cof/settings/secret_example.py > cof/settings/secret.py
|
|
||||||
- sed -i.bak -E 's;^REDIS_PASSWD = .*$;REDIS_PASSWD = "";' cof/settings/secret.py
|
|
||||||
# Remove the old test database if it has not been done yet
|
|
||||||
- psql --username=$POSTGRES_USER --host=$DBHOST -c "DROP DATABASE IF EXISTS test_$POSTGRES_DB"
|
|
||||||
- pip install --upgrade --cache-dir vendor/pip -t vendor/python -r requirements.txt
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
|
before_script:
|
||||||
|
- mkdir -p vendor/{pip,apt}
|
||||||
|
- apt-get update -q && apt-get -o dir::cache::archives="vendor/apt" install -yqq postgresql-client
|
||||||
|
- sed -E 's/^REDIS_HOST.*/REDIS_HOST = "redis"/' cof/settings/secret_example.py > cof/settings/secret.py
|
||||||
|
- sed -i.bak -E 's;^REDIS_PASSWD = .*$;REDIS_PASSWD = "";' cof/settings/secret.py
|
||||||
|
# Remove the old test database if it has not been done yet
|
||||||
|
- psql --username=$POSTGRES_USER --host=$DBHOST -c "DROP DATABASE IF EXISTS test_$POSTGRES_DB"
|
||||||
|
- pip install --upgrade -r requirements.txt coverage tblib
|
||||||
|
- python --version
|
||||||
script:
|
script:
|
||||||
- python manage.py test
|
- coverage run manage.py test --parallel
|
||||||
|
after_script:
|
||||||
|
- coverage report
|
||||||
|
services:
|
||||||
|
- postgres:9.6
|
||||||
|
- redis:latest
|
||||||
|
cache:
|
||||||
|
key: test
|
||||||
|
paths:
|
||||||
|
- vendor/
|
||||||
|
# For GitLab CI to get coverage from build.
|
||||||
|
# Keep this disabled for now, as it may kill GitLab...
|
||||||
|
# coverage: '/TOTAL.*\s(\d+\.\d+)\%$/'
|
||||||
|
|
||||||
|
linters:
|
||||||
|
image: python:3.6
|
||||||
|
stage: test
|
||||||
|
before_script:
|
||||||
|
- mkdir -p vendor/pip
|
||||||
|
- pip install --upgrade black isort flake8
|
||||||
|
script:
|
||||||
|
- black --check .
|
||||||
|
- isort --recursive --check-only --diff bda cof gestioncof kfet petitscours provisioning shared utils
|
||||||
|
# Print errors only
|
||||||
|
- flake8 --exit-zero bda cof gestioncof kfet petitscours provisioning shared utils
|
||||||
|
cache:
|
||||||
|
key: linters
|
||||||
|
paths:
|
||||||
|
- vendor/
|
||||||
|
|
106
.pre-commit.sh
Executable file
106
.pre-commit.sh
Executable file
|
@ -0,0 +1,106 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# pre-commit hook for gestioCOF project.
|
||||||
|
#
|
||||||
|
# Run formatters first, then checkers.
|
||||||
|
# Formatters which changed a file must set the flag 'formatter_updated'.
|
||||||
|
|
||||||
|
exit_code=0
|
||||||
|
formatter_updated=0
|
||||||
|
checker_dirty=0
|
||||||
|
|
||||||
|
# TODO(AD): We should check only staged changes.
|
||||||
|
# Working? -> Stash unstaged changes, run it, pop stash
|
||||||
|
STAGED_PYTHON_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep ".py$")
|
||||||
|
|
||||||
|
|
||||||
|
# Formatter: black
|
||||||
|
|
||||||
|
printf "> black ... "
|
||||||
|
|
||||||
|
if type black &>/dev/null; then
|
||||||
|
if [ -z "$STAGED_PYTHON_FILES" ]; then
|
||||||
|
printf "OK\n"
|
||||||
|
else
|
||||||
|
BLACK_OUTPUT="/tmp/gc-black-output.log"
|
||||||
|
touch $BLACK_OUTPUT
|
||||||
|
|
||||||
|
if ! echo "$STAGED_PYTHON_FILES" | xargs -d'\n' black --check &>$BLACK_OUTPUT; then
|
||||||
|
echo "$STAGED_PYTHON_FILES" | xargs -d'\n' black &>$BLACK_OUTPUT
|
||||||
|
tail -1 $BLACK_OUTPUT
|
||||||
|
formatter_updated=1
|
||||||
|
else
|
||||||
|
printf "OK\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "SKIP: program not found\n"
|
||||||
|
printf "HINT: Install black with 'pip3 install black' (black requires Python>=3.6)\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Formatter: isort
|
||||||
|
|
||||||
|
printf "> isort ... "
|
||||||
|
|
||||||
|
if type isort &>/dev/null; then
|
||||||
|
if [ -z "$STAGED_PYTHON_FILES" ]; then
|
||||||
|
printf "OK\n"
|
||||||
|
else
|
||||||
|
ISORT_OUTPUT="/tmp/gc-isort-output.log"
|
||||||
|
touch $ISORT_OUTPUT
|
||||||
|
|
||||||
|
if ! echo "$STAGED_PYTHON_FILES" | xargs -d'\n' isort --check-only &>$ISORT_OUTPUT; then
|
||||||
|
echo "$STAGED_PYTHON_FILES" | xargs -d'\n' isort &>$ISORT_OUTPUT
|
||||||
|
printf "Reformatted.\n"
|
||||||
|
formatter_updated=1
|
||||||
|
else
|
||||||
|
printf "OK\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "SKIP: program not found\n"
|
||||||
|
printf "HINT: Install isort with 'pip install isort'\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Checker: flake8
|
||||||
|
|
||||||
|
printf "> flake8 ... "
|
||||||
|
|
||||||
|
if type flake8 &>/dev/null; then
|
||||||
|
if [ -z "$STAGED_PYTHON_FILES" ]; then
|
||||||
|
printf "OK\n"
|
||||||
|
else
|
||||||
|
FLAKE8_OUTPUT="/tmp/gc-flake8-output.log"
|
||||||
|
touch $FLAKE8_OUTPUT
|
||||||
|
|
||||||
|
if ! echo "$STAGED_PYTHON_FILES" | xargs -d'\n' flake8 &>$FLAKE8_OUTPUT; then
|
||||||
|
printf "FAIL\n"
|
||||||
|
cat $FLAKE8_OUTPUT
|
||||||
|
checker_dirty=1
|
||||||
|
else
|
||||||
|
printf "OK\n"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "SKIP: program not found\n"
|
||||||
|
printf "HINT: Install flake8 with 'pip install flake8'\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# End
|
||||||
|
|
||||||
|
if [ $checker_dirty -ne 0 ]
|
||||||
|
then
|
||||||
|
printf ">>> Checker(s) detect(s) issue(s)\n"
|
||||||
|
printf " You can still commit and push :)\n"
|
||||||
|
printf " Be warned that our CI may cause you more trouble.\n"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $formatter_updated -ne 0 ]
|
||||||
|
then
|
||||||
|
printf ">>> Working tree updated by formatter(s)\n"
|
||||||
|
printf " Add changes to staging area and retry.\n"
|
||||||
|
exit_code=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "\n"
|
||||||
|
|
||||||
|
exit $exit_code
|
14
CHANGELOG
Normal file
14
CHANGELOG
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
- Nouveau site du COF en wagtail
|
||||||
|
- Meilleurs affichage des longues listes de spectacles à cocher dans BdA-Revente
|
||||||
|
- Bugfix : les pages de la revente ne sont plus accessibles qu'aux membres du
|
||||||
|
COF
|
||||||
|
|
||||||
|
* Version 0.2 - 07/11/2018
|
||||||
|
|
||||||
|
- Corrections de bugs d'interface dans l'inscription aux tirages BdA
|
||||||
|
- On peut annuler une revente à tout moment
|
||||||
|
- Pleiiiiin de tests
|
||||||
|
|
||||||
|
* Version 0.1 - 09/09/2018
|
||||||
|
|
||||||
|
Début de la numérotation des versions
|
146
README.md
146
README.md
|
@ -1,10 +1,79 @@
|
||||||
# GestioCOF
|
# GestioCOF
|
||||||
|
|
||||||
|
[![pipeline status](https://git.eleves.ens.fr/cof-geek/gestioCOF/badges/master/pipeline.svg)](https://git.eleves.ens.fr/cof-geek/gestioCOF/commits/master)
|
||||||
|
[![coverage report](https://git.eleves.ens.fr/cof-geek/gestioCOF/badges/master/coverage.svg)](https://git.eleves.ens.fr/cof-geek/gestioCOF/commits/master)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
Il est possible d'installer GestioCOF sur votre machine de deux façons différentes :
|
||||||
|
|
||||||
|
- L'[installation manuelle](#installation-manuelle) (**recommandée** sous linux et OSX), plus légère
|
||||||
|
- L'[installation via vagrant](#vagrant) qui fonctionne aussi sous windows mais un peu plus lourde
|
||||||
|
|
||||||
|
### Installation manuelle
|
||||||
|
|
||||||
|
Il est fortement conseillé d'utiliser un environnement virtuel pour Python.
|
||||||
|
|
||||||
|
Il vous faudra installer pip, les librairies de développement de python ainsi
|
||||||
|
que sqlite3, un moteur de base de données léger et simple d'utilisation. Sous
|
||||||
|
Debian et dérivées (Ubuntu, ...) :
|
||||||
|
|
||||||
|
sudo apt-get install python3-pip python3-dev python3-venv sqlite3
|
||||||
|
|
||||||
|
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
|
||||||
|
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
|
||||||
|
(le dossier où se trouve ce README), et créez-le maintenant :
|
||||||
|
|
||||||
|
python3 -m venv venv
|
||||||
|
|
||||||
|
Pour l'activer, il faut taper
|
||||||
|
|
||||||
|
. venv/bin/activate
|
||||||
|
|
||||||
|
depuis le même dossier.
|
||||||
|
|
||||||
|
Vous pouvez maintenant installer les dépendances Python depuis le fichier
|
||||||
|
`requirements-devel.txt` :
|
||||||
|
|
||||||
|
pip install -U pip # parfois nécessaire la première fois
|
||||||
|
pip install -r requirements-devel.txt
|
||||||
|
|
||||||
|
Pour terminer, copier le fichier `cof/settings/secret_example.py` vers
|
||||||
|
`cof/settings/secret.py`. Sous Linux ou Mac, préférez plutôt un lien symbolique
|
||||||
|
pour profiter de façon transparente des mises à jour du fichier:
|
||||||
|
|
||||||
|
ln -s secret_example.py cof/settings/secret.py
|
||||||
|
|
||||||
|
Nous avons un git hook de pre-commit pour formatter et vérifier que votre code
|
||||||
|
vérifie nos conventions. Pour bénéficier des mises à jour du hook, préférez
|
||||||
|
encore l'installation *via* un lien symbolique:
|
||||||
|
|
||||||
|
ln -s ../../.pre-commit.sh .git/hooks/pre-commit
|
||||||
|
|
||||||
|
Pour plus d'informations à ce sujet, consulter la
|
||||||
|
[page](https://git.eleves.ens.fr/cof-geek/gestioCOF/wikis/coding-style)
|
||||||
|
du wiki gestioCOF liée aux conventions.
|
||||||
|
|
||||||
|
|
||||||
|
#### Fin d'installation
|
||||||
|
|
||||||
|
Il ne vous reste plus qu'à initialiser les modèles de Django et peupler la base
|
||||||
|
de donnée avec les données nécessaires au bon fonctionnement de GestioCOF + des
|
||||||
|
données bidons bien pratiques pour développer avec la commande suivante :
|
||||||
|
|
||||||
|
bash provisioning/prepare_django.sh
|
||||||
|
|
||||||
|
Voir le paragraphe ["outils pour développer"](#outils-pour-d-velopper) plus bas
|
||||||
|
pour plus de détails.
|
||||||
|
|
||||||
|
Vous êtes prêts à développer ! Lancer GestioCOF en faisant
|
||||||
|
|
||||||
|
python manage.py runserver
|
||||||
|
|
||||||
|
|
||||||
### Vagrant
|
### Vagrant
|
||||||
|
|
||||||
La façon recommandée d'installer GestioCOF sur votre machine est d'utiliser
|
Une autre façon d'installer GestioCOF sur votre machine est d'utiliser
|
||||||
[Vagrant](https://www.vagrantup.com/). Vagrant permet de créer une machine
|
[Vagrant](https://www.vagrantup.com/). Vagrant permet de créer une machine
|
||||||
virtuelle minimale sur laquelle tournera GestioCOF; ainsi on s'assure que tout
|
virtuelle minimale sur laquelle tournera GestioCOF; ainsi on s'assure que tout
|
||||||
le monde à la même configuration de développement (même sous Windows !), et
|
le monde à la même configuration de développement (même sous Windows !), et
|
||||||
|
@ -81,55 +150,6 @@ Ce serveur se lance tout seul et est accessible en dehors de la VM à l'url
|
||||||
code change, il faut relancer le worker avec `sudo systemctl restart
|
code change, il faut relancer le worker avec `sudo systemctl restart
|
||||||
worker.service` pour visualiser la dernière version du code.
|
worker.service` pour visualiser la dernière version du code.
|
||||||
|
|
||||||
|
|
||||||
### Installation manuelle
|
|
||||||
|
|
||||||
Vous pouvez opter pour une installation manuelle plutôt que d'utiliser Vagrant,
|
|
||||||
il est fortement conseillé d'utiliser un environnement virtuel pour Python.
|
|
||||||
|
|
||||||
Il vous faudra installer pip, les librairies de développement de python ainsi
|
|
||||||
que sqlite3, un moteur de base de données léger et simple d'utilisation. Sous
|
|
||||||
Debian et dérivées (Ubuntu, ...) :
|
|
||||||
|
|
||||||
sudo apt-get install python3-pip python3-dev sqlite3
|
|
||||||
|
|
||||||
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
|
|
||||||
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
|
|
||||||
(le dossier où se trouve ce README), et créez-le maintenant :
|
|
||||||
|
|
||||||
python3 -m venv venv
|
|
||||||
|
|
||||||
Pour l'activer, il faut faire
|
|
||||||
|
|
||||||
. venv/bin/activate
|
|
||||||
|
|
||||||
dans le même dossier.
|
|
||||||
|
|
||||||
Vous pouvez maintenant installer les dépendances Python depuis le fichier
|
|
||||||
`requirements-devel.txt` :
|
|
||||||
|
|
||||||
pip install -U pip
|
|
||||||
pip install -r requirements-devel.txt
|
|
||||||
|
|
||||||
Pour terminer, copier le fichier `cof/settings/secret_example.py` vers
|
|
||||||
`cof/settings/secret.py`. Sous Linux ou Mac, préférez plutôt un lien symbolique
|
|
||||||
pour profiter de façon transparente des mises à jour du fichier:
|
|
||||||
|
|
||||||
ln -s secret_example.py cof/settings/secret.py
|
|
||||||
|
|
||||||
|
|
||||||
#### Fin d'installation
|
|
||||||
|
|
||||||
Il ne vous reste plus qu'à initialiser les modèles de Django et peupler la base
|
|
||||||
de donnée avec les données nécessaires au bon fonctionnement de GestioCOF + des
|
|
||||||
données bidons bien pratiques pour développer avec la commande suivante :
|
|
||||||
|
|
||||||
bash provisioning/prepare_django.sh
|
|
||||||
|
|
||||||
Vous êtes prêts à développer ! Lancer GestioCOF en faisant
|
|
||||||
|
|
||||||
python manage.py runserver
|
|
||||||
|
|
||||||
### Mise à jour
|
### Mise à jour
|
||||||
|
|
||||||
Pour mettre à jour les paquets Python, utiliser la commande suivante :
|
Pour mettre à jour les paquets Python, utiliser la commande suivante :
|
||||||
|
@ -141,6 +161,32 @@ Pour mettre à jour les modèles après une migration, il faut ensuite faire :
|
||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
|
|
||||||
|
|
||||||
|
## Outils pour développer
|
||||||
|
|
||||||
|
### Base de donnée
|
||||||
|
|
||||||
|
Quelle que soit la méthode d'installation choisie, la base de donnée locale est
|
||||||
|
peuplée avec des données artificielles pour faciliter le développement.
|
||||||
|
|
||||||
|
- Un compte `root` (mot de passe `root`) avec tous les accès est créé. Connectez
|
||||||
|
vous sur ce compte pour accéder à tout GestioCOF.
|
||||||
|
- Des comptes utilisateurs COF et non-COF sont créés ainsi que quelques
|
||||||
|
spectacles BdA et deux tirages au sort pour jouer avec les fonctionnalités du BdA.
|
||||||
|
- À chaque compte est associé un trigramme K-Fêt
|
||||||
|
- Un certain nombre d'articles K-Fêt sont renseignés.
|
||||||
|
|
||||||
|
### Tests unitaires
|
||||||
|
|
||||||
|
On écrit désormais des tests unitaires qui sont lancés automatiquement sur gitlab
|
||||||
|
à chaque push. Il est conseillé de lancer les tests sur sa machine avant de proposer un patch pour s'assurer qu'on ne casse pas une fonctionnalité existante.
|
||||||
|
|
||||||
|
Pour lancer les tests :
|
||||||
|
|
||||||
|
```
|
||||||
|
python manage.py test
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
## Documentation utilisateur
|
## Documentation utilisateur
|
||||||
|
|
||||||
Une brève documentation utilisateur est accessible sur le
|
Une brève documentation utilisateur est accessible sur le
|
||||||
|
|
1
TODO_PROD.md
Normal file
1
TODO_PROD.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
- Changer les urls dans les mails "bda-revente" et "bda-shotgun"
|
|
@ -1 +0,0 @@
|
||||||
|
|
198
bda/admin.py
198
bda/admin.py
|
@ -1,16 +1,24 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import autocomplete_light
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from custommail.shortcuts import send_mass_custom_mail
|
|
||||||
|
|
||||||
|
from custommail.shortcuts import send_mass_custom_mail
|
||||||
|
from dal.autocomplete import ModelSelect2
|
||||||
|
from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.db.models import Sum, Count
|
from django.db.models import Count, Sum
|
||||||
from django.template.defaultfilters import pluralize
|
from django.template.defaultfilters import pluralize
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django import forms
|
|
||||||
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
from bda.models import (
|
||||||
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
Attribution,
|
||||||
|
CategorieSpectacle,
|
||||||
|
ChoixSpectacle,
|
||||||
|
Participant,
|
||||||
|
Quote,
|
||||||
|
Salle,
|
||||||
|
Spectacle,
|
||||||
|
SpectacleRevente,
|
||||||
|
Tirage,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReadOnlyMixin(object):
|
class ReadOnlyMixin(object):
|
||||||
|
@ -24,8 +32,17 @@ class ReadOnlyMixin(object):
|
||||||
return readonly_fields + self.readonly_fields_update
|
return readonly_fields + self.readonly_fields_update
|
||||||
|
|
||||||
|
|
||||||
|
class ChoixSpectacleAdminForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
widgets = {
|
||||||
|
"participant": ModelSelect2(url="bda-participant-autocomplete"),
|
||||||
|
"spectacle": ModelSelect2(url="bda-spectacle-autocomplete"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ChoixSpectacleInline(admin.TabularInline):
|
class ChoixSpectacleInline(admin.TabularInline):
|
||||||
model = ChoixSpectacle
|
model = ChoixSpectacle
|
||||||
|
form = ChoixSpectacleAdminForm
|
||||||
sortable_field_name = "priority"
|
sortable_field_name = "priority"
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,10 +51,10 @@ class AttributionTabularAdminForm(forms.ModelForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
spectacles = Spectacle.objects.select_related('location')
|
spectacles = Spectacle.objects.select_related("location")
|
||||||
if self.listing is not None:
|
if self.listing is not None:
|
||||||
spectacles = spectacles.filter(listing=self.listing)
|
spectacles = spectacles.filter(listing=self.listing)
|
||||||
self.fields['spectacle'].queryset = spectacles
|
self.fields["spectacle"].queryset = spectacles
|
||||||
|
|
||||||
|
|
||||||
class WithoutListingAttributionTabularAdminForm(AttributionTabularAdminForm):
|
class WithoutListingAttributionTabularAdminForm(AttributionTabularAdminForm):
|
||||||
|
@ -61,7 +78,7 @@ class AttributionInline(admin.TabularInline):
|
||||||
|
|
||||||
|
|
||||||
class WithListingAttributionInline(AttributionInline):
|
class WithListingAttributionInline(AttributionInline):
|
||||||
exclude = ('given', )
|
exclude = ("given",)
|
||||||
form = WithListingAttributionTabularAdminForm
|
form = WithListingAttributionTabularAdminForm
|
||||||
listing = True
|
listing = True
|
||||||
|
|
||||||
|
@ -72,12 +89,10 @@ class WithoutListingAttributionInline(AttributionInline):
|
||||||
|
|
||||||
|
|
||||||
class ParticipantAdminForm(forms.ModelForm):
|
class ParticipantAdminForm(forms.ModelForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['choicesrevente'].queryset = (
|
self.fields["choicesrevente"].queryset = Spectacle.objects.select_related(
|
||||||
Spectacle.objects
|
"location"
|
||||||
.select_related('location')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -85,11 +100,13 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||||
inlines = [WithListingAttributionInline, WithoutListingAttributionInline]
|
inlines = [WithListingAttributionInline, WithoutListingAttributionInline]
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
return Participant.objects.annotate(nb_places=Count('attributions'),
|
return Participant.objects.annotate(
|
||||||
total=Sum('attributions__price'))
|
nb_places=Count("attributions"), total=Sum("attributions__price")
|
||||||
|
)
|
||||||
|
|
||||||
def nb_places(self, obj):
|
def nb_places(self, obj):
|
||||||
return obj.nb_places
|
return obj.nb_places
|
||||||
|
|
||||||
nb_places.admin_order_field = "nb_places"
|
nb_places.admin_order_field = "nb_places"
|
||||||
nb_places.short_description = "Nombre de places"
|
nb_places.short_description = "Nombre de places"
|
||||||
|
|
||||||
|
@ -99,33 +116,32 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||||
return "%.02f €" % tot
|
return "%.02f €" % tot
|
||||||
else:
|
else:
|
||||||
return "0 €"
|
return "0 €"
|
||||||
|
|
||||||
total.admin_order_field = "total"
|
total.admin_order_field = "total"
|
||||||
total.short_description = "Total à payer"
|
total.short_description = "Total à payer"
|
||||||
list_display = ("user", "nb_places", "total", "paid", "paymenttype",
|
list_display = ("user", "nb_places", "total", "paid", "paymenttype", "tirage")
|
||||||
"tirage")
|
|
||||||
list_filter = ("paid", "tirage")
|
list_filter = ("paid", "tirage")
|
||||||
search_fields = ('user__username', 'user__first_name', 'user__last_name')
|
search_fields = ("user__username", "user__first_name", "user__last_name")
|
||||||
actions = ['send_attribs', ]
|
actions = ["send_attribs"]
|
||||||
actions_on_bottom = True
|
actions_on_bottom = True
|
||||||
list_per_page = 400
|
list_per_page = 400
|
||||||
readonly_fields = ("total",)
|
readonly_fields = ("total",)
|
||||||
readonly_fields_update = ('user', 'tirage')
|
readonly_fields_update = ("user", "tirage")
|
||||||
form = ParticipantAdminForm
|
form = ParticipantAdminForm
|
||||||
|
|
||||||
def send_attribs(self, request, queryset):
|
def send_attribs(self, request, queryset):
|
||||||
datatuple = []
|
datatuple = []
|
||||||
for member in queryset.all():
|
for member in queryset.all():
|
||||||
attribs = member.attributions.all()
|
attribs = member.attributions.all()
|
||||||
context = {'member': member.user}
|
context = {"member": member.user}
|
||||||
shortname = ""
|
shortname = ""
|
||||||
if len(attribs) == 0:
|
if len(attribs) == 0:
|
||||||
shortname = "bda-attributions-decus"
|
shortname = "bda-attributions-decus"
|
||||||
else:
|
else:
|
||||||
shortname = "bda-attributions"
|
shortname = "bda-attributions"
|
||||||
context['places'] = attribs
|
context["places"] = attribs
|
||||||
print(context)
|
print(context)
|
||||||
datatuple.append((shortname, context, "bda@ens.fr",
|
datatuple.append((shortname, context, "bda@ens.fr", [member.user.email]))
|
||||||
[member.user.email]))
|
|
||||||
send_mass_custom_mail(datatuple)
|
send_mass_custom_mail(datatuple)
|
||||||
count = len(queryset.all())
|
count = len(queryset.all())
|
||||||
if count == 1:
|
if count == 1:
|
||||||
|
@ -134,63 +150,69 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||||
else:
|
else:
|
||||||
message_bit = "%d membres ont" % count
|
message_bit = "%d membres ont" % count
|
||||||
plural = "s"
|
plural = "s"
|
||||||
self.message_user(request, "%s été informé%s avec succès."
|
self.message_user(
|
||||||
% (message_bit, plural))
|
request, "%s été informé%s avec succès." % (message_bit, plural)
|
||||||
|
)
|
||||||
|
|
||||||
send_attribs.short_description = "Envoyer les résultats par mail"
|
send_attribs.short_description = "Envoyer les résultats par mail"
|
||||||
|
|
||||||
|
|
||||||
class AttributionAdminForm(forms.ModelForm):
|
class AttributionAdminForm(forms.ModelForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if 'spectacle' in self.fields:
|
if "spectacle" in self.fields:
|
||||||
self.fields['spectacle'].queryset = (
|
self.fields["spectacle"].queryset = Spectacle.objects.select_related(
|
||||||
Spectacle.objects
|
"location"
|
||||||
.select_related('location')
|
|
||||||
)
|
)
|
||||||
if 'participant' in self.fields:
|
if "participant" in self.fields:
|
||||||
self.fields['participant'].queryset = (
|
self.fields["participant"].queryset = Participant.objects.select_related(
|
||||||
Participant.objects
|
"user", "tirage"
|
||||||
.select_related('user', 'tirage')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super(AttributionAdminForm, self).clean()
|
cleaned_data = super().clean()
|
||||||
participant = cleaned_data.get("participant")
|
participant = cleaned_data.get("participant")
|
||||||
spectacle = cleaned_data.get("spectacle")
|
spectacle = cleaned_data.get("spectacle")
|
||||||
if participant and spectacle:
|
if participant and spectacle:
|
||||||
if participant.tirage != spectacle.tirage:
|
if participant.tirage != spectacle.tirage:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
"Erreur : le participant et le spectacle n'appartiennent"
|
"Erreur : le participant et le spectacle n'appartiennent"
|
||||||
"pas au même tirage")
|
"pas au même tirage"
|
||||||
|
)
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||||
def paid(self, obj):
|
def paid(self, obj):
|
||||||
return obj.participant.paid
|
return obj.participant.paid
|
||||||
paid.short_description = 'A payé'
|
|
||||||
|
paid.short_description = "A payé"
|
||||||
paid.boolean = True
|
paid.boolean = True
|
||||||
list_display = ("id", "spectacle", "participant", "given", "paid")
|
list_display = ("id", "spectacle", "participant", "given", "paid")
|
||||||
search_fields = ('spectacle__title', 'participant__user__username',
|
search_fields = (
|
||||||
'participant__user__first_name',
|
"spectacle__title",
|
||||||
'participant__user__last_name')
|
"participant__user__username",
|
||||||
|
"participant__user__first_name",
|
||||||
|
"participant__user__last_name",
|
||||||
|
)
|
||||||
form = AttributionAdminForm
|
form = AttributionAdminForm
|
||||||
readonly_fields_update = ('spectacle', 'participant')
|
readonly_fields_update = ("spectacle", "participant")
|
||||||
|
|
||||||
|
|
||||||
class ChoixSpectacleAdmin(admin.ModelAdmin):
|
class ChoixSpectacleAdmin(admin.ModelAdmin):
|
||||||
form = autocomplete_light.modelform_factory(ChoixSpectacle, exclude=[])
|
form = ChoixSpectacleAdminForm
|
||||||
|
|
||||||
def tirage(self, obj):
|
def tirage(self, obj):
|
||||||
return obj.participant.tirage
|
return obj.participant.tirage
|
||||||
list_display = ("participant", "tirage", "spectacle", "priority",
|
|
||||||
"double_choice")
|
list_display = ("participant", "tirage", "spectacle", "priority", "double_choice")
|
||||||
list_filter = ("double_choice", "participant__tirage")
|
list_filter = ("double_choice", "participant__tirage")
|
||||||
search_fields = ('participant__user__username',
|
search_fields = (
|
||||||
'participant__user__first_name',
|
"participant__user__username",
|
||||||
'participant__user__last_name',
|
"participant__user__first_name",
|
||||||
'spectacle__title')
|
"participant__user__last_name",
|
||||||
|
"spectacle__title",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class QuoteInline(admin.TabularInline):
|
class QuoteInline(admin.TabularInline):
|
||||||
|
@ -200,42 +222,36 @@ class QuoteInline(admin.TabularInline):
|
||||||
class SpectacleAdmin(admin.ModelAdmin):
|
class SpectacleAdmin(admin.ModelAdmin):
|
||||||
inlines = [QuoteInline]
|
inlines = [QuoteInline]
|
||||||
model = Spectacle
|
model = Spectacle
|
||||||
list_display = ("title", "date", "tirage", "location", "slots", "price",
|
list_display = ("title", "date", "tirage", "location", "slots", "price", "listing")
|
||||||
"listing")
|
list_filter = ("location", "tirage")
|
||||||
list_filter = ("location", "tirage",)
|
|
||||||
search_fields = ("title", "location__name")
|
search_fields = ("title", "location__name")
|
||||||
readonly_fields = ("rappel_sent", )
|
readonly_fields = ("rappel_sent",)
|
||||||
|
|
||||||
|
|
||||||
class TirageAdmin(admin.ModelAdmin):
|
class TirageAdmin(admin.ModelAdmin):
|
||||||
model = Tirage
|
model = Tirage
|
||||||
list_display = ("title", "ouverture", "fermeture", "active",
|
list_display = ("title", "ouverture", "fermeture", "active", "enable_do_tirage")
|
||||||
"enable_do_tirage")
|
readonly_fields = ("tokens",)
|
||||||
readonly_fields = ("tokens", )
|
list_filter = ("active",)
|
||||||
list_filter = ("active", )
|
search_fields = ("title",)
|
||||||
search_fields = ("title", )
|
|
||||||
|
|
||||||
|
|
||||||
class SalleAdmin(admin.ModelAdmin):
|
class SalleAdmin(admin.ModelAdmin):
|
||||||
model = Salle
|
model = Salle
|
||||||
search_fields = ('name', 'address')
|
search_fields = ("name", "address")
|
||||||
|
|
||||||
|
|
||||||
class SpectacleReventeAdminForm(forms.ModelForm):
|
class SpectacleReventeAdminForm(forms.ModelForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['answered_mail'].queryset = (
|
self.fields["confirmed_entry"].queryset = Participant.objects.select_related(
|
||||||
Participant.objects
|
"user", "tirage"
|
||||||
.select_related('user', 'tirage')
|
|
||||||
)
|
)
|
||||||
self.fields['seller'].queryset = (
|
self.fields["seller"].queryset = Participant.objects.select_related(
|
||||||
Participant.objects
|
"user", "tirage"
|
||||||
.select_related('user', 'tirage')
|
|
||||||
)
|
)
|
||||||
self.fields['soldTo'].queryset = (
|
self.fields["soldTo"].queryset = Participant.objects.select_related(
|
||||||
Participant.objects
|
"user", "tirage"
|
||||||
.select_related('user', 'tirage')
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -243,6 +259,7 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
Administration des reventes de spectacles
|
Administration des reventes de spectacles
|
||||||
"""
|
"""
|
||||||
|
|
||||||
model = SpectacleRevente
|
model = SpectacleRevente
|
||||||
|
|
||||||
def spectacle(self, obj):
|
def spectacle(self, obj):
|
||||||
|
@ -254,12 +271,14 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||||
list_display = ("spectacle", "seller", "date", "soldTo")
|
list_display = ("spectacle", "seller", "date", "soldTo")
|
||||||
raw_id_fields = ("attribution",)
|
raw_id_fields = ("attribution",)
|
||||||
readonly_fields = ("date_tirage",)
|
readonly_fields = ("date_tirage",)
|
||||||
search_fields = ['attribution__spectacle__title',
|
search_fields = [
|
||||||
'seller__user__username',
|
"attribution__spectacle__title",
|
||||||
'seller__user__first_name',
|
"seller__user__username",
|
||||||
'seller__user__last_name']
|
"seller__user__first_name",
|
||||||
|
"seller__user__last_name",
|
||||||
|
]
|
||||||
|
|
||||||
actions = ['transfer', 'reinit']
|
actions = ["transfer", "reinit"]
|
||||||
actions_on_bottom = True
|
actions_on_bottom = True
|
||||||
form = SpectacleReventeAdminForm
|
form = SpectacleReventeAdminForm
|
||||||
|
|
||||||
|
@ -275,10 +294,10 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||||
attrib.save()
|
attrib.save()
|
||||||
self.message_user(
|
self.message_user(
|
||||||
request,
|
request,
|
||||||
"%d attribution%s %s été transférée%s avec succès." % (
|
"%d attribution%s %s été transférée%s avec succès."
|
||||||
count, pluralize(count),
|
% (count, pluralize(count), pluralize(count, "a,ont"), pluralize(count)),
|
||||||
pluralize(count, "a,ont"), pluralize(count))
|
)
|
||||||
)
|
|
||||||
transfer.short_description = "Transférer les reventes sélectionnées"
|
transfer.short_description = "Transférer les reventes sélectionnées"
|
||||||
|
|
||||||
def reinit(self, request, queryset):
|
def reinit(self, request, queryset):
|
||||||
|
@ -287,20 +306,15 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
count = queryset.count()
|
count = queryset.count()
|
||||||
for revente in queryset.filter(
|
for revente in queryset.filter(
|
||||||
attribution__spectacle__date__gte=timezone.now()):
|
attribution__spectacle__date__gte=timezone.now()
|
||||||
revente.date = timezone.now() - timedelta(hours=1)
|
):
|
||||||
revente.soldTo = None
|
revente.reset(new_date=timezone.now() - timedelta(hours=1))
|
||||||
revente.notif_sent = False
|
|
||||||
revente.tirage_done = False
|
|
||||||
if revente.answered_mail:
|
|
||||||
revente.answered_mail.clear()
|
|
||||||
revente.save()
|
|
||||||
self.message_user(
|
self.message_user(
|
||||||
request,
|
request,
|
||||||
"%d attribution%s %s été réinitialisée%s avec succès." % (
|
"%d attribution%s %s été réinitialisée%s avec succès."
|
||||||
count, pluralize(count),
|
% (count, pluralize(count), pluralize(count, "a,ont"), pluralize(count)),
|
||||||
pluralize(count, "a,ont"), pluralize(count))
|
)
|
||||||
)
|
|
||||||
reinit.short_description = "Réinitialiser les reventes sélectionnées"
|
reinit.short_description = "Réinitialiser les reventes sélectionnées"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db.models import Max
|
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,7 +14,7 @@ class Algorithm(object):
|
||||||
show.requests
|
show.requests
|
||||||
- on crée des tables de demandes pour chaque personne, afin de
|
- on crée des tables de demandes pour chaque personne, afin de
|
||||||
pouvoir modifier les rankings"""
|
pouvoir modifier les rankings"""
|
||||||
self.max_group = 2*max(choice.priority for choice in choices)
|
self.max_group = 2 * max(choice.priority for choice in choices)
|
||||||
self.shows = []
|
self.shows = []
|
||||||
showdict = {}
|
showdict = {}
|
||||||
for show in shows:
|
for show in shows:
|
||||||
|
@ -60,16 +52,19 @@ class Algorithm(object):
|
||||||
self.ranks[member][show] -= increment
|
self.ranks[member][show] -= increment
|
||||||
|
|
||||||
def appendResult(self, l, member, show):
|
def appendResult(self, l, member, show):
|
||||||
l.append((member,
|
l.append(
|
||||||
self.ranks[member][show],
|
(
|
||||||
self.origranks[member][show],
|
member,
|
||||||
self.choices[member][show].double))
|
self.ranks[member][show],
|
||||||
|
self.origranks[member][show],
|
||||||
|
self.choices[member][show].double,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def __call__(self, seed):
|
def __call__(self, seed):
|
||||||
random.seed(seed)
|
random.seed(seed)
|
||||||
results = []
|
results = []
|
||||||
shows = sorted(self.shows, key=lambda x: x.nrequests / x.slots,
|
shows = sorted(self.shows, key=lambda x: x.nrequests / x.slots, reverse=True)
|
||||||
reverse=True)
|
|
||||||
for show in shows:
|
for show in shows:
|
||||||
# On regroupe tous les gens ayant le même rang
|
# On regroupe tous les gens ayant le même rang
|
||||||
groups = dict([(i, []) for i in range(1, self.max_group + 1)])
|
groups = dict([(i, []) for i in range(1, self.max_group + 1)])
|
||||||
|
@ -88,8 +83,10 @@ class Algorithm(object):
|
||||||
if len(winners) + 1 < show.slots:
|
if len(winners) + 1 < show.slots:
|
||||||
self.appendResult(winners, member, show)
|
self.appendResult(winners, member, show)
|
||||||
self.appendResult(winners, member, show)
|
self.appendResult(winners, member, show)
|
||||||
elif not self.choices[member][show].autoquit \
|
elif (
|
||||||
and len(winners) < show.slots:
|
not self.choices[member][show].autoquit
|
||||||
|
and len(winners) < show.slots
|
||||||
|
):
|
||||||
self.appendResult(winners, member, show)
|
self.appendResult(winners, member, show)
|
||||||
self.appendResult(losers, member, show)
|
self.appendResult(losers, member, show)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import autocomplete_light
|
|
||||||
|
|
||||||
from bda.models import Participant, Spectacle
|
|
||||||
|
|
||||||
autocomplete_light.register(
|
|
||||||
Participant, search_fields=('user__username', 'user__first_name',
|
|
||||||
'user__last_name'),
|
|
||||||
autocomplete_js_attributes={'placeholder': 'participant...'})
|
|
||||||
|
|
||||||
autocomplete_light.register(
|
|
||||||
Spectacle, search_fields=('title', ),
|
|
||||||
autocomplete_js_attributes={'placeholder': 'spectacle...'})
|
|
195
bda/forms.py
195
bda/forms.py
|
@ -1,14 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms.models import BaseInlineFormSet
|
from django.forms.models import BaseInlineFormSet
|
||||||
|
from django.template import loader
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from bda.models import Attribution, Spectacle
|
from bda.models import Attribution, Spectacle, SpectacleRevente
|
||||||
|
|
||||||
|
|
||||||
class InscriptionInlineFormSet(BaseInlineFormSet):
|
class InscriptionInlineFormSet(BaseInlineFormSet):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@ -18,9 +16,9 @@ class InscriptionInlineFormSet(BaseInlineFormSet):
|
||||||
# set once for all "spectacle" field choices
|
# set once for all "spectacle" field choices
|
||||||
# - restrict choices to the spectacles of this tirage
|
# - restrict choices to the spectacles of this tirage
|
||||||
# - force_choices avoid many db requests
|
# - force_choices avoid many db requests
|
||||||
spectacles = tirage.spectacle_set.select_related('location')
|
spectacles = tirage.spectacle_set.select_related("location")
|
||||||
choices = [(sp.pk, str(sp)) for sp in spectacles]
|
choices = [(sp.pk, str(sp)) for sp in spectacles]
|
||||||
self.force_choices('spectacle', choices)
|
self.force_choices("spectacle", choices)
|
||||||
|
|
||||||
def force_choices(self, name, choices):
|
def force_choices(self, name, choices):
|
||||||
"""Set choices of a field.
|
"""Set choices of a field.
|
||||||
|
@ -32,7 +30,7 @@ class InscriptionInlineFormSet(BaseInlineFormSet):
|
||||||
for form in self.forms:
|
for form in self.forms:
|
||||||
field = form.fields[name]
|
field = form.fields[name]
|
||||||
if field.empty_label is not None:
|
if field.empty_label is not None:
|
||||||
field.choices = [('', field.empty_label)] + choices
|
field.choices = [("", field.empty_label)] + choices
|
||||||
else:
|
else:
|
||||||
field.choices = choices
|
field.choices = choices
|
||||||
|
|
||||||
|
@ -41,77 +39,146 @@ class TokenForm(forms.Form):
|
||||||
token = forms.CharField(widget=forms.widgets.Textarea())
|
token = forms.CharField(widget=forms.widgets.Textarea())
|
||||||
|
|
||||||
|
|
||||||
class AttributionModelMultipleChoiceField(forms.ModelMultipleChoiceField):
|
class TemplateLabelField(forms.ModelMultipleChoiceField):
|
||||||
|
"""
|
||||||
|
Extends ModelMultipleChoiceField to offer two more customization options :
|
||||||
|
- `label_from_instance` can be used with a template file
|
||||||
|
- the widget rendering template can be specified with `option_template_name`
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
label_template_name=None,
|
||||||
|
context_object_name="obj",
|
||||||
|
option_template_name=None,
|
||||||
|
*args,
|
||||||
|
**kwargs
|
||||||
|
):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.label_template_name = label_template_name
|
||||||
|
self.context_object_name = context_object_name
|
||||||
|
if option_template_name is not None:
|
||||||
|
self.widget.option_template_name = option_template_name
|
||||||
|
|
||||||
def label_from_instance(self, obj):
|
def label_from_instance(self, obj):
|
||||||
return "%s" % str(obj.spectacle)
|
if self.label_template_name is None:
|
||||||
|
return super().label_from_instance(obj)
|
||||||
|
else:
|
||||||
|
return loader.render_to_string(
|
||||||
|
self.label_template_name, context={self.context_object_name: obj}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Formulaires pour revente_manage
|
||||||
|
|
||||||
|
|
||||||
class ResellForm(forms.Form):
|
class ResellForm(forms.Form):
|
||||||
attributions = AttributionModelMultipleChoiceField(
|
|
||||||
label='',
|
|
||||||
queryset=Attribution.objects.none(),
|
|
||||||
widget=forms.CheckboxSelectMultiple,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
def __init__(self, participant, *args, **kwargs):
|
||||||
super(ResellForm, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['attributions'].queryset = (
|
self.fields["attributions"] = TemplateLabelField(
|
||||||
participant.attribution_set
|
queryset=participant.attribution_set.filter(
|
||||||
.filter(spectacle__date__gte=timezone.now())
|
spectacle__date__gte=timezone.now()
|
||||||
|
)
|
||||||
.exclude(revente__seller=participant)
|
.exclude(revente__seller=participant)
|
||||||
.select_related('spectacle', 'spectacle__location',
|
.select_related("spectacle", "spectacle__location", "participant__user"),
|
||||||
'participant__user')
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
required=False,
|
||||||
|
label_template_name="bda/forms/attribution_label_table.html",
|
||||||
|
option_template_name="bda/forms/checkbox_table.html",
|
||||||
|
context_object_name="attribution",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AnnulForm(forms.Form):
|
class AnnulForm(forms.Form):
|
||||||
attributions = AttributionModelMultipleChoiceField(
|
|
||||||
label='',
|
|
||||||
queryset=Attribution.objects.none(),
|
|
||||||
widget=forms.CheckboxSelectMultiple,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
def __init__(self, participant, *args, **kwargs):
|
||||||
super(AnnulForm, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['attributions'].queryset = (
|
self.fields["reventes"] = TemplateLabelField(
|
||||||
participant.attribution_set
|
label="",
|
||||||
.filter(spectacle__date__gte=timezone.now(),
|
queryset=participant.original_shows.filter(
|
||||||
revente__isnull=False,
|
attribution__spectacle__date__gte=timezone.now(), soldTo__isnull=True
|
||||||
revente__notif_sent=False,
|
)
|
||||||
revente__soldTo__isnull=True)
|
.select_related(
|
||||||
.select_related('spectacle', 'spectacle__location',
|
"attribution__spectacle", "attribution__spectacle__location"
|
||||||
'participant__user')
|
)
|
||||||
)
|
.order_by("-date"),
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
required=False,
|
||||||
class InscriptionReventeForm(forms.Form):
|
label_template_name="bda/forms/revente_self_label_table.html",
|
||||||
spectacles = forms.ModelMultipleChoiceField(
|
option_template_name="bda/forms/checkbox_table.html",
|
||||||
queryset=Spectacle.objects.none(),
|
context_object_name="revente",
|
||||||
widget=forms.CheckboxSelectMultiple,
|
|
||||||
required=False)
|
|
||||||
|
|
||||||
def __init__(self, tirage, *args, **kwargs):
|
|
||||||
super(InscriptionReventeForm, self).__init__(*args, **kwargs)
|
|
||||||
self.fields['spectacles'].queryset = (
|
|
||||||
tirage.spectacle_set
|
|
||||||
.select_related('location')
|
|
||||||
.filter(date__gte=timezone.now())
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SoldForm(forms.Form):
|
class SoldForm(forms.Form):
|
||||||
attributions = AttributionModelMultipleChoiceField(
|
|
||||||
label='',
|
|
||||||
queryset=Attribution.objects.none(),
|
|
||||||
widget=forms.CheckboxSelectMultiple)
|
|
||||||
|
|
||||||
def __init__(self, participant, *args, **kwargs):
|
def __init__(self, participant, *args, **kwargs):
|
||||||
super(SoldForm, self).__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['attributions'].queryset = (
|
self.fields["reventes"] = TemplateLabelField(
|
||||||
participant.attribution_set
|
queryset=participant.original_shows.filter(soldTo__isnull=False)
|
||||||
.filter(revente__isnull=False,
|
.exclude(soldTo=participant)
|
||||||
revente__soldTo__isnull=False)
|
.select_related(
|
||||||
.exclude(revente__soldTo=participant)
|
"attribution__spectacle", "attribution__spectacle__location"
|
||||||
.select_related('spectacle', 'spectacle__location',
|
),
|
||||||
'participant__user')
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
label_template_name="bda/forms/revente_sold_label_table.html",
|
||||||
|
option_template_name="bda/forms/checkbox_table.html",
|
||||||
|
context_object_name="revente",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Formulaire pour revente_subscribe
|
||||||
|
|
||||||
|
|
||||||
|
class InscriptionReventeForm(forms.Form):
|
||||||
|
def __init__(self, tirage, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields["spectacles"] = TemplateLabelField(
|
||||||
|
queryset=tirage.spectacle_set.select_related("location").filter(
|
||||||
|
date__gte=timezone.now()
|
||||||
|
),
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
required=False,
|
||||||
|
label_template_name="bda/forms/spectacle_label_table.html",
|
||||||
|
option_template_name="bda/forms/checkbox_table.html",
|
||||||
|
context_object_name="spectacle",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Formulaires pour revente_tirages
|
||||||
|
|
||||||
|
|
||||||
|
class ReventeTirageAnnulForm(forms.Form):
|
||||||
|
def __init__(self, participant, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields["reventes"] = TemplateLabelField(
|
||||||
|
queryset=participant.entered.filter(soldTo__isnull=True).select_related(
|
||||||
|
"attribution__spectacle", "seller__user"
|
||||||
|
),
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
required=False,
|
||||||
|
label_template_name="bda/forms/revente_other_label_table.html",
|
||||||
|
option_template_name="bda/forms/checkbox_table.html",
|
||||||
|
context_object_name="revente",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReventeTirageForm(forms.Form):
|
||||||
|
def __init__(self, participant, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.fields["reventes"] = TemplateLabelField(
|
||||||
|
queryset=(
|
||||||
|
SpectacleRevente.objects.filter(
|
||||||
|
notif_sent=True,
|
||||||
|
shotgun=False,
|
||||||
|
tirage_done=False,
|
||||||
|
attribution__spectacle__tirage=participant.tirage,
|
||||||
|
)
|
||||||
|
.exclude(confirmed_entry=participant)
|
||||||
|
.select_related("attribution__spectacle")
|
||||||
|
),
|
||||||
|
widget=forms.CheckboxSelectMultiple,
|
||||||
|
required=False,
|
||||||
|
label_template_name="bda/forms/revente_other_label_table.html",
|
||||||
|
option_template_name="bda/forms/checkbox_table.html",
|
||||||
|
context_object_name="revente",
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,17 +5,15 @@ Crée deux tirages de test et y inscrit les utilisateurs
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from gestioncof.management.base import MyBaseCommand
|
from bda.models import ChoixSpectacle, Participant, Salle, Spectacle, Tirage
|
||||||
from bda.models import Tirage, Spectacle, Salle, Participant, ChoixSpectacle
|
|
||||||
from bda.views import do_tirage
|
from bda.views import do_tirage
|
||||||
|
from gestioncof.management.base import MyBaseCommand
|
||||||
|
|
||||||
# Où sont stockés les fichiers json
|
# Où sont stockés les fichiers json
|
||||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data")
|
||||||
'data')
|
|
||||||
|
|
||||||
|
|
||||||
class Command(MyBaseCommand):
|
class Command(MyBaseCommand):
|
||||||
|
@ -27,27 +25,29 @@ class Command(MyBaseCommand):
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
Tirage.objects.all().delete()
|
Tirage.objects.all().delete()
|
||||||
Tirage.objects.bulk_create([
|
Tirage.objects.bulk_create(
|
||||||
Tirage(
|
[
|
||||||
title="Tirage de test 1",
|
Tirage(
|
||||||
ouverture=timezone.now()-timezone.timedelta(days=7),
|
title="Tirage de test 1",
|
||||||
fermeture=timezone.now(),
|
ouverture=timezone.now() - timezone.timedelta(days=7),
|
||||||
active=True
|
fermeture=timezone.now(),
|
||||||
),
|
active=True,
|
||||||
Tirage(
|
),
|
||||||
title="Tirage de test 2",
|
Tirage(
|
||||||
ouverture=timezone.now(),
|
title="Tirage de test 2",
|
||||||
fermeture=timezone.now()+timezone.timedelta(days=60),
|
ouverture=timezone.now(),
|
||||||
active=True
|
fermeture=timezone.now() + timezone.timedelta(days=60),
|
||||||
)
|
active=True,
|
||||||
])
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
tirages = Tirage.objects.all()
|
tirages = Tirage.objects.all()
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Salles
|
# Salles
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
locations = self.from_json('locations.json', DATA_DIR, Salle)
|
locations = self.from_json("locations.json", DATA_DIR, Salle)
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Spectacles
|
# Spectacles
|
||||||
|
@ -60,15 +60,13 @@ class Command(MyBaseCommand):
|
||||||
"""
|
"""
|
||||||
show.tirage = random.choice(tirages)
|
show.tirage = random.choice(tirages)
|
||||||
show.listing = bool(random.randint(0, 1))
|
show.listing = bool(random.randint(0, 1))
|
||||||
show.date = (
|
show.date = show.tirage.fermeture + timezone.timedelta(
|
||||||
show.tirage.fermeture
|
days=random.randint(60, 90)
|
||||||
+ timezone.timedelta(days=random.randint(60, 90))
|
|
||||||
)
|
)
|
||||||
show.location = random.choice(locations)
|
show.location = random.choice(locations)
|
||||||
return show
|
return show
|
||||||
shows = self.from_json(
|
|
||||||
'shows.json', DATA_DIR, Spectacle, show_callback
|
shows = self.from_json("shows.json", DATA_DIR, Spectacle, show_callback)
|
||||||
)
|
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Inscriptions
|
# Inscriptions
|
||||||
|
@ -79,23 +77,19 @@ class Command(MyBaseCommand):
|
||||||
choices = []
|
choices = []
|
||||||
for user in User.objects.filter(profile__is_cof=True):
|
for user in User.objects.filter(profile__is_cof=True):
|
||||||
for tirage in tirages:
|
for tirage in tirages:
|
||||||
part, _ = Participant.objects.get_or_create(
|
part, _ = Participant.objects.get_or_create(user=user, tirage=tirage)
|
||||||
user=user,
|
|
||||||
tirage=tirage
|
|
||||||
)
|
|
||||||
shows = random.sample(
|
shows = random.sample(
|
||||||
list(tirage.spectacle_set.all()),
|
list(tirage.spectacle_set.all()), tirage.spectacle_set.count() // 2
|
||||||
tirage.spectacle_set.count() // 2
|
|
||||||
)
|
)
|
||||||
for (rank, show) in enumerate(shows):
|
for (rank, show) in enumerate(shows):
|
||||||
choices.append(ChoixSpectacle(
|
choices.append(
|
||||||
participant=part,
|
ChoixSpectacle(
|
||||||
spectacle=show,
|
participant=part,
|
||||||
priority=rank + 1,
|
spectacle=show,
|
||||||
double_choice=random.choice(
|
priority=rank + 1,
|
||||||
['1', 'double', 'autoquit']
|
double_choice=random.choice(["1", "double", "autoquit"]),
|
||||||
)
|
)
|
||||||
))
|
)
|
||||||
ChoixSpectacle.objects.bulk_create(choices)
|
ChoixSpectacle.objects.bulk_create(choices)
|
||||||
self.stdout.write("- {:d} inscriptions générées".format(len(choices)))
|
self.stdout.write("- {:d} inscriptions générées".format(len(choices)))
|
||||||
|
|
||||||
|
|
|
@ -1,43 +1,49 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Gestion en ligne de commande des reventes.
|
Gestion en ligne de commande des reventes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from datetime import timedelta
|
|
||||||
from django.core.management import BaseCommand
|
from django.core.management import BaseCommand
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from bda.models import SpectacleRevente
|
from bda.models import SpectacleRevente
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Envoie les mails de notification et effectue " \
|
help = (
|
||||||
"les tirages au sort des reventes"
|
"Envoie les mails de notification et effectue les tirages au sort des reventes"
|
||||||
|
)
|
||||||
leave_locale_alone = True
|
leave_locale_alone = True
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
reventes = SpectacleRevente.objects.all()
|
reventes = SpectacleRevente.objects.all()
|
||||||
for revente in reventes:
|
for revente in reventes:
|
||||||
# Check si < 24h
|
# Le spectacle est bientôt et on a pas encore envoyé de mail :
|
||||||
if (revente.attribution.spectacle.date <=
|
# on met la place au shotgun et on prévient.
|
||||||
revente.date + timedelta(days=1)) and \
|
if revente.is_urgent and not revente.notif_sent:
|
||||||
now >= revente.date + timedelta(minutes=15) and \
|
if revente.can_notif:
|
||||||
not revente.notif_sent:
|
self.stdout.write(str(now))
|
||||||
self.stdout.write(str(now))
|
revente.mail_shotgun()
|
||||||
revente.mail_shotgun()
|
self.stdout.write(
|
||||||
self.stdout.write("Mail de disponibilité immédiate envoyé")
|
"Mails de disponibilité immédiate envoyés "
|
||||||
# Check si délai de retrait dépassé
|
"pour la revente [%s]" % revente
|
||||||
elif (now >= revente.date + timedelta(hours=1) and
|
)
|
||||||
not revente.notif_sent):
|
|
||||||
|
# Le spectacle est dans plus longtemps : on prévient
|
||||||
|
elif revente.can_notif and not revente.notif_sent:
|
||||||
self.stdout.write(str(now))
|
self.stdout.write(str(now))
|
||||||
revente.send_notif()
|
revente.send_notif()
|
||||||
self.stdout.write("Mail d'inscription à une revente envoyé")
|
self.stdout.write(
|
||||||
# Check si tirage à faire
|
"Mails d'inscription à la revente [%s] envoyés" % revente
|
||||||
elif (now >= revente.date_tirage and
|
)
|
||||||
not revente.tirage_done):
|
|
||||||
|
# On fait le tirage
|
||||||
|
elif now >= revente.date_tirage and not revente.tirage_done:
|
||||||
self.stdout.write(str(now))
|
self.stdout.write(str(now))
|
||||||
revente.tirage()
|
winner = revente.tirage()
|
||||||
self.stdout.write("Tirage effectué, mails envoyés")
|
self.stdout.write("Tirage effectué pour la revente [%s]" % revente)
|
||||||
|
|
||||||
|
if winner:
|
||||||
|
self.stdout.write("Gagnant : %s" % winner.user)
|
||||||
|
else:
|
||||||
|
self.stdout.write("Pas de gagnant ; place au shotgun")
|
||||||
|
|
|
@ -1,33 +1,33 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Gestion en ligne de commande des mails de rappel.
|
Gestion en ligne de commande des mails de rappel.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from bda.models import Spectacle
|
from bda.models import Spectacle
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = 'Envoie les mails de rappel des spectacles dont la date ' \
|
help = (
|
||||||
'approche.\nNe renvoie pas les mails déjà envoyés.'
|
"Envoie les mails de rappel des spectacles dont la date approche.\n"
|
||||||
|
"Ne renvoie pas les mails déjà envoyés."
|
||||||
|
)
|
||||||
leave_locale_alone = True
|
leave_locale_alone = True
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
delay = timedelta(days=4)
|
delay = timedelta(days=4)
|
||||||
shows = Spectacle.objects \
|
shows = (
|
||||||
.filter(date__range=(now, now+delay)) \
|
Spectacle.objects.filter(date__range=(now, now + delay))
|
||||||
.filter(tirage__active=True) \
|
.filter(tirage__active=True)
|
||||||
.filter(rappel_sent__isnull=True) \
|
.filter(rappel_sent__isnull=True)
|
||||||
.all()
|
.all()
|
||||||
|
)
|
||||||
for show in shows:
|
for show in shows:
|
||||||
show.send_rappel()
|
show.send_rappel()
|
||||||
self.stdout.write(
|
self.stdout.write("Mails de rappels pour %s envoyés avec succès." % show)
|
||||||
'Mails de rappels pour %s envoyés avec succès.' % show)
|
|
||||||
if not shows:
|
if not shows:
|
||||||
self.stdout.write('Aucun mail à envoyer.')
|
self.stdout.write("Aucun mail à envoyer.")
|
||||||
|
|
|
@ -1,108 +1,206 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Attribution',
|
name="Attribution",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('given', models.BooleanField(default=False, verbose_name='Donn\xe9e')),
|
"id",
|
||||||
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("given", models.BooleanField(default=False, verbose_name="Donn\xe9e")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ChoixSpectacle',
|
name="ChoixSpectacle",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('priority', models.PositiveIntegerField(verbose_name=b'Priorit\xc3\xa9')),
|
"id",
|
||||||
('double_choice', models.CharField(default=b'1', max_length=10, verbose_name=b'Nombre de places', choices=[(b'1', b'1 place'), (b'autoquit', b'2 places si possible, 1 sinon'), (b'double', b'2 places sinon rien')])),
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"priority",
|
||||||
|
models.PositiveIntegerField(verbose_name=b"Priorit\xc3\xa9"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"double_choice",
|
||||||
|
models.CharField(
|
||||||
|
default=b"1",
|
||||||
|
max_length=10,
|
||||||
|
verbose_name=b"Nombre de places",
|
||||||
|
choices=[
|
||||||
|
(b"1", b"1 place"),
|
||||||
|
(b"autoquit", b"2 places si possible, 1 sinon"),
|
||||||
|
(b"double", b"2 places sinon rien"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('priority',),
|
"ordering": ("priority",),
|
||||||
'verbose_name': 'voeu',
|
"verbose_name": "voeu",
|
||||||
'verbose_name_plural': 'voeux',
|
"verbose_name_plural": "voeux",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Participant',
|
name="Participant",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('paid', models.BooleanField(default=False, verbose_name='A pay\xe9')),
|
"id",
|
||||||
('paymenttype', models.CharField(blank=True, max_length=6, verbose_name='Moyen de paiement', choices=[(b'cash', 'Cash'), (b'cb', b'CB'), (b'cheque', 'Ch\xe8que'), (b'autre', 'Autre')])),
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("paid", models.BooleanField(default=False, verbose_name="A pay\xe9")),
|
||||||
|
(
|
||||||
|
"paymenttype",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=6,
|
||||||
|
verbose_name="Moyen de paiement",
|
||||||
|
choices=[
|
||||||
|
(b"cash", "Cash"),
|
||||||
|
(b"cb", b"CB"),
|
||||||
|
(b"cheque", "Ch\xe8que"),
|
||||||
|
(b"autre", "Autre"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Salle',
|
name="Salle",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('name', models.CharField(max_length=300, verbose_name=b'Nom')),
|
"id",
|
||||||
('address', models.TextField(verbose_name=b'Adresse')),
|
models.AutoField(
|
||||||
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("name", models.CharField(max_length=300, verbose_name=b"Nom")),
|
||||||
|
("address", models.TextField(verbose_name=b"Adresse")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Spectacle',
|
name="Spectacle",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('title', models.CharField(max_length=300, verbose_name=b'Titre')),
|
"id",
|
||||||
('date', models.DateTimeField(verbose_name=b'Date & heure')),
|
models.AutoField(
|
||||||
('description', models.TextField(verbose_name=b'Description', blank=True)),
|
verbose_name="ID",
|
||||||
('slots_description', models.TextField(verbose_name=b'Description des places', blank=True)),
|
serialize=False,
|
||||||
('price', models.FloatField(verbose_name=b"Prix d'une place", blank=True)),
|
auto_created=True,
|
||||||
('slots', models.IntegerField(verbose_name=b'Places')),
|
primary_key=True,
|
||||||
('priority', models.IntegerField(default=1000, verbose_name=b'Priorit\xc3\xa9')),
|
),
|
||||||
('location', models.ForeignKey(to='bda.Salle')),
|
),
|
||||||
|
("title", models.CharField(max_length=300, verbose_name=b"Titre")),
|
||||||
|
("date", models.DateTimeField(verbose_name=b"Date & heure")),
|
||||||
|
(
|
||||||
|
"description",
|
||||||
|
models.TextField(verbose_name=b"Description", blank=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slots_description",
|
||||||
|
models.TextField(
|
||||||
|
verbose_name=b"Description des places", blank=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"price",
|
||||||
|
models.FloatField(verbose_name=b"Prix d'une place", blank=True),
|
||||||
|
),
|
||||||
|
("slots", models.IntegerField(verbose_name=b"Places")),
|
||||||
|
(
|
||||||
|
"priority",
|
||||||
|
models.IntegerField(default=1000, verbose_name=b"Priorit\xc3\xa9"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"location",
|
||||||
|
models.ForeignKey(to="bda.Salle", on_delete=models.CASCADE),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ('priority', 'date', 'title'),
|
"ordering": ("priority", "date", "title"),
|
||||||
'verbose_name': 'Spectacle',
|
"verbose_name": "Spectacle",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='participant',
|
model_name="participant",
|
||||||
name='attributions',
|
name="attributions",
|
||||||
field=models.ManyToManyField(related_name='attributed_to', through='bda.Attribution', to='bda.Spectacle'),
|
field=models.ManyToManyField(
|
||||||
|
related_name="attributed_to",
|
||||||
|
through="bda.Attribution",
|
||||||
|
to="bda.Spectacle",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='participant',
|
model_name="participant",
|
||||||
name='choices',
|
name="choices",
|
||||||
field=models.ManyToManyField(related_name='chosen_by', through='bda.ChoixSpectacle', to='bda.Spectacle'),
|
field=models.ManyToManyField(
|
||||||
|
related_name="chosen_by",
|
||||||
|
through="bda.ChoixSpectacle",
|
||||||
|
to="bda.Spectacle",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='participant',
|
model_name="participant",
|
||||||
name='user',
|
name="user",
|
||||||
field=models.OneToOneField(to=settings.AUTH_USER_MODEL),
|
field=models.OneToOneField(
|
||||||
|
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='choixspectacle',
|
model_name="choixspectacle",
|
||||||
name='participant',
|
name="participant",
|
||||||
field=models.ForeignKey(to='bda.Participant'),
|
field=models.ForeignKey(to="bda.Participant", on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='choixspectacle',
|
model_name="choixspectacle",
|
||||||
name='spectacle',
|
name="spectacle",
|
||||||
field=models.ForeignKey(related_name='participants', to='bda.Spectacle'),
|
field=models.ForeignKey(
|
||||||
|
related_name="participants",
|
||||||
|
to="bda.Spectacle",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='attribution',
|
model_name="attribution",
|
||||||
name='participant',
|
name="participant",
|
||||||
field=models.ForeignKey(to='bda.Participant'),
|
field=models.ForeignKey(to="bda.Participant", on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='attribution',
|
model_name="attribution",
|
||||||
name='spectacle',
|
name="spectacle",
|
||||||
field=models.ForeignKey(related_name='attribues', to='bda.Spectacle'),
|
field=models.ForeignKey(
|
||||||
|
related_name="attribues", to="bda.Spectacle", on_delete=models.CASCADE
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='choixspectacle',
|
name="choixspectacle", unique_together=set([("participant", "spectacle")])
|
||||||
unique_together=set([('participant', 'spectacle')]),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,49 +36,77 @@ def fill_tirage_fields(apps, schema_editor):
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0001_initial")]
|
||||||
('bda', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Tirage',
|
name="Tirage",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
(
|
||||||
('title', models.CharField(max_length=300, verbose_name=b'Titre')),
|
"id",
|
||||||
('ouverture', models.DateTimeField(verbose_name=b"Date et heure d'ouverture du tirage")),
|
models.AutoField(
|
||||||
('fermeture', models.DateTimeField(verbose_name=b'Date et heure de fermerture du tirage')),
|
verbose_name="ID",
|
||||||
('token', models.TextField(verbose_name=b'Graine du tirage', blank=True)),
|
serialize=False,
|
||||||
('active', models.BooleanField(default=True, verbose_name=b'Tirage actif')),
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("title", models.CharField(max_length=300, verbose_name=b"Titre")),
|
||||||
|
(
|
||||||
|
"ouverture",
|
||||||
|
models.DateTimeField(
|
||||||
|
verbose_name=b"Date et heure d'ouverture du tirage"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"fermeture",
|
||||||
|
models.DateTimeField(
|
||||||
|
verbose_name=b"Date et heure de fermerture du tirage"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"token",
|
||||||
|
models.TextField(verbose_name=b"Graine du tirage", blank=True),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"active",
|
||||||
|
models.BooleanField(default=True, verbose_name=b"Tirage actif"),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
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, on_delete=models.CASCADE
|
||||||
|
),
|
||||||
),
|
),
|
||||||
# Create fields `spectacle` for `Participant` and `Spectacle` models.
|
# Create fields `spectacle` for `Participant` and `Spectacle` models.
|
||||||
# These fields are not nullable, but we first create them as nullable
|
# These fields are not nullable, but we first create them as nullable
|
||||||
# to give a default value for existing instances of these models.
|
# 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(to='bda.Tirage', null=True),
|
field=models.ForeignKey(
|
||||||
|
to="bda.Tirage", null=True, on_delete=models.CASCADE
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='tirage',
|
name="tirage",
|
||||||
field=models.ForeignKey(to='bda.Tirage', null=True),
|
field=models.ForeignKey(
|
||||||
|
to="bda.Tirage", null=True, on_delete=models.CASCADE
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop),
|
migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='participant',
|
model_name="participant",
|
||||||
name='tirage',
|
name="tirage",
|
||||||
field=models.ForeignKey(to='bda.Tirage'),
|
field=models.ForeignKey(to="bda.Tirage", on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='tirage',
|
name="tirage",
|
||||||
field=models.ForeignKey(to='bda.Tirage'),
|
field=models.ForeignKey(to="bda.Tirage", on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,19 +6,17 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0002_add_tirage")]
|
||||||
('bda', '0002_add_tirage'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='price',
|
name="price",
|
||||||
field=models.FloatField(verbose_name=b"Prix d'une place"),
|
field=models.FloatField(verbose_name=b"Prix d'une place"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='active',
|
name="active",
|
||||||
field=models.BooleanField(default=False, verbose_name=b'Tirage actif'),
|
field=models.BooleanField(default=False, verbose_name=b"Tirage actif"),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,20 +6,22 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0003_update_tirage_and_spectacle")]
|
||||||
('bda', '0003_update_tirage_and_spectacle'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='listing',
|
name="listing",
|
||||||
field=models.BooleanField(default=False, verbose_name=b'Les places sont sur listing'),
|
field=models.BooleanField(
|
||||||
|
default=False, verbose_name=b"Les places sont sur listing"
|
||||||
|
),
|
||||||
preserve_default=False,
|
preserve_default=False,
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='rappel_sent',
|
name="rappel_sent",
|
||||||
field=models.DateTimeField(null=True, verbose_name=b'Mail de rappel envoy\xc3\xa9', blank=True),
|
field=models.DateTimeField(
|
||||||
|
null=True, verbose_name=b"Mail de rappel envoy\xc3\xa9", blank=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,24 +6,24 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0004_mails-rappel")]
|
||||||
('bda', '0004_mails-rappel'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='choixspectacle',
|
model_name="choixspectacle",
|
||||||
name='priority',
|
name="priority",
|
||||||
field=models.PositiveIntegerField(verbose_name='Priorit\xe9'),
|
field=models.PositiveIntegerField(verbose_name="Priorit\xe9"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='priority',
|
name="priority",
|
||||||
field=models.IntegerField(default=1000, verbose_name='Priorit\xe9'),
|
field=models.IntegerField(default=1000, verbose_name="Priorit\xe9"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='rappel_sent',
|
name="rappel_sent",
|
||||||
field=models.DateTimeField(null=True, verbose_name='Mail de rappel envoy\xe9', blank=True),
|
field=models.DateTimeField(
|
||||||
|
null=True, verbose_name="Mail de rappel envoy\xe9", blank=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -10,26 +10,25 @@ def forwards_func(apps, schema_editor):
|
||||||
db_alias = schema_editor.connection.alias
|
db_alias = schema_editor.connection.alias
|
||||||
for tirage in Tirage.objects.using(db_alias).all():
|
for tirage in Tirage.objects.using(db_alias).all():
|
||||||
if tirage.tokens:
|
if tirage.tokens:
|
||||||
tirage.tokens = "Before %s\n\"\"\"%s\"\"\"\n" % (
|
tirage.tokens = 'Before %s\n"""%s"""\n' % (
|
||||||
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
|
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
|
||||||
tirage.tokens)
|
tirage.tokens,
|
||||||
|
)
|
||||||
tirage.save()
|
tirage.save()
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0005_encoding")]
|
||||||
('bda', '0005_encoding'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.RenameField('tirage', 'token', 'tokens'),
|
migrations.RenameField("tirage", "token", "tokens"),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='enable_do_tirage',
|
name="enable_do_tirage",
|
||||||
field=models.BooleanField(
|
field=models.BooleanField(
|
||||||
default=False,
|
default=False, verbose_name=b"Le tirage peut \xc3\xaatre lanc\xc3\xa9"
|
||||||
verbose_name=b'Le tirage peut \xc3\xaatre lanc\xc3\xa9'),
|
),
|
||||||
),
|
),
|
||||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,89 +1,100 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0006_add_tirage_switch")]
|
||||||
('bda', '0006_add_tirage_switch'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='CategorieSpectacle',
|
name="CategorieSpectacle",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False,
|
(
|
||||||
auto_created=True, primary_key=True)),
|
"id",
|
||||||
('name', models.CharField(max_length=100, verbose_name='Nom',
|
models.AutoField(
|
||||||
unique=True)),
|
verbose_name="ID",
|
||||||
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"name",
|
||||||
|
models.CharField(max_length=100, verbose_name="Nom", unique=True),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={"verbose_name": "Cat\xe9gorie"},
|
||||||
'verbose_name': 'Cat\xe9gorie',
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Quote',
|
name="Quote",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False,
|
(
|
||||||
auto_created=True, primary_key=True)),
|
"id",
|
||||||
('text', models.TextField(verbose_name='Citation')),
|
models.AutoField(
|
||||||
('author', models.CharField(max_length=200,
|
verbose_name="ID",
|
||||||
verbose_name='Auteur')),
|
serialize=False,
|
||||||
|
auto_created=True,
|
||||||
|
primary_key=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("text", models.TextField(verbose_name="Citation")),
|
||||||
|
("author", models.CharField(max_length=200, verbose_name="Auteur")),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.AlterModelOptions(
|
migrations.AlterModelOptions(
|
||||||
name='spectacle',
|
name="spectacle",
|
||||||
options={'ordering': ('date', 'title'),
|
options={"ordering": ("date", "title"), "verbose_name": "Spectacle"},
|
||||||
'verbose_name': 'Spectacle'},
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='spectacle',
|
|
||||||
name='priority',
|
|
||||||
),
|
),
|
||||||
|
migrations.RemoveField(model_name="spectacle", name="priority"),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='ext_link',
|
name="ext_link",
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
max_length=500,
|
max_length=500,
|
||||||
verbose_name='Lien vers le site du spectacle',
|
verbose_name="Lien vers le site du spectacle",
|
||||||
blank=True),
|
blank=True,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='image',
|
name="image",
|
||||||
field=models.ImageField(upload_to='imgs/shows/', null=True,
|
field=models.ImageField(
|
||||||
verbose_name='Image', blank=True),
|
upload_to="imgs/shows/", null=True, verbose_name="Image", blank=True
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='enable_do_tirage',
|
name="enable_do_tirage",
|
||||||
field=models.BooleanField(
|
field=models.BooleanField(
|
||||||
default=False,
|
default=False, verbose_name="Le tirage peut \xeatre lanc\xe9"
|
||||||
verbose_name='Le tirage peut \xeatre lanc\xe9'),
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='tokens',
|
name="tokens",
|
||||||
field=models.TextField(verbose_name='Graine(s) du tirage',
|
field=models.TextField(verbose_name="Graine(s) du tirage", blank=True),
|
||||||
blank=True),
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='category',
|
name="category",
|
||||||
field=models.ForeignKey(blank=True, to='bda.CategorieSpectacle',
|
field=models.ForeignKey(
|
||||||
null=True),
|
blank=True,
|
||||||
|
to="bda.CategorieSpectacle",
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='vips',
|
name="vips",
|
||||||
field=models.TextField(verbose_name='Personnalit\xe9s',
|
field=models.TextField(verbose_name="Personnalit\xe9s", blank=True),
|
||||||
blank=True),
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='quote',
|
model_name="quote",
|
||||||
name='spectacle',
|
name="spectacle",
|
||||||
field=models.ForeignKey(to='bda.Spectacle'),
|
field=models.ForeignKey(to="bda.Spectacle", on_delete=models.CASCADE),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,103 +1,110 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0007_extends_spectacle")]
|
||||||
('bda', '0007_extends_spectacle'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='choixspectacle',
|
model_name="choixspectacle",
|
||||||
name='double_choice',
|
name="double_choice",
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
verbose_name='Nombre de places',
|
verbose_name="Nombre de places",
|
||||||
choices=[('1', '1 place'),
|
choices=[
|
||||||
('autoquit', '2 places si possible, 1 sinon'),
|
("1", "1 place"),
|
||||||
('double', '2 places sinon rien')],
|
("autoquit", "2 places si possible, 1 sinon"),
|
||||||
max_length=10, default='1'),
|
("double", "2 places sinon rien"),
|
||||||
|
],
|
||||||
|
max_length=10,
|
||||||
|
default="1",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='participant',
|
model_name="participant",
|
||||||
name='paymenttype',
|
name="paymenttype",
|
||||||
field=models.CharField(
|
field=models.CharField(
|
||||||
blank=True,
|
blank=True,
|
||||||
choices=[('cash', 'Cash'), ('cb', 'CB'),
|
choices=[
|
||||||
('cheque', 'Chèque'), ('autre', 'Autre')],
|
("cash", "Cash"),
|
||||||
max_length=6, verbose_name='Moyen de paiement'),
|
("cb", "CB"),
|
||||||
|
("cheque", "Chèque"),
|
||||||
|
("autre", "Autre"),
|
||||||
|
],
|
||||||
|
max_length=6,
|
||||||
|
verbose_name="Moyen de paiement",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='salle',
|
model_name="salle",
|
||||||
name='address',
|
name="address",
|
||||||
field=models.TextField(verbose_name='Adresse'),
|
field=models.TextField(verbose_name="Adresse"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='salle',
|
model_name="salle",
|
||||||
name='name',
|
name="name",
|
||||||
field=models.CharField(verbose_name='Nom', max_length=300),
|
field=models.CharField(verbose_name="Nom", max_length=300),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='date',
|
name="date",
|
||||||
field=models.DateTimeField(verbose_name='Date & heure'),
|
field=models.DateTimeField(verbose_name="Date & heure"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='description',
|
name="description",
|
||||||
field=models.TextField(verbose_name='Description', blank=True),
|
field=models.TextField(verbose_name="Description", blank=True),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='listing',
|
name="listing",
|
||||||
field=models.BooleanField(
|
field=models.BooleanField(verbose_name="Les places sont sur listing"),
|
||||||
verbose_name='Les places sont sur listing'),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='price',
|
name="price",
|
||||||
field=models.FloatField(verbose_name="Prix d'une place"),
|
field=models.FloatField(verbose_name="Prix d'une place"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='slots',
|
name="slots",
|
||||||
field=models.IntegerField(verbose_name='Places'),
|
field=models.IntegerField(verbose_name="Places"),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='slots_description',
|
name="slots_description",
|
||||||
field=models.TextField(verbose_name='Description des places',
|
field=models.TextField(verbose_name="Description des places", blank=True),
|
||||||
blank=True),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='spectacle',
|
model_name="spectacle",
|
||||||
name='title',
|
name="title",
|
||||||
field=models.CharField(verbose_name='Titre', max_length=300),
|
field=models.CharField(verbose_name="Titre", max_length=300),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='active',
|
name="active",
|
||||||
field=models.BooleanField(verbose_name='Tirage actif',
|
field=models.BooleanField(verbose_name="Tirage actif", default=False),
|
||||||
default=False),
|
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='fermeture',
|
name="fermeture",
|
||||||
field=models.DateTimeField(
|
field=models.DateTimeField(
|
||||||
verbose_name='Date et heure de fermerture du tirage'),
|
verbose_name="Date et heure de fermerture du tirage"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='ouverture',
|
name="ouverture",
|
||||||
field=models.DateTimeField(
|
field=models.DateTimeField(
|
||||||
verbose_name="Date et heure d'ouverture du tirage"),
|
verbose_name="Date et heure d'ouverture du tirage"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='title',
|
name="title",
|
||||||
field=models.CharField(verbose_name='Titre', max_length=300),
|
field=models.CharField(verbose_name="Titre", max_length=300),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,66 +1,87 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0008_py3")]
|
||||||
('bda', '0008_py3'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='SpectacleRevente',
|
name="SpectacleRevente",
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(serialize=False, primary_key=True,
|
(
|
||||||
auto_created=True, verbose_name='ID')),
|
"id",
|
||||||
('date', models.DateTimeField(
|
models.AutoField(
|
||||||
verbose_name='Date de mise en vente',
|
serialize=False,
|
||||||
default=django.utils.timezone.now)),
|
primary_key=True,
|
||||||
('notif_sent', models.BooleanField(
|
auto_created=True,
|
||||||
verbose_name='Notification envoyée', default=False)),
|
verbose_name="ID",
|
||||||
('tirage_done', models.BooleanField(
|
),
|
||||||
verbose_name='Tirage effectué', default=False)),
|
),
|
||||||
|
(
|
||||||
|
"date",
|
||||||
|
models.DateTimeField(
|
||||||
|
verbose_name="Date de mise en vente",
|
||||||
|
default=django.utils.timezone.now,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"notif_sent",
|
||||||
|
models.BooleanField(
|
||||||
|
verbose_name="Notification envoyée", default=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tirage_done",
|
||||||
|
models.BooleanField(verbose_name="Tirage effectué", default=False),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
options={
|
options={"verbose_name": "Revente"},
|
||||||
'verbose_name': 'Revente',
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='participant',
|
model_name="participant",
|
||||||
name='choicesrevente',
|
name="choicesrevente",
|
||||||
field=models.ManyToManyField(to='bda.Spectacle',
|
field=models.ManyToManyField(
|
||||||
related_name='subscribed',
|
to="bda.Spectacle", related_name="subscribed", blank=True
|
||||||
blank=True),
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectaclerevente',
|
model_name="spectaclerevente",
|
||||||
name='answered_mail',
|
name="answered_mail",
|
||||||
field=models.ManyToManyField(to='bda.Participant',
|
field=models.ManyToManyField(
|
||||||
related_name='wanted',
|
to="bda.Participant", related_name="wanted", blank=True
|
||||||
blank=True),
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectaclerevente',
|
model_name="spectaclerevente",
|
||||||
name='attribution',
|
name="attribution",
|
||||||
field=models.OneToOneField(to='bda.Attribution',
|
field=models.OneToOneField(
|
||||||
related_name='revente'),
|
to="bda.Attribution", on_delete=models.CASCADE, related_name="revente"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectaclerevente',
|
model_name="spectaclerevente",
|
||||||
name='seller',
|
name="seller",
|
||||||
field=models.ForeignKey(to='bda.Participant',
|
field=models.ForeignKey(
|
||||||
verbose_name='Vendeur',
|
to="bda.Participant",
|
||||||
related_name='original_shows'),
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name="Vendeur",
|
||||||
|
related_name="original_shows",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectaclerevente',
|
model_name="spectaclerevente",
|
||||||
name='soldTo',
|
name="soldTo",
|
||||||
field=models.ForeignKey(to='bda.Participant',
|
field=models.ForeignKey(
|
||||||
verbose_name='Vendue à', null=True,
|
to="bda.Participant",
|
||||||
blank=True),
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name="Vendue à",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,33 +1,35 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
from django.utils import timezone
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
def forwards_func(apps, schema_editor):
|
def forwards_func(apps, schema_editor):
|
||||||
SpectacleRevente = apps.get_model("bda", "SpectacleRevente")
|
SpectacleRevente = apps.get_model("bda", "SpectacleRevente")
|
||||||
|
|
||||||
for revente in SpectacleRevente.objects.all():
|
for revente in SpectacleRevente.objects.all():
|
||||||
is_expired = timezone.now() > revente.date_tirage()
|
is_expired = timezone.now() > revente.date_tirage()
|
||||||
is_direct = (revente.attribution.spectacle.date >= revente.date and
|
is_direct = revente.attribution.spectacle.date >= revente.date and timezone.now() > revente.date + timedelta(
|
||||||
timezone.now() > revente.date + timedelta(minutes=15))
|
minutes=15
|
||||||
|
)
|
||||||
revente.shotgun = is_expired or is_direct
|
revente.shotgun = is_expired or is_direct
|
||||||
revente.save()
|
revente.save()
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0009_revente")]
|
||||||
('bda', '0009_revente'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='spectaclerevente',
|
model_name="spectaclerevente",
|
||||||
name='shotgun',
|
name="shotgun",
|
||||||
field=models.BooleanField(default=False, verbose_name='Disponible imm\xe9diatement'),
|
field=models.BooleanField(
|
||||||
|
default=False, verbose_name="Disponible imm\xe9diatement"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,17 +6,14 @@ from django.db import migrations, models
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [("bda", "0010_spectaclerevente_shotgun")]
|
||||||
('bda', '0010_spectaclerevente_shotgun'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='tirage',
|
model_name="tirage",
|
||||||
name='appear_catalogue',
|
name="appear_catalogue",
|
||||||
field=models.BooleanField(
|
field=models.BooleanField(
|
||||||
default=False,
|
default=False, verbose_name="Tirage à afficher dans le catalogue"
|
||||||
verbose_name='Tirage à afficher dans le catalogue'
|
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
]
|
]
|
||||||
|
|
31
bda/migrations/0012_notif_time.py
Normal file
31
bda/migrations/0012_notif_time.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [("bda", "0011_tirage_appear_catalogue")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="spectaclerevente",
|
||||||
|
old_name="answered_mail",
|
||||||
|
new_name="confirmed_entry",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="spectaclerevente",
|
||||||
|
name="confirmed_entry",
|
||||||
|
field=models.ManyToManyField(
|
||||||
|
blank=True, related_name="entered", to="bda.Participant"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="spectaclerevente",
|
||||||
|
name="notif_time",
|
||||||
|
field=models.DateTimeField(
|
||||||
|
blank=True, verbose_name="Moment d'envoi de la notification", null=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
51
bda/migrations/0012_swap_double_choice.py
Normal file
51
bda/migrations/0012_swap_double_choice.py
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def swap_double_choice(apps, schema_editor):
|
||||||
|
choices = apps.get_model("bda", "ChoixSpectacle").objects
|
||||||
|
|
||||||
|
choices.filter(double_choice="double").update(double_choice="tmp")
|
||||||
|
choices.filter(double_choice="autoquit").update(double_choice="double")
|
||||||
|
choices.filter(double_choice="tmp").update(double_choice="autoquit")
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [("bda", "0011_tirage_appear_catalogue")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
# Temporarily allow an extra "tmp" value for the `double_choice` field
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="choixspectacle",
|
||||||
|
name="double_choice",
|
||||||
|
field=models.CharField(
|
||||||
|
verbose_name="Nombre de places",
|
||||||
|
max_length=10,
|
||||||
|
default="1",
|
||||||
|
choices=[
|
||||||
|
("tmp", "tmp"),
|
||||||
|
("1", "1 place"),
|
||||||
|
("double", "2 places si possible, 1 sinon"),
|
||||||
|
("autoquit", "2 places sinon rien"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(swap_double_choice, migrations.RunPython.noop),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="choixspectacle",
|
||||||
|
name="double_choice",
|
||||||
|
field=models.CharField(
|
||||||
|
verbose_name="Nombre de places",
|
||||||
|
max_length=10,
|
||||||
|
default="1",
|
||||||
|
choices=[
|
||||||
|
("1", "1 place"),
|
||||||
|
("double", "2 places si possible, 1 sinon"),
|
||||||
|
("autoquit", "2 places sinon rien"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
12
bda/migrations/0013_merge_20180524_2123.py
Normal file
12
bda/migrations/0013_merge_20180524_2123.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.13 on 2018-05-24 19:23
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [("bda", "0012_notif_time"), ("bda", "0012_swap_double_choice")]
|
||||||
|
|
||||||
|
operations = []
|
392
bda/models.py
392
bda/models.py
|
@ -1,22 +1,22 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import calendar
|
import calendar
|
||||||
import random
|
import random
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from custommail.shortcuts import send_mass_custom_mail
|
|
||||||
|
|
||||||
|
from custommail.models import CustomMail
|
||||||
|
from custommail.shortcuts import send_mass_custom_mail
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
|
from django.core import mail
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from django.contrib.auth.models import User
|
from django.utils import formats, timezone
|
||||||
from django.conf import settings
|
|
||||||
from django.utils import timezone, formats
|
|
||||||
|
|
||||||
|
|
||||||
def get_generic_user():
|
def get_generic_user():
|
||||||
generic, _ = User.objects.get_or_create(
|
generic, _ = User.objects.get_or_create(
|
||||||
username="bda_generic",
|
username="bda_generic",
|
||||||
defaults={"email": "bda@ens.fr", "first_name": "Bureau des arts"}
|
defaults={"email": "bda@ens.fr", "first_name": "Bureau des arts"},
|
||||||
)
|
)
|
||||||
return generic
|
return generic
|
||||||
|
|
||||||
|
@ -28,15 +28,15 @@ class Tirage(models.Model):
|
||||||
tokens = models.TextField("Graine(s) du tirage", blank=True)
|
tokens = models.TextField("Graine(s) du tirage", blank=True)
|
||||||
active = models.BooleanField("Tirage actif", default=False)
|
active = models.BooleanField("Tirage actif", default=False)
|
||||||
appear_catalogue = models.BooleanField(
|
appear_catalogue = models.BooleanField(
|
||||||
"Tirage à afficher dans le catalogue",
|
"Tirage à afficher dans le catalogue", default=False
|
||||||
default=False
|
|
||||||
)
|
)
|
||||||
enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
|
enable_do_tirage = models.BooleanField("Le tirage peut être lancé", default=False)
|
||||||
default=False)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s - %s" % (self.title, formats.localize(
|
return "%s - %s" % (
|
||||||
timezone.template_localtime(self.fermeture)))
|
self.title,
|
||||||
|
formats.localize(timezone.template_localtime(self.fermeture)),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Salle(models.Model):
|
class Salle(models.Model):
|
||||||
|
@ -48,7 +48,7 @@ class Salle(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class CategorieSpectacle(models.Model):
|
class CategorieSpectacle(models.Model):
|
||||||
name = models.CharField('Nom', max_length=100, unique=True)
|
name = models.CharField("Nom", max_length=100, unique=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -59,26 +59,27 @@ class CategorieSpectacle(models.Model):
|
||||||
|
|
||||||
class Spectacle(models.Model):
|
class Spectacle(models.Model):
|
||||||
title = models.CharField("Titre", max_length=300)
|
title = models.CharField("Titre", max_length=300)
|
||||||
category = models.ForeignKey(CategorieSpectacle, blank=True, null=True)
|
category = models.ForeignKey(
|
||||||
|
CategorieSpectacle, on_delete=models.CASCADE, blank=True, null=True
|
||||||
|
)
|
||||||
date = models.DateTimeField("Date & heure")
|
date = models.DateTimeField("Date & heure")
|
||||||
location = models.ForeignKey(Salle)
|
location = models.ForeignKey(Salle, on_delete=models.CASCADE)
|
||||||
vips = models.TextField('Personnalités', blank=True)
|
vips = models.TextField("Personnalités", blank=True)
|
||||||
description = models.TextField("Description", blank=True)
|
description = models.TextField("Description", blank=True)
|
||||||
slots_description = models.TextField("Description des places", blank=True)
|
slots_description = models.TextField("Description des places", blank=True)
|
||||||
image = models.ImageField('Image', blank=True, null=True,
|
image = models.ImageField("Image", blank=True, null=True, upload_to="imgs/shows/")
|
||||||
upload_to='imgs/shows/')
|
ext_link = models.CharField(
|
||||||
ext_link = models.CharField('Lien vers le site du spectacle', blank=True,
|
"Lien vers le site du spectacle", blank=True, max_length=500
|
||||||
max_length=500)
|
)
|
||||||
price = models.FloatField("Prix d'une place")
|
price = models.FloatField("Prix d'une place")
|
||||||
slots = models.IntegerField("Places")
|
slots = models.IntegerField("Places")
|
||||||
tirage = models.ForeignKey(Tirage)
|
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
|
||||||
listing = models.BooleanField("Les places sont sur listing")
|
listing = models.BooleanField("Les places sont sur listing")
|
||||||
rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True,
|
rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True, null=True)
|
||||||
null=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Spectacle"
|
verbose_name = "Spectacle"
|
||||||
ordering = ("date", "title",)
|
ordering = ("date", "title")
|
||||||
|
|
||||||
def timestamp(self):
|
def timestamp(self):
|
||||||
return "%d" % calendar.timegm(self.date.utctimetuple())
|
return "%d" % calendar.timegm(self.date.utctimetuple())
|
||||||
|
@ -88,7 +89,7 @@ class Spectacle(models.Model):
|
||||||
self.title,
|
self.title,
|
||||||
formats.localize(timezone.template_localtime(self.date)),
|
formats.localize(timezone.template_localtime(self.date)),
|
||||||
self.location,
|
self.location,
|
||||||
self.price
|
self.price,
|
||||||
)
|
)
|
||||||
|
|
||||||
def getImgUrl(self):
|
def getImgUrl(self):
|
||||||
|
@ -97,7 +98,7 @@ class Spectacle(models.Model):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self.image.url
|
return self.image.url
|
||||||
except:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def send_rappel(self):
|
def send_rappel(self):
|
||||||
|
@ -107,19 +108,21 @@ class Spectacle(models.Model):
|
||||||
"""
|
"""
|
||||||
# On récupère la liste des participants + le BdA
|
# On récupère la liste des participants + le BdA
|
||||||
members = list(
|
members = list(
|
||||||
User.objects
|
User.objects.filter(participant__attributions=self)
|
||||||
.filter(participant__attributions=self)
|
.annotate(nb_attr=Count("id"))
|
||||||
.annotate(nb_attr=Count("id")).order_by()
|
.order_by()
|
||||||
)
|
)
|
||||||
bda_generic = get_generic_user()
|
bda_generic = get_generic_user()
|
||||||
bda_generic.nb_attr = 1
|
bda_generic.nb_attr = 1
|
||||||
members.append(bda_generic)
|
members.append(bda_generic)
|
||||||
# On écrit un mail personnalisé à chaque participant
|
# On écrit un mail personnalisé à chaque participant
|
||||||
datatuple = [(
|
datatuple = [
|
||||||
'bda-rappel',
|
(
|
||||||
{'member': member, "nb_attr": member.nb_attr, 'show': self},
|
"bda-rappel",
|
||||||
settings.MAIL_DATA['rappels']['FROM'],
|
{"member": member, "nb_attr": member.nb_attr, "show": self},
|
||||||
[member.email])
|
settings.MAIL_DATA["rappels"]["FROM"],
|
||||||
|
[member.email],
|
||||||
|
)
|
||||||
for member in members
|
for member in members
|
||||||
]
|
]
|
||||||
send_mass_custom_mail(datatuple)
|
send_mass_custom_mail(datatuple)
|
||||||
|
@ -135,9 +138,9 @@ class Spectacle(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class Quote(models.Model):
|
class Quote(models.Model):
|
||||||
spectacle = models.ForeignKey(Spectacle)
|
spectacle = models.ForeignKey(Spectacle, on_delete=models.CASCADE)
|
||||||
text = models.TextField('Citation')
|
text = models.TextField("Citation")
|
||||||
author = models.CharField('Auteur', max_length=200)
|
author = models.CharField("Auteur", max_length=200)
|
||||||
|
|
||||||
|
|
||||||
PAYMENT_TYPES = (
|
PAYMENT_TYPES = (
|
||||||
|
@ -149,129 +152,200 @@ PAYMENT_TYPES = (
|
||||||
|
|
||||||
|
|
||||||
class Participant(models.Model):
|
class Participant(models.Model):
|
||||||
user = models.ForeignKey(User)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
choices = models.ManyToManyField(Spectacle,
|
choices = models.ManyToManyField(
|
||||||
through="ChoixSpectacle",
|
Spectacle, through="ChoixSpectacle", related_name="chosen_by"
|
||||||
related_name="chosen_by")
|
)
|
||||||
attributions = models.ManyToManyField(Spectacle,
|
attributions = models.ManyToManyField(
|
||||||
through="Attribution",
|
Spectacle, through="Attribution", related_name="attributed_to"
|
||||||
related_name="attributed_to")
|
)
|
||||||
paid = models.BooleanField("A payé", default=False)
|
paid = models.BooleanField("A payé", default=False)
|
||||||
paymenttype = models.CharField("Moyen de paiement",
|
paymenttype = models.CharField(
|
||||||
max_length=6, choices=PAYMENT_TYPES,
|
"Moyen de paiement", max_length=6, choices=PAYMENT_TYPES, blank=True
|
||||||
blank=True)
|
)
|
||||||
tirage = models.ForeignKey(Tirage)
|
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
|
||||||
choicesrevente = models.ManyToManyField(Spectacle,
|
choicesrevente = models.ManyToManyField(
|
||||||
related_name="subscribed",
|
Spectacle, related_name="subscribed", blank=True
|
||||||
blank=True)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s - %s" % (self.user, self.tirage.title)
|
return "%s - %s" % (self.user, self.tirage.title)
|
||||||
|
|
||||||
|
|
||||||
DOUBLE_CHOICES = (
|
DOUBLE_CHOICES = (
|
||||||
("1", "1 place"),
|
("1", "1 place"),
|
||||||
("autoquit", "2 places si possible, 1 sinon"),
|
("double", "2 places si possible, 1 sinon"),
|
||||||
("double", "2 places sinon rien"),
|
("autoquit", "2 places sinon rien"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ChoixSpectacle(models.Model):
|
class ChoixSpectacle(models.Model):
|
||||||
participant = models.ForeignKey(Participant)
|
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
|
||||||
spectacle = models.ForeignKey(Spectacle, related_name="participants")
|
spectacle = models.ForeignKey(
|
||||||
|
Spectacle, on_delete=models.CASCADE, related_name="participants"
|
||||||
|
)
|
||||||
priority = models.PositiveIntegerField("Priorité")
|
priority = models.PositiveIntegerField("Priorité")
|
||||||
double_choice = models.CharField("Nombre de places",
|
double_choice = models.CharField(
|
||||||
default="1", choices=DOUBLE_CHOICES,
|
"Nombre de places", default="1", choices=DOUBLE_CHOICES, max_length=10
|
||||||
max_length=10)
|
)
|
||||||
|
|
||||||
def get_double(self):
|
def get_double(self):
|
||||||
return self.double_choice != "1"
|
return self.double_choice != "1"
|
||||||
|
|
||||||
double = property(get_double)
|
double = property(get_double)
|
||||||
|
|
||||||
def get_autoquit(self):
|
def get_autoquit(self):
|
||||||
return self.double_choice == "autoquit"
|
return self.double_choice == "autoquit"
|
||||||
|
|
||||||
autoquit = property(get_autoquit)
|
autoquit = property(get_autoquit)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Vœux de %s pour %s" % (
|
return "Vœux de %s pour %s" % (
|
||||||
self.participant.user.get_full_name(),
|
self.participant.user.get_full_name(),
|
||||||
self.spectacle.title)
|
self.spectacle.title,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ("priority",)
|
ordering = ("priority",)
|
||||||
unique_together = (("participant", "spectacle",),)
|
unique_together = (("participant", "spectacle"),)
|
||||||
verbose_name = "voeu"
|
verbose_name = "voeu"
|
||||||
verbose_name_plural = "voeux"
|
verbose_name_plural = "voeux"
|
||||||
|
|
||||||
|
|
||||||
class Attribution(models.Model):
|
class Attribution(models.Model):
|
||||||
participant = models.ForeignKey(Participant)
|
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
|
||||||
spectacle = models.ForeignKey(Spectacle, related_name="attribues")
|
spectacle = models.ForeignKey(
|
||||||
|
Spectacle, on_delete=models.CASCADE, related_name="attribues"
|
||||||
|
)
|
||||||
given = models.BooleanField("Donnée", default=False)
|
given = models.BooleanField("Donnée", default=False)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s -- %s, %s" % (self.participant.user, self.spectacle.title,
|
return "%s -- %s, %s" % (
|
||||||
self.spectacle.date)
|
self.participant.user,
|
||||||
|
self.spectacle.title,
|
||||||
|
self.spectacle.date,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SpectacleRevente(models.Model):
|
class SpectacleRevente(models.Model):
|
||||||
attribution = models.OneToOneField(Attribution,
|
attribution = models.OneToOneField(
|
||||||
related_name="revente")
|
Attribution, on_delete=models.CASCADE, related_name="revente"
|
||||||
date = models.DateTimeField("Date de mise en vente",
|
)
|
||||||
default=timezone.now)
|
date = models.DateTimeField("Date de mise en vente", default=timezone.now)
|
||||||
answered_mail = models.ManyToManyField(Participant,
|
confirmed_entry = models.ManyToManyField(
|
||||||
related_name="wanted",
|
Participant, related_name="entered", blank=True
|
||||||
blank=True)
|
)
|
||||||
seller = models.ForeignKey(Participant,
|
seller = models.ForeignKey(
|
||||||
related_name="original_shows",
|
Participant,
|
||||||
verbose_name="Vendeur")
|
on_delete=models.CASCADE,
|
||||||
soldTo = models.ForeignKey(Participant, blank=True, null=True,
|
verbose_name="Vendeur",
|
||||||
verbose_name="Vendue à")
|
related_name="original_shows",
|
||||||
|
)
|
||||||
|
soldTo = models.ForeignKey(
|
||||||
|
Participant,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
verbose_name="Vendue à",
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
)
|
||||||
|
|
||||||
notif_sent = models.BooleanField("Notification envoyée",
|
notif_sent = models.BooleanField("Notification envoyée", default=False)
|
||||||
default=False)
|
|
||||||
tirage_done = models.BooleanField("Tirage effectué",
|
notif_time = models.DateTimeField(
|
||||||
default=False)
|
"Moment d'envoi de la notification", blank=True, null=True
|
||||||
shotgun = models.BooleanField("Disponible immédiatement",
|
)
|
||||||
default=False)
|
|
||||||
|
tirage_done = models.BooleanField("Tirage effectué", default=False)
|
||||||
|
|
||||||
|
shotgun = models.BooleanField("Disponible immédiatement", default=False)
|
||||||
|
####
|
||||||
|
# Some class attributes
|
||||||
|
###
|
||||||
|
# TODO : settings ?
|
||||||
|
|
||||||
|
# Temps minimum entre le tirage et le spectacle
|
||||||
|
min_margin = timedelta(days=5)
|
||||||
|
|
||||||
|
# Temps entre la création d'une revente et l'envoi du mail
|
||||||
|
remorse_time = timedelta(hours=1)
|
||||||
|
|
||||||
|
# Temps min/max d'attente avant le tirage
|
||||||
|
max_wait_time = timedelta(days=3)
|
||||||
|
min_wait_time = timedelta(days=1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def real_notif_time(self):
|
||||||
|
if self.notif_time:
|
||||||
|
return self.notif_time
|
||||||
|
else:
|
||||||
|
return self.date + self.remorse_time
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def date_tirage(self):
|
def date_tirage(self):
|
||||||
"""Renvoie la date du tirage au sort de la revente."""
|
"""Renvoie la date du tirage au sort de la revente."""
|
||||||
# L'acheteur doit être connu au plus 12h avant le spectacle
|
|
||||||
remaining_time = (self.attribution.spectacle.date
|
remaining_time = (
|
||||||
- self.date - timedelta(hours=13))
|
self.attribution.spectacle.date - self.real_notif_time - self.min_margin
|
||||||
# Au minimum, on attend 2 jours avant le tirage
|
)
|
||||||
delay = min(remaining_time, timedelta(days=2))
|
|
||||||
# Le vendeur a aussi 1h pour changer d'avis
|
delay = min(remaining_time, self.max_wait_time)
|
||||||
return self.date + delay + timedelta(hours=1)
|
|
||||||
|
return self.real_notif_time + delay
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_urgent(self):
|
||||||
|
"""
|
||||||
|
Renvoie True iff la revente doit être mise au shotgun directement.
|
||||||
|
Plus précisément, on doit avoir min_margin + min_wait_time de marge.
|
||||||
|
"""
|
||||||
|
spectacle_date = self.attribution.spectacle.date
|
||||||
|
return spectacle_date <= timezone.now() + self.min_margin + self.min_wait_time
|
||||||
|
|
||||||
|
@property
|
||||||
|
def can_notif(self):
|
||||||
|
return timezone.now() >= self.date + self.remorse_time
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s -- %s" % (self.seller,
|
return "%s -- %s" % (self.seller, self.attribution.spectacle.title)
|
||||||
self.attribution.spectacle.title)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Revente"
|
verbose_name = "Revente"
|
||||||
|
|
||||||
|
def reset(self, new_date=timezone.now()):
|
||||||
|
"""Réinitialise la revente pour permettre une remise sur le marché"""
|
||||||
|
self.seller = self.attribution.participant
|
||||||
|
self.date = new_date
|
||||||
|
self.confirmed_entry.clear()
|
||||||
|
self.soldTo = None
|
||||||
|
self.notif_sent = False
|
||||||
|
self.notif_time = None
|
||||||
|
self.tirage_done = False
|
||||||
|
self.shotgun = False
|
||||||
|
self.save()
|
||||||
|
|
||||||
def send_notif(self):
|
def send_notif(self):
|
||||||
"""
|
"""
|
||||||
Envoie une notification pour indiquer la mise en vente d'une place sur
|
Envoie une notification pour indiquer la mise en vente d'une place sur
|
||||||
BdA-Revente à tous les intéressés.
|
BdA-Revente à tous les intéressés.
|
||||||
"""
|
"""
|
||||||
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
inscrits = self.attribution.spectacle.subscribed.select_related("user")
|
||||||
datatuple = [(
|
datatuple = [
|
||||||
'bda-revente',
|
(
|
||||||
{
|
"bda-revente",
|
||||||
'member': participant.user,
|
{
|
||||||
'show': self.attribution.spectacle,
|
"member": participant.user,
|
||||||
'revente': self,
|
"show": self.attribution.spectacle,
|
||||||
'site': Site.objects.get_current()
|
"revente": self,
|
||||||
},
|
"site": Site.objects.get_current(),
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
},
|
||||||
[participant.user.email])
|
settings.MAIL_DATA["revente"]["FROM"],
|
||||||
|
[participant.user.email],
|
||||||
|
)
|
||||||
for participant in inscrits
|
for participant in inscrits
|
||||||
]
|
]
|
||||||
send_mass_custom_mail(datatuple)
|
send_mass_custom_mail(datatuple)
|
||||||
self.notif_sent = True
|
self.notif_sent = True
|
||||||
|
self.notif_time = timezone.now()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def mail_shotgun(self):
|
def mail_shotgun(self):
|
||||||
|
@ -279,72 +353,98 @@ class SpectacleRevente(models.Model):
|
||||||
Envoie un mail à toutes les personnes intéréssées par le spectacle pour
|
Envoie un mail à toutes les personnes intéréssées par le spectacle pour
|
||||||
leur indiquer qu'il est désormais disponible au shotgun.
|
leur indiquer qu'il est désormais disponible au shotgun.
|
||||||
"""
|
"""
|
||||||
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
inscrits = self.attribution.spectacle.subscribed.select_related("user")
|
||||||
datatuple = [(
|
datatuple = [
|
||||||
'bda-shotgun',
|
(
|
||||||
{
|
"bda-shotgun",
|
||||||
'member': participant.user,
|
{
|
||||||
'show': self.attribution.spectacle,
|
"member": participant.user,
|
||||||
'site': Site.objects.get_current(),
|
"show": self.attribution.spectacle,
|
||||||
},
|
"site": Site.objects.get_current(),
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
},
|
||||||
[participant.user.email])
|
settings.MAIL_DATA["revente"]["FROM"],
|
||||||
|
[participant.user.email],
|
||||||
|
)
|
||||||
for participant in inscrits
|
for participant in inscrits
|
||||||
]
|
]
|
||||||
send_mass_custom_mail(datatuple)
|
send_mass_custom_mail(datatuple)
|
||||||
self.notif_sent = True
|
self.notif_sent = True
|
||||||
|
self.notif_time = timezone.now()
|
||||||
# Flag inutile, sauf si l'horloge interne merde
|
# Flag inutile, sauf si l'horloge interne merde
|
||||||
self.tirage_done = True
|
self.tirage_done = True
|
||||||
self.shotgun = True
|
self.shotgun = True
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def tirage(self):
|
def tirage(self, send_mails=True):
|
||||||
"""
|
"""
|
||||||
Lance le tirage au sort associé à la revente. Un gagnant est choisi
|
Lance le tirage au sort associé à la revente. Un gagnant est choisi
|
||||||
parmis les personnes intéressées par le spectacle. Les personnes sont
|
parmis les personnes intéressées par le spectacle. Les personnes sont
|
||||||
ensuites prévenues par mail du résultat du tirage.
|
ensuites prévenues par mail du résultat du tirage.
|
||||||
"""
|
"""
|
||||||
inscrits = list(self.answered_mail.all())
|
inscrits = list(self.confirmed_entry.all())
|
||||||
spectacle = self.attribution.spectacle
|
spectacle = self.attribution.spectacle
|
||||||
seller = self.seller
|
seller = self.seller
|
||||||
|
winner = None
|
||||||
|
|
||||||
if inscrits:
|
if inscrits:
|
||||||
# Envoie un mail au gagnant et au vendeur
|
# Envoie un mail au gagnant et au vendeur
|
||||||
winner = random.choice(inscrits)
|
winner = random.choice(inscrits)
|
||||||
self.soldTo = winner
|
self.soldTo = winner
|
||||||
datatuple = []
|
if send_mails:
|
||||||
context = {
|
mails = []
|
||||||
'acheteur': winner.user,
|
|
||||||
'vendeur': seller.user,
|
|
||||||
'show': spectacle,
|
|
||||||
}
|
|
||||||
datatuple.append((
|
|
||||||
'bda-revente-winner',
|
|
||||||
context,
|
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
[winner.user.email],
|
|
||||||
))
|
|
||||||
datatuple.append((
|
|
||||||
'bda-revente-seller',
|
|
||||||
context,
|
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
|
||||||
[seller.user.email]
|
|
||||||
))
|
|
||||||
|
|
||||||
# Envoie un mail aux perdants
|
context = {
|
||||||
for inscrit in inscrits:
|
"acheteur": winner.user,
|
||||||
if inscrit != winner:
|
"vendeur": seller.user,
|
||||||
new_context = dict(context)
|
"show": spectacle,
|
||||||
new_context['acheteur'] = inscrit.user
|
}
|
||||||
datatuple.append((
|
|
||||||
'bda-revente-loser',
|
c_mails_qs = CustomMail.objects.filter(
|
||||||
new_context,
|
shortname__in=[
|
||||||
settings.MAIL_DATA['revente']['FROM'],
|
"bda-revente-winner",
|
||||||
[inscrit.user.email]
|
"bda-revente-loser",
|
||||||
))
|
"bda-revente-seller",
|
||||||
send_mass_custom_mail(datatuple)
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
c_mails = {cm.shortname: cm for cm in c_mails_qs}
|
||||||
|
|
||||||
|
mails.append(
|
||||||
|
c_mails["bda-revente-winner"].get_message(
|
||||||
|
context,
|
||||||
|
from_email=settings.MAIL_DATA["revente"]["FROM"],
|
||||||
|
to=[winner.user.email],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mails.append(
|
||||||
|
c_mails["bda-revente-seller"].get_message(
|
||||||
|
context,
|
||||||
|
from_email=settings.MAIL_DATA["revente"]["FROM"],
|
||||||
|
to=[seller.user.email],
|
||||||
|
reply_to=[winner.user.email],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Envoie un mail aux perdants
|
||||||
|
for inscrit in inscrits:
|
||||||
|
if inscrit != winner:
|
||||||
|
new_context = dict(context)
|
||||||
|
new_context["acheteur"] = inscrit.user
|
||||||
|
|
||||||
|
mails.append(
|
||||||
|
c_mails["bda-revente-loser"].get_message(
|
||||||
|
new_context,
|
||||||
|
from_email=settings.MAIL_DATA["revente"]["FROM"],
|
||||||
|
to=[inscrit.user.email],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mail_conn = mail.get_connection()
|
||||||
|
mail_conn.send_messages(mails)
|
||||||
# Si personne ne veut de la place, elle part au shotgun
|
# Si personne ne veut de la place, elle part au shotgun
|
||||||
else:
|
else:
|
||||||
self.shotgun = True
|
self.shotgun = True
|
||||||
self.tirage_done = True
|
self.tirage_done = True
|
||||||
self.save()
|
self.save()
|
||||||
|
return winner
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>Places disponibles immédiatement</h2>
|
|
||||||
{% if shotgun %}
|
|
||||||
<ul class="list-unstyled">
|
|
||||||
{% for spectacle in shotgun %}
|
|
||||||
<li><a href="{% url "bda-buy-revente" spectacle.id %}">{{spectacle}}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<p> Pas de places disponibles immédiatement, désolé !</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endblock %}
|
|
1
bda/templates/bda/forms/attribution_label_table.html
Normal file
1
bda/templates/bda/forms/attribution_label_table.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{% include 'bda/forms/spectacle_label_table.html' with spectacle=attribution.spectacle %}
|
4
bda/templates/bda/forms/checkbox_table.html
Normal file
4
bda/templates/bda/forms/checkbox_table.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<tr>
|
||||||
|
<td><input type="{{ widget.type }}" name="{{ widget.name }}" {% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}"{% endif %}{% include "django/forms/widgets/attrs.html" %}></td>
|
||||||
|
{{ widget.label }}
|
||||||
|
</tr>
|
1
bda/templates/bda/forms/date_tirage.html
Normal file
1
bda/templates/bda/forms/date_tirage.html
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<td data-sort-value="{{ revente.date_tirage | date:"U" }}">{{ revente.date_tirage }}</td>
|
3
bda/templates/bda/forms/revente_other_label_table.html
Normal file
3
bda/templates/bda/forms/revente_other_label_table.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{% include 'bda/forms/spectacle_label_table.html' with spectacle=revente.attribution.spectacle %}
|
||||||
|
{% with user=revente.seller.user %} <td>{{user.first_name}} {{user.last_name}}</td> {% endwith%}
|
||||||
|
{% include 'bda/forms/date_tirage.html' %}
|
2
bda/templates/bda/forms/revente_self_label_table.html
Normal file
2
bda/templates/bda/forms/revente_self_label_table.html
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
{% include 'bda/forms/spectacle_label_table.html' with spectacle=revente.attribution.spectacle %}
|
||||||
|
{% include 'bda/forms/date_tirage.html' %}
|
4
bda/templates/bda/forms/revente_sold_label_table.html
Normal file
4
bda/templates/bda/forms/revente_sold_label_table.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{% include 'bda/forms/spectacle_label_table.html' with spectacle=revente.attribution.spectacle %}
|
||||||
|
{% with user=revente.soldTo.user %}
|
||||||
|
<td><a href="mailto:{{ user.email }}">{{user.first_name}} {{user.last_name}}</a></td>
|
||||||
|
{% endwith %}
|
4
bda/templates/bda/forms/spectacle_label_table.html
Normal file
4
bda/templates/bda/forms/spectacle_label_table.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<td>{{ spectacle.title }}</td>
|
||||||
|
<td data-sort-value="{{ spectacle.timestamp }}">{{ spectacle.date }}</td>
|
||||||
|
<td data-sort-value="{{ spectacle.location }}">{{ spectacle.location }}</td>
|
||||||
|
<td data-sort-value="{{ spectacle.price |stringformat:".3f" }}">{{ spectacle.price |floatformat }}€</td>
|
|
@ -14,7 +14,7 @@
|
||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tbody class="bda_formset_content">
|
<tbody class="bda_formset_content">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<tr class="{% cycle row1,row2 %} dynamic-form {% if form.instance.pk %}has_original{% endif %}">
|
<tr class="{% cycle 'row1' 'row2' %} dynamic-form {% if form.instance.pk %}has_original{% endif %}">
|
||||||
{% for field in form.visible_fields %}
|
{% for field in form.visible_fields %}
|
||||||
{% if field.name != "DELETE" and field.name != "priority" %}
|
{% if field.name != "DELETE" and field.name != "priority" %}
|
||||||
<td class="bda-field-{{ field.name }}">
|
<td class="bda-field-{{ field.name }}">
|
||||||
|
|
|
@ -27,6 +27,14 @@ var django = {
|
||||||
var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
|
var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
|
||||||
$(this).attr('for', newFor);
|
$(this).attr('for', newFor);
|
||||||
});
|
});
|
||||||
|
// Cloning <select> element doesn't properly propagate the default
|
||||||
|
// selected <option>, so we set it manually.
|
||||||
|
newElement.find('select').each(function (index, select) {
|
||||||
|
var defaultValue = $(select).find('option[selected]').val();
|
||||||
|
if (typeof defaultValue !== 'undefined') {
|
||||||
|
$(select).val(defaultValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
total++;
|
total++;
|
||||||
$('#id_' + type + '-TOTAL_FORMS').val(total);
|
$('#id_' + type + '-TOTAL_FORMS').val(total);
|
||||||
$(selector).after(newElement);
|
$(selector).after(newElement);
|
||||||
|
@ -44,6 +52,11 @@ var django = {
|
||||||
} else {
|
} else {
|
||||||
deleteInput.attr("checked", true);
|
deleteInput.attr("checked", true);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Reset the default values
|
||||||
|
var selects = $(form).find("select");
|
||||||
|
$(selects[0]).val("");
|
||||||
|
$(selects[1]).val("1");
|
||||||
}
|
}
|
||||||
// callback
|
// callback
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load bootstrap %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
<h2>Inscriptions pour BdA-Revente</h2>
|
|
||||||
<form action="" class="form-horizontal" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class="form-group">
|
|
||||||
<h3>Spectacles</h3>
|
|
||||||
<br/>
|
|
||||||
<button type="button" class="btn btn-primary" onClick="select(true)">Tout sélectionner</button>
|
|
||||||
<button type="button" class="btn btn-primary" onClick="select(false)">Tout désélectionner</button>
|
|
||||||
|
|
||||||
<div class="multiple-checkbox">
|
|
||||||
<ul>
|
|
||||||
{% for checkbox in form.spectacles %}
|
|
||||||
<li>{{checkbox}}</li>
|
|
||||||
{%endfor%}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<input type="submit" class="btn btn-primary" value="S'inscrire pour les places sélectionnées">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<script language="JavaScript">
|
|
||||||
function select(check) {
|
|
||||||
checkboxes = document.getElementsByName("spectacles");
|
|
||||||
for(var i=0, n=checkboxes.length;i<n;i++) {
|
|
||||||
checkboxes[i].checked = check;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
|
@ -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>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<h4 class="bda-prix">Total à payer : {{ total|floatformat }}€</h4>
|
<h4 class="bda-prix">Total à payer : {{ total|floatformat }}€</h4>
|
||||||
<br/>
|
<br/>
|
||||||
<p>Ne manque pas un spectacle avec le
|
<p>Ne manque pas un spectacle avec le
|
||||||
<a href="{% url "gestioncof.views.calendar" %}">calendrier
|
<a href="{% url "calendar" %}">calendrier
|
||||||
automatique !</a></p>
|
automatique !</a></p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<h3>Vous n'avez aucune place :(</h3>
|
<h3>Vous n'avez aucune place :(</h3>
|
||||||
|
|
122
bda/templates/bda/revente/manage.html
Normal file
122
bda/templates/bda/revente/manage.html
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
|
||||||
|
<h2>Gestion des places que je revends</h2>
|
||||||
|
|
||||||
|
{% if resell_exists %}
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<h3>Places non revendues</h3>
|
||||||
|
<form class="form-horizontal" action="" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Cochez les places que vous souhaitez revendre, et validez. Vous aurez
|
||||||
|
ensuite 1h pour changer d'avis avant que la revente soit confirmée et
|
||||||
|
que les notifications soient envoyées aux intéressé·e·s.
|
||||||
|
</div>
|
||||||
|
{% csrf_token %}
|
||||||
|
<table class="table table-striped stupidtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th data-sort="string">Titre</th>
|
||||||
|
<th data-sort="string">Lieu</th>
|
||||||
|
<th data-sort="int">Date</th>
|
||||||
|
<th data-sort="int">Prix</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for checkbox in resellform.attributions %}{{ checkbox }}{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="form-actions">
|
||||||
|
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if annul_exists %}
|
||||||
|
<h3>Places en cours de revente</h3>
|
||||||
|
<form action="" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Vous pouvez annuler les reventes qui n'ont pas encore trouvé preneur·se.
|
||||||
|
</div>
|
||||||
|
{% csrf_token %}
|
||||||
|
<table class="table table-striped stupidtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th data-sort="string">Titre</th>
|
||||||
|
<th data-sort="int">Date</th>
|
||||||
|
<th data-sort="string">Lieu</th>
|
||||||
|
<th data-sort="int">Prix</th>
|
||||||
|
<th data-sort="int">Tirage le</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for checkbox in annulform.reventes %}{{ checkbox }}{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if sold_exists %}
|
||||||
|
<h3>Places revendues</h3>
|
||||||
|
<form action="" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Pour chaque revente, vous devez soit l'annuler soit la confirmer pour
|
||||||
|
transférer la place la place à la personne tirée au sort.
|
||||||
|
|
||||||
|
L'annulation sert par exemple à pouvoir remettre la place en jeu si
|
||||||
|
vous ne parvenez pas à entrer en contact avec la personne tirée au
|
||||||
|
sort.
|
||||||
|
</div>
|
||||||
|
{% csrf_token %}
|
||||||
|
<table class="table table-striped stupidtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th data-sort="string">Titre</th>
|
||||||
|
<th data-sort="int">Date</th>
|
||||||
|
<th data-sort="string">Lieu</th>
|
||||||
|
<th data-sort="int">Prix</th>
|
||||||
|
<th>Vendue à</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for checkbox in soldform.reventes %}{{ checkbox }}{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button type="submit" class="btn btn-primary" name="transfer">Transférer</button>
|
||||||
|
<button type="submit" class="btn btn-primary" name="reinit">Réinitialiser</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% if not resell_exists and not annul_exists and not sold_exists %}
|
||||||
|
<p>Plus de reventes possibles !</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}"></script>
|
||||||
|
<script language="JavaScript">
|
||||||
|
$(function(){
|
||||||
|
$("table.stupidtable").stupidtable();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("tr").click(function() {
|
||||||
|
$(this).find("input[type=checkbox]").click()
|
||||||
|
});
|
||||||
|
|
||||||
|
$("input[type=checkbox]").click(function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
29
bda/templates/bda/revente/shotgun.html
Normal file
29
bda/templates/bda/revente/shotgun.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
<h2>Places disponibles immédiatement</h2>
|
||||||
|
{% if spectacles %}
|
||||||
|
<table class="table table-striped stupidtable" id="bda-shotgun">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th data-sort="string">Titre</th>
|
||||||
|
<th data-sort="int">Date</th>
|
||||||
|
<th data-sort="string">Lieu</th>
|
||||||
|
<th data-sort="int">Prix</th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for spectacle in spectacles %}
|
||||||
|
<tr>
|
||||||
|
{% include "bda/forms/spectacle_label_table.html" with spectacle=spectacle %}
|
||||||
|
<td class="button"><a role="button" class="btn btn-primary" href="{% url 'bda-revente-buy' spectacle.id %}">Racheter</a>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p> Pas de places disponibles immédiatement, désolé !</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
65
bda/templates/bda/revente/subscribe.html
Normal file
65
bda/templates/bda/revente/subscribe.html
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
{% load staticfiles%}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
<h2>Inscriptions pour BdA-Revente</h2>
|
||||||
|
<form action="" class="form-horizontal" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Cochez les spectacles pour lesquels vous souhaitez recevoir une
|
||||||
|
notification quand une place est disponible en revente. <br />
|
||||||
|
Lorsque vous validez vos choix, si un tirage au sort est en cours pour
|
||||||
|
un des spectacles que vous avez sélectionné, vous serez automatiquement
|
||||||
|
inscrit à ce tirage.
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
onClick="select(true)">Tout sélectionner</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
onClick="select(false)">Tout désélectionner</button>
|
||||||
|
<table class="table table-striped stupidtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th data-sort="string">Titre</th>
|
||||||
|
<th data-sort="int">Date</th>
|
||||||
|
<th data-sort="string">Lieu</th>
|
||||||
|
<th data-sort="int">Prix</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for checkbox in form.spectacles %}{{ checkbox }}{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<input type="submit"
|
||||||
|
class="btn btn-primary"
|
||||||
|
value="S'inscrire pour les places sélectionnées">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}"></script>
|
||||||
|
<script language="JavaScript">
|
||||||
|
function select(check) {
|
||||||
|
checkboxes = document.getElementsByName("spectacles");
|
||||||
|
for(var i=0, n=checkboxes.length; i < n; i++) {
|
||||||
|
checkboxes[i].checked = check;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$(function(){
|
||||||
|
$("table.stupidtable").stupidtable();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("tr").click(function() {
|
||||||
|
$(this).find("input[type=checkbox]").click()
|
||||||
|
});
|
||||||
|
|
||||||
|
$("input[type=checkbox]").click(function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
100
bda/templates/bda/revente/tirages.html
Normal file
100
bda/templates/bda/revente/tirages.html
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
{% extends "base_title.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block realcontent %}
|
||||||
|
|
||||||
|
<h2>Tirages au sort de reventes</h2>
|
||||||
|
|
||||||
|
{% if annul_exists %}
|
||||||
|
<h3>Les reventes auxquelles vous êtes inscrit·e</h3>
|
||||||
|
<form class="form-horizontal" action="" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Voici la liste des reventes auxquelles vous êtes inscrit·e ; si vous ne souhaitez plus participer au tirage au sort vous pouvez vous en désister.
|
||||||
|
</div>
|
||||||
|
{% csrf_token %}
|
||||||
|
<table class="table table-striped stupidtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th data-sort="string">Titre</th>
|
||||||
|
<th data-sort="int">Date</th>
|
||||||
|
<th data-sort="string">Lieu</th>
|
||||||
|
<th data-sort="int">Prix</th>
|
||||||
|
<th>Vendue par</th>
|
||||||
|
<th data-sort="int">Tirage le</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for checkbox in annulform.reventes %}{{ checkbox }}{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="form-actions">
|
||||||
|
<input type="submit"
|
||||||
|
class="btn btn-primary"
|
||||||
|
name="annul"
|
||||||
|
value="Se désister des tirages sélectionnés">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
{% if sub_exists %}
|
||||||
|
|
||||||
|
<h3>Tirages en cours</h3>
|
||||||
|
<form class="form-horizontal" action="" method="post">
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Vous pouvez vous inscrire aux tirages en cours suivants.
|
||||||
|
</div>
|
||||||
|
{% csrf_token %}
|
||||||
|
<table class="table table-striped stupidtable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th data-sort="string">Titre</th>
|
||||||
|
<th data-sort="int">Date</th>
|
||||||
|
<th data-sort="string">Lieu</th>
|
||||||
|
<th data-sort="int">Prix</th>
|
||||||
|
<th>Vendue par</th>
|
||||||
|
<th data-sort="int">Tirage le</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for checkbox in subform.reventes %}{{ checkbox }}{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div class="form-actions">
|
||||||
|
<input type="submit"
|
||||||
|
class="btn btn-primary"
|
||||||
|
name="subscribe"
|
||||||
|
value="S'inscrire aux tirages sélectionnés">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if not annul_exists and not sub_exists %}
|
||||||
|
<div class="bg-info text-info center-block">
|
||||||
|
<span class="glyphicon glyphicon-info-sign" aria-hidden="true"></span>
|
||||||
|
Aucune revente n'est active pour le moment !
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<script type="text/javascript" src="{% static "js/joequery-Stupid-Table-Plugin/stupidtable.js" %}"></script>
|
||||||
|
<script language="JavaScript">
|
||||||
|
$(function(){
|
||||||
|
$("table.stupidtable").stupidtable();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("tr").click(function() {
|
||||||
|
$(this).find("input[type=checkbox]").click()
|
||||||
|
});
|
||||||
|
|
||||||
|
$("input[type=checkbox]").click(function(e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -6,7 +6,7 @@
|
||||||
<p>Le tirage au sort de cette revente a déjà été effectué !</p>
|
<p>Le tirage au sort de cette revente a déjà été effectué !</p>
|
||||||
|
|
||||||
<p>Si personne n'était intéressé, elle est maintenant disponible
|
<p>Si personne n'était intéressé, elle est maintenant disponible
|
||||||
<a href="{% url "bda-buy-revente" revente.attribution.spectacle.id %}">ici</a>.</p>
|
<a href="{% url "bda-revente-buy" revente.attribution.spectacle.id %}">ici</a>.</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p> Il n'est pas encore possible de s'inscrire à cette revente, réessaie dans quelque temps !</p>
|
<p> Il n'est pas encore possible de s'inscrire à cette revente, réessaie dans quelque temps !</p>
|
||||||
{% endif %}
|
{% endif %}
|
|
@ -1,56 +0,0 @@
|
||||||
{% extends "base_title.html" %}
|
|
||||||
{% load bootstrap %}
|
|
||||||
|
|
||||||
{% block realcontent %}
|
|
||||||
|
|
||||||
<h2>Revente de place</h2>
|
|
||||||
{% with resell_attributions=resellform.attributions annul_attributions=annulform.attributions sold_attributions=soldform.attributions %}
|
|
||||||
|
|
||||||
{% if resellform.attributions %}
|
|
||||||
<h3>Places non revendues</h3>
|
|
||||||
<form class="form-horizontal" action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{resellform|bootstrap}}
|
|
||||||
<div class="form-actions">
|
|
||||||
<input type="submit" class="btn btn-primary" name="resell" value="Revendre les places sélectionnées">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
|
||||||
{% if annul_attributions or overdue %}
|
|
||||||
<h3>Places en cours de revente</h3>
|
|
||||||
<form action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<div class='form-group'>
|
|
||||||
<div class='multiple-checkbox'>
|
|
||||||
<ul>
|
|
||||||
{% for attrib in annul_attributions %}
|
|
||||||
<li>{{attrib.tag}} {{attrib.choice_label}}</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% for attrib in overdue %}
|
|
||||||
<li>
|
|
||||||
<input type="checkbox" style="visibility:hidden">
|
|
||||||
{{attrib.spectacle}}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% if annul_attributions %}
|
|
||||||
<input type="submit" class="btn btn-primary" name="annul" value="Annuler les reventes sélectionnées">
|
|
||||||
{% endif %}
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
<br>
|
|
||||||
{% if sold_attributions %}
|
|
||||||
<h3>Places revendues</h3>
|
|
||||||
<form action="" method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
{{soldform|bootstrap}}
|
|
||||||
<button type="submit" class="btn btn-primary" name="transfer">Transférer</button>
|
|
||||||
<button type="submit" class="btn btn-primary" name="reinit">Réinitialiser</button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
{% if not resell_attributions and not annul_attributions and not overdue and not sold_attributions %}
|
|
||||||
<p>Plus de reventes possibles !</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% endwith %}
|
|
||||||
{% endblock %}
|
|
105
bda/tests.py
105
bda/tests.py
|
@ -1,105 +0,0 @@
|
||||||
import json
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.test import TestCase, Client
|
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from .models import Tirage, Spectacle, Salle, CategorieSpectacle
|
|
||||||
|
|
||||||
|
|
||||||
class TestBdAViews(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.tirage = Tirage.objects.create(
|
|
||||||
title="Test tirage",
|
|
||||||
appear_catalogue=True,
|
|
||||||
ouverture=timezone.now(),
|
|
||||||
fermeture=timezone.now(),
|
|
||||||
)
|
|
||||||
self.category = CategorieSpectacle.objects.create(name="Category")
|
|
||||||
self.location = Salle.objects.create(name="here")
|
|
||||||
Spectacle.objects.bulk_create([
|
|
||||||
Spectacle(
|
|
||||||
title="foo", date=timezone.now(), location=self.location,
|
|
||||||
price=0, slots=42, tirage=self.tirage, listing=False,
|
|
||||||
category=self.category
|
|
||||||
),
|
|
||||||
Spectacle(
|
|
||||||
title="bar", date=timezone.now(), location=self.location,
|
|
||||||
price=1, slots=142, tirage=self.tirage, listing=False,
|
|
||||||
category=self.category
|
|
||||||
),
|
|
||||||
Spectacle(
|
|
||||||
title="baz", date=timezone.now(), location=self.location,
|
|
||||||
price=2, slots=242, tirage=self.tirage, listing=False,
|
|
||||||
category=self.category
|
|
||||||
),
|
|
||||||
])
|
|
||||||
|
|
||||||
self.bda_user = User.objects.create_user(
|
|
||||||
username="bda_user", password="bda4ever"
|
|
||||||
)
|
|
||||||
self.bda_user.profile.is_cof = True
|
|
||||||
self.bda_user.profile.is_buro = True
|
|
||||||
self.bda_user.profile.save()
|
|
||||||
|
|
||||||
def bda_participants(self):
|
|
||||||
"""The BdA participants views can be queried"""
|
|
||||||
client = Client()
|
|
||||||
show = self.tirage.spectacle_set.first()
|
|
||||||
|
|
||||||
client.login(self.bda_user.username, "bda4ever")
|
|
||||||
tirage_resp = client.get("/bda/spectacles/{}".format(self.tirage.id))
|
|
||||||
show_resp = client.get(
|
|
||||||
"/bda/spectacles/{}/{}".format(self.tirage.id, show.id)
|
|
||||||
)
|
|
||||||
reminder_url = "/bda/mails-rappel/{}".format(show.id)
|
|
||||||
reminder_get_resp = client.get(reminder_url)
|
|
||||||
reminder_post_resp = client.post(reminder_url)
|
|
||||||
self.assertEqual(200, tirage_resp.status_code)
|
|
||||||
self.assertEqual(200, show_resp.status_code)
|
|
||||||
self.assertEqual(200, reminder_get_resp.status_code)
|
|
||||||
self.assertEqual(200, reminder_post_resp.status_code)
|
|
||||||
|
|
||||||
def test_catalogue(self):
|
|
||||||
"""Test the catalogue JSON API"""
|
|
||||||
client = Client()
|
|
||||||
|
|
||||||
# The `list` hook
|
|
||||||
resp = client.get("/bda/catalogue/list")
|
|
||||||
self.assertJSONEqual(
|
|
||||||
resp.content.decode("utf-8"),
|
|
||||||
[{"id": self.tirage.id, "title": self.tirage.title}]
|
|
||||||
)
|
|
||||||
|
|
||||||
# The `details` hook
|
|
||||||
resp = client.get(
|
|
||||||
"/bda/catalogue/details?id={}".format(self.tirage.id)
|
|
||||||
)
|
|
||||||
self.assertJSONEqual(
|
|
||||||
resp.content.decode("utf-8"),
|
|
||||||
{
|
|
||||||
"categories": [{
|
|
||||||
"id": self.category.id,
|
|
||||||
"name": self.category.name
|
|
||||||
}],
|
|
||||||
"locations": [{
|
|
||||||
"id": self.location.id,
|
|
||||||
"name": self.location.name
|
|
||||||
}],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# The `descriptions` hook
|
|
||||||
resp = client.get(
|
|
||||||
"/bda/catalogue/descriptions?id={}".format(self.tirage.id)
|
|
||||||
)
|
|
||||||
raw = resp.content.decode("utf-8")
|
|
||||||
try:
|
|
||||||
results = json.loads(raw)
|
|
||||||
except ValueError:
|
|
||||||
self.fail("Not valid JSON: {}".format(raw))
|
|
||||||
self.assertEqual(len(results), 3)
|
|
||||||
self.assertEqual(
|
|
||||||
{(s["title"], s["price"], s["slots"]) for s in results},
|
|
||||||
{("foo", 0, 42), ("bar", 1, 142), ("baz", 2, 242)}
|
|
||||||
)
|
|
0
bda/tests/__init__.py
Normal file
0
bda/tests/__init__.py
Normal file
102
bda/tests/test_models.py
Normal file
102
bda/tests/test_models.py
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.core import mail
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from bda.models import (
|
||||||
|
Attribution,
|
||||||
|
Participant,
|
||||||
|
Salle,
|
||||||
|
Spectacle,
|
||||||
|
SpectacleRevente,
|
||||||
|
Tirage,
|
||||||
|
)
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
|
class SpectacleReventeTests(TestCase):
|
||||||
|
fixtures = ["gestioncof/management/data/custommail.json"]
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
now = timezone.now()
|
||||||
|
|
||||||
|
self.t = Tirage.objects.create(
|
||||||
|
title="Tirage",
|
||||||
|
ouverture=now - timedelta(days=7),
|
||||||
|
fermeture=now - timedelta(days=3),
|
||||||
|
active=True,
|
||||||
|
)
|
||||||
|
self.s = Spectacle.objects.create(
|
||||||
|
title="Spectacle",
|
||||||
|
date=now + timedelta(days=20),
|
||||||
|
location=Salle.objects.create(name="Salle", address="Address"),
|
||||||
|
price=10.5,
|
||||||
|
slots=5,
|
||||||
|
tirage=self.t,
|
||||||
|
listing=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.seller = Participant.objects.create(
|
||||||
|
user=User.objects.create(username="seller", email="seller@mail.net"),
|
||||||
|
tirage=self.t,
|
||||||
|
)
|
||||||
|
self.p1 = Participant.objects.create(
|
||||||
|
user=User.objects.create(username="part1", email="part1@mail.net"),
|
||||||
|
tirage=self.t,
|
||||||
|
)
|
||||||
|
self.p2 = Participant.objects.create(
|
||||||
|
user=User.objects.create(username="part2", email="part2@mail.net"),
|
||||||
|
tirage=self.t,
|
||||||
|
)
|
||||||
|
self.p3 = Participant.objects.create(
|
||||||
|
user=User.objects.create(username="part3", email="part3@mail.net"),
|
||||||
|
tirage=self.t,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.attr = Attribution.objects.create(
|
||||||
|
participant=self.seller, spectacle=self.s
|
||||||
|
)
|
||||||
|
|
||||||
|
self.rev = SpectacleRevente.objects.create(
|
||||||
|
attribution=self.attr, seller=self.seller
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_tirage(self):
|
||||||
|
revente = self.rev
|
||||||
|
|
||||||
|
wanted_by = [self.p1, self.p2, self.p3]
|
||||||
|
revente.confirmed_entry = wanted_by
|
||||||
|
|
||||||
|
with mock.patch("bda.models.random.choice") as mc:
|
||||||
|
# Set winner to self.p1.
|
||||||
|
mc.return_value = self.p1
|
||||||
|
|
||||||
|
revente.tirage()
|
||||||
|
|
||||||
|
# Call to random.choice used participants in wanted_by.
|
||||||
|
mc_args, _ = mc.call_args
|
||||||
|
|
||||||
|
self.assertEqual(set(mc_args[0]), set(wanted_by))
|
||||||
|
|
||||||
|
self.assertEqual(revente.soldTo, self.p1)
|
||||||
|
self.assertTrue(revente.tirage_done)
|
||||||
|
|
||||||
|
mails = {m.to[0]: m for m in mail.outbox}
|
||||||
|
|
||||||
|
self.assertEqual(len(mails), 4)
|
||||||
|
|
||||||
|
m_seller = mails["seller@mail.net"]
|
||||||
|
self.assertListEqual(m_seller.to, ["seller@mail.net"])
|
||||||
|
self.assertListEqual(m_seller.reply_to, ["part1@mail.net"])
|
||||||
|
|
||||||
|
m_winner = mails["part1@mail.net"]
|
||||||
|
self.assertListEqual(m_winner.to, ["part1@mail.net"])
|
||||||
|
|
||||||
|
self.assertCountEqual(
|
||||||
|
[mails["part2@mail.net"].to, mails["part3@mail.net"].to],
|
||||||
|
[["part2@mail.net"], ["part3@mail.net"]],
|
||||||
|
)
|
79
bda/tests/test_revente.py
Normal file
79
bda/tests/test_revente.py
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from bda.models import (
|
||||||
|
Attribution,
|
||||||
|
CategorieSpectacle,
|
||||||
|
Participant,
|
||||||
|
Salle,
|
||||||
|
Spectacle,
|
||||||
|
SpectacleRevente,
|
||||||
|
Tirage,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestModels(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.tirage = Tirage.objects.create(
|
||||||
|
title="Tirage test",
|
||||||
|
appear_catalogue=True,
|
||||||
|
ouverture=timezone.now(),
|
||||||
|
fermeture=timezone.now(),
|
||||||
|
)
|
||||||
|
self.category = CategorieSpectacle.objects.create(name="Category")
|
||||||
|
self.location = Salle.objects.create(name="here")
|
||||||
|
self.spectacle_soon = Spectacle.objects.create(
|
||||||
|
title="foo",
|
||||||
|
date=timezone.now() + timedelta(days=1),
|
||||||
|
location=self.location,
|
||||||
|
price=0,
|
||||||
|
slots=42,
|
||||||
|
tirage=self.tirage,
|
||||||
|
listing=False,
|
||||||
|
category=self.category,
|
||||||
|
)
|
||||||
|
self.spectacle_later = Spectacle.objects.create(
|
||||||
|
title="bar",
|
||||||
|
date=timezone.now() + timedelta(days=30),
|
||||||
|
location=self.location,
|
||||||
|
price=0,
|
||||||
|
slots=42,
|
||||||
|
tirage=self.tirage,
|
||||||
|
listing=False,
|
||||||
|
category=self.category,
|
||||||
|
)
|
||||||
|
|
||||||
|
user_buyer = User.objects.create_user(
|
||||||
|
username="bda_buyer", password="testbuyer"
|
||||||
|
)
|
||||||
|
user_seller = User.objects.create_user(
|
||||||
|
username="bda_seller", password="testseller"
|
||||||
|
)
|
||||||
|
self.buyer = Participant.objects.create(user=user_buyer, tirage=self.tirage)
|
||||||
|
self.seller = Participant.objects.create(user=user_seller, tirage=self.tirage)
|
||||||
|
|
||||||
|
self.attr_soon = Attribution.objects.create(
|
||||||
|
participant=self.seller, spectacle=self.spectacle_soon
|
||||||
|
)
|
||||||
|
self.attr_later = Attribution.objects.create(
|
||||||
|
participant=self.seller, spectacle=self.spectacle_later
|
||||||
|
)
|
||||||
|
self.revente_soon = SpectacleRevente.objects.create(
|
||||||
|
seller=self.seller, attribution=self.attr_soon
|
||||||
|
)
|
||||||
|
self.revente_later = SpectacleRevente.objects.create(
|
||||||
|
seller=self.seller, attribution=self.attr_later
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_urgent(self):
|
||||||
|
self.assertTrue(self.revente_soon.is_urgent)
|
||||||
|
self.assertFalse(self.revente_later.is_urgent)
|
||||||
|
|
||||||
|
def test_tirage(self):
|
||||||
|
self.revente_soon.confirmed_entry.add(self.buyer)
|
||||||
|
|
||||||
|
self.assertEqual(self.revente_soon.tirage(send_mails=False), self.buyer)
|
||||||
|
self.assertIsNone(self.revente_later.tirage(send_mails=False))
|
380
bda/tests/test_views.py
Normal file
380
bda/tests/test_views.py
Normal file
|
@ -0,0 +1,380 @@
|
||||||
|
import json
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.utils import formats, timezone
|
||||||
|
|
||||||
|
from ..models import CategorieSpectacle, Participant, Salle
|
||||||
|
from .testcases import BdATestHelpers, BdAViewTestCaseMixin
|
||||||
|
from .utils import create_spectacle
|
||||||
|
|
||||||
|
|
||||||
|
class InscriptionViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||||
|
url_name = "bda-tirage-inscription"
|
||||||
|
|
||||||
|
http_methods = ["GET", "POST"]
|
||||||
|
|
||||||
|
auth_user = "bda_member"
|
||||||
|
auth_forbidden = [None, "bda_other"]
|
||||||
|
|
||||||
|
bda_testdata = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {"tirage_id": self.tirage.id}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return "/bda/inscription/{}".format(self.tirage.id)
|
||||||
|
|
||||||
|
def test_get_opened(self):
|
||||||
|
self.tirage.ouverture = timezone.now() - timedelta(days=1)
|
||||||
|
self.tirage.fermeture = timezone.now() + timedelta(days=1)
|
||||||
|
self.tirage.save()
|
||||||
|
|
||||||
|
resp = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertFalse(resp.context["messages"])
|
||||||
|
|
||||||
|
def test_get_closed_future(self):
|
||||||
|
self.tirage.ouverture = timezone.now() + timedelta(days=1)
|
||||||
|
self.tirage.fermeture = timezone.now() + timedelta(days=2)
|
||||||
|
self.tirage.save()
|
||||||
|
|
||||||
|
resp = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertIn(
|
||||||
|
"Le tirage n'est pas encore ouvert : ouverture le {}".format(
|
||||||
|
formats.localize(timezone.template_localtime(self.tirage.ouverture))
|
||||||
|
),
|
||||||
|
[str(msg) for msg in resp.context["messages"]],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_closed_past(self):
|
||||||
|
self.tirage.ouverture = timezone.now() - timedelta(days=2)
|
||||||
|
self.tirage.fermeture = timezone.now() - timedelta(days=1)
|
||||||
|
self.tirage.save()
|
||||||
|
|
||||||
|
resp = self.client.get(self.url)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertIn(
|
||||||
|
" C'est fini : tirage au sort dans la journée !",
|
||||||
|
[str(msg) for msg in resp.context["messages"]],
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_base_post_data(self):
|
||||||
|
return {
|
||||||
|
"choixspectacle_set-TOTAL_FORMS": "3",
|
||||||
|
"choixspectacle_set-INITIAL_FORMS": "0",
|
||||||
|
"choixspectacle_set-MIN_NUM_FORMS": "0",
|
||||||
|
"choixspectacle_set-MAX_NUM_FORMS": "1000",
|
||||||
|
}
|
||||||
|
|
||||||
|
base_post_data = property(get_base_post_data)
|
||||||
|
|
||||||
|
def test_post(self):
|
||||||
|
self.tirage.ouverture = timezone.now() - timedelta(days=1)
|
||||||
|
self.tirage.fermeture = timezone.now() + timedelta(days=1)
|
||||||
|
self.tirage.save()
|
||||||
|
|
||||||
|
data = dict(
|
||||||
|
self.base_post_data,
|
||||||
|
**{
|
||||||
|
"choixspectacle_set-TOTAL_FORMS": "2",
|
||||||
|
"choixspectacle_set-0-id": "",
|
||||||
|
"choixspectacle_set-0-participant": "",
|
||||||
|
"choixspectacle_set-0-spectacle": str(self.show1.pk),
|
||||||
|
"choixspectacle_set-0-double_choice": "1",
|
||||||
|
"choixspectacle_set-0-priority": "2",
|
||||||
|
"choixspectacle_set-1-id": "",
|
||||||
|
"choixspectacle_set-1-participant": "",
|
||||||
|
"choixspectacle_set-1-spectacle": str(self.show2.pk),
|
||||||
|
"choixspectacle_set-1-double_choice": "autoquit",
|
||||||
|
"choixspectacle_set-1-priority": "1",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
resp = self.client.post(self.url, data)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertIn(
|
||||||
|
"Votre inscription a été mise à jour avec succès !",
|
||||||
|
[str(msg) for msg in resp.context["messages"]],
|
||||||
|
)
|
||||||
|
participant = Participant.objects.get(
|
||||||
|
user=self.users["bda_member"], tirage=self.tirage
|
||||||
|
)
|
||||||
|
self.assertSetEqual(
|
||||||
|
set(
|
||||||
|
participant.choixspectacle_set.values_list(
|
||||||
|
"priority", "spectacle_id", "double_choice"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
{(1, self.show2.pk, "autoquit"), (2, self.show1.pk, "1")},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_post_state_changed(self):
|
||||||
|
self.tirage.ouverture = timezone.now() - timedelta(days=1)
|
||||||
|
self.tirage.fermeture = timezone.now() + timedelta(days=1)
|
||||||
|
self.tirage.save()
|
||||||
|
|
||||||
|
data = {"dbstate": "different"}
|
||||||
|
resp = self.client.post(self.url, data)
|
||||||
|
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertIn(
|
||||||
|
"Impossible d'enregistrer vos modifications : vous avez apporté d'autres "
|
||||||
|
"modifications entre temps.",
|
||||||
|
[str(msg) for msg in resp.context["messages"]],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PlacesViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||||
|
url_name = "bda-places-attribuees"
|
||||||
|
|
||||||
|
auth_user = "bda_member"
|
||||||
|
auth_forbidden = [None, "bda_other"]
|
||||||
|
|
||||||
|
bda_testdata = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {"tirage_id": self.tirage.id}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return "/bda/places/{}".format(self.tirage.id)
|
||||||
|
|
||||||
|
|
||||||
|
class EtatPlacesViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||||
|
url_name = "bda-etat-places"
|
||||||
|
|
||||||
|
auth_user = "bda_member"
|
||||||
|
auth_forbidden = [None, "bda_other"]
|
||||||
|
|
||||||
|
bda_testdata = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {"tirage_id": self.tirage.id}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return "/bda/etat-places/{}".format(self.tirage.id)
|
||||||
|
|
||||||
|
|
||||||
|
class TirageViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||||
|
url_name = "bda-tirage"
|
||||||
|
|
||||||
|
http_methods = ["GET", "POST"]
|
||||||
|
|
||||||
|
auth_user = "bda_staff"
|
||||||
|
auth_forbidden = [None, "bda_other", "bda_member"]
|
||||||
|
|
||||||
|
bda_testdata = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {"tirage_id": self.tirage.id}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return "/bda/tirage/{}".format(self.tirage.id)
|
||||||
|
|
||||||
|
def test_perform_tirage_disabled(self):
|
||||||
|
# Cannot be performed if disabled
|
||||||
|
self.tirage.enable_do_tirage = False
|
||||||
|
self.tirage.save()
|
||||||
|
resp = self.client.get(self.url)
|
||||||
|
self.assertTemplateUsed(resp, "tirage-failed.html")
|
||||||
|
|
||||||
|
def test_perform_tirage_opened_registrations(self):
|
||||||
|
# Cannot be performed if registrations are still open
|
||||||
|
self.tirage.enable_do_tirage = True
|
||||||
|
self.tirage.fermeture = timezone.now() + timedelta(seconds=3600)
|
||||||
|
self.tirage.save()
|
||||||
|
resp = self.client.get(self.url)
|
||||||
|
self.assertTemplateUsed(resp, "tirage-failed.html")
|
||||||
|
|
||||||
|
def test_perform_tirage(self):
|
||||||
|
# Otherwise, perform the tirage
|
||||||
|
self.tirage.enable_do_tirage = True
|
||||||
|
self.tirage.fermeture = timezone.now()
|
||||||
|
self.tirage.save()
|
||||||
|
resp = self.client.get(self.url)
|
||||||
|
self.assertTemplateNotUsed(resp, "tirage-failed.html")
|
||||||
|
|
||||||
|
|
||||||
|
class SpectacleListViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||||
|
url_name = "bda-liste-spectacles"
|
||||||
|
|
||||||
|
auth_user = "bda_staff"
|
||||||
|
auth_forbidden = [None, "bda_other", "bda_member"]
|
||||||
|
|
||||||
|
bda_testdata = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {"tirage_id": self.tirage.id}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return "/bda/spectacles/{}".format(self.tirage.id)
|
||||||
|
|
||||||
|
|
||||||
|
class SpectacleViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||||
|
url_name = "bda-spectacle"
|
||||||
|
|
||||||
|
auth_user = "bda_staff"
|
||||||
|
auth_forbidden = [None, "bda_other", "bda_member"]
|
||||||
|
|
||||||
|
bda_testdata = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {"tirage_id": self.tirage.id, "spectacle_id": self.show1.id}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return "/bda/spectacles/{}/{}".format(self.tirage.id, self.show1.id)
|
||||||
|
|
||||||
|
|
||||||
|
class UnpaidViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||||
|
url_name = "bda-unpaid"
|
||||||
|
|
||||||
|
auth_user = "bda_staff"
|
||||||
|
auth_forbidden = [None, "bda_other", "bda_member"]
|
||||||
|
|
||||||
|
bda_testdata = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {"tirage_id": self.tirage.id}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return "/bda/spectacles/unpaid/{}".format(self.tirage.id)
|
||||||
|
|
||||||
|
|
||||||
|
class SendRemindersViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||||
|
url_name = "bda-rappels"
|
||||||
|
|
||||||
|
auth_user = "bda_staff"
|
||||||
|
auth_forbidden = [None, "bda_other", "bda_member"]
|
||||||
|
|
||||||
|
bda_testdata = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {"spectacle_id": self.show1.id}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return "/bda/mails-rappel/{}".format(self.show1.id)
|
||||||
|
|
||||||
|
def test_post(self):
|
||||||
|
self.require_custommails()
|
||||||
|
resp = self.client.post(self.url)
|
||||||
|
self.assertEqual(200, resp.status_code)
|
||||||
|
# TODO: check that emails are sent
|
||||||
|
|
||||||
|
|
||||||
|
class DescriptionsSpectaclesViewTestCase(
|
||||||
|
BdATestHelpers, BdAViewTestCaseMixin, TestCase
|
||||||
|
):
|
||||||
|
url_name = "bda-descriptions"
|
||||||
|
|
||||||
|
auth_user = None
|
||||||
|
auth_forbidden = []
|
||||||
|
|
||||||
|
bda_testdata = True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_kwargs(self):
|
||||||
|
return {"tirage_id": self.tirage.pk}
|
||||||
|
|
||||||
|
@property
|
||||||
|
def url_expected(self):
|
||||||
|
return "/bda/descriptions/{}".format(self.tirage.pk)
|
||||||
|
|
||||||
|
def test_get(self):
|
||||||
|
resp = self.client.get(self.url)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertListEqual(
|
||||||
|
list(resp.context["shows"]), [self.show1, self.show2, self.show3]
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_filter_category(self):
|
||||||
|
category1 = CategorieSpectacle.objects.create(name="Category 1")
|
||||||
|
category2 = CategorieSpectacle.objects.create(name="Category 2")
|
||||||
|
show1 = create_spectacle(category=category1, tirage=self.tirage)
|
||||||
|
show2 = create_spectacle(category=category2, tirage=self.tirage)
|
||||||
|
|
||||||
|
resp = self.client.get(self.url, {"category": "Category 1"})
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertListEqual(list(resp.context["shows"]), [show1])
|
||||||
|
|
||||||
|
resp = self.client.get(self.url, {"category": "Category 2"})
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertListEqual(list(resp.context["shows"]), [show2])
|
||||||
|
|
||||||
|
def test_get_filter_location(self):
|
||||||
|
location1 = Salle.objects.create(name="Location 1")
|
||||||
|
location2 = Salle.objects.create(name="Location 2")
|
||||||
|
show1 = create_spectacle(location=location1, tirage=self.tirage)
|
||||||
|
show2 = create_spectacle(location=location2, tirage=self.tirage)
|
||||||
|
|
||||||
|
resp = self.client.get(self.url, {"location": str(location1.pk)})
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertListEqual(list(resp.context["shows"]), [show1])
|
||||||
|
|
||||||
|
resp = self.client.get(self.url, {"location": str(location2.pk)})
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertListEqual(list(resp.context["shows"]), [show2])
|
||||||
|
|
||||||
|
|
||||||
|
class CatalogueViewTestCase(BdATestHelpers, BdAViewTestCaseMixin, TestCase):
|
||||||
|
auth_user = None
|
||||||
|
auth_forbidden = []
|
||||||
|
|
||||||
|
bda_testdata = True
|
||||||
|
|
||||||
|
def test_api_list(self):
|
||||||
|
url_list = "/bda/catalogue/list"
|
||||||
|
resp = self.client.get(url_list)
|
||||||
|
self.assertJSONEqual(
|
||||||
|
resp.content.decode("utf-8"),
|
||||||
|
[{"id": self.tirage.id, "title": self.tirage.title}],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_api_details(self):
|
||||||
|
url_details = "/bda/catalogue/details?id={}".format(self.tirage.id)
|
||||||
|
resp = self.client.get(url_details)
|
||||||
|
self.assertJSONEqual(
|
||||||
|
resp.content.decode("utf-8"),
|
||||||
|
{
|
||||||
|
"categories": [{"id": self.category.id, "name": self.category.name}],
|
||||||
|
"locations": [{"id": self.location.id, "name": self.location.name}],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_api_descriptions(self):
|
||||||
|
url_descriptions = "/bda/catalogue/descriptions?id={}".format(self.tirage.id)
|
||||||
|
resp = self.client.get(url_descriptions)
|
||||||
|
raw = resp.content.decode("utf-8")
|
||||||
|
try:
|
||||||
|
results = json.loads(raw)
|
||||||
|
except ValueError:
|
||||||
|
self.fail("Not valid JSON: {}".format(raw))
|
||||||
|
self.assertEqual(len(results), 3)
|
||||||
|
self.assertEqual(
|
||||||
|
{(s["title"], s["price"], s["slots"]) for s in results},
|
||||||
|
{("foo", 0, 42), ("bar", 1, 142), ("baz", 2, 242)},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestBdaRevente:
|
||||||
|
pass
|
||||||
|
# TODO
|
75
bda/tests/testcases.py
Normal file
75
bda/tests/testcases.py
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from shared.tests.testcases import ViewTestCaseMixin
|
||||||
|
|
||||||
|
from ..models import CategorieSpectacle, Salle, Spectacle, Tirage
|
||||||
|
from .utils import create_user
|
||||||
|
|
||||||
|
|
||||||
|
class BdAViewTestCaseMixin(ViewTestCaseMixin):
|
||||||
|
def get_users_base(self):
|
||||||
|
return {
|
||||||
|
"bda_other": create_user(username="bda_other"),
|
||||||
|
"bda_member": create_user(username="bda_member", is_cof=True),
|
||||||
|
"bda_staff": create_user(username="bda_staff", is_cof=True, is_buro=True),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class BdATestHelpers:
|
||||||
|
bda_testdata = False
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
|
||||||
|
if self.bda_testdata:
|
||||||
|
self.load_bda_testdata()
|
||||||
|
|
||||||
|
def require_custommails(self):
|
||||||
|
data_file = os.path.join(
|
||||||
|
settings.BASE_DIR, "gestioncof", "management", "data", "custommail.json"
|
||||||
|
)
|
||||||
|
call_command("syncmails", data_file, verbosity=0)
|
||||||
|
|
||||||
|
def load_bda_testdata(self):
|
||||||
|
self.tirage = Tirage.objects.create(
|
||||||
|
title="Test tirage",
|
||||||
|
appear_catalogue=True,
|
||||||
|
ouverture=timezone.now(),
|
||||||
|
fermeture=timezone.now(),
|
||||||
|
)
|
||||||
|
self.category = CategorieSpectacle.objects.create(name="Category")
|
||||||
|
self.location = Salle.objects.create(name="here")
|
||||||
|
self.show1 = Spectacle.objects.create(
|
||||||
|
title="foo",
|
||||||
|
date=timezone.now(),
|
||||||
|
location=self.location,
|
||||||
|
price=0,
|
||||||
|
slots=42,
|
||||||
|
tirage=self.tirage,
|
||||||
|
listing=False,
|
||||||
|
category=self.category,
|
||||||
|
)
|
||||||
|
self.show2 = Spectacle.objects.create(
|
||||||
|
title="bar",
|
||||||
|
date=timezone.now(),
|
||||||
|
location=self.location,
|
||||||
|
price=1,
|
||||||
|
slots=142,
|
||||||
|
tirage=self.tirage,
|
||||||
|
listing=False,
|
||||||
|
category=self.category,
|
||||||
|
)
|
||||||
|
self.show3 = Spectacle.objects.create(
|
||||||
|
title="baz",
|
||||||
|
date=timezone.now(),
|
||||||
|
location=self.location,
|
||||||
|
price=2,
|
||||||
|
slots=242,
|
||||||
|
tirage=self.tirage,
|
||||||
|
listing=False,
|
||||||
|
category=self.category,
|
||||||
|
)
|
36
bda/tests/utils.py
Normal file
36
bda/tests/utils.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from ..models import CategorieSpectacle, Salle, Spectacle, Tirage
|
||||||
|
|
||||||
|
|
||||||
|
def create_user(username, is_cof=False, is_buro=False):
|
||||||
|
user = User.objects.create_user(username=username, password=username)
|
||||||
|
user.profile.is_cof = is_cof
|
||||||
|
user.profile.is_buro = is_buro
|
||||||
|
user.profile.save()
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
def user_is_cof(user):
|
||||||
|
return (user is not None) and user.profile.is_cof
|
||||||
|
|
||||||
|
|
||||||
|
def user_is_staff(user):
|
||||||
|
return (user is not None) and user.profile.is_buro
|
||||||
|
|
||||||
|
|
||||||
|
def create_spectacle(**kwargs):
|
||||||
|
defaults = {
|
||||||
|
"title": "Title",
|
||||||
|
"category": CategorieSpectacle.objects.first(),
|
||||||
|
"date": (timezone.now() + timedelta(days=7)).date(),
|
||||||
|
"location": Salle.objects.first(),
|
||||||
|
"price": 10.0,
|
||||||
|
"slots": 20,
|
||||||
|
"tirage": Tirage.objects.first(),
|
||||||
|
"listing": False,
|
||||||
|
}
|
||||||
|
return Spectacle.objects.create(**dict(defaults, **kwargs))
|
112
bda/urls.py
112
bda/urls.py
|
@ -1,55 +1,75 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import division
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from gestioncof.decorators import buro_required
|
|
||||||
from bda.views import SpectacleListView
|
|
||||||
from bda import views
|
from bda import views
|
||||||
|
from bda.views import SpectacleListView
|
||||||
|
from gestioncof.decorators import buro_required
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^inscription/(?P<tirage_id>\d+)$',
|
url(
|
||||||
|
r"^inscription/(?P<tirage_id>\d+)$",
|
||||||
views.inscription,
|
views.inscription,
|
||||||
name='bda-tirage-inscription'),
|
name="bda-tirage-inscription",
|
||||||
url(r'^places/(?P<tirage_id>\d+)$',
|
),
|
||||||
views.places,
|
url(r"^places/(?P<tirage_id>\d+)$", views.places, name="bda-places-attribuees"),
|
||||||
name="bda-places-attribuees"),
|
url(r"^etat-places/(?P<tirage_id>\d+)$", views.etat_places, name="bda-etat-places"),
|
||||||
url(r'^revente/(?P<tirage_id>\d+)$',
|
url(r"^tirage/(?P<tirage_id>\d+)$", views.tirage, name="bda-tirage"),
|
||||||
views.revente,
|
url(
|
||||||
name='bda-revente'),
|
r"^spectacles/(?P<tirage_id>\d+)$",
|
||||||
url(r'^etat-places/(?P<tirage_id>\d+)$',
|
|
||||||
views.etat_places,
|
|
||||||
name='bda-etat-places'),
|
|
||||||
url(r'^tirage/(?P<tirage_id>\d+)$', views.tirage),
|
|
||||||
url(r'^spectacles/(?P<tirage_id>\d+)$',
|
|
||||||
buro_required(SpectacleListView.as_view()),
|
buro_required(SpectacleListView.as_view()),
|
||||||
name="bda-liste-spectacles"),
|
name="bda-liste-spectacles",
|
||||||
url(r'^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$',
|
),
|
||||||
|
url(
|
||||||
|
r"^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$",
|
||||||
views.spectacle,
|
views.spectacle,
|
||||||
name="bda-spectacle"),
|
name="bda-spectacle",
|
||||||
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
|
),
|
||||||
views.unpaid,
|
url(r"^spectacles/unpaid/(?P<tirage_id>\d+)$", views.unpaid, name="bda-unpaid"),
|
||||||
name="bda-unpaid"),
|
url(
|
||||||
url(r'^liste-revente/(?P<tirage_id>\d+)$',
|
r"^spectacles/autocomplete$",
|
||||||
views.list_revente,
|
views.spectacle_autocomplete,
|
||||||
name="bda-liste-revente"),
|
name="bda-spectacle-autocomplete",
|
||||||
url(r'^buy-revente/(?P<spectacle_id>\d+)$',
|
),
|
||||||
views.buy_revente,
|
url(
|
||||||
name="bda-buy-revente"),
|
r"^participants/autocomplete$",
|
||||||
url(r'^revente-interested/(?P<revente_id>\d+)$',
|
views.participant_autocomplete,
|
||||||
views.revente_interested,
|
name="bda-participant-autocomplete",
|
||||||
name='bda-revente-interested'),
|
),
|
||||||
url(r'^revente-immediat/(?P<tirage_id>\d+)$',
|
# Urls BdA-Revente
|
||||||
|
url(
|
||||||
|
r"^revente/(?P<tirage_id>\d+)/manage$",
|
||||||
|
views.revente_manage,
|
||||||
|
name="bda-revente-manage",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^revente/(?P<tirage_id>\d+)/subscribe$",
|
||||||
|
views.revente_subscribe,
|
||||||
|
name="bda-revente-subscribe",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^revente/(?P<tirage_id>\d+)/tirages$",
|
||||||
|
views.revente_tirages,
|
||||||
|
name="bda-revente-tirages",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^revente/(?P<spectacle_id>\d+)/buy$",
|
||||||
|
views.revente_buy,
|
||||||
|
name="bda-revente-buy",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^revente/(?P<revente_id>\d+)/confirm$",
|
||||||
|
views.revente_confirm,
|
||||||
|
name="bda-revente-confirm",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^revente/(?P<tirage_id>\d+)/shotgun$",
|
||||||
views.revente_shotgun,
|
views.revente_shotgun,
|
||||||
name="bda-shotgun"),
|
name="bda-revente-shotgun",
|
||||||
url(r'^mails-rappel/(?P<spectacle_id>\d+)$',
|
),
|
||||||
views.send_rappel,
|
url(r"^mails-rappel/(?P<spectacle_id>\d+)$", views.send_rappel, name="bda-rappels"),
|
||||||
name="bda-rappels"
|
url(
|
||||||
),
|
r"^descriptions/(?P<tirage_id>\d+)$",
|
||||||
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
views.descriptions_spectacles,
|
||||||
name='bda-descriptions'),
|
name="bda-descriptions",
|
||||||
url(r'^catalogue/(?P<request_type>[a-z]+)$', views.catalogue,
|
),
|
||||||
name='bda-catalogue'),
|
url(r"^catalogue/(?P<request_type>[a-z]+)$", views.catalogue, name="bda-catalogue"),
|
||||||
]
|
]
|
||||||
|
|
811
bda/views.py
811
bda/views.py
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from channels.asgi import get_channel_layer
|
from channels.asgi import get_channel_layer
|
||||||
|
|
||||||
if "DJANGO_SETTINGS_MODULE" not in os.environ:
|
if "DJANGO_SETTINGS_MODULE" not in os.environ:
|
||||||
|
|
0
cof/locale/en/__init__.py
Normal file
0
cof/locale/en/__init__.py
Normal file
11
cof/locale/en/formats.py
Normal file
11
cof/locale/en/formats.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
|
"""
|
||||||
|
English formatting.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
DATETIME_FORMAT = r"l N j, Y \a\t P"
|
||||||
|
DATE_FORMAT = r"l N j, Y"
|
||||||
|
TIME_FORMAT = r"P"
|
|
@ -1,9 +1,7 @@
|
||||||
# -*- encoding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Formats français.
|
Formats français.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
DATETIME_FORMAT = r"l j F Y \à H\hi"
|
||||||
|
DATE_FORMAT = r"l j F Y"
|
||||||
DATETIME_FORMAT = r'l j F Y \à H:i'
|
TIME_FORMAT = r"H\hi"
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
from channels.routing import include
|
from channels.routing import include
|
||||||
|
|
||||||
|
routing = [include("kfet.routing.routing", path=r"^/ws/k-fet")]
|
||||||
routing = [
|
|
||||||
include('kfet.routing.routing', path=r'^/ws/k-fet'),
|
|
||||||
]
|
|
||||||
|
|
0
cof/settings/__init__.py
Normal file
0
cof/settings/__init__.py
Normal file
|
@ -1,4 +1,3 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
"""
|
||||||
Django common settings for cof project.
|
Django common settings for cof project.
|
||||||
|
|
||||||
|
@ -7,6 +6,7 @@ the local development server should be here.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from . import secret
|
from . import secret
|
||||||
|
@ -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")
|
||||||
|
@ -41,108 +42,115 @@ REDIS_DB = import_secret("REDIS_DB")
|
||||||
REDIS_HOST = import_secret("REDIS_HOST")
|
REDIS_HOST = import_secret("REDIS_HOST")
|
||||||
REDIS_PORT = import_secret("REDIS_PORT")
|
REDIS_PORT = import_secret("REDIS_PORT")
|
||||||
|
|
||||||
RECAPTCHA_PUBLIC_KEY = import_secret("RECAPTCHA_PUBLIC_KEY")
|
|
||||||
RECAPTCHA_PRIVATE_KEY = import_secret("RECAPTCHA_PRIVATE_KEY")
|
|
||||||
|
|
||||||
KFETOPEN_TOKEN = import_secret("KFETOPEN_TOKEN")
|
KFETOPEN_TOKEN = import_secret("KFETOPEN_TOKEN")
|
||||||
LDAP_SERVER_URL = import_secret("LDAP_SERVER_URL")
|
LDAP_SERVER_URL = import_secret("LDAP_SERVER_URL")
|
||||||
|
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
||||||
)
|
TESTING = sys.argv[1] == "test"
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'gestioncof',
|
"shared",
|
||||||
'django.contrib.auth',
|
"gestioncof",
|
||||||
'django.contrib.contenttypes',
|
# Must be before 'django.contrib.admin'.
|
||||||
'django.contrib.sessions',
|
# https://django-autocomplete-light.readthedocs.io/en/master/install.html
|
||||||
'django.contrib.sites',
|
"dal",
|
||||||
'django.contrib.messages',
|
"dal_select2",
|
||||||
'django.contrib.staticfiles',
|
"django.contrib.auth",
|
||||||
'grappelli',
|
"django.contrib.contenttypes",
|
||||||
'django.contrib.admin',
|
"django.contrib.sessions",
|
||||||
'django.contrib.admindocs',
|
"django.contrib.sites",
|
||||||
'bda',
|
"django.contrib.messages",
|
||||||
'autocomplete_light',
|
"django.contrib.staticfiles",
|
||||||
'captcha',
|
"django.contrib.admin",
|
||||||
'django_cas_ng',
|
"django.contrib.admindocs",
|
||||||
'bootstrapform',
|
"bda",
|
||||||
'kfet',
|
"petitscours",
|
||||||
'kfet.open',
|
"captcha",
|
||||||
'channels',
|
"django_cas_ng",
|
||||||
'widget_tweaks',
|
"bootstrapform",
|
||||||
'custommail',
|
"kfet",
|
||||||
'djconfig',
|
"kfet.open",
|
||||||
'wagtail.wagtailforms',
|
"channels",
|
||||||
'wagtail.wagtailredirects',
|
"widget_tweaks",
|
||||||
'wagtail.wagtailembeds',
|
"custommail",
|
||||||
'wagtail.wagtailsites',
|
"djconfig",
|
||||||
'wagtail.wagtailusers',
|
"wagtail.wagtailforms",
|
||||||
'wagtail.wagtailsnippets',
|
"wagtail.wagtailredirects",
|
||||||
'wagtail.wagtaildocs',
|
"wagtail.wagtailembeds",
|
||||||
'wagtail.wagtailimages',
|
"wagtail.wagtailsites",
|
||||||
'wagtail.wagtailsearch',
|
"wagtail.wagtailusers",
|
||||||
'wagtail.wagtailadmin',
|
"wagtail.wagtailsnippets",
|
||||||
'wagtail.wagtailcore',
|
"wagtail.wagtaildocs",
|
||||||
'wagtail.contrib.modeladmin',
|
"wagtail.wagtailimages",
|
||||||
'wagtailmenus',
|
"wagtail.wagtailsearch",
|
||||||
'modelcluster',
|
"wagtail.wagtailadmin",
|
||||||
'taggit',
|
"wagtail.wagtailcore",
|
||||||
'kfet.auth',
|
"wagtail.contrib.modeladmin",
|
||||||
'kfet.cms',
|
"wagtail.contrib.wagtailroutablepage",
|
||||||
|
"wagtailmenus",
|
||||||
|
"wagtail_modeltranslation",
|
||||||
|
"modelcluster",
|
||||||
|
"taggit",
|
||||||
|
"kfet.auth",
|
||||||
|
"kfet.cms",
|
||||||
|
"gestioncof.cms",
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE_CLASSES = [
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
MIDDLEWARE = [
|
||||||
'django.middleware.common.CommonMiddleware',
|
"corsheaders.middleware.CorsMiddleware",
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
"django.middleware.common.CommonMiddleware",
|
||||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
'kfet.auth.middleware.TemporaryAuthMiddleware',
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
"django.contrib.auth.middleware.SessionAuthenticationMiddleware",
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
"kfet.auth.middleware.TemporaryAuthMiddleware",
|
||||||
'django.middleware.security.SecurityMiddleware',
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
'djconfig.middleware.DjConfigMiddleware',
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
'wagtail.wagtailcore.middleware.SiteMiddleware',
|
"django.middleware.security.SecurityMiddleware",
|
||||||
'wagtail.wagtailredirects.middleware.RedirectMiddleware',
|
"djconfig.middleware.DjConfigMiddleware",
|
||||||
|
"wagtail.wagtailcore.middleware.SiteMiddleware",
|
||||||
|
"wagtail.wagtailredirects.middleware.RedirectMiddleware",
|
||||||
|
"django.middleware.locale.LocaleMiddleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'cof.urls'
|
ROOT_URLCONF = "cof.urls"
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
{
|
{
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
'DIRS': [],
|
"DIRS": [],
|
||||||
'APP_DIRS': True,
|
"APP_DIRS": True,
|
||||||
'OPTIONS': {
|
"OPTIONS": {
|
||||||
'context_processors': [
|
"context_processors": [
|
||||||
'django.template.context_processors.debug',
|
"django.template.context_processors.debug",
|
||||||
'django.template.context_processors.request',
|
"django.template.context_processors.request",
|
||||||
'django.contrib.auth.context_processors.auth',
|
"django.contrib.auth.context_processors.auth",
|
||||||
'django.contrib.messages.context_processors.messages',
|
"django.contrib.messages.context_processors.messages",
|
||||||
'django.core.context_processors.i18n',
|
"django.template.context_processors.i18n",
|
||||||
'django.core.context_processors.media',
|
"django.template.context_processors.media",
|
||||||
'django.core.context_processors.static',
|
"django.template.context_processors.static",
|
||||||
'wagtailmenus.context_processors.wagtailmenus',
|
"wagtailmenus.context_processors.wagtailmenus",
|
||||||
'djconfig.context_processors.config',
|
"djconfig.context_processors.config",
|
||||||
'gestioncof.shared.context_processor',
|
"gestioncof.shared.context_processor",
|
||||||
'kfet.auth.context_processors.temporary_auth',
|
"kfet.auth.context_processors.temporary_auth",
|
||||||
'kfet.context_processors.config',
|
"kfet.context_processors.config",
|
||||||
],
|
]
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
'default': {
|
"default": {
|
||||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
||||||
'NAME': DBNAME,
|
"NAME": DBNAME,
|
||||||
'USER': DBUSER,
|
"USER": DBUSER,
|
||||||
'PASSWORD': DBPASSWD,
|
"PASSWORD": DBPASSWD,
|
||||||
'HOST': os.environ.get('DBHOST', 'localhost'),
|
"HOST": os.environ.get("DBHOST", "localhost"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,9 +158,9 @@ DATABASES = {
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = 'fr-fr'
|
LANGUAGE_CODE = "fr-fr"
|
||||||
|
|
||||||
TIME_ZONE = 'Europe/Paris'
|
TIME_ZONE = "Europe/Paris"
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
|
@ -160,51 +168,63 @@ USE_L10N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
LANGUAGES = (("fr", "Français"), ("en", "English"))
|
||||||
|
|
||||||
# Various additional settings
|
# Various additional settings
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
GRAPPELLI_ADMIN_HEADLINE = "GestioCOF"
|
GRAPPELLI_ADMIN_HEADLINE = "GestioCOF"
|
||||||
GRAPPELLI_ADMIN_TITLE = "<a href=\"/\">GestioCOF</a>"
|
GRAPPELLI_ADMIN_TITLE = '<a href="/">GestioCOF</a>'
|
||||||
|
|
||||||
MAIL_DATA = {
|
MAIL_DATA = {
|
||||||
'petits_cours': {
|
"petits_cours": {
|
||||||
'FROM': "Le COF <cof@ens.fr>",
|
"FROM": "Le COF <cof@ens.fr>",
|
||||||
'BCC': "archivescof@gmail.com",
|
"BCC": "archivescof@gmail.com",
|
||||||
'REPLYTO': "cof@ens.fr"},
|
"REPLYTO": "cof@ens.fr",
|
||||||
'rappels': {
|
},
|
||||||
'FROM': 'Le BdA <bda@ens.fr>',
|
"rappels": {"FROM": "Le BdA <bda@ens.fr>", "REPLYTO": "Le BdA <bda@ens.fr>"},
|
||||||
'REPLYTO': 'Le BdA <bda@ens.fr>'},
|
"revente": {
|
||||||
'revente': {
|
"FROM": "BdA-Revente <bda-revente@ens.fr>",
|
||||||
'FROM': 'BdA-Revente <bda-revente@ens.fr>',
|
"REPLYTO": "BdA-Revente <bda-revente@ens.fr>",
|
||||||
'REPLYTO': 'BdA-Revente <bda-revente@ens.fr>'},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
LOGIN_URL = "cof-login"
|
LOGIN_URL = "cof-login"
|
||||||
LOGIN_REDIRECT_URL = "home"
|
LOGIN_REDIRECT_URL = "home"
|
||||||
|
|
||||||
CAS_SERVER_URL = 'https://cas.eleves.ens.fr/'
|
CAS_SERVER_URL = "https://cas.eleves.ens.fr/"
|
||||||
CAS_VERSION = '3'
|
CAS_VERSION = "2"
|
||||||
CAS_LOGIN_MSG = None
|
CAS_LOGIN_MSG = None
|
||||||
CAS_IGNORE_REFERER = True
|
CAS_IGNORE_REFERER = True
|
||||||
CAS_REDIRECT_URL = '/'
|
CAS_REDIRECT_URL = "/"
|
||||||
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
AUTHENTICATION_BACKENDS = (
|
||||||
'django.contrib.auth.backends.ModelBackend',
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
'gestioncof.shared.COFCASBackend',
|
"gestioncof.shared.COFCASBackend",
|
||||||
'kfet.auth.backends.GenericBackend',
|
"kfet.auth.backends.GenericBackend",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# reCAPTCHA settings
|
||||||
|
# https://github.com/praekelt/django-recaptcha
|
||||||
|
#
|
||||||
|
# Default settings authorize reCAPTCHA usage for local developement.
|
||||||
|
# Public and private keys are appended in the 'prod' module settings.
|
||||||
|
|
||||||
|
NOCAPTCHA = True
|
||||||
RECAPTCHA_USE_SSL = True
|
RECAPTCHA_USE_SSL = True
|
||||||
|
|
||||||
|
CORS_ORIGIN_WHITELIST = ("bda.ens.fr", "www.bda.ens.fr" "cof.ens.fr", "www.cof.ens.fr")
|
||||||
|
|
||||||
# Cache settings
|
# Cache settings
|
||||||
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
'default': {
|
"default": {
|
||||||
'BACKEND': 'redis_cache.RedisCache',
|
"BACKEND": "redis_cache.RedisCache",
|
||||||
'LOCATION': 'redis://:{passwd}@{host}:{port}/db'
|
"LOCATION": "redis://:{passwd}@{host}:{port}/db".format(
|
||||||
.format(passwd=REDIS_PASSWD, host=REDIS_HOST,
|
passwd=REDIS_PASSWD, host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB
|
||||||
port=REDIS_PORT, db=REDIS_DB),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -215,20 +235,25 @@ CHANNEL_LAYERS = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "asgi_redis.RedisChannelLayer",
|
"BACKEND": "asgi_redis.RedisChannelLayer",
|
||||||
"CONFIG": {
|
"CONFIG": {
|
||||||
"hosts": [(
|
"hosts": [
|
||||||
"redis://:{passwd}@{host}:{port}/{db}"
|
(
|
||||||
.format(passwd=REDIS_PASSWD, host=REDIS_HOST,
|
"redis://:{passwd}@{host}:{port}/{db}".format(
|
||||||
port=REDIS_PORT, db=REDIS_DB)
|
passwd=REDIS_PASSWD,
|
||||||
)],
|
host=REDIS_HOST,
|
||||||
|
port=REDIS_PORT,
|
||||||
|
db=REDIS_DB,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"ROUTING": "cof.routing.routing",
|
"ROUTING": "cof.routing.routing",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FORMAT_MODULE_PATH = 'cof.locale'
|
FORMAT_MODULE_PATH = "cof.locale"
|
||||||
|
|
||||||
# Wagtail settings
|
# Wagtail settings
|
||||||
|
|
||||||
WAGTAIL_SITE_NAME = 'GestioCOF'
|
WAGTAIL_SITE_NAME = "GestioCOF"
|
||||||
WAGTAIL_ENABLE_UPDATE_CHECK = False
|
WAGTAIL_ENABLE_UPDATE_CHECK = False
|
||||||
TAGGIT_CASE_INSENSITIVE = True
|
TAGGIT_CASE_INSENSITIVE = True
|
||||||
|
|
|
@ -4,29 +4,32 @@ The settings that are not listed here are imported from .common
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .common import * # NOQA
|
from .common import * # NOQA
|
||||||
from .common import INSTALLED_APPS, MIDDLEWARE_CLASSES
|
from .common import INSTALLED_APPS, MIDDLEWARE, TESTING
|
||||||
|
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
|
||||||
|
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
|
if TESTING:
|
||||||
|
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
||||||
|
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Apache static/media config
|
# Apache static/media config
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = "/static/"
|
||||||
STATIC_ROOT = '/srv/gestiocof/static/'
|
STATIC_ROOT = "/srv/gestiocof/static/"
|
||||||
|
|
||||||
MEDIA_ROOT = '/srv/gestiocof/media/'
|
MEDIA_ROOT = "/srv/gestiocof/media/"
|
||||||
MEDIA_URL = '/media/'
|
MEDIA_URL = "/media/"
|
||||||
|
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Debug tool bar
|
# Debug tool bar
|
||||||
# ---
|
# ---
|
||||||
|
|
||||||
|
|
||||||
def show_toolbar(request):
|
def show_toolbar(request):
|
||||||
"""
|
"""
|
||||||
On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar
|
On ne veut pas la vérification de INTERNAL_IPS faite par la debug-toolbar
|
||||||
|
@ -34,13 +37,12 @@ def show_toolbar(request):
|
||||||
machine physique n'est pas forcément connue, et peut difficilement être
|
machine physique n'est pas forcément connue, et peut difficilement être
|
||||||
mise dans les INTERNAL_IPS.
|
mise dans les INTERNAL_IPS.
|
||||||
"""
|
"""
|
||||||
return DEBUG
|
return DEBUG and not request.path.startswith("/admin/")
|
||||||
|
|
||||||
INSTALLED_APPS += ["debug_toolbar", "debug_panel"]
|
|
||||||
MIDDLEWARE_CLASSES = (
|
if not TESTING:
|
||||||
["debug_panel.middleware.DebugPanelMiddleware"]
|
INSTALLED_APPS += ["debug_toolbar", "debug_panel"]
|
||||||
+ MIDDLEWARE_CLASSES
|
|
||||||
)
|
MIDDLEWARE = ["debug_panel.middleware.DebugPanelMiddleware"] + MIDDLEWARE
|
||||||
DEBUG_TOOLBAR_CONFIG = {
|
|
||||||
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
|
DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": show_toolbar}
|
||||||
}
|
|
||||||
|
|
|
@ -8,21 +8,16 @@ import os
|
||||||
from .dev import * # NOQA
|
from .dev import * # NOQA
|
||||||
from .dev import BASE_DIR
|
from .dev import BASE_DIR
|
||||||
|
|
||||||
|
|
||||||
# Use sqlite for local development
|
# Use sqlite for local development
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
"NAME": os.path.join(BASE_DIR, "db.sqlite3")
|
"NAME": os.path.join(BASE_DIR, "db.sqlite3"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Use the default cache backend for local development
|
# Use the default cache backend for local development
|
||||||
CACHES = {
|
CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}}
|
||||||
"default": {
|
|
||||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Use the default in memory asgi backend for local development
|
# Use the default in memory asgi backend for local development
|
||||||
CHANNEL_LAYERS = {
|
CHANNEL_LAYERS = {
|
||||||
|
|
|
@ -6,25 +6,21 @@ The settings that are not listed here are imported from .common
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from .common import * # NOQA
|
from .common import * # NOQA
|
||||||
from .common import BASE_DIR
|
from .common import BASE_DIR, import_secret
|
||||||
|
|
||||||
|
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
|
|
||||||
ALLOWED_HOSTS = [
|
ALLOWED_HOSTS = ["cof.ens.fr", "www.cof.ens.fr", "dev.cof.ens.fr"]
|
||||||
"cof.ens.fr",
|
|
||||||
"www.cof.ens.fr",
|
|
||||||
"dev.cof.ens.fr"
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
STATIC_ROOT = os.path.join(
|
STATIC_ROOT = os.path.join(
|
||||||
os.path.dirname(os.path.dirname(BASE_DIR)),
|
os.path.dirname(os.path.dirname(BASE_DIR)), "public", "gestion", "static"
|
||||||
"public",
|
|
||||||
"gestion",
|
|
||||||
"static",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
STATIC_URL = "/gestion/static/"
|
STATIC_URL = "/gestion/static/"
|
||||||
MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "media")
|
MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "media")
|
||||||
MEDIA_URL = "/gestion/media/"
|
MEDIA_URL = "/gestion/media/"
|
||||||
|
|
||||||
|
|
||||||
|
RECAPTCHA_PUBLIC_KEY = import_secret("RECAPTCHA_PUBLIC_KEY")
|
||||||
|
RECAPTCHA_PRIVATE_KEY = import_secret("RECAPTCHA_PRIVATE_KEY")
|
||||||
|
|
|
@ -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"
|
||||||
|
|
157
cof/urls.py
157
cof/urls.py
|
@ -1,110 +1,137 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Fichier principal de configuration des urls du projet GestioCOF
|
Fichier principal de configuration des urls du projet GestioCOF
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import autocomplete_light
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import include, url
|
||||||
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.views.generic.base import TemplateView
|
|
||||||
from django.contrib.auth import views as django_views
|
from django.contrib.auth import views as django_views
|
||||||
|
from django.views.generic.base import TemplateView
|
||||||
from django_cas_ng import views as django_cas_views
|
from django_cas_ng import views as django_cas_views
|
||||||
|
|
||||||
from wagtail.wagtailadmin import urls as wagtailadmin_urls
|
from wagtail.wagtailadmin import urls as wagtailadmin_urls
|
||||||
from wagtail.wagtailcore import urls as wagtail_urls
|
from wagtail.wagtailcore import urls as wagtail_urls
|
||||||
from wagtail.wagtaildocs import urls as wagtaildocs_urls
|
from wagtail.wagtaildocs import urls as wagtaildocs_urls
|
||||||
|
|
||||||
from gestioncof import views as gestioncof_views, csv_views
|
from gestioncof import csv_views, views as gestioncof_views
|
||||||
from gestioncof.urls import export_patterns, petitcours_patterns, \
|
|
||||||
surveys_patterns, events_patterns, calendar_patterns, \
|
|
||||||
clubs_patterns
|
|
||||||
from gestioncof.autocomplete import autocomplete
|
from gestioncof.autocomplete import autocomplete
|
||||||
|
from gestioncof.urls import (
|
||||||
|
calendar_patterns,
|
||||||
|
clubs_patterns,
|
||||||
|
events_patterns,
|
||||||
|
export_patterns,
|
||||||
|
surveys_patterns,
|
||||||
|
)
|
||||||
|
|
||||||
autocomplete_light.autodiscover()
|
|
||||||
admin.autodiscover()
|
admin.autodiscover()
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
# Page d'accueil
|
# Page d'accueil
|
||||||
url(r'^$', gestioncof_views.home, name='home'),
|
url(r"^$", gestioncof_views.home, name="home"),
|
||||||
# Le BdA
|
# Le BdA
|
||||||
url(r'^bda/', include('bda.urls')),
|
url(r"^bda/", include("bda.urls")),
|
||||||
# Les exports
|
# Les exports
|
||||||
url(r'^export/', include(export_patterns)),
|
url(r"^export/", include(export_patterns)),
|
||||||
# Les petits cours
|
# Les petits cours
|
||||||
url(r'^petitcours/', include(petitcours_patterns)),
|
url(r"^petitcours/", include("petitscours.urls")),
|
||||||
# Les sondages
|
# Les sondages
|
||||||
url(r'^survey/', include(surveys_patterns)),
|
url(r"^survey/", include(surveys_patterns)),
|
||||||
# Evenements
|
# Evenements
|
||||||
url(r'^event/', include(events_patterns)),
|
url(r"^event/", include(events_patterns)),
|
||||||
# Calendrier
|
# Calendrier
|
||||||
url(r'^calendar/', include(calendar_patterns)),
|
url(r"^calendar/", include(calendar_patterns)),
|
||||||
# Clubs
|
# Clubs
|
||||||
url(r'^clubs/', include(clubs_patterns)),
|
url(r"^clubs/", include(clubs_patterns)),
|
||||||
# Authentification
|
# Authentification
|
||||||
url(r'^cof/denied$', TemplateView.as_view(template_name='cof-denied.html'),
|
url(
|
||||||
name="cof-denied"),
|
r"^cof/denied$",
|
||||||
url(r'^cas/login$', django_cas_views.login, name="cas_login_view"),
|
TemplateView.as_view(template_name="cof-denied.html"),
|
||||||
url(r'^cas/logout$', django_cas_views.logout),
|
name="cof-denied",
|
||||||
url(r'^outsider/login$', gestioncof_views.login_ext),
|
),
|
||||||
url(r'^outsider/logout$', django_views.logout, {'next_page': 'home'}),
|
url(r"^cas/login$", django_cas_views.login, name="cas_login_view"),
|
||||||
url(r'^login$', gestioncof_views.login, name="cof-login"),
|
url(r"^cas/logout$", django_cas_views.logout),
|
||||||
url(r'^logout$', gestioncof_views.logout, name="cof-logout"),
|
url(r"^outsider/login$", gestioncof_views.login_ext, name="ext_login_view"),
|
||||||
|
url(r"^outsider/logout$", django_views.logout, {"next_page": "home"}),
|
||||||
|
url(r"^login$", gestioncof_views.login, name="cof-login"),
|
||||||
|
url(r"^logout$", gestioncof_views.logout, name="cof-logout"),
|
||||||
# Infos persos
|
# Infos persos
|
||||||
url(r'^profile$', gestioncof_views.profile),
|
url(r"^profile$", gestioncof_views.profile, name="profile"),
|
||||||
url(r'^outsider/password-change$', django_views.password_change),
|
url(
|
||||||
url(r'^outsider/password-change-done$',
|
r"^outsider/password-change$",
|
||||||
|
django_views.password_change,
|
||||||
|
name="password_change",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^outsider/password-change-done$",
|
||||||
django_views.password_change_done,
|
django_views.password_change_done,
|
||||||
name='password_change_done'),
|
name="password_change_done",
|
||||||
|
),
|
||||||
# Inscription d'un nouveau membre
|
# Inscription d'un nouveau membre
|
||||||
url(r'^registration$', gestioncof_views.registration),
|
url(r"^registration$", gestioncof_views.registration, name="registration"),
|
||||||
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)/'
|
url(
|
||||||
r'(?P<fullname>.*)$',
|
r"^registration/clipper/(?P<login_clipper>[\w-]+)/" r"(?P<fullname>.*)$",
|
||||||
gestioncof_views.registration_form2, name="clipper-registration"),
|
gestioncof_views.registration_form2,
|
||||||
url(r'^registration/user/(?P<username>.+)$',
|
name="clipper-registration",
|
||||||
gestioncof_views.registration_form2, name="user-registration"),
|
),
|
||||||
url(r'^registration/empty$', gestioncof_views.registration_form2,
|
url(
|
||||||
name="empty-registration"),
|
r"^registration/user/(?P<username>.+)$",
|
||||||
|
gestioncof_views.registration_form2,
|
||||||
|
name="user-registration",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^registration/empty$",
|
||||||
|
gestioncof_views.registration_form2,
|
||||||
|
name="empty-registration",
|
||||||
|
),
|
||||||
# Autocompletion
|
# Autocompletion
|
||||||
url(r'^autocomplete/registration$', autocomplete),
|
url(
|
||||||
url(r'^autocomplete/', include('autocomplete_light.urls')),
|
r"^autocomplete/registration$",
|
||||||
|
autocomplete,
|
||||||
|
name="cof.registration.autocomplete",
|
||||||
|
),
|
||||||
|
url(
|
||||||
|
r"^user/autocomplete$",
|
||||||
|
gestioncof_views.user_autocomplete,
|
||||||
|
name="cof-user-autocomplete",
|
||||||
|
),
|
||||||
# Interface admin
|
# Interface admin
|
||||||
url(r'^admin/logout/', gestioncof_views.logout),
|
url(r"^admin/logout/", gestioncof_views.logout),
|
||||||
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
|
url(r"^admin/doc/", include("django.contrib.admindocs.urls")),
|
||||||
url(r'^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/',
|
url(
|
||||||
|
r"^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/",
|
||||||
csv_views.admin_list_export,
|
csv_views.admin_list_export,
|
||||||
{'fields': ['username', ]}),
|
{"fields": ["username"]},
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
),
|
||||||
url(r'^grappelli/', include('grappelli.urls')),
|
url(r"^admin/", include(admin.site.urls)),
|
||||||
# Liens utiles du COF et du BdA
|
# Liens utiles du COF et du BdA
|
||||||
url(r'^utile_cof$', gestioncof_views.utile_cof),
|
url(r"^utile_cof$", gestioncof_views.utile_cof, name="utile_cof"),
|
||||||
url(r'^utile_bda$', gestioncof_views.utile_bda),
|
url(r"^utile_bda$", gestioncof_views.utile_bda, name="utile_bda"),
|
||||||
url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff),
|
url(r"^utile_bda/bda_diff$", gestioncof_views.liste_bdadiff, name="ml_diffbda"),
|
||||||
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof),
|
url(r"^utile_cof/diff_cof$", gestioncof_views.liste_diffcof, name="ml_diffcof"),
|
||||||
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente),
|
url(
|
||||||
url(r'^k-fet/', include('kfet.urls')),
|
r"^utile_bda/bda_revente$",
|
||||||
url(r'^cms/', include(wagtailadmin_urls)),
|
gestioncof_views.liste_bdarevente,
|
||||||
url(r'^documents/', include(wagtaildocs_urls)),
|
name="ml_bda_revente",
|
||||||
|
),
|
||||||
|
url(r"^k-fet/", include("kfet.urls")),
|
||||||
|
url(r"^cms/", include(wagtailadmin_urls)),
|
||||||
|
url(r"^documents/", include(wagtaildocs_urls)),
|
||||||
# djconfig
|
# djconfig
|
||||||
url(r"^config", gestioncof_views.ConfigUpdate.as_view()),
|
url(r"^config", gestioncof_views.ConfigUpdate.as_view(), name="config.edit"),
|
||||||
]
|
]
|
||||||
|
|
||||||
if 'debug_toolbar' in settings.INSTALLED_APPS:
|
if "debug_toolbar" in settings.INSTALLED_APPS:
|
||||||
import debug_toolbar
|
import debug_toolbar
|
||||||
urlpatterns += [
|
|
||||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
urlpatterns += [url(r"^__debug__/", include(debug_toolbar.urls))]
|
||||||
]
|
|
||||||
|
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
# Si on est en production, MEDIA_ROOT est servi par Apache.
|
# Si on est en production, MEDIA_ROOT est servi par Apache.
|
||||||
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.
|
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.
|
||||||
urlpatterns += static(settings.MEDIA_URL,
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
document_root=settings.MEDIA_ROOT)
|
|
||||||
|
|
||||||
# Wagtail for uncatched
|
# Wagtail for uncatched
|
||||||
urlpatterns += [
|
urlpatterns += i18n_patterns(
|
||||||
url(r'', include(wagtail_urls)),
|
url(r"", include(wagtail_urls)), prefix_default_language=False
|
||||||
]
|
)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
default_app_config = 'gestioncof.apps.GestioncofConfig'
|
default_app_config = "gestioncof.apps.GestioncofConfig"
|
||||||
|
|
|
@ -1,23 +1,35 @@
|
||||||
|
from dal.autocomplete import ModelSelect2
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.utils.translation import ugettext_lazy as _
|
|
||||||
from gestioncof.models import SurveyQuestionAnswer, SurveyQuestion, \
|
|
||||||
CofProfile, EventOption, EventOptionChoice, Event, Club, \
|
|
||||||
Survey, EventCommentField, EventRegistration
|
|
||||||
from gestioncof.petits_cours_models import PetitCoursDemande, \
|
|
||||||
PetitCoursSubject, PetitCoursAbility, PetitCoursAttribution, \
|
|
||||||
PetitCoursAttributionCounter
|
|
||||||
from django.contrib.auth.models import User, Group, Permission
|
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
|
from django.contrib.auth.models import Group, Permission, User
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
import autocomplete_light
|
from gestioncof.models import (
|
||||||
|
Club,
|
||||||
|
CofProfile,
|
||||||
|
Event,
|
||||||
|
EventCommentField,
|
||||||
|
EventOption,
|
||||||
|
EventOptionChoice,
|
||||||
|
EventRegistration,
|
||||||
|
Survey,
|
||||||
|
SurveyQuestion,
|
||||||
|
SurveyQuestionAnswer,
|
||||||
|
)
|
||||||
|
from petitscours.models import (
|
||||||
|
PetitCoursAbility,
|
||||||
|
PetitCoursAttribution,
|
||||||
|
PetitCoursAttributionCounter,
|
||||||
|
PetitCoursDemande,
|
||||||
|
PetitCoursSubject,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_link_field(target_model='', field='', link_text=str,
|
def add_link_field(target_model="", field="", link_text=str, desc_text=str):
|
||||||
desc_text=str):
|
|
||||||
def add_link(cls):
|
def add_link(cls):
|
||||||
reverse_name = target_model or cls.model.__name__.lower()
|
reverse_name = target_model or cls.model.__name__.lower()
|
||||||
|
|
||||||
|
@ -28,14 +40,14 @@ def add_link_field(target_model='', field='', link_text=str,
|
||||||
if not link_obj.id:
|
if not link_obj.id:
|
||||||
return ""
|
return ""
|
||||||
url = reverse(reverse_path, args=(link_obj.id,))
|
url = reverse(reverse_path, args=(link_obj.id,))
|
||||||
return mark_safe("<a href='%s'>%s</a>"
|
return mark_safe("<a href='%s'>%s</a>" % (url, link_text(link_obj)))
|
||||||
% (url, link_text(link_obj)))
|
|
||||||
link.allow_tags = True
|
link.allow_tags = True
|
||||||
link.short_description = desc_text(reverse_name + ' link')
|
link.short_description = desc_text(reverse_name + " link")
|
||||||
cls.link = link
|
cls.link = link
|
||||||
cls.readonly_fields =\
|
cls.readonly_fields = list(getattr(cls, "readonly_fields", [])) + ["link"]
|
||||||
list(getattr(cls, 'readonly_fields', [])) + ['link']
|
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
return add_link
|
return add_link
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,32 +55,28 @@ class SurveyQuestionAnswerInline(admin.TabularInline):
|
||||||
model = SurveyQuestionAnswer
|
model = SurveyQuestionAnswer
|
||||||
|
|
||||||
|
|
||||||
@add_link_field(desc_text=lambda x: "Réponses",
|
@add_link_field(
|
||||||
link_text=lambda x: "Éditer les réponses")
|
desc_text=lambda x: "Réponses", link_text=lambda x: "Éditer les réponses"
|
||||||
|
)
|
||||||
class SurveyQuestionInline(admin.TabularInline):
|
class SurveyQuestionInline(admin.TabularInline):
|
||||||
model = SurveyQuestion
|
model = SurveyQuestion
|
||||||
|
|
||||||
|
|
||||||
class SurveyQuestionAdmin(admin.ModelAdmin):
|
class SurveyQuestionAdmin(admin.ModelAdmin):
|
||||||
search_fields = ('survey__title', 'answer')
|
search_fields = ("survey__title", "answer")
|
||||||
inlines = [
|
inlines = [SurveyQuestionAnswerInline]
|
||||||
SurveyQuestionAnswerInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class SurveyAdmin(admin.ModelAdmin):
|
class SurveyAdmin(admin.ModelAdmin):
|
||||||
search_fields = ('title', 'details')
|
search_fields = ("title", "details")
|
||||||
inlines = [
|
inlines = [SurveyQuestionInline]
|
||||||
SurveyQuestionInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EventOptionChoiceInline(admin.TabularInline):
|
class EventOptionChoiceInline(admin.TabularInline):
|
||||||
model = EventOptionChoice
|
model = EventOptionChoice
|
||||||
|
|
||||||
|
|
||||||
@add_link_field(desc_text=lambda x: "Choix",
|
@add_link_field(desc_text=lambda x: "Choix", link_text=lambda x: "Éditer les choix")
|
||||||
link_text=lambda x: "Éditer les choix")
|
|
||||||
class EventOptionInline(admin.TabularInline):
|
class EventOptionInline(admin.TabularInline):
|
||||||
model = EventOption
|
model = EventOption
|
||||||
|
|
||||||
|
@ -78,18 +86,13 @@ class EventCommentFieldInline(admin.TabularInline):
|
||||||
|
|
||||||
|
|
||||||
class EventOptionAdmin(admin.ModelAdmin):
|
class EventOptionAdmin(admin.ModelAdmin):
|
||||||
search_fields = ('event__title', 'name')
|
search_fields = ("event__title", "name")
|
||||||
inlines = [
|
inlines = [EventOptionChoiceInline]
|
||||||
EventOptionChoiceInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class EventAdmin(admin.ModelAdmin):
|
class EventAdmin(admin.ModelAdmin):
|
||||||
search_fields = ('title', 'location', 'description')
|
search_fields = ("title", "location", "description")
|
||||||
inlines = [
|
inlines = [EventOptionInline, EventCommentFieldInline]
|
||||||
EventOptionInline,
|
|
||||||
EventCommentFieldInline,
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class CofProfileInline(admin.StackedInline):
|
class CofProfileInline(admin.StackedInline):
|
||||||
|
@ -98,10 +101,9 @@ class CofProfileInline(admin.StackedInline):
|
||||||
|
|
||||||
|
|
||||||
class FkeyLookup(object):
|
class FkeyLookup(object):
|
||||||
def __init__(self, fkeydecl, short_description=None,
|
def __init__(self, fkeydecl, short_description=None, admin_order_field=None):
|
||||||
admin_order_field=None):
|
self.fk, fkattrs = fkeydecl.split("__", 1)
|
||||||
self.fk, fkattrs = fkeydecl.split('__', 1)
|
self.fkattrs = fkattrs.split("__")
|
||||||
self.fkattrs = fkattrs.split('__')
|
|
||||||
|
|
||||||
self.short_description = short_description or self.fkattrs[-1]
|
self.short_description = short_description or self.fkattrs[-1]
|
||||||
self.admin_order_field = admin_order_field or fkeydecl
|
self.admin_order_field = admin_order_field or fkeydecl
|
||||||
|
@ -126,19 +128,19 @@ def ProfileInfo(field, short_description, boolean=False):
|
||||||
return getattr(self.profile, field)
|
return getattr(self.profile, field)
|
||||||
except CofProfile.DoesNotExist:
|
except CofProfile.DoesNotExist:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
getter.short_description = short_description
|
getter.short_description = short_description
|
||||||
getter.boolean = boolean
|
getter.boolean = boolean
|
||||||
return getter
|
return getter
|
||||||
|
|
||||||
User.profile_login_clipper = FkeyLookup("profile__login_clipper",
|
|
||||||
"Login clipper")
|
User.profile_login_clipper = FkeyLookup("profile__login_clipper", "Login clipper")
|
||||||
User.profile_phone = ProfileInfo("phone", "Téléphone")
|
User.profile_phone = ProfileInfo("phone", "Téléphone")
|
||||||
User.profile_occupation = ProfileInfo("occupation", "Occupation")
|
User.profile_occupation = ProfileInfo("occupation", "Occupation")
|
||||||
User.profile_departement = ProfileInfo("departement", "Departement")
|
User.profile_departement = ProfileInfo("departement", "Departement")
|
||||||
User.profile_mailing_cof = ProfileInfo("mailing_cof", "ML COF", True)
|
User.profile_mailing_cof = ProfileInfo("mailing_cof", "ML COF", True)
|
||||||
User.profile_mailing_bda = ProfileInfo("mailing_bda", "ML BdA", True)
|
User.profile_mailing_bda = ProfileInfo("mailing_bda", "ML BdA", True)
|
||||||
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente",
|
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente", "ML BdA-R", True)
|
||||||
"ML BdA-R", True)
|
|
||||||
|
|
||||||
|
|
||||||
class UserProfileAdmin(UserAdmin):
|
class UserProfileAdmin(UserAdmin):
|
||||||
|
@ -147,7 +149,8 @@ class UserProfileAdmin(UserAdmin):
|
||||||
return obj.profile.is_buro
|
return obj.profile.is_buro
|
||||||
except CofProfile.DoesNotExist:
|
except CofProfile.DoesNotExist:
|
||||||
return False
|
return False
|
||||||
is_buro.short_description = 'Membre du Buro'
|
|
||||||
|
is_buro.short_description = "Membre du Buro"
|
||||||
is_buro.boolean = True
|
is_buro.boolean = True
|
||||||
|
|
||||||
def is_cof(self, obj):
|
def is_cof(self, obj):
|
||||||
|
@ -155,44 +158,50 @@ class UserProfileAdmin(UserAdmin):
|
||||||
return obj.profile.is_cof
|
return obj.profile.is_cof
|
||||||
except CofProfile.DoesNotExist:
|
except CofProfile.DoesNotExist:
|
||||||
return False
|
return False
|
||||||
is_cof.short_description = 'Membre du COF'
|
|
||||||
|
is_cof.short_description = "Membre du COF"
|
||||||
is_cof.boolean = True
|
is_cof.boolean = True
|
||||||
|
|
||||||
list_display = (
|
list_display = UserAdmin.list_display + (
|
||||||
UserAdmin.list_display
|
"profile_login_clipper",
|
||||||
+ ('profile_login_clipper', 'profile_phone', 'profile_occupation',
|
"profile_phone",
|
||||||
'profile_mailing_cof', 'profile_mailing_bda',
|
"profile_occupation",
|
||||||
'profile_mailing_bda_revente', 'is_cof', 'is_buro', )
|
"profile_mailing_cof",
|
||||||
|
"profile_mailing_bda",
|
||||||
|
"profile_mailing_bda_revente",
|
||||||
|
"is_cof",
|
||||||
|
"is_buro",
|
||||||
)
|
)
|
||||||
list_display_links = ('username', 'email', 'first_name', 'last_name')
|
list_display_links = ("username", "email", "first_name", "last_name")
|
||||||
list_filter = UserAdmin.list_filter \
|
list_filter = UserAdmin.list_filter + (
|
||||||
+ ('profile__is_cof', 'profile__is_buro', 'profile__mailing_cof',
|
"profile__is_cof",
|
||||||
'profile__mailing_bda')
|
"profile__is_buro",
|
||||||
search_fields = UserAdmin.search_fields + ('profile__phone',)
|
"profile__mailing_cof",
|
||||||
inlines = [
|
"profile__mailing_bda",
|
||||||
CofProfileInline,
|
)
|
||||||
]
|
search_fields = UserAdmin.search_fields + ("profile__phone",)
|
||||||
|
inlines = [CofProfileInline]
|
||||||
|
|
||||||
staff_fieldsets = [
|
staff_fieldsets = [
|
||||||
(None, {'fields': ['username', 'password']}),
|
(None, {"fields": ["username", "password"]}),
|
||||||
(_('Personal info'), {'fields': ['first_name', 'last_name', 'email']}),
|
(_("Personal info"), {"fields": ["first_name", "last_name", "email"]}),
|
||||||
]
|
]
|
||||||
|
|
||||||
def get_fieldsets(self, request, user=None):
|
def get_fieldsets(self, request, user=None):
|
||||||
if not request.user.is_superuser:
|
if not request.user.is_superuser:
|
||||||
return self.staff_fieldsets
|
return self.staff_fieldsets
|
||||||
return super(UserProfileAdmin, self).get_fieldsets(request, user)
|
return super().get_fieldsets(request, user)
|
||||||
|
|
||||||
def save_model(self, request, user, form, change):
|
def save_model(self, request, user, form, change):
|
||||||
cof_group, created = Group.objects.get_or_create(name='COF')
|
cof_group, created = Group.objects.get_or_create(name="COF")
|
||||||
if created:
|
if created:
|
||||||
# Si le groupe COF n'était pas déjà dans la bdd
|
# Si le groupe COF n'était pas déjà dans la bdd
|
||||||
# On lui assigne les bonnes permissions
|
# On lui assigne les bonnes permissions
|
||||||
perms = Permission.objects.filter(
|
perms = Permission.objects.filter(
|
||||||
Q(content_type__app_label='gestioncof')
|
Q(content_type__app_label="gestioncof")
|
||||||
| Q(content_type__app_label='bda')
|
| Q(content_type__app_label="bda")
|
||||||
| (Q(content_type__app_label='auth')
|
| (Q(content_type__app_label="auth") & Q(content_type__model="user"))
|
||||||
& Q(content_type__model='user')))
|
)
|
||||||
cof_group.permissions = perms
|
cof_group.permissions = perms
|
||||||
# On y associe les membres du Burô
|
# On y associe les membres du Burô
|
||||||
cof_group.user_set = User.objects.filter(profile__is_buro=True)
|
cof_group.user_set = User.objects.filter(profile__is_buro=True)
|
||||||
|
@ -214,64 +223,97 @@ def user_str(self):
|
||||||
return "{} ({})".format(self.get_full_name(), self.username)
|
return "{} ({})".format(self.get_full_name(), self.username)
|
||||||
else:
|
else:
|
||||||
return self.username
|
return self.username
|
||||||
|
|
||||||
|
|
||||||
User.__str__ = user_str
|
User.__str__ = user_str
|
||||||
|
|
||||||
|
|
||||||
|
class EventRegistrationAdminForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
widgets = {"user": ModelSelect2(url="cof-user-autocomplete")}
|
||||||
|
|
||||||
|
|
||||||
class EventRegistrationAdmin(admin.ModelAdmin):
|
class EventRegistrationAdmin(admin.ModelAdmin):
|
||||||
form = autocomplete_light.modelform_factory(EventRegistration, exclude=[])
|
form = EventRegistrationAdminForm
|
||||||
list_display = ('__str__', 'event', 'user', 'paid')
|
|
||||||
list_filter = ('paid',)
|
list_display = ("__str__", "event", "user", "paid")
|
||||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
list_filter = ("paid",)
|
||||||
'user__email', 'event__title')
|
search_fields = (
|
||||||
|
"user__username",
|
||||||
|
"user__first_name",
|
||||||
|
"user__last_name",
|
||||||
|
"user__email",
|
||||||
|
"event__title",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAbilityAdmin(admin.ModelAdmin):
|
class PetitCoursAbilityAdmin(admin.ModelAdmin):
|
||||||
list_display = ('user', 'matiere', 'niveau', 'agrege')
|
list_display = ("user", "matiere", "niveau", "agrege")
|
||||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
search_fields = (
|
||||||
'user__email', 'matiere__name', 'niveau')
|
"user__username",
|
||||||
list_filter = ('matiere', 'niveau', 'agrege')
|
"user__first_name",
|
||||||
|
"user__last_name",
|
||||||
|
"user__email",
|
||||||
|
"matiere__name",
|
||||||
|
"niveau",
|
||||||
|
)
|
||||||
|
list_filter = ("matiere", "niveau", "agrege")
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAttributionAdmin(admin.ModelAdmin):
|
class PetitCoursAttributionAdmin(admin.ModelAdmin):
|
||||||
list_display = ('user', 'demande', 'matiere', 'rank', )
|
list_display = ("user", "demande", "matiere", "rank")
|
||||||
search_fields = ('user__username', 'matiere__name')
|
search_fields = ("user__username", "matiere__name")
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursAttributionCounterAdmin(admin.ModelAdmin):
|
class PetitCoursAttributionCounterAdmin(admin.ModelAdmin):
|
||||||
list_display = ('user', 'matiere', 'count', )
|
list_display = ("user", "matiere", "count")
|
||||||
list_filter = ('matiere',)
|
list_filter = ("matiere",)
|
||||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
search_fields = (
|
||||||
'user__email', 'matiere__name')
|
"user__username",
|
||||||
actions = ['reset', ]
|
"user__first_name",
|
||||||
|
"user__last_name",
|
||||||
|
"user__email",
|
||||||
|
"matiere__name",
|
||||||
|
)
|
||||||
|
actions = ["reset"]
|
||||||
actions_on_bottom = True
|
actions_on_bottom = True
|
||||||
|
|
||||||
def reset(self, request, queryset):
|
def reset(self, request, queryset):
|
||||||
queryset.update(count=0)
|
queryset.update(count=0)
|
||||||
|
|
||||||
reset.short_description = "Remise à zéro du compteur"
|
reset.short_description = "Remise à zéro du compteur"
|
||||||
|
|
||||||
|
|
||||||
class PetitCoursDemandeAdmin(admin.ModelAdmin):
|
class PetitCoursDemandeAdmin(admin.ModelAdmin):
|
||||||
list_display = ('name', 'email', 'agrege_requis', 'niveau', 'created',
|
list_display = (
|
||||||
'traitee', 'processed')
|
"name",
|
||||||
list_filter = ('traitee', 'niveau')
|
"email",
|
||||||
search_fields = ('name', 'email', 'phone', 'lieu', 'remarques')
|
"agrege_requis",
|
||||||
|
"niveau",
|
||||||
|
"created",
|
||||||
|
"traitee",
|
||||||
|
"processed",
|
||||||
|
)
|
||||||
|
list_filter = ("traitee", "niveau")
|
||||||
|
search_fields = ("name", "email", "phone", "lieu", "remarques")
|
||||||
|
|
||||||
|
|
||||||
class ClubAdminForm(forms.ModelForm):
|
class ClubAdminForm(forms.ModelForm):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super(ClubAdminForm, self).clean()
|
cleaned_data = super().clean()
|
||||||
respos = cleaned_data.get('respos')
|
respos = cleaned_data.get("respos")
|
||||||
members = cleaned_data.get('membres')
|
members = cleaned_data.get("membres")
|
||||||
for respo in respos.all():
|
for respo in respos.all():
|
||||||
if respo not in members:
|
if respo not in members:
|
||||||
raise forms.ValidationError(
|
raise forms.ValidationError(
|
||||||
"Erreur : le respo %s n'est pas membre du club."
|
"Erreur : le respo %s n'est pas membre du club."
|
||||||
% respo.get_full_name())
|
% respo.get_full_name()
|
||||||
|
)
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class ClubAdmin(admin.ModelAdmin):
|
class ClubAdmin(admin.ModelAdmin):
|
||||||
list_display = ['name']
|
list_display = ["name"]
|
||||||
form = ClubAdminForm
|
form = ClubAdminForm
|
||||||
|
|
||||||
|
|
||||||
|
@ -286,7 +328,6 @@ admin.site.register(Club, ClubAdmin)
|
||||||
admin.site.register(PetitCoursSubject)
|
admin.site.register(PetitCoursSubject)
|
||||||
admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin)
|
admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin)
|
||||||
admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin)
|
admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin)
|
||||||
admin.site.register(PetitCoursAttributionCounter,
|
admin.site.register(PetitCoursAttributionCounter, PetitCoursAttributionCounterAdmin)
|
||||||
PetitCoursAttributionCounterAdmin)
|
|
||||||
admin.site.register(PetitCoursDemande, PetitCoursDemandeAdmin)
|
admin.site.register(PetitCoursDemande, PetitCoursDemandeAdmin)
|
||||||
admin.site.register(EventRegistration, EventRegistrationAdmin)
|
admin.site.register(EventRegistration, EventRegistrationAdmin)
|
||||||
|
|
|
@ -2,14 +2,16 @@ from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
class GestioncofConfig(AppConfig):
|
class GestioncofConfig(AppConfig):
|
||||||
name = 'gestioncof'
|
name = "gestioncof"
|
||||||
verbose_name = "Gestion des adhérents du COF"
|
verbose_name = "Gestion des adhérents du COF"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
from . import signals
|
from . import signals # noqa
|
||||||
|
|
||||||
self.register_config()
|
self.register_config()
|
||||||
|
|
||||||
def register_config(self):
|
def register_config(self):
|
||||||
import djconfig
|
import djconfig
|
||||||
from .forms import GestioncofConfigForm
|
from .forms import GestioncofConfigForm
|
||||||
|
|
||||||
djconfig.register(GestioncofConfigForm)
|
djconfig.register(GestioncofConfigForm)
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
# -*- coding: utf-8 -*-
|
from django import shortcuts
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.http import Http404
|
||||||
from ldap3 import Connection
|
from ldap3 import Connection
|
||||||
|
|
||||||
from django import shortcuts
|
|
||||||
from django.http import Http404
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from gestioncof.models import CofProfile
|
|
||||||
from gestioncof.decorators import buro_required
|
from gestioncof.decorators import buro_required
|
||||||
|
from gestioncof.models import CofProfile
|
||||||
|
|
||||||
|
|
||||||
class Clipper(object):
|
class Clipper(object):
|
||||||
|
@ -21,68 +18,71 @@ class Clipper(object):
|
||||||
self.clipper = clipper
|
self.clipper = clipper
|
||||||
self.fullname = fullname
|
self.fullname = fullname
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{} ({})".format(self.clipper, self.fullname)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.clipper == other.clipper and self.fullname == other.fullname
|
||||||
|
|
||||||
|
|
||||||
@buro_required
|
@buro_required
|
||||||
def autocomplete(request):
|
def autocomplete(request):
|
||||||
if "q" not in request.GET:
|
if "q" not in request.GET:
|
||||||
raise Http404
|
raise Http404
|
||||||
q = request.GET['q']
|
q = request.GET["q"]
|
||||||
data = {
|
data = {"q": q}
|
||||||
'q': q,
|
|
||||||
}
|
|
||||||
|
|
||||||
queries = {}
|
queries = {}
|
||||||
bits = q.split()
|
bits = q.split()
|
||||||
|
|
||||||
# Fetching data from User and CofProfile tables
|
# Fetching data from User and CofProfile tables
|
||||||
queries['members'] = CofProfile.objects.filter(is_cof=True)
|
queries["members"] = CofProfile.objects.filter(is_cof=True)
|
||||||
queries['users'] = User.objects.filter(profile__is_cof=False)
|
queries["users"] = User.objects.filter(profile__is_cof=False)
|
||||||
for bit in bits:
|
for bit in bits:
|
||||||
queries['members'] = queries['members'].filter(
|
queries["members"] = queries["members"].filter(
|
||||||
Q(user__first_name__icontains=bit)
|
Q(user__first_name__icontains=bit)
|
||||||
| Q(user__last_name__icontains=bit)
|
| Q(user__last_name__icontains=bit)
|
||||||
| Q(user__username__icontains=bit)
|
| Q(user__username__icontains=bit)
|
||||||
| Q(login_clipper__icontains=bit))
|
| Q(login_clipper__icontains=bit)
|
||||||
queries['users'] = queries['users'].filter(
|
)
|
||||||
|
queries["users"] = queries["users"].filter(
|
||||||
Q(first_name__icontains=bit)
|
Q(first_name__icontains=bit)
|
||||||
| Q(last_name__icontains=bit)
|
| Q(last_name__icontains=bit)
|
||||||
| Q(username__icontains=bit))
|
| Q(username__icontains=bit)
|
||||||
queries['members'] = queries['members'].distinct()
|
)
|
||||||
queries['users'] = queries['users'].distinct()
|
queries["members"] = queries["members"].distinct()
|
||||||
|
queries["users"] = queries["users"].distinct()
|
||||||
|
|
||||||
# Clearing redundancies
|
# Clearing redundancies
|
||||||
usernames = (
|
usernames = set(queries["members"].values_list("login_clipper", flat="True")) | set(
|
||||||
set(queries['members'].values_list('login_clipper', flat='True'))
|
queries["users"].values_list("profile__login_clipper", flat="True")
|
||||||
| set(queries['users'].values_list('profile__login_clipper',
|
|
||||||
flat='True'))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fetching data from the SPI
|
# Fetching data from the SPI
|
||||||
if getattr(settings, 'LDAP_SERVER_URL', None):
|
if getattr(settings, "LDAP_SERVER_URL", None):
|
||||||
# Fetching
|
# Fetching
|
||||||
ldap_query = '(&{:s})'.format(''.join(
|
ldap_query = "(&{:s})".format(
|
||||||
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=bit)
|
"".join(
|
||||||
for bit in bits if bit.isalnum()
|
"(|(cn=*{bit:s}*)(uid=*{bit:s}*))".format(bit=bit)
|
||||||
))
|
for bit in bits
|
||||||
|
if bit.isalnum()
|
||||||
|
)
|
||||||
|
)
|
||||||
if ldap_query != "(&)":
|
if ldap_query != "(&)":
|
||||||
# If none of the bits were legal, we do not perform the query
|
# If none of the bits were legal, we do not perform the query
|
||||||
entries = None
|
entries = None
|
||||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
with Connection(settings.LDAP_SERVER_URL) as conn:
|
||||||
conn.search(
|
conn.search("dc=spi,dc=ens,dc=fr", ldap_query, attributes=["uid", "cn"])
|
||||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
|
||||||
attributes=['uid', 'cn']
|
|
||||||
)
|
|
||||||
entries = conn.entries
|
entries = conn.entries
|
||||||
# Clearing redundancies
|
# Clearing redundancies
|
||||||
queries['clippers'] = [
|
queries["clippers"] = [
|
||||||
Clipper(entry.uid.value, entry.cn.value)
|
Clipper(entry.uid.value, entry.cn.value)
|
||||||
for entry in entries
|
for entry in entries
|
||||||
if entry.uid.value
|
if entry.uid.value and entry.uid.value not in usernames
|
||||||
and entry.uid.value not in usernames
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Resulting data
|
# Resulting data
|
||||||
data.update(queries)
|
data.update(queries)
|
||||||
data['options'] = sum(len(query) for query in queries)
|
data["options"] = sum(len(query) for query in queries)
|
||||||
|
|
||||||
return shortcuts.render(request, "autocomplete_user.html", data)
|
return shortcuts.render(request, "autocomplete_user.html", data)
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import autocomplete_light
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
|
|
||||||
autocomplete_light.register(
|
|
||||||
User, search_fields=('username', 'first_name', 'last_name'),
|
|
||||||
attrs={'placeholder': 'membre...'}
|
|
||||||
)
|
|
1
gestioncof/cms/__init__.py
Normal file
1
gestioncof/cms/__init__.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
default_app_config = "gestioncof.cms.apps.COFCMSAppConfig"
|
7
gestioncof/cms/apps.py
Normal file
7
gestioncof/cms/apps.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class COFCMSAppConfig(AppConfig):
|
||||||
|
name = "gestioncof.cms"
|
||||||
|
label = "cofcms"
|
||||||
|
verbose_name = "CMS COF"
|
1193
gestioncof/cms/fixtures/cofcms.json
Normal file
1193
gestioncof/cms/fixtures/cofcms.json
Normal file
File diff suppressed because one or more lines are too long
940
gestioncof/cms/migrations/0001_initial.py
Normal file
940
gestioncof/cms/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,940 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.9 on 2018-01-20 19:10
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import wagtail.wagtailcore.blocks
|
||||||
|
import wagtail.wagtailcore.fields
|
||||||
|
import wagtail.wagtailimages.blocks
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import gestioncof.cms.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("wagtailcore", "0033_remove_golive_expiry_help_text"),
|
||||||
|
("wagtailimages", "0019_delete_filter"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="COFActuIndexPage",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"page_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title_fr",
|
||||||
|
models.CharField(
|
||||||
|
help_text="The page title as you'd like it to be seen by the public",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title_en",
|
||||||
|
models.CharField(
|
||||||
|
help_text="The page title as you'd like it to be seen by the public",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug_fr",
|
||||||
|
models.SlugField(
|
||||||
|
allow_unicode=True,
|
||||||
|
help_text="The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug_en",
|
||||||
|
models.SlugField(
|
||||||
|
allow_unicode=True,
|
||||||
|
help_text="The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"url_path_fr",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, editable=False, null=True, verbose_name="URL path"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"url_path_en",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, editable=False, null=True, verbose_name="URL path"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"seo_title_fr",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional. 'Search Engine Friendly' title. This will appear at the top of the browser window.",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="page title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"seo_title_en",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional. 'Search Engine Friendly' title. This will appear at the top of the browser window.",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="page title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"search_description_fr",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="search description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"search_description_en",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="search description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Index des actualités",
|
||||||
|
"verbose_name_plural": "Indexs des actualités",
|
||||||
|
},
|
||||||
|
bases=("wagtailcore.page", gestioncof.cms.models.COFActuIndexMixin),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="COFActuPage",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"page_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title_fr",
|
||||||
|
models.CharField(
|
||||||
|
help_text="The page title as you'd like it to be seen by the public",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title_en",
|
||||||
|
models.CharField(
|
||||||
|
help_text="The page title as you'd like it to be seen by the public",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug_fr",
|
||||||
|
models.SlugField(
|
||||||
|
allow_unicode=True,
|
||||||
|
help_text="The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug_en",
|
||||||
|
models.SlugField(
|
||||||
|
allow_unicode=True,
|
||||||
|
help_text="The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"url_path_fr",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, editable=False, null=True, verbose_name="URL path"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"url_path_en",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, editable=False, null=True, verbose_name="URL path"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"seo_title_fr",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional. 'Search Engine Friendly' title. This will appear at the top of the browser window.",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="page title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"seo_title_en",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional. 'Search Engine Friendly' title. This will appear at the top of the browser window.",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="page title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"search_description_fr",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="search description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"search_description_en",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="search description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"chapo",
|
||||||
|
models.TextField(blank=True, verbose_name="Description rapide"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"chapo_fr",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="Description rapide"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"chapo_en",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="Description rapide"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"body",
|
||||||
|
wagtail.wagtailcore.fields.RichTextField(verbose_name="Contenu"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"body_fr",
|
||||||
|
wagtail.wagtailcore.fields.RichTextField(
|
||||||
|
null=True, verbose_name="Contenu"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"body_en",
|
||||||
|
wagtail.wagtailcore.fields.RichTextField(
|
||||||
|
null=True, verbose_name="Contenu"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"is_event",
|
||||||
|
models.BooleanField(default=True, verbose_name="Évènement"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"date_start",
|
||||||
|
models.DateTimeField(verbose_name="Date et heure de début"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"date_end",
|
||||||
|
models.DateTimeField(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
verbose_name="Date et heure de fin",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"all_day",
|
||||||
|
models.BooleanField(default=False, verbose_name="Toute la journée"),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"image",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
to="wagtailimages.Image",
|
||||||
|
verbose_name="Image à la Une",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={"verbose_name": "Actualité", "verbose_name_plural": "Actualités"},
|
||||||
|
bases=("wagtailcore.page",),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="COFDirectoryEntryPage",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"page_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title_fr",
|
||||||
|
models.CharField(
|
||||||
|
help_text="The page title as you'd like it to be seen by the public",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title_en",
|
||||||
|
models.CharField(
|
||||||
|
help_text="The page title as you'd like it to be seen by the public",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug_fr",
|
||||||
|
models.SlugField(
|
||||||
|
allow_unicode=True,
|
||||||
|
help_text="The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug_en",
|
||||||
|
models.SlugField(
|
||||||
|
allow_unicode=True,
|
||||||
|
help_text="The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"url_path_fr",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, editable=False, null=True, verbose_name="URL path"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"url_path_en",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, editable=False, null=True, verbose_name="URL path"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"seo_title_fr",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional. 'Search Engine Friendly' title. This will appear at the top of the browser window.",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="page title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"seo_title_en",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional. 'Search Engine Friendly' title. This will appear at the top of the browser window.",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="page title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"search_description_fr",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="search description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"search_description_en",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="search description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"body",
|
||||||
|
wagtail.wagtailcore.fields.RichTextField(
|
||||||
|
verbose_name="Description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"body_fr",
|
||||||
|
wagtail.wagtailcore.fields.RichTextField(
|
||||||
|
null=True, verbose_name="Description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"body_en",
|
||||||
|
wagtail.wagtailcore.fields.RichTextField(
|
||||||
|
null=True, verbose_name="Description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"links",
|
||||||
|
wagtail.wagtailcore.fields.StreamField(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"lien",
|
||||||
|
wagtail.wagtailcore.blocks.StructBlock(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"url",
|
||||||
|
wagtail.wagtailcore.blocks.URLBlock(
|
||||||
|
required=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"texte",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"contact",
|
||||||
|
wagtail.wagtailcore.blocks.StructBlock(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"email",
|
||||||
|
wagtail.wagtailcore.blocks.EmailBlock(
|
||||||
|
required=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"texte",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"links_fr",
|
||||||
|
wagtail.wagtailcore.fields.StreamField(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"lien",
|
||||||
|
wagtail.wagtailcore.blocks.StructBlock(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"url",
|
||||||
|
wagtail.wagtailcore.blocks.URLBlock(
|
||||||
|
required=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"texte",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"contact",
|
||||||
|
wagtail.wagtailcore.blocks.StructBlock(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"email",
|
||||||
|
wagtail.wagtailcore.blocks.EmailBlock(
|
||||||
|
required=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"texte",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"links_en",
|
||||||
|
wagtail.wagtailcore.fields.StreamField(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"lien",
|
||||||
|
wagtail.wagtailcore.blocks.StructBlock(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"url",
|
||||||
|
wagtail.wagtailcore.blocks.URLBlock(
|
||||||
|
required=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"texte",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"contact",
|
||||||
|
wagtail.wagtailcore.blocks.StructBlock(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"email",
|
||||||
|
wagtail.wagtailcore.blocks.EmailBlock(
|
||||||
|
required=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"texte",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"image",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
to="wagtailimages.Image",
|
||||||
|
verbose_name="Image",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Éntrée d'annuaire",
|
||||||
|
"verbose_name_plural": "Éntrées d'annuaire",
|
||||||
|
},
|
||||||
|
bases=("wagtailcore.page",),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="COFDirectoryPage",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"page_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title_fr",
|
||||||
|
models.CharField(
|
||||||
|
help_text="The page title as you'd like it to be seen by the public",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title_en",
|
||||||
|
models.CharField(
|
||||||
|
help_text="The page title as you'd like it to be seen by the public",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug_fr",
|
||||||
|
models.SlugField(
|
||||||
|
allow_unicode=True,
|
||||||
|
help_text="The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug_en",
|
||||||
|
models.SlugField(
|
||||||
|
allow_unicode=True,
|
||||||
|
help_text="The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"url_path_fr",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, editable=False, null=True, verbose_name="URL path"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"url_path_en",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, editable=False, null=True, verbose_name="URL path"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"seo_title_fr",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional. 'Search Engine Friendly' title. This will appear at the top of the browser window.",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="page title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"seo_title_en",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional. 'Search Engine Friendly' title. This will appear at the top of the browser window.",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="page title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"search_description_fr",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="search description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"search_description_en",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="search description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"introduction",
|
||||||
|
wagtail.wagtailcore.fields.RichTextField(
|
||||||
|
verbose_name="Introduction"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"introduction_fr",
|
||||||
|
wagtail.wagtailcore.fields.RichTextField(
|
||||||
|
null=True, verbose_name="Introduction"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"introduction_en",
|
||||||
|
wagtail.wagtailcore.fields.RichTextField(
|
||||||
|
null=True, verbose_name="Introduction"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Annuaire (clubs, partenaires, bons plans...)",
|
||||||
|
"verbose_name_plural": "Annuaires",
|
||||||
|
},
|
||||||
|
bases=("wagtailcore.page",),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="COFPage",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"page_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title_fr",
|
||||||
|
models.CharField(
|
||||||
|
help_text="The page title as you'd like it to be seen by the public",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title_en",
|
||||||
|
models.CharField(
|
||||||
|
help_text="The page title as you'd like it to be seen by the public",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug_fr",
|
||||||
|
models.SlugField(
|
||||||
|
allow_unicode=True,
|
||||||
|
help_text="The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug_en",
|
||||||
|
models.SlugField(
|
||||||
|
allow_unicode=True,
|
||||||
|
help_text="The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"url_path_fr",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, editable=False, null=True, verbose_name="URL path"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"url_path_en",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, editable=False, null=True, verbose_name="URL path"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"seo_title_fr",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional. 'Search Engine Friendly' title. This will appear at the top of the browser window.",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="page title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"seo_title_en",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional. 'Search Engine Friendly' title. This will appear at the top of the browser window.",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="page title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"search_description_fr",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="search description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"search_description_en",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="search description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"body",
|
||||||
|
wagtail.wagtailcore.fields.StreamField(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"heading",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(
|
||||||
|
classname="full title"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("paragraph", wagtail.wagtailcore.blocks.RichTextBlock()),
|
||||||
|
("image", wagtail.wagtailimages.blocks.ImageChooserBlock()),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"body_fr",
|
||||||
|
wagtail.wagtailcore.fields.StreamField(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"heading",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(
|
||||||
|
classname="full title"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("paragraph", wagtail.wagtailcore.blocks.RichTextBlock()),
|
||||||
|
("image", wagtail.wagtailimages.blocks.ImageChooserBlock()),
|
||||||
|
),
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"body_en",
|
||||||
|
wagtail.wagtailcore.fields.StreamField(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"heading",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(
|
||||||
|
classname="full title"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("paragraph", wagtail.wagtailcore.blocks.RichTextBlock()),
|
||||||
|
("image", wagtail.wagtailimages.blocks.ImageChooserBlock()),
|
||||||
|
),
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Page normale COF",
|
||||||
|
"verbose_name_plural": "Pages normales COF",
|
||||||
|
},
|
||||||
|
bases=("wagtailcore.page",),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="COFRootPage",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"page_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title_fr",
|
||||||
|
models.CharField(
|
||||||
|
help_text="The page title as you'd like it to be seen by the public",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"title_en",
|
||||||
|
models.CharField(
|
||||||
|
help_text="The page title as you'd like it to be seen by the public",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug_fr",
|
||||||
|
models.SlugField(
|
||||||
|
allow_unicode=True,
|
||||||
|
help_text="The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"slug_en",
|
||||||
|
models.SlugField(
|
||||||
|
allow_unicode=True,
|
||||||
|
help_text="The name of the page as it will appear in URLs e.g http://domain.com/blog/[my-slug]/",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="slug",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"url_path_fr",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, editable=False, null=True, verbose_name="URL path"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"url_path_en",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, editable=False, null=True, verbose_name="URL path"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"seo_title_fr",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional. 'Search Engine Friendly' title. This will appear at the top of the browser window.",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="page title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"seo_title_en",
|
||||||
|
models.CharField(
|
||||||
|
blank=True,
|
||||||
|
help_text="Optional. 'Search Engine Friendly' title. This will appear at the top of the browser window.",
|
||||||
|
max_length=255,
|
||||||
|
null=True,
|
||||||
|
verbose_name="page title",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"search_description_fr",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="search description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"search_description_en",
|
||||||
|
models.TextField(
|
||||||
|
blank=True, null=True, verbose_name="search description"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"introduction",
|
||||||
|
wagtail.wagtailcore.fields.RichTextField(
|
||||||
|
verbose_name="Introduction"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"introduction_fr",
|
||||||
|
wagtail.wagtailcore.fields.RichTextField(
|
||||||
|
null=True, verbose_name="Introduction"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"introduction_en",
|
||||||
|
wagtail.wagtailcore.fields.RichTextField(
|
||||||
|
null=True, verbose_name="Introduction"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Racine site du COF",
|
||||||
|
"verbose_name_plural": "Racines site du COF",
|
||||||
|
},
|
||||||
|
bases=("wagtailcore.page", gestioncof.cms.models.COFActuIndexMixin),
|
||||||
|
),
|
||||||
|
]
|
160
gestioncof/cms/migrations/0002_utilpage_and_fixes.py
Normal file
160
gestioncof/cms/migrations/0002_utilpage_and_fixes.py
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.9 on 2018-04-28 13:46
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import wagtail.contrib.wagtailroutablepage.models
|
||||||
|
import wagtail.wagtailcore.blocks
|
||||||
|
import wagtail.wagtailcore.fields
|
||||||
|
import wagtail.wagtailimages.blocks
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("wagtailcore", "0039_collectionviewrestriction"),
|
||||||
|
("cofcms", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="COFUtilPage",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"page_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
primary_key=True,
|
||||||
|
serialize=False,
|
||||||
|
to="wagtailcore.Page",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"verbose_name": "Page utilitaire",
|
||||||
|
"verbose_name_plural": "Pages utilitaires",
|
||||||
|
},
|
||||||
|
bases=(
|
||||||
|
wagtail.contrib.wagtailroutablepage.models.RoutablePageMixin,
|
||||||
|
"wagtailcore.page",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name="cofdirectoryentrypage",
|
||||||
|
options={
|
||||||
|
"verbose_name": "Entrée d'annuaire",
|
||||||
|
"verbose_name_plural": "Entrées d'annuaire",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="cofdirectorypage",
|
||||||
|
name="alphabetique",
|
||||||
|
field=models.BooleanField(
|
||||||
|
default=True, verbose_name="Tri par ordre alphabétique ?"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="cofpage",
|
||||||
|
name="body",
|
||||||
|
field=wagtail.wagtailcore.fields.StreamField(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"heading",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(classname="full title"),
|
||||||
|
),
|
||||||
|
("paragraph", wagtail.wagtailcore.blocks.RichTextBlock()),
|
||||||
|
("image", wagtail.wagtailimages.blocks.ImageChooserBlock()),
|
||||||
|
(
|
||||||
|
"iframe",
|
||||||
|
wagtail.wagtailcore.blocks.StructBlock(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"url",
|
||||||
|
wagtail.wagtailcore.blocks.URLBlock(
|
||||||
|
"Adresse de la page"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"height",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(
|
||||||
|
"Hauteur (en pixels)"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="cofpage",
|
||||||
|
name="body_en",
|
||||||
|
field=wagtail.wagtailcore.fields.StreamField(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"heading",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(classname="full title"),
|
||||||
|
),
|
||||||
|
("paragraph", wagtail.wagtailcore.blocks.RichTextBlock()),
|
||||||
|
("image", wagtail.wagtailimages.blocks.ImageChooserBlock()),
|
||||||
|
(
|
||||||
|
"iframe",
|
||||||
|
wagtail.wagtailcore.blocks.StructBlock(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"url",
|
||||||
|
wagtail.wagtailcore.blocks.URLBlock(
|
||||||
|
"Adresse de la page"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"height",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(
|
||||||
|
"Hauteur (en pixels)"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="cofpage",
|
||||||
|
name="body_fr",
|
||||||
|
field=wagtail.wagtailcore.fields.StreamField(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"heading",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(classname="full title"),
|
||||||
|
),
|
||||||
|
("paragraph", wagtail.wagtailcore.blocks.RichTextBlock()),
|
||||||
|
("image", wagtail.wagtailimages.blocks.ImageChooserBlock()),
|
||||||
|
(
|
||||||
|
"iframe",
|
||||||
|
wagtail.wagtailcore.blocks.StructBlock(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"url",
|
||||||
|
wagtail.wagtailcore.blocks.URLBlock(
|
||||||
|
"Adresse de la page"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"height",
|
||||||
|
wagtail.wagtailcore.blocks.CharBlock(
|
||||||
|
"Hauteur (en pixels)"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
null=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
0
gestioncof/cms/migrations/__init__.py
Normal file
0
gestioncof/cms/migrations/__init__.py
Normal file
234
gestioncof/cms/models.py
Normal file
234
gestioncof/cms/models.py
Normal file
|
@ -0,0 +1,234 @@
|
||||||
|
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
||||||
|
from django.db import models
|
||||||
|
from wagtail.contrib.wagtailroutablepage.models import RoutablePageMixin, route
|
||||||
|
from wagtail.wagtailadmin.edit_handlers import FieldPanel, StreamFieldPanel
|
||||||
|
from wagtail.wagtailcore import blocks
|
||||||
|
from wagtail.wagtailcore.fields import RichTextField, StreamField
|
||||||
|
from wagtail.wagtailcore.models import Page
|
||||||
|
from wagtail.wagtailimages.blocks import ImageChooserBlock
|
||||||
|
from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
|
||||||
|
|
||||||
|
|
||||||
|
# Page pouvant afficher des actualités
|
||||||
|
class COFActuIndexMixin:
|
||||||
|
@property
|
||||||
|
def actus(self):
|
||||||
|
actus = COFActuPage.objects.live().order_by("-date_start").descendant_of(self)
|
||||||
|
return actus
|
||||||
|
|
||||||
|
|
||||||
|
# Racine du site du COF
|
||||||
|
class COFRootPage(Page, COFActuIndexMixin):
|
||||||
|
introduction = RichTextField("Introduction")
|
||||||
|
|
||||||
|
content_panels = Page.content_panels + [
|
||||||
|
FieldPanel("introduction", classname="full")
|
||||||
|
]
|
||||||
|
|
||||||
|
subpage_types = ["COFActuIndexPage", "COFPage", "COFDirectoryPage", "COFUtilPage"]
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Racine site du COF"
|
||||||
|
verbose_name_plural = "Racines site du COF"
|
||||||
|
|
||||||
|
|
||||||
|
# Block iframe
|
||||||
|
class IFrameBlock(blocks.StructBlock):
|
||||||
|
url = blocks.URLBlock("Adresse de la page")
|
||||||
|
height = blocks.CharBlock("Hauteur (en pixels)")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Page incluse (iframe, à utiliser avec précaution)"
|
||||||
|
verbose_name_plural = "Pages incluses (iframes, à utiliser avec précaution)"
|
||||||
|
template = "cofcms/iframe_block.html"
|
||||||
|
|
||||||
|
|
||||||
|
# Page lambda du site
|
||||||
|
class COFPage(Page):
|
||||||
|
body = StreamField(
|
||||||
|
[
|
||||||
|
("heading", blocks.CharBlock(classname="full title")),
|
||||||
|
("paragraph", blocks.RichTextBlock()),
|
||||||
|
("image", ImageChooserBlock()),
|
||||||
|
("iframe", IFrameBlock()),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
content_panels = Page.content_panels + [StreamFieldPanel("body")]
|
||||||
|
|
||||||
|
subpage_types = ["COFDirectoryPage", "COFPage"]
|
||||||
|
parent_page_types = ["COFPage", "COFRootPage"]
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Page normale COF"
|
||||||
|
verbose_name_plural = "Pages normales COF"
|
||||||
|
|
||||||
|
|
||||||
|
# Actualités
|
||||||
|
class COFActuIndexPage(Page, COFActuIndexMixin):
|
||||||
|
subpage_types = ["COFActuPage"]
|
||||||
|
parent_page_types = ["COFRootPage"]
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Index des actualités"
|
||||||
|
verbose_name_plural = "Indexs des actualités"
|
||||||
|
|
||||||
|
def get_context(self, request):
|
||||||
|
context = super().get_context(request)
|
||||||
|
actus = COFActuPage.objects.live().descendant_of(self).order_by("-date_end")
|
||||||
|
|
||||||
|
page = request.GET.get("page")
|
||||||
|
paginator = Paginator(actus, 5)
|
||||||
|
try:
|
||||||
|
actus = paginator.page(page)
|
||||||
|
except PageNotAnInteger:
|
||||||
|
actus = paginator.page(1)
|
||||||
|
except EmptyPage:
|
||||||
|
actus = paginator.page(paginator.num_pages)
|
||||||
|
|
||||||
|
context["actus"] = actus
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class COFActuPage(RoutablePageMixin, Page):
|
||||||
|
chapo = models.TextField("Description rapide", blank=True)
|
||||||
|
body = RichTextField("Contenu")
|
||||||
|
image = models.ForeignKey(
|
||||||
|
"wagtailimages.Image",
|
||||||
|
verbose_name="Image à la Une",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
)
|
||||||
|
is_event = models.BooleanField("Évènement", default=True, blank=True)
|
||||||
|
date_start = models.DateTimeField("Date et heure de début")
|
||||||
|
date_end = models.DateTimeField(
|
||||||
|
"Date et heure de fin", blank=True, default=None, null=True
|
||||||
|
)
|
||||||
|
all_day = models.BooleanField("Toute la journée", default=False, blank=True)
|
||||||
|
|
||||||
|
content_panels = Page.content_panels + [
|
||||||
|
ImageChooserPanel("image"),
|
||||||
|
FieldPanel("chapo"),
|
||||||
|
FieldPanel("body", classname="full"),
|
||||||
|
FieldPanel("is_event"),
|
||||||
|
FieldPanel("date_start"),
|
||||||
|
FieldPanel("date_end"),
|
||||||
|
FieldPanel("all_day"),
|
||||||
|
]
|
||||||
|
|
||||||
|
subpage_types = []
|
||||||
|
parent_page_types = ["COFActuIndexPage"]
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Actualité"
|
||||||
|
verbose_name_plural = "Actualités"
|
||||||
|
|
||||||
|
|
||||||
|
# Annuaires (Clubs, partenaires, bonnes adresses)
|
||||||
|
class COFDirectoryPage(Page):
|
||||||
|
introduction = RichTextField("Introduction")
|
||||||
|
alphabetique = models.BooleanField(
|
||||||
|
"Tri par ordre alphabétique ?", default=True, blank=True
|
||||||
|
)
|
||||||
|
|
||||||
|
content_panels = Page.content_panels + [
|
||||||
|
FieldPanel("introduction"),
|
||||||
|
FieldPanel("alphabetique"),
|
||||||
|
]
|
||||||
|
|
||||||
|
subpage_types = ["COFActuPage", "COFDirectoryEntryPage"]
|
||||||
|
parent_page_types = ["COFRootPage", "COFPage"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def entries(self):
|
||||||
|
entries = COFDirectoryEntryPage.objects.live().descendant_of(self)
|
||||||
|
if self.alphabetique:
|
||||||
|
entries = entries.order_by("title")
|
||||||
|
return entries
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Annuaire (clubs, partenaires, bons plans...)"
|
||||||
|
verbose_name_plural = "Annuaires"
|
||||||
|
|
||||||
|
|
||||||
|
class COFDirectoryEntryPage(Page):
|
||||||
|
body = RichTextField("Description")
|
||||||
|
links = StreamField(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"lien",
|
||||||
|
blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("url", blocks.URLBlock(required=True)),
|
||||||
|
("texte", blocks.CharBlock()),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"contact",
|
||||||
|
blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("email", blocks.EmailBlock(required=True)),
|
||||||
|
("texte", blocks.CharBlock()),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
image = models.ForeignKey(
|
||||||
|
"wagtailimages.Image",
|
||||||
|
verbose_name="Image",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
)
|
||||||
|
|
||||||
|
content_panels = Page.content_panels + [
|
||||||
|
ImageChooserPanel("image"),
|
||||||
|
FieldPanel("body", classname="full"),
|
||||||
|
StreamFieldPanel("links"),
|
||||||
|
]
|
||||||
|
|
||||||
|
subpage_types = []
|
||||||
|
parent_page_types = ["COFDirectoryPage"]
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Entrée d'annuaire"
|
||||||
|
verbose_name_plural = "Entrées d'annuaire"
|
||||||
|
|
||||||
|
|
||||||
|
# Pour le calendrier, ne doit pas être pris par ModelTranslation
|
||||||
|
class COFUtilPage(RoutablePageMixin, Page):
|
||||||
|
|
||||||
|
# Mini calendrier
|
||||||
|
@route(r"^calendar/(\d+)/(\d+)/$")
|
||||||
|
def calendar(self, request, year, month):
|
||||||
|
from .views import raw_calendar_view
|
||||||
|
|
||||||
|
return raw_calendar_view(request, int(year), int(month))
|
||||||
|
|
||||||
|
"""
|
||||||
|
ModelTranslation override le système des @route de wagtail, ce qui empêche
|
||||||
|
COFUtilPage d'être une page traduite pour pouvoir l'utiliser.
|
||||||
|
Ce qui fait planter `get_absolute_url` pour des problèmes d'héritage des
|
||||||
|
pages parentes (qui sont, elles, traduites).
|
||||||
|
Le seul moyen trouvé pour résoudre ce problème est de faire une autre
|
||||||
|
fonction à qui on fournit request en argument (donc pas un override de
|
||||||
|
get_absolute_url).
|
||||||
|
|
||||||
|
TODO : vérifier si ces problèmes ont été résolus dans les màj de wagtail
|
||||||
|
et modeltranslation
|
||||||
|
"""
|
||||||
|
|
||||||
|
def debugged_get_url(self, request):
|
||||||
|
parent = COFRootPage.objects.parent_of(self).live().first()
|
||||||
|
burl = parent.relative_url(request.site)
|
||||||
|
return burl + self.slug
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Page utilitaire"
|
||||||
|
verbose_name_plural = "Pages utilitaires"
|
25
gestioncof/cms/static/cofcms/config.rb
Normal file
25
gestioncof/cms/static/cofcms/config.rb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
require 'compass/import-once/activate'
|
||||||
|
# Require any additional compass plugins here.
|
||||||
|
|
||||||
|
# Set this to the root of your project when deployed:
|
||||||
|
http_path = "/"
|
||||||
|
css_dir = "css"
|
||||||
|
sass_dir = "sass"
|
||||||
|
images_dir = "images"
|
||||||
|
javascripts_dir = "js"
|
||||||
|
|
||||||
|
# You can select your preferred output style here (can be overridden via the command line):
|
||||||
|
# output_style = :expanded or :nested or :compact or :compressed
|
||||||
|
|
||||||
|
# To enable relative paths to assets via compass helper functions. Uncomment:
|
||||||
|
# relative_assets = true
|
||||||
|
|
||||||
|
# To disable debugging comments that display the original location of your selectors. Uncomment:
|
||||||
|
# line_comments = false
|
||||||
|
|
||||||
|
|
||||||
|
# If you prefer the indented syntax, you might want to regenerate this
|
||||||
|
# project again passing --syntax sass, or you can uncomment this:
|
||||||
|
# preferred_syntax = :sass
|
||||||
|
# and then run:
|
||||||
|
# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
|
5
gestioncof/cms/static/cofcms/css/ie.css
Normal file
5
gestioncof/cms/static/cofcms/css/ie.css
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/* Welcome to Compass. Use this file to write IE specific override styles.
|
||||||
|
* Import this file using the following HTML or equivalent:
|
||||||
|
* <!--[if IE]>
|
||||||
|
* <link href="/stylesheets/ie.css" media="screen, projection" rel="stylesheet" type="text/css" />
|
||||||
|
* <![endif]--> */
|
3
gestioncof/cms/static/cofcms/css/print.css
Normal file
3
gestioncof/cms/static/cofcms/css/print.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/* Welcome to Compass. Use this file to define print styles.
|
||||||
|
* Import this file using the following HTML or equivalent:
|
||||||
|
* <link href="/stylesheets/print.css" media="print" rel="stylesheet" type="text/css" /> */
|
669
gestioncof/cms/static/cofcms/css/screen.css
Normal file
669
gestioncof/cms/static/cofcms/css/screen.css
Normal file
|
@ -0,0 +1,669 @@
|
||||||
|
/* Welcome to Compass.
|
||||||
|
* In this file you should write your main styles. (or centralize your imports)
|
||||||
|
* Import this file using the following HTML or equivalent:
|
||||||
|
* <link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css" /> */
|
||||||
|
@import url("https://fonts.googleapis.com/css?family=Carter+One|Source+Sans+Pro:300,300i,700");
|
||||||
|
/* line 5, ../../../../../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
html, body, div, span, applet, object, iframe,
|
||||||
|
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||||
|
a, abbr, acronym, address, big, cite, code,
|
||||||
|
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||||
|
small, strike, strong, sub, sup, tt, var,
|
||||||
|
b, u, i, center,
|
||||||
|
dl, dt, dd, ol, ul, li,
|
||||||
|
fieldset, form, label, legend,
|
||||||
|
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||||
|
article, aside, canvas, details, embed,
|
||||||
|
figure, figcaption, footer, header, hgroup,
|
||||||
|
menu, nav, output, ruby, section, summary,
|
||||||
|
time, mark, audio, video {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 100%;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 22, ../../../../../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
html {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 24, ../../../../../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
ol, ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 26, ../../../../../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 28, ../../../../../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
caption, th, td {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: normal;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 30, ../../../../../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
q, blockquote {
|
||||||
|
quotes: none;
|
||||||
|
}
|
||||||
|
/* line 103, ../../../../../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
q:before, q:after, blockquote:before, blockquote:after {
|
||||||
|
content: "";
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 32, ../../../../../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
a img {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 116, ../../../../../../../../../../var/lib/gems/2.3.0/gems/compass-core-1.0.3/stylesheets/compass/reset/_utilities.scss */
|
||||||
|
article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 12, ../sass/screen.scss */
|
||||||
|
*, *:after, *:before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 16, ../sass/screen.scss */
|
||||||
|
body {
|
||||||
|
background: #fefefe;
|
||||||
|
font: 17px "Source Sans Pro", "sans-serif";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 21, ../sass/screen.scss */
|
||||||
|
header {
|
||||||
|
background: #5B0012;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 25, ../sass/screen.scss */
|
||||||
|
h1, h2 {
|
||||||
|
font-family: "Carter One", "serif";
|
||||||
|
color: #90001C;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 30, ../sass/screen.scss */
|
||||||
|
h1 {
|
||||||
|
font-size: 2.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 34, ../sass/screen.scss */
|
||||||
|
h2 {
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 38, ../sass/screen.scss */
|
||||||
|
a {
|
||||||
|
color: #CC9500;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 44, ../sass/screen.scss */
|
||||||
|
h2 a {
|
||||||
|
font-weight: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 50, ../sass/screen.scss */
|
||||||
|
header a {
|
||||||
|
color: #fefefe;
|
||||||
|
}
|
||||||
|
/* line 53, ../sass/screen.scss */
|
||||||
|
header section {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
/* line 59, ../sass/screen.scss */
|
||||||
|
header section.bottom-menu {
|
||||||
|
justify-content: space-around;
|
||||||
|
text-align: center;
|
||||||
|
background: #90001C;
|
||||||
|
}
|
||||||
|
/* line 65, ../sass/screen.scss */
|
||||||
|
header h1 {
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
/* line 69, ../sass/screen.scss */
|
||||||
|
header nav ul {
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
/* line 71, ../sass/screen.scss */
|
||||||
|
header nav ul li {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
/* line 73, ../sass/screen.scss */
|
||||||
|
header nav ul li > * {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 15px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
/* line 78, ../sass/screen.scss */
|
||||||
|
header nav ul li > *:hover {
|
||||||
|
background: #280008;
|
||||||
|
}
|
||||||
|
/* line 84, ../sass/screen.scss */
|
||||||
|
header nav .lang-select {
|
||||||
|
display: inline-block;
|
||||||
|
height: 100%;
|
||||||
|
vertical-align: top;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
/* line 90, ../sass/screen.scss */
|
||||||
|
header nav .lang-select:before {
|
||||||
|
content: "";
|
||||||
|
color: #fff;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-left: 1px solid #fff;
|
||||||
|
height: calc(100% - 20px);
|
||||||
|
margin: 10px 0;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
/* line 102, ../sass/screen.scss */
|
||||||
|
header nav .lang-select a {
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/* line 106, ../sass/screen.scss */
|
||||||
|
header nav .lang-select a img {
|
||||||
|
display: block;
|
||||||
|
width: auto;
|
||||||
|
max-height: 20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 117, ../sass/screen.scss */
|
||||||
|
article {
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
/* line 119, ../sass/screen.scss */
|
||||||
|
article p, article ul {
|
||||||
|
margin: 0.4em 0;
|
||||||
|
}
|
||||||
|
/* line 122, ../sass/screen.scss */
|
||||||
|
article ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
/* line 124, ../sass/screen.scss */
|
||||||
|
article ul li {
|
||||||
|
list-style: outside;
|
||||||
|
}
|
||||||
|
/* line 128, ../sass/screen.scss */
|
||||||
|
article:last-child {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 133, ../sass/screen.scss */
|
||||||
|
.container {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
/* line 138, ../sass/screen.scss */
|
||||||
|
.container .aside-wrap {
|
||||||
|
position: absolute;
|
||||||
|
top: 30px;
|
||||||
|
height: 100%;
|
||||||
|
width: 25%;
|
||||||
|
left: 6px;
|
||||||
|
}
|
||||||
|
/* line 145, ../sass/screen.scss */
|
||||||
|
.container .aside-wrap .aside {
|
||||||
|
color: #222;
|
||||||
|
position: fixed;
|
||||||
|
position: sticky;
|
||||||
|
top: 5px;
|
||||||
|
width: 100%;
|
||||||
|
background: #FFC500;
|
||||||
|
padding: 15px;
|
||||||
|
box-shadow: -4px 4px 1px rgba(153, 118, 0, 0.3);
|
||||||
|
}
|
||||||
|
/* line 155, ../sass/screen.scss */
|
||||||
|
.container .aside-wrap .aside h2 {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
/* line 159, ../sass/screen.scss */
|
||||||
|
.container .aside-wrap .aside .calendar {
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/* line 164, ../sass/screen.scss */
|
||||||
|
.container .aside-wrap .aside a {
|
||||||
|
color: #997000;
|
||||||
|
}
|
||||||
|
/* line 170, ../sass/screen.scss */
|
||||||
|
.container .content {
|
||||||
|
max-width: 900px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
/* line 175, ../sass/screen.scss */
|
||||||
|
.container .content .intro {
|
||||||
|
border-bottom: 3px solid #7f7f7f;
|
||||||
|
margin: 20px 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding: 15px 5px;
|
||||||
|
}
|
||||||
|
/* line 184, ../sass/screen.scss */
|
||||||
|
.container .content section article {
|
||||||
|
background: #fff;
|
||||||
|
padding: 20px 30px;
|
||||||
|
box-shadow: -4px 4px 1px rgba(153, 118, 0, 0.3);
|
||||||
|
border: 1px solid rgba(153, 118, 0, 0.1);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
/* line 190, ../sass/screen.scss */
|
||||||
|
.container .content section article a {
|
||||||
|
color: #CC9500;
|
||||||
|
}
|
||||||
|
/* line 195, ../sass/screen.scss */
|
||||||
|
.container .content section article + h2 {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
/* line 199, ../sass/screen.scss */
|
||||||
|
.container .content section article + article {
|
||||||
|
margin-top: 25px;
|
||||||
|
}
|
||||||
|
/* line 203, ../sass/screen.scss */
|
||||||
|
.container .content section .image {
|
||||||
|
margin: 15px 0;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
/* line 208, ../sass/screen.scss */
|
||||||
|
.container .content section .image img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
box-shadow: -7px 7px 1px rgba(153, 118, 0, 0.2);
|
||||||
|
}
|
||||||
|
/* line 216, ../sass/screen.scss */
|
||||||
|
.container .content section.directory article.entry {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 600px;
|
||||||
|
max-height: 100%;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 6%;
|
||||||
|
}
|
||||||
|
/* line 223, ../sass/screen.scss */
|
||||||
|
.container .content section.directory article.entry .entry-image {
|
||||||
|
display: block;
|
||||||
|
float: right;
|
||||||
|
width: 150px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: -4px 4px 1px rgba(153, 118, 0, 0.2);
|
||||||
|
border-right: 1px solid rgba(153, 118, 0, 0.2);
|
||||||
|
border-top: 1px solid rgba(153, 118, 0, 0.2);
|
||||||
|
padding: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
transform: translateX(10px);
|
||||||
|
}
|
||||||
|
/* line 237, ../sass/screen.scss */
|
||||||
|
.container .content section.directory article.entry .entry-image img {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
/* line 245, ../sass/screen.scss */
|
||||||
|
.container .content section.directory article.entry ul.links {
|
||||||
|
margin-top: 10px;
|
||||||
|
border-top: 1px solid #90001C;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
/* line 253, ../sass/screen.scss */
|
||||||
|
.container .content section.actuhome {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: top;
|
||||||
|
}
|
||||||
|
/* line 259, ../sass/screen.scss */
|
||||||
|
.container .content section.actuhome article + article {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
/* line 263, ../sass/screen.scss */
|
||||||
|
.container .content section.actuhome article.actu {
|
||||||
|
position: relative;
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
max-width: 400px;
|
||||||
|
min-width: 300px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
/* line 272, ../sass/screen.scss */
|
||||||
|
.container .content section.actuhome article.actu .actu-header {
|
||||||
|
position: relative;
|
||||||
|
box-shadow: -4px 5px 1px rgba(153, 118, 0, 0.3);
|
||||||
|
border-right: 1px solid rgba(153, 118, 0, 0.2);
|
||||||
|
border-top: 1px solid rgba(153, 118, 0, 0.2);
|
||||||
|
min-height: 180px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
/* line 285, ../sass/screen.scss */
|
||||||
|
.container .content section.actuhome article.actu .actu-header h2 {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
padding: 5px;
|
||||||
|
text-shadow: 0 0 5px rgba(153, 118, 0, 0.8);
|
||||||
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
|
||||||
|
}
|
||||||
|
/* line 293, ../sass/screen.scss */
|
||||||
|
.container .content section.actuhome article.actu .actu-header h2 a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
/* line 299, ../sass/screen.scss */
|
||||||
|
.container .content section.actuhome article.actu .actu-misc {
|
||||||
|
background: white;
|
||||||
|
box-shadow: -2px 2px 1px rgba(153, 118, 0, 0.2);
|
||||||
|
border: 1px solid rgba(153, 118, 0, 0.2);
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: 0 10px;
|
||||||
|
padding: 15px;
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
/* line 308, ../sass/screen.scss */
|
||||||
|
.container .content section.actuhome article.actu .actu-misc .actu-minical {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/* line 311, ../sass/screen.scss */
|
||||||
|
.container .content section.actuhome article.actu .actu-misc .actu-dates {
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
/* line 318, ../sass/screen.scss */
|
||||||
|
.container .content section.actuhome article.actu .actu-overlay {
|
||||||
|
display: block;
|
||||||
|
background: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 5;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
/* line 334, ../sass/screen.scss */
|
||||||
|
.container .content section.actulist article.actu {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
/* line 339, ../sass/screen.scss */
|
||||||
|
.container .content section.actulist article.actu .actu-image {
|
||||||
|
width: 30%;
|
||||||
|
max-width: 200px;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center center;
|
||||||
|
}
|
||||||
|
/* line 345, ../sass/screen.scss */
|
||||||
|
.container .content section.actulist article.actu .actu-infos {
|
||||||
|
padding: 15px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
/* line 349, ../sass/screen.scss */
|
||||||
|
.container .content section.actulist article.actu .actu-infos .actu-dates {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
/* line 359, ../sass/screen.scss */
|
||||||
|
.container .aside-wrap + .content {
|
||||||
|
max-width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 364, ../sass/screen.scss */
|
||||||
|
.calendar {
|
||||||
|
color: rgba(0, 0, 0, 0.8);
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
/* line 368, ../sass/screen.scss */
|
||||||
|
.calendar td, .calendar th {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
/* line 375, ../sass/screen.scss */
|
||||||
|
.calendar th {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
/* line 379, ../sass/screen.scss */
|
||||||
|
.calendar td {
|
||||||
|
font-size: 0.8em;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
}
|
||||||
|
/* line 384, ../sass/screen.scss */
|
||||||
|
.calendar td.out {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
/* line 387, ../sass/screen.scss */
|
||||||
|
.calendar td.today {
|
||||||
|
border-bottom-color: #000;
|
||||||
|
}
|
||||||
|
/* line 390, ../sass/screen.scss */
|
||||||
|
.calendar td:nth-child(7), .calendar td:nth-child(6) {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
/* line 393, ../sass/screen.scss */
|
||||||
|
.calendar td.hasevent {
|
||||||
|
position: relative;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #90001C;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
/* line 399, ../sass/screen.scss */
|
||||||
|
.calendar td.hasevent > a {
|
||||||
|
padding: 3px;
|
||||||
|
color: #90001C !important;
|
||||||
|
}
|
||||||
|
/* line 404, ../sass/screen.scss */
|
||||||
|
.calendar td.hasevent ul.cal-events {
|
||||||
|
text-align: left;
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
background: #fff;
|
||||||
|
width: 150px;
|
||||||
|
left: -30px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 5px;
|
||||||
|
background-color: #90001C;
|
||||||
|
}
|
||||||
|
/* line 417, ../sass/screen.scss */
|
||||||
|
.calendar td.hasevent ul.cal-events .datename {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
/* line 420, ../sass/screen.scss */
|
||||||
|
.calendar td.hasevent ul.cal-events:before {
|
||||||
|
top: -12px;
|
||||||
|
left: 38px;
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
border: 6px solid transparent;
|
||||||
|
border-bottom-color: #90001C;
|
||||||
|
}
|
||||||
|
/* line 428, ../sass/screen.scss */
|
||||||
|
.calendar td.hasevent ul.cal-events a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
/* line 433, ../sass/screen.scss */
|
||||||
|
.calendar td.hasevent > a:hover {
|
||||||
|
background-color: #90001C;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
/* line 437, ../sass/screen.scss */
|
||||||
|
.calendar td.hasevent > a:hover + ul.cal-events {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 445, ../sass/screen.scss */
|
||||||
|
#calendar-wrap .details {
|
||||||
|
border-top: 1px solid #90001C;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
/* line 450, ../sass/screen.scss */
|
||||||
|
#calendar-wrap .details li.datename {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1em;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
/* line 451, ../sass/screen.scss */
|
||||||
|
#calendar-wrap .details li.datename:after {
|
||||||
|
content: " :";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 1, ../sass/_responsive.scss */
|
||||||
|
header .minimenu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
/* line 6, ../sass/_responsive.scss */
|
||||||
|
header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 100vh;
|
||||||
|
height: 60px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
/* line 16, ../sass/_responsive.scss */
|
||||||
|
header .minimenu {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: 3px;
|
||||||
|
top: 3px;
|
||||||
|
}
|
||||||
|
/* line 23, ../sass/_responsive.scss */
|
||||||
|
header section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
/* line 25, ../sass/_responsive.scss */
|
||||||
|
header section nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 31, ../sass/_responsive.scss */
|
||||||
|
header.expanded {
|
||||||
|
overflow: auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
/* line 35, ../sass/_responsive.scss */
|
||||||
|
header.expanded nav {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
/* line 38, ../sass/_responsive.scss */
|
||||||
|
header.expanded nav ul {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: right;
|
||||||
|
}
|
||||||
|
/* line 41, ../sass/_responsive.scss */
|
||||||
|
header.expanded nav ul li > * {
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* line 48, ../sass/_responsive.scss */
|
||||||
|
.container {
|
||||||
|
margin-top: 65px;
|
||||||
|
}
|
||||||
|
/* line 51, ../sass/_responsive.scss */
|
||||||
|
.container .content {
|
||||||
|
max-width: unset;
|
||||||
|
margin: 6px;
|
||||||
|
}
|
||||||
|
/* line 56, ../sass/_responsive.scss */
|
||||||
|
.container .content section article {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
/* line 60, ../sass/_responsive.scss */
|
||||||
|
.container .content section .image {
|
||||||
|
padding: 0;
|
||||||
|
margin: 10px -6px;
|
||||||
|
}
|
||||||
|
/* line 65, ../sass/_responsive.scss */
|
||||||
|
.container .content section.directory article.entry {
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
/* line 72, ../sass/_responsive.scss */
|
||||||
|
.container .aside-wrap + .content {
|
||||||
|
max-width: unset;
|
||||||
|
margin-top: 120px;
|
||||||
|
}
|
||||||
|
/* line 77, ../sass/_responsive.scss */
|
||||||
|
.container .aside-wrap {
|
||||||
|
z-index: 3;
|
||||||
|
top: 60px;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
height: auto;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
/* line 86, ../sass/_responsive.scss */
|
||||||
|
.container .aside-wrap .aside {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
top: 0;
|
||||||
|
position: unset;
|
||||||
|
}
|
||||||
|
/* line 92, ../sass/_responsive.scss */
|
||||||
|
.container .aside-wrap .aside > h2 {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
/* line 96, ../sass/_responsive.scss */
|
||||||
|
.container .aside-wrap .aside > h2:after {
|
||||||
|
content: "v";
|
||||||
|
font-family: "Source Sans Pro", "sans-serif";
|
||||||
|
font-weight: bold;
|
||||||
|
color: #CC9500;
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
/* line 106, ../sass/_responsive.scss */
|
||||||
|
.container .aside-wrap .aside:not(.expanded) .aside-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
/* line 111, ../sass/_responsive.scss */
|
||||||
|
.container .aside-wrap .aside ul {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
/* line 113, ../sass/_responsive.scss */
|
||||||
|
.container .aside-wrap .aside ul li {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
/* line 115, ../sass/_responsive.scss */
|
||||||
|
.container .aside-wrap .aside ul li > * {
|
||||||
|
display: block;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
BIN
gestioncof/cms/static/cofcms/images/en.png
Normal file
BIN
gestioncof/cms/static/cofcms/images/en.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
BIN
gestioncof/cms/static/cofcms/images/fr.png
Normal file
BIN
gestioncof/cms/static/cofcms/images/fr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 300 B |
11
gestioncof/cms/static/cofcms/images/minimenu.svg
Normal file
11
gestioncof/cms/static/cofcms/images/minimenu.svg
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="50px" height="50px" viewBox="0 0 50 50" enable-background="new 0 0 50 50" xml:space="preserve">
|
||||||
|
<path fill="none" stroke="#FFFFFF" stroke-width="2" d="M47.062,41.393c0,3.131-2.538,5.669-5.669,5.669H8.608 c-3.131,0-5.669-2.538-5.669-5.669V8.608c0-3.131,2.538-5.669,5.669-5.669h32.785c3.131,0,5.669,2.538,5.669,5.669V41.393z"/>
|
||||||
|
<g>
|
||||||
|
<line fill="none" stroke="#FFFFFF" stroke-width="3" x1="10.826" y1="15" x2="40.241" y2="15"/>
|
||||||
|
<line fill="none" stroke="#FFFFFF" stroke-width="3" x1="10.826" y1="25" x2="40.241" y2="25"/>
|
||||||
|
<line fill="none" stroke="#FFFFFF" stroke-width="3" x1="10.826" y1="35" x2="40.241" y2="35"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 998 B |
32
gestioncof/cms/static/cofcms/js/calendar.js
Normal file
32
gestioncof/cms/static/cofcms/js/calendar.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
$(function(){
|
||||||
|
var mem = {};
|
||||||
|
var ctt = $("#calendar-wrap");
|
||||||
|
makeInteractive();
|
||||||
|
|
||||||
|
function makeInteractive () {
|
||||||
|
$(".cal-btn").on("click", loadCalendar);
|
||||||
|
$(".hasevent a").on("click", showEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCalendar () {
|
||||||
|
var url = $(this).attr("cal-dest");
|
||||||
|
if (mem[url] != undefined) {
|
||||||
|
ctt.html(mem[url]);
|
||||||
|
makeInteractive();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctt.innerText = "Chargement...";
|
||||||
|
ctt.load(url, function () {
|
||||||
|
mem[url] = this.innerHTML;
|
||||||
|
makeInteractive();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showEvents() {
|
||||||
|
ctt.find(".details").remove();
|
||||||
|
ctt.append(
|
||||||
|
$("<div>", {class:"details"})
|
||||||
|
.html(this.nextElementSibling.outerHTML)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
13
gestioncof/cms/static/cofcms/js/script.js
Normal file
13
gestioncof/cms/static/cofcms/js/script.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
$(function() {
|
||||||
|
$(".facteur").on("click", function(){
|
||||||
|
var $this = $(this);
|
||||||
|
var sticker = $this.attr('data-mref')
|
||||||
|
.replace('pont', '.')
|
||||||
|
.replace('arbre', '@')
|
||||||
|
.replace(/(.)-/g, '$1');
|
||||||
|
|
||||||
|
var boite = $("<a>", {href:"ma"+"il"+"to:"+sticker}).text(sticker);
|
||||||
|
$(this).before(boite)
|
||||||
|
.remove();
|
||||||
|
})
|
||||||
|
});
|
11
gestioncof/cms/static/cofcms/sass/_colors.scss
Normal file
11
gestioncof/cms/static/cofcms/sass/_colors.scss
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
$fond: #fefefe;
|
||||||
|
$bandeau: #5B0012;
|
||||||
|
$sousbandeau: #90001C;
|
||||||
|
$aside: #FFC500;
|
||||||
|
$titre: $sousbandeau;
|
||||||
|
$lien: #CC9500;
|
||||||
|
$headerlien: $fond;
|
||||||
|
$ombres: darken($aside, 20%);
|
||||||
|
|
||||||
|
$bodyfont: "Source Sans Pro", "sans-serif";
|
||||||
|
$headfont: "Carter One", "serif";
|
124
gestioncof/cms/static/cofcms/sass/_responsive.scss
Normal file
124
gestioncof/cms/static/cofcms/sass/_responsive.scss
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
header .minimenu {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
header {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 100vh;
|
||||||
|
height: 60px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.minimenu {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: 3px;
|
||||||
|
top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
section {
|
||||||
|
display: block;
|
||||||
|
nav {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header.expanded {
|
||||||
|
overflow: auto;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
nav {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
ul {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: right;
|
||||||
|
li > * {
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
margin-top: 65px;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
max-width: unset;
|
||||||
|
margin: 6px;
|
||||||
|
|
||||||
|
section {
|
||||||
|
article {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
padding: 0;
|
||||||
|
margin: 10px -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.directory article.entry {
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside-wrap + .content {
|
||||||
|
max-width: unset;
|
||||||
|
margin-top: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside-wrap {
|
||||||
|
z-index: 3;
|
||||||
|
top: 60px;
|
||||||
|
position: fixed;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
height: auto;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
.aside {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
top: 0;
|
||||||
|
position: unset;
|
||||||
|
|
||||||
|
& > h2 {
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px 10px;
|
||||||
|
&:after {
|
||||||
|
content: "v";
|
||||||
|
font-family: $bodyfont;
|
||||||
|
font-weight: bold;
|
||||||
|
color: $lien;
|
||||||
|
position: absolute;
|
||||||
|
right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:not(.expanded) {
|
||||||
|
.aside-content {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
text-align: center;
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
& > * {
|
||||||
|
display: block;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
460
gestioncof/cms/static/cofcms/sass/screen.scss
Normal file
460
gestioncof/cms/static/cofcms/sass/screen.scss
Normal file
|
@ -0,0 +1,460 @@
|
||||||
|
/* Welcome to Compass.
|
||||||
|
* In this file you should write your main styles. (or centralize your imports)
|
||||||
|
* Import this file using the following HTML or equivalent:
|
||||||
|
* <link href="/stylesheets/screen.css" media="screen, projection" rel="stylesheet" type="text/css" /> */
|
||||||
|
|
||||||
|
@import url('https://fonts.googleapis.com/css?family=Carter+One|Source+Sans+Pro:300,300i,700');
|
||||||
|
|
||||||
|
@import "compass/reset";
|
||||||
|
|
||||||
|
@import "_colors";
|
||||||
|
|
||||||
|
*, *:after, *:before {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: $fond;
|
||||||
|
font: 17px $bodyfont;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
background: $bandeau;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2 {
|
||||||
|
font-family: $headfont;
|
||||||
|
color: $titre;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.6em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: $lien;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 a {
|
||||||
|
font-weight: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
a {
|
||||||
|
color: $headerlien;
|
||||||
|
}
|
||||||
|
section {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
&.bottom-menu {
|
||||||
|
justify-content: space-around;
|
||||||
|
text-align: center;
|
||||||
|
background: $sousbandeau;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
nav {
|
||||||
|
ul {
|
||||||
|
display: inline-flex;
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
& > * {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 15px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: darken($bandeau, 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.lang-select {
|
||||||
|
display: inline-block;
|
||||||
|
height: 100%;
|
||||||
|
vertical-align: top;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
color: #fff;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-left: 1px solid #fff;
|
||||||
|
height: calc(100% - 20px);
|
||||||
|
margin: 10px 0;
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
width: auto;
|
||||||
|
max-height: 20px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
line-height: 1.4;
|
||||||
|
p, ul {
|
||||||
|
margin: 0.4em 0;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
padding-left: 20px;
|
||||||
|
li {
|
||||||
|
list-style: outside;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.aside-wrap {
|
||||||
|
position: absolute;
|
||||||
|
top: 30px;
|
||||||
|
height: 100%;
|
||||||
|
width: 25%;
|
||||||
|
left: 6px;
|
||||||
|
|
||||||
|
.aside {
|
||||||
|
color: #222;
|
||||||
|
position: fixed;
|
||||||
|
position: sticky;
|
||||||
|
top: 5px;
|
||||||
|
width: 100%;
|
||||||
|
background: $aside;
|
||||||
|
padding: 15px;
|
||||||
|
box-shadow: -4px 4px 1px rgba($ombres, 0.3);
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar {
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: darken($lien, 10%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
max-width: 900px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 6px;
|
||||||
|
|
||||||
|
.intro {
|
||||||
|
border-bottom: 3px solid darken($fond, 50%);
|
||||||
|
margin: 20px 0;
|
||||||
|
margin-top: 5px;
|
||||||
|
padding: 15px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
section {
|
||||||
|
article {
|
||||||
|
background: #fff;
|
||||||
|
padding: 20px 30px;;
|
||||||
|
box-shadow: -4px 4px 1px rgba($ombres, 0.3);
|
||||||
|
border: 1px solid rgba($ombres, 0.1);
|
||||||
|
border-radius: 2px;
|
||||||
|
a {
|
||||||
|
color: $lien;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
article + h2 {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
article + article {
|
||||||
|
margin-top: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image {
|
||||||
|
margin: 15px 0;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
box-shadow: -7px 7px 1px rgba($ombres, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.directory {
|
||||||
|
article.entry {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 600px;
|
||||||
|
max-height: 100%;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 6%;
|
||||||
|
|
||||||
|
.entry-image {
|
||||||
|
display: block;
|
||||||
|
float: right;
|
||||||
|
width: 150px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: -4px 4px 1px rgba($ombres, 0.2);
|
||||||
|
border-right: 1px solid rgba($ombres, 0.2);
|
||||||
|
border-top: 1px solid rgba($ombres, 0.2);
|
||||||
|
padding: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
transform: translateX(10px);
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.links {
|
||||||
|
margin-top: 10px;
|
||||||
|
border-top: 1px solid $sousbandeau;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.actuhome {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: top;
|
||||||
|
|
||||||
|
article + article {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
article.actu {
|
||||||
|
position: relative;
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
max-width: 400px;
|
||||||
|
min-width: 300px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.actu-header {
|
||||||
|
position: relative;
|
||||||
|
box-shadow: -4px 5px 1px rgba($ombres, 0.3);
|
||||||
|
border-right: 1px solid rgba($ombres, 0.2);
|
||||||
|
border-top: 1px solid rgba($ombres, 0.2);
|
||||||
|
min-height: 180px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
padding: 5px;
|
||||||
|
text-shadow: 0 0 5px rgba($ombres, 0.8);
|
||||||
|
background: linear-gradient(to top, rgba(#000, 0.7), rgba(#000, 0));
|
||||||
|
a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actu-misc {
|
||||||
|
background: lighten($fond, 15%);
|
||||||
|
box-shadow: -2px 2px 1px rgba($ombres, 0.2);
|
||||||
|
border: 1px solid rgba($ombres, 0.2);
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: 0 10px;
|
||||||
|
padding: 15px;
|
||||||
|
padding-top: 5px;
|
||||||
|
|
||||||
|
.actu-minical {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.actu-dates {
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actu-overlay {
|
||||||
|
display: block;
|
||||||
|
background: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 5;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.actulist {
|
||||||
|
article.actu {
|
||||||
|
display:flex;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.actu-image {
|
||||||
|
width: 30%;
|
||||||
|
max-width: 200px;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center center;
|
||||||
|
}
|
||||||
|
.actu-infos {
|
||||||
|
padding: 15px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.actu-dates {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.aside-wrap + .content {
|
||||||
|
max-width: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.calendar {
|
||||||
|
color: rgba(#000, 0.8);
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
font-size: 0.8em;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
|
||||||
|
&.out {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
&.today {
|
||||||
|
border-bottom-color: #000;
|
||||||
|
}
|
||||||
|
&:nth-child(7), &:nth-child(6) {
|
||||||
|
background: rgba(#000, 0.2);
|
||||||
|
}
|
||||||
|
&.hasevent {
|
||||||
|
position: relative;
|
||||||
|
font-weight: bold;
|
||||||
|
color: $sousbandeau;
|
||||||
|
font-size: 1em;
|
||||||
|
|
||||||
|
& > a {
|
||||||
|
padding: 3px;
|
||||||
|
color: $sousbandeau !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.cal-events {
|
||||||
|
|
||||||
|
text-align: left;
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
background: #fff;
|
||||||
|
width: 150px;
|
||||||
|
left: -30px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 5px;
|
||||||
|
background-color: $sousbandeau;
|
||||||
|
|
||||||
|
.datename {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&:before {
|
||||||
|
top: -12px;
|
||||||
|
left: 38px;
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
border: 6px solid transparent;
|
||||||
|
border-bottom-color: $sousbandeau;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& > a:hover {
|
||||||
|
background-color: $sousbandeau;
|
||||||
|
color: #fff !important;
|
||||||
|
|
||||||
|
& + ul.cal-events {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#calendar-wrap .details {
|
||||||
|
border-top: 1px solid $sousbandeau;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding-top: 10px;
|
||||||
|
|
||||||
|
li.datename {
|
||||||
|
&:after {
|
||||||
|
content: " :";
|
||||||
|
}
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.1em;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@import "_responsive";
|
51
gestioncof/cms/templates/cofcms/base.html
Normal file
51
gestioncof/cms/templates/cofcms/base.html
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
{% load static menu_tags wagtailuserbar i18n wagtailcore_tags %}
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{% block title %}Association des élèves de l'ENS Ulm{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "cofcms/css/screen.css" %}"/>
|
||||||
|
{% block extra_head %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<header id="header">
|
||||||
|
<section class="top-menu">
|
||||||
|
<h1 class="cof"><a href="/">COF</a></h1>
|
||||||
|
<a class="minimenu" href="javascript:void(0)" onclick="document.getElementById('header').classList.toggle('expanded');"><img src="{% static "cofcms/images/minimenu.svg" %}"></a>
|
||||||
|
<nav>
|
||||||
|
{% flat_menu "cof-nav-ext" template="cofcms/base_nav.html" %}
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
<section class="bottom-menu">
|
||||||
|
<nav>
|
||||||
|
{% flat_menu "cof-nav-int" template="cofcms/base_nav.html" apply_active_classes=True %}
|
||||||
|
|
||||||
|
{% get_current_language as curlang %}
|
||||||
|
|
||||||
|
<div class="lang-select">
|
||||||
|
{% if curlang == 'en' %}
|
||||||
|
{% language 'fr' %}
|
||||||
|
<a href="{% pageurl self %}" title="Français"><img src="{% static "cofcms/images/fr.png" %}"></a>
|
||||||
|
{% endlanguage %}
|
||||||
|
{% else %}
|
||||||
|
{% language 'en' %}
|
||||||
|
<a href="{% pageurl self %}" title="English"><img src="{% static "cofcms/images/en.png" %}"></a>
|
||||||
|
{% endlanguage %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</section>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
{% block superaside %}{% endblock %}
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% wagtailuserbar %}
|
||||||
|
</body>
|
||||||
|
</html>
|
12
gestioncof/cms/templates/cofcms/base_aside.html
Normal file
12
gestioncof/cms/templates/cofcms/base_aside.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends "cofcms/base.html" %}
|
||||||
|
|
||||||
|
{% block superaside %}
|
||||||
|
<div class="aside-wrap">
|
||||||
|
<div class="aside" id="aside">
|
||||||
|
<h2 onclick="document.getElementById('aside').classList.toggle('expanded')">{% block aside_title %}{% endblock %}</h2>
|
||||||
|
<div class="aside-content">
|
||||||
|
{% block aside %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue