Compare commits

..

79 commits

Author SHA1 Message Date
8d7ccccc9b
feat(bda/place): show amount left to pay 2025-03-03 17:15:19 +01:00
4d4b536781
fix(nix-shell): avoid overlay impurity 2025-03-03 15:00:29 +01:00
0674217526
fix(migrations): merge generated migrations 2025-02-26 09:24:21 +01:00
bf30a9e510
feat(bda): ajout d'un choix "virement" pour le paiement des tirages
fix(bda): génération automatique des migrations
2025-02-26 09:21:44 +01:00
47cf999359
test(daphne): some stuff to test websocket things 2025-02-26 08:59:44 +01:00
e1bd6bc6ad fix(kfet/ws): 'type' field is required 2025-02-26 08:40:23 +01:00
9178511005 fix(mails/kfet): use right dict key 2025-02-25 22:04:35 +01:00
3997f48eb8
feat(shared): Add a view for exporting members to a mailing-list 2025-02-25 20:54:40 +01:00
bb80716cb4
fix(kfet/utils): Add sync_to_async 2025-02-25 20:54:40 +01:00
2ec15ad2d5
feat(kfet): block self deposit 2025-02-25 20:54:40 +01:00
81af13a216
fix(kfet/accounts): Exclude #13 from the statistics 2025-02-25 20:54:40 +01:00
1a8fe48d05
feat(kfet): Add a summary of the account balances 2025-02-25 20:54:40 +01:00
18c0f0f699
fix(gestioncof/cms): The tag ifnotequal has been deprecated for 5 years and has now been removed 2025-02-25 20:54:40 +01:00
2d1357c4ff
fix(kfet/open): Wrap sync function for async use 2025-02-25 20:54:40 +01:00
853611556c
fix(settings_cof): Origins must have a scheme 2025-02-25 20:54:40 +01:00
29b1581ab7
fix(settings_cof): Update variables 2025-02-25 20:54:40 +01:00
2fb4c6a95c
fix(cof_settings): Set ASGI_APPLICATION 2025-02-25 20:54:40 +01:00
e1c9a4474d
fix(prepare_django): make the .static dir 2025-02-17 17:11:51 +01:00
e74e03d36b
doc(readme): adapt to settings refactor 2025-02-17 17:11:51 +01:00
c8dc0ca34e
doc(readme): missing depencies 2025-02-17 17:11:48 +01:00
982c82ba31 Merge pull request 'fix(kfet/promo): new subscribers may arrive in august' (#838) from Morpheus/gestioCOF:master into master
Reviewed-on: DGNum/gestioCOF#838
2025-01-11 16:47:22 +01:00
531ba575c8 Update kfet/forms.py
Correction de l'assignation des promos
2025-01-11 16:46:31 +01:00
def9b68a90 Merge pull request 'django-apps' (#847) from django-apps into master
Reviewed-on: DGNum/gestioCOF#847
2025-01-11 15:02:15 +01:00
7cd4338a3a
fix(cof_settings): Serve files at the right url 2025-01-11 14:39:49 +01:00
f5bfbddfbe
fix(bds_settings): Serve files at the right url 2025-01-11 14:16:13 +01:00
9e185c8cb5
feat: Refactor settings 2024-12-28 21:27:22 +01:00
b8c963779a
fix: shell.nix 2024-12-28 17:23:52 +01:00
57ff8fe131
chore: use Django LTS 2024-12-28 17:23:04 +01:00
0fdc1a2a46
fix(k-fet/accouts/new): reverse path at trigramme creation 2024-12-23 11:27:19 +01:00
aafbab29ff
chore: Finished version bump 2024-12-20 00:20:31 +01:00
fb66c064cd
chore: Partial version bump 2024-12-19 14:07:35 +01:00
2126224e15 Merge pull request 'Thubrecht/daphne' (#815) from thubrecht/daphne into master
Reviewed-on: DGNum/gestioCOF#815
2024-07-30 18:40:56 +02:00
d6109a9312 dev: update requirements and directly read the files 2024-07-12 14:54:14 +02:00
a69bd0426f kfet/open: Add comment to test 2024-07-12 14:54:14 +02:00
81a4dbef7c dev: set explicitely DJANGO_SETTINGS_MODULE 2024-07-12 14:54:14 +02:00
4fedf3453d kfet: remove redundant static tag 2024-07-12 14:54:14 +02:00
8607d77c84 dev: update requirements 2024-07-12 14:54:14 +02:00
bc55a3067e Remove useless migrations 2024-07-12 14:54:14 +02:00
dd68ad91cd Update django-hCaptcha 2024-07-12 14:54:14 +02:00
2f71246509 assertDictContainsSubset is deprecated 2024-07-12 14:54:14 +02:00
a20a1c11d6 Explicitely set transform=repr in assertQuerysetEquals 2024-07-12 14:54:14 +02:00
0e1ff1765a assertEquals is deprecated 2024-07-12 14:54:14 +02:00
177d413f4c Use AutoField instead of BigAutoField 2024-07-12 14:54:14 +02:00
01dd16c795 url -> re_path 2024-07-12 14:54:14 +02:00
84c87c1b4b Replace unique_together by UniqueConstraint 2024-07-12 14:54:14 +02:00
1b143b322f Update changelog 2024-07-12 14:54:14 +02:00
f9456e3c29 cof has been renamed to gestioasso 2024-07-12 14:54:14 +02:00
dd1350f1c2 Fix typo 2024-07-12 14:54:14 +02:00
8f4cb68d31 Only run kf tests in sequential mode (to fix issues with channels) 2024-07-12 14:54:14 +02:00
3a3f96a8df Try not running tests in parallel 2024-07-12 14:54:14 +02:00
7d8926e459 Try to flush old messages in tests 2024-07-12 14:54:14 +02:00
3fee014384 Update kfet.open tests 2024-07-12 14:54:14 +02:00
cd351786bb Remove default_app_config 2024-07-12 14:54:14 +02:00
7362c2fa2a Update django-djconfig 2024-07-12 14:54:14 +02:00
efbc947145 Change backend name as the old one is deprecated 2024-07-12 14:54:14 +02:00
0c45262fbc ugettext -> gettext 2024-07-12 14:54:14 +02:00
cabd277b4a Update redis, and implement a custom channel layer to send datetime/decimal objects 2024-07-12 14:54:14 +02:00
4108efe8c9 Fix kfet.ope tests 2024-07-12 14:54:14 +02:00
693e4252d5 Fix kpsul tests 2024-07-12 14:54:14 +02:00
3aa928e8f0 Simplify group_send for kpsul 2024-07-12 14:54:14 +02:00
5e2e68960b Channels 3 2024-07-12 14:54:14 +02:00
91e9beee11 Add default django asgi handler 2024-07-12 14:54:14 +02:00
ac286209ed Fix app import 2024-07-12 14:54:14 +02:00
b03cda5962 keyOrder is deprecated, using a list in Meta gives the correct order 2024-07-12 14:54:14 +02:00
4feb567af7 Update django-redis-cache and fix vagrant setup 2024-07-12 14:54:14 +02:00
1ac47885d0 Update django version 2024-07-12 14:54:14 +02:00
071c810605 Update base consumers 2024-07-12 14:54:14 +02:00
7f00ce0ff1 Add default http router 2024-07-12 14:54:14 +02:00
95136cb4eb Update daphne version 2024-07-12 14:54:14 +02:00
e299997aa8 Update to django channels 2 2024-07-12 14:54:14 +02:00
40f34926bb Fix vagrant setup w/ daphne 2024-07-12 14:54:14 +02:00
69976a878a Merge pull request 'Envoi de mail lors de la création d'un trigramme' (#833) from agroudiev/mail-creation-trigramme into master
Reviewed-on: DGNum/gestioCOF#833
2024-07-06 16:54:25 +02:00
6621ae3950 style(kfet): suppression de return 2024-07-06 16:49:35 +02:00
9288daaf9e feat(kfet): envoi de mail lors de la création d'un trigramme 2024-07-06 12:14:49 +02:00
e92c500940 feat(shell.nix): Switch to python 3.9 2024-02-11 19:59:29 +01:00
Tom Hubrecht
d75eaf583f Merge branch 'master' into 'master'
Modification du délai pour l'indicateur K-Fêt ouverte

See merge request klub-dev-ens/gestioCOF!529
2023-12-10 10:11:33 +01:00
55bd3ab51d
Modification du délai du websocket 2023-12-08 21:17:53 +01:00
Tom Hubrecht
f640a25f59 Merge branch 'petitcours-template-tweak' into 'master'
[petitcours] Tweak `eleve.txt` template

See merge request klub-dev-ens/gestioCOF!528
2023-10-03 15:20:40 +02:00
Leo Lanteri--Thauvin
f881c7cd8b [petitcours] Tweak eleve.txt template 2023-09-11 16:44:45 +02:00
126 changed files with 2157 additions and 600 deletions

View file

@ -0,0 +1 @@
0x0000000000000000000000000000000000000000

View file

@ -0,0 +1 @@
10000000-ffff-ffff-ffff-000000000001

View file

@ -0,0 +1 @@
k-feste_token

1
.credentials/SECRET_KEY Normal file
View file

@ -0,0 +1 @@
insecure-key

View file

@ -0,0 +1 @@
toto

View file

@ -0,0 +1 @@
sympa

1
.gitignore vendored
View file

@ -21,3 +21,4 @@ media/
# VSCode # VSCode
.vscode/ .vscode/
.direnv .direnv
.static

View file

@ -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

View file

@ -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

View file

@ -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` :

View 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"
),
),
]

View 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'),
),
]

View file

@ -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 = []

View file

@ -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"

View file

@ -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" %}" />

View file

@ -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>

View file

@ -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>

View file

@ -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>

View file

@ -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

View file

@ -1,5 +1,5 @@
{% extends "base_title.html" %} {% extends "base_title.html" %}
{% load staticfiles %} {% load static %}
{%block realcontent %} {%block realcontent %}

View file

@ -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>

View file

@ -1,5 +1,5 @@
{% extends "base_title.html" %} {% extends "base_title.html" %}
{% load staticfiles %} {% load static %}
{% block realcontent %} {% block realcontent %}

View file

@ -1,5 +1,5 @@
{% extends "base_title.html" %} {% extends "base_title.html" %}
{% load staticfiles %} {% load static %}
{% block realcontent %} {% block realcontent %}

View file

@ -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>

View file

@ -1,5 +1,5 @@
{% extends "base_title.html" %} {% extends "base_title.html" %}
{% load staticfiles %} {% load static %}
{% block realcontent %} {% block realcontent %}

View file

@ -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" %}" />

View file

@ -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"
),
] ]

View file

@ -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,
}, },
) )

View file

@ -1 +0,0 @@
default_app_config = "bds.apps.BdsConfig"

View file

@ -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

View file

@ -1,4 +1,4 @@
{% load staticfiles %} {% load static %}
{% load bulma_utils %} {% load bulma_utils %}
<!DOCTYPE html> <!DOCTYPE html>

View file

@ -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)

View file

@ -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",
),
] ]

View 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"
),
),
]

View file

@ -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)

View file

@ -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()

View file

@ -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(),
}
)

View file

@ -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>",
}, },

View file

@ -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

View file

@ -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
View 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
View 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

View file

@ -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 += [

View file

@ -1 +0,0 @@
default_app_config = "gestioncof.apps.GestioncofConfig"

View file

@ -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,

View file

@ -1 +0,0 @@
default_app_config = "gestioncof.cms.apps.COFCMSAppConfig"

View file

@ -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"
), ),
), ),

View file

@ -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()),
] ]
), ),
), ),

View file

@ -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)),
] ]
), ),
), ),

View file

@ -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,
),
),
]

View file

@ -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 = []

View file

@ -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' %}&amp;{{ 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' %}&amp;{{ 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' %}&amp;{{ 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' %}&amp;{{ 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' %}&amp;{{ 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' %}&amp;{{ 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' %}&amp;{{ 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' %}&amp;{{ key }}={{ value }}{% endif %}{% endfor %}">{% trans "Actualités plus anciennes" %}</a>
{% endif %} {% endif %}
</section> </section>
{% endblock %} {% endblock %}

View file

@ -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

View file

@ -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"),

View 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"
),
),
]

View 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 = []

View file

@ -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" % (

View file

@ -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

View file

@ -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">

View file

@ -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 %}

View file

@ -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

View file

@ -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):

View file

@ -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)),
] ]

View file

@ -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

View file

@ -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"

View file

@ -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"

View file

@ -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):

View file

@ -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

View file

@ -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"]

View file

@ -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

View file

@ -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,
) )

View file

@ -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

View file

@ -1 +0,0 @@
default_app_config = "kfet.cms.apps.KFetCMSAppConfig"

View file

@ -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")

View file

@ -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):

View file

@ -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
), ),

View 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",
),
),
]

View file

@ -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"),

View file

@ -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)

View file

@ -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():

View file

@ -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,
), ),

View file

@ -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):

View file

@ -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))

View file

@ -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()

View file

@ -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()),
]
)

View file

@ -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"})
# ... but not to the 'kfet.open' one await channel_layer.group_send(
OpenKfetConsumer.group_send("kfet.open.base", {"test": "plop"}) "kfet.open.base", {"test": "plop", "type": "open.status"}
self.assertIsNone(c.receive()) )
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
await channel_layer.group_send(
"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)
)

View file

@ -1,3 +1,4 @@
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
@ -18,7 +19,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 +28,5 @@ 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()

View file

@ -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),
]
)

View file

@ -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));

View file

@ -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 %}

View file

@ -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 %}

View file

@ -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 %}

View file

@ -1,5 +1,5 @@
{% extends 'kfet/base_form.html' %} {% extends 'kfet/base_form.html' %}
{% load staticfiles %} {% load static %}
{% load widget_tweaks %} {% load widget_tweaks %}
{% block title %}Permissions - Édition{% endblock %} {% block title %}Permissions - Édition{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "kfet/base_col_2.html" %} {% extends "kfet/base_col_2.html" %}
{% load staticfiles %} {% load static %}
{% load kfet_tags %} {% load kfet_tags %}
{% load l10n %} {% load l10n %}

View file

@ -1,5 +1,5 @@
{% extends 'kfet/base_col_2.html' %} {% extends 'kfet/base_col_2.html' %}
{% load staticfiles kfet_tags %} {% load static kfet_tags %}
{% block extra_head %} {% block extra_head %}
<script type="text/javascript" src="{% static 'kfet/vendor/Chart.min.js' %}"></script> <script type="text/javascript" src="{% static 'kfet/vendor/Chart.min.js' %}"></script>

View file

@ -1,5 +1,5 @@
{% extends 'kfet/base_col_2.html' %} {% extends 'kfet/base_col_2.html' %}
{% load l10n staticfiles widget_tweaks bootstrap %} {% load l10n static widget_tweaks bootstrap %}
{% block extra_head %} {% block extra_head %}
<link rel="stylesheet" type="text/css" href="{% static 'kfet/vendor/multiple-select/multiple-select.css' %}"> <link rel="stylesheet" type="text/css" href="{% static 'kfet/vendor/multiple-select/multiple-select.css' %}">

View file

@ -1,5 +1,5 @@
{% extends "kfet/base_col_1.html" %} {% extends "kfet/base_col_1.html" %}
{% load staticfiles %} {% load static %}
{% load kfet_tags %} {% load kfet_tags %}
{% block title %}Accueil{% endblock %} {% block title %}Accueil{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends 'kfet/base.html' %} {% extends 'kfet/base.html' %}
{% load staticfiles %} {% load static %}
{% block extra_head %} {% block extra_head %}
<link rel="stylesheet" style="text/css" href="{% static 'kfet/css/kpsul_grid.css' %}"> <link rel="stylesheet" style="text/css" href="{% static 'kfet/css/kpsul_grid.css' %}">

View file

@ -0,0 +1,12 @@
Salut {{ account.name }},
Ton compte K-Fêt a bien été créé le {{ account.created_at }} avec le trigramme {{ account.trigramme }}.
Tu peux désormais :
- Accéder à ton historique personnel des consommations : https://{{ site }}{{ url_read }}
- Modifier tes informations : https://{{ site }}{{ url_update }}
- Supprimer ton compte : https://{{ site }}{{ url_delete }}
En espérant te revoir bientôt,
--
L'équipe K-Fêt

View file

@ -1,6 +1,5 @@
{% extends 'kfet/base_col_2.html' %} {% extends 'kfet/base_col_2.html' %}
{% load staticfiles %} {% load l10n static widget_tweaks %}
{% load l10n staticfiles widget_tweaks %}
{% block title %}Transferts{% endblock %} {% block title %}Transferts{% endblock %}
{% block header-title %}Transferts{% endblock %} {% block header-title %}Transferts{% endblock %}

Some files were not shown because too many files have changed in this diff Show more