diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b0c1f4c6..ce3bd041 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f7b871d0..b68fb40c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/bda/migrations/0019_auto_20220630_1245.py b/bda/migrations/0019_auto_20220630_1245.py
new file mode 100644
index 00000000..12b7149d
--- /dev/null
+++ b/bda/migrations/0019_auto_20220630_1245.py
@@ -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"
+ ),
+ ),
+ ]
diff --git a/bda/models.py b/bda/models.py
index 578f235c..9bc2ce3c 100644
--- a/bda/models.py
+++ b/bda/models.py
@@ -253,7 +253,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"
diff --git a/bda/templates/bda-attrib.html b/bda/templates/bda-attrib.html
index fac0de67..057cacb4 100644
--- a/bda/templates/bda-attrib.html
+++ b/bda/templates/bda-attrib.html
@@ -1,5 +1,5 @@
{% extends "base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block extra_head %}
diff --git a/bda/templates/bda/etat-places.html b/bda/templates/bda/etat-places.html
index 401cc856..d1af0667 100644
--- a/bda/templates/bda/etat-places.html
+++ b/bda/templates/bda/etat-places.html
@@ -1,5 +1,5 @@
{% extends "base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block realcontent %}
État des inscriptions BdA
diff --git a/bda/templates/bda/inscription-tirage.html b/bda/templates/bda/inscription-tirage.html
index 3f8091df..1eecd7af 100644
--- a/bda/templates/bda/inscription-tirage.html
+++ b/bda/templates/bda/inscription-tirage.html
@@ -1,5 +1,5 @@
{% extends "base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block extra_head %}
diff --git a/bda/templates/bda/participants.html b/bda/templates/bda/participants.html
index 4ab2d1f7..c99e5182 100644
--- a/bda/templates/bda/participants.html
+++ b/bda/templates/bda/participants.html
@@ -1,5 +1,5 @@
{% extends "base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block realcontent %}
{{ spectacle }}
diff --git a/bda/templates/bda/revente/confirm-shotgun.html b/bda/templates/bda/revente/confirm-shotgun.html
index d7614c25..bf8dccba 100644
--- a/bda/templates/bda/revente/confirm-shotgun.html
+++ b/bda/templates/bda/revente/confirm-shotgun.html
@@ -1,5 +1,5 @@
{% extends "base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{%block realcontent %}
diff --git a/bda/templates/bda/revente/confirmed.html b/bda/templates/bda/revente/confirmed.html
index 780330bd..6f8ee583 100644
--- a/bda/templates/bda/revente/confirmed.html
+++ b/bda/templates/bda/revente/confirmed.html
@@ -1,5 +1,5 @@
{% extends "base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block realcontent %}
Inscription à une revente
diff --git a/bda/templates/bda/revente/mail-success.html b/bda/templates/bda/revente/mail-success.html
index 5e970eb7..6340a451 100644
--- a/bda/templates/bda/revente/mail-success.html
+++ b/bda/templates/bda/revente/mail-success.html
@@ -1,5 +1,5 @@
{% extends "base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block realcontent %}
diff --git a/bda/templates/bda/revente/manage.html b/bda/templates/bda/revente/manage.html
index cd09f997..c42e0203 100644
--- a/bda/templates/bda/revente/manage.html
+++ b/bda/templates/bda/revente/manage.html
@@ -1,5 +1,5 @@
{% extends "base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block realcontent %}
diff --git a/bda/templates/bda/revente/subscribe.html b/bda/templates/bda/revente/subscribe.html
index e0a7176c..c91fff15 100644
--- a/bda/templates/bda/revente/subscribe.html
+++ b/bda/templates/bda/revente/subscribe.html
@@ -1,5 +1,5 @@
{% extends "base_title.html" %}
-{% load staticfiles%}
+{% load static %}
{% block realcontent %}
Inscriptions pour BdA-Revente
diff --git a/bda/templates/bda/revente/tirages.html b/bda/templates/bda/revente/tirages.html
index 4d9ac126..6ef55e03 100644
--- a/bda/templates/bda/revente/tirages.html
+++ b/bda/templates/bda/revente/tirages.html
@@ -1,5 +1,5 @@
{% extends "base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block realcontent %}
diff --git a/bda/templates/spectacle_list.html b/bda/templates/spectacle_list.html
index 4539d730..1ffd7cc3 100644
--- a/bda/templates/spectacle_list.html
+++ b/bda/templates/spectacle_list.html
@@ -1,5 +1,5 @@
{% extends "base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block extra_head %}
diff --git a/bda/urls.py b/bda/urls.py
index 5b452362..726c4057 100644
--- a/bda/urls.py
+++ b/bda/urls.py
@@ -1,74 +1,80 @@
-from django.conf.urls import url
+from django.urls import re_path
from bda import views
from bda.views import SpectacleListView
from gestioncof.decorators import buro_required
urlpatterns = [
- url(
+ re_path(
r"^inscription/(?P\d+)$",
views.inscription,
name="bda-tirage-inscription",
),
- url(r"^places/(?P\d+)$", views.places, name="bda-places-attribuees"),
- url(r"^etat-places/(?P\d+)$", views.etat_places, name="bda-etat-places"),
- url(r"^tirage/(?P\d+)$", views.tirage, name="bda-tirage"),
- url(
+ re_path(r"^places/(?P\d+)$", views.places, name="bda-places-attribuees"),
+ re_path(
+ r"^etat-places/(?P\d+)$", views.etat_places, name="bda-etat-places"
+ ),
+ re_path(r"^tirage/(?P\d+)$", views.tirage, name="bda-tirage"),
+ re_path(
r"^spectacles/(?P\d+)$",
buro_required(SpectacleListView.as_view()),
name="bda-liste-spectacles",
),
- url(
+ re_path(
r"^spectacles/(?P\d+)/(?P\d+)$",
views.spectacle,
name="bda-spectacle",
),
- url(
+ re_path(
r"^spectacles/unpaid/(?P\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\d+)/manage$",
views.revente_manage,
name="bda-revente-manage",
),
- url(
+ re_path(
r"^revente/(?P\d+)/subscribe$",
views.revente_subscribe,
name="bda-revente-subscribe",
),
- url(
+ re_path(
r"^revente/(?P\d+)/tirages$",
views.revente_tirages,
name="bda-revente-tirages",
),
- url(
+ re_path(
r"^revente/(?P\d+)/buy$",
views.revente_buy,
name="bda-revente-buy",
),
- url(
+ re_path(
r"^revente/(?P\d+)/confirm$",
views.revente_confirm,
name="bda-revente-confirm",
),
- url(
+ re_path(
r"^revente/(?P\d+)/shotgun$",
views.revente_shotgun,
name="bda-revente-shotgun",
),
- url(r"^mails-rappel/(?P\d+)$", views.send_rappel, name="bda-rappels"),
- url(r"^catalogue/(?P[a-z]+)$", views.catalogue, name="bda-catalogue"),
+ re_path(
+ r"^mails-rappel/(?P\d+)$", views.send_rappel, name="bda-rappels"
+ ),
+ re_path(
+ r"^catalogue/(?P[a-z]+)$", views.catalogue, name="bda-catalogue"
+ ),
]
diff --git a/bds/__init__.py b/bds/__init__.py
index 5c287005..e69de29b 100644
--- a/bds/__init__.py
+++ b/bds/__init__.py
@@ -1 +0,0 @@
-default_app_config = "bds.apps.BdsConfig"
diff --git a/bds/apps.py b/bds/apps.py
index 5c0fa0fd..740d3559 100644
--- a/bds/apps.py
+++ b/bds/apps.py
@@ -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
diff --git a/bds/templates/bds/base.html b/bds/templates/bds/base.html
index f456f6dc..74759e88 100644
--- a/bds/templates/bds/base.html
+++ b/bds/templates/bds/base.html
@@ -1,4 +1,4 @@
-{% load staticfiles %}
+{% load static %}
{% load bulma_utils %}
diff --git a/bds/tests/test_views.py b/bds/tests/test_views.py
index ef6139f4..332db8d7 100644
--- a/bds/tests/test_views.py
+++ b/bds/tests/test_views.py
@@ -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)
diff --git a/events/migrations/0005_auto_20220630_1239.py b/events/migrations/0005_auto_20220630_1239.py
new file mode 100644
index 00000000..d2624da2
--- /dev/null
+++ b/events/migrations/0005_auto_20220630_1239.py
@@ -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"
+ ),
+ ),
+ ]
diff --git a/events/models.py b/events/models.py
index 7b536c86..a421e8a3 100644
--- a/events/models.py
+++ b/events/models.py
@@ -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)
diff --git a/gestioasso/asgi.py b/gestioasso/asgi.py
index 773acaa0..728a3433 100644
--- a/gestioasso/asgi.py
+++ b/gestioasso/asgi.py
@@ -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()
diff --git a/gestioasso/routing.py b/gestioasso/routing.py
index 3c2e5718..2b42648a 100644
--- a/gestioasso/routing.py
+++ b/gestioasso/routing.py
@@ -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(),
+ }
+)
diff --git a/gestioasso/settings/cof_prod.py b/gestioasso/settings/cof_prod.py
index 9e3f9f70..58496057 100644
--- a/gestioasso/settings/cof_prod.py
+++ b/gestioasso/settings/cof_prod.py
@@ -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
diff --git a/gestioasso/settings/common.py b/gestioasso/settings/common.py
index cabe7000..13f2e5b1 100644
--- a/gestioasso/settings/common.py
+++ b/gestioasso/settings/common.py
@@ -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
diff --git a/gestioasso/settings/local.py b/gestioasso/settings/local.py
index 5c8c2734..2cba6a10 100644
--- a/gestioasso/settings/local.py
+++ b/gestioasso/settings/local.py
@@ -47,8 +47,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",
}
}
diff --git a/gestioncof/__init__.py b/gestioncof/__init__.py
index 3bb260b9..e69de29b 100644
--- a/gestioncof/__init__.py
+++ b/gestioncof/__init__.py
@@ -1 +0,0 @@
-default_app_config = "gestioncof.apps.GestioncofConfig"
diff --git a/gestioncof/admin.py b/gestioncof/admin.py
index 89e4160d..bb90cf98 100644
--- a/gestioncof/admin.py
+++ b/gestioncof/admin.py
@@ -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,
diff --git a/gestioncof/cms/__init__.py b/gestioncof/cms/__init__.py
index 043b644d..e69de29b 100644
--- a/gestioncof/cms/__init__.py
+++ b/gestioncof/cms/__init__.py
@@ -1 +0,0 @@
-default_app_config = "gestioncof.cms.apps.COFCMSAppConfig"
diff --git a/gestioncof/cms/templatetags/cofcms_tags.py b/gestioncof/cms/templatetags/cofcms_tags.py
index f9e62aed..36774232 100644
--- a/gestioncof/cms/templatetags/cofcms_tags.py
+++ b/gestioncof/cms/templatetags/cofcms_tags.py
@@ -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
diff --git a/gestioncof/forms.py b/gestioncof/forms.py
index 2a57f970..1d482e7f 100644
--- a/gestioncof/forms.py
+++ b/gestioncof/forms.py
@@ -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"),
diff --git a/gestioncof/migrations/0019_auto_20220630_1241.py b/gestioncof/migrations/0019_auto_20220630_1241.py
new file mode 100644
index 00000000..eec1cfe3
--- /dev/null
+++ b/gestioncof/migrations/0019_auto_20220630_1241.py
@@ -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"
+ ),
+ ),
+ ]
diff --git a/gestioncof/models.py b/gestioncof/models.py
index d16b3db2..c6d32efc 100644
--- a/gestioncof/models.py
+++ b/gestioncof/models.py
@@ -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" % (
diff --git a/gestioncof/signals.py b/gestioncof/signals.py
index cf4b1f16..deed6ce9 100644
--- a/gestioncof/signals.py
+++ b/gestioncof/signals.py
@@ -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
diff --git a/gestioncof/templates/base.html b/gestioncof/templates/base.html
index d313ee9d..7020e3c5 100644
--- a/gestioncof/templates/base.html
+++ b/gestioncof/templates/base.html
@@ -1,4 +1,4 @@
-{% load staticfiles %}
+{% load static %}
diff --git a/gestioncof/templates/registration.html b/gestioncof/templates/registration.html
index 2ef997e1..9807afde 100644
--- a/gestioncof/templates/registration.html
+++ b/gestioncof/templates/registration.html
@@ -1,5 +1,5 @@
{% extends "base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block page_size %}col-sm-8{% endblock %}
diff --git a/gestioncof/templates/tristate_js.html b/gestioncof/templates/tristate_js.html
index af906ebe..6b5312a8 100644
--- a/gestioncof/templates/tristate_js.html
+++ b/gestioncof/templates/tristate_js.html
@@ -1,4 +1,4 @@
-{% load staticfiles %}
+{% load static %}
diff --git a/kfet/templates/kfet/history.html b/kfet/templates/kfet/history.html
index 93cd25f9..e80d0014 100644
--- a/kfet/templates/kfet/history.html
+++ b/kfet/templates/kfet/history.html
@@ -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 %}
diff --git a/kfet/templates/kfet/home.html b/kfet/templates/kfet/home.html
index e5175dc3..8704bbe9 100644
--- a/kfet/templates/kfet/home.html
+++ b/kfet/templates/kfet/home.html
@@ -1,5 +1,5 @@
{% extends "kfet/base_col_1.html" %}
-{% load staticfiles %}
+{% load static %}
{% load kfet_tags %}
{% block title %}Accueil{% endblock %}
diff --git a/kfet/templates/kfet/kpsul.html b/kfet/templates/kfet/kpsul.html
index a49d1677..ad8ee240 100644
--- a/kfet/templates/kfet/kpsul.html
+++ b/kfet/templates/kfet/kpsul.html
@@ -1,5 +1,5 @@
{% extends 'kfet/base.html' %}
-{% load staticfiles %}
+{% load static %}
{% block extra_head %}
diff --git a/kfet/templates/kfet/transfers.html b/kfet/templates/kfet/transfers.html
index d86115aa..f285b4dc 100644
--- a/kfet/templates/kfet/transfers.html
+++ b/kfet/templates/kfet/transfers.html
@@ -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 %}
diff --git a/kfet/templates/kfet/transfers_create.html b/kfet/templates/kfet/transfers_create.html
index fc429d97..3a85264d 100644
--- a/kfet/templates/kfet/transfers_create.html
+++ b/kfet/templates/kfet/transfers_create.html
@@ -1,5 +1,5 @@
{% extends "kfet/base_col_1.html" %}
-{% load staticfiles %}
+{% load static %}
{% block extra_head %}
diff --git a/kfet/tests/test_tests_utils.py b/kfet/tests/test_tests_utils.py
index 49661e23..2c42ff79 100644
--- a/kfet/tests/test_tests_utils.py
+++ b/kfet/tests/test_tests_utils.py
@@ -94,6 +94,7 @@ class PermHelpersTest(TestCaseMixin, TestCase):
self.assertQuerysetEqual(
user.user_permissions.all(),
map(repr, [self.perm1, self.perm2, self.perm_team]),
+ transform=repr,
ordered=False,
)
diff --git a/kfet/tests/test_views.py b/kfet/tests/test_views.py
index 7a7eddcb..d09ff3e8 100644
--- a/kfet/tests/test_views.py
+++ b/kfet/tests/test_views.py
@@ -3,6 +3,8 @@ from datetime import datetime, timedelta
from decimal import Decimal
from unittest import mock
+from asgiref.sync import async_to_sync
+from channels.layers import get_channel_layer
from django.contrib.auth.models import User
from django.test import Client, TestCase
from django.urls import reverse
@@ -518,6 +520,7 @@ class AccountGroupCreateViewTests(ViewTestCaseMixin, TestCase):
self.assertQuerysetEqual(
group.permissions.all(),
map(repr, [self.perms["kfet.is_team"], self.perms["kfet.manage_perms"]]),
+ transform=repr,
ordered=False,
)
@@ -571,6 +574,7 @@ class AccountGroupUpdateViewTests(ViewTestCaseMixin, TestCase):
self.assertQuerysetEqual(
self.group.permissions.all(),
map(repr, [self.perms["kfet.is_team"], self.perms["kfet.manage_perms"]]),
+ transform=repr,
ordered=False,
)
@@ -598,6 +602,7 @@ class AccountNegativeListViewTests(ViewTestCaseMixin, TestCase):
self.assertQuerysetEqual(
r.context["negatives"],
map(repr, [self.accounts["user"].negative]),
+ transform=repr,
ordered=False,
)
@@ -698,7 +703,7 @@ class AccountStatOperationListViewTests(ViewTestCaseMixin, TestCase):
for stat, expected in zip(content["stats"], expected_stats):
expected_url = expected.pop("url")
self.assertUrlsEqual(stat["url"], expected_url)
- self.assertDictContainsSubset(expected, stat)
+ self.assertEqual(stat, {**stat, **expected})
class AccountStatOperationViewTests(ViewTestCaseMixin, TestCase):
@@ -807,7 +812,7 @@ class AccountStatBalanceListViewTests(ViewTestCaseMixin, TestCase):
for stat, expected in zip(content["stats"], expected_stats):
expected_url = expected.pop("url")
self.assertUrlsEqual(stat["url"], expected_url)
- self.assertDictContainsSubset(expected, stat)
+ self.assertEqual(stat, {**stat, **expected})
class AccountStatBalanceViewTests(ViewTestCaseMixin, TestCase):
@@ -875,6 +880,7 @@ class CheckoutListViewTests(ViewTestCaseMixin, TestCase):
self.assertQuerysetEqual(
r.context["checkouts"],
map(repr, [self.checkout1, self.checkout2]),
+ transform=repr,
ordered=False,
)
@@ -1065,6 +1071,7 @@ class CheckoutStatementListViewTests(ViewTestCaseMixin, TestCase):
self.assertQuerysetEqual(
r.context["checkoutstatements"],
map(repr, expected_statements),
+ transform=repr,
ordered=False,
)
@@ -1291,7 +1298,9 @@ class ArticleCategoryListViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200)
self.assertQuerysetEqual(
- r.context["categories"], map(repr, [self.category1, self.category2])
+ r.context["categories"],
+ map(repr, [self.category1, self.category2]),
+ transform=repr,
)
@@ -1366,7 +1375,9 @@ class ArticleListViewTests(ViewTestCaseMixin, TestCase):
r = self.client.get(self.url)
self.assertEqual(r.status_code, 200)
self.assertQuerysetEqual(
- r.context["articles"], map(repr, [self.article1, self.article2])
+ r.context["articles"],
+ map(repr, [self.article1, self.article2]),
+ transform=repr,
)
@@ -1636,7 +1647,7 @@ class ArticleStatSalesListViewTests(ViewTestCaseMixin, TestCase):
for stat, expected in zip(content["stats"], expected_stats):
expected_url = expected.pop("url")
self.assertUrlsEqual(stat["url"], expected_url)
- self.assertDictContainsSubset(expected, stat)
+ self.assertEqual(stat, {**stat, **expected})
class ArticleStatSalesViewTests(ViewTestCaseMixin, TestCase):
@@ -1705,7 +1716,7 @@ class KPsulCheckoutDataViewTests(ViewTestCaseMixin, TestCase):
expected = {"name": "Checkout", "balance": "10.00"}
- self.assertDictContainsSubset(expected, content)
+ self.assertEqual(content, {**content, **expected})
self.assertSetEqual(
set(content.keys()),
@@ -1808,10 +1819,13 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.account.balance = Decimal("50.00")
self.account.save()
- # Mock consumer of K-Psul websocket to catch what we're sending
- kpsul_consumer_patcher = mock.patch("kfet.consumers.KPsul")
- self.kpsul_consumer_mock = kpsul_consumer_patcher.start()
- self.addCleanup(kpsul_consumer_patcher.stop)
+ # Create a channel to listen to KPsul's messages
+ channel_layer = get_channel_layer()
+ self.channel = async_to_sync(channel_layer.new_channel)()
+
+ async_to_sync(channel_layer.group_add)("kfet.kpsul", self.channel)
+
+ self.receive_msg = lambda: async_to_sync(channel_layer.receive)(self.channel)
# Reset cache of kfet config
kfet_config._conf_init = False
@@ -2043,9 +2057,12 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(self.article.stock, 18)
# Check websocket data
- self.kpsul_consumer_mock.group_send.assert_called_once_with(
- "kfet.kpsul",
+ ws_data = self.receive_msg()
+
+ self.assertDictEqual(
+ ws_data,
{
+ "type": "kpsul",
"groups": [
{
"add": True,
@@ -2307,9 +2324,12 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("110.75"))
- self.kpsul_consumer_mock.group_send.assert_called_once_with(
- "kfet.kpsul",
+ ws_data = self.receive_msg()
+
+ self.assertDictEqual(
+ ws_data,
{
+ "type": "kpsul",
"groups": [
{
"add": True,
@@ -2478,9 +2498,12 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("89.25"))
- self.kpsul_consumer_mock.group_send.assert_called_once_with(
- "kfet.kpsul",
+ ws_data = self.receive_msg()
+
+ self.assertDictEqual(
+ ws_data,
{
+ "type": "kpsul",
"groups": [
{
"add": True,
@@ -2635,9 +2658,12 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00"))
- self.kpsul_consumer_mock.group_send.assert_called_once_with(
- "kfet.kpsul",
+ ws_data = self.receive_msg()
+
+ self.assertDictEqual(
+ ws_data,
{
+ "type": "kpsul",
"groups": [
{
"add": True,
@@ -2750,9 +2776,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00"))
- ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
- "entries"
- ][0]
+ ws_data = self.receive_msg()
+ ws_data_ope = ws_data["groups"][0]["entries"][0]
+
self.assertEqual(ws_data_ope["addcost_amount"], Decimal("1.00"))
self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD")
@@ -2790,9 +2816,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00"))
- ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
- "entries"
- ][0]
+ ws_data = self.receive_msg()
+ ws_data_ope = ws_data["groups"][0]["entries"][0]
+
self.assertEqual(ws_data_ope["addcost_amount"], Decimal("0.80"))
self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD")
@@ -2828,9 +2854,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("106.00"))
- ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
- "entries"
- ][0]
+ ws_data = self.receive_msg()
+ ws_data_ope = ws_data["groups"][0]["entries"][0]
+
self.assertEqual(ws_data_ope["addcost_amount"], Decimal("1.00"))
self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD")
@@ -2864,9 +2890,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.accounts["addcost"].refresh_from_db()
self.assertEqual(self.accounts["addcost"].balance, Decimal("15.00"))
- ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
- "entries"
- ][0]
+ ws_data = self.receive_msg()
+ ws_data_ope = ws_data["groups"][0]["entries"][0]
+
self.assertEqual(ws_data_ope["addcost_amount"], None)
self.assertEqual(ws_data_ope["addcost_for__trigramme"], None)
@@ -2899,9 +2925,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.accounts["addcost"].refresh_from_db()
self.assertEqual(self.accounts["addcost"].balance, Decimal("0.00"))
- ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
- "entries"
- ][0]
+ ws_data = self.receive_msg()
+ ws_data_ope = ws_data["groups"][0]["entries"][0]
+
self.assertEqual(ws_data_ope["addcost_amount"], None)
self.assertEqual(ws_data_ope["addcost_for__trigramme"], None)
@@ -3123,9 +3149,12 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(article2.stock, -6)
# Check websocket data
- self.kpsul_consumer_mock.group_send.assert_called_once_with(
- "kfet.kpsul",
+ ws_data = self.receive_msg()
+
+ self.assertDictEqual(
+ ws_data,
{
+ "type": "kpsul",
"groups": [
{
"add": True,
@@ -3218,10 +3247,14 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.account.balance = Decimal("50.00")
self.account.save()
- # Mock consumer of K-Psul websocket to catch what we're sending
- kpsul_consumer_patcher = mock.patch("kfet.consumers.KPsul")
- self.kpsul_consumer_mock = kpsul_consumer_patcher.start()
- self.addCleanup(kpsul_consumer_patcher.stop)
+ # Create a channel to listen to KPsul's messages
+ channel_layer = get_channel_layer()
+
+ self.channel = async_to_sync(channel_layer.new_channel)()
+
+ async_to_sync(channel_layer.group_add)("kfet.kpsul", self.channel)
+
+ self.receive_msg = lambda: async_to_sync(channel_layer.receive)(self.channel)
def _assertResponseOk(self, response):
"""
@@ -3271,7 +3304,11 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
on_acc=self.account,
checkout=self.checkout,
content=[
- {"type": Operation.PURCHASE, "article": self.article, "article_nb": 2}
+ {
+ "type": Operation.PURCHASE,
+ "article": self.article,
+ "article_nb": 2,
+ }
],
)
operation = group.opes.get()
@@ -3345,9 +3382,15 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00"))
- self.kpsul_consumer_mock.group_send.assert_called_with(
- "kfet.kpsul",
- {"checkouts": [], "articles": [{"id": self.article.pk, "stock": 22}]},
+ ws_data = self.receive_msg()
+
+ self.assertDictEqual(
+ ws_data,
+ {
+ "type": "kpsul",
+ "checkouts": [],
+ "articles": [{"id": self.article.pk, "stock": 22}],
+ },
)
def test_purchase_with_addcost(self):
@@ -3407,11 +3450,11 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("95.00"))
- ws_data_checkouts = self.kpsul_consumer_mock.group_send.call_args[0][1][
- "checkouts"
- ]
+ ws_data = self.receive_msg()
+
self.assertListEqual(
- ws_data_checkouts, [{"id": self.checkout.pk, "balance": Decimal("95.00")}]
+ ws_data["checkouts"],
+ [{"id": self.checkout.pk, "balance": Decimal("95.00")}],
)
def test_purchase_cash_with_addcost(self):
@@ -3447,11 +3490,11 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
addcost_account.refresh_from_db()
self.assertEqual(addcost_account.balance, Decimal("9.00"))
- ws_data_checkouts = self.kpsul_consumer_mock.group_send.call_args[0][1][
- "checkouts"
- ]
+ ws_data = self.receive_msg()
+
self.assertListEqual(
- ws_data_checkouts, [{"id": self.checkout.pk, "balance": Decimal("94.00")}]
+ ws_data["checkouts"],
+ [{"id": self.checkout.pk, "balance": Decimal("94.00")}],
)
@mock.patch("django.utils.timezone.now")
@@ -3533,9 +3576,12 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("89.25"))
- self.kpsul_consumer_mock.group_send.assert_called_with(
- "kfet.kpsul",
+ ws_data = self.receive_msg()
+
+ self.assertDictEqual(
+ ws_data,
{
+ "type": "kpsul",
"checkouts": [{"id": self.checkout.pk, "balance": Decimal("89.25")}],
"articles": [],
},
@@ -3620,9 +3666,12 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("110.75"))
- self.kpsul_consumer_mock.group_send.assert_called_with(
- "kfet.kpsul",
+ ws_data = self.receive_msg()
+
+ self.assertDictEqual(
+ ws_data,
{
+ "type": "kpsul",
"checkouts": [{"id": self.checkout.pk, "balance": Decimal("110.75")}],
"articles": [],
},
@@ -3707,9 +3756,11 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00"))
- self.kpsul_consumer_mock.group_send.assert_called_with(
- "kfet.kpsul",
- {"checkouts": [], "articles": []},
+ ws_data = self.receive_msg()
+
+ self.assertDictEqual(
+ ws_data,
+ {"type": "kpsul", "checkouts": [], "articles": []},
)
@mock.patch("django.utils.timezone.now")
@@ -4049,7 +4100,7 @@ class KPsulArticlesData(ViewTestCaseMixin, TestCase):
]
for expected, article in zip(expected_list, articles):
- self.assertDictContainsSubset(expected, article)
+ self.assertEqual(article, {**article, **expected})
self.assertSetEqual(
set(article.keys()),
set(
@@ -4149,7 +4200,7 @@ class AccountReadJSONViewTests(ViewTestCaseMixin, TestCase):
content = json.loads(r.content.decode("utf-8"))
expected = {"name": "first last", "trigramme": "000", "balance": "0.00"}
- self.assertDictContainsSubset(expected, content)
+ self.assertEqual(content, {**content, **expected})
self.assertSetEqual(
set(content.keys()),
@@ -4393,7 +4444,9 @@ class InventoryListViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200)
inventories = r.context["inventories"]
- self.assertQuerysetEqual(inventories, map(repr, [self.inventory]))
+ self.assertQuerysetEqual(
+ inventories, map(repr, [self.inventory]), transform=repr
+ )
class InventoryCreateViewTests(ViewTestCaseMixin, TestCase):
@@ -4580,7 +4633,7 @@ class OrderListViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200)
orders = r.context["orders"]
- self.assertQuerysetEqual(orders, map(repr, [self.order]))
+ self.assertQuerysetEqual(orders, map(repr, [self.order]), transform=repr)
class OrderReadViewTests(ViewTestCaseMixin, TestCase):
@@ -4795,7 +4848,9 @@ class OrderToInventoryViewTests(ViewTestCaseMixin, TestCase):
inventory,
{"by": self.accounts["team1"], "at": self.now, "order": self.order},
)
- self.assertQuerysetEqual(inventory.articles.all(), map(repr, [self.article]))
+ self.assertQuerysetEqual(
+ inventory.articles.all(), map(repr, [self.article]), transform=repr
+ )
compte = InventoryArticle.objects.get(article=self.article)
diff --git a/kfet/utils.py b/kfet/utils.py
index 0c4f170a..540f260c 100644
--- a/kfet/utils.py
+++ b/kfet/utils.py
@@ -1,8 +1,7 @@
import json
import math
-from channels.channel import Group
-from channels.generic.websockets import JsonWebsocketConsumer
+from channels.generic.websocket import AsyncJsonWebsocketConsumer
from django.core.cache import cache
from django.core.serializers.json import DjangoJSONEncoder
@@ -63,7 +62,7 @@ class CachedMixin:
# Consumers
-class DjangoJsonWebsocketConsumer(JsonWebsocketConsumer):
+class DjangoJsonWebsocketConsumer(AsyncJsonWebsocketConsumer):
"""Custom Json Websocket Consumer.
Encode to JSON with DjangoJSONEncoder.
@@ -71,7 +70,10 @@ class DjangoJsonWebsocketConsumer(JsonWebsocketConsumer):
"""
@classmethod
- def encode_json(cls, content):
+ async def encode_json(cls, content):
+ # Remove the type value, only used by Channels to choose the group to send to
+ content.pop("type")
+
return json.dumps(content, cls=DjangoJSONEncoder)
@@ -89,31 +91,11 @@ class PermConsumerMixin:
http_user = True # Enable message.user
perms_connect = []
- def connect(self, message, **kwargs):
+ async def connect(self):
"""Check permissions on connection."""
- if message.user.has_perms(self.perms_connect):
- super().connect(message, **kwargs)
+ self.user = self.scope["user"]
+
+ if self.user.has_perms(self.perms_connect):
+ await super().connect()
else:
- self.close()
-
- def raw_connect(self, message, **kwargs):
- # Same as original raw_connect method of JsonWebsocketConsumer
- # We add user to connection_groups call.
- groups = self.connection_groups(user=message.user, **kwargs)
- for group in groups:
- Group(group, channel_layer=message.channel_layer).add(message.reply_channel)
- self.connect(message, **kwargs)
-
- def raw_disconnect(self, message, **kwargs):
- # Same as original raw_connect method of JsonWebsocketConsumer
- # We add user to connection_groups call.
- groups = self.connection_groups(user=message.user, **kwargs)
- for group in groups:
- Group(group, channel_layer=message.channel_layer).discard(
- message.reply_channel
- )
- self.disconnect(message, **kwargs)
-
- def connection_groups(self, user, **kwargs):
- """`message.user` is available as `user` arg. Original behavior."""
- return super().connection_groups(user=user, **kwargs)
+ await self.close()
diff --git a/kfet/views.py b/kfet/views.py
index 154be949..83d1a9e2 100644
--- a/kfet/views.py
+++ b/kfet/views.py
@@ -44,10 +44,11 @@ from django.views.generic.detail import BaseDetailView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from gestioncof.models import CofProfile
-from kfet import KFET_DELETED_TRIGRAMME, consumers
+from kfet import KFET_DELETED_TRIGRAMME
from kfet.auth.decorators import kfet_password_auth
from kfet.autocomplete import kfet_account_only_autocomplete, kfet_autocomplete
from kfet.config import kfet_config
+from kfet.consumers import KPsul
from kfet.decorators import teamkfet_required
from kfet.forms import (
AccountForm,
@@ -1054,8 +1055,13 @@ def kpsul_update_addcost(request):
kfet_config.set(addcost_for=account, addcost_amount=amount)
- data = {"addcost": {"for": account and account.trigramme or None, "amount": amount}}
- consumers.KPsul.group_send("kfet.kpsul", data)
+ data = {
+ "addcost": {"for": account and account.trigramme or None, "amount": amount},
+ "type": "kpsul",
+ }
+
+ KPsul.group_send("kfet.kpsul", data)
+
return JsonResponse(data)
@@ -1239,7 +1245,7 @@ def kpsul_perform_operations(request):
)
# Websocket data
- websocket_data = {}
+ websocket_data = {"type": "kpsul"}
websocket_data["groups"] = [
{
"add": True,
@@ -1286,7 +1292,9 @@ def kpsul_perform_operations(request):
websocket_data["articles"].append(
{"id": article["id"], "stock": article["stock"]}
)
- consumers.KPsul.group_send("kfet.kpsul", websocket_data)
+
+ KPsul.group_send("kfet.kpsul", websocket_data)
+
return JsonResponse(data)
@@ -1481,7 +1489,7 @@ def cancel_operations(request):
articles = Article.objects.values("id", "stock").filter(pk__in=articles_pk)
# Websocket data
- websocket_data = {"checkouts": [], "articles": []}
+ websocket_data = {"checkouts": [], "articles": [], "type": "kpsul"}
for checkout in checkouts:
websocket_data["checkouts"].append(
{"id": checkout["id"], "balance": checkout["balance"]}
@@ -1490,7 +1498,8 @@ def cancel_operations(request):
websocket_data["articles"].append(
{"id": article["id"], "stock": article["stock"]}
)
- consumers.KPsul.group_send("kfet.kpsul", websocket_data)
+
+ KPsul.group_send("kfet.kpsul", websocket_data)
data["canceled"] = list(opes)
data["opegroups_to_update"] = list(opegroups)
diff --git a/manage.py b/manage.py
index 913e4f6e..00e46405 100755
--- a/manage.py
+++ b/manage.py
@@ -3,7 +3,7 @@ import os
import sys
if __name__ == "__main__":
- os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings.local")
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings")
from django.core.management import execute_from_command_line
diff --git a/petitscours/models.py b/petitscours/models.py
index 8e5d4884..0be81449 100644
--- a/petitscours/models.py
+++ b/petitscours/models.py
@@ -2,7 +2,7 @@ from django.contrib.auth.models import User
from django.db import models
from django.db.models import Min
from django.utils.functional import cached_property
-from django.utils.translation import ugettext_lazy as _
+from django.utils.translation import gettext_lazy as _
from shared.utils import choices_length
@@ -44,9 +44,13 @@ class PetitCoursAbility(models.Model):
class Meta:
app_label = "gestioncof"
+ constraints = [
+ models.UniqueConstraint(
+ fields=["user", "niveau", "matiere"], name="unique_competence_level"
+ )
+ ]
verbose_name = "Compétence petits cours"
verbose_name_plural = "Compétences des petits cours"
- unique_together = ("user", "niveau", "matiere")
def __str__(self):
return "{:s} - {!s} - {:s}".format(
diff --git a/petitscours/templates/petitscours/demande_detail.html b/petitscours/templates/petitscours/demande_detail.html
index d7f9ca8b..8711fcda 100644
--- a/petitscours/templates/petitscours/demande_detail.html
+++ b/petitscours/templates/petitscours/demande_detail.html
@@ -1,5 +1,5 @@
{% extends "petitscours/base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block page_size %}col-sm-8{% endblock %}
diff --git a/petitscours/templates/petitscours/demande_list.html b/petitscours/templates/petitscours/demande_list.html
index e4c3c782..04132d57 100644
--- a/petitscours/templates/petitscours/demande_list.html
+++ b/petitscours/templates/petitscours/demande_list.html
@@ -1,5 +1,5 @@
{% extends "petitscours/base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block realcontent %}
Demandes de petits cours
diff --git a/petitscours/templates/petitscours/details_demande_infos.html b/petitscours/templates/petitscours/details_demande_infos.html
index 39cee1d3..42f37d56 100644
--- a/petitscours/templates/petitscours/details_demande_infos.html
+++ b/petitscours/templates/petitscours/details_demande_infos.html
@@ -1,4 +1,4 @@
-{% load staticfiles %}
+{% load static %}
Date | {{ demande.created }} |
Nom/prénom | {{ demande.name }} |
diff --git a/petitscours/templates/petitscours/inscription.html b/petitscours/templates/petitscours/inscription.html
index 9512e0b3..eaf10524 100644
--- a/petitscours/templates/petitscours/inscription.html
+++ b/petitscours/templates/petitscours/inscription.html
@@ -1,5 +1,5 @@
{% extends "base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block extra_head %}
diff --git a/petitscours/templates/petitscours/traitement_demande_autre_niveau.html b/petitscours/templates/petitscours/traitement_demande_autre_niveau.html
index cb3ec379..c10c8aaf 100644
--- a/petitscours/templates/petitscours/traitement_demande_autre_niveau.html
+++ b/petitscours/templates/petitscours/traitement_demande_autre_niveau.html
@@ -1,5 +1,5 @@
{% extends "petitscours/base_title.html" %}
-{% load staticfiles %}
+{% load static %}
{% block realcontent %}
diff --git a/provisioning/nginx/gestiocof.conf b/provisioning/nginx/gestiocof.conf
index f623bcf9..7d7567c6 100644
--- a/provisioning/nginx/gestiocof.conf
+++ b/provisioning/nginx/gestiocof.conf
@@ -15,8 +15,8 @@ server {
rewrite ^/gestion$ http://localhost:8080/gestion/ redirect;
# Les pages statiques sont servies à part.
- location /gestion/static { try_files $uri $uri/ =404; }
- location /gestion/media { try_files $uri $uri/ =404; }
+ location /static { try_files $uri $uri/ =404; }
+ location /media { try_files $uri $uri/ =404; }
# On proxy-pass les requêtes vers les pages dynamiques à daphne
location / {
diff --git a/provisioning/systemd/daphne.service b/provisioning/systemd/daphne.service
index 31b31c16..bae9f3ca 100644
--- a/provisioning/systemd/daphne.service
+++ b/provisioning/systemd/daphne.service
@@ -11,7 +11,7 @@ WorkingDirectory=/vagrant
Environment="DJANGO_SETTINGS_MODULE=gestioasso.settings.dev"
ExecStart=/home/vagrant/venv/bin/daphne \
-u /srv/gestiocof/gestiocof.sock \
- cof.asgi:channel_layer
+ gestioasso.asgi:application
[Install]
WantedBy=multi-user.target
diff --git a/provisioning/systemd/worker.service b/provisioning/systemd/worker.service
index a9ea733f..0d97e9a4 100644
--- a/provisioning/systemd/worker.service
+++ b/provisioning/systemd/worker.service
@@ -10,7 +10,10 @@ Group=vagrant
TimeoutSec=300
WorkingDirectory=/vagrant
Environment="DJANGO_SETTINGS_MODULE=gestioasso.settings.dev"
-ExecStart=/home/vagrant/venv/bin/python manage.py runworker
+ExecStart=/home/vagrant/venv/bin/python manage.py runworker \
+ 'kfet.open.team' \
+ 'kfet.open.base' \
+ 'kpsul'
[Install]
WantedBy=multi-user.target
diff --git a/requirements-devel.txt b/requirements-devel.txt
index a4607d08..d6b5c0a4 100644
--- a/requirements-devel.txt
+++ b/requirements-devel.txt
@@ -3,6 +3,6 @@ django-debug-toolbar==3.2.*
ipython
# Tools
-black
+black==22.3.0
flake8
isort
diff --git a/requirements-prod.txt b/requirements-prod.txt
index 6d6fd334..b4a99d6b 100644
--- a/requirements-prod.txt
+++ b/requirements-prod.txt
@@ -1,16 +1,15 @@
-r requirements.txt
# Postgresql bindings
-psycopg2<2.8
+psycopg2==2.9.*
# Redis
-django-redis-cache==2.1.*
-redis~=2.10.6
-asgi-redis==1.4.*
+django-redis-cache==3.0.*
+redis==3.5.*
+channels-redis==3.4.*
# ASGI protocol and HTTP server
-asgiref~=1.1.2
-daphne==1.3.0
+daphne==3.0.*
# ldap bindings
python-ldap
diff --git a/requirements.txt b/requirements.txt
index c5685b03..2016b576 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,19 +1,19 @@
-Django==2.2.*
+Django==3.2.*
Pillow==7.2.0
-authens==0.1b0
-channels==1.1.*
+authens==0.1b4
+channels==3.0.*
configparser==3.5.0
-django-autocomplete-light==3.3.*
+django-autocomplete-light==3.9.4
django-bootstrap-form==3.3
-django-cas-ng==3.6.*
-django-cors-headers==2.2.0
-django-djconfig==0.8.0
-django-hCaptcha==0.1.0
+django-cas-ng==4.3.*
+django-cors-headers==3.13.0
+django-djconfig==0.10.0
+django-hCaptcha==0.2.0
django-js-reverse==0.9.1
django-widget-tweaks==1.4.1
icalendar==4.0.7
python-dateutil==2.8.1
statistics==1.0.3.5
-wagtail-modeltranslation==0.10.*
-wagtail==2.7.*
+wagtail-modeltranslation==0.11.*
+wagtail==2.13.*
wagtailmenus==3.0.*
diff --git a/setup.cfg b/setup.cfg
index 8aa73856..9b1c72d0 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,8 +3,8 @@ source =
bda
bds
clubs
- cof
events
+ gestioasso
gestioncof
kfet
petitscours
diff --git a/shared/channels.py b/shared/channels.py
new file mode 100644
index 00000000..ae8c1248
--- /dev/null
+++ b/shared/channels.py
@@ -0,0 +1,45 @@
+import datetime
+import random
+from decimal import Decimal
+
+import msgpack
+from channels_redis.core import RedisChannelLayer
+
+
+def encode_kf(obj):
+ if isinstance(obj, Decimal):
+ return {"__decimal__": True, "as_str": str(obj)}
+ elif isinstance(obj, datetime.datetime):
+ return {"__datetime__": True, "as_str": obj.strftime("%Y%m%dT%H:%M:%S.%f")}
+ return obj
+
+
+def decode_kf(obj):
+ if "__decimal__" in obj:
+ obj = Decimal(obj["as_str"])
+ elif "__datetime__" in obj:
+ obj = datetime.datetime.strptime(obj["as_str"], "%Y%m%dT%H:%M:%S.%f")
+ return obj
+
+
+class ChannelLayer(RedisChannelLayer):
+ def serialize(self, message):
+ """Serializes to a byte string."""
+ value = msgpack.packb(message, default=encode_kf, use_bin_type=True)
+
+ if self.crypter:
+ value = self.crypter.encrypt(value)
+
+ # As we use an sorted set to expire messages
+ # we need to guarantee uniqueness, with 12 bytes.
+ random_prefix = random.getrandbits(8 * 12).to_bytes(12, "big")
+ return random_prefix + value
+
+ def deserialize(self, message):
+ """Deserializes from a byte string."""
+ # Removes the random prefix
+ message = message[12:]
+
+ if self.crypter:
+ message = self.crypter.decrypt(message, self.expiry + 10)
+ return msgpack.unpackb(message, object_hook=decode_kf, raw=False)