forked from DGNum/gestioCOF
Compare commits
85 commits
Author | SHA1 | Date | |
---|---|---|---|
635b3d1607 | |||
7709f8a652 | |||
db4d1264c1 | |||
a9be316aaa | |||
622770aec4 | |||
7acc4609d3 | |||
65c483e935 | |||
|
e6930d3ebb | ||
8d7ccccc9b | |||
4d4b536781 | |||
0674217526 | |||
bf30a9e510 | |||
47cf999359 | |||
e1bd6bc6ad | |||
9178511005 | |||
3997f48eb8 | |||
bb80716cb4 | |||
2ec15ad2d5 | |||
81af13a216 | |||
1a8fe48d05 | |||
18c0f0f699 | |||
2d1357c4ff | |||
853611556c | |||
29b1581ab7 | |||
2fb4c6a95c | |||
e1c9a4474d | |||
e74e03d36b | |||
c8dc0ca34e | |||
982c82ba31 | |||
531ba575c8 | |||
def9b68a90 | |||
7cd4338a3a | |||
f5bfbddfbe | |||
9e185c8cb5 | |||
b8c963779a | |||
57ff8fe131 | |||
0fdc1a2a46 | |||
aafbab29ff | |||
fb66c064cd | |||
2126224e15 | |||
d6109a9312 | |||
a69bd0426f | |||
81a4dbef7c | |||
4fedf3453d | |||
8607d77c84 | |||
bc55a3067e | |||
dd68ad91cd | |||
2f71246509 | |||
a20a1c11d6 | |||
0e1ff1765a | |||
177d413f4c | |||
01dd16c795 | |||
84c87c1b4b | |||
1b143b322f | |||
f9456e3c29 | |||
dd1350f1c2 | |||
8f4cb68d31 | |||
3a3f96a8df | |||
7d8926e459 | |||
3fee014384 | |||
cd351786bb | |||
7362c2fa2a | |||
efbc947145 | |||
0c45262fbc | |||
cabd277b4a | |||
4108efe8c9 | |||
693e4252d5 | |||
3aa928e8f0 | |||
5e2e68960b | |||
91e9beee11 | |||
ac286209ed | |||
b03cda5962 | |||
4feb567af7 | |||
1ac47885d0 | |||
071c810605 | |||
7f00ce0ff1 | |||
95136cb4eb | |||
e299997aa8 | |||
40f34926bb | |||
69976a878a | |||
6621ae3950 | |||
9288daaf9e | |||
e92c500940 | |||
|
d75eaf583f | ||
55bd3ab51d |
136 changed files with 2448 additions and 643 deletions
1
.credentials/HCAPTCHA_SECRET
Normal file
1
.credentials/HCAPTCHA_SECRET
Normal file
|
@ -0,0 +1 @@
|
||||||
|
0x0000000000000000000000000000000000000000
|
1
.credentials/HCAPTCHA_SITEKEY
Normal file
1
.credentials/HCAPTCHA_SITEKEY
Normal file
|
@ -0,0 +1 @@
|
||||||
|
10000000-ffff-ffff-ffff-000000000001
|
1
.credentials/KFETOPEN_TOKEN
Normal file
1
.credentials/KFETOPEN_TOKEN
Normal file
|
@ -0,0 +1 @@
|
||||||
|
k-feste_token
|
1
.credentials/SECRET_KEY
Normal file
1
.credentials/SECRET_KEY
Normal file
|
@ -0,0 +1 @@
|
||||||
|
insecure-key
|
1
.credentials/SYMPA_PASSWORD
Normal file
1
.credentials/SYMPA_PASSWORD
Normal file
|
@ -0,0 +1 @@
|
||||||
|
toto
|
1
.credentials/SYMPA_USERNAME
Normal file
1
.credentials/SYMPA_USERNAME
Normal file
|
@ -0,0 +1 @@
|
||||||
|
sympa
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -21,3 +21,4 @@ media/
|
||||||
# VSCode
|
# VSCode
|
||||||
.vscode/
|
.vscode/
|
||||||
.direnv
|
.direnv
|
||||||
|
.static
|
||||||
|
|
|
@ -43,13 +43,21 @@ variables:
|
||||||
# Keep this disabled for now, as it may kill GitLab...
|
# Keep this disabled for now, as it may kill GitLab...
|
||||||
# coverage: '/TOTAL.*\s(\d+\.\d+)\%$/'
|
# coverage: '/TOTAL.*\s(\d+\.\d+)\%$/'
|
||||||
|
|
||||||
|
kfettest:
|
||||||
|
stage: test
|
||||||
|
extends: .test_template
|
||||||
|
variables:
|
||||||
|
DJANGO_SETTINGS_MODULE: "gestioasso.settings.cof_prod"
|
||||||
|
script:
|
||||||
|
- coverage run manage.py test kfet
|
||||||
|
|
||||||
coftest:
|
coftest:
|
||||||
stage: test
|
stage: test
|
||||||
extends: .test_template
|
extends: .test_template
|
||||||
variables:
|
variables:
|
||||||
DJANGO_SETTINGS_MODULE: "gestioasso.settings.cof_prod"
|
DJANGO_SETTINGS_MODULE: "gestioasso.settings.cof_prod"
|
||||||
script:
|
script:
|
||||||
- coverage run manage.py test gestioncof bda kfet petitscours shared --parallel
|
- coverage run manage.py test gestioncof bda petitscours shared --parallel
|
||||||
|
|
||||||
bdstest:
|
bdstest:
|
||||||
stage: test
|
stage: test
|
||||||
|
|
|
@ -23,6 +23,9 @@ adhérents ni des cotisations.
|
||||||
|
|
||||||
## TODO Prod
|
## TODO Prod
|
||||||
|
|
||||||
|
- Lancer `python manage.py update_translation_fields` après la migration
|
||||||
|
- Mettre à jour les units systemd `daphne.service` et `worker.service`
|
||||||
|
|
||||||
- Créer un compte hCaptcha (https://www.hcaptcha.com/), au COF, et remplacer les secrets associés
|
- Créer un compte hCaptcha (https://www.hcaptcha.com/), au COF, et remplacer les secrets associés
|
||||||
|
|
||||||
## Version ??? - ??/??/????
|
## Version ??? - ??/??/????
|
||||||
|
@ -65,6 +68,8 @@ adhérents ni des cotisations.
|
||||||
|
|
||||||
- Fixe un problème de rendu causé par l'agrandissement du menu
|
- Fixe un problème de rendu causé par l'agrandissement du menu
|
||||||
|
|
||||||
|
- Mise à jour vers Channels 3.x et Django 3.2
|
||||||
|
|
||||||
## Version 0.12 - 17/06/2022
|
## Version 0.12 - 17/06/2022
|
||||||
|
|
||||||
### K-Fêt
|
### K-Fêt
|
||||||
|
|
12
README.md
12
README.md
|
@ -18,7 +18,7 @@ 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
|
que sqlite3, un moteur de base de données léger et simple d'utilisation. Sous
|
||||||
Debian et dérivées (Ubuntu, ...) :
|
Debian et dérivées (Ubuntu, ...) :
|
||||||
|
|
||||||
sudo apt-get install python3-pip python3-dev python3-venv sqlite3
|
sudo apt-get install python3-pip python3-dev python3-venv sqlite3 libsasl2-dev python-dev-is-python3 libldap2-dev libssl-dev
|
||||||
|
|
||||||
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
|
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
|
||||||
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
|
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
|
||||||
|
@ -30,7 +30,15 @@ Pour l'activer, il faut taper
|
||||||
|
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
|
|
||||||
depuis le même dossier.
|
depuis le même dossier. Pour préparer l'environnement à l'utilisation de `./manage.py`
|
||||||
|
(qui permet de faire des tests en local), il faut également taper
|
||||||
|
|
||||||
|
export CREDENTIALS_DIRECTORY=$(realpath .credentials)
|
||||||
|
export DJANGO_SETTINGS_MODULE=gestioasso.settings.local
|
||||||
|
export GESTIOCOF_DEBUG=true
|
||||||
|
export GESTIOCOF_STATIC_ROOT=$(realpath .static)
|
||||||
|
export GESTIOBDS_DEBUG=true
|
||||||
|
export GESTIOBDS_STATIC_ROOT=$(realpath .static)
|
||||||
|
|
||||||
Vous pouvez maintenant installer les dépendances Python depuis le fichier
|
Vous pouvez maintenant installer les dépendances Python depuis le fichier
|
||||||
`requirements-devel.txt` :
|
`requirements-devel.txt` :
|
||||||
|
|
23
bda/migrations/0019_auto_20220630_1245.py
Normal file
23
bda/migrations/0019_auto_20220630_1245.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# Generated by Django 3.2.13 on 2022-06-30 10:45
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bda", "0018_auto_20201021_1818"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="choixspectacle",
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="choixspectacle",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("participant", "spectacle"), name="unique_participation"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
18
bda/migrations/0019_auto_20240707_1359.py
Normal file
18
bda/migrations/0019_auto_20240707_1359.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.28 on 2024-07-07 11:59
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('bda', '0018_auto_20201021_1818'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='attribution',
|
||||||
|
name='paymenttype',
|
||||||
|
field=models.CharField(blank=True, choices=[('cash', 'Cash'), ('cb', 'CB'), ('cheque', 'Chèque'), ('virement', 'Virement'), ('autre', 'Autre')], max_length=8, verbose_name='Moyen de paiement'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Generated by Django 4.2.16 on 2025-02-26 08:23
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bda", "0019_auto_20220630_1245"),
|
||||||
|
("bda", "0019_auto_20240707_1359"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = []
|
|
@ -151,6 +151,7 @@ PAYMENT_TYPES = (
|
||||||
("cash", "Cash"),
|
("cash", "Cash"),
|
||||||
("cb", "CB"),
|
("cb", "CB"),
|
||||||
("cheque", "Chèque"),
|
("cheque", "Chèque"),
|
||||||
|
("virement", "Virement"),
|
||||||
("autre", "Autre"),
|
("autre", "Autre"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -163,7 +164,7 @@ class Attribution(models.Model):
|
||||||
given = models.BooleanField("Donnée", default=False)
|
given = models.BooleanField("Donnée", default=False)
|
||||||
paid = models.BooleanField("Payée", default=False)
|
paid = models.BooleanField("Payée", default=False)
|
||||||
paymenttype = models.CharField(
|
paymenttype = models.CharField(
|
||||||
"Moyen de paiement", max_length=6, choices=PAYMENT_TYPES, blank=True
|
"Moyen de paiement", max_length=8, choices=PAYMENT_TYPES, blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -253,7 +254,11 @@ class ChoixSpectacle(models.Model):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ("priority",)
|
ordering = ("priority",)
|
||||||
unique_together = (("participant", "spectacle"),)
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["participant", "spectacle"], name="unique_participation"
|
||||||
|
)
|
||||||
|
]
|
||||||
verbose_name = "voeu"
|
verbose_name = "voeu"
|
||||||
verbose_name_plural = "voeux"
|
verbose_name_plural = "voeux"
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<link type="text/css" rel="stylesheet" href="{% static "bda/css/bda.css" %}" />
|
<link type="text/css" rel="stylesheet" href="{% static "bda/css/bda.css" %}" />
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
<h2>État des inscriptions BdA</h2>
|
<h2>État des inscriptions BdA</h2>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<script type="text/javascript" src="{% static 'vendor/jquery/jquery-ui.min.js' %}" ></script>
|
<script type="text/javascript" src="{% static 'vendor/jquery/jquery-ui.min.js' %}" ></script>
|
||||||
|
|
|
@ -6,25 +6,23 @@ pour les spectacles suivants :
|
||||||
- 1 place pour {{ place }}{% endfor %}
|
- 1 place pour {{ place }}{% endfor %}
|
||||||
|
|
||||||
*Paiement*
|
*Paiement*
|
||||||
L'intégralité de ces places de spectacles est à régler dès maintenant et AVANT
|
Au burô :
|
||||||
vendredi prochain, au bureau du COF pendant les heures de permanences (du lundi au vendredi
|
L'intégralité de ces places de spectacles est à régler dès maintenant, au bureau du COF pendant les heures de permanences (lundi, mardi, jeudi entre 12h et 14h et entre 18h30 et 19h30, mercredi entre 18h30 et 19h30, vendredi entre 12h et 14h). Les places sont à régler AVANT les représentations. Si vous êtes en vacances, vous pourrez venir les régler dès votre retour. Il est demandé à chacun·e de prendre garde à honorer l’ensemble des places qui lui sont attribuées et de s'engager de fait à payer la ou les place(s) qui lui sont attribuées.
|
||||||
entre 12h et 14h, et entre 18h et 20h). Des facilités de paiement sont bien
|
|
||||||
évidemment possibles : nous pouvons ne pas encaisser le chèque immédiatement,
|
Par virements :
|
||||||
ou bien découper votre paiement en deux fois. Pour ceux qui ne pourraient pas
|
L'intégralité de ces places de spectacles est à régler dès maintenant par virements. Il vous sera demandé d'envoyer une confirmation de l'envoi de virement à bda@ens.fr.
|
||||||
venir payer au bureau, merci de nous contacter par mail.
|
IBAN AEENS : FR76 4255 9100 0008 0263 8331 927
|
||||||
|
Motif de virements : AVR25(ou MAI25)-tirageprintemps-NOM-prénom
|
||||||
|
|
||||||
|
Les places sont à régler AVANT les représentations. Il est demandé à chacun·e de prendre garde à honorer l’ensemble des places qui lui sont attribuées et de s'engager de fait à payer la ou les place(s) qui lui sont attribuées.
|
||||||
|
Des facilités de paiement sont bien évidemment possibles : nous pouvons ne pas encaisser le chèque immédiatement, ou bien découper votre paiement en deux fois. Pour ceux qui ne pourraient pas venir payer au bureau, merci de nous contacter par mail.
|
||||||
|
|
||||||
*Mode de retrait des places*
|
*Mode de retrait des places*
|
||||||
Au moment du paiement, certaines places vous seront remises directement,
|
Au moment du paiement, certaines places vous seront remises directement, d'autres seront à récupérer au cours de l'année, d'autres encore seront nominatives et à retirer le soir même dans les théâtres correspondants. Pour chaque spectacle, vous recevrez un mail quelques jours avant la représentation vous indiquant le mode de retrait.
|
||||||
d'autres seront à récupérer au cours de l'année, d'autres encore seront
|
Nous vous rappelons que l'obtention de places du BdA vous engage à respecter les règles de fonctionnement :
|
||||||
nominatives et à retirer le soir même dans les théâtres correspondants.
|
|
||||||
Pour chaque spectacle, vous recevrez un mail quelques jours avant la
|
|
||||||
représentation vous indiquant le mode de retrait.
|
|
||||||
|
|
||||||
Nous vous rappelons que l'obtention de places du BdA vous engage à
|
|
||||||
respecter les règles de fonctionnement :
|
|
||||||
https://bda.ens.fr/lequipe/charte-bda/
|
https://bda.ens.fr/lequipe/charte-bda/
|
||||||
Un système de revente des places via les mails BdA-revente est disponible
|
|
||||||
directement sur votre compte GestioCOF.
|
Un système de revente des places via les mails BdA-revente est disponible directement sur votre compte GestioCOF. Pour pouvoir l'utiliser, il faut que vous ayez payé vos places en amont.
|
||||||
|
|
||||||
En vous souhaitant de très beaux spectacles tout au long de l'année,
|
En vous souhaitant de très beaux spectacles tout au long de l'année,
|
||||||
--
|
--
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
<h2>{{ spectacle }}</h2>
|
<h2>{{ spectacle }}</h2>
|
||||||
|
|
|
@ -11,9 +11,19 @@
|
||||||
<td>{{place.spectacle.date}}</td>
|
<td>{{place.spectacle.date}}</td>
|
||||||
<td>{% if place.double %}deux places{%else%}une place{% endif %}</td>
|
<td>{% if place.double %}deux places{%else%}une place{% endif %}</td>
|
||||||
<td>{% if place.spectacle.listing %}sur listing{% else %}place physique{% endif %}</td>
|
<td>{% if place.spectacle.listing %}sur listing{% else %}place physique{% endif %}</td>
|
||||||
|
<td>
|
||||||
|
{% if place.unpaid == 0 %}
|
||||||
|
Payé
|
||||||
|
{% elif place.unpaid == 1 %}
|
||||||
|
Une place à payer ({{place.unpaid_price|floatformat}}€)
|
||||||
|
{% else %}
|
||||||
|
Deux places à payer ({{place.unpaid_price|floatformat}}€)
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
<h4 class="bda-prix">Reste à payer : {{ unpaid|floatformat }}€</h4>
|
||||||
<h4 class="bda-prix">Total à payer : {{ total|floatformat }}€</h4>
|
<h4 class="bda-prix">Total à payer : {{ total|floatformat }}€</h4>
|
||||||
<br/>
|
<br/>
|
||||||
<p>Ne manque pas un spectacle avec le
|
<p>Ne manque pas un spectacle avec le
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
|
|
||||||
{%block realcontent %}
|
{%block realcontent %}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
<h2>Inscription à une revente</h2>
|
<h2>Inscription à une revente</h2>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles%}
|
{% load static %}
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
<h2>Inscriptions pour BdA-Revente</h2>
|
<h2>Inscriptions pour BdA-Revente</h2>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
{% block realcontent %}
|
{% block realcontent %}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
{% block extra_head %}
|
{% block extra_head %}
|
||||||
<link type="text/css" rel="stylesheet" href="{% static "bda/css/bda.css" %}" />
|
<link type="text/css" rel="stylesheet" href="{% static "bda/css/bda.css" %}" />
|
||||||
|
|
42
bda/urls.py
42
bda/urls.py
|
@ -1,74 +1,80 @@
|
||||||
from django.conf.urls import url
|
from django.urls import re_path
|
||||||
|
|
||||||
from bda import views
|
from bda import views
|
||||||
from bda.views import SpectacleListView
|
from bda.views import SpectacleListView
|
||||||
from gestioncof.decorators import buro_required
|
from gestioncof.decorators import buro_required
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(
|
re_path(
|
||||||
r"^inscription/(?P<tirage_id>\d+)$",
|
r"^inscription/(?P<tirage_id>\d+)$",
|
||||||
views.inscription,
|
views.inscription,
|
||||||
name="bda-tirage-inscription",
|
name="bda-tirage-inscription",
|
||||||
),
|
),
|
||||||
url(r"^places/(?P<tirage_id>\d+)$", views.places, name="bda-places-attribuees"),
|
re_path(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"),
|
re_path(
|
||||||
url(r"^tirage/(?P<tirage_id>\d+)$", views.tirage, name="bda-tirage"),
|
r"^etat-places/(?P<tirage_id>\d+)$", views.etat_places, name="bda-etat-places"
|
||||||
url(
|
),
|
||||||
|
re_path(r"^tirage/(?P<tirage_id>\d+)$", views.tirage, name="bda-tirage"),
|
||||||
|
re_path(
|
||||||
r"^spectacles/(?P<tirage_id>\d+)$",
|
r"^spectacles/(?P<tirage_id>\d+)$",
|
||||||
buro_required(SpectacleListView.as_view()),
|
buro_required(SpectacleListView.as_view()),
|
||||||
name="bda-liste-spectacles",
|
name="bda-liste-spectacles",
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r"^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$",
|
r"^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$",
|
||||||
views.spectacle,
|
views.spectacle,
|
||||||
name="bda-spectacle",
|
name="bda-spectacle",
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r"^spectacles/unpaid/(?P<tirage_id>\d+)$",
|
r"^spectacles/unpaid/(?P<tirage_id>\d+)$",
|
||||||
views.UnpaidParticipants.as_view(),
|
views.UnpaidParticipants.as_view(),
|
||||||
name="bda-unpaid",
|
name="bda-unpaid",
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r"^spectacles/autocomplete$",
|
r"^spectacles/autocomplete$",
|
||||||
views.spectacle_autocomplete,
|
views.spectacle_autocomplete,
|
||||||
name="bda-spectacle-autocomplete",
|
name="bda-spectacle-autocomplete",
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r"^participants/autocomplete$",
|
r"^participants/autocomplete$",
|
||||||
views.participant_autocomplete,
|
views.participant_autocomplete,
|
||||||
name="bda-participant-autocomplete",
|
name="bda-participant-autocomplete",
|
||||||
),
|
),
|
||||||
# Urls BdA-Revente
|
# Urls BdA-Revente
|
||||||
url(
|
re_path(
|
||||||
r"^revente/(?P<tirage_id>\d+)/manage$",
|
r"^revente/(?P<tirage_id>\d+)/manage$",
|
||||||
views.revente_manage,
|
views.revente_manage,
|
||||||
name="bda-revente-manage",
|
name="bda-revente-manage",
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r"^revente/(?P<tirage_id>\d+)/subscribe$",
|
r"^revente/(?P<tirage_id>\d+)/subscribe$",
|
||||||
views.revente_subscribe,
|
views.revente_subscribe,
|
||||||
name="bda-revente-subscribe",
|
name="bda-revente-subscribe",
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r"^revente/(?P<tirage_id>\d+)/tirages$",
|
r"^revente/(?P<tirage_id>\d+)/tirages$",
|
||||||
views.revente_tirages,
|
views.revente_tirages,
|
||||||
name="bda-revente-tirages",
|
name="bda-revente-tirages",
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r"^revente/(?P<spectacle_id>\d+)/buy$",
|
r"^revente/(?P<spectacle_id>\d+)/buy$",
|
||||||
views.revente_buy,
|
views.revente_buy,
|
||||||
name="bda-revente-buy",
|
name="bda-revente-buy",
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r"^revente/(?P<revente_id>\d+)/confirm$",
|
r"^revente/(?P<revente_id>\d+)/confirm$",
|
||||||
views.revente_confirm,
|
views.revente_confirm,
|
||||||
name="bda-revente-confirm",
|
name="bda-revente-confirm",
|
||||||
),
|
),
|
||||||
url(
|
re_path(
|
||||||
r"^revente/(?P<tirage_id>\d+)/shotgun$",
|
r"^revente/(?P<tirage_id>\d+)/shotgun$",
|
||||||
views.revente_shotgun,
|
views.revente_shotgun,
|
||||||
name="bda-revente-shotgun",
|
name="bda-revente-shotgun",
|
||||||
),
|
),
|
||||||
url(r"^mails-rappel/(?P<spectacle_id>\d+)$", views.send_rappel, name="bda-rappels"),
|
re_path(
|
||||||
url(r"^catalogue/(?P<request_type>[a-z]+)$", views.catalogue, name="bda-catalogue"),
|
r"^mails-rappel/(?P<spectacle_id>\d+)$", views.send_rappel, name="bda-rappels"
|
||||||
|
),
|
||||||
|
re_path(
|
||||||
|
r"^catalogue/(?P<request_type>[a-z]+)$", views.catalogue, name="bda-catalogue"
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
10
bda/views.py
10
bda/views.py
|
@ -114,6 +114,7 @@ def places(request, tirage_id):
|
||||||
"spectacle__date", "spectacle"
|
"spectacle__date", "spectacle"
|
||||||
).select_related("spectacle", "spectacle__location")
|
).select_related("spectacle", "spectacle__location")
|
||||||
total = sum(place.spectacle.price for place in places)
|
total = sum(place.spectacle.price for place in places)
|
||||||
|
unpaid = 0
|
||||||
filtered_places = []
|
filtered_places = []
|
||||||
places_dict = {}
|
places_dict = {}
|
||||||
spectacles = []
|
spectacles = []
|
||||||
|
@ -124,6 +125,8 @@ def places(request, tirage_id):
|
||||||
places_dict[place.spectacle].double = True
|
places_dict[place.spectacle].double = True
|
||||||
else:
|
else:
|
||||||
place.double = False
|
place.double = False
|
||||||
|
place.unpaid = 0
|
||||||
|
place.unpaid_price = 0
|
||||||
places_dict[place.spectacle] = place
|
places_dict[place.spectacle] = place
|
||||||
spectacles.append(place.spectacle)
|
spectacles.append(place.spectacle)
|
||||||
filtered_places.append(place)
|
filtered_places.append(place)
|
||||||
|
@ -132,6 +135,12 @@ def places(request, tirage_id):
|
||||||
warning = True
|
warning = True
|
||||||
else:
|
else:
|
||||||
dates.append(date)
|
dates.append(date)
|
||||||
|
|
||||||
|
if not place.paid:
|
||||||
|
unpaid += place.spectacle.price
|
||||||
|
places_dict[place.spectacle].unpaid += 1
|
||||||
|
places_dict[place.spectacle].unpaid_price += place.spectacle.price
|
||||||
|
|
||||||
# On prévient l'utilisateur s'il a deux places à la même date
|
# On prévient l'utilisateur s'il a deux places à la même date
|
||||||
if warning:
|
if warning:
|
||||||
messages.warning(
|
messages.warning(
|
||||||
|
@ -147,6 +156,7 @@ def places(request, tirage_id):
|
||||||
"places": filtered_places,
|
"places": filtered_places,
|
||||||
"tirage": tirage,
|
"tirage": tirage,
|
||||||
"total": total,
|
"total": total,
|
||||||
|
"unpaid": unpaid,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
default_app_config = "bds.apps.BdsConfig"
|
|
|
@ -1,5 +1,4 @@
|
||||||
from django import apps as global_apps
|
from django.apps import AppConfig, apps as global_apps
|
||||||
from django.apps import AppConfig
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.models.signals import post_migrate
|
from django.db.models.signals import post_migrate
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
{% load bulma_utils %}
|
{% load bulma_utils %}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
|
|
@ -31,7 +31,7 @@ class TestHomeView(TestCase):
|
||||||
user, backend="django.contrib.auth.backends.ModelBackend"
|
user, backend="django.contrib.auth.backends.ModelBackend"
|
||||||
)
|
)
|
||||||
resp = self.client.get(reverse("bds:home"))
|
resp = self.client.get(reverse("bds:home"))
|
||||||
self.assertEquals(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
class TestRegistrationView(TestCase):
|
class TestRegistrationView(TestCase):
|
||||||
|
@ -48,12 +48,12 @@ class TestRegistrationView(TestCase):
|
||||||
# Logged-in but unprivileged GET
|
# Logged-in but unprivileged GET
|
||||||
client.force_login(user, backend="django.contrib.auth.backends.ModelBackend")
|
client.force_login(user, backend="django.contrib.auth.backends.ModelBackend")
|
||||||
resp = client.get(url)
|
resp = client.get(url)
|
||||||
self.assertEquals(resp.status_code, 403)
|
self.assertEqual(resp.status_code, 403)
|
||||||
|
|
||||||
# Burô user GET
|
# Burô user GET
|
||||||
give_bds_buro_permissions(user)
|
give_bds_buro_permissions(user)
|
||||||
resp = client.get(url)
|
resp = client.get(url)
|
||||||
self.assertEquals(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
||||||
@mock.patch("gestioncof.signals.messages")
|
@mock.patch("gestioncof.signals.messages")
|
||||||
def test_get(self, mock_messages):
|
def test_get(self, mock_messages):
|
||||||
|
@ -68,9 +68,9 @@ class TestRegistrationView(TestCase):
|
||||||
# Logged-in but unprivileged GET
|
# Logged-in but unprivileged GET
|
||||||
client.force_login(user, backend="django.contrib.auth.backends.ModelBackend")
|
client.force_login(user, backend="django.contrib.auth.backends.ModelBackend")
|
||||||
resp = client.get(url)
|
resp = client.get(url)
|
||||||
self.assertEquals(resp.status_code, 403)
|
self.assertEqual(resp.status_code, 403)
|
||||||
|
|
||||||
# Burô user GET
|
# Burô user GET
|
||||||
give_bds_buro_permissions(user)
|
give_bds_buro_permissions(user)
|
||||||
resp = client.get(url)
|
resp = client.get(url)
|
||||||
self.assertEquals(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from bds import views
|
from bds import views
|
||||||
|
from shared.views import SympaListView
|
||||||
|
|
||||||
app_name = "bds"
|
app_name = "bds"
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
@ -21,4 +22,10 @@ urlpatterns = [
|
||||||
name="members.expired",
|
name="members.expired",
|
||||||
),
|
),
|
||||||
path("members/reset", views.ResetMembershipView.as_view(), name="members.reset"),
|
path("members/reset", views.ResetMembershipView.as_view(), name="members.reset"),
|
||||||
|
# Sympa export view
|
||||||
|
path(
|
||||||
|
"sympa/members/",
|
||||||
|
SympaListView.as_view(filters={"bds__is_member": True}),
|
||||||
|
name="export.sympa",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|
63
events/migrations/0005_auto_20220630_1239.py
Normal file
63
events/migrations/0005_auto_20220630_1239.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
# Generated by Django 3.2.13 on 2022-06-30 10:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("events", "0004_unique_constraints"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="extrafield",
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="extrafieldcontent",
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="option",
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="optionchoice",
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="registration",
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="extrafield",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("event", "name"), name="unique_extra_field"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="extrafieldcontent",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("field", "registration"), name="unique_extra_field_content"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="option",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("event", "name"), name="unique_event_option"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="optionchoice",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("option", "choice"), name="unique_option_choice"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="registration",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("event", "user"), name="unique_registration"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -72,9 +72,13 @@ class Option(models.Model):
|
||||||
multi_choices = models.BooleanField(_("choix multiples"), default=False)
|
multi_choices = models.BooleanField(_("choix multiples"), default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["event", "name"], name="unique_event_option"
|
||||||
|
)
|
||||||
|
]
|
||||||
verbose_name = _("option d'événement")
|
verbose_name = _("option d'événement")
|
||||||
verbose_name_plural = _("options d'événement")
|
verbose_name_plural = _("options d'événement")
|
||||||
unique_together = [["event", "name"]]
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -87,9 +91,13 @@ class OptionChoice(models.Model):
|
||||||
choice = models.CharField(_("choix"), max_length=200)
|
choice = models.CharField(_("choix"), max_length=200)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["option", "choice"], name="unique_option_choice"
|
||||||
|
)
|
||||||
|
]
|
||||||
verbose_name = _("choix d'option d'événement")
|
verbose_name = _("choix d'option d'événement")
|
||||||
verbose_name_plural = _("choix d'option d'événement")
|
verbose_name_plural = _("choix d'option d'événement")
|
||||||
unique_together = [["option", "choice"]]
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.choice
|
return self.choice
|
||||||
|
@ -118,7 +126,9 @@ class ExtraField(models.Model):
|
||||||
field_type = models.CharField(_("type de champ"), max_length=9, choices=FIELD_TYPE)
|
field_type = models.CharField(_("type de champ"), max_length=9, choices=FIELD_TYPE)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = [["event", "name"]]
|
constraints = [
|
||||||
|
models.UniqueConstraint(fields=["event", "name"], name="unique_extra_field")
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class ExtraFieldContent(models.Model):
|
class ExtraFieldContent(models.Model):
|
||||||
|
@ -137,9 +147,13 @@ class ExtraFieldContent(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["field", "registration"], name="unique_extra_field_content"
|
||||||
|
)
|
||||||
|
]
|
||||||
verbose_name = _("contenu d'un champ événement supplémentaire")
|
verbose_name = _("contenu d'un champ événement supplémentaire")
|
||||||
verbose_name_plural = _("contenus d'un champ événement supplémentaire")
|
verbose_name_plural = _("contenus d'un champ événement supplémentaire")
|
||||||
unique_together = [["field", "registration"]]
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
max_length = 50
|
max_length = 50
|
||||||
|
@ -163,9 +177,13 @@ class Registration(models.Model):
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["event", "user"], name="unique_registration"
|
||||||
|
)
|
||||||
|
]
|
||||||
verbose_name = _("inscription à un événement")
|
verbose_name = _("inscription à un événement")
|
||||||
verbose_name_plural = _("inscriptions à un événement")
|
verbose_name_plural = _("inscriptions à un événement")
|
||||||
unique_together = [["event", "user"]]
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "inscription de {} à {}".format(self.user, self.event)
|
return "inscription de {} à {}".format(self.user, self.event)
|
||||||
|
|
|
@ -1,8 +1,15 @@
|
||||||
|
"""
|
||||||
|
ASGI entrypoint. Configures Django and then runs the application
|
||||||
|
defined in the ASGI_APPLICATION setting.
|
||||||
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from channels.asgi import get_channel_layer
|
import django
|
||||||
|
from channels.routing import get_default_application
|
||||||
|
|
||||||
if "DJANGO_SETTINGS_MODULE" not in os.environ:
|
if "DJANGO_SETTINGS_MODULE" not in os.environ:
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings")
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings.local")
|
||||||
|
|
||||||
channel_layer = get_channel_layer()
|
django.setup()
|
||||||
|
application = get_default_application()
|
||||||
|
|
|
@ -1,3 +1,20 @@
|
||||||
from channels.routing import include
|
from channels.auth import AuthMiddlewareStack
|
||||||
|
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
routing = [include("kfet.routing.routing", path=r"^/ws/k-fet")]
|
from kfet.routing import KFRouter
|
||||||
|
|
||||||
|
application = ProtocolTypeRouter(
|
||||||
|
{
|
||||||
|
# WebSocket chat handler
|
||||||
|
"websocket": AuthMiddlewareStack(
|
||||||
|
URLRouter(
|
||||||
|
[
|
||||||
|
path("ws/k-fet", KFRouter),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
),
|
||||||
|
"http": get_asgi_application(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -67,8 +67,8 @@ INSTALLED_APPS = (
|
||||||
"wagtail.images",
|
"wagtail.images",
|
||||||
"wagtail.search",
|
"wagtail.search",
|
||||||
"wagtail.admin",
|
"wagtail.admin",
|
||||||
"wagtail.core",
|
"wagtail",
|
||||||
"wagtail.contrib.modeladmin",
|
# "wagtail.contrib.modeladmin",
|
||||||
"wagtail.contrib.routable_page",
|
"wagtail.contrib.routable_page",
|
||||||
"wagtailmenus",
|
"wagtailmenus",
|
||||||
"modelcluster",
|
"modelcluster",
|
||||||
|
@ -85,7 +85,6 @@ MIDDLEWARE = (
|
||||||
+ MIDDLEWARE
|
+ MIDDLEWARE
|
||||||
+ [
|
+ [
|
||||||
"djconfig.middleware.DjConfigMiddleware",
|
"djconfig.middleware.DjConfigMiddleware",
|
||||||
"wagtail.core.middleware.SiteMiddleware",
|
|
||||||
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
|
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -109,6 +108,8 @@ MEDIA_URL = "/gestion/media/"
|
||||||
CORS_ORIGIN_WHITELIST = ("bda.ens.fr", "www.bda.ens.fr" "cof.ens.fr", "www.cof.ens.fr")
|
CORS_ORIGIN_WHITELIST = ("bda.ens.fr", "www.bda.ens.fr" "cof.ens.fr", "www.cof.ens.fr")
|
||||||
|
|
||||||
|
|
||||||
|
ASGI_APPLICATION = "gestioasso.routing.application"
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Auth-related stuff
|
# Auth-related stuff
|
||||||
# ---
|
# ---
|
||||||
|
@ -147,7 +148,7 @@ CACHES = {
|
||||||
|
|
||||||
CHANNEL_LAYERS = {
|
CHANNEL_LAYERS = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "asgi_redis.RedisChannelLayer",
|
"BACKEND": "shared.channels.ChannelLayer",
|
||||||
"CONFIG": {
|
"CONFIG": {
|
||||||
"hosts": [
|
"hosts": [
|
||||||
(
|
(
|
||||||
|
@ -160,11 +161,9 @@ CHANNEL_LAYERS = {
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ROUTING": "gestioasso.routing.routing",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# reCAPTCHA settings
|
# reCAPTCHA settings
|
||||||
# https://github.com/praekelt/django-recaptcha
|
# https://github.com/praekelt/django-recaptcha
|
||||||
|
@ -206,7 +205,7 @@ MAIL_DATA = {
|
||||||
"REPLYTO": "cof@ens.fr",
|
"REPLYTO": "cof@ens.fr",
|
||||||
},
|
},
|
||||||
"rappels": {"FROM": "Le BdA <bda@ens.fr>", "REPLYTO": "Le BdA <bda@ens.fr>"},
|
"rappels": {"FROM": "Le BdA <bda@ens.fr>", "REPLYTO": "Le BdA <bda@ens.fr>"},
|
||||||
"rappel_negatif": {
|
"kfet": {
|
||||||
"FROM": "La K-Fêt <chefs-k-fet@ens.fr>",
|
"FROM": "La K-Fêt <chefs-k-fet@ens.fr>",
|
||||||
"REPLYTO": "La K-Fêt <chefs-k-fet@ens.fr>",
|
"REPLYTO": "La K-Fêt <chefs-k-fet@ens.fr>",
|
||||||
},
|
},
|
||||||
|
|
|
@ -101,7 +101,7 @@ TEMPLATES = [
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.postgresql_psycopg2",
|
"ENGINE": "django.db.backends.postgresql",
|
||||||
"NAME": DBNAME,
|
"NAME": DBNAME,
|
||||||
"USER": DBUSER,
|
"USER": DBUSER,
|
||||||
"PASSWORD": DBPASSWD,
|
"PASSWORD": DBPASSWD,
|
||||||
|
@ -111,6 +111,7 @@ DATABASES = {
|
||||||
|
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||||
|
|
||||||
# ---
|
# ---
|
||||||
# Internationalization
|
# Internationalization
|
||||||
|
|
|
@ -27,6 +27,9 @@ ALLOWED_HOSTS = []
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
|
||||||
|
SYMPA_PASSWORD = b"sympa"
|
||||||
|
SYMPA_USERNAME = b"sympa"
|
||||||
|
|
||||||
if TESTING:
|
if TESTING:
|
||||||
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]
|
||||||
|
|
||||||
|
@ -47,8 +50,7 @@ CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"
|
||||||
# Use the default in memory asgi backend for local development
|
# Use the default in memory asgi backend for local development
|
||||||
CHANNEL_LAYERS = {
|
CHANNEL_LAYERS = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "asgiref.inmemory.ChannelLayer",
|
"BACKEND": "channels.layers.InMemoryChannelLayer",
|
||||||
"ROUTING": "gestioasso.routing.routing",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
197
gestioasso/settings_bds.py
Normal file
197
gestioasso/settings_bds.py
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
"""
|
||||||
|
Django settings for the gestioBDS project.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from loadcredential import Credentials
|
||||||
|
|
||||||
|
credentials = Credentials(env_prefix="GESTIOBDS_")
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = credentials["SECRET_KEY"]
|
||||||
|
|
||||||
|
# WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = credentials.get_json("DEBUG", False)
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = credentials.get_json("ALLOWED_HOSTS", [])
|
||||||
|
ADMINS = credentials.get_json("ADMINS", [])
|
||||||
|
|
||||||
|
SERVER_EMAIL = credentials.get("SERVER_EMAIL")
|
||||||
|
EMAIL_HOST = credentials.get("EMAIL_HOST")
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||||
|
|
||||||
|
SYMPA_PASSWORD = credentials["SYMPA_PASSWORD"].encode()
|
||||||
|
SYMPA_USERNAME = credentials["SYMPA_USERNAME"].encode()
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Installed Apps configuration
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"shared",
|
||||||
|
# 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.admin",
|
||||||
|
"django.contrib.admindocs",
|
||||||
|
"gestioasso.apps.IgnoreSrcStaticFilesConfig",
|
||||||
|
"django_cas_ng",
|
||||||
|
"bootstrapform",
|
||||||
|
"widget_tweaks",
|
||||||
|
"bds",
|
||||||
|
"events",
|
||||||
|
"clubs",
|
||||||
|
"authens",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Middleware configuration
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"django.middleware.locale.LocaleMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# URL configuration
|
||||||
|
|
||||||
|
ROOT_URLCONF = "gestioasso.urls"
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Templates configuration
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"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",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Database configuration
|
||||||
|
|
||||||
|
DATABASES = credentials.get_json(
|
||||||
|
"DATABASES",
|
||||||
|
default={
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": (BASE_DIR / "db.sqlite3"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
CACHES = credentials.get_json(
|
||||||
|
"CACHES",
|
||||||
|
default={
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
CORS_ORIGIN_WHITELIST = credentials.get("CORS_ORIGIN_WHITELIST", [])
|
||||||
|
|
||||||
|
|
||||||
|
SITE_ID = 1
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Staticfiles configuration
|
||||||
|
|
||||||
|
STATIC_ROOT = credentials["STATIC_ROOT"]
|
||||||
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
|
MEDIA_ROOT = credentials.get("MEDIA_ROOT", (BASE_DIR / "media"))
|
||||||
|
MEDIA_URL = "/media/"
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Authens and Authentication configuration
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
|
"authens.backends.ENSCASBackend",
|
||||||
|
"authens.backends.OldCASBackend",
|
||||||
|
]
|
||||||
|
|
||||||
|
AUTHENS_USE_OLDCAS = False
|
||||||
|
|
||||||
|
LOGIN_URL = "authens:login"
|
||||||
|
LOGIN_REDIRECT_URL = "bds:home"
|
||||||
|
LOGOUT_REDIRECT_URL = "bds:home"
|
||||||
|
|
||||||
|
|
||||||
|
# ---
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||||
|
# ---
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "fr-fr"
|
||||||
|
TIME_ZONE = "Europe/Paris"
|
||||||
|
USE_I18N = True
|
||||||
|
USE_L10N = True
|
||||||
|
USE_TZ = True
|
||||||
|
LANGUAGES = (("fr", "Français"), ("en", "English"))
|
||||||
|
FORMAT_MODULE_PATH = "gestioasso.locale"
|
||||||
|
|
||||||
|
##
|
||||||
|
# Development configuration
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
|
||||||
|
def show_toolbar(request):
|
||||||
|
"""
|
||||||
|
On active la debug-toolbar en mode développement local sauf :
|
||||||
|
- dans l'admin où ça ne sert pas à grand chose;
|
||||||
|
- si la variable d'environnement DJANGO_NO_DDT est à 1 → ça permet de la désactiver
|
||||||
|
sans modifier ce fichier en exécutant `export DJANGO_NO_DDT=1` dans le terminal
|
||||||
|
qui lance `./manage.py runserver`.
|
||||||
|
|
||||||
|
Autre side effect de cette fonction : on ne fait pas la vérification de INTERNAL_IPS
|
||||||
|
que ferait la debug-toolbar par défaut, ce qui la fait fonctionner aussi à
|
||||||
|
l'intérieur de Vagrant (comportement non testé depuis un moment…)
|
||||||
|
"""
|
||||||
|
|
||||||
|
env_no_ddt = bool(os.environ.get("DJANGO_NO_DDT", None))
|
||||||
|
return not (env_no_ddt or request.path.startswith("/admin/"))
|
||||||
|
|
||||||
|
##
|
||||||
|
# Django Debug Toolbar configuration
|
||||||
|
|
||||||
|
DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": show_toolbar}
|
||||||
|
INSTALLED_APPS += ["debug_toolbar"]
|
||||||
|
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE
|
324
gestioasso/settings_cof.py
Normal file
324
gestioasso/settings_cof.py
Normal file
|
@ -0,0 +1,324 @@
|
||||||
|
"""
|
||||||
|
Django settings for the gestioCOF project.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from loadcredential import Credentials
|
||||||
|
|
||||||
|
credentials = Credentials(env_prefix="GESTIOCOF_")
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
# WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = credentials["SECRET_KEY"]
|
||||||
|
|
||||||
|
# WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = credentials.get_json("DEBUG", False)
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = credentials.get_json("ALLOWED_HOSTS", [])
|
||||||
|
ADMINS = credentials.get_json("ADMINS", [])
|
||||||
|
|
||||||
|
SERVER_EMAIL = credentials.get("SERVER_EMAIL")
|
||||||
|
EMAIL_HOST = credentials.get("EMAIL_HOST")
|
||||||
|
|
||||||
|
LDAP_SERVER_URL = credentials.get("LDAP_SERVER_URL")
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
|
||||||
|
|
||||||
|
SYMPA_PASSWORD = credentials["SYMPA_PASSWORD"].encode()
|
||||||
|
SYMPA_USERNAME = credentials["SYMPA_USERNAME"].encode()
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Installed Apps configuration
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"gestioncof",
|
||||||
|
# Must be before django admin
|
||||||
|
# https://github.com/infoportugal/wagtail-modeltranslation/issues/193
|
||||||
|
"wagtail_modeltranslation",
|
||||||
|
"wagtail_modeltranslation.makemigrations",
|
||||||
|
"wagtail_modeltranslation.migrate",
|
||||||
|
"modeltranslation",
|
||||||
|
"shared",
|
||||||
|
# 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.admin",
|
||||||
|
"django.contrib.admindocs",
|
||||||
|
"gestioasso.apps.IgnoreSrcStaticFilesConfig",
|
||||||
|
"django_cas_ng",
|
||||||
|
"bootstrapform",
|
||||||
|
"widget_tweaks",
|
||||||
|
"bda",
|
||||||
|
"petitscours",
|
||||||
|
"hcaptcha",
|
||||||
|
"kfet",
|
||||||
|
"kfet.open",
|
||||||
|
"channels",
|
||||||
|
"djconfig",
|
||||||
|
"wagtail.contrib.forms",
|
||||||
|
"wagtail.contrib.redirects",
|
||||||
|
"wagtail.embeds",
|
||||||
|
"wagtail.sites",
|
||||||
|
"wagtail.users",
|
||||||
|
"wagtail.snippets",
|
||||||
|
"wagtail.documents",
|
||||||
|
"wagtail.images",
|
||||||
|
"wagtail.search",
|
||||||
|
"wagtail.admin",
|
||||||
|
"wagtail",
|
||||||
|
# "wagtail.contrib.modeladmin",
|
||||||
|
"wagtail.contrib.routable_page",
|
||||||
|
"wagtailmenus",
|
||||||
|
"modelcluster",
|
||||||
|
"taggit",
|
||||||
|
"kfet.auth",
|
||||||
|
"kfet.cms",
|
||||||
|
"gestioncof.cms",
|
||||||
|
"django_js_reverse",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Middleware configuration
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"corsheaders.middleware.CorsMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"django.middleware.locale.LocaleMiddleware",
|
||||||
|
"djconfig.middleware.DjConfigMiddleware",
|
||||||
|
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# URL configuration
|
||||||
|
|
||||||
|
ROOT_URLCONF = "gestioasso.urls"
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Templates configuration
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"wagtailmenus.context_processors.wagtailmenus",
|
||||||
|
"djconfig.context_processors.config",
|
||||||
|
"gestioncof.shared.context_processor",
|
||||||
|
"kfet.auth.context_processors.temporary_auth",
|
||||||
|
"kfet.context_processors.config",
|
||||||
|
"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",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Wagtail configuration
|
||||||
|
|
||||||
|
WAGTAIL_SITE_NAME = "GestioCOF"
|
||||||
|
WAGTAIL_ENABLE_UPDATE_CHECK = False
|
||||||
|
TAGGIT_CASE_INSENSITIVE = True
|
||||||
|
|
||||||
|
##
|
||||||
|
# Django-js-reverse settings
|
||||||
|
|
||||||
|
JS_REVERSE_JS_VAR_NAME = "django_urls"
|
||||||
|
# Quand on aura namespace les urls...
|
||||||
|
# JS_REVERSE_INCLUDE_ONLY_NAMESPACES = ['k-fet']
|
||||||
|
|
||||||
|
##
|
||||||
|
# K-Fêt history configuration
|
||||||
|
|
||||||
|
# L'historique n'est accesible que d'aujourd'hui
|
||||||
|
# à aujourd'hui - KFET_HISTORY_DATE_LIMIT
|
||||||
|
KFET_HISTORY_DATE_LIMIT = timedelta(days=7)
|
||||||
|
|
||||||
|
# Limite plus longue pour les chefs/trez
|
||||||
|
# (qui ont la permission kfet.access_old_history)
|
||||||
|
KFET_HISTORY_LONG_DATE_LIMIT = timedelta(days=30)
|
||||||
|
|
||||||
|
# These accounts don't represent actual people and can be freely accessed
|
||||||
|
# Identification based on trigrammes
|
||||||
|
KFET_HISTORY_NO_DATE_LIMIT_TRIGRAMMES = ["LIQ", "#13"]
|
||||||
|
KFET_HISTORY_NO_DATE_LIMIT = datetime(1794, 10, 30) # AKA the distant past
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Database configuration
|
||||||
|
|
||||||
|
DATABASES = credentials.get_json(
|
||||||
|
"DATABASES",
|
||||||
|
default={
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": (BASE_DIR / "db.sqlite3"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
CACHES = credentials.get_json(
|
||||||
|
"CACHES",
|
||||||
|
default={
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
CHANNEL_LAYERS = credentials.get_json(
|
||||||
|
"CHANNEL_LAYERS",
|
||||||
|
default={
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "channels.layers.InMemoryChannelLayer",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ASGI_APPLICATION = "gestioasso.routing.application"
|
||||||
|
|
||||||
|
CORS_ALLOWED_ORIGINS = credentials.get("CORS_ALLOWED_ORIGINS", [])
|
||||||
|
CSRF_TRUSTED_ORIGINS = [f"https://{host}" for host in ALLOWED_HOSTS]
|
||||||
|
|
||||||
|
|
||||||
|
SITE_ID = 1
|
||||||
|
|
||||||
|
|
||||||
|
###
|
||||||
|
# Staticfiles configuration
|
||||||
|
|
||||||
|
STATIC_ROOT = credentials["STATIC_ROOT"]
|
||||||
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
|
MEDIA_ROOT = credentials.get("MEDIA_ROOT", (BASE_DIR / "media"))
|
||||||
|
MEDIA_URL = "/media/"
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Authentication configuration
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
"kfet.auth.backends.BlockFrozenAccountBackend", # Must be in first
|
||||||
|
"django.contrib.auth.backends.ModelBackend",
|
||||||
|
"gestioncof.shared.COFCASBackend",
|
||||||
|
"kfet.auth.backends.GenericBackend",
|
||||||
|
]
|
||||||
|
|
||||||
|
LOGIN_URL = "cof-login"
|
||||||
|
LOGIN_REDIRECT_URL = reverse_lazy("home")
|
||||||
|
|
||||||
|
# FIXME: Switch to authens
|
||||||
|
CAS_SERVER_URL = "https://cas.eleves.ens.fr/"
|
||||||
|
CAS_VERSION = "2"
|
||||||
|
CAS_LOGIN_MSG = None
|
||||||
|
CAS_IGNORE_REFERER = True
|
||||||
|
CAS_REDIRECT_URL = "/"
|
||||||
|
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# h-captcha configuration
|
||||||
|
|
||||||
|
HCAPTCHA_SITEKEY = credentials["HCAPTCHA_SITEKEY"]
|
||||||
|
HCAPTCHA_SECRET = credentials["HCAPTCHA_SECRET"]
|
||||||
|
|
||||||
|
##
|
||||||
|
# K-Fêt token for the openness indicator
|
||||||
|
|
||||||
|
KFETOPEN_TOKEN = credentials["KFETOPEN_TOKEN"]
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# Mail configuration
|
||||||
|
|
||||||
|
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>",
|
||||||
|
},
|
||||||
|
"kfet": {
|
||||||
|
"FROM": "La K-Fêt <chefs-k-fet@ens.fr>",
|
||||||
|
"REPLYTO": "La K-Fêt <chefs-k-fet@ens.fr>",
|
||||||
|
},
|
||||||
|
"revente": {
|
||||||
|
"FROM": "BdA-Revente <bda-revente@ens.fr>",
|
||||||
|
"REPLYTO": "BdA-Revente <bda-revente@ens.fr>",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# ---
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||||
|
# ---
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "fr-fr"
|
||||||
|
TIME_ZONE = "Europe/Paris"
|
||||||
|
USE_I18N = True
|
||||||
|
USE_L10N = True
|
||||||
|
USE_TZ = True
|
||||||
|
LANGUAGES = (("fr", "Français"), ("en", "English"))
|
||||||
|
FORMAT_MODULE_PATH = "gestioasso.locale"
|
||||||
|
|
||||||
|
##
|
||||||
|
# Development configuration
|
||||||
|
|
||||||
|
if DEBUG:
|
||||||
|
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||||
|
|
||||||
|
def show_toolbar(request):
|
||||||
|
"""
|
||||||
|
On active la debug-toolbar en mode développement local sauf :
|
||||||
|
- dans l'admin où ça ne sert pas à grand chose;
|
||||||
|
- si la variable d'environnement DJANGO_NO_DDT est à 1 → ça permet de la désactiver
|
||||||
|
sans modifier ce fichier en exécutant `export DJANGO_NO_DDT=1` dans le terminal
|
||||||
|
qui lance `./manage.py runserver`.
|
||||||
|
|
||||||
|
Autre side effect de cette fonction : on ne fait pas la vérification de INTERNAL_IPS
|
||||||
|
que ferait la debug-toolbar par défaut, ce qui la fait fonctionner aussi à
|
||||||
|
l'intérieur de Vagrant (comportement non testé depuis un moment…)
|
||||||
|
"""
|
||||||
|
|
||||||
|
env_no_ddt = bool(os.environ.get("DJANGO_NO_DDT", None))
|
||||||
|
return not (env_no_ddt or request.path.startswith("/admin/"))
|
||||||
|
|
||||||
|
##
|
||||||
|
# Django Debug Toolbar configuration
|
||||||
|
|
||||||
|
DEBUG_TOOLBAR_CONFIG = {"SHOW_TOOLBAR_CALLBACK": show_toolbar}
|
||||||
|
INSTALLED_APPS += ["debug_toolbar"]
|
||||||
|
MIDDLEWARE = ["debug_toolbar.middleware.DebugToolbarMiddleware"] + MIDDLEWARE
|
|
@ -1,6 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Fichier principal de configuration des urls du projet GestioCOF
|
Fichier principal de configuration des urls du projet GestioCOF
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.conf.urls.i18n import i18n_patterns
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
from django.conf.urls.static import static
|
from django.conf.urls.static import static
|
||||||
|
@ -56,12 +57,13 @@ if settings.DEBUG:
|
||||||
# Si on est en production, MEDIA_ROOT est servi par Apache.
|
# Si on est en production, MEDIA_ROOT est servi par Apache.
|
||||||
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.
|
# Il faut dire à Django de servir MEDIA_ROOT lui-même en développement.
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||||
|
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||||
|
|
||||||
|
|
||||||
# Wagtail URLs (wagtail.core urls must be last, as catch-all)
|
# Wagtail URLs (wagtail urls must be last, as catch-all)
|
||||||
if "wagtail.core" in settings.INSTALLED_APPS:
|
if "wagtail" in settings.INSTALLED_APPS:
|
||||||
|
from wagtail import urls as wagtail_urls
|
||||||
from wagtail.admin import urls as wagtailadmin_urls
|
from wagtail.admin import urls as wagtailadmin_urls
|
||||||
from wagtail.core import urls as wagtail_urls
|
|
||||||
from wagtail.documents import urls as wagtaildocs_urls
|
from wagtail.documents import urls as wagtaildocs_urls
|
||||||
|
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
default_app_config = "gestioncof.apps.GestioncofConfig"
|
|
|
@ -6,7 +6,7 @@ from django.contrib.auth.models import Group, Permission, User
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from gestioncof.models import (
|
from gestioncof.models import (
|
||||||
Club,
|
Club,
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
default_app_config = "gestioncof.cms.apps.COFCMSAppConfig"
|
|
|
@ -3,9 +3,9 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
import wagtail.blocks
|
||||||
import wagtail.contrib.routable_page.models
|
import wagtail.contrib.routable_page.models
|
||||||
import wagtail.core.blocks
|
import wagtail.fields
|
||||||
import wagtail.core.fields
|
|
||||||
import wagtail.images.blocks
|
import wagtail.images.blocks
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
@ -72,18 +72,14 @@ class Migration(migrations.Migration):
|
||||||
blank=True, null=True, verbose_name="Description rapide"
|
blank=True, null=True, verbose_name="Description rapide"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
("body", wagtail.core.fields.RichTextField(verbose_name="Contenu")),
|
("body", wagtail.fields.RichTextField(verbose_name="Contenu")),
|
||||||
(
|
(
|
||||||
"body_fr",
|
"body_fr",
|
||||||
wagtail.core.fields.RichTextField(
|
wagtail.fields.RichTextField(null=True, verbose_name="Contenu"),
|
||||||
null=True, verbose_name="Contenu"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"body_en",
|
"body_en",
|
||||||
wagtail.core.fields.RichTextField(
|
wagtail.fields.RichTextField(null=True, verbose_name="Contenu"),
|
||||||
null=True, verbose_name="Contenu"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"is_event",
|
"is_event",
|
||||||
|
@ -138,46 +134,40 @@ class Migration(migrations.Migration):
|
||||||
to="wagtailcore.Page",
|
to="wagtailcore.Page",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
("body", wagtail.core.fields.RichTextField(verbose_name="Description")),
|
("body", wagtail.fields.RichTextField(verbose_name="Description")),
|
||||||
(
|
(
|
||||||
"body_fr",
|
"body_fr",
|
||||||
wagtail.core.fields.RichTextField(
|
wagtail.fields.RichTextField(null=True, verbose_name="Description"),
|
||||||
null=True, verbose_name="Description"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"body_en",
|
"body_en",
|
||||||
wagtail.core.fields.RichTextField(
|
wagtail.fields.RichTextField(null=True, verbose_name="Description"),
|
||||||
null=True, verbose_name="Description"
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"links",
|
"links",
|
||||||
wagtail.core.fields.StreamField(
|
wagtail.fields.StreamField(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"lien",
|
"lien",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"url",
|
"url",
|
||||||
wagtail.core.blocks.URLBlock(required=True),
|
wagtail.blocks.URLBlock(required=True),
|
||||||
),
|
),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"contact",
|
"contact",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"email",
|
"email",
|
||||||
wagtail.core.blocks.EmailBlock(
|
wagtail.blocks.EmailBlock(required=True),
|
||||||
required=True
|
|
||||||
),
|
),
|
||||||
),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -186,31 +176,29 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"links_fr",
|
"links_fr",
|
||||||
wagtail.core.fields.StreamField(
|
wagtail.fields.StreamField(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"lien",
|
"lien",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"url",
|
"url",
|
||||||
wagtail.core.blocks.URLBlock(required=True),
|
wagtail.blocks.URLBlock(required=True),
|
||||||
),
|
),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"contact",
|
"contact",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"email",
|
"email",
|
||||||
wagtail.core.blocks.EmailBlock(
|
wagtail.blocks.EmailBlock(required=True),
|
||||||
required=True
|
|
||||||
),
|
),
|
||||||
),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -220,31 +208,29 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"links_en",
|
"links_en",
|
||||||
wagtail.core.fields.StreamField(
|
wagtail.fields.StreamField(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"lien",
|
"lien",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"url",
|
"url",
|
||||||
wagtail.core.blocks.URLBlock(required=True),
|
wagtail.blocks.URLBlock(required=True),
|
||||||
),
|
),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"contact",
|
"contact",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"email",
|
"email",
|
||||||
wagtail.core.blocks.EmailBlock(
|
wagtail.blocks.EmailBlock(required=True),
|
||||||
required=True
|
|
||||||
),
|
),
|
||||||
),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -286,17 +272,17 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"introduction",
|
"introduction",
|
||||||
wagtail.core.fields.RichTextField(verbose_name="Introduction"),
|
wagtail.fields.RichTextField(verbose_name="Introduction"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"introduction_fr",
|
"introduction_fr",
|
||||||
wagtail.core.fields.RichTextField(
|
wagtail.fields.RichTextField(
|
||||||
null=True, verbose_name="Introduction"
|
null=True, verbose_name="Introduction"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"introduction_en",
|
"introduction_en",
|
||||||
wagtail.core.fields.RichTextField(
|
wagtail.fields.RichTextField(
|
||||||
null=True, verbose_name="Introduction"
|
null=True, verbose_name="Introduction"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -329,27 +315,27 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"body",
|
"body",
|
||||||
wagtail.core.fields.StreamField(
|
wagtail.fields.StreamField(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"heading",
|
"heading",
|
||||||
wagtail.core.blocks.CharBlock(classname="full title"),
|
wagtail.blocks.CharBlock(classname="full title"),
|
||||||
),
|
),
|
||||||
("paragraph", wagtail.core.blocks.RichTextBlock()),
|
("paragraph", wagtail.blocks.RichTextBlock()),
|
||||||
("image", wagtail.images.blocks.ImageChooserBlock()),
|
("image", wagtail.images.blocks.ImageChooserBlock()),
|
||||||
(
|
(
|
||||||
"iframe",
|
"iframe",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"url",
|
"url",
|
||||||
wagtail.core.blocks.URLBlock(
|
wagtail.blocks.URLBlock(
|
||||||
"Adresse de la page"
|
"Adresse de la page"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"height",
|
"height",
|
||||||
wagtail.core.blocks.CharBlock(
|
wagtail.blocks.CharBlock(
|
||||||
"Hauteur (en pixels)"
|
"Hauteur (en pixels)"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -361,27 +347,27 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"body_fr",
|
"body_fr",
|
||||||
wagtail.core.fields.StreamField(
|
wagtail.fields.StreamField(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"heading",
|
"heading",
|
||||||
wagtail.core.blocks.CharBlock(classname="full title"),
|
wagtail.blocks.CharBlock(classname="full title"),
|
||||||
),
|
),
|
||||||
("paragraph", wagtail.core.blocks.RichTextBlock()),
|
("paragraph", wagtail.blocks.RichTextBlock()),
|
||||||
("image", wagtail.images.blocks.ImageChooserBlock()),
|
("image", wagtail.images.blocks.ImageChooserBlock()),
|
||||||
(
|
(
|
||||||
"iframe",
|
"iframe",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"url",
|
"url",
|
||||||
wagtail.core.blocks.URLBlock(
|
wagtail.blocks.URLBlock(
|
||||||
"Adresse de la page"
|
"Adresse de la page"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"height",
|
"height",
|
||||||
wagtail.core.blocks.CharBlock(
|
wagtail.blocks.CharBlock(
|
||||||
"Hauteur (en pixels)"
|
"Hauteur (en pixels)"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -394,27 +380,27 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"body_en",
|
"body_en",
|
||||||
wagtail.core.fields.StreamField(
|
wagtail.fields.StreamField(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"heading",
|
"heading",
|
||||||
wagtail.core.blocks.CharBlock(classname="full title"),
|
wagtail.blocks.CharBlock(classname="full title"),
|
||||||
),
|
),
|
||||||
("paragraph", wagtail.core.blocks.RichTextBlock()),
|
("paragraph", wagtail.blocks.RichTextBlock()),
|
||||||
("image", wagtail.images.blocks.ImageChooserBlock()),
|
("image", wagtail.images.blocks.ImageChooserBlock()),
|
||||||
(
|
(
|
||||||
"iframe",
|
"iframe",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"url",
|
"url",
|
||||||
wagtail.core.blocks.URLBlock(
|
wagtail.blocks.URLBlock(
|
||||||
"Adresse de la page"
|
"Adresse de la page"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"height",
|
"height",
|
||||||
wagtail.core.blocks.CharBlock(
|
wagtail.blocks.CharBlock(
|
||||||
"Hauteur (en pixels)"
|
"Hauteur (en pixels)"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -448,17 +434,17 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"introduction",
|
"introduction",
|
||||||
wagtail.core.fields.RichTextField(verbose_name="Introduction"),
|
wagtail.fields.RichTextField(verbose_name="Introduction"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"introduction_fr",
|
"introduction_fr",
|
||||||
wagtail.core.fields.RichTextField(
|
wagtail.fields.RichTextField(
|
||||||
null=True, verbose_name="Introduction"
|
null=True, verbose_name="Introduction"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"introduction_en",
|
"introduction_en",
|
||||||
wagtail.core.fields.RichTextField(
|
wagtail.fields.RichTextField(
|
||||||
null=True, verbose_name="Introduction"
|
null=True, verbose_name="Introduction"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Generated by Django 2.2.8 on 2019-12-20 16:22
|
# Generated by Django 2.2.8 on 2019-12-20 16:22
|
||||||
|
|
||||||
import wagtail.core.blocks
|
import wagtail.blocks
|
||||||
import wagtail.core.fields
|
import wagtail.fields
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,26 +14,26 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="cofdirectoryentrypage",
|
model_name="cofdirectoryentrypage",
|
||||||
name="links",
|
name="links",
|
||||||
field=wagtail.core.fields.StreamField(
|
field=wagtail.fields.StreamField(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"lien",
|
"lien",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
("url", wagtail.core.blocks.URLBlock(required=True)),
|
("url", wagtail.blocks.URLBlock(required=True)),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"contact",
|
"contact",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"email",
|
"email",
|
||||||
wagtail.core.blocks.EmailBlock(required=True),
|
wagtail.blocks.EmailBlock(required=True),
|
||||||
),
|
),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -44,26 +44,26 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="cofdirectoryentrypage",
|
model_name="cofdirectoryentrypage",
|
||||||
name="links_en",
|
name="links_en",
|
||||||
field=wagtail.core.fields.StreamField(
|
field=wagtail.fields.StreamField(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"lien",
|
"lien",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
("url", wagtail.core.blocks.URLBlock(required=True)),
|
("url", wagtail.blocks.URLBlock(required=True)),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"contact",
|
"contact",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"email",
|
"email",
|
||||||
wagtail.core.blocks.EmailBlock(required=True),
|
wagtail.blocks.EmailBlock(required=True),
|
||||||
),
|
),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -75,26 +75,26 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="cofdirectoryentrypage",
|
model_name="cofdirectoryentrypage",
|
||||||
name="links_fr",
|
name="links_fr",
|
||||||
field=wagtail.core.fields.StreamField(
|
field=wagtail.fields.StreamField(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"lien",
|
"lien",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
("url", wagtail.core.blocks.URLBlock(required=True)),
|
("url", wagtail.blocks.URLBlock(required=True)),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"contact",
|
"contact",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"email",
|
"email",
|
||||||
wagtail.core.blocks.EmailBlock(required=True),
|
wagtail.blocks.EmailBlock(required=True),
|
||||||
),
|
),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Generated by Django 2.2.15 on 2020-08-29 21:14
|
# Generated by Django 2.2.15 on 2020-08-29 21:14
|
||||||
|
|
||||||
import wagtail.core.blocks
|
import wagtail.blocks
|
||||||
import wagtail.core.fields
|
import wagtail.fields
|
||||||
from django.db import migrations
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,35 +14,35 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="cofdirectoryentrypage",
|
model_name="cofdirectoryentrypage",
|
||||||
name="links",
|
name="links",
|
||||||
field=wagtail.core.fields.StreamField(
|
field=wagtail.fields.StreamField(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"lien",
|
"lien",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
("url", wagtail.core.blocks.URLBlock(required=True)),
|
("url", wagtail.blocks.URLBlock(required=True)),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"contact",
|
"contact",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"email",
|
"email",
|
||||||
wagtail.core.blocks.EmailBlock(required=True),
|
wagtail.blocks.EmailBlock(required=True),
|
||||||
),
|
),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"info",
|
"info",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
("nom", wagtail.core.blocks.CharBlock(required=False)),
|
("nom", wagtail.blocks.CharBlock(required=False)),
|
||||||
("texte", wagtail.core.blocks.CharBlock(required=True)),
|
("texte", wagtail.blocks.CharBlock(required=True)),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -53,35 +53,35 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="cofdirectoryentrypage",
|
model_name="cofdirectoryentrypage",
|
||||||
name="links_en",
|
name="links_en",
|
||||||
field=wagtail.core.fields.StreamField(
|
field=wagtail.fields.StreamField(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"lien",
|
"lien",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
("url", wagtail.core.blocks.URLBlock(required=True)),
|
("url", wagtail.blocks.URLBlock(required=True)),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"contact",
|
"contact",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"email",
|
"email",
|
||||||
wagtail.core.blocks.EmailBlock(required=True),
|
wagtail.blocks.EmailBlock(required=True),
|
||||||
),
|
),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"info",
|
"info",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
("nom", wagtail.core.blocks.CharBlock(required=False)),
|
("nom", wagtail.blocks.CharBlock(required=False)),
|
||||||
("texte", wagtail.core.blocks.CharBlock(required=True)),
|
("texte", wagtail.blocks.CharBlock(required=True)),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -93,35 +93,35 @@ class Migration(migrations.Migration):
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="cofdirectoryentrypage",
|
model_name="cofdirectoryentrypage",
|
||||||
name="links_fr",
|
name="links_fr",
|
||||||
field=wagtail.core.fields.StreamField(
|
field=wagtail.fields.StreamField(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"lien",
|
"lien",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
("url", wagtail.core.blocks.URLBlock(required=True)),
|
("url", wagtail.blocks.URLBlock(required=True)),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"contact",
|
"contact",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
"email",
|
"email",
|
||||||
wagtail.core.blocks.EmailBlock(required=True),
|
wagtail.blocks.EmailBlock(required=True),
|
||||||
),
|
),
|
||||||
("texte", wagtail.core.blocks.CharBlock()),
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"info",
|
"info",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
[
|
[
|
||||||
("nom", wagtail.core.blocks.CharBlock(required=False)),
|
("nom", wagtail.blocks.CharBlock(required=False)),
|
||||||
("texte", wagtail.core.blocks.CharBlock(required=True)),
|
("texte", wagtail.blocks.CharBlock(required=True)),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
# Generated by Django 4.2.17 on 2024-12-19 12:27
|
||||||
|
|
||||||
|
import wagtail.blocks
|
||||||
|
import wagtail.fields
|
||||||
|
import wagtail.images.blocks
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("cofcms", "0004_auto_20200829_2314"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="cofdirectoryentrypage",
|
||||||
|
name="links",
|
||||||
|
field=wagtail.fields.StreamField(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"lien",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("url", wagtail.blocks.URLBlock(required=True)),
|
||||||
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"contact",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("email", wagtail.blocks.EmailBlock(required=True)),
|
||||||
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"info",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("nom", wagtail.blocks.CharBlock(required=False)),
|
||||||
|
("texte", wagtail.blocks.CharBlock(required=True)),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
blank=True,
|
||||||
|
use_json_field=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="cofdirectoryentrypage",
|
||||||
|
name="links_en",
|
||||||
|
field=wagtail.fields.StreamField(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"lien",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("url", wagtail.blocks.URLBlock(required=True)),
|
||||||
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"contact",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("email", wagtail.blocks.EmailBlock(required=True)),
|
||||||
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"info",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("nom", wagtail.blocks.CharBlock(required=False)),
|
||||||
|
("texte", wagtail.blocks.CharBlock(required=True)),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
use_json_field=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="cofdirectoryentrypage",
|
||||||
|
name="links_fr",
|
||||||
|
field=wagtail.fields.StreamField(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"lien",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("url", wagtail.blocks.URLBlock(required=True)),
|
||||||
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"contact",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("email", wagtail.blocks.EmailBlock(required=True)),
|
||||||
|
("texte", wagtail.blocks.CharBlock()),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"info",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("nom", wagtail.blocks.CharBlock(required=False)),
|
||||||
|
("texte", wagtail.blocks.CharBlock(required=True)),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
use_json_field=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="cofpage",
|
||||||
|
name="body",
|
||||||
|
field=wagtail.fields.StreamField(
|
||||||
|
[
|
||||||
|
("heading", wagtail.blocks.CharBlock(form_classname="full title")),
|
||||||
|
("paragraph", wagtail.blocks.RichTextBlock()),
|
||||||
|
("image", wagtail.images.blocks.ImageChooserBlock()),
|
||||||
|
(
|
||||||
|
"iframe",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("url", wagtail.blocks.URLBlock("Adresse de la page")),
|
||||||
|
(
|
||||||
|
"height",
|
||||||
|
wagtail.blocks.CharBlock("Hauteur (en pixels)"),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
use_json_field=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="cofpage",
|
||||||
|
name="body_en",
|
||||||
|
field=wagtail.fields.StreamField(
|
||||||
|
[
|
||||||
|
("heading", wagtail.blocks.CharBlock(form_classname="full title")),
|
||||||
|
("paragraph", wagtail.blocks.RichTextBlock()),
|
||||||
|
("image", wagtail.images.blocks.ImageChooserBlock()),
|
||||||
|
(
|
||||||
|
"iframe",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("url", wagtail.blocks.URLBlock("Adresse de la page")),
|
||||||
|
(
|
||||||
|
"height",
|
||||||
|
wagtail.blocks.CharBlock("Hauteur (en pixels)"),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
null=True,
|
||||||
|
use_json_field=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="cofpage",
|
||||||
|
name="body_fr",
|
||||||
|
field=wagtail.fields.StreamField(
|
||||||
|
[
|
||||||
|
("heading", wagtail.blocks.CharBlock(form_classname="full title")),
|
||||||
|
("paragraph", wagtail.blocks.RichTextBlock()),
|
||||||
|
("image", wagtail.images.blocks.ImageChooserBlock()),
|
||||||
|
(
|
||||||
|
"iframe",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("url", wagtail.blocks.URLBlock("Adresse de la page")),
|
||||||
|
(
|
||||||
|
"height",
|
||||||
|
wagtail.blocks.CharBlock("Hauteur (en pixels)"),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
null=True,
|
||||||
|
use_json_field=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,12 +1,11 @@
|
||||||
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
|
from wagtail import blocks
|
||||||
|
from wagtail.admin.panels import FieldPanel
|
||||||
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
|
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
|
||||||
from wagtail.core import blocks
|
from wagtail.fields import RichTextField, StreamField
|
||||||
from wagtail.core.fields import RichTextField, StreamField
|
|
||||||
from wagtail.core.models import Page
|
|
||||||
from wagtail.images.blocks import ImageChooserBlock
|
from wagtail.images.blocks import ImageChooserBlock
|
||||||
from wagtail.images.edit_handlers import ImageChooserPanel
|
from wagtail.models import Page
|
||||||
|
|
||||||
|
|
||||||
# Page pouvant afficher des actualités
|
# Page pouvant afficher des actualités
|
||||||
|
@ -69,10 +68,11 @@ class COFPage(Page):
|
||||||
("paragraph", blocks.RichTextBlock()),
|
("paragraph", blocks.RichTextBlock()),
|
||||||
("image", ImageChooserBlock()),
|
("image", ImageChooserBlock()),
|
||||||
("iframe", IFrameBlock()),
|
("iframe", IFrameBlock()),
|
||||||
]
|
],
|
||||||
|
use_json_field=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
content_panels = Page.content_panels + [StreamFieldPanel("body")]
|
content_panels = Page.content_panels + [FieldPanel("body")]
|
||||||
|
|
||||||
subpage_types = ["COFDirectoryPage", "COFPage"]
|
subpage_types = ["COFDirectoryPage", "COFPage"]
|
||||||
parent_page_types = ["COFPage", "COFRootPage"]
|
parent_page_types = ["COFPage", "COFRootPage"]
|
||||||
|
@ -127,7 +127,7 @@ class COFActuPage(RoutablePageMixin, Page):
|
||||||
all_day = models.BooleanField("Toute la journée", default=False, blank=True)
|
all_day = models.BooleanField("Toute la journée", default=False, blank=True)
|
||||||
|
|
||||||
content_panels = Page.content_panels + [
|
content_panels = Page.content_panels + [
|
||||||
ImageChooserPanel("image"),
|
FieldPanel("image"),
|
||||||
FieldPanel("chapo"),
|
FieldPanel("chapo"),
|
||||||
FieldPanel("body", classname="full"),
|
FieldPanel("body", classname="full"),
|
||||||
FieldPanel("is_event"),
|
FieldPanel("is_event"),
|
||||||
|
@ -204,6 +204,7 @@ class COFDirectoryEntryPage(Page):
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
blank=True,
|
blank=True,
|
||||||
|
use_json_field=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
image = models.ForeignKey(
|
image = models.ForeignKey(
|
||||||
|
@ -216,9 +217,9 @@ class COFDirectoryEntryPage(Page):
|
||||||
)
|
)
|
||||||
|
|
||||||
content_panels = Page.content_panels + [
|
content_panels = Page.content_panels + [
|
||||||
ImageChooserPanel("image"),
|
FieldPanel("image"),
|
||||||
FieldPanel("body", classname="full"),
|
FieldPanel("body", classname="full"),
|
||||||
StreamFieldPanel("links"),
|
FieldPanel("links"),
|
||||||
]
|
]
|
||||||
|
|
||||||
subpage_types = []
|
subpage_types = []
|
||||||
|
|
|
@ -22,10 +22,10 @@
|
||||||
|
|
||||||
<section class="actulist">
|
<section class="actulist">
|
||||||
{% if actus.has_previous %}
|
{% if actus.has_previous %}
|
||||||
<a class="block prev-actus" href="?page={{ actus.previous_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&{{ key }}={{ value }}{% endifnotequal %}{% endfor %}">{% trans "Actualités plus récentes" %}</a>
|
<a class="block prev-actus" href="?page={{ actus.previous_page_number }}{% for key,value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">{% trans "Actualités plus récentes" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if actus.has_next %}
|
{% if actus.has_next %}
|
||||||
<a class="block next-actus" href="?page={{ actus.next_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&{{ key }}={{ value }}{% endifnotequal %}{% endfor %}">{% trans "Actualités plus anciennes" %}</a>
|
<a class="block next-actus" href="?page={{ actus.next_page_number }}{% for key,value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">{% trans "Actualités plus anciennes" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% for actu in page.actus %}
|
{% for actu in page.actus %}
|
||||||
|
@ -44,10 +44,10 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if actus.has_previous %}
|
{% if actus.has_previous %}
|
||||||
<a class="block prev-actus" href="?page={{ actus.previous_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&{{ key }}={{ value }}{% endifnotequal %}{% endfor %}">{% trans "Actualités plus récentes" %}</a>
|
<a class="block prev-actus" href="?page={{ actus.previous_page_number }}{% for key,value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">{% trans "Actualités plus récentes" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if actus.has_next %}
|
{% if actus.has_next %}
|
||||||
<a class="block next-actus" href="?page={{ actus.next_page_number }}{% for key,value in request.GET.items %}{% ifnotequal key 'page' %}&{{ key }}={{ value }}{% endifnotequal %}{% endfor %}">{% trans "Actualités plus anciennes" %}</a>
|
<a class="block next-actus" href="?page={{ actus.next_page_number }}{% for key,value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">{% trans "Actualités plus anciennes" %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</section>
|
</section>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -2,7 +2,7 @@ from datetime import date, timedelta
|
||||||
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.utils import formats, timezone
|
from django.utils import formats, timezone
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from ..models import COFActuPage, COFRootPage
|
from ..models import COFActuPage, COFRootPage
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from django.views.decorators.clickjacking import xframe_options_sameorigin
|
||||||
|
|
||||||
from gestioncof.cms.forms import CaptchaForm
|
from gestioncof.cms.forms import CaptchaForm
|
||||||
|
|
||||||
|
|
||||||
|
@xframe_options_sameorigin
|
||||||
def raw_calendar_view(request, year, month):
|
def raw_calendar_view(request, year, month):
|
||||||
return render(request, "cofcms/calendar_raw.html", {"month": month, "year": year})
|
return render(request, "cofcms/calendar_raw.html", {"month": month, "year": year})
|
||||||
|
|
||||||
|
|
||||||
|
@xframe_options_sameorigin
|
||||||
def sympa_captcha_form_view(request):
|
def sympa_captcha_form_view(request):
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = CaptchaForm(request.POST)
|
form = CaptchaForm(request.POST)
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
from django.forms.formsets import BaseFormSet, formset_factory
|
from django.forms.formsets import BaseFormSet, formset_factory
|
||||||
from django.forms.widgets import CheckboxSelectMultiple, RadioSelect
|
from django.forms.widgets import CheckboxSelectMultiple, RadioSelect
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from djconfig.forms import ConfigForm
|
from djconfig.forms import ConfigForm
|
||||||
|
|
||||||
from bda.models import Spectacle
|
from bda.models import Spectacle
|
||||||
|
@ -276,7 +276,9 @@ class RegistrationProfileForm(forms.ModelForm):
|
||||||
self.fields["mailing_bda_revente"].initial = True
|
self.fields["mailing_bda_revente"].initial = True
|
||||||
self.fields["mailing_unernestaparis"].initial = True
|
self.fields["mailing_unernestaparis"].initial = True
|
||||||
|
|
||||||
self.fields.keyOrder = [
|
class Meta:
|
||||||
|
model = CofProfile
|
||||||
|
fields = [
|
||||||
"login_clipper",
|
"login_clipper",
|
||||||
"phone",
|
"phone",
|
||||||
"occupation",
|
"occupation",
|
||||||
|
@ -290,22 +292,6 @@ class RegistrationProfileForm(forms.ModelForm):
|
||||||
"comments",
|
"comments",
|
||||||
]
|
]
|
||||||
|
|
||||||
class Meta:
|
|
||||||
model = CofProfile
|
|
||||||
fields = (
|
|
||||||
"login_clipper",
|
|
||||||
"phone",
|
|
||||||
"occupation",
|
|
||||||
"departement",
|
|
||||||
"is_cof",
|
|
||||||
"type_cotiz",
|
|
||||||
"mailing_cof",
|
|
||||||
"mailing_bda",
|
|
||||||
"mailing_bda_revente",
|
|
||||||
"mailing_unernestaparis",
|
|
||||||
"comments",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
STATUS_CHOICES = (
|
STATUS_CHOICES = (
|
||||||
("no", "Non"),
|
("no", "Non"),
|
||||||
|
|
43
gestioncof/migrations/0019_auto_20220630_1241.py
Normal file
43
gestioncof/migrations/0019_auto_20220630_1241.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
# Generated by Django 3.2.13 on 2022-06-30 10:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("gestioncof", "0018_petitscours_email"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="eventregistration",
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="petitcoursability",
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name="surveyanswer",
|
||||||
|
unique_together=set(),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="eventregistration",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("user", "event"), name="unique_event_registration"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="petitcoursability",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("user", "niveau", "matiere"), name="unique_competence_level"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="surveyanswer",
|
||||||
|
constraint=models.UniqueConstraint(
|
||||||
|
fields=("user", "survey"), name="unique_survey_answer"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
13
gestioncof/migrations/0020_merge_20241218_2240.py
Normal file
13
gestioncof/migrations/0020_merge_20241218_2240.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Generated by Django 3.2.25 on 2024-12-18 21:40
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("gestioncof", "0019_auto_20220630_1241"),
|
||||||
|
("gestioncof", "0019_cofprofile_date_adhesion"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = []
|
|
@ -2,7 +2,7 @@ from django.contrib.auth.models import User
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.signals import post_delete, post_save
|
from django.db.models.signals import post_delete, post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from bda.models import Spectacle
|
from bda.models import Spectacle
|
||||||
from shared.utils import choices_length
|
from shared.utils import choices_length
|
||||||
|
@ -194,8 +194,12 @@ class EventRegistration(models.Model):
|
||||||
paid = models.BooleanField("A payé", default=False)
|
paid = models.BooleanField("A payé", default=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["user", "event"], name="unique_event_registration"
|
||||||
|
)
|
||||||
|
]
|
||||||
verbose_name = "Inscription"
|
verbose_name = "Inscription"
|
||||||
unique_together = ("user", "event")
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Inscription de {} à {}".format(self.user, self.event.title)
|
return "Inscription de {} à {}".format(self.user, self.event.title)
|
||||||
|
@ -247,8 +251,12 @@ class SurveyAnswer(models.Model):
|
||||||
answers = models.ManyToManyField(SurveyQuestionAnswer, related_name="selected_by")
|
answers = models.ManyToManyField(SurveyQuestionAnswer, related_name="selected_by")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["user", "survey"], name="unique_survey_answer"
|
||||||
|
)
|
||||||
|
]
|
||||||
verbose_name = "Réponses"
|
verbose_name = "Réponses"
|
||||||
unique_together = ("user", "survey")
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Réponse de %s sondage %s" % (
|
return "Réponse de %s sondage %s" % (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth.signals import user_logged_in
|
from django.contrib.auth.signals import user_logged_in
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_cas_ng.signals import cas_user_authenticated
|
from django_cas_ng.signals import cas_user_authenticated
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="fr">
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="fr">
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "base_title.html" %}
|
{% extends "base_title.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
{% block page_size %}col-sm-8{% endblock %}
|
{% block page_size %}col-sm-8{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var supernifty_tristate = function() {
|
var supernifty_tristate = function() {
|
||||||
var
|
var
|
||||||
|
|
|
@ -194,7 +194,9 @@ class RegistrationViewTests(ViewTestCaseMixin, TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
er = e.eventregistration_set.get(user=self.users["user"])
|
er = e.eventregistration_set.get(user=self.users["user"])
|
||||||
self.assertQuerysetEqual(er.options.all(), map(repr, [oc1, oc3]), ordered=False)
|
self.assertQuerysetEqual(
|
||||||
|
er.options.all(), map(repr, [oc1, oc3]), transform=repr, ordered=False
|
||||||
|
)
|
||||||
self.assertCountEqual(
|
self.assertCountEqual(
|
||||||
er.comments.values_list("content", flat=True), ["comment 1"]
|
er.comments.values_list("content", flat=True), ["comment 1"]
|
||||||
)
|
)
|
||||||
|
@ -299,10 +301,10 @@ class RegistrationAutocompleteViewTests(MockLDAPMixin, ViewTestCaseMixin, TestCa
|
||||||
raise ValueError("Unexpected section name: {}".format(section.name))
|
raise ValueError("Unexpected section name: {}".format(section.name))
|
||||||
|
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
others, map(str, expected_others), ordered=False, transform=str
|
others, map(str, expected_others), transform=str, ordered=False
|
||||||
)
|
)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
members, map(str, expected_members), ordered=False, transform=str
|
members, map(str, expected_members), transform=str, ordered=False
|
||||||
)
|
)
|
||||||
self.assertSetEqual(
|
self.assertSetEqual(
|
||||||
set(clippers), set(map(LDAPSearch().result_verbose_name, expected_clippers))
|
set(clippers), set(map(LDAPSearch().result_verbose_name, expected_clippers))
|
||||||
|
@ -648,7 +650,10 @@ class ClubListViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
r.context["owned_clubs"], map(repr, [self.c1, self.c2]), ordered=False
|
r.context["owned_clubs"],
|
||||||
|
map(repr, [self.c1, self.c2]),
|
||||||
|
transform=repr,
|
||||||
|
ordered=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -950,7 +955,10 @@ class EventViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
er = self.e.eventregistration_set.get(user=self.users["user"])
|
er = self.e.eventregistration_set.get(user=self.users["user"])
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
er.options.all(), map(repr, [self.oc1, self.oc3, self.oc4]), ordered=False
|
er.options.all(),
|
||||||
|
map(repr, [self.oc1, self.oc3, self.oc4]),
|
||||||
|
transform=repr,
|
||||||
|
ordered=False,
|
||||||
)
|
)
|
||||||
# TODO: Make the view care about comments.
|
# TODO: Make the view care about comments.
|
||||||
# self.assertQuerysetEqual(
|
# self.assertQuerysetEqual(
|
||||||
|
@ -975,7 +983,9 @@ class EventViewTests(ViewTestCaseMixin, TestCase):
|
||||||
self.assertIn(self.post_expected_message, get_messages(r.wsgi_request))
|
self.assertIn(self.post_expected_message, get_messages(r.wsgi_request))
|
||||||
|
|
||||||
er.refresh_from_db()
|
er.refresh_from_db()
|
||||||
self.assertQuerysetEqual(er.options.all(), map(repr, [self.oc3]), ordered=False)
|
self.assertQuerysetEqual(
|
||||||
|
er.options.all(), map(repr, [self.oc3]), transform=repr, ordered=False
|
||||||
|
)
|
||||||
# TODO: Make the view care about comments.
|
# TODO: Make the view care about comments.
|
||||||
# self.assertQuerysetEqual(
|
# self.assertQuerysetEqual(
|
||||||
# er.comments.all(), map(repr, []),
|
# er.comments.all(), map(repr, []),
|
||||||
|
@ -1029,7 +1039,10 @@ class EventStatusViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
r.context["user_choices"], map(repr, expected), ordered=False
|
r.context["user_choices"],
|
||||||
|
map(repr, expected),
|
||||||
|
transform=repr,
|
||||||
|
ordered=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_filter_none(self):
|
def test_filter_none(self):
|
||||||
|
@ -1096,7 +1109,10 @@ class SurveyViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
a = self.s.surveyanswer_set.get(user=self.users["user"])
|
a = self.s.surveyanswer_set.get(user=self.users["user"])
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
a.answers.all(), map(repr, [self.qa1, self.qa3, self.qa4]), ordered=False
|
a.answers.all(),
|
||||||
|
map(repr, [self.qa1, self.qa3, self.qa4]),
|
||||||
|
transform=repr,
|
||||||
|
ordered=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_post_edit(self):
|
def test_post_edit(self):
|
||||||
|
@ -1115,7 +1131,9 @@ class SurveyViewTests(ViewTestCaseMixin, TestCase):
|
||||||
self.assertIn(self.post_expected_message, get_messages(r.wsgi_request))
|
self.assertIn(self.post_expected_message, get_messages(r.wsgi_request))
|
||||||
|
|
||||||
a.refresh_from_db()
|
a.refresh_from_db()
|
||||||
self.assertQuerysetEqual(a.answers.all(), map(repr, [self.qa3]), ordered=False)
|
self.assertQuerysetEqual(
|
||||||
|
a.answers.all(), map(repr, [self.qa3]), transform=repr, ordered=False
|
||||||
|
)
|
||||||
|
|
||||||
def test_post_delete(self):
|
def test_post_delete(self):
|
||||||
a = self.s.surveyanswer_set.create(user=self.users["user"])
|
a = self.s.surveyanswer_set.create(user=self.users["user"])
|
||||||
|
@ -1196,7 +1214,10 @@ class SurveyStatusViewTests(ViewTestCaseMixin, TestCase):
|
||||||
|
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
r.context["user_answers"], map(repr, expected), ordered=False
|
r.context["user_answers"],
|
||||||
|
map(repr, expected),
|
||||||
|
transform=repr,
|
||||||
|
ordered=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_filter_none(self):
|
def test_filter_none(self):
|
||||||
|
|
|
@ -4,6 +4,16 @@ from django.views.generic.base import TemplateView
|
||||||
from django_cas_ng import views as django_cas_views
|
from django_cas_ng import views as django_cas_views
|
||||||
|
|
||||||
from gestioncof import csv_views, views
|
from gestioncof import csv_views, views
|
||||||
|
from shared.views import SympaListView
|
||||||
|
|
||||||
|
sympa_patterns = [
|
||||||
|
path(
|
||||||
|
f"{mailing}/",
|
||||||
|
SympaListView.as_view(filters={f"profile__mailing_{mailing}": True}),
|
||||||
|
name=f"sympa.{mailing}",
|
||||||
|
)
|
||||||
|
for mailing in ["bda", "bda_revente", "cof", "unernestaparis"]
|
||||||
|
]
|
||||||
|
|
||||||
export_patterns = [
|
export_patterns = [
|
||||||
path("members", views.export_members, name="export.members"),
|
path("members", views.export_members, name="export.members"),
|
||||||
|
@ -162,4 +172,8 @@ urlpatterns = [
|
||||||
# Clubs
|
# Clubs
|
||||||
# -----
|
# -----
|
||||||
path("clubs/", include(clubs_patterns)),
|
path("clubs/", include(clubs_patterns)),
|
||||||
|
# -----
|
||||||
|
# Sympa export
|
||||||
|
# -----
|
||||||
|
path("sympa/", include(sympa_patterns)),
|
||||||
]
|
]
|
||||||
|
|
|
@ -20,7 +20,7 @@ from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import FormView, TemplateView
|
from django.views.generic import FormView, TemplateView
|
||||||
from django_cas_ng.views import LogoutView as CasLogoutView
|
from django_cas_ng.views import LogoutView as CasLogoutView
|
||||||
from icalendar import Calendar, Event as Vevent
|
from icalendar import Calendar, Event as Vevent
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
default_app_config = "kfet.apps.KFetConfig"
|
|
||||||
KFET_DELETED_TRIGRAMME = "☠☠☠"
|
KFET_DELETED_TRIGRAMME = "☠☠☠"
|
||||||
KFET_DELETED_USERNAME = "kfet_deleted_user"
|
KFET_DELETED_USERNAME = "kfet_deleted_user"
|
||||||
|
|
|
@ -1,4 +1,2 @@
|
||||||
default_app_config = "kfet.auth.apps.KFetAuthConfig"
|
|
||||||
|
|
||||||
KFET_GENERIC_USERNAME = "kfet_genericteam"
|
KFET_GENERIC_USERNAME = "kfet_genericteam"
|
||||||
KFET_GENERIC_TRIGRAMME = "GNR"
|
KFET_GENERIC_TRIGRAMME = "GNR"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.db.models.signals import post_migrate
|
from django.db.models.signals import post_migrate
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class KFetAuthConfig(AppConfig):
|
class KFetAuthConfig(AppConfig):
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from shared.forms import ProtectedModelForm
|
from shared.forms import ProtectedModelForm
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.contrib.auth.models import Group, Permission
|
from django.contrib.auth.models import Group, Permission
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
KFET_APP_LABELS = ["kfet", "kfetauth"]
|
KFET_APP_LABELS = ["kfet", "kfetauth"]
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django.contrib.auth.signals import user_logged_in
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .utils import get_kfet_generic_user
|
from .utils import get_kfet_generic_user
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ class UserGroupFormTests(TestCase):
|
||||||
self.assertQuerysetEqual(
|
self.assertQuerysetEqual(
|
||||||
groups_field.queryset,
|
groups_field.queryset,
|
||||||
[repr(g.group_ptr) for g in self.kfet_groups],
|
[repr(g.group_ptr) for g in self.kfet_groups],
|
||||||
|
transform=repr,
|
||||||
ordered=False,
|
ordered=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from django.http import QueryDict
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from django.views.generic.edit import CreateView, UpdateView
|
from django.views.generic.edit import CreateView, UpdateView
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
default_app_config = "kfet.cms.apps.KFetCMSAppConfig"
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django.contrib.staticfiles.templatetags.staticfiles import static
|
from django.templatetags.static import static
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from wagtail.core import hooks
|
from wagtail import hooks
|
||||||
|
|
||||||
|
|
||||||
@hooks.register("insert_editor_css")
|
@hooks.register("insert_editor_css")
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from wagtail.core.models import Page, Site
|
from wagtail.models import Page, Site
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import wagtail.core.blocks
|
import wagtail.blocks
|
||||||
import wagtail.core.fields
|
import wagtail.fields
|
||||||
import wagtail.snippets.blocks
|
import wagtail.snippets.blocks
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
@ -41,20 +41,20 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"content",
|
"content",
|
||||||
wagtail.core.fields.StreamField(
|
wagtail.fields.StreamField(
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
"rich",
|
"rich",
|
||||||
wagtail.core.blocks.RichTextBlock(label="Éditeur"),
|
wagtail.blocks.RichTextBlock(label="Éditeur"),
|
||||||
),
|
),
|
||||||
("carte", kfet.cms.models.MenuBlock()),
|
("carte", kfet.cms.models.MenuBlock()),
|
||||||
(
|
(
|
||||||
"group_team",
|
"group_team",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
"show_only",
|
"show_only",
|
||||||
wagtail.core.blocks.IntegerBlock(
|
wagtail.blocks.IntegerBlock(
|
||||||
help_text="Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.", # noqa
|
help_text="Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.", # noqa
|
||||||
required=False,
|
required=False,
|
||||||
label="Montrer seulement",
|
label="Montrer seulement",
|
||||||
|
@ -62,7 +62,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"members",
|
"members",
|
||||||
wagtail.core.blocks.ListBlock(
|
wagtail.blocks.ListBlock(
|
||||||
wagtail.snippets.blocks.SnippetChooserBlock( # noqa
|
wagtail.snippets.blocks.SnippetChooserBlock( # noqa
|
||||||
kfet.cms.models.MemberTeam
|
kfet.cms.models.MemberTeam
|
||||||
),
|
),
|
||||||
|
@ -75,22 +75,22 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"group",
|
"group",
|
||||||
wagtail.core.blocks.StreamBlock(
|
wagtail.blocks.StreamBlock(
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
"rich",
|
"rich",
|
||||||
wagtail.core.blocks.RichTextBlock(
|
wagtail.blocks.RichTextBlock(
|
||||||
label="Éditeur"
|
label="Éditeur"
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
("carte", kfet.cms.models.MenuBlock()),
|
("carte", kfet.cms.models.MenuBlock()),
|
||||||
(
|
(
|
||||||
"group_team",
|
"group_team",
|
||||||
wagtail.core.blocks.StructBlock(
|
wagtail.blocks.StructBlock(
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
"show_only",
|
"show_only",
|
||||||
wagtail.core.blocks.IntegerBlock( # noqa
|
wagtail.blocks.IntegerBlock( # noqa
|
||||||
help_text="Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.", # noqa
|
help_text="Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.", # noqa
|
||||||
required=False,
|
required=False,
|
||||||
label="Montrer seulement",
|
label="Montrer seulement",
|
||||||
|
@ -98,7 +98,7 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"members",
|
"members",
|
||||||
wagtail.core.blocks.ListBlock(
|
wagtail.blocks.ListBlock(
|
||||||
wagtail.snippets.blocks.SnippetChooserBlock( # noqa
|
wagtail.snippets.blocks.SnippetChooserBlock( # noqa
|
||||||
kfet.cms.models.MemberTeam # noqa
|
kfet.cms.models.MemberTeam # noqa
|
||||||
),
|
),
|
||||||
|
|
90
kfet/cms/migrations/0003_alter_kfetpage_content.py
Normal file
90
kfet/cms/migrations/0003_alter_kfetpage_content.py
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
# Generated by Django 4.2.17 on 2024-12-19 12:27
|
||||||
|
|
||||||
|
import wagtail.blocks
|
||||||
|
import wagtail.fields
|
||||||
|
import wagtail.snippets.blocks
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
import kfet.cms.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("kfetcms", "0002_alter_kfetpage_colcount"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="kfetpage",
|
||||||
|
name="content",
|
||||||
|
field=wagtail.fields.StreamField(
|
||||||
|
[
|
||||||
|
("rich", wagtail.blocks.RichTextBlock(label="Éditeur")),
|
||||||
|
("carte", kfet.cms.models.MenuBlock()),
|
||||||
|
(
|
||||||
|
"group_team",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"show_only",
|
||||||
|
wagtail.blocks.IntegerBlock(
|
||||||
|
help_text="Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.",
|
||||||
|
label="Montrer seulement",
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"members",
|
||||||
|
wagtail.blocks.ListBlock(
|
||||||
|
wagtail.snippets.blocks.SnippetChooserBlock(
|
||||||
|
kfet.cms.models.MemberTeam
|
||||||
|
),
|
||||||
|
form_classname="team-group",
|
||||||
|
label="K-Fêt-eux-ses",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"group",
|
||||||
|
wagtail.blocks.StreamBlock(
|
||||||
|
[
|
||||||
|
("rich", wagtail.blocks.RichTextBlock(label="Éditeur")),
|
||||||
|
("carte", kfet.cms.models.MenuBlock()),
|
||||||
|
(
|
||||||
|
"group_team",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"show_only",
|
||||||
|
wagtail.blocks.IntegerBlock(
|
||||||
|
help_text="Nombre initial de membres affichés. Laisser vide pour tou-te-s les afficher.",
|
||||||
|
label="Montrer seulement",
|
||||||
|
required=False,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"members",
|
||||||
|
wagtail.blocks.ListBlock(
|
||||||
|
wagtail.snippets.blocks.SnippetChooserBlock(
|
||||||
|
kfet.cms.models.MemberTeam
|
||||||
|
),
|
||||||
|
form_classname="team-group",
|
||||||
|
label="K-Fêt-eux-ses",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
label="Contenu groupé",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
use_json_field=True,
|
||||||
|
verbose_name="Contenu",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,15 +1,9 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from wagtail.admin.edit_handlers import (
|
from wagtail import blocks
|
||||||
FieldPanel,
|
from wagtail.admin.panels import FieldPanel, FieldRowPanel, MultiFieldPanel
|
||||||
FieldRowPanel,
|
from wagtail.fields import StreamField
|
||||||
MultiFieldPanel,
|
from wagtail.models import Page
|
||||||
StreamFieldPanel,
|
|
||||||
)
|
|
||||||
from wagtail.core import blocks
|
|
||||||
from wagtail.core.fields import StreamField
|
|
||||||
from wagtail.core.models import Page
|
|
||||||
from wagtail.images.edit_handlers import ImageChooserPanel
|
|
||||||
from wagtail.snippets.blocks import SnippetChooserBlock
|
from wagtail.snippets.blocks import SnippetChooserBlock
|
||||||
from wagtail.snippets.models import register_snippet
|
from wagtail.snippets.models import register_snippet
|
||||||
|
|
||||||
|
@ -43,7 +37,7 @@ class MemberTeam(models.Model):
|
||||||
FieldPanel("first_name"),
|
FieldPanel("first_name"),
|
||||||
FieldPanel("last_name"),
|
FieldPanel("last_name"),
|
||||||
FieldPanel("nick_name"),
|
FieldPanel("nick_name"),
|
||||||
ImageChooserPanel("photo"),
|
FieldPanel("photo"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
@ -97,7 +91,9 @@ class KFetStreamBlock(ChoicesStreamBlock):
|
||||||
|
|
||||||
|
|
||||||
class KFetPage(Page):
|
class KFetPage(Page):
|
||||||
content = StreamField(KFetStreamBlock, verbose_name=_("Contenu"))
|
content = StreamField(
|
||||||
|
KFetStreamBlock, verbose_name=_("Contenu"), use_json_field=True
|
||||||
|
)
|
||||||
|
|
||||||
# Layout fields
|
# Layout fields
|
||||||
|
|
||||||
|
@ -135,7 +131,7 @@ class KFetPage(Page):
|
||||||
|
|
||||||
# Panels
|
# Panels
|
||||||
|
|
||||||
content_panels = Page.content_panels + [StreamFieldPanel("content")]
|
content_panels = Page.content_panels + [FieldPanel("content")]
|
||||||
|
|
||||||
layout_panel = [
|
layout_panel = [
|
||||||
FieldPanel("no_header"),
|
FieldPanel("no_header"),
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
|
from asgiref.sync import async_to_sync
|
||||||
|
from channels.layers import get_channel_layer
|
||||||
|
|
||||||
from .utils import DjangoJsonWebsocketConsumer, PermConsumerMixin
|
from .utils import DjangoJsonWebsocketConsumer, PermConsumerMixin
|
||||||
|
|
||||||
|
|
||||||
class KPsul(PermConsumerMixin, DjangoJsonWebsocketConsumer):
|
class KPsul(PermConsumerMixin, DjangoJsonWebsocketConsumer):
|
||||||
groups = ["kfet.kpsul"]
|
groups = ["kfet.kpsul"]
|
||||||
perms_connect = ["kfet.is_team"]
|
perms_connect = ["kfet.is_team"]
|
||||||
|
|
||||||
|
async def kpsul(self, event):
|
||||||
|
await self.send_json(event)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
@async_to_sync
|
||||||
|
async def group_send(cls, group, data):
|
||||||
|
channel_layer = get_channel_layer()
|
||||||
|
await channel_layer.group_send(group, data)
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.core import validators
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.forms import modelformset_factory
|
from django.forms import modelformset_factory
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from djconfig.forms import ConfigForm
|
from djconfig.forms import ConfigForm
|
||||||
|
|
||||||
from gestioncof.models import CofProfile
|
from gestioncof.models import CofProfile
|
||||||
|
@ -93,7 +93,7 @@ class DemandeSoireeForm(forms.Form):
|
||||||
|
|
||||||
def default_promo():
|
def default_promo():
|
||||||
now = date.today()
|
now = date.today()
|
||||||
return now.month <= 8 and now.year - 1 or now.year
|
return now.month <= 7 and now.year - 1 or now.year
|
||||||
|
|
||||||
|
|
||||||
def get_promo_choices():
|
def get_promo_choices():
|
||||||
|
@ -118,7 +118,11 @@ class AccountForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Account
|
model = Account
|
||||||
fields = ["trigramme", "promo", "nickname"]
|
fields = ["trigramme", "promo", "nickname"]
|
||||||
widgets = {"trigramme": forms.TextInput(attrs={"autocomplete": "off"})}
|
widgets = {
|
||||||
|
"trigramme": forms.TextInput(
|
||||||
|
attrs={"autocomplete": "off", "class": "trigramme_field"}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class AccountBalanceForm(forms.ModelForm):
|
class AccountBalanceForm(forms.ModelForm):
|
||||||
|
@ -404,7 +408,11 @@ class KPsulAccountForm(forms.ModelForm):
|
||||||
fields = ["trigramme"]
|
fields = ["trigramme"]
|
||||||
widgets = {
|
widgets = {
|
||||||
"trigramme": forms.TextInput(
|
"trigramme": forms.TextInput(
|
||||||
attrs={"autocomplete": "off", "spellcheck": "false"}
|
attrs={
|
||||||
|
"autocomplete": "off",
|
||||||
|
"spellcheck": "false",
|
||||||
|
"class": "trigramme_field",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import unicode_literals
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
from django.utils.timezone import utc
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -17,7 +16,9 @@ class Migration(migrations.Migration):
|
||||||
name="at",
|
name="at",
|
||||||
field=models.DateTimeField(
|
field=models.DateTimeField(
|
||||||
auto_now_add=True,
|
auto_now_add=True,
|
||||||
default=datetime.datetime(2016, 8, 29, 18, 35, 3, 419033, tzinfo=utc),
|
default=datetime.datetime(
|
||||||
|
2016, 8, 29, 18, 35, 3, 419033, tzinfo=datetime.timezone.utc
|
||||||
|
),
|
||||||
),
|
),
|
||||||
preserve_default=False,
|
preserve_default=False,
|
||||||
),
|
),
|
||||||
|
|
|
@ -2,6 +2,7 @@ import re
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.contrib.sites.models import Site
|
||||||
from django.core.mail import EmailMessage
|
from django.core.mail import EmailMessage
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
|
@ -9,7 +10,7 @@ from django.db.models import F
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from gestioncof.models import CofProfile
|
from gestioncof.models import CofProfile
|
||||||
from shared.utils import choices_length
|
from shared.utils import choices_length
|
||||||
|
@ -269,6 +270,36 @@ class Account(models.Model):
|
||||||
def __init__(self, trigramme):
|
def __init__(self, trigramme):
|
||||||
self.trigramme = trigramme
|
self.trigramme = trigramme
|
||||||
|
|
||||||
|
def send_creation_email(self):
|
||||||
|
"""
|
||||||
|
Envoie un mail à la création du trigramme.
|
||||||
|
"""
|
||||||
|
mail_data = settings.MAIL_DATA["kfet"]
|
||||||
|
|
||||||
|
email = EmailMessage(
|
||||||
|
subject="Création d'un trigramme",
|
||||||
|
body=loader.render_to_string(
|
||||||
|
"kfet/mails/creation_trigramme.txt",
|
||||||
|
context={
|
||||||
|
"account": self,
|
||||||
|
"site": Site.objects.get_current(),
|
||||||
|
"url_read": reverse("kfet.account.read", args=(self.trigramme,)),
|
||||||
|
"url_update": reverse(
|
||||||
|
"kfet.account.update", args=(self.trigramme,)
|
||||||
|
),
|
||||||
|
"url_delete": reverse(
|
||||||
|
"kfet.account.delete", args=(self.trigramme,)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
from_email=mail_data["FROM"],
|
||||||
|
to=[self.email],
|
||||||
|
reply_to=[mail_data["REPLYTO"]],
|
||||||
|
)
|
||||||
|
|
||||||
|
# On envoie le mail
|
||||||
|
email.send()
|
||||||
|
|
||||||
|
|
||||||
def get_deleted_account():
|
def get_deleted_account():
|
||||||
return Account.objects.get(trigramme=KFET_DELETED_TRIGRAMME)
|
return Account.objects.get(trigramme=KFET_DELETED_TRIGRAMME)
|
||||||
|
@ -298,7 +329,7 @@ class AccountNegative(models.Model):
|
||||||
"""
|
"""
|
||||||
Envoie un mail de rappel signalant que la personne est en négatif.
|
Envoie un mail de rappel signalant que la personne est en négatif.
|
||||||
"""
|
"""
|
||||||
mail_data = settings.MAIL_DATA["rappel_negatif"]
|
mail_data = settings.MAIL_DATA["kfet"]
|
||||||
|
|
||||||
email = EmailMessage(
|
email = EmailMessage(
|
||||||
subject="Compte K-Psul négatif",
|
subject="Compte K-Psul négatif",
|
||||||
|
@ -321,7 +352,6 @@ class AccountNegative(models.Model):
|
||||||
# On enregistre le fait que l'envoi a bien eu lieu
|
# On enregistre le fait que l'envoi a bien eu lieu
|
||||||
self.last_rappel = timezone.now()
|
self.last_rappel = timezone.now()
|
||||||
self.save()
|
self.save()
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
class CheckoutQuerySet(models.QuerySet):
|
class CheckoutQuerySet(models.QuerySet):
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
from asgiref.sync import sync_to_async
|
||||||
|
|
||||||
from ..decorators import kfet_is_team
|
from ..decorators import kfet_is_team
|
||||||
from ..utils import DjangoJsonWebsocketConsumer, PermConsumerMixin
|
from ..utils import DjangoJsonWebsocketConsumer, PermConsumerMixin
|
||||||
from .open import kfet_open
|
from .open import kfet_open
|
||||||
|
@ -12,13 +14,15 @@ class OpenKfetConsumer(PermConsumerMixin, DjangoJsonWebsocketConsumer):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def connection_groups(self, user, **kwargs):
|
async def open_status(self, event):
|
||||||
"""Select which group the user should be connected."""
|
await self.send_json(event)
|
||||||
if kfet_is_team(user):
|
|
||||||
return ["kfet.open.team"]
|
|
||||||
return ["kfet.open.base"]
|
|
||||||
|
|
||||||
def connect(self, message, *args, **kwargs):
|
async def connect(self):
|
||||||
"""Send current status on connect."""
|
"""Send current status on connect."""
|
||||||
super().connect(message, *args, **kwargs)
|
await super().connect()
|
||||||
self.send(kfet_open.export(message.user))
|
|
||||||
|
group = "team" if await sync_to_async(kfet_is_team)(self.user) else "base"
|
||||||
|
|
||||||
|
await self.channel_layer.group_add(f"kfet.open.{group}", self.channel_name)
|
||||||
|
|
||||||
|
await self.send_json(kfet_open.export(self.user))
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from channels.layers import get_channel_layer
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from ..decorators import kfet_is_team
|
from ..decorators import kfet_is_team
|
||||||
|
@ -77,7 +78,7 @@ class OpenKfet(CachedMixin, object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
status = self.status()
|
status = self.status()
|
||||||
base = {"status": status}
|
base = {"status": status, "type": "open.status"}
|
||||||
restrict = {
|
restrict = {
|
||||||
"admin_status": self.admin_status(status),
|
"admin_status": self.admin_status(status),
|
||||||
"force_close": self.force_close,
|
"force_close": self.force_close,
|
||||||
|
@ -95,13 +96,14 @@ class OpenKfet(CachedMixin, object):
|
||||||
base, team = self._export()
|
base, team = self._export()
|
||||||
return team if kfet_is_team(user) else base
|
return team if kfet_is_team(user) else base
|
||||||
|
|
||||||
def send_ws(self):
|
async def send_ws(self):
|
||||||
"""Send internal state to websocket channels."""
|
"""Send internal state to websocket channels."""
|
||||||
from .consumers import OpenKfetConsumer
|
|
||||||
|
|
||||||
base, team = self._export()
|
base, team = self._export()
|
||||||
OpenKfetConsumer.group_send("kfet.open.base", base)
|
|
||||||
OpenKfetConsumer.group_send("kfet.open.team", team)
|
channel_layer = get_channel_layer()
|
||||||
|
|
||||||
|
await channel_layer.group_send("kfet.open.base", base)
|
||||||
|
await channel_layer.group_send("kfet.open.team", team)
|
||||||
|
|
||||||
|
|
||||||
kfet_open = OpenKfet()
|
kfet_open = OpenKfet()
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
from channels.routing import route_class
|
from channels.routing import URLRouter
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
from . import consumers
|
from .consumers import OpenKfetConsumer
|
||||||
|
|
||||||
routing = [route_class(consumers.OpenKfetConsumer)]
|
OpenRouter = URLRouter(
|
||||||
|
[
|
||||||
|
path("", OpenKfetConsumer.as_asgi()),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
|
@ -3,20 +3,22 @@ var OpenWS = new KfetWebsocket({
|
||||||
});
|
});
|
||||||
|
|
||||||
var OpenKfet = function (force_close_url, admin) {
|
var OpenKfet = function (force_close_url, admin) {
|
||||||
this.force_close_url = force_close_url;
|
that = this;
|
||||||
this.admin = admin;
|
$( function() {
|
||||||
|
that.force_close_url = force_close_url;
|
||||||
|
that.admin = admin;
|
||||||
|
|
||||||
this.status = this.UNKNOWN;
|
that.status = that.UNKNOWN;
|
||||||
this.dom = {
|
that.dom = {
|
||||||
status_text: $('.kfetopen .status-text'),
|
status_text: $('.kfetopen .status-text'),
|
||||||
force_close_btn: $('.kfetopen .force-close-btn'),
|
force_close_btn: $('.kfetopen .force-close-btn'),
|
||||||
warning: $('.kfetopen .warning')
|
warning: $('.kfetopen .warning')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dom.force_close_btn.click(() => this.toggle_force_close());
|
that.dom.force_close_btn.click(() => that.toggle_force_close());
|
||||||
setInterval(() => this.refresh(), this.refresh_interval * 1000);
|
setInterval(() => that.refresh(), that.refresh_interval * 1000);
|
||||||
OpenWS.add_handler(data => this.refresh(data));
|
OpenWS.add_handler(data => that.refresh(data));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
OpenKfet.prototype = {
|
OpenKfet.prototype = {
|
||||||
|
@ -49,6 +51,8 @@ OpenKfet.prototype = {
|
||||||
deactivate: "Réouvrir la K-Fêt"
|
deactivate: "Réouvrir la K-Fêt"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
callbacks: [ ],
|
||||||
|
|
||||||
get is_recent() {
|
get is_recent() {
|
||||||
return this.last_update && moment().diff(this.last_update, 'minute') <= this.time_unknown;
|
return this.last_update && moment().diff(this.last_update, 'minute') <= this.time_unknown;
|
||||||
},
|
},
|
||||||
|
@ -69,6 +73,9 @@ OpenKfet.prototype = {
|
||||||
|
|
||||||
this.add_class(status);
|
this.add_class(status);
|
||||||
this.dom.status_text.html(this.status_text[status]);
|
this.dom.status_text.html(this.status_text[status]);
|
||||||
|
for (callback of this.callbacks) {
|
||||||
|
callback(status);
|
||||||
|
}
|
||||||
|
|
||||||
// admin specific
|
// admin specific
|
||||||
if (this.admin) {
|
if (this.admin) {
|
||||||
|
@ -109,5 +116,9 @@ OpenKfet.prototype = {
|
||||||
|
|
||||||
add_class: function (status) {
|
add_class: function (status) {
|
||||||
$(this.target).addClass(this.class_prefix + status);
|
$(this.target).addClass(this.class_prefix + status);
|
||||||
|
},
|
||||||
|
|
||||||
|
add_callback: function (callback) {
|
||||||
|
this.callbacks.push(callback);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
89
kfet/open/templates/kfetopen/indicator.html
Normal file
89
kfet/open/templates/kfetopen/indicator.html
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
{% extends 'kfet/base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_head %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
kfet_open.add_callback(function(status) {
|
||||||
|
const div = document.getElementById("main");
|
||||||
|
switch (status) {
|
||||||
|
case "opened":
|
||||||
|
div.className = "green";
|
||||||
|
document.title = "🟢 Ouvert | K-Fêt";
|
||||||
|
break;
|
||||||
|
case "closed":
|
||||||
|
div.className = "red";
|
||||||
|
document.title = "🔴 Fermé | K-Fêt";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
div.className = "orange";
|
||||||
|
document.title = "🟠 Indéfini | K-Fêt";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
#main {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: min(15vw, 0.75*(100vh - 50px));
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
width: 100vw;
|
||||||
|
top: 50px;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red {
|
||||||
|
background-color: red;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orange {
|
||||||
|
background-color: orange;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.green {
|
||||||
|
background-color: green;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main > p {
|
||||||
|
overflow: hidden;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orange > #orange {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.green > #green {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.red > #red {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block title %}Indicateur{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
{% block help %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<div id="main" class="orange">
|
||||||
|
<p id="orange">Non défini</p>
|
||||||
|
<p id="red">Fermé</p>
|
||||||
|
<p id="green">Ouvert</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -4,10 +4,8 @@
|
||||||
<script type="text/javascript" src="{% static "kfetopen/kfet-open.js" %}"></script>
|
<script type="text/javascript" src="{% static "kfetopen/kfet-open.js" %}"></script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$( function() {
|
|
||||||
kfet_open = new OpenKfet(
|
kfet_open = new OpenKfet(
|
||||||
"{% url "kfet.open.edit_force_close" %}",
|
"{% url "kfet.open.edit_force_close" %}",
|
||||||
{{ perms.kfet.is_team|yesno:"true,false" }}
|
{{ perms.kfet.is_team|yesno:"true,false" }}
|
||||||
);
|
);
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
import json
|
|
||||||
import random
|
import random
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from channels.channel import Group
|
from asgiref.sync import async_to_sync
|
||||||
from channels.test import ChannelTestCase, WSClient
|
from channels.auth import AuthMiddlewareStack
|
||||||
|
from channels.consumer import get_channel_layer
|
||||||
|
from channels.testing import WebsocketCommunicator
|
||||||
from django.contrib.auth.models import AnonymousUser, Permission, User
|
from django.contrib.auth.models import AnonymousUser, Permission, User
|
||||||
from django.test import Client
|
from django.test import Client, TestCase
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from . import OpenKfet
|
from . import OpenKfet
|
||||||
from .consumers import OpenKfetConsumer
|
from .consumers import OpenKfetConsumer
|
||||||
|
|
||||||
|
|
||||||
class OpenKfetTest(ChannelTestCase):
|
def ws_communicator(cls, path: str, headers=[]):
|
||||||
|
return WebsocketCommunicator(AuthMiddlewareStack(cls.as_asgi()), path, headers)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenKfetTest(TestCase):
|
||||||
"""OpenKfet object unit-tests suite."""
|
"""OpenKfet object unit-tests suite."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -79,7 +84,7 @@ class OpenKfetTest(ChannelTestCase):
|
||||||
def test_export_user(self):
|
def test_export_user(self):
|
||||||
"""Export is limited for an anonymous user."""
|
"""Export is limited for an anonymous user."""
|
||||||
export = self.kfet_open.export(AnonymousUser())
|
export = self.kfet_open.export(AnonymousUser())
|
||||||
self.assertSetEqual(set(["status"]), set(export))
|
self.assertSetEqual(set(["status", "type"]), set(export))
|
||||||
|
|
||||||
def test_export_team(self):
|
def test_export_team(self):
|
||||||
"""Export all values for a team member."""
|
"""Export all values for a team member."""
|
||||||
|
@ -89,24 +94,32 @@ class OpenKfetTest(ChannelTestCase):
|
||||||
)
|
)
|
||||||
user.user_permissions.add(is_team)
|
user.user_permissions.add(is_team)
|
||||||
export = self.kfet_open.export(user)
|
export = self.kfet_open.export(user)
|
||||||
self.assertSetEqual(set(["status", "admin_status", "force_close"]), set(export))
|
self.assertSetEqual(
|
||||||
|
set(["status", "admin_status", "force_close", "type"]), set(export)
|
||||||
|
)
|
||||||
|
|
||||||
def test_send_ws(self):
|
async def test_send_ws(self):
|
||||||
Group("kfet.open.base").add("test.open.base")
|
channel_layer = get_channel_layer()
|
||||||
Group("kfet.open.team").add("test.open.team")
|
base_channel = await channel_layer.new_channel()
|
||||||
|
team_channel = await channel_layer.new_channel()
|
||||||
|
|
||||||
self.kfet_open.send_ws()
|
await channel_layer.group_add("kfet.open.base", base_channel)
|
||||||
|
await channel_layer.group_add("kfet.open.team", team_channel)
|
||||||
|
|
||||||
recv_base = self.get_next_message("test.open.base", require=True)
|
await self.kfet_open.send_ws()
|
||||||
base = json.loads(recv_base["text"])
|
|
||||||
self.assertSetEqual(set(["status"]), set(base))
|
|
||||||
|
|
||||||
recv_admin = self.get_next_message("test.open.team", require=True)
|
base = await channel_layer.receive(base_channel)
|
||||||
admin = json.loads(recv_admin["text"])
|
|
||||||
self.assertSetEqual(set(["status", "admin_status", "force_close"]), set(admin))
|
self.assertSetEqual(set(["status", "type"]), set(base))
|
||||||
|
|
||||||
|
team = await channel_layer.receive(team_channel)
|
||||||
|
|
||||||
|
self.assertSetEqual(
|
||||||
|
set(["status", "admin_status", "force_close", "type"]), set(team)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OpenKfetViewsTest(ChannelTestCase):
|
class OpenKfetViewsTest(TestCase):
|
||||||
"""OpenKfet views unit-tests suite."""
|
"""OpenKfet views unit-tests suite."""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -177,119 +190,136 @@ class OpenKfetViewsTest(ChannelTestCase):
|
||||||
self.assertEqual(403, resp.status_code)
|
self.assertEqual(403, resp.status_code)
|
||||||
|
|
||||||
|
|
||||||
class OpenKfetConsumerTest(ChannelTestCase):
|
class OpenKfetConsumerTest(TestCase):
|
||||||
"""OpenKfet consumer unit-tests suite."""
|
"""OpenKfet consumer unit-tests suite."""
|
||||||
|
|
||||||
def test_standard_user(self):
|
@classmethod
|
||||||
"""Lambda user is added to kfet.open.base group."""
|
def setUpTestData(cls):
|
||||||
# setup anonymous client
|
|
||||||
c = WSClient()
|
|
||||||
|
|
||||||
# connect
|
|
||||||
c.send_and_consume(
|
|
||||||
"websocket.connect", path="/ws/k-fet/open", fail_on_none=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# initialization data is replied on connection
|
|
||||||
self.assertIsNotNone(c.receive())
|
|
||||||
|
|
||||||
# client belongs to the 'kfet.open' group...
|
|
||||||
OpenKfetConsumer.group_send("kfet.open.base", {"test": "plop"})
|
|
||||||
self.assertEqual(c.receive(), {"test": "plop"})
|
|
||||||
|
|
||||||
# ...but not to the 'kfet.open.admin' one
|
|
||||||
OpenKfetConsumer.group_send("kfet.open.team", {"test": "plop"})
|
|
||||||
self.assertIsNone(c.receive())
|
|
||||||
|
|
||||||
@mock.patch("gestioncof.signals.messages")
|
|
||||||
def test_team_user(self, mock_messages):
|
|
||||||
"""Team user is added to kfet.open.team group."""
|
|
||||||
# setup team user and its client
|
|
||||||
t = User.objects.create_user("team", "", "team")
|
t = User.objects.create_user("team", "", "team")
|
||||||
is_team = Permission.objects.get(
|
is_team = Permission.objects.get(
|
||||||
codename="is_team", content_type__app_label="kfet"
|
codename="is_team", content_type__app_label="kfet"
|
||||||
)
|
)
|
||||||
t.user_permissions.add(is_team)
|
t.user_permissions.add(is_team)
|
||||||
c = WSClient()
|
|
||||||
c.force_login(t, backend="django.contrib.auth.backends.ModelBackend")
|
|
||||||
|
|
||||||
# connect
|
cls.team_user = t
|
||||||
c.send_and_consume(
|
|
||||||
"websocket.connect", path="/ws/k-fet/open", fail_on_none=True
|
async def test_standard_user(self):
|
||||||
)
|
"""Lambda user is added to kfet.open.base group."""
|
||||||
|
# setup anonymous client
|
||||||
|
c = ws_communicator(OpenKfetConsumer, "/ws/k-fet/open")
|
||||||
|
|
||||||
|
connected, _ = await c.connect()
|
||||||
|
|
||||||
|
self.assertTrue(connected)
|
||||||
|
|
||||||
# initialization data is replied on connection
|
# initialization data is replied on connection
|
||||||
self.assertIsNotNone(c.receive())
|
message = await c.receive_json_from()
|
||||||
|
self.assertIsNotNone(message)
|
||||||
|
|
||||||
# client belongs to the 'kfet.open.admin' group...
|
# client belongs to the 'kfet.open' group...
|
||||||
OpenKfetConsumer.group_send("kfet.open.team", {"test": "plop"})
|
channel_layer = get_channel_layer()
|
||||||
self.assertEqual(c.receive(), {"test": "plop"})
|
|
||||||
|
await channel_layer.group_send(
|
||||||
|
"kfet.open.base", {"test": "plop", "type": "open.status"}
|
||||||
|
)
|
||||||
|
message = await c.receive_json_from()
|
||||||
|
|
||||||
|
self.assertEqual(message, {"test": "plop"})
|
||||||
|
|
||||||
|
# ...but not to the 'kfet.open.admin' one
|
||||||
|
await channel_layer.group_send(
|
||||||
|
"kfet.open.team", {"test": "plop", "type": "open.status"}
|
||||||
|
)
|
||||||
|
self.assertTrue(await c.receive_nothing())
|
||||||
|
|
||||||
|
async def test_team_user(self):
|
||||||
|
"""Team user is added to kfet.open.team group."""
|
||||||
|
|
||||||
|
# On simule l'appartenance de l'user à la team kfet car l'utilisation de
|
||||||
|
# tests async avec postgres fait tout planter si on modifie la db dans un
|
||||||
|
# des sous tests.
|
||||||
|
with mock.patch("gestioncof.signals.messages"), mock.patch(
|
||||||
|
"kfet.open.consumers.kfet_is_team", return_value=True
|
||||||
|
):
|
||||||
|
c = ws_communicator(OpenKfetConsumer, "/ws/k-fet/open")
|
||||||
|
|
||||||
|
connected, _ = await c.connect()
|
||||||
|
|
||||||
|
channel_layer = get_channel_layer()
|
||||||
|
|
||||||
|
self.assertTrue(connected)
|
||||||
|
|
||||||
|
# initialization data is replied on connection
|
||||||
|
message = await c.receive_json_from()
|
||||||
|
self.assertIsNotNone(message)
|
||||||
|
|
||||||
|
# client belongs to the 'kfet.open.team' group...
|
||||||
|
await channel_layer.group_send(
|
||||||
|
"kfet.open.team", {"test": "plop", "type": "open.status"}
|
||||||
|
)
|
||||||
|
message = await c.receive_json_from()
|
||||||
|
|
||||||
|
self.assertEqual(message, {"test": "plop"})
|
||||||
|
|
||||||
# ...but not to the 'kfet.open' one
|
# ...but not to the 'kfet.open' one
|
||||||
OpenKfetConsumer.group_send("kfet.open.base", {"test": "plop"})
|
await channel_layer.group_send(
|
||||||
self.assertIsNone(c.receive())
|
"kfet.open.base", {"test": "plop", "type": "open.status"}
|
||||||
|
)
|
||||||
|
self.assertTrue(await c.receive_nothing())
|
||||||
|
|
||||||
|
|
||||||
class OpenKfetScenarioTest(ChannelTestCase):
|
class OpenKfetScenarioTest(TestCase):
|
||||||
"""OpenKfet functionnal tests suite."""
|
"""OpenKfet functionnal tests suite."""
|
||||||
|
|
||||||
def setUp(self):
|
@classmethod
|
||||||
# Need this (and here) because of '<client>.login' in setUp
|
def setUpTestData(cls):
|
||||||
patcher_messages = mock.patch("gestioncof.signals.messages")
|
# root user
|
||||||
patcher_messages.start()
|
cls.r = User.objects.create_superuser("team", "", "team")
|
||||||
self.addCleanup(patcher_messages.stop)
|
|
||||||
|
|
||||||
# anonymous client (for views)
|
# anonymous client (for views)
|
||||||
self.c = Client()
|
cls.c = Client()
|
||||||
# anonymous client (for websockets)
|
|
||||||
self.c_ws = WSClient()
|
|
||||||
|
|
||||||
# root user
|
# root client
|
||||||
self.r = User.objects.create_superuser("root", "", "root")
|
cls.r_c = Client()
|
||||||
# its client (for views)
|
|
||||||
self.r_c = Client()
|
with mock.patch("gestioncof.signals.messages"):
|
||||||
self.r_c.login(username="root", password="root")
|
cls.r_c.login(username="team", password="team")
|
||||||
# its client (for websockets)
|
|
||||||
self.r_c_ws = WSClient()
|
def setUp(self):
|
||||||
self.r_c_ws.force_login(
|
# Create channels to listen to messages
|
||||||
self.r, backend="django.contrib.auth.backends.ModelBackend"
|
channel_layer = get_channel_layer()
|
||||||
)
|
|
||||||
|
self.channel = async_to_sync(channel_layer.new_channel)()
|
||||||
|
self.team_channel = async_to_sync(channel_layer.new_channel)()
|
||||||
|
|
||||||
|
async_to_sync(channel_layer.group_add)("kfet.open.base", self.channel)
|
||||||
|
async_to_sync(channel_layer.group_add)("kfet.open.team", self.team_channel)
|
||||||
|
|
||||||
|
self.receive_msg = lambda c: async_to_sync(channel_layer.receive)(c)
|
||||||
|
|
||||||
self.kfet_open = OpenKfet(
|
self.kfet_open = OpenKfet(
|
||||||
cache_prefix="test_kfetopen_%s" % random.randrange(2**20)
|
cache_prefix="test_kfetopen_%s" % random.randrange(2**20)
|
||||||
)
|
)
|
||||||
self.addCleanup(self.kfet_open.clear_cache)
|
self.addCleanup(self.kfet_open.clear_cache)
|
||||||
|
|
||||||
def ws_connect(self, ws_client):
|
async def ws_connect(self, ws_communicator):
|
||||||
ws_client.send_and_consume(
|
c, _ = await ws_communicator.connect()
|
||||||
"websocket.connect", path="/ws/k-fet/open", fail_on_none=True
|
|
||||||
)
|
|
||||||
return ws_client.receive(json=True)
|
|
||||||
|
|
||||||
def test_scenario_0(self):
|
self.assertTrue(c)
|
||||||
"""Clients connect."""
|
return await ws_communicator.receive_json_from()
|
||||||
# test for anonymous user
|
|
||||||
msg = self.ws_connect(self.c_ws)
|
|
||||||
self.assertSetEqual(set(["status"]), set(msg))
|
|
||||||
|
|
||||||
# test for root user
|
|
||||||
msg = self.ws_connect(self.r_c_ws)
|
|
||||||
self.assertSetEqual(set(["status", "admin_status", "force_close"]), set(msg))
|
|
||||||
|
|
||||||
def test_scenario_1(self):
|
def test_scenario_1(self):
|
||||||
"""Clients connect, door opens, enable force close."""
|
"""Clients connect, door opens, enable force close."""
|
||||||
self.ws_connect(self.c_ws)
|
|
||||||
self.ws_connect(self.r_c_ws)
|
|
||||||
|
|
||||||
# door sent "I'm open!"
|
# door sent "I'm open!"
|
||||||
self.c.post("/k-fet/open/raw_open", {"raw_open": True, "token": "plop"})
|
self.c.post("/k-fet/open/raw_open", {"raw_open": True, "token": "plop"})
|
||||||
|
|
||||||
# anonymous user agree
|
# anonymous user agree
|
||||||
msg = self.c_ws.receive(json=True)
|
msg = self.receive_msg(self.channel)
|
||||||
self.assertEqual(OpenKfet.OPENED, msg["status"])
|
self.assertEqual(OpenKfet.OPENED, msg["status"])
|
||||||
|
|
||||||
# root user too
|
# root user too
|
||||||
msg = self.r_c_ws.receive(json=True)
|
msg = self.receive_msg(self.team_channel)
|
||||||
self.assertEqual(OpenKfet.OPENED, msg["status"])
|
self.assertEqual(OpenKfet.OPENED, msg["status"])
|
||||||
self.assertEqual(OpenKfet.OPENED, msg["admin_status"])
|
self.assertEqual(OpenKfet.OPENED, msg["admin_status"])
|
||||||
|
|
||||||
|
@ -297,11 +327,11 @@ class OpenKfetScenarioTest(ChannelTestCase):
|
||||||
self.r_c.post("/k-fet/open/force_close", {"force_close": True})
|
self.r_c.post("/k-fet/open/force_close", {"force_close": True})
|
||||||
|
|
||||||
# so anonymous user see it's closed
|
# so anonymous user see it's closed
|
||||||
msg = self.c_ws.receive(json=True)
|
msg = self.receive_msg(self.channel)
|
||||||
self.assertEqual(OpenKfet.CLOSED, msg["status"])
|
self.assertEqual(OpenKfet.CLOSED, msg["status"])
|
||||||
|
|
||||||
# root user too
|
# root user too
|
||||||
msg = self.r_c_ws.receive(json=True)
|
msg = self.receive_msg(self.team_channel)
|
||||||
self.assertEqual(OpenKfet.CLOSED, msg["status"])
|
self.assertEqual(OpenKfet.CLOSED, msg["status"])
|
||||||
# but root knows things
|
# but root knows things
|
||||||
self.assertEqual(OpenKfet.FAKE_CLOSED, msg["admin_status"])
|
self.assertEqual(OpenKfet.FAKE_CLOSED, msg["admin_status"])
|
||||||
|
@ -312,20 +342,42 @@ class OpenKfetScenarioTest(ChannelTestCase):
|
||||||
self.kfet_open.raw_open = True
|
self.kfet_open.raw_open = True
|
||||||
self.kfet_open.force_close = True
|
self.kfet_open.force_close = True
|
||||||
|
|
||||||
msg = self.ws_connect(self.c_ws)
|
async_to_sync(OpenKfet().send_ws)()
|
||||||
|
|
||||||
|
msg = self.receive_msg(self.channel)
|
||||||
self.assertEqual(OpenKfet.CLOSED, msg["status"])
|
self.assertEqual(OpenKfet.CLOSED, msg["status"])
|
||||||
|
|
||||||
msg = self.ws_connect(self.r_c_ws)
|
msg = self.receive_msg(self.team_channel)
|
||||||
self.assertEqual(OpenKfet.CLOSED, msg["status"])
|
self.assertEqual(OpenKfet.CLOSED, msg["status"])
|
||||||
self.assertEqual(OpenKfet.FAKE_CLOSED, msg["admin_status"])
|
self.assertEqual(OpenKfet.FAKE_CLOSED, msg["admin_status"])
|
||||||
self.assertTrue(msg["force_close"])
|
self.assertTrue(msg["force_close"])
|
||||||
|
|
||||||
self.r_c.post("/k-fet/open/force_close", {"force_close": False})
|
self.r_c.post("/k-fet/open/force_close", {"force_close": False})
|
||||||
|
|
||||||
msg = self.c_ws.receive(json=True)
|
msg = self.receive_msg(self.channel)
|
||||||
self.assertEqual(OpenKfet.OPENED, msg["status"])
|
self.assertEqual(OpenKfet.OPENED, msg["status"])
|
||||||
|
|
||||||
msg = self.r_c_ws.receive(json=True)
|
msg = self.receive_msg(self.team_channel)
|
||||||
self.assertEqual(OpenKfet.OPENED, msg["status"])
|
self.assertEqual(OpenKfet.OPENED, msg["status"])
|
||||||
self.assertEqual(OpenKfet.OPENED, msg["admin_status"])
|
self.assertEqual(OpenKfet.OPENED, msg["admin_status"])
|
||||||
self.assertFalse(msg["force_close"])
|
self.assertFalse(msg["force_close"])
|
||||||
|
|
||||||
|
async def test_scenario_3(self):
|
||||||
|
"""Clients connect."""
|
||||||
|
# anonymous client (for websockets)
|
||||||
|
self.c_ws = ws_communicator(OpenKfetConsumer, "/ws/k-fet/open")
|
||||||
|
|
||||||
|
# test for anonymous user
|
||||||
|
msg = await self.ws_connect(self.c_ws)
|
||||||
|
self.assertSetEqual(set(["status"]), set(msg))
|
||||||
|
|
||||||
|
# test for root user
|
||||||
|
with mock.patch(
|
||||||
|
"kfet.open.consumers.kfet_is_team", return_value=True
|
||||||
|
), mock.patch("kfet.open.open.kfet_is_team", return_value=True):
|
||||||
|
self.r_c_ws = ws_communicator(OpenKfetConsumer, "/ws/k-fet/open")
|
||||||
|
|
||||||
|
msg = await self.ws_connect(self.r_c_ws)
|
||||||
|
self.assertSetEqual(
|
||||||
|
set(["status", "admin_status", "force_close"]), set(msg)
|
||||||
|
)
|
||||||
|
|
|
@ -5,4 +5,5 @@ from . import views
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("raw_open", views.raw_open, name="kfet.open.edit_raw_open"),
|
path("raw_open", views.raw_open, name="kfet.open.edit_raw_open"),
|
||||||
path("force_close", views.force_close, name="kfet.open.edit_force_close"),
|
path("force_close", views.force_close, name="kfet.open.edit_force_close"),
|
||||||
|
path("", views.indicator, name="kfet.open.indicator"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
from asgiref.sync import async_to_sync
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import permission_required
|
from django.contrib.auth.decorators import permission_required
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
|
from django.shortcuts import render
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from django.views.decorators.http import require_POST
|
from django.views.decorators.http import require_POST
|
||||||
|
|
||||||
|
@ -18,7 +20,7 @@ def raw_open(request):
|
||||||
raise PermissionDenied
|
raise PermissionDenied
|
||||||
raw_open = request.POST.get("raw_open") in TRUE_STR
|
raw_open = request.POST.get("raw_open") in TRUE_STR
|
||||||
kfet_open.raw_open = raw_open
|
kfet_open.raw_open = raw_open
|
||||||
kfet_open.send_ws()
|
async_to_sync(kfet_open.send_ws)()
|
||||||
return HttpResponse()
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,5 +29,12 @@ def raw_open(request):
|
||||||
def force_close(request):
|
def force_close(request):
|
||||||
force_close = request.POST.get("force_close") in TRUE_STR
|
force_close = request.POST.get("force_close") in TRUE_STR
|
||||||
kfet_open.force_close = force_close
|
kfet_open.force_close = force_close
|
||||||
kfet_open.send_ws()
|
async_to_sync(kfet_open.send_ws)()
|
||||||
return HttpResponse()
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
|
def indicator(request):
|
||||||
|
return render(
|
||||||
|
request,
|
||||||
|
"kfetopen/indicator.html",
|
||||||
|
)
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
from channels.routing import include, route_class
|
from channels.routing import URLRouter
|
||||||
|
from django.urls import path
|
||||||
|
|
||||||
from . import consumers
|
from kfet.open.routing import OpenRouter
|
||||||
|
|
||||||
routing = [
|
from .consumers import KPsul
|
||||||
route_class(consumers.KPsul, path=r"^/k-psul/$"),
|
|
||||||
include("kfet.open.routing.routing", path=r"^/open"),
|
KFRouter = URLRouter(
|
||||||
|
[
|
||||||
|
path("k-psul/", KPsul.as_asgi()),
|
||||||
|
path("open", OpenRouter),
|
||||||
]
|
]
|
||||||
|
)
|
||||||
|
|
|
@ -91,7 +91,7 @@ var AccountView = Backbone.View.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
get_buttons: function () {
|
get_buttons: function () {
|
||||||
var url = django_urls["kfet.account.read"](this.model.get("trigramme"));
|
var url = django_urls["kfet.account.read"](encodeURIComponent(this.model.get("trigramme")));
|
||||||
|
|
||||||
return `<a href="${url}" class="btn btn-primary" target="_blank" title="Modifier"><span class="glyphicon glyphicon-cog"></span></a>`;
|
return `<a href="${url}" class="btn btn-primary" target="_blank" title="Modifier"><span class="glyphicon glyphicon-cog"></span></a>`;
|
||||||
},
|
},
|
||||||
|
|
|
@ -3,12 +3,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
String.prototype.format_trigramme = function () {
|
String.prototype.format_trigramme = function () {
|
||||||
return this.toUpperCase().substr(0, 3)
|
return _.toArray(this.toUpperCase()).splice(0,3).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
String.prototype.is_valid_trigramme = function () {
|
String.prototype.is_valid_trigramme = function () {
|
||||||
var pattern = /^[^a-z]{3}$/;
|
var arr = _.toArray(this);
|
||||||
return pattern.test(this);
|
return arr && arr.length == 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ class KfetWebsocket {
|
||||||
|
|
||||||
listen() {
|
listen() {
|
||||||
var that = this;
|
var that = this;
|
||||||
this.socket = new ReconnectingWebSocket(this.url);
|
this.socket = new ReconnectingWebSocket(this.url, [], { minReconnectionDelay: 100 });
|
||||||
|
|
||||||
this.socket.onmessage = function (e) {
|
this.socket.onmessage = function (e) {
|
||||||
var data = $.extend({}, that.default_msg, JSON.parse(e.data));
|
var data = $.extend({}, that.default_msg, JSON.parse(e.data));
|
||||||
|
|
117
kfet/static/kfet/vendor/lodash.min.js
vendored
Normal file
117
kfet/static/kfet/vendor/lodash.min.js
vendored
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* lodash 4.0.0 (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
|
||||||
|
* Build: `lodash -o ./dist/lodash.js`
|
||||||
|
*/
|
||||||
|
;(function(){function n(n,t){return n.set(t[0],t[1]),n}function t(n,t){return n.add(t),n}function r(n,t,r){switch(r?r.length:0){case 0:return n.call(t);case 1:return n.call(t,r[0]);case 2:return n.call(t,r[0],r[1]);case 3:return n.call(t,r[0],r[1],r[2])}return n.apply(t,r)}function e(n,t){for(var r=-1,e=n.length;++r<e&&false!==t(n[r],r,n););return n}function u(n,t){for(var r=-1,e=n.length;++r<e;)if(!t(n[r],r,n))return false;return true}function o(n,t){for(var r=-1,e=n.length,u=-1,o=[];++r<e;){var i=n[r];t(i,r,n)&&(o[++u]=i);
|
||||||
|
}return o}function i(n,t){return!!n.length&&-1<v(n,t,0)}function f(n,t,r){for(var e=-1,u=n.length;++e<u;)if(r(t,n[e]))return true;return false}function c(n,t){for(var r=-1,e=n.length,u=Array(e);++r<e;)u[r]=t(n[r],r,n);return u}function a(n,t){for(var r=-1,e=t.length,u=n.length;++r<e;)n[u+r]=t[r];return n}function l(n,t,r,e){var u=-1,o=n.length;for(e&&o&&(r=n[++u]);++u<o;)r=t(r,n[u],u,n);return r}function s(n,t,r,e){var u=n.length;for(e&&u&&(r=n[--u]);u--;)r=t(r,n[u],u,n);return r}function h(n,t){for(var r=-1,e=n.length;++r<e;)if(t(n[r],r,n))return true;
|
||||||
|
return false}function p(n,t,r){for(var e=-1,u=n.length;++e<u;){var o=n[e],i=t(o);if(null!=i&&(f===Z?i===i:r(i,f)))var f=i,c=o}return c}function _(n,t,r,e){var u;return r(n,function(n,r,o){return t(n,r,o)?(u=e?r:n,false):void 0}),u}function g(n,t,r){for(var e=n.length,u=r?e:-1;r?u--:++u<e;)if(t(n[u],u,n))return u;return-1}function v(n,t,r){if(t!==t)return W(n,r);--r;for(var e=n.length;++r<e;)if(n[r]===t)return r;return-1}function d(n,t,r,e,u){return u(n,function(n,u,o){r=e?(e=false,n):t(r,n,u,o)}),r}function y(n,t){
|
||||||
|
var r=n.length;for(n.sort(t);r--;)n[r]=n[r].c;return n}function b(n,t){for(var r,e=-1,u=n.length;++e<u;){var o=t(n[e]);o!==Z&&(r=r===Z?o:r+o)}return r}function x(n,t){for(var r=-1,e=Array(n);++r<n;)e[r]=t(r);return e}function m(n,t){return c(t,function(t){return[t,n[t]]})}function j(n){return function(t){return n(t)}}function w(n,t){return c(t,function(t){return n[t]})}function A(n,t){for(var r=-1,e=n.length;++r<e&&-1<v(t,n[r],0););return r}function O(n,t){for(var r=n.length;r--&&-1<v(t,n[r],0););
|
||||||
|
return r}function E(n){return n&&n.Object===Object?n:null}function k(n,t){if(n!==t){var r=null===n,e=n===Z,u=n===n,o=null===t,i=t===Z,f=t===t;if(n>t&&!o||!u||r&&!i&&f||e&&f)return 1;if(t>n&&!r||!f||o&&!e&&u||i&&u)return-1}return 0}function I(n){return Un[n]}function R(n){return zn[n]}function S(n){return"\\"+$n[n]}function W(n,t,r){var e=n.length;for(t+=r?0:-1;r?t--:++t<e;){var u=n[t];if(u!==u)return t}return-1}function C(n){var t=false;if(null!=n&&typeof n.toString!="function")try{t=!!(n+"")}catch(r){}
|
||||||
|
return t}function U(n,t){return n=typeof n=="number"||dn.test(n)?+n:-1,n>-1&&0==n%1&&(null==t?9007199254740991:t)>n}function z(n){for(var t,r=[];!(t=n.next()).done;)r.push(t.value);return r}function B(n){var t=-1,r=Array(n.size);return n.forEach(function(n,e){r[++t]=[e,n]}),r}function L(n,t){for(var r=-1,e=n.length,u=-1,o=[];++r<e;)n[r]===t&&(n[r]="__lodash_placeholder__",o[++u]=r);return o}function $(n){var t=-1,r=Array(n.size);return n.forEach(function(n){r[++t]=n}),r}function F(n){if(!n||!En.test(n))return n.length;
|
||||||
|
for(var t=On.lastIndex=0;On.test(n);)t++;return t}function M(n){return Bn[n]}function N(E){function dn(n){if(_e(n)&&!Wo(n)&&!(n instanceof wn)){if(n instanceof jn)return n;if(tu.call(n,"__wrapped__"))return Br(n)}return new jn(n)}function mn(){}function jn(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=Z}function wn(n){this.__wrapped__=n,this.__actions__=[],this.__dir__=1,this.__filtered__=false,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[];
|
||||||
|
}function Un(){}function zn(n){var t=-1,r=n?n.length:0;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function Bn(n){var t=-1,r=n?n.length:0;for(this.__data__=new zn;++t<r;)this.push(n[t])}function Ln(n,t){var r=n.__data__;return kr(t)?(r=r.__data__,"__lodash_hash_undefined__"===(typeof t=="string"?r.string:r.hash)[t]):r.has(t)}function $n(n){var t=-1,r=n?n.length:0;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function Nn(n,t){var r=Dn(n,t);return 0>r?false:(r==n.length-1?n.pop():du.call(n,r,1),
|
||||||
|
!0)}function Zn(n,t){var r=Dn(n,t);return 0>r?Z:n[r][1]}function Dn(n,t){for(var r=n.length;r--;)if(ue(n[r][0],t))return r;return-1}function qn(n,t,r){var e=Dn(n,t);0>e?n.push([t,r]):n[e][1]=r}function Pn(n,t,r,e){return n===Z||ue(n,Xe[r])&&!tu.call(e,r)?t:n}function Tn(n,t,r){(r!==Z&&!ue(n[t],r)||typeof t=="number"&&r===Z&&!(t in n))&&(n[t]=r)}function Vn(n,t,r){var e=n[t];(!ue(e,r)||ue(e,Xe[t])&&!tu.call(n,t)||r===Z&&!(t in n))&&(n[t]=r)}function Jn(n,t){return n&&Tt(t,Ce(t),n)}function Yn(n,t){
|
||||||
|
for(var r=-1,e=null==n,u=t.length,o=Array(u);++r<u;)o[r]=e?Z:Re(n,t[r]);return o}function Hn(n,t,r){return n===n&&(r!==Z&&(n=n>r?r:n),t!==Z&&(n=t>n?t:n)),n}function Qn(n,t,r,u,o,i){var f;if(r&&(f=o?r(n,u,o,i):r(n)),f!==Z)return f;if(!pe(n))return n;if(u=Wo(n)){if(f=mr(n),!t)return Pt(n,f)}else{var c=br(n),a="[object Function]"==c||"[object GeneratorFunction]"==c;if("[object Object]"!=c&&"[object Arguments]"!=c&&(!a||o))return Cn[c]?wr(n,c,t):o?n:{};if(C(n))return o?n:{};if(f=jr(a?{}:n),!t)return Gt(n,Jn(f,n));
|
||||||
|
}return i||(i=new $n),(o=i.get(n))?o:(i.set(n,f),(u?e:it)(n,function(e,u){Vn(f,u,Qn(e,t,r,u,n,i))}),u?f:Gt(n,f))}function Xn(n){var t=Ce(n),r=t.length;return function(e){if(null==e)return!r;for(var u=r;u--;){var o=t[u],i=n[o],f=e[o];if(f===Z&&!(o in Object(e))||!i(f))return false}return true}}function nt(n,t,r){if(typeof n!="function")throw new He("Expected a function");return vu(function(){n.apply(Z,r)},t)}function tt(n,t,r,e){var u=-1,o=i,a=true,l=n.length,s=[],h=t.length;if(!l)return s;r&&(t=c(t,j(r))),
|
||||||
|
e?(o=f,a=false):t.length>=200&&(o=Ln,a=false,t=new Bn(t));n:for(;++u<l;){var p=n[u],_=r?r(p):p;if(a&&_===_){for(var g=h;g--;)if(t[g]===_)continue n;s.push(p)}else o(t,_,e)||s.push(p)}return s}function rt(n,t){var r=true;return Nu(n,function(n,e,u){return r=!!t(n,e,u)}),r}function et(n,t){var r=[];return Nu(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function ut(n,t,r,e){e||(e=[]);for(var u=-1,o=n.length;++u<o;){var i=n[u];ce(i)&&(r||Wo(i)||ie(i))?t?ut(i,t,r,e):a(e,i):r||(e[e.length]=i)}return e}function ot(n,t){
|
||||||
|
null==n||Du(n,t,Ue)}function it(n,t){return n&&Du(n,t,Ce)}function ft(n,t){return n&&qu(n,t,Ce)}function ct(n,t){return o(t,function(t){return le(n[t])})}function at(n,t){t=Er(t,n)?[t+""]:Lt(t);for(var r=0,e=t.length;null!=n&&e>r;)n=n[t[r++]];return r&&r==e?n:Z}function lt(n,t){return tu.call(n,t)||typeof n=="object"&&t in n&&null===hu(n)}function st(n,t){return t in Object(n)}function ht(n,t,r){for(var e=r?f:i,u=n.length,o=u,a=Array(u),l=[];o--;){var s=n[o];o&&t&&(s=c(s,j(t))),a[o]=r||!t&&120>s.length?Z:new Bn(o&&s);
|
||||||
|
}var s=n[0],h=-1,p=s.length,_=a[0];n:for(;++h<p;){var g=s[h],v=t?t(g):g;if(_?!Ln(_,v):!e(l,v,r)){for(o=u;--o;){var d=a[o];if(d?!Ln(d,v):!e(n[o],v,r))continue n}_&&_.push(v),l.push(g)}}return l}function pt(n,t,e){return Er(t,n)||(t=Lt(t),n=Wr(n,t),t=Mr(t)),t=null==n?n:n[t],null==t?Z:r(t,n,e)}function _t(n,t,r,e,u){if(n===t)n=true;else if(null==n||null==t||!pe(n)&&!_e(t))n=n!==n&&t!==t;else n:{var o=Wo(n),i=Wo(t),f="[object Array]",c="[object Array]";o||(f=br(n),"[object Arguments]"==f?f="[object Object]":"[object Object]"!=f&&(o=me(n))),
|
||||||
|
i||(c=br(t),"[object Arguments]"==c?c="[object Object]":"[object Object]"!=c&&me(t));var a="[object Object]"==f&&!C(n),i="[object Object]"==c&&!C(t),c=f==c;if(!c||o||a){if(!(2&e)&&(f=a&&tu.call(n,"__wrapped__"),i=i&&tu.call(t,"__wrapped__"),f||i)){n=_t(f?n.value():n,i?t.value():t,r,e,u);break n}c?(u||(u=new $n),n=(o?hr:_r)(n,t,_t,r,e,u)):n=false}else n=pr(n,t,f,_t,r,e)}return n}function gt(n,t,r,e){var u=r.length,o=u,i=!e;if(null==n)return!o;for(n=Object(n);u--;){var f=r[u];if(i&&f[2]?f[1]!==n[f[0]]:!(f[0]in n))return false;
|
||||||
|
}for(;++u<o;){var f=r[u],c=f[0],a=n[c],l=f[1];if(i&&f[2]){if(a===Z&&!(c in n))return false}else if(f=new $n,c=e?e(a,l,c,n,t,f):Z,c===Z?!_t(l,a,e,3,f):!c)return false}return true}function vt(n){var t=typeof n;return"function"==t?n:null==n?Ne:"object"==t?Wo(n)?xt(n[0],n[1]):bt(n):Te(n)}function dt(n){n=null==n?n:Object(n);var t,r=[];for(t in n)r.push(t);return r}function yt(n,t){var r=-1,e=fe(n)?Array(n.length):[];return Nu(n,function(n,u,o){e[++r]=t(n,u,o)}),e}function bt(n){var t=dr(n);if(1==t.length&&t[0][2]){
|
||||||
|
var r=t[0][0],e=t[0][1];return function(n){return null==n?false:n[r]===e&&(e!==Z||r in Object(n))}}return function(r){return r===n||gt(r,n,t)}}function xt(n,t){return function(r){var e=Re(r,n);return e===Z&&e===t?We(r,n):_t(t,e,Z,3)}}function mt(n,t,r,u){if(n!==t){var o=Wo(t)||me(t)?Z:Ue(t);e(o||t,function(e,i){if(o&&(i=e,e=t[i]),pe(e)){u||(u=new $n);var f=i,c=u,a=n[f],l=t[f],s=c.get(l)||c.get(a);if(s)Tn(n,f,s);else{var s=r?r(a,l,f+"",n,t,c):Z,h=s===Z;h&&(s=l,Wo(l)||me(l)?s=Wo(a)?a:ce(a)?Pt(a):Qn(l):de(l)||ie(l)?s=ie(a)?ke(a):pe(a)?a:Qn(l):h=le(l)),
|
||||||
|
c.set(l,s),h&&mt(s,l,r,c),Tn(n,f,s)}}else f=r?r(n[i],e,i+"",n,t,u):Z,f===Z&&(f=e),Tn(n,i,f)})}}function jt(n,t,r){var e=-1,u=vr();return t=c(t.length?t:Array(1),function(n){return u(n)}),n=yt(n,function(n){return{a:c(t,function(t){return t(n)}),b:++e,c:n}}),y(n,function(n,t){var e;n:{e=-1;for(var u=n.a,o=t.a,i=u.length,f=r.length;++e<i;){var c=k(u[e],o[e]);if(c){e=f>e?c*("desc"==r[e]?-1:1):c;break n}}e=n.b-t.b}return e})}function wt(n,t){return n=Object(n),l(t,function(t,r){return r in n&&(t[r]=n[r]),
|
||||||
|
t},{})}function At(n,t){var r={};return ot(n,function(n,e){t(n)&&(r[e]=n)}),r}function Ot(n){return function(t){return null==t?Z:t[n]}}function Et(n){return function(t){return at(t,n)}}function kt(n,t,r){var e=-1,u=t.length,o=n;for(r&&(o=c(n,function(n){return r(n)}));++e<u;)for(var i=0,f=t[e],f=r?r(f):f;-1<(i=v(o,f,i));)o!==n&&du.call(o,i,1),du.call(n,i,1);return n}function It(n,t){for(var r=n?t.length:0,e=r-1;r--;){var u=t[r];if(e==r||u!=o){var o=u;if(U(u))du.call(n,u,1);else if(Er(u,n))delete n[u];else{
|
||||||
|
var u=Lt(u),i=Wr(n,u);null!=i&&delete i[Mr(u)]}}}}function Rt(n,t){return n+bu(Eu()*(t-n+1))}function St(n,t,r,e){t=Er(t,n)?[t+""]:Lt(t);for(var u=-1,o=t.length,i=o-1,f=n;null!=f&&++u<o;){var c=t[u];if(pe(f)){var a=r;if(u!=i){var l=f[c],a=e?e(l,c,f):Z;a===Z&&(a=null==l?U(t[u+1])?[]:{}:l)}Vn(f,c,a)}f=f[c]}return n}function Wt(n,t,r){var e=-1,u=n.length;for(0>t&&(t=-t>u?0:u+t),r=r>u?u:r,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Array(u);++e<u;)r[e]=n[e+t];return r}function Ct(n,t){var r;return Nu(n,function(n,e,u){
|
||||||
|
return r=t(n,e,u),!r}),!!r}function Ut(n,t,r){var e=0,u=n?n.length:e;if(typeof t=="number"&&t===t&&2147483647>=u){for(;u>e;){var o=e+u>>>1,i=n[o];(r?t>=i:t>i)&&null!==i?e=o+1:u=o}return u}return zt(n,t,Ne,r)}function zt(n,t,r,e){t=r(t);for(var u=0,o=n?n.length:0,i=t!==t,f=null===t,c=t===Z;o>u;){var a=bu((u+o)/2),l=r(n[a]),s=l!==Z,h=l===l;(i?h||e:f?h&&s&&(e||null!=l):c?h&&(e||s):null==l?0:e?t>=l:t>l)?u=a+1:o=a}return Au(o,4294967294)}function Bt(n,t){for(var r=0,e=n.length,u=n[0],o=t?t(u):u,i=o,f=0,c=[u];++r<e;)u=n[r],
|
||||||
|
o=t?t(u):u,ue(o,i)||(i=o,c[++f]=u);return c}function Lt(n){return Wo(n)?n:Cr(n)}function $t(n,t,r){var e=-1,u=i,o=n.length,c=true,a=[],l=a;if(r)c=false,u=f;else if(o<200)l=t?[]:a;else{if(u=t?null:Tu(n))return $(u);c=false,u=Ln,l=new Bn}n:for(;++e<o;){var s=n[e],h=t?t(s):s;if(c&&h===h){for(var p=l.length;p--;)if(l[p]===h)continue n;t&&l.push(h),a.push(s)}else u(l,h,r)||(l!==a&&l.push(h),a.push(s))}return a}function Ft(n,t,r,e){for(var u=n.length,o=e?u:-1;(e?o--:++o<u)&&t(n[o],o,n););return r?Wt(n,e?0:o,e?o+1:u):Wt(n,e?o+1:0,e?u:o);
|
||||||
|
}function Mt(n,t){var r=n;return r instanceof wn&&(r=r.value()),l(t,function(n,t){return t.func.apply(t.thisArg,a([n],t.args))},r)}function Nt(n,t,r){for(var e=-1,u=n.length;++e<u;)var o=o?a(tt(o,n[e],t,r),tt(n[e],o,t,r)):n[e];return o&&o.length?$t(o,t,r):[]}function Zt(n){var t=new n.constructor(n.byteLength);return new au(t).set(new au(n)),t}function Dt(n,t,r){for(var e=r.length,u=-1,o=wu(n.length-e,0),i=-1,f=t.length,c=Array(f+o);++i<f;)c[i]=t[i];for(;++u<e;)c[r[u]]=n[u];for(;o--;)c[i++]=n[u++];
|
||||||
|
return c}function qt(n,t,r){for(var e=-1,u=r.length,o=-1,i=wu(n.length-u,0),f=-1,c=t.length,a=Array(i+c);++o<i;)a[o]=n[o];for(i=o;++f<c;)a[i+f]=t[f];for(;++e<u;)a[i+r[e]]=n[o++];return a}function Pt(n,t){var r=-1,e=n.length;for(t||(t=Array(e));++r<e;)t[r]=n[r];return t}function Tt(n,t,r){return Kt(n,t,r)}function Kt(n,t,r,e){r||(r={});for(var u=-1,o=t.length;++u<o;){var i=t[u],f=e?e(r[i],n[i],i,r,n):n[i];Vn(r,i,f)}return r}function Gt(n,t){return Tt(n,Vu(n),t)}function Vt(n,t){return function(r,e){
|
||||||
|
var u=t?t():{};if(e=vr(e),Wo(r))for(var o=-1,i=r.length;++o<i;){var f=r[o];n(u,f,e(f),r)}else Nu(r,function(t,r,o){n(u,t,e(t),o)});return u}}function Jt(n){return ee(function(t,r){var e=-1,u=r.length,o=u>1?r[u-1]:Z,i=u>2?r[2]:Z,o=typeof o=="function"?(u--,o):Z;for(i&&Or(r[0],r[1],i)&&(o=3>u?Z:o,u=1),t=Object(t);++e<u;)(i=r[e])&&n(t,i,o);return t})}function Yt(n,t){return function(r,e){if(null==r)return r;if(!fe(r))return n(r,e);for(var u=r.length,o=t?u:-1,i=Object(r);(t?o--:++o<u)&&false!==e(i[o],o,i););
|
||||||
|
return r}}function Ht(n){return function(t,r,e){var u=-1,o=Object(t);e=e(t);for(var i=e.length;i--;){var f=e[n?i:++u];if(false===r(o[f],f,o))break}return t}}function Qt(n,t,r){function e(){return(this&&this!==Kn&&this instanceof e?o:n).apply(u?r:this,arguments)}var u=1&t,o=tr(n);return e}function Xt(n){return function(t){t=Ie(t);var r=En.test(t)?t.match(On):Z,e=r?r[0]:t.charAt(0);return t=r?r.slice(1).join(""):t.slice(1),e[n]()+t}}function nr(n){return function(t){return l(Me($e(t)),n,"")}}function tr(n){
|
||||||
|
return function(){var t=arguments;switch(t.length){case 0:return new n;case 1:return new n(t[0]);case 2:return new n(t[0],t[1]);case 3:return new n(t[0],t[1],t[2]);case 4:return new n(t[0],t[1],t[2],t[3]);case 5:return new n(t[0],t[1],t[2],t[3],t[4]);case 6:return new n(t[0],t[1],t[2],t[3],t[4],t[5]);case 7:return new n(t[0],t[1],t[2],t[3],t[4],t[5],t[6])}var r=Mu(n.prototype),t=n.apply(r,t);return pe(t)?t:r}}function rr(n,t,e){function u(){for(var i=arguments.length,f=i,c=Array(i),a=this&&this!==Kn&&this instanceof u?o:n,l=u.placeholder;f--;)c[f]=arguments[f];
|
||||||
|
return f=3>i&&c[0]!==l&&c[i-1]!==l?[]:L(c,l),i-=f.length,e>i?ar(n,t,ur,l,Z,c,f,Z,Z,e-i):r(a,this,c)}var o=tr(n);return u}function er(n){return ee(function(t){t=ut(t);var r=t.length,e=r,u=jn.prototype.thru;for(n&&t.reverse();e--;){var o=t[e];if(typeof o!="function")throw new He("Expected a function");if(u&&!i&&"wrapper"==gr(o))var i=new jn([],true)}for(e=i?e:r;++e<r;)var o=t[e],u=gr(o),f="wrapper"==u?Ku(o):Z,i=f&&Ir(f[0])&&424==f[1]&&!f[4].length&&1==f[9]?i[gr(f[0])].apply(i,f[3]):1==o.length&&Ir(o)?i[u]():i.thru(o);
|
||||||
|
return function(){var n=arguments,e=n[0];if(i&&1==n.length&&Wo(e)&&e.length>=200)return i.plant(e).value();for(var u=0,n=r?t[u].apply(this,n):e;++u<r;)n=t[u].call(this,n);return n}})}function ur(n,t,r,e,u,o,i,f,c,a){function l(){for(var y=arguments.length,b=y,x=Array(y);b--;)x[b]=arguments[b];if(e&&(x=Dt(x,e,u)),o&&(x=qt(x,o,i)),_||g){var b=l.placeholder,m=L(x,b),y=y-m.length;if(a>y)return ar(n,t,ur,b,r,x,m,f,c,a-y)}if(y=h?r:this,b=p?y[n]:n,f)for(var m=x.length,j=Au(f.length,m),w=Pt(x);j--;){var A=f[j];
|
||||||
|
x[j]=U(A,m)?w[A]:Z}else v&&x.length>1&&x.reverse();return s&&x.length>c&&(x.length=c),this&&this!==Kn&&this instanceof l&&(b=d||tr(b)),b.apply(y,x)}var s=128&t,h=1&t,p=2&t,_=8&t,g=16&t,v=512&t,d=p?Z:tr(n);return l}function or(n){return ee(function(t){return t=c(ut(t),vr()),ee(function(e){var u=this;return n(t,function(n){return r(n,u,e)})})})}function ir(n,t,r){return t=Ae(t),n=F(n),t&&t>n?(t-=n,r=r===Z?" ":r+"",n=Fe(r,yu(t/F(r))),En.test(r)?n.match(On).slice(0,t).join(""):n.slice(0,t)):""}function fr(n,t,e,u){
|
||||||
|
function o(){for(var t=-1,c=arguments.length,a=-1,l=u.length,s=Array(l+c),h=this&&this!==Kn&&this instanceof o?f:n;++a<l;)s[a]=u[a];for(;c--;)s[a++]=arguments[++t];return r(h,i?e:this,s)}var i=1&t,f=tr(n);return o}function cr(n){return function(t,r,e){e&&typeof e!="number"&&Or(t,r,e)&&(r=e=Z),t=Ee(t),t=t===t?t:0,r===Z?(r=t,t=0):r=Ee(r)||0,e=e===Z?r>t?1:-1:Ee(e)||0;var u=-1;r=wu(yu((r-t)/(e||1)),0);for(var o=Array(r);r--;)o[n?r:++u]=t,t+=e;return o}}function ar(n,t,r,e,u,o,i,f,c,a){var l=8&t;f=f?Pt(f):Z;
|
||||||
|
var s=l?i:Z;i=l?Z:i;var h=l?o:Z;return o=l?Z:o,t=(t|(l?32:64))&~(l?64:32),4&t||(t&=-4),t=[n,t,u,h,s,o,i,f,c,a],r=r.apply(Z,t),Ir(n)&&Ju(r,t),r.placeholder=e,r}function lr(n){var t=Je[n];return function(n,r){if(n=Ee(n),r=Ae(r)){var e=(Ie(n)+"e").split("e"),e=t(e[0]+"e"+(+e[1]+r)),e=(Ie(e)+"e").split("e");return+(e[0]+"e"+(+e[1]-r))}return t(n)}}function sr(n,t,r,e,u,o,i,f){var c=2&t;if(!c&&typeof n!="function")throw new He("Expected a function");var a=e?e.length:0;if(a||(t&=-97,e=u=Z),i=i===Z?i:wu(Ae(i),0),
|
||||||
|
f=f===Z?f:Ae(f),a-=u?u.length:0,64&t){var l=e,s=u;e=u=Z}var h=c?Z:Ku(n);return o=[n,t,r,e,u,l,s,o,i,f],h&&(r=o[1],n=h[1],t=r|n,e=128==n&&8==r||128==n&&256==r&&h[8]>=o[7].length||384==n&&h[8]>=h[7].length&&8==r,131>t||e)&&(1&n&&(o[2]=h[2],t|=1&r?0:4),(r=h[3])&&(e=o[3],o[3]=e?Dt(e,r,h[4]):Pt(r),o[4]=e?L(o[3],"__lodash_placeholder__"):Pt(h[4])),(r=h[5])&&(e=o[5],o[5]=e?qt(e,r,h[6]):Pt(r),o[6]=e?L(o[5],"__lodash_placeholder__"):Pt(h[6])),(r=h[7])&&(o[7]=Pt(r)),128&n&&(o[8]=null==o[8]?h[8]:Au(o[8],h[8])),
|
||||||
|
null==o[9]&&(o[9]=h[9]),o[0]=h[0],o[1]=t),n=o[0],t=o[1],r=o[2],e=o[3],u=o[4],f=o[9]=null==o[9]?c?0:n.length:wu(o[9]-a,0),!f&&24&t&&(t&=-25),(h?Pu:Ju)(t&&1!=t?8==t||16==t?rr(n,t,f):32!=t&&33!=t||u.length?ur.apply(Z,o):fr(n,t,r,e):Qt(n,t,r),o)}function hr(n,t,r,e,u,o){var i=-1,f=2&u,c=1&u,a=n.length,l=t.length;if(!(a==l||f&&l>a))return false;if(l=o.get(n))return l==t;for(l=true,o.set(n,t);++i<a;){var s=n[i],p=t[i];if(e)var _=f?e(p,s,i,t,n,o):e(s,p,i,n,t,o);if(_!==Z){if(_)continue;l=false;break}if(c){if(!h(t,function(n){
|
||||||
|
return s===n||r(s,n,e,u,o)})){l=false;break}}else if(s!==p&&!r(s,p,e,u,o)){l=false;break}}return o["delete"](n),l}function pr(n,t,r,e,u,o){switch(r){case"[object ArrayBuffer]":if(n.byteLength!=t.byteLength||!e(new au(n),new au(t)))break;return true;case"[object Boolean]":case"[object Date]":return+n==+t;case"[object Error]":return n.name==t.name&&n.message==t.message;case"[object Number]":return n!=+n?t!=+t:n==+t;case"[object RegExp]":case"[object String]":return n==t+"";case"[object Map]":var i=B;case"[object Set]":
|
||||||
|
return i||(i=$),(2&o||n.size==t.size)&&e(i(n),i(t),u,1|o);case"[object Symbol]":return!!fu&&Lu.call(n)==Lu.call(t)}return false}function _r(n,t,r,e,u,o){var i=2&u,f=1&u,c=Ce(n),a=c.length,l=Ce(t);if(a!=l.length&&!i)return false;for(var s=a;s--;){var h=c[s];if(!(i?h in t:lt(t,h))||!f&&h!=l[s])return false}if(h=o.get(n))return h==t;for(f=true,o.set(n,t),l=i;++s<a;){var h=c[s],p=n[h],_=t[h];if(e)var g=i?e(_,p,h,t,n,o):e(p,_,h,n,t,o);if(g===Z?p!==_&&!r(p,_,e,u,o):!g){f=false;break}l||(l="constructor"==h)}return f&&!l&&(r=n.constructor,
|
||||||
|
e=t.constructor,r!=e&&"constructor"in n&&"constructor"in t&&!(typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)&&(f=false)),o["delete"](n),f}function gr(n){for(var t=n.name+"",r=Fu[t],e=r?r.length:0;e--;){var u=r[e],o=u.func;if(null==o||o==n)return u.name}return t}function vr(){var n=dn.iteratee||Ze,n=n===Ze?vt:n;return arguments.length?n(arguments[0],arguments[1]):n}function dr(n){n=ze(n);for(var t=n.length;t--;){var r=n[t][1];n[t][2]=r===r&&!pe(r)}return n}function yr(n,t){
|
||||||
|
var r=null==n?Z:n[t];return ge(r)?r:Z}function br(n){return uu.call(n)}function xr(n,t,r){if(null==n)return false;var e=r(n,t);return e||Er(t)||(t=Lt(t),n=Wr(n,t),null!=n&&(t=Mr(t),e=r(n,t))),e||he(n&&n.length)&&U(t,n.length)&&(Wo(n)||be(n)||ie(n))}function mr(n){var t=n.length,r=n.constructor(t);return t&&"string"==typeof n[0]&&tu.call(n,"index")&&(r.index=n.index,r.input=n.input),r}function jr(n){return n=n.constructor,Mu(le(n)?n.prototype:Z)}function wr(r,e,u){var o=r.constructor;switch(e){case"[object ArrayBuffer]":
|
||||||
|
return Zt(r);case"[object Boolean]":case"[object Date]":return new o(+r);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":case"[object Uint16Array]":case"[object Uint32Array]":return e=r.buffer,new r.constructor(u?Zt(e):e,r.byteOffset,r.length);case"[object Map]":return u=r.constructor,l(B(r),n,new u);case"[object Number]":case"[object String]":return new o(r);
|
||||||
|
case"[object RegExp]":return u=new r.constructor(r.source,sn.exec(r)),u.lastIndex=r.lastIndex,u;case"[object Set]":return u=r.constructor,l($(r),t,new u);case"[object Symbol]":return fu?Object(Lu.call(r)):{}}}function Ar(n){var t=n?n.length:Z;return he(t)&&(Wo(n)||be(n)||ie(n))?x(t,String):null}function Or(n,t,r){if(!pe(r))return false;var e=typeof t;return("number"==e?fe(r)&&U(t,r.length):"string"==e&&t in r)?ue(r[t],n):false}function Er(n,t){return typeof n=="number"?true:!Wo(n)&&(tn.test(n)||!nn.test(n)||null!=t&&n in Object(t));
|
||||||
|
}function kr(n){var t=typeof n;return"number"==t||"boolean"==t||"string"==t&&"__proto__"!==n||null==n}function Ir(n){var t=gr(n),r=dn[t];return typeof r=="function"&&t in wn.prototype?n===r?true:(t=Ku(r),!!t&&n===t[0]):false}function Rr(n){var t=n&&n.constructor;return n===(typeof t=="function"&&t.prototype||Xe)}function Sr(n,t,r,e,u,o){return pe(n)&&pe(t)&&(o.set(t,n),mt(n,t,Sr,o)),n===Z?Qn(t):n}function Wr(n,t){return 1==t.length?n:Re(n,Wt(t,0,-1))}function Cr(n){var t=[];return Ie(n).replace(rn,function(n,r,e,u){
|
||||||
|
t.push(e?u.replace(an,"$1"):r||n)}),t}function Ur(n){return ce(n)?n:[]}function zr(n){return typeof n=="function"?n:Ne}function Br(n){if(n instanceof wn)return n.clone();var t=new jn(n.__wrapped__,n.__chain__);return t.__actions__=Pt(n.__actions__),t.__index__=n.__index__,t.__values__=n.__values__,t}function Lr(n,t,r){var e=n?n.length:0;return e?(t=r||t===Z?1:Ae(t),Wt(n,0>t?0:t,e)):[]}function $r(n,t,r){var e=n?n.length:0;return e?(t=r||t===Z?1:Ae(t),t=e-t,Wt(n,0,0>t?0:t)):[]}function Fr(n){return n?n[0]:Z;
|
||||||
|
}function Mr(n){var t=n?n.length:0;return t?n[t-1]:Z}function Nr(n,t){return n&&n.length&&t&&t.length?kt(n,t):n}function Zr(n){return n?ku.call(n):n}function Dr(n){if(!n||!n.length)return[];var t=0;return n=o(n,function(n){return ce(n)?(t=wu(n.length,t),true):void 0}),x(t,function(t){return c(n,Ot(t))})}function qr(n,t){if(!n||!n.length)return[];var e=Dr(n);return null==t?e:c(e,function(n){return r(t,Z,n)})}function Pr(n){return n=dn(n),n.__chain__=true,n}function Tr(n,t){return t(n)}function Kr(){return this;
|
||||||
|
}function Gr(n,t){return typeof t=="function"&&Wo(n)?e(n,t):Nu(n,zr(t))}function Vr(n,t){var r;if(typeof t=="function"&&Wo(n)){for(r=n.length;r--&&false!==t(n[r],r,n););r=n}else r=Zu(n,zr(t));return r}function Jr(n,t){var r=-1,e=we(n),u=e.length,o=u-1;for(t=Hn(Ae(t),0,u);++r<t;){var u=Rt(r,o),i=e[u];e[u]=e[r],e[r]=i}return e.length=t,e}function Yr(n){if(null==n)return 0;if(fe(n)){var t=n.length;return t&&be(n)?F(n):t}return Ce(n).length}function Hr(n,t,r){return t=r?Z:t,t=n&&null==t?n.length:t,sr(n,128,Z,Z,Z,Z,t);
|
||||||
|
}function Qr(n,t){var r;if(typeof t!="function")throw new He("Expected a function");return n=Ae(n),function(){return 0<--n&&(r=t.apply(this,arguments)),1>=n&&(t=Z),r}}function Xr(n,t,r){return t=r?Z:t,n=sr(n,8,Z,Z,Z,Z,Z,t),n.placeholder=Xr.placeholder,n}function ne(n,t,r){return t=r?Z:t,n=sr(n,16,Z,Z,Z,Z,Z,t),n.placeholder=ne.placeholder,n}function te(n,t,r){function e(){p&&lu(p),a&&lu(a),g=0,c=a=h=p=_=Z}function u(t,r){r&&lu(r),a=p=_=Z,t&&(g=jo(),l=n.apply(h,c),p||a||(c=h=Z))}function o(){var n=t-(jo()-s);
|
||||||
|
0>=n||n>t?u(_,a):p=vu(o,n)}function i(){u(y,p)}function f(){if(c=arguments,s=jo(),h=this,_=y&&(p||!v),false===d)var r=v&&!p;else{a||v||(g=s);var e=d-(s-g),u=0>=e||e>d;u?(a&&(a=lu(a)),g=s,l=n.apply(h,c)):a||(a=vu(i,e))}return u&&p?p=lu(p):p||t===d||(p=vu(o,t)),r&&(u=true,l=n.apply(h,c)),!u||p||a||(c=h=Z),l}var c,a,l,s,h,p,_,g=0,v=false,d=false,y=true;if(typeof n!="function")throw new He("Expected a function");return t=Ee(t)||0,pe(r)&&(v=!!r.leading,d="maxWait"in r&&wu(Ee(r.maxWait)||0,t),y="trailing"in r?!!r.trailing:y),
|
||||||
|
f.cancel=e,f.flush=function(){return(p&&_||a&&y)&&(l=n.apply(h,c)),e(),l},f}function re(n,t){function r(){var e=arguments,u=t?t.apply(this,e):e[0],o=r.cache;return o.has(u)?o.get(u):(e=n.apply(this,e),r.cache=o.set(u,e),e)}if(typeof n!="function"||t&&typeof t!="function")throw new He("Expected a function");return r.cache=new re.Cache,r}function ee(n,t){if(typeof n!="function")throw new He("Expected a function");return t=wu(t===Z?n.length-1:Ae(t),0),function(){for(var e=arguments,u=-1,o=wu(e.length-t,0),i=Array(o);++u<o;)i[u]=e[t+u];
|
||||||
|
switch(t){case 0:return n.call(this,i);case 1:return n.call(this,e[0],i);case 2:return n.call(this,e[0],e[1],i)}for(o=Array(t+1),u=-1;++u<t;)o[u]=e[u];return o[t]=i,r(n,this,o)}}function ue(n,t){return n===t||n!==n&&t!==t}function oe(n,t){return n>t}function ie(n){return ce(n)&&tu.call(n,"callee")&&(!gu.call(n,"callee")||"[object Arguments]"==uu.call(n))}function fe(n){return null!=n&&!(typeof n=="function"&&le(n))&&he(Gu(n))}function ce(n){return _e(n)&&fe(n)}function ae(n){return _e(n)&&typeof n.message=="string"&&"[object Error]"==uu.call(n);
|
||||||
|
}function le(n){return n=pe(n)?uu.call(n):"","[object Function]"==n||"[object GeneratorFunction]"==n}function se(n){return typeof n=="number"&&n==Ae(n)}function he(n){return typeof n=="number"&&n>-1&&0==n%1&&9007199254740991>=n}function pe(n){var t=typeof n;return!!n&&("object"==t||"function"==t)}function _e(n){return!!n&&typeof n=="object"}function ge(n){return null==n?false:le(n)?iu.test(nu.call(n)):_e(n)&&(C(n)?iu:gn).test(n)}function ve(n){return typeof n=="number"||_e(n)&&"[object Number]"==uu.call(n);
|
||||||
|
}function de(n){if(!_e(n)||"[object Object]"!=uu.call(n)||C(n))return false;var t=Xe;return typeof n.constructor=="function"&&(t=hu(n)),null===t?true:(n=t.constructor,typeof n=="function"&&n instanceof n&&nu.call(n)==eu)}function ye(n){return pe(n)&&"[object RegExp]"==uu.call(n)}function be(n){return typeof n=="string"||!Wo(n)&&_e(n)&&"[object String]"==uu.call(n)}function xe(n){return typeof n=="symbol"||_e(n)&&"[object Symbol]"==uu.call(n)}function me(n){return _e(n)&&he(n.length)&&!!Wn[uu.call(n)]}function je(n,t){
|
||||||
|
return t>n}function we(n){if(!n)return[];if(fe(n))return be(n)?n.match(On):Pt(n);if(_u&&n[_u])return z(n[_u]());var t=br(n);return("[object Map]"==t?B:"[object Set]"==t?$:Be)(n)}function Ae(n){if(!n)return 0===n?n:0;if(n=Ee(n),n===D||n===-D)return 1.7976931348623157e308*(0>n?-1:1);var t=n%1;return n===n?t?n-t:n:0}function Oe(n){return n?Hn(Ae(n),0,4294967295):0}function Ee(n){if(pe(n)&&(n=le(n.valueOf)?n.valueOf():n,n=pe(n)?n+"":n),typeof n!="string")return 0===n?n:+n;n=n.replace(on,"");var t=_n.test(n);
|
||||||
|
return t||vn.test(n)?Mn(n.slice(2),t?2:8):pn.test(n)?q:+n}function ke(n){return Tt(n,Ue(n))}function Ie(n){if(typeof n=="string")return n;if(null==n)return"";if(xe(n))return fu?$u.call(n):"";var t=n+"";return"0"==t&&1/n==-D?"-0":t}function Re(n,t,r){return n=null==n?Z:at(n,t),n===Z?r:n}function Se(n,t){return xr(n,t,lt)}function We(n,t){return xr(n,t,st)}function Ce(n){var t=Rr(n);if(!t&&!fe(n))return ju(Object(n));var r,e=Ar(n),u=!!e,e=e||[],o=e.length;for(r in n)!lt(n,r)||u&&("length"==r||U(r,o))||t&&"constructor"==r||e.push(r);
|
||||||
|
return e}function Ue(n){for(var t=-1,r=Rr(n),e=dt(n),u=e.length,o=Ar(n),i=!!o,o=o||[],f=o.length;++t<u;){var c=e[t];i&&("length"==c||U(c,f))||"constructor"==c&&(r||!tu.call(n,c))||o.push(c)}return o}function ze(n){return m(n,Ce(n))}function Be(n){return n?w(n,Ce(n)):[]}function Le(n){return Vo(Ie(n).toLowerCase())}function $e(n){return(n=Ie(n))&&n.replace(yn,I).replace(An,"")}function Fe(n,t){n=Ie(n),t=Ae(t);var r="";if(!n||1>t||t>9007199254740991)return r;do t%2&&(r+=n),t=bu(t/2),n+=n;while(t);return r;
|
||||||
|
}function Me(n,t,r){return n=Ie(n),t=r?Z:t,t===Z&&(t=Rn.test(n)?In:kn),n.match(t)||[]}function Ne(n){return n}function Ze(n){return _e(n)&&!Wo(n)?De(n):vt(n)}function De(n){return bt(Qn(n,true))}function qe(n,t,r){var u=Ce(t),o=ct(t,u);null!=r||pe(t)&&(o.length||!u.length)||(r=t,t=n,n=this,o=ct(t,Ce(t)));var i=pe(r)&&"chain"in r?r.chain:true,f=le(n);return e(o,function(r){var e=t[r];n[r]=e,f&&(n.prototype[r]=function(){var t=this.__chain__;if(i||t){var r=n(this.__wrapped__);return(r.__actions__=Pt(this.__actions__)).push({
|
||||||
|
func:e,args:arguments,thisArg:n}),r.__chain__=t,r}return e.apply(n,a([this.value()],arguments))})}),n}function Pe(){}function Te(n){return Er(n)?Ot(n):Et(n)}function Ke(n){return n&&n.length?b(n,Ne):Z}E=E?Gn.defaults({},E,Gn.pick(Kn,Sn)):Kn;var Ge=E.Date,Ve=E.Error,Je=E.Math,Ye=E.RegExp,He=E.TypeError,Qe=E.Array.prototype,Xe=E.Object.prototype,nu=E.Function.prototype.toString,tu=Xe.hasOwnProperty,ru=0,eu=nu.call(Object),uu=Xe.toString,ou=Kn._,iu=Ye("^"+nu.call(tu).replace(en,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),fu=E.Symbol,cu=E.f,au=E.Uint8Array,lu=E.clearTimeout,su=cu?cu.g:Z,hu=Object.getPrototypeOf,pu=Object.getOwnPropertySymbols,_u=typeof(_u=fu&&fu.iterator)=="symbol"?_u:Z,gu=Xe.propertyIsEnumerable,vu=E.setTimeout,du=Qe.splice,yu=Je.ceil,bu=Je.floor,xu=E.isFinite,mu=Qe.join,ju=Object.keys,wu=Je.max,Au=Je.min,Ou=E.parseInt,Eu=Je.random,ku=Qe.reverse,Iu=yr(E,"Map"),Ru=yr(E,"Set"),Su=yr(E,"WeakMap"),Wu=yr(Object,"create"),Cu=Su&&new Su,Uu=Iu?nu.call(Iu):"",zu=Ru?nu.call(Ru):"",Bu=fu?fu.prototype:Z,Lu=fu?Bu.valueOf:Z,$u=fu?Bu.toString:Z,Fu={};
|
||||||
|
dn.templateSettings={escape:H,evaluate:Q,interpolate:X,variable:"",imports:{_:dn}};var Mu=function(){function n(){}return function(t){if(pe(t)){n.prototype=t;var r=new n;n.prototype=Z}return r||{}}}(),Nu=Yt(it),Zu=Yt(ft,true),Du=Ht(),qu=Ht(true);su&&!gu.call({valueOf:1},"valueOf")&&(dt=function(n){return z(su(n))});var Pu=Cu?function(n,t){return Cu.set(n,t),n}:Ne,Tu=Ru&&2===new Ru([1,2]).size?function(n){return new Ru(n)}:Pe,Ku=Cu?function(n){return Cu.get(n)}:Pe,Gu=Ot("length"),Vu=pu||function(){return[];
|
||||||
|
};(Iu&&"[object Map]"!=br(new Iu)||Ru&&"[object Set]"!=br(new Ru))&&(br=function(n){var t=uu.call(n);if(n="[object Object]"==t?n.constructor:null,n=typeof n=="function"?nu.call(n):""){if(n==Uu)return"[object Map]";if(n==zu)return"[object Set]"}return t});var Ju=function(){var n=0,t=0;return function(r,e){var u=jo(),o=16-(u-t);if(t=u,o>0){if(150<=++n)return r}else n=0;return Pu(r,e)}}(),Yu=ee(function(n,t){t=ut(t);for(var r=Wo(n)?n:[Object(n)],e=t,u=-1,o=r.length,i=-1,f=e.length,c=Array(o+f);++u<o;)c[u]=r[u];
|
||||||
|
for(;++i<f;)c[u++]=e[i];return c}),Hu=ee(function(n,t){return ce(n)?tt(n,ut(t,false,true)):[]}),Qu=ee(function(n,t){var r=Mr(t);return ce(r)&&(r=Z),ce(n)?tt(n,ut(t,false,true),vr(r)):[]}),Xu=ee(function(n,t){var r=Mr(t);return ce(r)&&(r=Z),ce(n)?tt(n,ut(t,false,true),Z,r):[]}),no=ee(function(n){var t=c(n,Ur);return t.length&&t[0]===n[0]?ht(t):[]}),to=ee(function(n){var t=Mr(n),r=c(n,Ur);return t===Mr(r)?t=Z:r.pop(),r.length&&r[0]===n[0]?ht(r,vr(t)):[]}),ro=ee(function(n){var t=Mr(n),r=c(n,Ur);return t===Mr(r)?t=Z:r.pop(),
|
||||||
|
r.length&&r[0]===n[0]?ht(r,Z,t):[]}),eo=ee(Nr),uo=ee(function(n,t){t=c(ut(t),String);var r=Yn(n,t);return It(n,t.sort(k)),r}),oo=ee(function(n){return $t(ut(n,false,true))}),io=ee(function(n){var t=Mr(n);return ce(t)&&(t=Z),$t(ut(n,false,true),vr(t))}),fo=ee(function(n){var t=Mr(n);return ce(t)&&(t=Z),$t(ut(n,false,true),Z,t)}),co=ee(function(n,t){return ce(n)?tt(n,t):[]}),ao=ee(function(n){return Nt(o(n,ce))}),lo=ee(function(n){var t=Mr(n);return ce(t)&&(t=Z),Nt(o(n,ce),vr(t))}),so=ee(function(n){var t=Mr(n);return ce(t)&&(t=Z),
|
||||||
|
Nt(o(n,ce),Z,t)}),ho=ee(Dr),po=ee(function(n){var t=n.length,t=t>1?n[t-1]:Z,t=typeof t=="function"?(n.pop(),t):Z;return qr(n,t)}),_o=ee(function(n){function t(t){return Yn(t,n)}n=ut(n);var r=n.length,e=r?n[0]:0,u=this.__wrapped__;return 1>=r&&!this.__actions__.length&&u instanceof wn&&U(e)?(u=u.slice(e,+e+(r?1:0)),u.__actions__.push({func:Tr,args:[t],thisArg:Z}),new jn(u,this.__chain__).thru(function(n){return r&&!n.length&&n.push(Z),n})):this.thru(t)}),go=Vt(function(n,t,r){tu.call(n,r)?++n[r]:n[r]=1;
|
||||||
|
}),vo=Vt(function(n,t,r){tu.call(n,r)?n[r].push(t):n[r]=[t]}),yo=ee(function(n,t,e){var u=-1,o=typeof t=="function",i=Er(t),f=fe(n)?Array(n.length):[];return Nu(n,function(n){var c=o?t:i&&null!=n?n[t]:Z;f[++u]=c?r(c,n,e):pt(n,t,e)}),f}),bo=Vt(function(n,t,r){n[r]=t}),xo=Vt(function(n,t,r){n[r?0:1].push(t)},function(){return[[],[]]}),mo=ee(function(n,t){if(null==n)return[];var r=t.length;return r>1&&Or(n,t[0],t[1])?t=[]:r>2&&Or(t[0],t[1],t[2])&&(t.length=1),jt(n,ut(t),[])}),jo=Ge.now,wo=ee(function(n,t,r){
|
||||||
|
var e=1;if(r.length)var u=L(r,wo.placeholder),e=32|e;return sr(n,e,t,r,u)}),Ao=ee(function(n,t,r){var e=3;if(r.length)var u=L(r,Ao.placeholder),e=32|e;return sr(t,e,n,r,u)}),Oo=ee(function(n,t){return nt(n,1,t)}),Eo=ee(function(n,t,r){return nt(n,Ee(t)||0,r)}),ko=ee(function(n,t){t=c(ut(t),vr());var e=t.length;return ee(function(u){for(var o=-1,i=Au(u.length,e);++o<i;)u[o]=t[o].call(this,u[o]);return r(n,this,u)})}),Io=ee(function(n,t){var r=L(t,Io.placeholder);return sr(n,32,Z,t,r)}),Ro=ee(function(n,t){
|
||||||
|
var r=L(t,Ro.placeholder);return sr(n,64,Z,t,r)}),So=ee(function(n,t){return sr(n,256,Z,Z,Z,ut(t))}),Wo=Array.isArray,Co=Jt(function(n,t){Tt(t,Ce(t),n)}),Uo=Jt(function(n,t){Tt(t,Ue(t),n)}),zo=Jt(function(n,t,r){Kt(t,Ue(t),n,r)}),Bo=Jt(function(n,t,r){Kt(t,Ce(t),n,r)}),Lo=ee(function(n,t){return Yn(n,ut(t))}),$o=ee(function(n){return n.push(Z,Pn),r(zo,Z,n)}),Fo=ee(function(n){return n.push(Z,Sr),r(Zo,Z,n)}),Mo=ee(pt),No=Jt(function(n,t){mt(n,t)}),Zo=Jt(function(n,t,r){mt(n,t,r)}),Do=ee(function(n,t){
|
||||||
|
return null==n?{}:(t=c(ut(t),String),wt(n,tt(Ue(n),t)))}),qo=ee(function(n,t){return null==n?{}:wt(n,ut(t))}),Po=nr(function(n,t,r){return t=t.toLowerCase(),n+(r?Le(t):t)}),To=nr(function(n,t,r){return n+(r?"-":"")+t.toLowerCase()}),Ko=nr(function(n,t,r){return n+(r?" ":"")+t.toLowerCase()}),Go=Xt("toLowerCase"),Vo=Xt("toUpperCase"),Jo=nr(function(n,t,r){return n+(r?"_":"")+t.toLowerCase()}),Yo=nr(function(n,t,r){return n+(r?" ":"")+Le(t)}),Ho=nr(function(n,t,r){return n+(r?" ":"")+t.toUpperCase();
|
||||||
|
}),Qo=ee(function(n,t){try{return r(n,Z,t)}catch(e){return ae(e)?e:new Ve(e)}}),Xo=ee(function(n,t){return e(ut(t),function(t){n[t]=wo(n[t],n)}),n}),ni=er(),ti=er(true),ri=ee(function(n,t){return function(r){return pt(r,n,t)}}),ei=ee(function(n,t){return function(r){return pt(n,r,t)}}),ui=or(c),oi=or(u),ii=or(h),fi=cr(),ci=cr(true),ai=lr("ceil"),li=lr("floor"),si=lr("round");return dn.prototype=mn.prototype,jn.prototype=Mu(mn.prototype),jn.prototype.constructor=jn,wn.prototype=Mu(mn.prototype),wn.prototype.constructor=wn,
|
||||||
|
Un.prototype=Wu?Wu(null):Xe,zn.prototype.clear=function(){this.__data__={hash:new Un,map:Iu?new Iu:[],string:new Un}},zn.prototype["delete"]=function(n){var t=this.__data__;return kr(n)?(t=typeof n=="string"?t.string:t.hash,n=(Wu?t[n]!==Z:tu.call(t,n))&&delete t[n]):n=Iu?t.map["delete"](n):Nn(t.map,n),n},zn.prototype.get=function(n){var t=this.__data__;return kr(n)?(t=typeof n=="string"?t.string:t.hash,Wu?(n=t[n],n="__lodash_hash_undefined__"===n?Z:n):n=tu.call(t,n)?t[n]:Z):n=Iu?t.map.get(n):Zn(t.map,n),
|
||||||
|
n},zn.prototype.has=function(n){var t=this.__data__;return kr(n)?(t=typeof n=="string"?t.string:t.hash,n=Wu?t[n]!==Z:tu.call(t,n)):n=Iu?t.map.has(n):-1<Dn(t.map,n),n},zn.prototype.set=function(n,t){var r=this.__data__;return kr(n)?(typeof n=="string"?r.string:r.hash)[n]=Wu&&t===Z?"__lodash_hash_undefined__":t:Iu?r.map.set(n,t):qn(r.map,n,t),this},Bn.prototype.push=function(n){var t=this.__data__;kr(n)?(t=t.__data__,(typeof n=="string"?t.string:t.hash)[n]="__lodash_hash_undefined__"):t.set(n,"__lodash_hash_undefined__");
|
||||||
|
},$n.prototype.clear=function(){this.__data__={array:[],map:null}},$n.prototype["delete"]=function(n){var t=this.__data__,r=t.array;return r?Nn(r,n):t.map["delete"](n)},$n.prototype.get=function(n){var t=this.__data__,r=t.array;return r?Zn(r,n):t.map.get(n)},$n.prototype.has=function(n){var t=this.__data__,r=t.array;return r?-1<Dn(r,n):t.map.has(n)},$n.prototype.set=function(n,t){var r=this.__data__,e=r.array;return e&&(199>e.length?qn(e,n,t):(r.array=null,r.map=new zn(e))),(r=r.map)&&r.set(n,t),
|
||||||
|
this},re.Cache=zn,dn.after=function(n,t){if(typeof t!="function")throw new He("Expected a function");return n=Ae(n),function(){return 1>--n?t.apply(this,arguments):void 0}},dn.ary=Hr,dn.assign=Co,dn.assignIn=Uo,dn.assignInWith=zo,dn.assignWith=Bo,dn.at=Lo,dn.before=Qr,dn.bind=wo,dn.bindAll=Xo,dn.bindKey=Ao,dn.chain=Pr,dn.chunk=function(n,t){t=wu(Ae(t),0);var r=n?n.length:0;if(!r||1>t)return[];for(var e=0,u=-1,o=Array(yu(r/t));r>e;)o[++u]=Wt(n,e,e+=t);return o},dn.compact=function(n){for(var t=-1,r=n?n.length:0,e=-1,u=[];++t<r;){
|
||||||
|
var o=n[t];o&&(u[++e]=o)}return u},dn.concat=Yu,dn.cond=function(n){var t=n?n.length:0,e=vr();return n=t?c(n,function(n){if("function"!=typeof n[1])throw new He("Expected a function");return[e(n[0]),n[1]]}):[],ee(function(e){for(var u=-1;++u<t;){var o=n[u];if(r(o[0],this,e))return r(o[1],this,e)}})},dn.conforms=function(n){return Xn(Qn(n,true))},dn.constant=function(n){return function(){return n}},dn.countBy=go,dn.create=function(n,t){var r=Mu(n);return t?Jn(r,t):r},dn.curry=Xr,dn.curryRight=ne,dn.debounce=te,
|
||||||
|
dn.defaults=$o,dn.defaultsDeep=Fo,dn.defer=Oo,dn.delay=Eo,dn.difference=Hu,dn.differenceBy=Qu,dn.differenceWith=Xu,dn.drop=Lr,dn.dropRight=$r,dn.dropRightWhile=function(n,t){return n&&n.length?Ft(n,vr(t,3),true,true):[]},dn.dropWhile=function(n,t){return n&&n.length?Ft(n,vr(t,3),true):[]},dn.fill=function(n,t,r,e){var u=n?n.length:0;if(!u)return[];for(r&&typeof r!="number"&&Or(n,t,r)&&(r=0,e=u),u=n.length,r=Ae(r),0>r&&(r=-r>u?0:u+r),e=e===Z||e>u?u:Ae(e),0>e&&(e+=u),e=r>e?0:Oe(e);e>r;)n[r++]=t;return n},
|
||||||
|
dn.filter=function(n,t){return(Wo(n)?o:et)(n,vr(t,3))},dn.flatMap=function(n,t){return n&&n.length?ut(c(n,vr(t,3))):[]},dn.flatten=function(n){return n&&n.length?ut(n):[]},dn.flattenDeep=function(n){return n&&n.length?ut(n,true):[]},dn.flip=function(n){return sr(n,512)},dn.flow=ni,dn.flowRight=ti,dn.fromPairs=function(n){for(var t=-1,r=n?n.length:0,e={};++t<r;){var u=n[t];St(e,u[0],u[1])}return e},dn.functions=function(n){return null==n?[]:ct(n,Ce(n))},dn.functionsIn=function(n){return null==n?[]:ct(n,Ue(n));
|
||||||
|
},dn.groupBy=vo,dn.initial=function(n){return $r(n,1)},dn.intersection=no,dn.intersectionBy=to,dn.intersectionWith=ro,dn.invert=function(n,t,r){return l(Ce(n),function(e,u){var o=n[u];return t&&!r?tu.call(e,o)?e[o].push(u):e[o]=[u]:e[o]=u,e},{})},dn.invokeMap=yo,dn.iteratee=Ze,dn.keyBy=bo,dn.keys=Ce,dn.keysIn=Ue,dn.map=function(n,t){return(Wo(n)?c:yt)(n,vr(t,3))},dn.mapKeys=function(n,t){var r={};return t=vr(t,3),it(n,function(n,e,u){r[t(n,e,u)]=n}),r},dn.mapValues=function(n,t){var r={};return t=vr(t,3),
|
||||||
|
it(n,function(n,e,u){r[e]=t(n,e,u)}),r},dn.matches=De,dn.matchesProperty=function(n,t){return xt(n,Qn(t,true))},dn.memoize=re,dn.merge=No,dn.mergeWith=Zo,dn.method=ri,dn.methodOf=ei,dn.mixin=qe,dn.negate=function(n){if(typeof n!="function")throw new He("Expected a function");return function(){return!n.apply(this,arguments)}},dn.nthArg=function(n){return n=Ae(n),function(){return arguments[n]}},dn.omit=Do,dn.omitBy=function(n,t){return t=vr(t),At(n,function(n){return!t(n)})},dn.once=function(n){return Qr(2,n);
|
||||||
|
},dn.orderBy=function(n,t,r,e){return null==n?[]:(Wo(t)||(t=null==t?[]:[t]),r=e?Z:r,Wo(r)||(r=null==r?[]:[r]),jt(n,t,r))},dn.over=ui,dn.overArgs=ko,dn.overEvery=oi,dn.overSome=ii,dn.partial=Io,dn.partialRight=Ro,dn.partition=xo,dn.pick=qo,dn.pickBy=function(n,t){return null==n?{}:At(n,vr(t))},dn.property=Te,dn.propertyOf=function(n){return function(t){return null==n?Z:at(n,t)}},dn.pull=eo,dn.pullAll=Nr,dn.pullAllBy=function(n,t,r){return n&&n.length&&t&&t.length?kt(n,t,vr(r)):n},dn.pullAt=uo,dn.range=fi,
|
||||||
|
dn.rangeRight=ci,dn.rearg=So,dn.reject=function(n,t){var r=Wo(n)?o:et;return t=vr(t,3),r(n,function(n,r,e){return!t(n,r,e)})},dn.remove=function(n,t){var r=[];if(!n||!n.length)return r;var e=-1,u=[],o=n.length;for(t=vr(t,3);++e<o;){var i=n[e];t(i,e,n)&&(r.push(i),u.push(e))}return It(n,u),r},dn.rest=ee,dn.reverse=Zr,dn.sampleSize=Jr,dn.set=function(n,t,r){return null==n?n:St(n,t,r)},dn.setWith=function(n,t,r,e){return e=typeof e=="function"?e:Z,null==n?n:St(n,t,r,e)},dn.shuffle=function(n){return Jr(n,4294967295);
|
||||||
|
},dn.slice=function(n,t,r){var e=n?n.length:0;return e?(r&&typeof r!="number"&&Or(n,t,r)?(t=0,r=e):(t=null==t?0:Ae(t),r=r===Z?e:Ae(r)),Wt(n,t,r)):[]},dn.sortBy=mo,dn.sortedUniq=function(n){return n&&n.length?Bt(n):[]},dn.sortedUniqBy=function(n,t){return n&&n.length?Bt(n,vr(t)):[]},dn.split=function(n,t,r){return Ie(n).split(t,r)},dn.spread=function(n){if(typeof n!="function")throw new He("Expected a function");return function(t){return r(n,this,t)}},dn.tail=function(n){return Lr(n,1)},dn.take=function(n,t,r){
|
||||||
|
return n&&n.length?(t=r||t===Z?1:Ae(t),Wt(n,0,0>t?0:t)):[]},dn.takeRight=function(n,t,r){var e=n?n.length:0;return e?(t=r||t===Z?1:Ae(t),t=e-t,Wt(n,0>t?0:t,e)):[]},dn.takeRightWhile=function(n,t){return n&&n.length?Ft(n,vr(t,3),false,true):[]},dn.takeWhile=function(n,t){return n&&n.length?Ft(n,vr(t,3)):[]},dn.tap=function(n,t){return t(n),n},dn.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new He("Expected a function");return pe(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),
|
||||||
|
te(n,t,{leading:e,maxWait:t,trailing:u})},dn.thru=Tr,dn.toArray=we,dn.toPairs=ze,dn.toPairsIn=function(n){return m(n,Ue(n))},dn.toPath=function(n){return Wo(n)?c(n,String):Cr(n)},dn.toPlainObject=ke,dn.transform=function(n,t,r){var u=Wo(n)||me(n);if(t=vr(t,4),null==r)if(u||pe(n)){var o=n.constructor;r=u?Wo(n)?new o:[]:Mu(le(o)?o.prototype:Z)}else r={};return(u?e:it)(n,function(n,e,u){return t(r,n,e,u)}),r},dn.unary=function(n){return Hr(n,1)},dn.union=oo,dn.unionBy=io,dn.unionWith=fo,dn.uniq=function(n){
|
||||||
|
return n&&n.length?$t(n):[]},dn.uniqBy=function(n,t){return n&&n.length?$t(n,vr(t)):[]},dn.uniqWith=function(n,t){return n&&n.length?$t(n,Z,t):[]},dn.unset=function(n,t){var r;if(null==n)r=true;else{r=n;var e=t,e=Er(e,r)?[e+""]:Lt(e);r=Wr(r,e),e=Mr(e),r=null!=r&&Se(r,e)?delete r[e]:true}return r},dn.unzip=Dr,dn.unzipWith=qr,dn.values=Be,dn.valuesIn=function(n){return null==n?w(n,Ue(n)):[]},dn.without=co,dn.words=Me,dn.wrap=function(n,t){return t=null==t?Ne:t,Io(t,n)},dn.xor=ao,dn.xorBy=lo,dn.xorWith=so,
|
||||||
|
dn.zip=ho,dn.zipObject=function(n,t){for(var r=-1,e=n?n.length:0,u=t?t.length:0,o={};++r<e;)St(o,n[r],u>r?t[r]:Z);return o},dn.zipWith=po,dn.each=Gr,dn.eachRight=Vr,dn.extend=Uo,dn.extendWith=zo,qe(dn,dn),dn.add=function(n,t){var r;return n!==Z&&(r=n),t!==Z&&(r=r===Z?t:r+t),r},dn.attempt=Qo,dn.camelCase=Po,dn.capitalize=Le,dn.ceil=ai,dn.clamp=function(n,t,r){return r===Z&&(r=t,t=Z),r!==Z&&(r=Ee(r),r=r===r?r:0),t!==Z&&(t=Ee(t),t=t===t?t:0),Hn(Ee(n),t,r)},dn.clone=function(n){return Qn(n)},dn.cloneDeep=function(n){
|
||||||
|
return Qn(n,true)},dn.cloneDeepWith=function(n,t){return Qn(n,true,t)},dn.cloneWith=function(n,t){return Qn(n,false,t)},dn.deburr=$e,dn.endsWith=function(n,t,r){n=Ie(n),t=typeof t=="string"?t:t+"";var e=n.length;return r=r===Z?e:Hn(Ae(r),0,e),r-=t.length,r>=0&&n.indexOf(t,r)==r},dn.eq=ue,dn.escape=function(n){return(n=Ie(n))&&Y.test(n)?n.replace(V,R):n},dn.escapeRegExp=function(n){return(n=Ie(n))&&un.test(n)?n.replace(en,"\\$&"):n},dn.every=function(n,t,r){var e=Wo(n)?u:rt;return r&&Or(n,t,r)&&(t=Z),e(n,vr(t,3));
|
||||||
|
},dn.find=function(n,t){if(t=vr(t,3),Wo(n)){var r=g(n,t);return r>-1?n[r]:Z}return _(n,t,Nu)},dn.findIndex=function(n,t){return n&&n.length?g(n,vr(t,3)):-1},dn.findKey=function(n,t){return _(n,vr(t,3),it,true)},dn.findLast=function(n,t){if(t=vr(t,3),Wo(n)){var r=g(n,t,true);return r>-1?n[r]:Z}return _(n,t,Zu)},dn.findLastIndex=function(n,t){return n&&n.length?g(n,vr(t,3),true):-1},dn.findLastKey=function(n,t){return _(n,vr(t,3),ft,true)},dn.floor=li,dn.forEach=Gr,dn.forEachRight=Vr,dn.forIn=function(n,t){
|
||||||
|
return null==n?n:Du(n,zr(t),Ue)},dn.forInRight=function(n,t){return null==n?n:qu(n,zr(t),Ue)},dn.forOwn=function(n,t){return n&&it(n,zr(t))},dn.forOwnRight=function(n,t){return n&&ft(n,zr(t))},dn.get=Re,dn.gt=oe,dn.gte=function(n,t){return n>=t},dn.has=Se,dn.hasIn=We,dn.head=Fr,dn.identity=Ne,dn.includes=function(n,t,r,e){return n=fe(n)?n:Be(n),r=r&&!e?Ae(r):0,e=n.length,0>r&&(r=wu(e+r,0)),be(n)?e>=r&&-1<n.indexOf(t,r):!!e&&-1<v(n,t,r)},dn.indexOf=function(n,t,r){var e=n?n.length:0;return e?(r=Ae(r),
|
||||||
|
0>r&&(r=wu(e+r,0)),v(n,t,r)):-1},dn.inRange=function(n,t,r){return t=Ee(t)||0,r===Z?(r=t,t=0):r=Ee(r)||0,n=Ee(n),n>=Au(t,r)&&n<wu(t,r)},dn.invoke=Mo,dn.isArguments=ie,dn.isArray=Wo,dn.isArrayLike=fe,dn.isArrayLikeObject=ce,dn.isBoolean=function(n){return true===n||false===n||_e(n)&&"[object Boolean]"==uu.call(n)},dn.isDate=function(n){return _e(n)&&"[object Date]"==uu.call(n)},dn.isElement=function(n){return!!n&&1===n.nodeType&&_e(n)&&!de(n)},dn.isEmpty=function(n){return!_e(n)||le(n.splice)?!Yr(n):!Ce(n).length;
|
||||||
|
},dn.isEqual=function(n,t){return _t(n,t)},dn.isEqualWith=function(n,t,r){var e=(r=typeof r=="function"?r:Z)?r(n,t):Z;return e===Z?_t(n,t,r):!!e},dn.isError=ae,dn.isFinite=function(n){return typeof n=="number"&&xu(n)},dn.isFunction=le,dn.isInteger=se,dn.isLength=he,dn.isMatch=function(n,t){return n===t||gt(n,t,dr(t))},dn.isMatchWith=function(n,t,r){return r=typeof r=="function"?r:Z,gt(n,t,dr(t),r)},dn.isNaN=function(n){return ve(n)&&n!=+n},dn.isNative=ge,dn.isNil=function(n){return null==n},dn.isNull=function(n){
|
||||||
|
return null===n},dn.isNumber=ve,dn.isObject=pe,dn.isObjectLike=_e,dn.isPlainObject=de,dn.isRegExp=ye,dn.isSafeInteger=function(n){return se(n)&&n>=-9007199254740991&&9007199254740991>=n},dn.isString=be,dn.isSymbol=xe,dn.isTypedArray=me,dn.isUndefined=function(n){return n===Z},dn.join=function(n,t){return n?mu.call(n,t):""},dn.kebabCase=To,dn.last=Mr,dn.lastIndexOf=function(n,t,r){var e=n?n.length:0;if(!e)return-1;var u=e;if(r!==Z&&(u=Ae(r),u=(0>u?wu(e+u,0):Au(u,e-1))+1),t!==t)return W(n,u,true);for(;u--;)if(n[u]===t)return u;
|
||||||
|
return-1},dn.lowerCase=Ko,dn.lowerFirst=Go,dn.lt=je,dn.lte=function(n,t){return t>=n},dn.max=function(n){return n&&n.length?p(n,Ne,oe):Z},dn.maxBy=function(n,t){return n&&n.length?p(n,vr(t),oe):Z},dn.mean=function(n){return Ke(n)/(n?n.length:0)},dn.min=function(n){return n&&n.length?p(n,Ne,je):Z},dn.minBy=function(n,t){return n&&n.length?p(n,vr(t),je):Z},dn.noConflict=function(){return Kn._=ou,this},dn.noop=Pe,dn.now=jo,dn.pad=function(n,t,r){n=Ie(n),t=Ae(t);var e=F(n);return t&&t>e?(e=(t-e)/2,t=bu(e),
|
||||||
|
e=yu(e),ir("",t,r)+n+ir("",e,r)):n},dn.padEnd=function(n,t,r){return n=Ie(n),n+ir(n,t,r)},dn.padStart=function(n,t,r){return n=Ie(n),ir(n,t,r)+n},dn.parseInt=function(n,t,r){return r||null==t?t=0:t&&(t=+t),n=Ie(n).replace(on,""),Ou(n,t||(hn.test(n)?16:10))},dn.random=function(n,t,r){if(r&&typeof r!="boolean"&&Or(n,t,r)&&(t=r=Z),r===Z&&(typeof t=="boolean"?(r=t,t=Z):typeof n=="boolean"&&(r=n,n=Z)),n===Z&&t===Z?(n=0,t=1):(n=Ee(n)||0,t===Z?(t=n,n=0):t=Ee(t)||0),n>t){var e=n;n=t,t=e}return r||n%1||t%1?(r=Eu(),
|
||||||
|
Au(n+r*(t-n+Fn("1e-"+((r+"").length-1))),t)):Rt(n,t)},dn.reduce=function(n,t,r){var e=Wo(n)?l:d,u=3>arguments.length;return e(n,vr(t,4),r,u,Nu)},dn.reduceRight=function(n,t,r){var e=Wo(n)?s:d,u=3>arguments.length;return e(n,vr(t,4),r,u,Zu)},dn.repeat=Fe,dn.replace=function(){var n=arguments,t=Ie(n[0]);return 3>n.length?t:t.replace(n[1],n[2])},dn.result=function(n,t,r){if(Er(t,n))e=null==n?Z:n[t];else{t=Lt(t);var e=Re(n,t);n=Wr(n,t)}return e===Z&&(e=r),le(e)?e.call(n):e},dn.round=si,dn.runInContext=N,
|
||||||
|
dn.sample=function(n){n=fe(n)?n:Be(n);var t=n.length;return t>0?n[Rt(0,t-1)]:Z},dn.size=Yr,dn.snakeCase=Jo,dn.some=function(n,t,r){var e=Wo(n)?h:Ct;return r&&Or(n,t,r)&&(t=Z),e(n,vr(t,3))},dn.sortedIndex=function(n,t){return Ut(n,t)},dn.sortedIndexBy=function(n,t,r){return zt(n,t,vr(r))},dn.sortedIndexOf=function(n,t){var r=n?n.length:0;if(r){var e=Ut(n,t);if(r>e&&ue(n[e],t))return e}return-1},dn.sortedLastIndex=function(n,t){return Ut(n,t,true)},dn.sortedLastIndexBy=function(n,t,r){return zt(n,t,vr(r),true);
|
||||||
|
},dn.sortedLastIndexOf=function(n,t){if(n&&n.length){var r=Ut(n,t,true)-1;if(ue(n[r],t))return r}return-1},dn.startCase=Yo,dn.startsWith=function(n,t,r){return n=Ie(n),r=Hn(Ae(r),0,n.length),n.lastIndexOf(t,r)==r},dn.subtract=function(n,t){var r;return n!==Z&&(r=n),t!==Z&&(r=r===Z?t:r-t),r},dn.sum=Ke,dn.sumBy=function(n,t){return n&&n.length?b(n,vr(t)):Z},dn.template=function(n,t,r){var e=dn.templateSettings;r&&Or(n,t,r)&&(t=Z),n=Ie(n),t=zo({},t,e,Pn),r=zo({},t.imports,e.imports,Pn);var u,o,i=Ce(r),f=w(r,i),c=0;
|
||||||
|
r=t.interpolate||bn;var a="__p+='";r=Ye((t.escape||bn).source+"|"+r.source+"|"+(r===X?ln:bn).source+"|"+(t.evaluate||bn).source+"|$","g");var l="sourceURL"in t?"//# sourceURL="+t.sourceURL+"\n":"";if(n.replace(r,function(t,r,e,i,f,l){return e||(e=i),a+=n.slice(c,l).replace(xn,S),r&&(u=true,a+="'+__e("+r+")+'"),f&&(o=true,a+="';"+f+";\n__p+='"),e&&(a+="'+((__t=("+e+"))==null?'':__t)+'"),c=l+t.length,t}),a+="';",(t=t.variable)||(a="with(obj){"+a+"}"),a=(o?a.replace(P,""):a).replace(T,"$1").replace(K,"$1;"),
|
||||||
|
a="function("+(t||"obj")+"){"+(t?"":"obj||(obj={});")+"var __t,__p=''"+(u?",__e=_.escape":"")+(o?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+a+"return __p}",t=Qo(function(){return Function(i,l+"return "+a).apply(Z,f)}),t.source=a,ae(t))throw t;return t},dn.times=function(n,t){if(n=Ae(n),1>n||n>9007199254740991)return[];var r=4294967295,e=Au(n,4294967295);for(t=zr(t),n-=4294967295,e=x(e,t);++r<n;)t(r);return e},dn.toInteger=Ae,dn.toLength=Oe,dn.toLower=function(n){
|
||||||
|
return Ie(n).toLowerCase()},dn.toNumber=Ee,dn.toSafeInteger=function(n){return Hn(Ae(n),-9007199254740991,9007199254740991)},dn.toString=Ie,dn.toUpper=function(n){return Ie(n).toUpperCase()},dn.trim=function(n,t,r){return(n=Ie(n))?r||t===Z?n.replace(on,""):(t+="")?(n=n.match(On),t=t.match(On),n.slice(A(n,t),O(n,t)+1).join("")):n:n},dn.trimEnd=function(n,t,r){return(n=Ie(n))?r||t===Z?n.replace(cn,""):(t+="")?(n=n.match(On),n.slice(0,O(n,t.match(On))+1).join("")):n:n},dn.trimStart=function(n,t,r){return(n=Ie(n))?r||t===Z?n.replace(fn,""):(t+="")?(n=n.match(On),
|
||||||
|
n.slice(A(n,t.match(On))).join("")):n:n},dn.truncate=function(n,t){var r=30,e="...";if(pe(t))var u="separator"in t?t.separator:u,r="length"in t?Ae(t.length):r,e="omission"in t?Ie(t.omission):e;n=Ie(n);var o=n.length;if(En.test(n))var i=n.match(On),o=i.length;if(r>=o)return n;if(o=r-F(e),1>o)return e;if(r=i?i.slice(0,o).join(""):n.slice(0,o),u===Z)return r+e;if(i&&(o+=r.length-o),ye(u)){if(n.slice(o).search(u)){var f=r;for(u.global||(u=Ye(u.source,Ie(sn.exec(u))+"g")),u.lastIndex=0;i=u.exec(f);)var c=i.index;
|
||||||
|
r=r.slice(0,c===Z?o:c)}}else n.indexOf(u,o)!=o&&(u=r.lastIndexOf(u),u>-1&&(r=r.slice(0,u)));return r+e},dn.unescape=function(n){return(n=Ie(n))&&J.test(n)?n.replace(G,M):n},dn.uniqueId=function(n){var t=++ru;return Ie(n)+t},dn.upperCase=Ho,dn.upperFirst=Vo,dn.first=Fr,qe(dn,function(){var n={};return it(dn,function(t,r){tu.call(dn.prototype,r)||(n[r]=t)}),n}(),{chain:false}),dn.VERSION="4.0.0",e("bind bindKey curry curryRight partial partialRight".split(" "),function(n){dn[n].placeholder=dn}),e(["drop","take"],function(n,t){
|
||||||
|
wn.prototype[n]=function(r){var e=this.__filtered__;if(e&&!t)return new wn(this);r=r===Z?1:wu(Ae(r),0);var u=this.clone();return e?u.__takeCount__=Au(r,u.__takeCount__):u.__views__.push({size:Au(r,4294967295),type:n+(0>u.__dir__?"Right":"")}),u},wn.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()}}),e(["filter","map","takeWhile"],function(n,t){var r=t+1,e=1==r||3==r;wn.prototype[n]=function(n){var t=this.clone();return t.__iteratees__.push({iteratee:vr(n,3),type:r}),t.__filtered__=t.__filtered__||e,
|
||||||
|
t}}),e(["head","last"],function(n,t){var r="take"+(t?"Right":"");wn.prototype[n]=function(){return this[r](1).value()[0]}}),e(["initial","tail"],function(n,t){var r="drop"+(t?"":"Right");wn.prototype[n]=function(){return this.__filtered__?new wn(this):this[r](1)}}),wn.prototype.compact=function(){return this.filter(Ne)},wn.prototype.find=function(n){return this.filter(n).head()},wn.prototype.findLast=function(n){return this.reverse().find(n)},wn.prototype.invokeMap=ee(function(n,t){return typeof n=="function"?new wn(this):this.map(function(r){
|
||||||
|
return pt(r,n,t)})}),wn.prototype.reject=function(n){return n=vr(n,3),this.filter(function(t){return!n(t)})},wn.prototype.slice=function(n,t){n=Ae(n);var r=this;return r.__filtered__&&(n>0||0>t)?new wn(r):(0>n?r=r.takeRight(-n):n&&(r=r.drop(n)),t!==Z&&(t=Ae(t),r=0>t?r.dropRight(-t):r.take(t-n)),r)},wn.prototype.takeRightWhile=function(n){return this.reverse().takeWhile(n).reverse()},wn.prototype.toArray=function(){return this.take(4294967295)},it(wn.prototype,function(n,t){var r=/^(?:filter|find|map|reject)|While$/.test(t),e=/^(?:head|last)$/.test(t),u=dn[e?"take"+("last"==t?"Right":""):t],o=e||/^find/.test(t);
|
||||||
|
u&&(dn.prototype[t]=function(){function t(n){return n=u.apply(dn,a([n],f)),e&&h?n[0]:n}var i=this.__wrapped__,f=e?[1]:arguments,c=i instanceof wn,l=f[0],s=c||Wo(i);s&&r&&typeof l=="function"&&1!=l.length&&(c=s=false);var h=this.__chain__,p=!!this.__actions__.length,l=o&&!h,c=c&&!p;return!o&&s?(i=c?i:new wn(this),i=n.apply(i,f),i.__actions__.push({func:Tr,args:[t],thisArg:Z}),new jn(i,h)):l&&c?n.apply(this,f):(i=this.thru(t),l?e?i.value()[0]:i.value():i)})}),e("pop push shift sort splice unshift".split(" "),function(n){
|
||||||
|
var t=Qe[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|shift)$/.test(n);dn.prototype[n]=function(){var n=arguments;return e&&!this.__chain__?t.apply(this.value(),n):this[r](function(r){return t.apply(r,n)})}}),it(wn.prototype,function(n,t){var r=dn[t];if(r){var e=r.name+"";(Fu[e]||(Fu[e]=[])).push({name:t,func:r})}}),Fu[ur(Z,2).name]=[{name:"wrapper",func:Z}],wn.prototype.clone=function(){var n=new wn(this.__wrapped__);return n.__actions__=Pt(this.__actions__),n.__dir__=this.__dir__,
|
||||||
|
n.__filtered__=this.__filtered__,n.__iteratees__=Pt(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=Pt(this.__views__),n},wn.prototype.reverse=function(){if(this.__filtered__){var n=new wn(this);n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},wn.prototype.value=function(){var n,t=this.__wrapped__.value(),r=this.__dir__,e=Wo(t),u=0>r,o=e?t.length:0;n=o;for(var i=this.__views__,f=0,c=-1,a=i.length;++c<a;){var l=i[c],s=l.size;switch(l.type){case"drop":f+=s;
|
||||||
|
break;case"dropRight":n-=s;break;case"take":n=Au(n,f+s);break;case"takeRight":f=wu(f,n-s)}}if(n={start:f,end:n},i=n.start,f=n.end,n=f-i,u=u?f:i-1,i=this.__iteratees__,f=i.length,c=0,a=Au(n,this.__takeCount__),!e||200>o||o==n&&a==n)return Mt(t,this.__actions__);e=[];n:for(;n--&&a>c;){for(u+=r,o=-1,l=t[u];++o<f;){var h=i[o],s=h.type,h=(0,h.iteratee)(l);if(2==s)l=h;else if(!h){if(1==s)continue n;break n}}e[c++]=l}return e},dn.prototype.at=_o,dn.prototype.chain=function(){return Pr(this)},dn.prototype.commit=function(){
|
||||||
|
return new jn(this.value(),this.__chain__)},dn.prototype.flatMap=function(n){return this.map(n).flatten()},dn.prototype.next=function(){this.__values__===Z&&(this.__values__=we(this.value()));var n=this.__index__>=this.__values__.length,t=n?Z:this.__values__[this.__index__++];return{done:n,value:t}},dn.prototype.plant=function(n){for(var t,r=this;r instanceof mn;){var e=Br(r);e.__index__=0,e.__values__=Z,t?u.__wrapped__=e:t=e;var u=e,r=r.__wrapped__}return u.__wrapped__=n,t},dn.prototype.reverse=function(){
|
||||||
|
var n=this.__wrapped__;return n instanceof wn?(this.__actions__.length&&(n=new wn(this)),n=n.reverse(),n.__actions__.push({func:Tr,args:[Zr],thisArg:Z}),new jn(n,this.__chain__)):this.thru(Zr)},dn.prototype.toJSON=dn.prototype.valueOf=dn.prototype.value=function(){return Mt(this.__wrapped__,this.__actions__)},_u&&(dn.prototype[_u]=Kr),dn}var Z,D=1/0,q=NaN,P=/\b__p\+='';/g,T=/\b(__p\+=)''\+/g,K=/(__e\(.*?\)|\b__t\))\+'';/g,G=/&(?:amp|lt|gt|quot|#39|#96);/g,V=/[&<>"'`]/g,J=RegExp(G.source),Y=RegExp(V.source),H=/<%-([\s\S]+?)%>/g,Q=/<%([\s\S]+?)%>/g,X=/<%=([\s\S]+?)%>/g,nn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,tn=/^\w*$/,rn=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]/g,en=/[\\^$.*+?()[\]{}|]/g,un=RegExp(en.source),on=/^\s+|\s+$/g,fn=/^\s+/,cn=/\s+$/,an=/\\(\\)?/g,ln=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,sn=/\w*$/,hn=/^0x/i,pn=/^[-+]0x[0-9a-f]+$/i,_n=/^0b[01]+$/i,gn=/^\[object .+?Constructor\]$/,vn=/^0o[0-7]+$/i,dn=/^(?:0|[1-9]\d*)$/,yn=/[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g,bn=/($^)/,xn=/['\n\r\u2028\u2029\\]/g,mn="[\\ufe0e\\ufe0f]?(?:\\ud83c[\\udffb-\\udfff])?(?:\\u200d(?:[^\\ud800-\\udfff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])[\\ufe0e\\ufe0f]?(?:\\ud83c[\\udffb-\\udfff])?)*",jn="(?:[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])"+mn,wn="(?:[^\\ud800-\\udfff][\\u0300-\\u036f\\ufe20-\\ufe23]?|[\\u0300-\\u036f\\ufe20-\\ufe23]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\ud800-\\udfff])",An=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe23]","g"),On=RegExp(wn+mn,"g"),En=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe23\\ufe0e\\ufe0f]"),kn=/[a-zA-Z0-9]+/g,In=RegExp(["[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2018\\u2019\\u201c\\u201d \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde]|$)|(?:[A-Z\\xc0-\\xd6\\xd8-\\xde]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2018\\u2019\\u201c\\u201d \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2018\\u2019\\u201c\\u201d \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde](?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2018\\u2019\\u201c\\u201d \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])|$)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?(?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2018\\u2019\\u201c\\u201d \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+|\\d+(?:(?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2018\\u2019\\u201c\\u201d \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+)?",jn].join("|"),"g"),Rn=/[a-z][A-Z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Sn="Array Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Map Math Object Reflect RegExp Set String Symbol TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap _ clearTimeout isFinite parseInt setTimeout".split(" "),Wn={};
|
||||||
|
Wn["[object Float32Array]"]=Wn["[object Float64Array]"]=Wn["[object Int8Array]"]=Wn["[object Int16Array]"]=Wn["[object Int32Array]"]=Wn["[object Uint8Array]"]=Wn["[object Uint8ClampedArray]"]=Wn["[object Uint16Array]"]=Wn["[object Uint32Array]"]=true,Wn["[object Arguments]"]=Wn["[object Array]"]=Wn["[object ArrayBuffer]"]=Wn["[object Boolean]"]=Wn["[object Date]"]=Wn["[object Error]"]=Wn["[object Function]"]=Wn["[object Map]"]=Wn["[object Number]"]=Wn["[object Object]"]=Wn["[object RegExp]"]=Wn["[object Set]"]=Wn["[object String]"]=Wn["[object WeakMap]"]=false;
|
||||||
|
var Cn={};Cn["[object Arguments]"]=Cn["[object Array]"]=Cn["[object ArrayBuffer]"]=Cn["[object Boolean]"]=Cn["[object Date]"]=Cn["[object Float32Array]"]=Cn["[object Float64Array]"]=Cn["[object Int8Array]"]=Cn["[object Int16Array]"]=Cn["[object Int32Array]"]=Cn["[object Map]"]=Cn["[object Number]"]=Cn["[object Object]"]=Cn["[object RegExp]"]=Cn["[object Set]"]=Cn["[object String]"]=Cn["[object Symbol]"]=Cn["[object Uint8Array]"]=Cn["[object Uint8ClampedArray]"]=Cn["[object Uint16Array]"]=Cn["[object Uint32Array]"]=true,
|
||||||
|
Cn["[object Error]"]=Cn["[object Function]"]=Cn["[object WeakMap]"]=false;var Un={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O",
|
||||||
|
"\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss"},zn={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`"},Bn={"&":"&","<":"<",">":">",""":'"',"'":"'","`":"`"},Ln={"function":true,object:true},$n={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"
|
||||||
|
},Fn=parseFloat,Mn=parseInt,Nn=Ln[typeof exports]&&exports&&!exports.nodeType?exports:null,Zn=Ln[typeof module]&&module&&!module.nodeType?module:null,Dn=E(Ln[typeof self]&&self),qn=E(Ln[typeof window]&&window),Pn=Zn&&Zn.exports===Nn?Nn:null,Tn=E(Ln[typeof this]&&this),Kn=E(Nn&&Zn&&typeof global=="object"&&global)||qn!==(Tn&&Tn.window)&&qn||Dn||Tn||Function("return this")(),Gn=N();(qn||Dn||{})._=Gn,typeof define=="function"&&typeof define.amd=="object"&&define.amd? define(function(){return Gn}):Nn&&Zn?(Pn&&((Zn.exports=Gn)._=Gn),
|
||||||
|
Nn._=Gn):Kn._=Gn}).call(this);
|
|
@ -31,6 +31,28 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
<div class="heading">
|
||||||
|
{{ positive_count }}
|
||||||
|
<span class="sub">compte{{ positive_count|pluralize }} en positif</span>
|
||||||
|
</div>
|
||||||
|
<div class="heading">
|
||||||
|
{{ positives_sum|floatformat:2 }}€
|
||||||
|
<span class="sub">de positif total</span>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
<div class="heading">
|
||||||
|
{{ negative_count }}
|
||||||
|
<span class="sub">compte{{ negative_count|pluralize }} en négatif</span>
|
||||||
|
</div>
|
||||||
|
<div class="heading">
|
||||||
|
{{ negatives_sum|floatformat:2 }}€
|
||||||
|
<span class="sub">de négatif total</span>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block main %}
|
{% block main %}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "kfet/base_form.html" %}
|
{% extends "kfet/base_form.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
{% block title %}Nouveau compte{% endblock %}
|
{% block title %}Nouveau compte{% endblock %}
|
||||||
{% block header-title %}Création d'un compte{% endblock %}
|
{% block header-title %}Création d'un compte{% endblock %}
|
||||||
|
@ -60,8 +60,7 @@
|
||||||
$('#id_trigramme').on('input', function() {
|
$('#id_trigramme').on('input', function() {
|
||||||
var trigramme = $('#id_trigramme').val().toUpperCase();
|
var trigramme = $('#id_trigramme').val().toUpperCase();
|
||||||
|
|
||||||
var pattern = /^[^a-z]{3}$/;
|
if (!(trigramme.is_valid_trigramme())) {
|
||||||
if (!(trigramme.match(pattern))) {
|
|
||||||
$('#id_trigramme')
|
$('#id_trigramme')
|
||||||
.css('background', '#fff')
|
.css('background', '#fff')
|
||||||
.css('color', '#000');
|
.css('color', '#000');
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "kfet/base.html" %}
|
{% extends "kfet/base.html" %}
|
||||||
{% load staticfiles %}
|
{% load static %}
|
||||||
|
|
||||||
{% block title %}Nouveau compte{% endblock %}
|
{% block title %}Nouveau compte{% endblock %}
|
||||||
{% block header-title %}Création d'un compte{% endblock %}
|
{% block header-title %}Création d'un compte{% endblock %}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue