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/
.direnv
.static

View file

@ -43,13 +43,21 @@ variables:
# Keep this disabled for now, as it may kill GitLab...
# 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:
stage: test
extends: .test_template
variables:
DJANGO_SETTINGS_MODULE: "gestioasso.settings.cof_prod"
script:
- coverage run manage.py test gestioncof bda kfet petitscours shared --parallel
- coverage run manage.py test gestioncof bda petitscours shared --parallel
bdstest:
stage: test

View file

@ -23,6 +23,9 @@ adhérents ni des cotisations.
## 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
## Version ??? - ??/??/????
@ -65,6 +68,8 @@ adhérents ni des cotisations.
- 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
### 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
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;
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
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
`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"),
("cb", "CB"),
("cheque", "Chèque"),
("virement", "Virement"),
("autre", "Autre"),
)
@ -163,7 +164,7 @@ class Attribution(models.Model):
given = models.BooleanField("Donnée", default=False)
paid = models.BooleanField("Payée", default=False)
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):
@ -253,7 +254,11 @@ class ChoixSpectacle(models.Model):
class Meta:
ordering = ("priority",)
unique_together = (("participant", "spectacle"),)
constraints = [
models.UniqueConstraint(
fields=["participant", "spectacle"], name="unique_participation"
)
]
verbose_name = "voeu"
verbose_name_plural = "voeux"

View file

@ -1,5 +1,5 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% load static %}
{% block extra_head %}
<link type="text/css" rel="stylesheet" href="{% static "bda/css/bda.css" %}" />

View file

@ -1,5 +1,5 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% load static %}
{% block realcontent %}
<h2>État des inscriptions BdA</h2>

View file

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

View file

@ -1,5 +1,5 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% load static %}
{% block realcontent %}
<h2>{{ spectacle }}</h2>

View file

@ -11,9 +11,19 @@
<td>{{place.spectacle.date}}</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.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>
{% endfor %}
</table>
<h4 class="bda-prix">Reste à payer : {{ unpaid|floatformat }}€</h4>
<h4 class="bda-prix">Total à payer : {{ total|floatformat }}€</h4>
<br/>
<p>Ne manque pas un spectacle avec le

View file

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

View file

@ -1,5 +1,5 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% load static %}
{% block realcontent %}
<h2>Inscription à une revente</h2>

View file

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

View file

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

View file

@ -1,5 +1,5 @@
{% extends "base_title.html" %}
{% load staticfiles%}
{% load static %}
{% block realcontent %}
<h2>Inscriptions pour BdA-Revente</h2>

View file

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

View file

@ -1,5 +1,5 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% load static %}
{% block extra_head %}
<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.views import SpectacleListView
from gestioncof.decorators import buro_required
urlpatterns = [
url(
re_path(
r"^inscription/(?P<tirage_id>\d+)$",
views.inscription,
name="bda-tirage-inscription",
),
url(r"^places/(?P<tirage_id>\d+)$", views.places, name="bda-places-attribuees"),
url(r"^etat-places/(?P<tirage_id>\d+)$", views.etat_places, name="bda-etat-places"),
url(r"^tirage/(?P<tirage_id>\d+)$", views.tirage, name="bda-tirage"),
url(
re_path(r"^places/(?P<tirage_id>\d+)$", views.places, name="bda-places-attribuees"),
re_path(
r"^etat-places/(?P<tirage_id>\d+)$", views.etat_places, name="bda-etat-places"
),
re_path(r"^tirage/(?P<tirage_id>\d+)$", views.tirage, name="bda-tirage"),
re_path(
r"^spectacles/(?P<tirage_id>\d+)$",
buro_required(SpectacleListView.as_view()),
name="bda-liste-spectacles",
),
url(
re_path(
r"^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$",
views.spectacle,
name="bda-spectacle",
),
url(
re_path(
r"^spectacles/unpaid/(?P<tirage_id>\d+)$",
views.UnpaidParticipants.as_view(),
name="bda-unpaid",
),
url(
re_path(
r"^spectacles/autocomplete$",
views.spectacle_autocomplete,
name="bda-spectacle-autocomplete",
),
url(
re_path(
r"^participants/autocomplete$",
views.participant_autocomplete,
name="bda-participant-autocomplete",
),
# Urls BdA-Revente
url(
re_path(
r"^revente/(?P<tirage_id>\d+)/manage$",
views.revente_manage,
name="bda-revente-manage",
),
url(
re_path(
r"^revente/(?P<tirage_id>\d+)/subscribe$",
views.revente_subscribe,
name="bda-revente-subscribe",
),
url(
re_path(
r"^revente/(?P<tirage_id>\d+)/tirages$",
views.revente_tirages,
name="bda-revente-tirages",
),
url(
re_path(
r"^revente/(?P<spectacle_id>\d+)/buy$",
views.revente_buy,
name="bda-revente-buy",
),
url(
re_path(
r"^revente/(?P<revente_id>\d+)/confirm$",
views.revente_confirm,
name="bda-revente-confirm",
),
url(
re_path(
r"^revente/(?P<tirage_id>\d+)/shotgun$",
views.revente_shotgun,
name="bda-revente-shotgun",
),
url(r"^mails-rappel/(?P<spectacle_id>\d+)$", views.send_rappel, name="bda-rappels"),
url(r"^catalogue/(?P<request_type>[a-z]+)$", views.catalogue, name="bda-catalogue"),
re_path(
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"
).select_related("spectacle", "spectacle__location")
total = sum(place.spectacle.price for place in places)
unpaid = 0
filtered_places = []
places_dict = {}
spectacles = []
@ -124,6 +125,8 @@ def places(request, tirage_id):
places_dict[place.spectacle].double = True
else:
place.double = False
place.unpaid = 0
place.unpaid_price = 0
places_dict[place.spectacle] = place
spectacles.append(place.spectacle)
filtered_places.append(place)
@ -132,6 +135,12 @@ def places(request, tirage_id):
warning = True
else:
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
if warning:
messages.warning(
@ -147,6 +156,7 @@ def places(request, tirage_id):
"places": filtered_places,
"tirage": tirage,
"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
from django.apps import AppConfig, apps as global_apps
from django.db.models import Q
from django.db.models.signals import post_migrate

View file

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

View file

@ -31,7 +31,7 @@ class TestHomeView(TestCase):
user, backend="django.contrib.auth.backends.ModelBackend"
)
resp = self.client.get(reverse("bds:home"))
self.assertEquals(resp.status_code, 200)
self.assertEqual(resp.status_code, 200)
class TestRegistrationView(TestCase):
@ -48,12 +48,12 @@ class TestRegistrationView(TestCase):
# Logged-in but unprivileged GET
client.force_login(user, backend="django.contrib.auth.backends.ModelBackend")
resp = client.get(url)
self.assertEquals(resp.status_code, 403)
self.assertEqual(resp.status_code, 403)
# Burô user GET
give_bds_buro_permissions(user)
resp = client.get(url)
self.assertEquals(resp.status_code, 200)
self.assertEqual(resp.status_code, 200)
@mock.patch("gestioncof.signals.messages")
def test_get(self, mock_messages):
@ -68,9 +68,9 @@ class TestRegistrationView(TestCase):
# Logged-in but unprivileged GET
client.force_login(user, backend="django.contrib.auth.backends.ModelBackend")
resp = client.get(url)
self.assertEquals(resp.status_code, 403)
self.assertEqual(resp.status_code, 403)
# Burô user GET
give_bds_buro_permissions(user)
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 bds import views
from shared.views import SympaListView
app_name = "bds"
urlpatterns = [
@ -21,4 +22,10 @@ urlpatterns = [
name="members.expired",
),
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)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["event", "name"], name="unique_event_option"
)
]
verbose_name = _("option d'événement")
verbose_name_plural = _("options d'événement")
unique_together = [["event", "name"]]
def __str__(self):
return self.name
@ -87,9 +91,13 @@ class OptionChoice(models.Model):
choice = models.CharField(_("choix"), max_length=200)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["option", "choice"], name="unique_option_choice"
)
]
verbose_name = _("choix d'option d'événement")
verbose_name_plural = _("choix d'option d'événement")
unique_together = [["option", "choice"]]
def __str__(self):
return self.choice
@ -118,7 +126,9 @@ class ExtraField(models.Model):
field_type = models.CharField(_("type de champ"), max_length=9, choices=FIELD_TYPE)
class Meta:
unique_together = [["event", "name"]]
constraints = [
models.UniqueConstraint(fields=["event", "name"], name="unique_extra_field")
]
class ExtraFieldContent(models.Model):
@ -137,9 +147,13 @@ class ExtraFieldContent(models.Model):
)
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_plural = _("contenus d'un champ événement supplémentaire")
unique_together = [["field", "registration"]]
def __str__(self):
max_length = 50
@ -163,9 +177,13 @@ class Registration(models.Model):
)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["event", "user"], name="unique_registration"
)
]
verbose_name = _("inscription à un événement")
verbose_name_plural = _("inscriptions à un événement")
unique_together = [["event", "user"]]
def __str__(self):
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
from channels.asgi import get_channel_layer
import django
from channels.routing import get_default_application
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.search",
"wagtail.admin",
"wagtail.core",
"wagtail.contrib.modeladmin",
"wagtail",
# "wagtail.contrib.modeladmin",
"wagtail.contrib.routable_page",
"wagtailmenus",
"modelcluster",
@ -85,7 +85,6 @@ MIDDLEWARE = (
+ MIDDLEWARE
+ [
"djconfig.middleware.DjConfigMiddleware",
"wagtail.core.middleware.SiteMiddleware",
"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")
ASGI_APPLICATION = "gestioasso.routing.application"
# ---
# Auth-related stuff
# ---
@ -147,7 +148,7 @@ CACHES = {
CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgi_redis.RedisChannelLayer",
"BACKEND": "shared.channels.ChannelLayer",
"CONFIG": {
"hosts": [
(
@ -160,11 +161,9 @@ CHANNEL_LAYERS = {
)
]
},
"ROUTING": "gestioasso.routing.routing",
}
}
# ---
# reCAPTCHA settings
# https://github.com/praekelt/django-recaptcha
@ -206,7 +205,7 @@ MAIL_DATA = {
"REPLYTO": "cof@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>",
"REPLYTO": "La K-Fêt <chefs-k-fet@ens.fr>",
},

View file

@ -101,7 +101,7 @@ TEMPLATES = [
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"ENGINE": "django.db.backends.postgresql",
"NAME": DBNAME,
"USER": DBUSER,
"PASSWORD": DBPASSWD,
@ -111,6 +111,7 @@ DATABASES = {
SITE_ID = 1
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# ---
# Internationalization

View file

@ -27,6 +27,9 @@ ALLOWED_HOSTS = []
DEBUG = True
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
SYMPA_PASSWORD = b"sympa"
SYMPA_USERNAME = b"sympa"
if TESTING:
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
CHANNEL_LAYERS = {
"default": {
"BACKEND": "asgiref.inmemory.ChannelLayer",
"ROUTING": "gestioasso.routing.routing",
"BACKEND": "channels.layers.InMemoryChannelLayer",
}
}

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
"""
from django.conf import settings
from django.conf.urls.i18n import i18n_patterns
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.
# 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.STATIC_URL, document_root=settings.STATIC_ROOT)
# Wagtail URLs (wagtail.core urls must be last, as catch-all)
if "wagtail.core" in settings.INSTALLED_APPS:
# Wagtail URLs (wagtail urls must be last, as catch-all)
if "wagtail" in settings.INSTALLED_APPS:
from wagtail import urls as wagtail_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
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.urls import reverse
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 (
Club,

View file

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

View file

@ -3,9 +3,9 @@
from __future__ import unicode_literals
import django.db.models.deletion
import wagtail.blocks
import wagtail.contrib.routable_page.models
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.fields
import wagtail.images.blocks
from django.db import migrations, models
@ -72,18 +72,14 @@ class Migration(migrations.Migration):
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",
wagtail.core.fields.RichTextField(
null=True, verbose_name="Contenu"
),
wagtail.fields.RichTextField(null=True, verbose_name="Contenu"),
),
(
"body_en",
wagtail.core.fields.RichTextField(
null=True, verbose_name="Contenu"
),
wagtail.fields.RichTextField(null=True, verbose_name="Contenu"),
),
(
"is_event",
@ -138,46 +134,40 @@ class Migration(migrations.Migration):
to="wagtailcore.Page",
),
),
("body", wagtail.core.fields.RichTextField(verbose_name="Description")),
("body", wagtail.fields.RichTextField(verbose_name="Description")),
(
"body_fr",
wagtail.core.fields.RichTextField(
null=True, verbose_name="Description"
),
wagtail.fields.RichTextField(null=True, verbose_name="Description"),
),
(
"body_en",
wagtail.core.fields.RichTextField(
null=True, verbose_name="Description"
),
wagtail.fields.RichTextField(null=True, verbose_name="Description"),
),
(
"links",
wagtail.core.fields.StreamField(
wagtail.fields.StreamField(
[
(
"lien",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"url",
wagtail.core.blocks.URLBlock(required=True),
wagtail.blocks.URLBlock(required=True),
),
("texte", wagtail.core.blocks.CharBlock()),
("texte", wagtail.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"email",
wagtail.core.blocks.EmailBlock(
required=True
),
wagtail.blocks.EmailBlock(required=True),
),
("texte", wagtail.core.blocks.CharBlock()),
("texte", wagtail.blocks.CharBlock()),
]
),
),
@ -186,31 +176,29 @@ class Migration(migrations.Migration):
),
(
"links_fr",
wagtail.core.fields.StreamField(
wagtail.fields.StreamField(
[
(
"lien",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"url",
wagtail.core.blocks.URLBlock(required=True),
wagtail.blocks.URLBlock(required=True),
),
("texte", wagtail.core.blocks.CharBlock()),
("texte", wagtail.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"email",
wagtail.core.blocks.EmailBlock(
required=True
),
wagtail.blocks.EmailBlock(required=True),
),
("texte", wagtail.core.blocks.CharBlock()),
("texte", wagtail.blocks.CharBlock()),
]
),
),
@ -220,31 +208,29 @@ class Migration(migrations.Migration):
),
(
"links_en",
wagtail.core.fields.StreamField(
wagtail.fields.StreamField(
[
(
"lien",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"url",
wagtail.core.blocks.URLBlock(required=True),
wagtail.blocks.URLBlock(required=True),
),
("texte", wagtail.core.blocks.CharBlock()),
("texte", wagtail.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"email",
wagtail.core.blocks.EmailBlock(
required=True
),
wagtail.blocks.EmailBlock(required=True),
),
("texte", wagtail.core.blocks.CharBlock()),
("texte", wagtail.blocks.CharBlock()),
]
),
),
@ -286,17 +272,17 @@ class Migration(migrations.Migration):
),
(
"introduction",
wagtail.core.fields.RichTextField(verbose_name="Introduction"),
wagtail.fields.RichTextField(verbose_name="Introduction"),
),
(
"introduction_fr",
wagtail.core.fields.RichTextField(
wagtail.fields.RichTextField(
null=True, verbose_name="Introduction"
),
),
(
"introduction_en",
wagtail.core.fields.RichTextField(
wagtail.fields.RichTextField(
null=True, verbose_name="Introduction"
),
),
@ -329,27 +315,27 @@ class Migration(migrations.Migration):
),
(
"body",
wagtail.core.fields.StreamField(
wagtail.fields.StreamField(
[
(
"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()),
(
"iframe",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"url",
wagtail.core.blocks.URLBlock(
wagtail.blocks.URLBlock(
"Adresse de la page"
),
),
(
"height",
wagtail.core.blocks.CharBlock(
wagtail.blocks.CharBlock(
"Hauteur (en pixels)"
),
),
@ -361,27 +347,27 @@ class Migration(migrations.Migration):
),
(
"body_fr",
wagtail.core.fields.StreamField(
wagtail.fields.StreamField(
[
(
"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()),
(
"iframe",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"url",
wagtail.core.blocks.URLBlock(
wagtail.blocks.URLBlock(
"Adresse de la page"
),
),
(
"height",
wagtail.core.blocks.CharBlock(
wagtail.blocks.CharBlock(
"Hauteur (en pixels)"
),
),
@ -394,27 +380,27 @@ class Migration(migrations.Migration):
),
(
"body_en",
wagtail.core.fields.StreamField(
wagtail.fields.StreamField(
[
(
"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()),
(
"iframe",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"url",
wagtail.core.blocks.URLBlock(
wagtail.blocks.URLBlock(
"Adresse de la page"
),
),
(
"height",
wagtail.core.blocks.CharBlock(
wagtail.blocks.CharBlock(
"Hauteur (en pixels)"
),
),
@ -448,17 +434,17 @@ class Migration(migrations.Migration):
),
(
"introduction",
wagtail.core.fields.RichTextField(verbose_name="Introduction"),
wagtail.fields.RichTextField(verbose_name="Introduction"),
),
(
"introduction_fr",
wagtail.core.fields.RichTextField(
wagtail.fields.RichTextField(
null=True, verbose_name="Introduction"
),
),
(
"introduction_en",
wagtail.core.fields.RichTextField(
wagtail.fields.RichTextField(
null=True, verbose_name="Introduction"
),
),

View file

@ -1,7 +1,7 @@
# Generated by Django 2.2.8 on 2019-12-20 16:22
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
from django.db import migrations
@ -14,26 +14,26 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name="cofdirectoryentrypage",
name="links",
field=wagtail.core.fields.StreamField(
field=wagtail.fields.StreamField(
[
(
"lien",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
("url", wagtail.core.blocks.URLBlock(required=True)),
("texte", wagtail.core.blocks.CharBlock()),
("url", wagtail.blocks.URLBlock(required=True)),
("texte", wagtail.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"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(
model_name="cofdirectoryentrypage",
name="links_en",
field=wagtail.core.fields.StreamField(
field=wagtail.fields.StreamField(
[
(
"lien",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
("url", wagtail.core.blocks.URLBlock(required=True)),
("texte", wagtail.core.blocks.CharBlock()),
("url", wagtail.blocks.URLBlock(required=True)),
("texte", wagtail.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"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(
model_name="cofdirectoryentrypage",
name="links_fr",
field=wagtail.core.fields.StreamField(
field=wagtail.fields.StreamField(
[
(
"lien",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
("url", wagtail.core.blocks.URLBlock(required=True)),
("texte", wagtail.core.blocks.CharBlock()),
("url", wagtail.blocks.URLBlock(required=True)),
("texte", wagtail.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"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
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
from django.db import migrations
@ -14,35 +14,35 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name="cofdirectoryentrypage",
name="links",
field=wagtail.core.fields.StreamField(
field=wagtail.fields.StreamField(
[
(
"lien",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
("url", wagtail.core.blocks.URLBlock(required=True)),
("texte", wagtail.core.blocks.CharBlock()),
("url", wagtail.blocks.URLBlock(required=True)),
("texte", wagtail.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"email",
wagtail.core.blocks.EmailBlock(required=True),
wagtail.blocks.EmailBlock(required=True),
),
("texte", wagtail.core.blocks.CharBlock()),
("texte", wagtail.blocks.CharBlock()),
]
),
),
(
"info",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
("nom", wagtail.core.blocks.CharBlock(required=False)),
("texte", wagtail.core.blocks.CharBlock(required=True)),
("nom", wagtail.blocks.CharBlock(required=False)),
("texte", wagtail.blocks.CharBlock(required=True)),
]
),
),
@ -53,35 +53,35 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name="cofdirectoryentrypage",
name="links_en",
field=wagtail.core.fields.StreamField(
field=wagtail.fields.StreamField(
[
(
"lien",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
("url", wagtail.core.blocks.URLBlock(required=True)),
("texte", wagtail.core.blocks.CharBlock()),
("url", wagtail.blocks.URLBlock(required=True)),
("texte", wagtail.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"email",
wagtail.core.blocks.EmailBlock(required=True),
wagtail.blocks.EmailBlock(required=True),
),
("texte", wagtail.core.blocks.CharBlock()),
("texte", wagtail.blocks.CharBlock()),
]
),
),
(
"info",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
("nom", wagtail.core.blocks.CharBlock(required=False)),
("texte", wagtail.core.blocks.CharBlock(required=True)),
("nom", wagtail.blocks.CharBlock(required=False)),
("texte", wagtail.blocks.CharBlock(required=True)),
]
),
),
@ -93,35 +93,35 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name="cofdirectoryentrypage",
name="links_fr",
field=wagtail.core.fields.StreamField(
field=wagtail.fields.StreamField(
[
(
"lien",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
("url", wagtail.core.blocks.URLBlock(required=True)),
("texte", wagtail.core.blocks.CharBlock()),
("url", wagtail.blocks.URLBlock(required=True)),
("texte", wagtail.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
(
"email",
wagtail.core.blocks.EmailBlock(required=True),
wagtail.blocks.EmailBlock(required=True),
),
("texte", wagtail.core.blocks.CharBlock()),
("texte", wagtail.blocks.CharBlock()),
]
),
),
(
"info",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
[
("nom", wagtail.core.blocks.CharBlock(required=False)),
("texte", wagtail.core.blocks.CharBlock(required=True)),
("nom", wagtail.blocks.CharBlock(required=False)),
("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.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.core import blocks
from wagtail.core.fields import RichTextField, StreamField
from wagtail.core.models import Page
from wagtail.fields import RichTextField, StreamField
from wagtail.images.blocks import ImageChooserBlock
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.models import Page
# Page pouvant afficher des actualités
@ -69,10 +68,11 @@ class COFPage(Page):
("paragraph", blocks.RichTextBlock()),
("image", ImageChooserBlock()),
("iframe", IFrameBlock()),
]
],
use_json_field=True,
)
content_panels = Page.content_panels + [StreamFieldPanel("body")]
content_panels = Page.content_panels + [FieldPanel("body")]
subpage_types = ["COFDirectoryPage", "COFPage"]
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)
content_panels = Page.content_panels + [
ImageChooserPanel("image"),
FieldPanel("image"),
FieldPanel("chapo"),
FieldPanel("body", classname="full"),
FieldPanel("is_event"),
@ -204,6 +204,7 @@ class COFDirectoryEntryPage(Page):
),
],
blank=True,
use_json_field=True,
)
image = models.ForeignKey(
@ -216,9 +217,9 @@ class COFDirectoryEntryPage(Page):
)
content_panels = Page.content_panels + [
ImageChooserPanel("image"),
FieldPanel("image"),
FieldPanel("body", classname="full"),
StreamFieldPanel("links"),
FieldPanel("links"),
]
subpage_types = []

View file

@ -22,10 +22,10 @@
<section class="actulist">
{% 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 %}
{% 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 %}
{% for actu in page.actus %}
@ -44,10 +44,10 @@
{% endfor %}
{% 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 %}
{% 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 %}
</section>
{% endblock %}

View file

@ -2,7 +2,7 @@ from datetime import date, timedelta
from django import template
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

View file

@ -3,7 +3,7 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.forms import AuthenticationForm
from django.forms.formsets import BaseFormSet, formset_factory
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 bda.models import Spectacle
@ -276,7 +276,9 @@ class RegistrationProfileForm(forms.ModelForm):
self.fields["mailing_bda_revente"].initial = True
self.fields["mailing_unernestaparis"].initial = True
self.fields.keyOrder = [
class Meta:
model = CofProfile
fields = [
"login_clipper",
"phone",
"occupation",
@ -290,22 +292,6 @@ class RegistrationProfileForm(forms.ModelForm):
"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 = (
("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.models.signals import post_delete, post_save
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 shared.utils import choices_length
@ -194,8 +194,12 @@ class EventRegistration(models.Model):
paid = models.BooleanField("A payé", default=False)
class Meta:
constraints = [
models.UniqueConstraint(
fields=["user", "event"], name="unique_event_registration"
)
]
verbose_name = "Inscription"
unique_together = ("user", "event")
def __str__(self):
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")
class Meta:
constraints = [
models.UniqueConstraint(
fields=["user", "survey"], name="unique_survey_answer"
)
]
verbose_name = "Réponses"
unique_together = ("user", "survey")
def __str__(self):
return "Réponse de %s sondage %s" % (

View file

@ -1,7 +1,7 @@
from django.contrib import messages
from django.contrib.auth.signals import user_logged_in
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

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">
<html xmlns="http://www.w3.org/1999/xhtml" lang="fr">

View file

@ -1,5 +1,5 @@
{% extends "base_title.html" %}
{% load staticfiles %}
{% load static %}
{% block page_size %}col-sm-8{% endblock %}

View file

@ -1,4 +1,4 @@
{% load staticfiles %}
{% load static %}
<script type="text/javascript">
var supernifty_tristate = function() {
var

View file

@ -194,7 +194,9 @@ class RegistrationViewTests(ViewTestCaseMixin, TestCase):
)
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(
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))
self.assertQuerysetEqual(
others, map(str, expected_others), ordered=False, transform=str
others, map(str, expected_others), transform=str, ordered=False
)
self.assertQuerysetEqual(
members, map(str, expected_members), ordered=False, transform=str
members, map(str, expected_members), transform=str, ordered=False
)
self.assertSetEqual(
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.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"])
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.
# self.assertQuerysetEqual(
@ -975,7 +983,9 @@ class EventViewTests(ViewTestCaseMixin, TestCase):
self.assertIn(self.post_expected_message, get_messages(r.wsgi_request))
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.
# self.assertQuerysetEqual(
# er.comments.all(), map(repr, []),
@ -1029,7 +1039,10 @@ class EventStatusViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200)
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):
@ -1096,7 +1109,10 @@ class SurveyViewTests(ViewTestCaseMixin, TestCase):
a = self.s.surveyanswer_set.get(user=self.users["user"])
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):
@ -1115,7 +1131,9 @@ class SurveyViewTests(ViewTestCaseMixin, TestCase):
self.assertIn(self.post_expected_message, get_messages(r.wsgi_request))
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):
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.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):

View file

@ -4,6 +4,16 @@ from django.views.generic.base import TemplateView
from django_cas_ng import views as django_cas_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 = [
path("members", views.export_members, name="export.members"),
@ -162,4 +172,8 @@ urlpatterns = [
# Clubs
# -----
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.urls import reverse_lazy
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_cas_ng.views import LogoutView as CasLogoutView
from icalendar import Calendar, Event as Vevent

View file

@ -1,3 +1,2 @@
default_app_config = "kfet.apps.KFetConfig"
KFET_DELETED_TRIGRAMME = "☠☠☠"
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_TRIGRAMME = "GNR"

View file

@ -1,6 +1,6 @@
from django.apps import AppConfig
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):

View file

@ -1,5 +1,5 @@
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

View file

@ -1,7 +1,7 @@
from django.contrib.auth.models import Group, Permission
from django.db import models
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"]

View file

@ -3,7 +3,7 @@ from django.contrib.auth.signals import user_logged_in
from django.dispatch import receiver
from django.urls import reverse
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

View file

@ -40,6 +40,7 @@ class UserGroupFormTests(TestCase):
self.assertQuerysetEqual(
groups_field.queryset,
[repr(g.group_ptr) for g in self.kfet_groups],
transform=repr,
ordered=False,
)

View file

@ -9,7 +9,7 @@ from django.http import QueryDict
from django.shortcuts import redirect, render
from django.urls import reverse, reverse_lazy
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.generic import View
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 wagtail.core import hooks
from wagtail import hooks
@hooks.register("insert_editor_css")

View file

@ -1,7 +1,7 @@
from django.contrib.auth.models import Group
from django.core.management import call_command
from django.core.management.base import BaseCommand
from wagtail.core.models import Page, Site
from wagtail.models import Page, Site
class Command(BaseCommand):

View file

@ -2,8 +2,8 @@
from __future__ import unicode_literals
import django.db.models.deletion
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.blocks
import wagtail.fields
import wagtail.snippets.blocks
from django.db import migrations, models
@ -41,20 +41,20 @@ class Migration(migrations.Migration):
),
(
"content",
wagtail.core.fields.StreamField(
wagtail.fields.StreamField(
(
(
"rich",
wagtail.core.blocks.RichTextBlock(label="Éditeur"),
wagtail.blocks.RichTextBlock(label="Éditeur"),
),
("carte", kfet.cms.models.MenuBlock()),
(
"group_team",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
(
(
"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
required=False,
label="Montrer seulement",
@ -62,7 +62,7 @@ class Migration(migrations.Migration):
),
(
"members",
wagtail.core.blocks.ListBlock(
wagtail.blocks.ListBlock(
wagtail.snippets.blocks.SnippetChooserBlock( # noqa
kfet.cms.models.MemberTeam
),
@ -75,22 +75,22 @@ class Migration(migrations.Migration):
),
(
"group",
wagtail.core.blocks.StreamBlock(
wagtail.blocks.StreamBlock(
(
(
"rich",
wagtail.core.blocks.RichTextBlock(
wagtail.blocks.RichTextBlock(
label="Éditeur"
),
),
("carte", kfet.cms.models.MenuBlock()),
(
"group_team",
wagtail.core.blocks.StructBlock(
wagtail.blocks.StructBlock(
(
(
"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
required=False,
label="Montrer seulement",
@ -98,7 +98,7 @@ class Migration(migrations.Migration):
),
(
"members",
wagtail.core.blocks.ListBlock(
wagtail.blocks.ListBlock(
wagtail.snippets.blocks.SnippetChooserBlock( # 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.utils.translation import ugettext_lazy as _
from wagtail.admin.edit_handlers import (
FieldPanel,
FieldRowPanel,
MultiFieldPanel,
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 django.utils.translation import gettext_lazy as _
from wagtail import blocks
from wagtail.admin.panels import FieldPanel, FieldRowPanel, MultiFieldPanel
from wagtail.fields import StreamField
from wagtail.models import Page
from wagtail.snippets.blocks import SnippetChooserBlock
from wagtail.snippets.models import register_snippet
@ -43,7 +37,7 @@ class MemberTeam(models.Model):
FieldPanel("first_name"),
FieldPanel("last_name"),
FieldPanel("nick_name"),
ImageChooserPanel("photo"),
FieldPanel("photo"),
]
def __str__(self):
@ -97,7 +91,9 @@ class KFetStreamBlock(ChoicesStreamBlock):
class KFetPage(Page):
content = StreamField(KFetStreamBlock, verbose_name=_("Contenu"))
content = StreamField(
KFetStreamBlock, verbose_name=_("Contenu"), use_json_field=True
)
# Layout fields
@ -135,7 +131,7 @@ class KFetPage(Page):
# Panels
content_panels = Page.content_panels + [StreamFieldPanel("content")]
content_panels = Page.content_panels + [FieldPanel("content")]
layout_panel = [
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
class KPsul(PermConsumerMixin, DjangoJsonWebsocketConsumer):
groups = ["kfet.kpsul"]
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.forms import modelformset_factory
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 gestioncof.models import CofProfile
@ -93,7 +93,7 @@ class DemandeSoireeForm(forms.Form):
def default_promo():
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():

View file

@ -4,7 +4,6 @@ from __future__ import unicode_literals
import datetime
from django.db import migrations, models
from django.utils.timezone import utc
class Migration(migrations.Migration):
@ -17,7 +16,9 @@ class Migration(migrations.Migration):
name="at",
field=models.DateTimeField(
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,
),

View file

@ -2,6 +2,7 @@ import re
from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.core.mail import EmailMessage
from django.core.validators import RegexValidator
from django.db import models, transaction
@ -9,7 +10,7 @@ from django.db.models import F
from django.template import loader
from django.urls import reverse
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 shared.utils import choices_length
@ -269,6 +270,36 @@ class Account(models.Model):
def __init__(self, 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():
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.
"""
mail_data = settings.MAIL_DATA["rappel_negatif"]
mail_data = settings.MAIL_DATA["kfet"]
email = EmailMessage(
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
self.last_rappel = timezone.now()
self.save()
return
class CheckoutQuerySet(models.QuerySet):

View file

@ -1,3 +1,5 @@
from asgiref.sync import sync_to_async
from ..decorators import kfet_is_team
from ..utils import DjangoJsonWebsocketConsumer, PermConsumerMixin
from .open import kfet_open
@ -12,13 +14,15 @@ class OpenKfetConsumer(PermConsumerMixin, DjangoJsonWebsocketConsumer):
"""
def connection_groups(self, user, **kwargs):
"""Select which group the user should be connected."""
if kfet_is_team(user):
return ["kfet.open.team"]
return ["kfet.open.base"]
async def open_status(self, event):
await self.send_json(event)
def connect(self, message, *args, **kwargs):
async def connect(self):
"""Send current status on connect."""
super().connect(message, *args, **kwargs)
self.send(kfet_open.export(message.user))
await super().connect()
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 channels.layers import get_channel_layer
from django.utils import timezone
from ..decorators import kfet_is_team
@ -77,7 +78,7 @@ class OpenKfet(CachedMixin, object):
"""
status = self.status()
base = {"status": status}
base = {"status": status, "type": "open.status"}
restrict = {
"admin_status": self.admin_status(status),
"force_close": self.force_close,
@ -95,13 +96,14 @@ class OpenKfet(CachedMixin, object):
base, team = self._export()
return team if kfet_is_team(user) else base
def send_ws(self):
async def send_ws(self):
"""Send internal state to websocket channels."""
from .consumers import OpenKfetConsumer
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()

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
from datetime import timedelta
from unittest import mock
from channels.channel import Group
from channels.test import ChannelTestCase, WSClient
from asgiref.sync import async_to_sync
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.test import Client
from django.test import Client, TestCase
from django.utils import timezone
from . import OpenKfet
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."""
def setUp(self):
@ -79,7 +84,7 @@ class OpenKfetTest(ChannelTestCase):
def test_export_user(self):
"""Export is limited for an anonymous user."""
export = self.kfet_open.export(AnonymousUser())
self.assertSetEqual(set(["status"]), set(export))
self.assertSetEqual(set(["status", "type"]), set(export))
def test_export_team(self):
"""Export all values for a team member."""
@ -89,24 +94,32 @@ class OpenKfetTest(ChannelTestCase):
)
user.user_permissions.add(is_team)
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):
Group("kfet.open.base").add("test.open.base")
Group("kfet.open.team").add("test.open.team")
async def test_send_ws(self):
channel_layer = get_channel_layer()
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)
base = json.loads(recv_base["text"])
self.assertSetEqual(set(["status"]), set(base))
await self.kfet_open.send_ws()
recv_admin = self.get_next_message("test.open.team", require=True)
admin = json.loads(recv_admin["text"])
self.assertSetEqual(set(["status", "admin_status", "force_close"]), set(admin))
base = await channel_layer.receive(base_channel)
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."""
def setUp(self):
@ -177,119 +190,136 @@ class OpenKfetViewsTest(ChannelTestCase):
self.assertEqual(403, resp.status_code)
class OpenKfetConsumerTest(ChannelTestCase):
class OpenKfetConsumerTest(TestCase):
"""OpenKfet consumer unit-tests suite."""
def test_standard_user(self):
"""Lambda user is added to kfet.open.base group."""
# 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
@classmethod
def setUpTestData(cls):
t = User.objects.create_user("team", "", "team")
is_team = Permission.objects.get(
codename="is_team", content_type__app_label="kfet"
)
t.user_permissions.add(is_team)
c = WSClient()
c.force_login(t, backend="django.contrib.auth.backends.ModelBackend")
# connect
c.send_and_consume(
"websocket.connect", path="/ws/k-fet/open", fail_on_none=True
)
cls.team_user = t
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
self.assertIsNotNone(c.receive())
message = await c.receive_json_from()
self.assertIsNotNone(message)
# client belongs to the 'kfet.open.admin' group...
OpenKfetConsumer.group_send("kfet.open.team", {"test": "plop"})
self.assertEqual(c.receive(), {"test": "plop"})
# client belongs to the 'kfet.open' group...
channel_layer = get_channel_layer()
# ... but not to the 'kfet.open' one
OpenKfetConsumer.group_send("kfet.open.base", {"test": "plop"})
self.assertIsNone(c.receive())
await channel_layer.group_send(
"kfet.open.base", {"test": "plop", "type": "open.status"}
)
message = await c.receive_json_from()
self.assertEqual(message, {"test": "plop"})
# ...but not to the 'kfet.open.admin' one
await channel_layer.group_send(
"kfet.open.team", {"test": "plop", "type": "open.status"}
)
self.assertTrue(await c.receive_nothing())
async def test_team_user(self):
"""Team user is added to kfet.open.team group."""
# On simule l'appartenance de l'user à la team kfet car l'utilisation de
# tests async avec postgres fait tout planter si on modifie la db dans un
# des sous tests.
with mock.patch("gestioncof.signals.messages"), mock.patch(
"kfet.open.consumers.kfet_is_team", return_value=True
):
c = ws_communicator(OpenKfetConsumer, "/ws/k-fet/open")
connected, _ = await c.connect()
channel_layer = get_channel_layer()
self.assertTrue(connected)
# initialization data is replied on connection
message = await c.receive_json_from()
self.assertIsNotNone(message)
# client belongs to the 'kfet.open.team' group...
await channel_layer.group_send(
"kfet.open.team", {"test": "plop", "type": "open.status"}
)
message = await c.receive_json_from()
self.assertEqual(message, {"test": "plop"})
# ...but not to the 'kfet.open' one
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."""
def setUp(self):
# Need this (and here) because of '<client>.login' in setUp
patcher_messages = mock.patch("gestioncof.signals.messages")
patcher_messages.start()
self.addCleanup(patcher_messages.stop)
@classmethod
def setUpTestData(cls):
# root user
cls.r = User.objects.create_superuser("team", "", "team")
# anonymous client (for views)
self.c = Client()
# anonymous client (for websockets)
self.c_ws = WSClient()
cls.c = Client()
# root user
self.r = User.objects.create_superuser("root", "", "root")
# its client (for views)
self.r_c = Client()
self.r_c.login(username="root", password="root")
# its client (for websockets)
self.r_c_ws = WSClient()
self.r_c_ws.force_login(
self.r, backend="django.contrib.auth.backends.ModelBackend"
)
# root client
cls.r_c = Client()
with mock.patch("gestioncof.signals.messages"):
cls.r_c.login(username="team", password="team")
def setUp(self):
# Create channels to listen to messages
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(
cache_prefix="test_kfetopen_%s" % random.randrange(2**20)
)
self.addCleanup(self.kfet_open.clear_cache)
def ws_connect(self, ws_client):
ws_client.send_and_consume(
"websocket.connect", path="/ws/k-fet/open", fail_on_none=True
)
return ws_client.receive(json=True)
async def ws_connect(self, ws_communicator):
c, _ = await ws_communicator.connect()
def test_scenario_0(self):
"""Clients connect."""
# 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))
self.assertTrue(c)
return await ws_communicator.receive_json_from()
def test_scenario_1(self):
"""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!"
self.c.post("/k-fet/open/raw_open", {"raw_open": True, "token": "plop"})
# anonymous user agree
msg = self.c_ws.receive(json=True)
msg = self.receive_msg(self.channel)
self.assertEqual(OpenKfet.OPENED, msg["status"])
# 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["admin_status"])
@ -297,11 +327,11 @@ class OpenKfetScenarioTest(ChannelTestCase):
self.r_c.post("/k-fet/open/force_close", {"force_close": True})
# 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"])
# root user too
msg = self.r_c_ws.receive(json=True)
msg = self.receive_msg(self.team_channel)
self.assertEqual(OpenKfet.CLOSED, msg["status"])
# but root knows things
self.assertEqual(OpenKfet.FAKE_CLOSED, msg["admin_status"])
@ -312,20 +342,42 @@ class OpenKfetScenarioTest(ChannelTestCase):
self.kfet_open.raw_open = 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"])
msg = self.ws_connect(self.r_c_ws)
msg = self.receive_msg(self.team_channel)
self.assertEqual(OpenKfet.CLOSED, msg["status"])
self.assertEqual(OpenKfet.FAKE_CLOSED, msg["admin_status"])
self.assertTrue(msg["force_close"])
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"])
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["admin_status"])
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.contrib.auth.decorators import permission_required
from django.core.exceptions import PermissionDenied
@ -18,7 +19,7 @@ def raw_open(request):
raise PermissionDenied
raw_open = request.POST.get("raw_open") in TRUE_STR
kfet_open.raw_open = raw_open
kfet_open.send_ws()
async_to_sync(kfet_open.send_ws)()
return HttpResponse()
@ -27,5 +28,5 @@ def raw_open(request):
def force_close(request):
force_close = request.POST.get("force_close") in TRUE_STR
kfet_open.force_close = force_close
kfet_open.send_ws()
async_to_sync(kfet_open.send_ws)()
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 = [
route_class(consumers.KPsul, path=r"^/k-psul/$"),
include("kfet.open.routing.routing", path=r"^/open"),
]
from .consumers import KPsul
KFRouter = URLRouter(
[
path("k-psul/", KPsul.as_asgi()),
path("open", OpenRouter),
]
)

View file

@ -78,7 +78,7 @@ class KfetWebsocket {
listen() {
var that = this;
this.socket = new ReconnectingWebSocket(this.url);
this.socket = new ReconnectingWebSocket(this.url, [], { minReconnectionDelay: 100 });
this.socket.onmessage = function (e) {
var data = $.extend({}, that.default_msg, JSON.parse(e.data));

View file

@ -31,6 +31,28 @@
{% endif %}
</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 %}
{% block main %}

View file

@ -1,5 +1,5 @@
{% extends "kfet/base_form.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Nouveau compte{% endblock %}
{% block header-title %}Création d'un compte{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends "kfet/base.html" %}
{% load staticfiles %}
{% load static %}
{% block title %}Nouveau compte{% endblock %}
{% block header-title %}Création d'un compte{% endblock %}

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
{% extends 'kfet/base_col_2.html' %}
{% load l10n staticfiles widget_tweaks bootstrap %}
{% load l10n static widget_tweaks bootstrap %}
{% block extra_head %}
<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" %}
{% load staticfiles %}
{% load static %}
{% load kfet_tags %}
{% block title %}Accueil{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends 'kfet/base.html' %}
{% load staticfiles %}
{% load static %}
{% block extra_head %}
<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' %}
{% load staticfiles %}
{% load l10n staticfiles widget_tweaks %}
{% load l10n static widget_tweaks %}
{% block title %}Transferts{% endblock %}
{% block header-title %}Transferts{% endblock %}

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