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
|
||||
media/
|
||||
*.log
|
||||
.sass-cache/
|
||||
*.sqlite3
|
||||
.coverage
|
||||
|
||||
# PyCharm
|
||||
.idea
|
||||
.cache
|
||||
|
||||
# VSCode
|
||||
.vscode/
|
|
@ -1,6 +1,4 @@
|
|||
services:
|
||||
- postgres:latest
|
||||
- redis:latest
|
||||
image: "python:3.5"
|
||||
|
||||
variables:
|
||||
# GestioCOF settings
|
||||
|
@ -10,7 +8,7 @@ variables:
|
|||
REDIS_PASSWD: "dummy"
|
||||
|
||||
# Cached packages
|
||||
PYTHONPATH: "$CI_PROJECT_DIR/vendor/python"
|
||||
PIP_CACHE_DIR: "$CI_PROJECT_DIR/vendor/pip"
|
||||
|
||||
# postgres service configuration
|
||||
POSTGRES_PASSWORD: "4KZt3nGPLVeWSvtBZPSM3fSzXpzEU4"
|
||||
|
@ -20,22 +18,44 @@ variables:
|
|||
# psql password authentication
|
||||
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:
|
||||
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:
|
||||
- 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
|
148
README.md
148
README.md
|
@ -1,17 +1,86 @@
|
|||
# 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
l'installation se fait en une commande.
|
||||
|
||||
Pour utiliser Vagrant, il faut le
|
||||
[télécharger](https://www.vagrantup.com/downloads.html) et l'installer.
|
||||
[télécharger](https://www.vagrantup.com/downloads.html) et l'installer.
|
||||
|
||||
Si vous êtes sous Linux, votre distribution propose probablement des paquets
|
||||
Vagrant dans le gestionnaire de paquets (la version sera moins récente, ce qui
|
||||
|
@ -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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
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 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.db.models import Sum, Count
|
||||
from django.db.models import Count, Sum
|
||||
from django.template.defaultfilters import pluralize
|
||||
from django.utils import timezone
|
||||
from django import forms
|
||||
from bda.models import Spectacle, Salle, Participant, ChoixSpectacle,\
|
||||
Attribution, Tirage, Quote, CategorieSpectacle, SpectacleRevente
|
||||
|
||||
from bda.models import (
|
||||
Attribution,
|
||||
CategorieSpectacle,
|
||||
ChoixSpectacle,
|
||||
Participant,
|
||||
Quote,
|
||||
Salle,
|
||||
Spectacle,
|
||||
SpectacleRevente,
|
||||
Tirage,
|
||||
)
|
||||
|
||||
|
||||
class ReadOnlyMixin(object):
|
||||
|
@ -24,8 +32,17 @@ class ReadOnlyMixin(object):
|
|||
return readonly_fields + self.readonly_fields_update
|
||||
|
||||
|
||||
class ChoixSpectacleAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
widgets = {
|
||||
"participant": ModelSelect2(url="bda-participant-autocomplete"),
|
||||
"spectacle": ModelSelect2(url="bda-spectacle-autocomplete"),
|
||||
}
|
||||
|
||||
|
||||
class ChoixSpectacleInline(admin.TabularInline):
|
||||
model = ChoixSpectacle
|
||||
form = ChoixSpectacleAdminForm
|
||||
sortable_field_name = "priority"
|
||||
|
||||
|
||||
|
@ -34,10 +51,10 @@ class AttributionTabularAdminForm(forms.ModelForm):
|
|||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
spectacles = Spectacle.objects.select_related('location')
|
||||
spectacles = Spectacle.objects.select_related("location")
|
||||
if self.listing is not None:
|
||||
spectacles = spectacles.filter(listing=self.listing)
|
||||
self.fields['spectacle'].queryset = spectacles
|
||||
self.fields["spectacle"].queryset = spectacles
|
||||
|
||||
|
||||
class WithoutListingAttributionTabularAdminForm(AttributionTabularAdminForm):
|
||||
|
@ -61,7 +78,7 @@ class AttributionInline(admin.TabularInline):
|
|||
|
||||
|
||||
class WithListingAttributionInline(AttributionInline):
|
||||
exclude = ('given', )
|
||||
exclude = ("given",)
|
||||
form = WithListingAttributionTabularAdminForm
|
||||
listing = True
|
||||
|
||||
|
@ -72,12 +89,10 @@ class WithoutListingAttributionInline(AttributionInline):
|
|||
|
||||
|
||||
class ParticipantAdminForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['choicesrevente'].queryset = (
|
||||
Spectacle.objects
|
||||
.select_related('location')
|
||||
self.fields["choicesrevente"].queryset = Spectacle.objects.select_related(
|
||||
"location"
|
||||
)
|
||||
|
||||
|
||||
|
@ -85,11 +100,13 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
|||
inlines = [WithListingAttributionInline, WithoutListingAttributionInline]
|
||||
|
||||
def get_queryset(self, request):
|
||||
return Participant.objects.annotate(nb_places=Count('attributions'),
|
||||
total=Sum('attributions__price'))
|
||||
return Participant.objects.annotate(
|
||||
nb_places=Count("attributions"), total=Sum("attributions__price")
|
||||
)
|
||||
|
||||
def nb_places(self, obj):
|
||||
return obj.nb_places
|
||||
|
||||
nb_places.admin_order_field = "nb_places"
|
||||
nb_places.short_description = "Nombre de places"
|
||||
|
||||
|
@ -99,33 +116,32 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
|||
return "%.02f €" % tot
|
||||
else:
|
||||
return "0 €"
|
||||
|
||||
total.admin_order_field = "total"
|
||||
total.short_description = "Total à payer"
|
||||
list_display = ("user", "nb_places", "total", "paid", "paymenttype",
|
||||
"tirage")
|
||||
list_display = ("user", "nb_places", "total", "paid", "paymenttype", "tirage")
|
||||
list_filter = ("paid", "tirage")
|
||||
search_fields = ('user__username', 'user__first_name', 'user__last_name')
|
||||
actions = ['send_attribs', ]
|
||||
search_fields = ("user__username", "user__first_name", "user__last_name")
|
||||
actions = ["send_attribs"]
|
||||
actions_on_bottom = True
|
||||
list_per_page = 400
|
||||
readonly_fields = ("total",)
|
||||
readonly_fields_update = ('user', 'tirage')
|
||||
readonly_fields_update = ("user", "tirage")
|
||||
form = ParticipantAdminForm
|
||||
|
||||
def send_attribs(self, request, queryset):
|
||||
datatuple = []
|
||||
for member in queryset.all():
|
||||
attribs = member.attributions.all()
|
||||
context = {'member': member.user}
|
||||
context = {"member": member.user}
|
||||
shortname = ""
|
||||
if len(attribs) == 0:
|
||||
shortname = "bda-attributions-decus"
|
||||
else:
|
||||
shortname = "bda-attributions"
|
||||
context['places'] = attribs
|
||||
context["places"] = attribs
|
||||
print(context)
|
||||
datatuple.append((shortname, context, "bda@ens.fr",
|
||||
[member.user.email]))
|
||||
datatuple.append((shortname, context, "bda@ens.fr", [member.user.email]))
|
||||
send_mass_custom_mail(datatuple)
|
||||
count = len(queryset.all())
|
||||
if count == 1:
|
||||
|
@ -134,63 +150,69 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
|||
else:
|
||||
message_bit = "%d membres ont" % count
|
||||
plural = "s"
|
||||
self.message_user(request, "%s été informé%s avec succès."
|
||||
% (message_bit, plural))
|
||||
self.message_user(
|
||||
request, "%s été informé%s avec succès." % (message_bit, plural)
|
||||
)
|
||||
|
||||
send_attribs.short_description = "Envoyer les résultats par mail"
|
||||
|
||||
|
||||
class AttributionAdminForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
if 'spectacle' in self.fields:
|
||||
self.fields['spectacle'].queryset = (
|
||||
Spectacle.objects
|
||||
.select_related('location')
|
||||
if "spectacle" in self.fields:
|
||||
self.fields["spectacle"].queryset = Spectacle.objects.select_related(
|
||||
"location"
|
||||
)
|
||||
if 'participant' in self.fields:
|
||||
self.fields['participant'].queryset = (
|
||||
Participant.objects
|
||||
.select_related('user', 'tirage')
|
||||
if "participant" in self.fields:
|
||||
self.fields["participant"].queryset = Participant.objects.select_related(
|
||||
"user", "tirage"
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(AttributionAdminForm, self).clean()
|
||||
cleaned_data = super().clean()
|
||||
participant = cleaned_data.get("participant")
|
||||
spectacle = cleaned_data.get("spectacle")
|
||||
if participant and spectacle:
|
||||
if participant.tirage != spectacle.tirage:
|
||||
raise forms.ValidationError(
|
||||
"Erreur : le participant et le spectacle n'appartiennent"
|
||||
"pas au même tirage")
|
||||
"pas au même tirage"
|
||||
)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin):
|
||||
def paid(self, obj):
|
||||
return obj.participant.paid
|
||||
paid.short_description = 'A payé'
|
||||
|
||||
paid.short_description = "A payé"
|
||||
paid.boolean = True
|
||||
list_display = ("id", "spectacle", "participant", "given", "paid")
|
||||
search_fields = ('spectacle__title', 'participant__user__username',
|
||||
'participant__user__first_name',
|
||||
'participant__user__last_name')
|
||||
search_fields = (
|
||||
"spectacle__title",
|
||||
"participant__user__username",
|
||||
"participant__user__first_name",
|
||||
"participant__user__last_name",
|
||||
)
|
||||
form = AttributionAdminForm
|
||||
readonly_fields_update = ('spectacle', 'participant')
|
||||
readonly_fields_update = ("spectacle", "participant")
|
||||
|
||||
|
||||
class ChoixSpectacleAdmin(admin.ModelAdmin):
|
||||
form = autocomplete_light.modelform_factory(ChoixSpectacle, exclude=[])
|
||||
form = ChoixSpectacleAdminForm
|
||||
|
||||
def tirage(self, obj):
|
||||
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")
|
||||
search_fields = ('participant__user__username',
|
||||
'participant__user__first_name',
|
||||
'participant__user__last_name',
|
||||
'spectacle__title')
|
||||
search_fields = (
|
||||
"participant__user__username",
|
||||
"participant__user__first_name",
|
||||
"participant__user__last_name",
|
||||
"spectacle__title",
|
||||
)
|
||||
|
||||
|
||||
class QuoteInline(admin.TabularInline):
|
||||
|
@ -200,42 +222,36 @@ class QuoteInline(admin.TabularInline):
|
|||
class SpectacleAdmin(admin.ModelAdmin):
|
||||
inlines = [QuoteInline]
|
||||
model = Spectacle
|
||||
list_display = ("title", "date", "tirage", "location", "slots", "price",
|
||||
"listing")
|
||||
list_filter = ("location", "tirage",)
|
||||
list_display = ("title", "date", "tirage", "location", "slots", "price", "listing")
|
||||
list_filter = ("location", "tirage")
|
||||
search_fields = ("title", "location__name")
|
||||
readonly_fields = ("rappel_sent", )
|
||||
readonly_fields = ("rappel_sent",)
|
||||
|
||||
|
||||
class TirageAdmin(admin.ModelAdmin):
|
||||
model = Tirage
|
||||
list_display = ("title", "ouverture", "fermeture", "active",
|
||||
"enable_do_tirage")
|
||||
readonly_fields = ("tokens", )
|
||||
list_filter = ("active", )
|
||||
search_fields = ("title", )
|
||||
list_display = ("title", "ouverture", "fermeture", "active", "enable_do_tirage")
|
||||
readonly_fields = ("tokens",)
|
||||
list_filter = ("active",)
|
||||
search_fields = ("title",)
|
||||
|
||||
|
||||
class SalleAdmin(admin.ModelAdmin):
|
||||
model = Salle
|
||||
search_fields = ('name', 'address')
|
||||
search_fields = ("name", "address")
|
||||
|
||||
|
||||
class SpectacleReventeAdminForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['answered_mail'].queryset = (
|
||||
Participant.objects
|
||||
.select_related('user', 'tirage')
|
||||
self.fields["confirmed_entry"].queryset = Participant.objects.select_related(
|
||||
"user", "tirage"
|
||||
)
|
||||
self.fields['seller'].queryset = (
|
||||
Participant.objects
|
||||
.select_related('user', 'tirage')
|
||||
self.fields["seller"].queryset = Participant.objects.select_related(
|
||||
"user", "tirage"
|
||||
)
|
||||
self.fields['soldTo'].queryset = (
|
||||
Participant.objects
|
||||
.select_related('user', 'tirage')
|
||||
self.fields["soldTo"].queryset = Participant.objects.select_related(
|
||||
"user", "tirage"
|
||||
)
|
||||
|
||||
|
||||
|
@ -243,6 +259,7 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
|||
"""
|
||||
Administration des reventes de spectacles
|
||||
"""
|
||||
|
||||
model = SpectacleRevente
|
||||
|
||||
def spectacle(self, obj):
|
||||
|
@ -254,12 +271,14 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
|||
list_display = ("spectacle", "seller", "date", "soldTo")
|
||||
raw_id_fields = ("attribution",)
|
||||
readonly_fields = ("date_tirage",)
|
||||
search_fields = ['attribution__spectacle__title',
|
||||
'seller__user__username',
|
||||
'seller__user__first_name',
|
||||
'seller__user__last_name']
|
||||
search_fields = [
|
||||
"attribution__spectacle__title",
|
||||
"seller__user__username",
|
||||
"seller__user__first_name",
|
||||
"seller__user__last_name",
|
||||
]
|
||||
|
||||
actions = ['transfer', 'reinit']
|
||||
actions = ["transfer", "reinit"]
|
||||
actions_on_bottom = True
|
||||
form = SpectacleReventeAdminForm
|
||||
|
||||
|
@ -275,10 +294,10 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
|||
attrib.save()
|
||||
self.message_user(
|
||||
request,
|
||||
"%d attribution%s %s été transférée%s avec succès." % (
|
||||
count, pluralize(count),
|
||||
pluralize(count, "a,ont"), pluralize(count))
|
||||
)
|
||||
"%d attribution%s %s été transférée%s avec succès."
|
||||
% (count, pluralize(count), pluralize(count, "a,ont"), pluralize(count)),
|
||||
)
|
||||
|
||||
transfer.short_description = "Transférer les reventes sélectionnées"
|
||||
|
||||
def reinit(self, request, queryset):
|
||||
|
@ -287,20 +306,15 @@ class SpectacleReventeAdmin(admin.ModelAdmin):
|
|||
"""
|
||||
count = queryset.count()
|
||||
for revente in queryset.filter(
|
||||
attribution__spectacle__date__gte=timezone.now()):
|
||||
revente.date = timezone.now() - timedelta(hours=1)
|
||||
revente.soldTo = None
|
||||
revente.notif_sent = False
|
||||
revente.tirage_done = False
|
||||
if revente.answered_mail:
|
||||
revente.answered_mail.clear()
|
||||
revente.save()
|
||||
attribution__spectacle__date__gte=timezone.now()
|
||||
):
|
||||
revente.reset(new_date=timezone.now() - timedelta(hours=1))
|
||||
self.message_user(
|
||||
request,
|
||||
"%d attribution%s %s été réinitialisée%s avec succès." % (
|
||||
count, pluralize(count),
|
||||
pluralize(count, "a,ont"), pluralize(count))
|
||||
)
|
||||
"%d attribution%s %s été réinitialisée%s avec succès."
|
||||
% (count, pluralize(count), pluralize(count, "a,ont"), pluralize(count)),
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
@ -22,7 +14,7 @@ class Algorithm(object):
|
|||
show.requests
|
||||
- on crée des tables de demandes pour chaque personne, afin de
|
||||
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 = []
|
||||
showdict = {}
|
||||
for show in shows:
|
||||
|
@ -60,16 +52,19 @@ class Algorithm(object):
|
|||
self.ranks[member][show] -= increment
|
||||
|
||||
def appendResult(self, l, member, show):
|
||||
l.append((member,
|
||||
self.ranks[member][show],
|
||||
self.origranks[member][show],
|
||||
self.choices[member][show].double))
|
||||
l.append(
|
||||
(
|
||||
member,
|
||||
self.ranks[member][show],
|
||||
self.origranks[member][show],
|
||||
self.choices[member][show].double,
|
||||
)
|
||||
)
|
||||
|
||||
def __call__(self, seed):
|
||||
random.seed(seed)
|
||||
results = []
|
||||
shows = sorted(self.shows, key=lambda x: x.nrequests / x.slots,
|
||||
reverse=True)
|
||||
shows = sorted(self.shows, key=lambda x: x.nrequests / x.slots, reverse=True)
|
||||
for show in shows:
|
||||
# On regroupe tous les gens ayant le même rang
|
||||
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:
|
||||
self.appendResult(winners, member, show)
|
||||
self.appendResult(winners, member, show)
|
||||
elif not self.choices[member][show].autoquit \
|
||||
and len(winners) < show.slots:
|
||||
elif (
|
||||
not self.choices[member][show].autoquit
|
||||
and len(winners) < show.slots
|
||||
):
|
||||
self.appendResult(winners, member, show)
|
||||
self.appendResult(losers, member, show)
|
||||
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.forms.models import BaseInlineFormSet
|
||||
from django.template import loader
|
||||
from django.utils import timezone
|
||||
|
||||
from bda.models import Attribution, Spectacle
|
||||
from bda.models import Attribution, Spectacle, SpectacleRevente
|
||||
|
||||
|
||||
class InscriptionInlineFormSet(BaseInlineFormSet):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
@ -18,9 +16,9 @@ class InscriptionInlineFormSet(BaseInlineFormSet):
|
|||
# set once for all "spectacle" field choices
|
||||
# - restrict choices to the spectacles of this tirage
|
||||
# - 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]
|
||||
self.force_choices('spectacle', choices)
|
||||
self.force_choices("spectacle", choices)
|
||||
|
||||
def force_choices(self, name, choices):
|
||||
"""Set choices of a field.
|
||||
|
@ -32,7 +30,7 @@ class InscriptionInlineFormSet(BaseInlineFormSet):
|
|||
for form in self.forms:
|
||||
field = form.fields[name]
|
||||
if field.empty_label is not None:
|
||||
field.choices = [('', field.empty_label)] + choices
|
||||
field.choices = [("", field.empty_label)] + choices
|
||||
else:
|
||||
field.choices = choices
|
||||
|
||||
|
@ -41,77 +39,146 @@ class TokenForm(forms.Form):
|
|||
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):
|
||||
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):
|
||||
attributions = AttributionModelMultipleChoiceField(
|
||||
label='',
|
||||
queryset=Attribution.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
|
||||
def __init__(self, participant, *args, **kwargs):
|
||||
super(ResellForm, self).__init__(*args, **kwargs)
|
||||
self.fields['attributions'].queryset = (
|
||||
participant.attribution_set
|
||||
.filter(spectacle__date__gte=timezone.now())
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["attributions"] = TemplateLabelField(
|
||||
queryset=participant.attribution_set.filter(
|
||||
spectacle__date__gte=timezone.now()
|
||||
)
|
||||
.exclude(revente__seller=participant)
|
||||
.select_related('spectacle', 'spectacle__location',
|
||||
'participant__user')
|
||||
.select_related("spectacle", "spectacle__location", "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):
|
||||
attributions = AttributionModelMultipleChoiceField(
|
||||
label='',
|
||||
queryset=Attribution.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False)
|
||||
|
||||
def __init__(self, participant, *args, **kwargs):
|
||||
super(AnnulForm, self).__init__(*args, **kwargs)
|
||||
self.fields['attributions'].queryset = (
|
||||
participant.attribution_set
|
||||
.filter(spectacle__date__gte=timezone.now(),
|
||||
revente__isnull=False,
|
||||
revente__notif_sent=False,
|
||||
revente__soldTo__isnull=True)
|
||||
.select_related('spectacle', 'spectacle__location',
|
||||
'participant__user')
|
||||
)
|
||||
|
||||
|
||||
class InscriptionReventeForm(forms.Form):
|
||||
spectacles = forms.ModelMultipleChoiceField(
|
||||
queryset=Spectacle.objects.none(),
|
||||
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())
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["reventes"] = TemplateLabelField(
|
||||
label="",
|
||||
queryset=participant.original_shows.filter(
|
||||
attribution__spectacle__date__gte=timezone.now(), soldTo__isnull=True
|
||||
)
|
||||
.select_related(
|
||||
"attribution__spectacle", "attribution__spectacle__location"
|
||||
)
|
||||
.order_by("-date"),
|
||||
widget=forms.CheckboxSelectMultiple,
|
||||
required=False,
|
||||
label_template_name="bda/forms/revente_self_label_table.html",
|
||||
option_template_name="bda/forms/checkbox_table.html",
|
||||
context_object_name="revente",
|
||||
)
|
||||
|
||||
|
||||
class SoldForm(forms.Form):
|
||||
attributions = AttributionModelMultipleChoiceField(
|
||||
label='',
|
||||
queryset=Attribution.objects.none(),
|
||||
widget=forms.CheckboxSelectMultiple)
|
||||
|
||||
def __init__(self, participant, *args, **kwargs):
|
||||
super(SoldForm, self).__init__(*args, **kwargs)
|
||||
self.fields['attributions'].queryset = (
|
||||
participant.attribution_set
|
||||
.filter(revente__isnull=False,
|
||||
revente__soldTo__isnull=False)
|
||||
.exclude(revente__soldTo=participant)
|
||||
.select_related('spectacle', 'spectacle__location',
|
||||
'participant__user')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields["reventes"] = TemplateLabelField(
|
||||
queryset=participant.original_shows.filter(soldTo__isnull=False)
|
||||
.exclude(soldTo=participant)
|
||||
.select_related(
|
||||
"attribution__spectacle", "attribution__spectacle__location"
|
||||
),
|
||||
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 random
|
||||
|
||||
from django.utils import timezone
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
|
||||
from gestioncof.management.base import MyBaseCommand
|
||||
from bda.models import Tirage, Spectacle, Salle, Participant, ChoixSpectacle
|
||||
from bda.models import ChoixSpectacle, Participant, Salle, Spectacle, Tirage
|
||||
from bda.views import do_tirage
|
||||
|
||||
from gestioncof.management.base import MyBaseCommand
|
||||
|
||||
# Où sont stockés les fichiers json
|
||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)),
|
||||
'data')
|
||||
DATA_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "data")
|
||||
|
||||
|
||||
class Command(MyBaseCommand):
|
||||
|
@ -27,27 +25,29 @@ class Command(MyBaseCommand):
|
|||
# ---
|
||||
|
||||
Tirage.objects.all().delete()
|
||||
Tirage.objects.bulk_create([
|
||||
Tirage(
|
||||
title="Tirage de test 1",
|
||||
ouverture=timezone.now()-timezone.timedelta(days=7),
|
||||
fermeture=timezone.now(),
|
||||
active=True
|
||||
),
|
||||
Tirage(
|
||||
title="Tirage de test 2",
|
||||
ouverture=timezone.now(),
|
||||
fermeture=timezone.now()+timezone.timedelta(days=60),
|
||||
active=True
|
||||
)
|
||||
])
|
||||
Tirage.objects.bulk_create(
|
||||
[
|
||||
Tirage(
|
||||
title="Tirage de test 1",
|
||||
ouverture=timezone.now() - timezone.timedelta(days=7),
|
||||
fermeture=timezone.now(),
|
||||
active=True,
|
||||
),
|
||||
Tirage(
|
||||
title="Tirage de test 2",
|
||||
ouverture=timezone.now(),
|
||||
fermeture=timezone.now() + timezone.timedelta(days=60),
|
||||
active=True,
|
||||
),
|
||||
]
|
||||
)
|
||||
tirages = Tirage.objects.all()
|
||||
|
||||
# ---
|
||||
# Salles
|
||||
# ---
|
||||
|
||||
locations = self.from_json('locations.json', DATA_DIR, Salle)
|
||||
locations = self.from_json("locations.json", DATA_DIR, Salle)
|
||||
|
||||
# ---
|
||||
# Spectacles
|
||||
|
@ -60,15 +60,13 @@ class Command(MyBaseCommand):
|
|||
"""
|
||||
show.tirage = random.choice(tirages)
|
||||
show.listing = bool(random.randint(0, 1))
|
||||
show.date = (
|
||||
show.tirage.fermeture
|
||||
+ timezone.timedelta(days=random.randint(60, 90))
|
||||
show.date = show.tirage.fermeture + timezone.timedelta(
|
||||
days=random.randint(60, 90)
|
||||
)
|
||||
show.location = random.choice(locations)
|
||||
return show
|
||||
shows = self.from_json(
|
||||
'shows.json', DATA_DIR, Spectacle, show_callback
|
||||
)
|
||||
|
||||
shows = self.from_json("shows.json", DATA_DIR, Spectacle, show_callback)
|
||||
|
||||
# ---
|
||||
# Inscriptions
|
||||
|
@ -79,23 +77,19 @@ class Command(MyBaseCommand):
|
|||
choices = []
|
||||
for user in User.objects.filter(profile__is_cof=True):
|
||||
for tirage in tirages:
|
||||
part, _ = Participant.objects.get_or_create(
|
||||
user=user,
|
||||
tirage=tirage
|
||||
)
|
||||
part, _ = Participant.objects.get_or_create(user=user, tirage=tirage)
|
||||
shows = random.sample(
|
||||
list(tirage.spectacle_set.all()),
|
||||
tirage.spectacle_set.count() // 2
|
||||
list(tirage.spectacle_set.all()), tirage.spectacle_set.count() // 2
|
||||
)
|
||||
for (rank, show) in enumerate(shows):
|
||||
choices.append(ChoixSpectacle(
|
||||
participant=part,
|
||||
spectacle=show,
|
||||
priority=rank + 1,
|
||||
double_choice=random.choice(
|
||||
['1', 'double', 'autoquit']
|
||||
choices.append(
|
||||
ChoixSpectacle(
|
||||
participant=part,
|
||||
spectacle=show,
|
||||
priority=rank + 1,
|
||||
double_choice=random.choice(["1", "double", "autoquit"]),
|
||||
)
|
||||
))
|
||||
)
|
||||
ChoixSpectacle.objects.bulk_create(choices)
|
||||
self.stdout.write("- {:d} inscriptions générées".format(len(choices)))
|
||||
|
||||
|
|
|
@ -1,43 +1,49 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Gestion en ligne de commande des reventes.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import timedelta
|
||||
from django.core.management import BaseCommand
|
||||
from django.utils import timezone
|
||||
|
||||
from bda.models import SpectacleRevente
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Envoie les mails de notification et effectue " \
|
||||
"les tirages au sort des reventes"
|
||||
help = (
|
||||
"Envoie les mails de notification et effectue les tirages au sort des reventes"
|
||||
)
|
||||
leave_locale_alone = True
|
||||
|
||||
def handle(self, *args, **options):
|
||||
now = timezone.now()
|
||||
reventes = SpectacleRevente.objects.all()
|
||||
for revente in reventes:
|
||||
# Check si < 24h
|
||||
if (revente.attribution.spectacle.date <=
|
||||
revente.date + timedelta(days=1)) and \
|
||||
now >= revente.date + timedelta(minutes=15) and \
|
||||
not revente.notif_sent:
|
||||
self.stdout.write(str(now))
|
||||
revente.mail_shotgun()
|
||||
self.stdout.write("Mail de disponibilité immédiate envoyé")
|
||||
# Check si délai de retrait dépassé
|
||||
elif (now >= revente.date + timedelta(hours=1) and
|
||||
not revente.notif_sent):
|
||||
# Le spectacle est bientôt et on a pas encore envoyé de mail :
|
||||
# on met la place au shotgun et on prévient.
|
||||
if revente.is_urgent and not revente.notif_sent:
|
||||
if revente.can_notif:
|
||||
self.stdout.write(str(now))
|
||||
revente.mail_shotgun()
|
||||
self.stdout.write(
|
||||
"Mails de disponibilité immédiate envoyés "
|
||||
"pour la revente [%s]" % revente
|
||||
)
|
||||
|
||||
# Le spectacle est dans plus longtemps : on prévient
|
||||
elif revente.can_notif and not revente.notif_sent:
|
||||
self.stdout.write(str(now))
|
||||
revente.send_notif()
|
||||
self.stdout.write("Mail d'inscription à une revente envoyé")
|
||||
# Check si tirage à faire
|
||||
elif (now >= revente.date_tirage and
|
||||
not revente.tirage_done):
|
||||
self.stdout.write(
|
||||
"Mails d'inscription à la revente [%s] envoyés" % revente
|
||||
)
|
||||
|
||||
# On fait le tirage
|
||||
elif now >= revente.date_tirage and not revente.tirage_done:
|
||||
self.stdout.write(str(now))
|
||||
revente.tirage()
|
||||
self.stdout.write("Tirage effectué, mails envoyés")
|
||||
winner = revente.tirage()
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils import timezone
|
||||
|
||||
from bda.models import Spectacle
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Envoie les mails de rappel des spectacles dont la date ' \
|
||||
'approche.\nNe renvoie pas les mails déjà envoyés.'
|
||||
help = (
|
||||
"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
|
||||
|
||||
def handle(self, *args, **options):
|
||||
now = timezone.now()
|
||||
delay = timedelta(days=4)
|
||||
shows = Spectacle.objects \
|
||||
.filter(date__range=(now, now+delay)) \
|
||||
.filter(tirage__active=True) \
|
||||
.filter(rappel_sent__isnull=True) \
|
||||
shows = (
|
||||
Spectacle.objects.filter(date__range=(now, now + delay))
|
||||
.filter(tirage__active=True)
|
||||
.filter(rappel_sent__isnull=True)
|
||||
.all()
|
||||
)
|
||||
for show in shows:
|
||||
show.send_rappel()
|
||||
self.stdout.write(
|
||||
'Mails de rappels pour %s envoyés avec succès.' % show)
|
||||
self.stdout.write("Mails de rappels pour %s envoyés avec succès." % show)
|
||||
if not shows:
|
||||
self.stdout.write('Aucun mail à envoyer.')
|
||||
self.stdout.write("Aucun mail à envoyer.")
|
||||
|
|
|
@ -1,108 +1,206 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
dependencies = [migrations.swappable_dependency(settings.AUTH_USER_MODEL)]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Attribution',
|
||||
name="Attribution",
|
||||
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(
|
||||
name='ChoixSpectacle',
|
||||
name="ChoixSpectacle",
|
||||
fields=[
|
||||
('id', 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')])),
|
||||
(
|
||||
"id",
|
||||
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={
|
||||
'ordering': ('priority',),
|
||||
'verbose_name': 'voeu',
|
||||
'verbose_name_plural': 'voeux',
|
||||
"ordering": ("priority",),
|
||||
"verbose_name": "voeu",
|
||||
"verbose_name_plural": "voeux",
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Participant',
|
||||
name="Participant",
|
||||
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')),
|
||||
('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')])),
|
||||
(
|
||||
"id",
|
||||
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(
|
||||
name='Salle',
|
||||
name="Salle",
|
||||
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')),
|
||||
('address', models.TextField(verbose_name=b'Adresse')),
|
||||
(
|
||||
"id",
|
||||
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(
|
||||
name='Spectacle',
|
||||
name="Spectacle",
|
||||
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')),
|
||||
('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')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
("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={
|
||||
'ordering': ('priority', 'date', 'title'),
|
||||
'verbose_name': 'Spectacle',
|
||||
"ordering": ("priority", "date", "title"),
|
||||
"verbose_name": "Spectacle",
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='participant',
|
||||
name='attributions',
|
||||
field=models.ManyToManyField(related_name='attributed_to', through='bda.Attribution', to='bda.Spectacle'),
|
||||
model_name="participant",
|
||||
name="attributions",
|
||||
field=models.ManyToManyField(
|
||||
related_name="attributed_to",
|
||||
through="bda.Attribution",
|
||||
to="bda.Spectacle",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='participant',
|
||||
name='choices',
|
||||
field=models.ManyToManyField(related_name='chosen_by', through='bda.ChoixSpectacle', to='bda.Spectacle'),
|
||||
model_name="participant",
|
||||
name="choices",
|
||||
field=models.ManyToManyField(
|
||||
related_name="chosen_by",
|
||||
through="bda.ChoixSpectacle",
|
||||
to="bda.Spectacle",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='participant',
|
||||
name='user',
|
||||
field=models.OneToOneField(to=settings.AUTH_USER_MODEL),
|
||||
model_name="participant",
|
||||
name="user",
|
||||
field=models.OneToOneField(
|
||||
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='choixspectacle',
|
||||
name='participant',
|
||||
field=models.ForeignKey(to='bda.Participant'),
|
||||
model_name="choixspectacle",
|
||||
name="participant",
|
||||
field=models.ForeignKey(to="bda.Participant", on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='choixspectacle',
|
||||
name='spectacle',
|
||||
field=models.ForeignKey(related_name='participants', to='bda.Spectacle'),
|
||||
model_name="choixspectacle",
|
||||
name="spectacle",
|
||||
field=models.ForeignKey(
|
||||
related_name="participants",
|
||||
to="bda.Spectacle",
|
||||
on_delete=models.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='attribution',
|
||||
name='participant',
|
||||
field=models.ForeignKey(to='bda.Participant'),
|
||||
model_name="attribution",
|
||||
name="participant",
|
||||
field=models.ForeignKey(to="bda.Participant", on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='attribution',
|
||||
name='spectacle',
|
||||
field=models.ForeignKey(related_name='attribues', to='bda.Spectacle'),
|
||||
model_name="attribution",
|
||||
name="spectacle",
|
||||
field=models.ForeignKey(
|
||||
related_name="attribues", to="bda.Spectacle", on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='choixspectacle',
|
||||
unique_together=set([('participant', 'spectacle')]),
|
||||
name="choixspectacle", unique_together=set([("participant", "spectacle")])
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
|
@ -36,49 +36,77 @@ def fill_tirage_fields(apps, schema_editor):
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0001_initial'),
|
||||
]
|
||||
dependencies = [("bda", "0001_initial")]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Tirage',
|
||||
name="Tirage",
|
||||
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')),
|
||||
('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')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
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(
|
||||
model_name='participant',
|
||||
name='user',
|
||||
field=models.ForeignKey(to=settings.AUTH_USER_MODEL),
|
||||
model_name="participant",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
to=settings.AUTH_USER_MODEL, on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
# Create fields `spectacle` for `Participant` and `Spectacle` models.
|
||||
# These fields are not nullable, but we first create them as nullable
|
||||
# to give a default value for existing instances of these models.
|
||||
migrations.AddField(
|
||||
model_name='participant',
|
||||
name='tirage',
|
||||
field=models.ForeignKey(to='bda.Tirage', null=True),
|
||||
model_name="participant",
|
||||
name="tirage",
|
||||
field=models.ForeignKey(
|
||||
to="bda.Tirage", null=True, on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='tirage',
|
||||
field=models.ForeignKey(to='bda.Tirage', null=True),
|
||||
model_name="spectacle",
|
||||
name="tirage",
|
||||
field=models.ForeignKey(
|
||||
to="bda.Tirage", null=True, on_delete=models.CASCADE
|
||||
),
|
||||
),
|
||||
migrations.RunPython(fill_tirage_fields, migrations.RunPython.noop),
|
||||
migrations.AlterField(
|
||||
model_name='participant',
|
||||
name='tirage',
|
||||
field=models.ForeignKey(to='bda.Tirage'),
|
||||
model_name="participant",
|
||||
name="tirage",
|
||||
field=models.ForeignKey(to="bda.Tirage", on_delete=models.CASCADE),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='tirage',
|
||||
field=models.ForeignKey(to='bda.Tirage'),
|
||||
model_name="spectacle",
|
||||
name="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):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0002_add_tirage'),
|
||||
]
|
||||
dependencies = [("bda", "0002_add_tirage")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='price',
|
||||
model_name="spectacle",
|
||||
name="price",
|
||||
field=models.FloatField(verbose_name=b"Prix d'une place"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='active',
|
||||
field=models.BooleanField(default=False, verbose_name=b'Tirage actif'),
|
||||
model_name="tirage",
|
||||
name="active",
|
||||
field=models.BooleanField(default=False, verbose_name=b"Tirage actif"),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -6,20 +6,22 @@ from django.db import migrations, models
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0003_update_tirage_and_spectacle'),
|
||||
]
|
||||
dependencies = [("bda", "0003_update_tirage_and_spectacle")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='listing',
|
||||
field=models.BooleanField(default=False, verbose_name=b'Les places sont sur listing'),
|
||||
model_name="spectacle",
|
||||
name="listing",
|
||||
field=models.BooleanField(
|
||||
default=False, verbose_name=b"Les places sont sur listing"
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='rappel_sent',
|
||||
field=models.DateTimeField(null=True, verbose_name=b'Mail de rappel envoy\xc3\xa9', blank=True),
|
||||
model_name="spectacle",
|
||||
name="rappel_sent",
|
||||
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):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0004_mails-rappel'),
|
||||
]
|
||||
dependencies = [("bda", "0004_mails-rappel")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='choixspectacle',
|
||||
name='priority',
|
||||
field=models.PositiveIntegerField(verbose_name='Priorit\xe9'),
|
||||
model_name="choixspectacle",
|
||||
name="priority",
|
||||
field=models.PositiveIntegerField(verbose_name="Priorit\xe9"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='priority',
|
||||
field=models.IntegerField(default=1000, verbose_name='Priorit\xe9'),
|
||||
model_name="spectacle",
|
||||
name="priority",
|
||||
field=models.IntegerField(default=1000, verbose_name="Priorit\xe9"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='rappel_sent',
|
||||
field=models.DateTimeField(null=True, verbose_name='Mail de rappel envoy\xe9', blank=True),
|
||||
model_name="spectacle",
|
||||
name="rappel_sent",
|
||||
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
|
||||
for tirage in Tirage.objects.using(db_alias).all():
|
||||
if tirage.tokens:
|
||||
tirage.tokens = "Before %s\n\"\"\"%s\"\"\"\n" % (
|
||||
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
|
||||
tirage.tokens)
|
||||
tirage.tokens = 'Before %s\n"""%s"""\n' % (
|
||||
timezone.now().strftime("%y-%m-%d %H:%M:%S"),
|
||||
tirage.tokens,
|
||||
)
|
||||
tirage.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0005_encoding'),
|
||||
]
|
||||
dependencies = [("bda", "0005_encoding")]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField('tirage', 'token', 'tokens'),
|
||||
migrations.RenameField("tirage", "token", "tokens"),
|
||||
migrations.AddField(
|
||||
model_name='tirage',
|
||||
name='enable_do_tirage',
|
||||
model_name="tirage",
|
||||
name="enable_do_tirage",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=b'Le tirage peut \xc3\xaatre lanc\xc3\xa9'),
|
||||
default=False, verbose_name=b"Le tirage peut \xc3\xaatre lanc\xc3\xa9"
|
||||
),
|
||||
),
|
||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
||||
]
|
||||
|
|
|
@ -1,89 +1,100 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0006_add_tirage_switch'),
|
||||
]
|
||||
dependencies = [("bda", "0006_add_tirage_switch")]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CategorieSpectacle',
|
||||
name="CategorieSpectacle",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True)),
|
||||
('name', models.CharField(max_length=100, verbose_name='Nom',
|
||||
unique=True)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
serialize=False,
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
),
|
||||
),
|
||||
(
|
||||
"name",
|
||||
models.CharField(max_length=100, verbose_name="Nom", unique=True),
|
||||
),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Cat\xe9gorie',
|
||||
},
|
||||
options={"verbose_name": "Cat\xe9gorie"},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Quote',
|
||||
name="Quote",
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False,
|
||||
auto_created=True, primary_key=True)),
|
||||
('text', models.TextField(verbose_name='Citation')),
|
||||
('author', models.CharField(max_length=200,
|
||||
verbose_name='Auteur')),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
verbose_name="ID",
|
||||
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(
|
||||
name='spectacle',
|
||||
options={'ordering': ('date', 'title'),
|
||||
'verbose_name': 'Spectacle'},
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='spectacle',
|
||||
name='priority',
|
||||
name="spectacle",
|
||||
options={"ordering": ("date", "title"), "verbose_name": "Spectacle"},
|
||||
),
|
||||
migrations.RemoveField(model_name="spectacle", name="priority"),
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='ext_link',
|
||||
model_name="spectacle",
|
||||
name="ext_link",
|
||||
field=models.CharField(
|
||||
max_length=500,
|
||||
verbose_name='Lien vers le site du spectacle',
|
||||
blank=True),
|
||||
verbose_name="Lien vers le site du spectacle",
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='image',
|
||||
field=models.ImageField(upload_to='imgs/shows/', null=True,
|
||||
verbose_name='Image', blank=True),
|
||||
model_name="spectacle",
|
||||
name="image",
|
||||
field=models.ImageField(
|
||||
upload_to="imgs/shows/", null=True, verbose_name="Image", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='enable_do_tirage',
|
||||
model_name="tirage",
|
||||
name="enable_do_tirage",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='Le tirage peut \xeatre lanc\xe9'),
|
||||
default=False, verbose_name="Le tirage peut \xeatre lanc\xe9"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='tokens',
|
||||
field=models.TextField(verbose_name='Graine(s) du tirage',
|
||||
blank=True),
|
||||
model_name="tirage",
|
||||
name="tokens",
|
||||
field=models.TextField(verbose_name="Graine(s) du tirage", blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='category',
|
||||
field=models.ForeignKey(blank=True, to='bda.CategorieSpectacle',
|
||||
null=True),
|
||||
model_name="spectacle",
|
||||
name="category",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
to="bda.CategorieSpectacle",
|
||||
on_delete=models.CASCADE,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectacle',
|
||||
name='vips',
|
||||
field=models.TextField(verbose_name='Personnalit\xe9s',
|
||||
blank=True),
|
||||
model_name="spectacle",
|
||||
name="vips",
|
||||
field=models.TextField(verbose_name="Personnalit\xe9s", blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='quote',
|
||||
name='spectacle',
|
||||
field=models.ForeignKey(to='bda.Spectacle'),
|
||||
model_name="quote",
|
||||
name="spectacle",
|
||||
field=models.ForeignKey(to="bda.Spectacle", on_delete=models.CASCADE),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,103 +1,110 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0007_extends_spectacle'),
|
||||
]
|
||||
dependencies = [("bda", "0007_extends_spectacle")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='choixspectacle',
|
||||
name='double_choice',
|
||||
model_name="choixspectacle",
|
||||
name="double_choice",
|
||||
field=models.CharField(
|
||||
verbose_name='Nombre de places',
|
||||
choices=[('1', '1 place'),
|
||||
('autoquit', '2 places si possible, 1 sinon'),
|
||||
('double', '2 places sinon rien')],
|
||||
max_length=10, default='1'),
|
||||
verbose_name="Nombre de places",
|
||||
choices=[
|
||||
("1", "1 place"),
|
||||
("autoquit", "2 places si possible, 1 sinon"),
|
||||
("double", "2 places sinon rien"),
|
||||
],
|
||||
max_length=10,
|
||||
default="1",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='participant',
|
||||
name='paymenttype',
|
||||
model_name="participant",
|
||||
name="paymenttype",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[('cash', 'Cash'), ('cb', 'CB'),
|
||||
('cheque', 'Chèque'), ('autre', 'Autre')],
|
||||
max_length=6, verbose_name='Moyen de paiement'),
|
||||
choices=[
|
||||
("cash", "Cash"),
|
||||
("cb", "CB"),
|
||||
("cheque", "Chèque"),
|
||||
("autre", "Autre"),
|
||||
],
|
||||
max_length=6,
|
||||
verbose_name="Moyen de paiement",
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='salle',
|
||||
name='address',
|
||||
field=models.TextField(verbose_name='Adresse'),
|
||||
model_name="salle",
|
||||
name="address",
|
||||
field=models.TextField(verbose_name="Adresse"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='salle',
|
||||
name='name',
|
||||
field=models.CharField(verbose_name='Nom', max_length=300),
|
||||
model_name="salle",
|
||||
name="name",
|
||||
field=models.CharField(verbose_name="Nom", max_length=300),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='date',
|
||||
field=models.DateTimeField(verbose_name='Date & heure'),
|
||||
model_name="spectacle",
|
||||
name="date",
|
||||
field=models.DateTimeField(verbose_name="Date & heure"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='description',
|
||||
field=models.TextField(verbose_name='Description', blank=True),
|
||||
model_name="spectacle",
|
||||
name="description",
|
||||
field=models.TextField(verbose_name="Description", blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='listing',
|
||||
field=models.BooleanField(
|
||||
verbose_name='Les places sont sur listing'),
|
||||
model_name="spectacle",
|
||||
name="listing",
|
||||
field=models.BooleanField(verbose_name="Les places sont sur listing"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='price',
|
||||
model_name="spectacle",
|
||||
name="price",
|
||||
field=models.FloatField(verbose_name="Prix d'une place"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='slots',
|
||||
field=models.IntegerField(verbose_name='Places'),
|
||||
model_name="spectacle",
|
||||
name="slots",
|
||||
field=models.IntegerField(verbose_name="Places"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='slots_description',
|
||||
field=models.TextField(verbose_name='Description des places',
|
||||
blank=True),
|
||||
model_name="spectacle",
|
||||
name="slots_description",
|
||||
field=models.TextField(verbose_name="Description des places", blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='spectacle',
|
||||
name='title',
|
||||
field=models.CharField(verbose_name='Titre', max_length=300),
|
||||
model_name="spectacle",
|
||||
name="title",
|
||||
field=models.CharField(verbose_name="Titre", max_length=300),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='active',
|
||||
field=models.BooleanField(verbose_name='Tirage actif',
|
||||
default=False),
|
||||
model_name="tirage",
|
||||
name="active",
|
||||
field=models.BooleanField(verbose_name="Tirage actif", default=False),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='fermeture',
|
||||
model_name="tirage",
|
||||
name="fermeture",
|
||||
field=models.DateTimeField(
|
||||
verbose_name='Date et heure de fermerture du tirage'),
|
||||
verbose_name="Date et heure de fermerture du tirage"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='ouverture',
|
||||
model_name="tirage",
|
||||
name="ouverture",
|
||||
field=models.DateTimeField(
|
||||
verbose_name="Date et heure d'ouverture du tirage"),
|
||||
verbose_name="Date et heure d'ouverture du tirage"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='tirage',
|
||||
name='title',
|
||||
field=models.CharField(verbose_name='Titre', max_length=300),
|
||||
model_name="tirage",
|
||||
name="title",
|
||||
field=models.CharField(verbose_name="Titre", max_length=300),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,66 +1,87 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0008_py3'),
|
||||
]
|
||||
dependencies = [("bda", "0008_py3")]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SpectacleRevente',
|
||||
name="SpectacleRevente",
|
||||
fields=[
|
||||
('id', models.AutoField(serialize=False, primary_key=True,
|
||||
auto_created=True, verbose_name='ID')),
|
||||
('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)),
|
||||
(
|
||||
"id",
|
||||
models.AutoField(
|
||||
serialize=False,
|
||||
primary_key=True,
|
||||
auto_created=True,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"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={
|
||||
'verbose_name': 'Revente',
|
||||
},
|
||||
options={"verbose_name": "Revente"},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='participant',
|
||||
name='choicesrevente',
|
||||
field=models.ManyToManyField(to='bda.Spectacle',
|
||||
related_name='subscribed',
|
||||
blank=True),
|
||||
model_name="participant",
|
||||
name="choicesrevente",
|
||||
field=models.ManyToManyField(
|
||||
to="bda.Spectacle", related_name="subscribed", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='answered_mail',
|
||||
field=models.ManyToManyField(to='bda.Participant',
|
||||
related_name='wanted',
|
||||
blank=True),
|
||||
model_name="spectaclerevente",
|
||||
name="answered_mail",
|
||||
field=models.ManyToManyField(
|
||||
to="bda.Participant", related_name="wanted", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='attribution',
|
||||
field=models.OneToOneField(to='bda.Attribution',
|
||||
related_name='revente'),
|
||||
model_name="spectaclerevente",
|
||||
name="attribution",
|
||||
field=models.OneToOneField(
|
||||
to="bda.Attribution", on_delete=models.CASCADE, related_name="revente"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='seller',
|
||||
field=models.ForeignKey(to='bda.Participant',
|
||||
verbose_name='Vendeur',
|
||||
related_name='original_shows'),
|
||||
model_name="spectaclerevente",
|
||||
name="seller",
|
||||
field=models.ForeignKey(
|
||||
to="bda.Participant",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Vendeur",
|
||||
related_name="original_shows",
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='soldTo',
|
||||
field=models.ForeignKey(to='bda.Participant',
|
||||
verbose_name='Vendue à', null=True,
|
||||
blank=True),
|
||||
model_name="spectaclerevente",
|
||||
name="soldTo",
|
||||
field=models.ForeignKey(
|
||||
to="bda.Participant",
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Vendue à",
|
||||
null=True,
|
||||
blank=True,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,33 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db import migrations, models
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def forwards_func(apps, schema_editor):
|
||||
SpectacleRevente = apps.get_model("bda", "SpectacleRevente")
|
||||
|
||||
for revente in SpectacleRevente.objects.all():
|
||||
is_expired = timezone.now() > revente.date_tirage()
|
||||
is_direct = (revente.attribution.spectacle.date >= revente.date and
|
||||
timezone.now() > revente.date + timedelta(minutes=15))
|
||||
is_direct = revente.attribution.spectacle.date >= revente.date and timezone.now() > revente.date + timedelta(
|
||||
minutes=15
|
||||
)
|
||||
revente.shotgun = is_expired or is_direct
|
||||
revente.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0009_revente'),
|
||||
]
|
||||
dependencies = [("bda", "0009_revente")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='spectaclerevente',
|
||||
name='shotgun',
|
||||
field=models.BooleanField(default=False, verbose_name='Disponible imm\xe9diatement'),
|
||||
model_name="spectaclerevente",
|
||||
name="shotgun",
|
||||
field=models.BooleanField(
|
||||
default=False, verbose_name="Disponible imm\xe9diatement"
|
||||
),
|
||||
),
|
||||
migrations.RunPython(forwards_func, migrations.RunPython.noop),
|
||||
]
|
||||
|
|
|
@ -6,17 +6,14 @@ from django.db import migrations, models
|
|||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('bda', '0010_spectaclerevente_shotgun'),
|
||||
]
|
||||
dependencies = [("bda", "0010_spectaclerevente_shotgun")]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='tirage',
|
||||
name='appear_catalogue',
|
||||
model_name="tirage",
|
||||
name="appear_catalogue",
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
verbose_name='Tirage à afficher dans le catalogue'
|
||||
default=False, 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 random
|
||||
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.core import mail
|
||||
from django.db import models
|
||||
from django.db.models import Count
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
from django.utils import timezone, formats
|
||||
from django.utils import formats, timezone
|
||||
|
||||
|
||||
def get_generic_user():
|
||||
generic, _ = User.objects.get_or_create(
|
||||
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
|
||||
|
||||
|
@ -28,15 +28,15 @@ class Tirage(models.Model):
|
|||
tokens = models.TextField("Graine(s) du tirage", blank=True)
|
||||
active = models.BooleanField("Tirage actif", default=False)
|
||||
appear_catalogue = models.BooleanField(
|
||||
"Tirage à afficher dans le catalogue",
|
||||
default=False
|
||||
"Tirage à afficher dans le catalogue", default=False
|
||||
)
|
||||
enable_do_tirage = models.BooleanField("Le tirage peut être lancé",
|
||||
default=False)
|
||||
enable_do_tirage = models.BooleanField("Le tirage peut être lancé", default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "%s - %s" % (self.title, formats.localize(
|
||||
timezone.template_localtime(self.fermeture)))
|
||||
return "%s - %s" % (
|
||||
self.title,
|
||||
formats.localize(timezone.template_localtime(self.fermeture)),
|
||||
)
|
||||
|
||||
|
||||
class Salle(models.Model):
|
||||
|
@ -48,7 +48,7 @@ class Salle(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):
|
||||
return self.name
|
||||
|
@ -59,26 +59,27 @@ class CategorieSpectacle(models.Model):
|
|||
|
||||
class Spectacle(models.Model):
|
||||
title = models.CharField("Titre", max_length=300)
|
||||
category = models.ForeignKey(CategorieSpectacle, blank=True, null=True)
|
||||
category = models.ForeignKey(
|
||||
CategorieSpectacle, on_delete=models.CASCADE, blank=True, null=True
|
||||
)
|
||||
date = models.DateTimeField("Date & heure")
|
||||
location = models.ForeignKey(Salle)
|
||||
vips = models.TextField('Personnalités', blank=True)
|
||||
location = models.ForeignKey(Salle, on_delete=models.CASCADE)
|
||||
vips = models.TextField("Personnalités", blank=True)
|
||||
description = models.TextField("Description", blank=True)
|
||||
slots_description = models.TextField("Description des places", blank=True)
|
||||
image = models.ImageField('Image', blank=True, null=True,
|
||||
upload_to='imgs/shows/')
|
||||
ext_link = models.CharField('Lien vers le site du spectacle', blank=True,
|
||||
max_length=500)
|
||||
image = models.ImageField("Image", blank=True, null=True, upload_to="imgs/shows/")
|
||||
ext_link = models.CharField(
|
||||
"Lien vers le site du spectacle", blank=True, max_length=500
|
||||
)
|
||||
price = models.FloatField("Prix d'une place")
|
||||
slots = models.IntegerField("Places")
|
||||
tirage = models.ForeignKey(Tirage)
|
||||
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
|
||||
listing = models.BooleanField("Les places sont sur listing")
|
||||
rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True,
|
||||
null=True)
|
||||
rappel_sent = models.DateTimeField("Mail de rappel envoyé", blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Spectacle"
|
||||
ordering = ("date", "title",)
|
||||
ordering = ("date", "title")
|
||||
|
||||
def timestamp(self):
|
||||
return "%d" % calendar.timegm(self.date.utctimetuple())
|
||||
|
@ -88,7 +89,7 @@ class Spectacle(models.Model):
|
|||
self.title,
|
||||
formats.localize(timezone.template_localtime(self.date)),
|
||||
self.location,
|
||||
self.price
|
||||
self.price,
|
||||
)
|
||||
|
||||
def getImgUrl(self):
|
||||
|
@ -97,7 +98,7 @@ class Spectacle(models.Model):
|
|||
"""
|
||||
try:
|
||||
return self.image.url
|
||||
except:
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def send_rappel(self):
|
||||
|
@ -107,19 +108,21 @@ class Spectacle(models.Model):
|
|||
"""
|
||||
# On récupère la liste des participants + le BdA
|
||||
members = list(
|
||||
User.objects
|
||||
.filter(participant__attributions=self)
|
||||
.annotate(nb_attr=Count("id")).order_by()
|
||||
User.objects.filter(participant__attributions=self)
|
||||
.annotate(nb_attr=Count("id"))
|
||||
.order_by()
|
||||
)
|
||||
bda_generic = get_generic_user()
|
||||
bda_generic.nb_attr = 1
|
||||
members.append(bda_generic)
|
||||
# On écrit un mail personnalisé à chaque participant
|
||||
datatuple = [(
|
||||
'bda-rappel',
|
||||
{'member': member, "nb_attr": member.nb_attr, 'show': self},
|
||||
settings.MAIL_DATA['rappels']['FROM'],
|
||||
[member.email])
|
||||
datatuple = [
|
||||
(
|
||||
"bda-rappel",
|
||||
{"member": member, "nb_attr": member.nb_attr, "show": self},
|
||||
settings.MAIL_DATA["rappels"]["FROM"],
|
||||
[member.email],
|
||||
)
|
||||
for member in members
|
||||
]
|
||||
send_mass_custom_mail(datatuple)
|
||||
|
@ -135,9 +138,9 @@ class Spectacle(models.Model):
|
|||
|
||||
|
||||
class Quote(models.Model):
|
||||
spectacle = models.ForeignKey(Spectacle)
|
||||
text = models.TextField('Citation')
|
||||
author = models.CharField('Auteur', max_length=200)
|
||||
spectacle = models.ForeignKey(Spectacle, on_delete=models.CASCADE)
|
||||
text = models.TextField("Citation")
|
||||
author = models.CharField("Auteur", max_length=200)
|
||||
|
||||
|
||||
PAYMENT_TYPES = (
|
||||
|
@ -149,129 +152,200 @@ PAYMENT_TYPES = (
|
|||
|
||||
|
||||
class Participant(models.Model):
|
||||
user = models.ForeignKey(User)
|
||||
choices = models.ManyToManyField(Spectacle,
|
||||
through="ChoixSpectacle",
|
||||
related_name="chosen_by")
|
||||
attributions = models.ManyToManyField(Spectacle,
|
||||
through="Attribution",
|
||||
related_name="attributed_to")
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
choices = models.ManyToManyField(
|
||||
Spectacle, through="ChoixSpectacle", related_name="chosen_by"
|
||||
)
|
||||
attributions = models.ManyToManyField(
|
||||
Spectacle, through="Attribution", related_name="attributed_to"
|
||||
)
|
||||
paid = models.BooleanField("A payé", default=False)
|
||||
paymenttype = models.CharField("Moyen de paiement",
|
||||
max_length=6, choices=PAYMENT_TYPES,
|
||||
blank=True)
|
||||
tirage = models.ForeignKey(Tirage)
|
||||
choicesrevente = models.ManyToManyField(Spectacle,
|
||||
related_name="subscribed",
|
||||
blank=True)
|
||||
paymenttype = models.CharField(
|
||||
"Moyen de paiement", max_length=6, choices=PAYMENT_TYPES, blank=True
|
||||
)
|
||||
tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE)
|
||||
choicesrevente = models.ManyToManyField(
|
||||
Spectacle, related_name="subscribed", blank=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return "%s - %s" % (self.user, self.tirage.title)
|
||||
|
||||
|
||||
DOUBLE_CHOICES = (
|
||||
("1", "1 place"),
|
||||
("autoquit", "2 places si possible, 1 sinon"),
|
||||
("double", "2 places sinon rien"),
|
||||
("double", "2 places si possible, 1 sinon"),
|
||||
("autoquit", "2 places sinon rien"),
|
||||
)
|
||||
|
||||
|
||||
class ChoixSpectacle(models.Model):
|
||||
participant = models.ForeignKey(Participant)
|
||||
spectacle = models.ForeignKey(Spectacle, related_name="participants")
|
||||
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
|
||||
spectacle = models.ForeignKey(
|
||||
Spectacle, on_delete=models.CASCADE, related_name="participants"
|
||||
)
|
||||
priority = models.PositiveIntegerField("Priorité")
|
||||
double_choice = models.CharField("Nombre de places",
|
||||
default="1", choices=DOUBLE_CHOICES,
|
||||
max_length=10)
|
||||
double_choice = models.CharField(
|
||||
"Nombre de places", default="1", choices=DOUBLE_CHOICES, max_length=10
|
||||
)
|
||||
|
||||
def get_double(self):
|
||||
return self.double_choice != "1"
|
||||
|
||||
double = property(get_double)
|
||||
|
||||
def get_autoquit(self):
|
||||
return self.double_choice == "autoquit"
|
||||
|
||||
autoquit = property(get_autoquit)
|
||||
|
||||
def __str__(self):
|
||||
return "Vœux de %s pour %s" % (
|
||||
self.participant.user.get_full_name(),
|
||||
self.spectacle.title)
|
||||
self.participant.user.get_full_name(),
|
||||
self.spectacle.title,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ("priority",)
|
||||
unique_together = (("participant", "spectacle",),)
|
||||
unique_together = (("participant", "spectacle"),)
|
||||
verbose_name = "voeu"
|
||||
verbose_name_plural = "voeux"
|
||||
|
||||
|
||||
class Attribution(models.Model):
|
||||
participant = models.ForeignKey(Participant)
|
||||
spectacle = models.ForeignKey(Spectacle, related_name="attribues")
|
||||
participant = models.ForeignKey(Participant, on_delete=models.CASCADE)
|
||||
spectacle = models.ForeignKey(
|
||||
Spectacle, on_delete=models.CASCADE, related_name="attribues"
|
||||
)
|
||||
given = models.BooleanField("Donnée", default=False)
|
||||
|
||||
def __str__(self):
|
||||
return "%s -- %s, %s" % (self.participant.user, self.spectacle.title,
|
||||
self.spectacle.date)
|
||||
return "%s -- %s, %s" % (
|
||||
self.participant.user,
|
||||
self.spectacle.title,
|
||||
self.spectacle.date,
|
||||
)
|
||||
|
||||
|
||||
class SpectacleRevente(models.Model):
|
||||
attribution = models.OneToOneField(Attribution,
|
||||
related_name="revente")
|
||||
date = models.DateTimeField("Date de mise en vente",
|
||||
default=timezone.now)
|
||||
answered_mail = models.ManyToManyField(Participant,
|
||||
related_name="wanted",
|
||||
blank=True)
|
||||
seller = models.ForeignKey(Participant,
|
||||
related_name="original_shows",
|
||||
verbose_name="Vendeur")
|
||||
soldTo = models.ForeignKey(Participant, blank=True, null=True,
|
||||
verbose_name="Vendue à")
|
||||
attribution = models.OneToOneField(
|
||||
Attribution, on_delete=models.CASCADE, related_name="revente"
|
||||
)
|
||||
date = models.DateTimeField("Date de mise en vente", default=timezone.now)
|
||||
confirmed_entry = models.ManyToManyField(
|
||||
Participant, related_name="entered", blank=True
|
||||
)
|
||||
seller = models.ForeignKey(
|
||||
Participant,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Vendeur",
|
||||
related_name="original_shows",
|
||||
)
|
||||
soldTo = models.ForeignKey(
|
||||
Participant,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Vendue à",
|
||||
blank=True,
|
||||
null=True,
|
||||
)
|
||||
|
||||
notif_sent = models.BooleanField("Notification envoyée",
|
||||
default=False)
|
||||
tirage_done = models.BooleanField("Tirage effectué",
|
||||
default=False)
|
||||
shotgun = models.BooleanField("Disponible immédiatement",
|
||||
default=False)
|
||||
notif_sent = models.BooleanField("Notification envoyée", default=False)
|
||||
|
||||
notif_time = models.DateTimeField(
|
||||
"Moment d'envoi de la notification", blank=True, null=True
|
||||
)
|
||||
|
||||
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
|
||||
def date_tirage(self):
|
||||
"""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
|
||||
- self.date - timedelta(hours=13))
|
||||
# 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
|
||||
return self.date + delay + timedelta(hours=1)
|
||||
|
||||
remaining_time = (
|
||||
self.attribution.spectacle.date - self.real_notif_time - self.min_margin
|
||||
)
|
||||
|
||||
delay = min(remaining_time, self.max_wait_time)
|
||||
|
||||
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):
|
||||
return "%s -- %s" % (self.seller,
|
||||
self.attribution.spectacle.title)
|
||||
return "%s -- %s" % (self.seller, self.attribution.spectacle.title)
|
||||
|
||||
class Meta:
|
||||
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):
|
||||
"""
|
||||
Envoie une notification pour indiquer la mise en vente d'une place sur
|
||||
BdA-Revente à tous les intéressés.
|
||||
"""
|
||||
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
||||
datatuple = [(
|
||||
'bda-revente',
|
||||
{
|
||||
'member': participant.user,
|
||||
'show': self.attribution.spectacle,
|
||||
'revente': self,
|
||||
'site': Site.objects.get_current()
|
||||
},
|
||||
settings.MAIL_DATA['revente']['FROM'],
|
||||
[participant.user.email])
|
||||
inscrits = self.attribution.spectacle.subscribed.select_related("user")
|
||||
datatuple = [
|
||||
(
|
||||
"bda-revente",
|
||||
{
|
||||
"member": participant.user,
|
||||
"show": self.attribution.spectacle,
|
||||
"revente": self,
|
||||
"site": Site.objects.get_current(),
|
||||
},
|
||||
settings.MAIL_DATA["revente"]["FROM"],
|
||||
[participant.user.email],
|
||||
)
|
||||
for participant in inscrits
|
||||
]
|
||||
send_mass_custom_mail(datatuple)
|
||||
self.notif_sent = True
|
||||
self.notif_time = timezone.now()
|
||||
self.save()
|
||||
|
||||
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
|
||||
leur indiquer qu'il est désormais disponible au shotgun.
|
||||
"""
|
||||
inscrits = self.attribution.spectacle.subscribed.select_related('user')
|
||||
datatuple = [(
|
||||
'bda-shotgun',
|
||||
{
|
||||
'member': participant.user,
|
||||
'show': self.attribution.spectacle,
|
||||
'site': Site.objects.get_current(),
|
||||
},
|
||||
settings.MAIL_DATA['revente']['FROM'],
|
||||
[participant.user.email])
|
||||
inscrits = self.attribution.spectacle.subscribed.select_related("user")
|
||||
datatuple = [
|
||||
(
|
||||
"bda-shotgun",
|
||||
{
|
||||
"member": participant.user,
|
||||
"show": self.attribution.spectacle,
|
||||
"site": Site.objects.get_current(),
|
||||
},
|
||||
settings.MAIL_DATA["revente"]["FROM"],
|
||||
[participant.user.email],
|
||||
)
|
||||
for participant in inscrits
|
||||
]
|
||||
send_mass_custom_mail(datatuple)
|
||||
self.notif_sent = True
|
||||
self.notif_time = timezone.now()
|
||||
# Flag inutile, sauf si l'horloge interne merde
|
||||
self.tirage_done = True
|
||||
self.shotgun = True
|
||||
self.save()
|
||||
|
||||
def tirage(self):
|
||||
def tirage(self, send_mails=True):
|
||||
"""
|
||||
Lance le tirage au sort associé à la revente. Un gagnant est choisi
|
||||
parmis les personnes intéressées par le spectacle. Les personnes sont
|
||||
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
|
||||
seller = self.seller
|
||||
winner = None
|
||||
|
||||
if inscrits:
|
||||
# Envoie un mail au gagnant et au vendeur
|
||||
winner = random.choice(inscrits)
|
||||
self.soldTo = winner
|
||||
datatuple = []
|
||||
context = {
|
||||
'acheteur': winner.user,
|
||||
'vendeur': seller.user,
|
||||
'show': spectacle,
|
||||
}
|
||||
datatuple.append((
|
||||
'bda-revente-winner',
|
||||
context,
|
||||
settings.MAIL_DATA['revente']['FROM'],
|
||||
[winner.user.email],
|
||||
))
|
||||
datatuple.append((
|
||||
'bda-revente-seller',
|
||||
context,
|
||||
settings.MAIL_DATA['revente']['FROM'],
|
||||
[seller.user.email]
|
||||
))
|
||||
if send_mails:
|
||||
mails = []
|
||||
|
||||
# Envoie un mail aux perdants
|
||||
for inscrit in inscrits:
|
||||
if inscrit != winner:
|
||||
new_context = dict(context)
|
||||
new_context['acheteur'] = inscrit.user
|
||||
datatuple.append((
|
||||
'bda-revente-loser',
|
||||
new_context,
|
||||
settings.MAIL_DATA['revente']['FROM'],
|
||||
[inscrit.user.email]
|
||||
))
|
||||
send_mass_custom_mail(datatuple)
|
||||
context = {
|
||||
"acheteur": winner.user,
|
||||
"vendeur": seller.user,
|
||||
"show": spectacle,
|
||||
}
|
||||
|
||||
c_mails_qs = CustomMail.objects.filter(
|
||||
shortname__in=[
|
||||
"bda-revente-winner",
|
||||
"bda-revente-loser",
|
||||
"bda-revente-seller",
|
||||
]
|
||||
)
|
||||
|
||||
c_mails = {cm.shortname: cm for cm in c_mails_qs}
|
||||
|
||||
mails.append(
|
||||
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
|
||||
else:
|
||||
self.shotgun = True
|
||||
self.tirage_done = True
|
||||
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>
|
||||
<tbody class="bda_formset_content">
|
||||
{% 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 %}
|
||||
{% if field.name != "DELETE" and field.name != "priority" %}
|
||||
<td class="bda-field-{{ field.name }}">
|
||||
|
|
|
@ -27,6 +27,14 @@ var django = {
|
|||
var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
|
||||
$(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++;
|
||||
$('#id_' + type + '-TOTAL_FORMS').val(total);
|
||||
$(selector).after(newElement);
|
||||
|
@ -44,6 +52,11 @@ var django = {
|
|||
} else {
|
||||
deleteInput.attr("checked", true);
|
||||
}
|
||||
} else {
|
||||
// Reset the default values
|
||||
var selects = $(form).find("select");
|
||||
$(selects[0]).val("");
|
||||
$(selects[1]).val("1");
|
||||
}
|
||||
// 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,11 +47,11 @@
|
|||
<div>
|
||||
<button class="btn btn-default" type="button" onclick="toggle('export-salle')">Afficher/Cacher liste noms</button>
|
||||
<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 %}
|
||||
{% endspaceless %}</pre>
|
||||
</div>
|
||||
|
||||
|
||||
<div>
|
||||
<a href="{% url 'bda-rappels' spectacle.id %}">Page d'envoi manuel des mails de rappel</a>
|
||||
</div>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<h4 class="bda-prix">Total à payer : {{ total|floatformat }}€</h4>
|
||||
<br/>
|
||||
<p>Ne manque pas un spectacle avec le
|
||||
<a href="{% url "gestioncof.views.calendar" %}">calendrier
|
||||
<a href="{% url "calendar" %}">calendrier
|
||||
automatique !</a></p>
|
||||
{% else %}
|
||||
<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>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 %}
|
||||
<p> Il n'est pas encore possible de s'inscrire à cette revente, réessaie dans quelque temps !</p>
|
||||
{% 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 gestioncof.decorators import buro_required
|
||||
from bda.views import SpectacleListView
|
||||
|
||||
from bda import views
|
||||
from bda.views import SpectacleListView
|
||||
from gestioncof.decorators import buro_required
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^inscription/(?P<tirage_id>\d+)$',
|
||||
url(
|
||||
r"^inscription/(?P<tirage_id>\d+)$",
|
||||
views.inscription,
|
||||
name='bda-tirage-inscription'),
|
||||
url(r'^places/(?P<tirage_id>\d+)$',
|
||||
views.places,
|
||||
name="bda-places-attribuees"),
|
||||
url(r'^revente/(?P<tirage_id>\d+)$',
|
||||
views.revente,
|
||||
name='bda-revente'),
|
||||
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+)$',
|
||||
name="bda-tirage-inscription",
|
||||
),
|
||||
url(r"^places/(?P<tirage_id>\d+)$", views.places, name="bda-places-attribuees"),
|
||||
url(r"^etat-places/(?P<tirage_id>\d+)$", views.etat_places, name="bda-etat-places"),
|
||||
url(r"^tirage/(?P<tirage_id>\d+)$", views.tirage, name="bda-tirage"),
|
||||
url(
|
||||
r"^spectacles/(?P<tirage_id>\d+)$",
|
||||
buro_required(SpectacleListView.as_view()),
|
||||
name="bda-liste-spectacles"),
|
||||
url(r'^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$',
|
||||
name="bda-liste-spectacles",
|
||||
),
|
||||
url(
|
||||
r"^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$",
|
||||
views.spectacle,
|
||||
name="bda-spectacle"),
|
||||
url(r'^spectacles/unpaid/(?P<tirage_id>\d+)$',
|
||||
views.unpaid,
|
||||
name="bda-unpaid"),
|
||||
url(r'^liste-revente/(?P<tirage_id>\d+)$',
|
||||
views.list_revente,
|
||||
name="bda-liste-revente"),
|
||||
url(r'^buy-revente/(?P<spectacle_id>\d+)$',
|
||||
views.buy_revente,
|
||||
name="bda-buy-revente"),
|
||||
url(r'^revente-interested/(?P<revente_id>\d+)$',
|
||||
views.revente_interested,
|
||||
name='bda-revente-interested'),
|
||||
url(r'^revente-immediat/(?P<tirage_id>\d+)$',
|
||||
name="bda-spectacle",
|
||||
),
|
||||
url(r"^spectacles/unpaid/(?P<tirage_id>\d+)$", views.unpaid, name="bda-unpaid"),
|
||||
url(
|
||||
r"^spectacles/autocomplete$",
|
||||
views.spectacle_autocomplete,
|
||||
name="bda-spectacle-autocomplete",
|
||||
),
|
||||
url(
|
||||
r"^participants/autocomplete$",
|
||||
views.participant_autocomplete,
|
||||
name="bda-participant-autocomplete",
|
||||
),
|
||||
# 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,
|
||||
name="bda-shotgun"),
|
||||
url(r'^mails-rappel/(?P<spectacle_id>\d+)$',
|
||||
views.send_rappel,
|
||||
name="bda-rappels"
|
||||
),
|
||||
url(r'^descriptions/(?P<tirage_id>\d+)$', views.descriptions_spectacles,
|
||||
name='bda-descriptions'),
|
||||
url(r'^catalogue/(?P<request_type>[a-z]+)$', views.catalogue,
|
||||
name='bda-catalogue'),
|
||||
name="bda-revente-shotgun",
|
||||
),
|
||||
url(r"^mails-rappel/(?P<spectacle_id>\d+)$", views.send_rappel, name="bda-rappels"),
|
||||
url(
|
||||
r"^descriptions/(?P<tirage_id>\d+)$",
|
||||
views.descriptions_spectacles,
|
||||
name="bda-descriptions",
|
||||
),
|
||||
url(r"^catalogue/(?P<request_type>[a-z]+)$", views.catalogue, name="bda-catalogue"),
|
||||
]
|
||||
|
|
811
bda/views.py
811
bda/views.py
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
|
||||
from channels.asgi import get_channel_layer
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
DATETIME_FORMAT = r'l j F Y \à H:i'
|
||||
DATETIME_FORMAT = r"l j F Y \à H\hi"
|
||||
DATE_FORMAT = r"l j F Y"
|
||||
TIME_FORMAT = r"H\hi"
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
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.
|
||||
|
||||
|
@ -7,6 +6,7 @@ the local development server should be here.
|
|||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from . import secret
|
||||
|
@ -31,6 +31,7 @@ def import_secret(name):
|
|||
SECRET_KEY = import_secret("SECRET_KEY")
|
||||
ADMINS = import_secret("ADMINS")
|
||||
SERVER_EMAIL = import_secret("SERVER_EMAIL")
|
||||
EMAIL_HOST = import_secret("EMAIL_HOST")
|
||||
|
||||
DBNAME = import_secret("DBNAME")
|
||||
DBUSER = import_secret("DBUSER")
|
||||
|
@ -41,108 +42,115 @@ REDIS_DB = import_secret("REDIS_DB")
|
|||
REDIS_HOST = import_secret("REDIS_HOST")
|
||||
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")
|
||||
LDAP_SERVER_URL = import_secret("LDAP_SERVER_URL")
|
||||
|
||||
|
||||
BASE_DIR = os.path.dirname(
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
TESTING = sys.argv[1] == "test"
|
||||
|
||||
|
||||
# Application definition
|
||||
INSTALLED_APPS = [
|
||||
'gestioncof',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'grappelli',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.admindocs',
|
||||
'bda',
|
||||
'autocomplete_light',
|
||||
'captcha',
|
||||
'django_cas_ng',
|
||||
'bootstrapform',
|
||||
'kfet',
|
||||
'kfet.open',
|
||||
'channels',
|
||||
'widget_tweaks',
|
||||
'custommail',
|
||||
'djconfig',
|
||||
'wagtail.wagtailforms',
|
||||
'wagtail.wagtailredirects',
|
||||
'wagtail.wagtailembeds',
|
||||
'wagtail.wagtailsites',
|
||||
'wagtail.wagtailusers',
|
||||
'wagtail.wagtailsnippets',
|
||||
'wagtail.wagtaildocs',
|
||||
'wagtail.wagtailimages',
|
||||
'wagtail.wagtailsearch',
|
||||
'wagtail.wagtailadmin',
|
||||
'wagtail.wagtailcore',
|
||||
'wagtail.contrib.modeladmin',
|
||||
'wagtailmenus',
|
||||
'modelcluster',
|
||||
'taggit',
|
||||
'kfet.auth',
|
||||
'kfet.cms',
|
||||
"shared",
|
||||
"gestioncof",
|
||||
# Must be before 'django.contrib.admin'.
|
||||
# https://django-autocomplete-light.readthedocs.io/en/master/install.html
|
||||
"dal",
|
||||
"dal_select2",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.sites",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"django.contrib.admin",
|
||||
"django.contrib.admindocs",
|
||||
"bda",
|
||||
"petitscours",
|
||||
"captcha",
|
||||
"django_cas_ng",
|
||||
"bootstrapform",
|
||||
"kfet",
|
||||
"kfet.open",
|
||||
"channels",
|
||||
"widget_tweaks",
|
||||
"custommail",
|
||||
"djconfig",
|
||||
"wagtail.wagtailforms",
|
||||
"wagtail.wagtailredirects",
|
||||
"wagtail.wagtailembeds",
|
||||
"wagtail.wagtailsites",
|
||||
"wagtail.wagtailusers",
|
||||
"wagtail.wagtailsnippets",
|
||||
"wagtail.wagtaildocs",
|
||||
"wagtail.wagtailimages",
|
||||
"wagtail.wagtailsearch",
|
||||
"wagtail.wagtailadmin",
|
||||
"wagtail.wagtailcore",
|
||||
"wagtail.contrib.modeladmin",
|
||||
"wagtail.contrib.wagtailroutablepage",
|
||||
"wagtailmenus",
|
||||
"wagtail_modeltranslation",
|
||||
"modelcluster",
|
||||
"taggit",
|
||||
"kfet.auth",
|
||||
"kfet.cms",
|
||||
"gestioncof.cms",
|
||||
]
|
||||
|
||||
MIDDLEWARE_CLASSES = [
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'kfet.auth.middleware.TemporaryAuthMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'djconfig.middleware.DjConfigMiddleware',
|
||||
'wagtail.wagtailcore.middleware.SiteMiddleware',
|
||||
'wagtail.wagtailredirects.middleware.RedirectMiddleware',
|
||||
|
||||
MIDDLEWARE = [
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.auth.middleware.SessionAuthenticationMiddleware",
|
||||
"kfet.auth.middleware.TemporaryAuthMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"djconfig.middleware.DjConfigMiddleware",
|
||||
"wagtail.wagtailcore.middleware.SiteMiddleware",
|
||||
"wagtail.wagtailredirects.middleware.RedirectMiddleware",
|
||||
"django.middleware.locale.LocaleMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'cof.urls'
|
||||
ROOT_URLCONF = "cof.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.core.context_processors.i18n',
|
||||
'django.core.context_processors.media',
|
||||
'django.core.context_processors.static',
|
||||
'wagtailmenus.context_processors.wagtailmenus',
|
||||
'djconfig.context_processors.config',
|
||||
'gestioncof.shared.context_processor',
|
||||
'kfet.auth.context_processors.temporary_auth',
|
||||
'kfet.context_processors.config',
|
||||
],
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
"django.template.context_processors.i18n",
|
||||
"django.template.context_processors.media",
|
||||
"django.template.context_processors.static",
|
||||
"wagtailmenus.context_processors.wagtailmenus",
|
||||
"djconfig.context_processors.config",
|
||||
"gestioncof.shared.context_processor",
|
||||
"kfet.auth.context_processors.temporary_auth",
|
||||
"kfet.context_processors.config",
|
||||
]
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'NAME': DBNAME,
|
||||
'USER': DBUSER,
|
||||
'PASSWORD': DBPASSWD,
|
||||
'HOST': os.environ.get('DBHOST', 'localhost'),
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
||||
"NAME": DBNAME,
|
||||
"USER": DBUSER,
|
||||
"PASSWORD": DBPASSWD,
|
||||
"HOST": os.environ.get("DBHOST", "localhost"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,9 +158,9 @@ DATABASES = {
|
|||
# Internationalization
|
||||
# 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
|
||||
|
||||
|
@ -160,51 +168,63 @@ USE_L10N = True
|
|||
|
||||
USE_TZ = True
|
||||
|
||||
LANGUAGES = (("fr", "Français"), ("en", "English"))
|
||||
|
||||
# Various additional settings
|
||||
SITE_ID = 1
|
||||
|
||||
GRAPPELLI_ADMIN_HEADLINE = "GestioCOF"
|
||||
GRAPPELLI_ADMIN_TITLE = "<a href=\"/\">GestioCOF</a>"
|
||||
GRAPPELLI_ADMIN_TITLE = '<a href="/">GestioCOF</a>'
|
||||
|
||||
MAIL_DATA = {
|
||||
'petits_cours': {
|
||||
'FROM': "Le COF <cof@ens.fr>",
|
||||
'BCC': "archivescof@gmail.com",
|
||||
'REPLYTO': "cof@ens.fr"},
|
||||
'rappels': {
|
||||
'FROM': 'Le BdA <bda@ens.fr>',
|
||||
'REPLYTO': 'Le BdA <bda@ens.fr>'},
|
||||
'revente': {
|
||||
'FROM': 'BdA-Revente <bda-revente@ens.fr>',
|
||||
'REPLYTO': 'BdA-Revente <bda-revente@ens.fr>'},
|
||||
"petits_cours": {
|
||||
"FROM": "Le COF <cof@ens.fr>",
|
||||
"BCC": "archivescof@gmail.com",
|
||||
"REPLYTO": "cof@ens.fr",
|
||||
},
|
||||
"rappels": {"FROM": "Le BdA <bda@ens.fr>", "REPLYTO": "Le BdA <bda@ens.fr>"},
|
||||
"revente": {
|
||||
"FROM": "BdA-Revente <bda-revente@ens.fr>",
|
||||
"REPLYTO": "BdA-Revente <bda-revente@ens.fr>",
|
||||
},
|
||||
}
|
||||
|
||||
LOGIN_URL = "cof-login"
|
||||
LOGIN_REDIRECT_URL = "home"
|
||||
|
||||
CAS_SERVER_URL = 'https://cas.eleves.ens.fr/'
|
||||
CAS_VERSION = '3'
|
||||
CAS_SERVER_URL = "https://cas.eleves.ens.fr/"
|
||||
CAS_VERSION = "2"
|
||||
CAS_LOGIN_MSG = None
|
||||
CAS_IGNORE_REFERER = True
|
||||
CAS_REDIRECT_URL = '/'
|
||||
CAS_REDIRECT_URL = "/"
|
||||
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
'gestioncof.shared.COFCASBackend',
|
||||
'kfet.auth.backends.GenericBackend',
|
||||
"django.contrib.auth.backends.ModelBackend",
|
||||
"gestioncof.shared.COFCASBackend",
|
||||
"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
|
||||
|
||||
CORS_ORIGIN_WHITELIST = ("bda.ens.fr", "www.bda.ens.fr" "cof.ens.fr", "www.cof.ens.fr")
|
||||
|
||||
# Cache settings
|
||||
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'redis_cache.RedisCache',
|
||||
'LOCATION': 'redis://:{passwd}@{host}:{port}/db'
|
||||
.format(passwd=REDIS_PASSWD, host=REDIS_HOST,
|
||||
port=REDIS_PORT, db=REDIS_DB),
|
||||
"default": {
|
||||
"BACKEND": "redis_cache.RedisCache",
|
||||
"LOCATION": "redis://:{passwd}@{host}:{port}/db".format(
|
||||
passwd=REDIS_PASSWD, host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,20 +235,25 @@ CHANNEL_LAYERS = {
|
|||
"default": {
|
||||
"BACKEND": "asgi_redis.RedisChannelLayer",
|
||||
"CONFIG": {
|
||||
"hosts": [(
|
||||
"redis://:{passwd}@{host}:{port}/{db}"
|
||||
.format(passwd=REDIS_PASSWD, host=REDIS_HOST,
|
||||
port=REDIS_PORT, db=REDIS_DB)
|
||||
)],
|
||||
"hosts": [
|
||||
(
|
||||
"redis://:{passwd}@{host}:{port}/{db}".format(
|
||||
passwd=REDIS_PASSWD,
|
||||
host=REDIS_HOST,
|
||||
port=REDIS_PORT,
|
||||
db=REDIS_DB,
|
||||
)
|
||||
)
|
||||
]
|
||||
},
|
||||
"ROUTING": "cof.routing.routing",
|
||||
}
|
||||
}
|
||||
|
||||
FORMAT_MODULE_PATH = 'cof.locale'
|
||||
FORMAT_MODULE_PATH = "cof.locale"
|
||||
|
||||
# Wagtail settings
|
||||
|
||||
WAGTAIL_SITE_NAME = 'GestioCOF'
|
||||
WAGTAIL_SITE_NAME = "GestioCOF"
|
||||
WAGTAIL_ENABLE_UPDATE_CHECK = False
|
||||
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 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
|
||||
|
||||
if TESTING:
|
||||
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
||||
|
||||
|
||||
# ---
|
||||
# Apache static/media config
|
||||
# ---
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = '/srv/gestiocof/static/'
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = "/srv/gestiocof/static/"
|
||||
|
||||
MEDIA_ROOT = '/srv/gestiocof/media/'
|
||||
MEDIA_URL = '/media/'
|
||||
MEDIA_ROOT = "/srv/gestiocof/media/"
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
|
||||
# ---
|
||||
# Debug tool bar
|
||||
# ---
|
||||
|
||||
|
||||
def show_toolbar(request):
|
||||
"""
|
||||
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
|
||||
mise dans les INTERNAL_IPS.
|
||||
"""
|
||||
return DEBUG
|
||||
return DEBUG and not request.path.startswith("/admin/")
|
||||
|
||||
INSTALLED_APPS += ["debug_toolbar", "debug_panel"]
|
||||
MIDDLEWARE_CLASSES = (
|
||||
["debug_panel.middleware.DebugPanelMiddleware"]
|
||||
+ MIDDLEWARE_CLASSES
|
||||
)
|
||||
DEBUG_TOOLBAR_CONFIG = {
|
||||
'SHOW_TOOLBAR_CALLBACK': show_toolbar,
|
||||
}
|
||||
|
||||
if not TESTING:
|
||||
INSTALLED_APPS += ["debug_toolbar", "debug_panel"]
|
||||
|
||||
MIDDLEWARE = ["debug_panel.middleware.DebugPanelMiddleware"] + MIDDLEWARE
|
||||
|
||||
DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": show_toolbar}
|
||||
|
|
|
@ -8,21 +8,16 @@ import os
|
|||
from .dev import * # NOQA
|
||||
from .dev import BASE_DIR
|
||||
|
||||
|
||||
# Use sqlite for local development
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"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
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache"
|
||||
}
|
||||
}
|
||||
CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}}
|
||||
|
||||
# Use the default in memory asgi backend for local development
|
||||
CHANNEL_LAYERS = {
|
||||
|
|
|
@ -6,25 +6,21 @@ The settings that are not listed here are imported from .common
|
|||
import os
|
||||
|
||||
from .common import * # NOQA
|
||||
from .common import BASE_DIR
|
||||
|
||||
from .common import BASE_DIR, import_secret
|
||||
|
||||
DEBUG = False
|
||||
|
||||
ALLOWED_HOSTS = [
|
||||
"cof.ens.fr",
|
||||
"www.cof.ens.fr",
|
||||
"dev.cof.ens.fr"
|
||||
]
|
||||
ALLOWED_HOSTS = ["cof.ens.fr", "www.cof.ens.fr", "dev.cof.ens.fr"]
|
||||
|
||||
|
||||
STATIC_ROOT = os.path.join(
|
||||
os.path.dirname(os.path.dirname(BASE_DIR)),
|
||||
"public",
|
||||
"gestion",
|
||||
"static",
|
||||
os.path.dirname(os.path.dirname(BASE_DIR)), "public", "gestion", "static"
|
||||
)
|
||||
|
||||
STATIC_URL = "/gestion/static/"
|
||||
MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), "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
|
||||
SERVER_EMAIL = "root@vagrant"
|
||||
EMAIL_HOST = "localhost"
|
||||
|
||||
DBUSER = "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
|
||||
"""
|
||||
|
||||
import autocomplete_light
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include, url
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
from django.views.generic.base import TemplateView
|
||||
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 wagtail.wagtailadmin import urls as wagtailadmin_urls
|
||||
from wagtail.wagtailcore import urls as wagtail_urls
|
||||
from wagtail.wagtaildocs import urls as wagtaildocs_urls
|
||||
|
||||
from gestioncof import views as gestioncof_views, csv_views
|
||||
from gestioncof.urls import export_patterns, petitcours_patterns, \
|
||||
surveys_patterns, events_patterns, calendar_patterns, \
|
||||
clubs_patterns
|
||||
from gestioncof import csv_views, views as gestioncof_views
|
||||
from gestioncof.autocomplete import autocomplete
|
||||
from gestioncof.urls import (
|
||||
calendar_patterns,
|
||||
clubs_patterns,
|
||||
events_patterns,
|
||||
export_patterns,
|
||||
surveys_patterns,
|
||||
)
|
||||
|
||||
autocomplete_light.autodiscover()
|
||||
admin.autodiscover()
|
||||
|
||||
urlpatterns = [
|
||||
# Page d'accueil
|
||||
url(r'^$', gestioncof_views.home, name='home'),
|
||||
url(r"^$", gestioncof_views.home, name="home"),
|
||||
# Le BdA
|
||||
url(r'^bda/', include('bda.urls')),
|
||||
url(r"^bda/", include("bda.urls")),
|
||||
# Les exports
|
||||
url(r'^export/', include(export_patterns)),
|
||||
url(r"^export/", include(export_patterns)),
|
||||
# Les petits cours
|
||||
url(r'^petitcours/', include(petitcours_patterns)),
|
||||
url(r"^petitcours/", include("petitscours.urls")),
|
||||
# Les sondages
|
||||
url(r'^survey/', include(surveys_patterns)),
|
||||
url(r"^survey/", include(surveys_patterns)),
|
||||
# Evenements
|
||||
url(r'^event/', include(events_patterns)),
|
||||
url(r"^event/", include(events_patterns)),
|
||||
# Calendrier
|
||||
url(r'^calendar/', include(calendar_patterns)),
|
||||
url(r"^calendar/", include(calendar_patterns)),
|
||||
# Clubs
|
||||
url(r'^clubs/', include(clubs_patterns)),
|
||||
url(r"^clubs/", include(clubs_patterns)),
|
||||
# Authentification
|
||||
url(r'^cof/denied$', TemplateView.as_view(template_name='cof-denied.html'),
|
||||
name="cof-denied"),
|
||||
url(r'^cas/login$', django_cas_views.login, name="cas_login_view"),
|
||||
url(r'^cas/logout$', django_cas_views.logout),
|
||||
url(r'^outsider/login$', gestioncof_views.login_ext),
|
||||
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"),
|
||||
url(
|
||||
r"^cof/denied$",
|
||||
TemplateView.as_view(template_name="cof-denied.html"),
|
||||
name="cof-denied",
|
||||
),
|
||||
url(r"^cas/login$", django_cas_views.login, name="cas_login_view"),
|
||||
url(r"^cas/logout$", django_cas_views.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
|
||||
url(r'^profile$', gestioncof_views.profile),
|
||||
url(r'^outsider/password-change$', django_views.password_change),
|
||||
url(r'^outsider/password-change-done$',
|
||||
url(r"^profile$", gestioncof_views.profile, name="profile"),
|
||||
url(
|
||||
r"^outsider/password-change$",
|
||||
django_views.password_change,
|
||||
name="password_change",
|
||||
),
|
||||
url(
|
||||
r"^outsider/password-change-done$",
|
||||
django_views.password_change_done,
|
||||
name='password_change_done'),
|
||||
name="password_change_done",
|
||||
),
|
||||
# Inscription d'un nouveau membre
|
||||
url(r'^registration$', gestioncof_views.registration),
|
||||
url(r'^registration/clipper/(?P<login_clipper>[\w-]+)/'
|
||||
r'(?P<fullname>.*)$',
|
||||
gestioncof_views.registration_form2, name="clipper-registration"),
|
||||
url(r'^registration/user/(?P<username>.+)$',
|
||||
gestioncof_views.registration_form2, name="user-registration"),
|
||||
url(r'^registration/empty$', gestioncof_views.registration_form2,
|
||||
name="empty-registration"),
|
||||
url(r"^registration$", gestioncof_views.registration, name="registration"),
|
||||
url(
|
||||
r"^registration/clipper/(?P<login_clipper>[\w-]+)/" r"(?P<fullname>.*)$",
|
||||
gestioncof_views.registration_form2,
|
||||
name="clipper-registration",
|
||||
),
|
||||
url(
|
||||
r"^registration/user/(?P<username>.+)$",
|
||||
gestioncof_views.registration_form2,
|
||||
name="user-registration",
|
||||
),
|
||||
url(
|
||||
r"^registration/empty$",
|
||||
gestioncof_views.registration_form2,
|
||||
name="empty-registration",
|
||||
),
|
||||
# Autocompletion
|
||||
url(r'^autocomplete/registration$', autocomplete),
|
||||
url(r'^autocomplete/', include('autocomplete_light.urls')),
|
||||
url(
|
||||
r"^autocomplete/registration$",
|
||||
autocomplete,
|
||||
name="cof.registration.autocomplete",
|
||||
),
|
||||
url(
|
||||
r"^user/autocomplete$",
|
||||
gestioncof_views.user_autocomplete,
|
||||
name="cof-user-autocomplete",
|
||||
),
|
||||
# Interface admin
|
||||
url(r'^admin/logout/', gestioncof_views.logout),
|
||||
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/logout/", gestioncof_views.logout),
|
||||
url(r"^admin/doc/", include("django.contrib.admindocs.urls")),
|
||||
url(
|
||||
r"^admin/(?P<app_label>[\d\w]+)/(?P<model_name>[\d\w]+)/csv/",
|
||||
csv_views.admin_list_export,
|
||||
{'fields': ['username', ]}),
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
url(r'^grappelli/', include('grappelli.urls')),
|
||||
{"fields": ["username"]},
|
||||
),
|
||||
url(r"^admin/", include(admin.site.urls)),
|
||||
# Liens utiles du COF et du BdA
|
||||
url(r'^utile_cof$', gestioncof_views.utile_cof),
|
||||
url(r'^utile_bda$', gestioncof_views.utile_bda),
|
||||
url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff),
|
||||
url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof),
|
||||
url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente),
|
||||
url(r'^k-fet/', include('kfet.urls')),
|
||||
url(r'^cms/', include(wagtailadmin_urls)),
|
||||
url(r'^documents/', include(wagtaildocs_urls)),
|
||||
url(r"^utile_cof$", gestioncof_views.utile_cof, name="utile_cof"),
|
||||
url(r"^utile_bda$", gestioncof_views.utile_bda, name="utile_bda"),
|
||||
url(r"^utile_bda/bda_diff$", gestioncof_views.liste_bdadiff, name="ml_diffbda"),
|
||||
url(r"^utile_cof/diff_cof$", gestioncof_views.liste_diffcof, name="ml_diffcof"),
|
||||
url(
|
||||
r"^utile_bda/bda_revente$",
|
||||
gestioncof_views.liste_bdarevente,
|
||||
name="ml_bda_revente",
|
||||
),
|
||||
url(r"^k-fet/", include("kfet.urls")),
|
||||
url(r"^cms/", include(wagtailadmin_urls)),
|
||||
url(r"^documents/", include(wagtaildocs_urls)),
|
||||
# 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
|
||||
urlpatterns += [
|
||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
||||
]
|
||||
|
||||
urlpatterns += [url(r"^__debug__/", include(debug_toolbar.urls))]
|
||||
|
||||
if settings.DEBUG:
|
||||
# Si on est en production, MEDIA_ROOT est servi par Apache.
|
||||
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.
|
||||
urlpatterns += static(settings.MEDIA_URL,
|
||||
document_root=settings.MEDIA_ROOT)
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
# Wagtail for uncatched
|
||||
urlpatterns += [
|
||||
url(r'', include(wagtail_urls)),
|
||||
]
|
||||
urlpatterns += i18n_patterns(
|
||||
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.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.models import Group, Permission, User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
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,
|
||||
desc_text=str):
|
||||
def add_link_field(target_model="", field="", link_text=str, desc_text=str):
|
||||
def add_link(cls):
|
||||
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:
|
||||
return ""
|
||||
url = reverse(reverse_path, args=(link_obj.id,))
|
||||
return mark_safe("<a href='%s'>%s</a>"
|
||||
% (url, link_text(link_obj)))
|
||||
return mark_safe("<a href='%s'>%s</a>" % (url, link_text(link_obj)))
|
||||
|
||||
link.allow_tags = True
|
||||
link.short_description = desc_text(reverse_name + ' link')
|
||||
link.short_description = desc_text(reverse_name + " link")
|
||||
cls.link = link
|
||||
cls.readonly_fields =\
|
||||
list(getattr(cls, 'readonly_fields', [])) + ['link']
|
||||
cls.readonly_fields = list(getattr(cls, "readonly_fields", [])) + ["link"]
|
||||
return cls
|
||||
|
||||
return add_link
|
||||
|
||||
|
||||
|
@ -43,32 +55,28 @@ class SurveyQuestionAnswerInline(admin.TabularInline):
|
|||
model = SurveyQuestionAnswer
|
||||
|
||||
|
||||
@add_link_field(desc_text=lambda x: "Réponses",
|
||||
link_text=lambda x: "Éditer les réponses")
|
||||
@add_link_field(
|
||||
desc_text=lambda x: "Réponses", link_text=lambda x: "Éditer les réponses"
|
||||
)
|
||||
class SurveyQuestionInline(admin.TabularInline):
|
||||
model = SurveyQuestion
|
||||
|
||||
|
||||
class SurveyQuestionAdmin(admin.ModelAdmin):
|
||||
search_fields = ('survey__title', 'answer')
|
||||
inlines = [
|
||||
SurveyQuestionAnswerInline,
|
||||
]
|
||||
search_fields = ("survey__title", "answer")
|
||||
inlines = [SurveyQuestionAnswerInline]
|
||||
|
||||
|
||||
class SurveyAdmin(admin.ModelAdmin):
|
||||
search_fields = ('title', 'details')
|
||||
inlines = [
|
||||
SurveyQuestionInline,
|
||||
]
|
||||
search_fields = ("title", "details")
|
||||
inlines = [SurveyQuestionInline]
|
||||
|
||||
|
||||
class EventOptionChoiceInline(admin.TabularInline):
|
||||
model = EventOptionChoice
|
||||
|
||||
|
||||
@add_link_field(desc_text=lambda x: "Choix",
|
||||
link_text=lambda x: "Éditer les choix")
|
||||
@add_link_field(desc_text=lambda x: "Choix", link_text=lambda x: "Éditer les choix")
|
||||
class EventOptionInline(admin.TabularInline):
|
||||
model = EventOption
|
||||
|
||||
|
@ -78,18 +86,13 @@ class EventCommentFieldInline(admin.TabularInline):
|
|||
|
||||
|
||||
class EventOptionAdmin(admin.ModelAdmin):
|
||||
search_fields = ('event__title', 'name')
|
||||
inlines = [
|
||||
EventOptionChoiceInline,
|
||||
]
|
||||
search_fields = ("event__title", "name")
|
||||
inlines = [EventOptionChoiceInline]
|
||||
|
||||
|
||||
class EventAdmin(admin.ModelAdmin):
|
||||
search_fields = ('title', 'location', 'description')
|
||||
inlines = [
|
||||
EventOptionInline,
|
||||
EventCommentFieldInline,
|
||||
]
|
||||
search_fields = ("title", "location", "description")
|
||||
inlines = [EventOptionInline, EventCommentFieldInline]
|
||||
|
||||
|
||||
class CofProfileInline(admin.StackedInline):
|
||||
|
@ -98,10 +101,9 @@ class CofProfileInline(admin.StackedInline):
|
|||
|
||||
|
||||
class FkeyLookup(object):
|
||||
def __init__(self, fkeydecl, short_description=None,
|
||||
admin_order_field=None):
|
||||
self.fk, fkattrs = fkeydecl.split('__', 1)
|
||||
self.fkattrs = fkattrs.split('__')
|
||||
def __init__(self, fkeydecl, short_description=None, admin_order_field=None):
|
||||
self.fk, fkattrs = fkeydecl.split("__", 1)
|
||||
self.fkattrs = fkattrs.split("__")
|
||||
|
||||
self.short_description = short_description or self.fkattrs[-1]
|
||||
self.admin_order_field = admin_order_field or fkeydecl
|
||||
|
@ -126,19 +128,19 @@ def ProfileInfo(field, short_description, boolean=False):
|
|||
return getattr(self.profile, field)
|
||||
except CofProfile.DoesNotExist:
|
||||
return ""
|
||||
|
||||
getter.short_description = short_description
|
||||
getter.boolean = boolean
|
||||
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_occupation = ProfileInfo("occupation", "Occupation")
|
||||
User.profile_departement = ProfileInfo("departement", "Departement")
|
||||
User.profile_mailing_cof = ProfileInfo("mailing_cof", "ML COF", True)
|
||||
User.profile_mailing_bda = ProfileInfo("mailing_bda", "ML BdA", True)
|
||||
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente",
|
||||
"ML BdA-R", True)
|
||||
User.profile_mailing_bda_revente = ProfileInfo("mailing_bda_revente", "ML BdA-R", True)
|
||||
|
||||
|
||||
class UserProfileAdmin(UserAdmin):
|
||||
|
@ -147,7 +149,8 @@ class UserProfileAdmin(UserAdmin):
|
|||
return obj.profile.is_buro
|
||||
except CofProfile.DoesNotExist:
|
||||
return False
|
||||
is_buro.short_description = 'Membre du Buro'
|
||||
|
||||
is_buro.short_description = "Membre du Buro"
|
||||
is_buro.boolean = True
|
||||
|
||||
def is_cof(self, obj):
|
||||
|
@ -155,44 +158,50 @@ class UserProfileAdmin(UserAdmin):
|
|||
return obj.profile.is_cof
|
||||
except CofProfile.DoesNotExist:
|
||||
return False
|
||||
is_cof.short_description = 'Membre du COF'
|
||||
|
||||
is_cof.short_description = "Membre du COF"
|
||||
is_cof.boolean = True
|
||||
|
||||
list_display = (
|
||||
UserAdmin.list_display
|
||||
+ ('profile_login_clipper', 'profile_phone', 'profile_occupation',
|
||||
'profile_mailing_cof', 'profile_mailing_bda',
|
||||
'profile_mailing_bda_revente', 'is_cof', 'is_buro', )
|
||||
list_display = UserAdmin.list_display + (
|
||||
"profile_login_clipper",
|
||||
"profile_phone",
|
||||
"profile_occupation",
|
||||
"profile_mailing_cof",
|
||||
"profile_mailing_bda",
|
||||
"profile_mailing_bda_revente",
|
||||
"is_cof",
|
||||
"is_buro",
|
||||
)
|
||||
list_display_links = ('username', 'email', 'first_name', 'last_name')
|
||||
list_filter = UserAdmin.list_filter \
|
||||
+ ('profile__is_cof', 'profile__is_buro', 'profile__mailing_cof',
|
||||
'profile__mailing_bda')
|
||||
search_fields = UserAdmin.search_fields + ('profile__phone',)
|
||||
inlines = [
|
||||
CofProfileInline,
|
||||
]
|
||||
list_display_links = ("username", "email", "first_name", "last_name")
|
||||
list_filter = UserAdmin.list_filter + (
|
||||
"profile__is_cof",
|
||||
"profile__is_buro",
|
||||
"profile__mailing_cof",
|
||||
"profile__mailing_bda",
|
||||
)
|
||||
search_fields = UserAdmin.search_fields + ("profile__phone",)
|
||||
inlines = [CofProfileInline]
|
||||
|
||||
staff_fieldsets = [
|
||||
(None, {'fields': ['username', 'password']}),
|
||||
(_('Personal info'), {'fields': ['first_name', 'last_name', 'email']}),
|
||||
(None, {"fields": ["username", "password"]}),
|
||||
(_("Personal info"), {"fields": ["first_name", "last_name", "email"]}),
|
||||
]
|
||||
|
||||
def get_fieldsets(self, request, user=None):
|
||||
if not request.user.is_superuser:
|
||||
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):
|
||||
cof_group, created = Group.objects.get_or_create(name='COF')
|
||||
cof_group, created = Group.objects.get_or_create(name="COF")
|
||||
if created:
|
||||
# Si le groupe COF n'était pas déjà dans la bdd
|
||||
# On lui assigne les bonnes permissions
|
||||
perms = Permission.objects.filter(
|
||||
Q(content_type__app_label='gestioncof')
|
||||
| Q(content_type__app_label='bda')
|
||||
| (Q(content_type__app_label='auth')
|
||||
& Q(content_type__model='user')))
|
||||
Q(content_type__app_label="gestioncof")
|
||||
| Q(content_type__app_label="bda")
|
||||
| (Q(content_type__app_label="auth") & Q(content_type__model="user"))
|
||||
)
|
||||
cof_group.permissions = perms
|
||||
# On y associe les membres du Burô
|
||||
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)
|
||||
else:
|
||||
return self.username
|
||||
|
||||
|
||||
User.__str__ = user_str
|
||||
|
||||
|
||||
class EventRegistrationAdminForm(forms.ModelForm):
|
||||
class Meta:
|
||||
widgets = {"user": ModelSelect2(url="cof-user-autocomplete")}
|
||||
|
||||
|
||||
class EventRegistrationAdmin(admin.ModelAdmin):
|
||||
form = autocomplete_light.modelform_factory(EventRegistration, exclude=[])
|
||||
list_display = ('__str__', 'event', 'user', 'paid')
|
||||
list_filter = ('paid',)
|
||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
||||
'user__email', 'event__title')
|
||||
form = EventRegistrationAdminForm
|
||||
|
||||
list_display = ("__str__", "event", "user", "paid")
|
||||
list_filter = ("paid",)
|
||||
search_fields = (
|
||||
"user__username",
|
||||
"user__first_name",
|
||||
"user__last_name",
|
||||
"user__email",
|
||||
"event__title",
|
||||
)
|
||||
|
||||
|
||||
class PetitCoursAbilityAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'matiere', 'niveau', 'agrege')
|
||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
||||
'user__email', 'matiere__name', 'niveau')
|
||||
list_filter = ('matiere', 'niveau', 'agrege')
|
||||
list_display = ("user", "matiere", "niveau", "agrege")
|
||||
search_fields = (
|
||||
"user__username",
|
||||
"user__first_name",
|
||||
"user__last_name",
|
||||
"user__email",
|
||||
"matiere__name",
|
||||
"niveau",
|
||||
)
|
||||
list_filter = ("matiere", "niveau", "agrege")
|
||||
|
||||
|
||||
class PetitCoursAttributionAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'demande', 'matiere', 'rank', )
|
||||
search_fields = ('user__username', 'matiere__name')
|
||||
list_display = ("user", "demande", "matiere", "rank")
|
||||
search_fields = ("user__username", "matiere__name")
|
||||
|
||||
|
||||
class PetitCoursAttributionCounterAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'matiere', 'count', )
|
||||
list_filter = ('matiere',)
|
||||
search_fields = ('user__username', 'user__first_name', 'user__last_name',
|
||||
'user__email', 'matiere__name')
|
||||
actions = ['reset', ]
|
||||
list_display = ("user", "matiere", "count")
|
||||
list_filter = ("matiere",)
|
||||
search_fields = (
|
||||
"user__username",
|
||||
"user__first_name",
|
||||
"user__last_name",
|
||||
"user__email",
|
||||
"matiere__name",
|
||||
)
|
||||
actions = ["reset"]
|
||||
actions_on_bottom = True
|
||||
|
||||
def reset(self, request, queryset):
|
||||
queryset.update(count=0)
|
||||
|
||||
reset.short_description = "Remise à zéro du compteur"
|
||||
|
||||
|
||||
class PetitCoursDemandeAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'email', 'agrege_requis', 'niveau', 'created',
|
||||
'traitee', 'processed')
|
||||
list_filter = ('traitee', 'niveau')
|
||||
search_fields = ('name', 'email', 'phone', 'lieu', 'remarques')
|
||||
list_display = (
|
||||
"name",
|
||||
"email",
|
||||
"agrege_requis",
|
||||
"niveau",
|
||||
"created",
|
||||
"traitee",
|
||||
"processed",
|
||||
)
|
||||
list_filter = ("traitee", "niveau")
|
||||
search_fields = ("name", "email", "phone", "lieu", "remarques")
|
||||
|
||||
|
||||
class ClubAdminForm(forms.ModelForm):
|
||||
def clean(self):
|
||||
cleaned_data = super(ClubAdminForm, self).clean()
|
||||
respos = cleaned_data.get('respos')
|
||||
members = cleaned_data.get('membres')
|
||||
cleaned_data = super().clean()
|
||||
respos = cleaned_data.get("respos")
|
||||
members = cleaned_data.get("membres")
|
||||
for respo in respos.all():
|
||||
if respo not in members:
|
||||
raise forms.ValidationError(
|
||||
"Erreur : le respo %s n'est pas membre du club."
|
||||
% respo.get_full_name())
|
||||
% respo.get_full_name()
|
||||
)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class ClubAdmin(admin.ModelAdmin):
|
||||
list_display = ['name']
|
||||
list_display = ["name"]
|
||||
form = ClubAdminForm
|
||||
|
||||
|
||||
|
@ -286,7 +328,6 @@ admin.site.register(Club, ClubAdmin)
|
|||
admin.site.register(PetitCoursSubject)
|
||||
admin.site.register(PetitCoursAbility, PetitCoursAbilityAdmin)
|
||||
admin.site.register(PetitCoursAttribution, PetitCoursAttributionAdmin)
|
||||
admin.site.register(PetitCoursAttributionCounter,
|
||||
PetitCoursAttributionCounterAdmin)
|
||||
admin.site.register(PetitCoursAttributionCounter, PetitCoursAttributionCounterAdmin)
|
||||
admin.site.register(PetitCoursDemande, PetitCoursDemandeAdmin)
|
||||
admin.site.register(EventRegistration, EventRegistrationAdmin)
|
||||
|
|
|
@ -2,14 +2,16 @@ from django.apps import AppConfig
|
|||
|
||||
|
||||
class GestioncofConfig(AppConfig):
|
||||
name = 'gestioncof'
|
||||
name = "gestioncof"
|
||||
verbose_name = "Gestion des adhérents du COF"
|
||||
|
||||
def ready(self):
|
||||
from . import signals
|
||||
from . import signals # noqa
|
||||
|
||||
self.register_config()
|
||||
|
||||
def register_config(self):
|
||||
import djconfig
|
||||
from .forms import 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 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.models import CofProfile
|
||||
|
||||
|
||||
class Clipper(object):
|
||||
|
@ -21,68 +18,71 @@ class Clipper(object):
|
|||
self.clipper = clipper
|
||||
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
|
||||
def autocomplete(request):
|
||||
if "q" not in request.GET:
|
||||
raise Http404
|
||||
q = request.GET['q']
|
||||
data = {
|
||||
'q': q,
|
||||
}
|
||||
q = request.GET["q"]
|
||||
data = {"q": q}
|
||||
|
||||
queries = {}
|
||||
bits = q.split()
|
||||
|
||||
# Fetching data from User and CofProfile tables
|
||||
queries['members'] = CofProfile.objects.filter(is_cof=True)
|
||||
queries['users'] = User.objects.filter(profile__is_cof=False)
|
||||
queries["members"] = CofProfile.objects.filter(is_cof=True)
|
||||
queries["users"] = User.objects.filter(profile__is_cof=False)
|
||||
for bit in bits:
|
||||
queries['members'] = queries['members'].filter(
|
||||
queries["members"] = queries["members"].filter(
|
||||
Q(user__first_name__icontains=bit)
|
||||
| Q(user__last_name__icontains=bit)
|
||||
| Q(user__username__icontains=bit)
|
||||
| Q(login_clipper__icontains=bit))
|
||||
queries['users'] = queries['users'].filter(
|
||||
| Q(login_clipper__icontains=bit)
|
||||
)
|
||||
queries["users"] = queries["users"].filter(
|
||||
Q(first_name__icontains=bit)
|
||||
| Q(last_name__icontains=bit)
|
||||
| Q(username__icontains=bit))
|
||||
queries['members'] = queries['members'].distinct()
|
||||
queries['users'] = queries['users'].distinct()
|
||||
| Q(username__icontains=bit)
|
||||
)
|
||||
queries["members"] = queries["members"].distinct()
|
||||
queries["users"] = queries["users"].distinct()
|
||||
|
||||
# Clearing redundancies
|
||||
usernames = (
|
||||
set(queries['members'].values_list('login_clipper', flat='True'))
|
||||
| set(queries['users'].values_list('profile__login_clipper',
|
||||
flat='True'))
|
||||
usernames = set(queries["members"].values_list("login_clipper", flat="True")) | set(
|
||||
queries["users"].values_list("profile__login_clipper", flat="True")
|
||||
)
|
||||
|
||||
# Fetching data from the SPI
|
||||
if getattr(settings, 'LDAP_SERVER_URL', None):
|
||||
if getattr(settings, "LDAP_SERVER_URL", None):
|
||||
# Fetching
|
||||
ldap_query = '(&{:s})'.format(''.join(
|
||||
'(|(cn=*{bit:s}*)(uid=*{bit:s}*))'.format(bit=bit)
|
||||
for bit in bits if bit.isalnum()
|
||||
))
|
||||
ldap_query = "(&{:s})".format(
|
||||
"".join(
|
||||
"(|(cn=*{bit:s}*)(uid=*{bit:s}*))".format(bit=bit)
|
||||
for bit in bits
|
||||
if bit.isalnum()
|
||||
)
|
||||
)
|
||||
if ldap_query != "(&)":
|
||||
# If none of the bits were legal, we do not perform the query
|
||||
entries = None
|
||||
with Connection(settings.LDAP_SERVER_URL) as conn:
|
||||
conn.search(
|
||||
'dc=spi,dc=ens,dc=fr', ldap_query,
|
||||
attributes=['uid', 'cn']
|
||||
)
|
||||
conn.search("dc=spi,dc=ens,dc=fr", ldap_query, attributes=["uid", "cn"])
|
||||
entries = conn.entries
|
||||
# Clearing redundancies
|
||||
queries['clippers'] = [
|
||||
queries["clippers"] = [
|
||||
Clipper(entry.uid.value, entry.cn.value)
|
||||
for entry in entries
|
||||
if entry.uid.value
|
||||
and entry.uid.value not in usernames
|
||||
if entry.uid.value and entry.uid.value not in usernames
|
||||
]
|
||||
|
||||
# Resulting data
|
||||
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)
|
||||
|
|
|
@ -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