Compare commits

..

5 commits

Author SHA1 Message Date
Tom Hubrecht
7460ca591f Merge branch 'master' into 'Production'
Master

See merge request klub-dev-ens/gestioCOF!527
2023-06-15 13:59:53 +02:00
Tom Hubrecht
a4494c3648 Merge branch 'master' into 'Production'
Version 0.15

See merge request klub-dev-ens/gestioCOF!524
2023-05-22 20:49:38 +02:00
Tom Hubrecht
1fbf27a9f7 Merge branch 'master' into 'Production'
Mise en production de la v0.14

See merge request klub-dev-ens/gestioCOF!519
2023-05-19 20:24:04 +02:00
Tom Hubrecht
3178657e33 Merge branch 'master' into 'Production'
Update prod

See merge request klub-dev-ens/gestioCOF!517
2023-02-21 21:50:26 +01:00
Tom Hubrecht
a4ebeb266a Merge branch 'master' into 'Production'
Mise en production de 0.12.1

See merge request klub-dev-ens/gestioCOF!514
2022-10-03 18:46:49 +02:00
114 changed files with 592 additions and 1979 deletions

View file

@ -1 +0,0 @@
0x0000000000000000000000000000000000000000

View file

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

View file

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

View file

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

View file

@ -43,21 +43,13 @@ 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 petitscours shared --parallel
- coverage run manage.py test gestioncof bda kfet petitscours shared --parallel
bdstest:
stage: test

View file

@ -23,9 +23,6 @@ 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 ??? - ??/??/????
@ -68,8 +65,6 @@ 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 libsasl2-dev python-dev-is-python3 libldap2-dev libssl-dev
sudo apt-get install python3-pip python3-dev python3-venv sqlite3
Si vous décidez d'utiliser un environnement virtuel Python (virtualenv;
fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF
@ -30,15 +30,7 @@ Pour l'activer, il faut taper
. venv/bin/activate
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)
depuis le même dossier.
Vous pouvez maintenant installer les dépendances Python depuis le fichier
`requirements-devel.txt` :

View file

@ -1,23 +0,0 @@
# 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

@ -253,11 +253,7 @@ class ChoixSpectacle(models.Model):
class Meta:
ordering = ("priority",)
constraints = [
models.UniqueConstraint(
fields=["participant", "spectacle"], name="unique_participation"
)
]
unique_together = (("participant", "spectacle"),)
verbose_name = "voeu"
verbose_name_plural = "voeux"

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,80 +1,74 @@
from django.urls import re_path
from django.conf.urls import url
from bda import views
from bda.views import SpectacleListView
from gestioncof.decorators import buro_required
urlpatterns = [
re_path(
url(
r"^inscription/(?P<tirage_id>\d+)$",
views.inscription,
name="bda-tirage-inscription",
),
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(
url(r"^places/(?P<tirage_id>\d+)$", views.places, name="bda-places-attribuees"),
url(r"^etat-places/(?P<tirage_id>\d+)$", views.etat_places, name="bda-etat-places"),
url(r"^tirage/(?P<tirage_id>\d+)$", views.tirage, name="bda-tirage"),
url(
r"^spectacles/(?P<tirage_id>\d+)$",
buro_required(SpectacleListView.as_view()),
name="bda-liste-spectacles",
),
re_path(
url(
r"^spectacles/(?P<tirage_id>\d+)/(?P<spectacle_id>\d+)$",
views.spectacle,
name="bda-spectacle",
),
re_path(
url(
r"^spectacles/unpaid/(?P<tirage_id>\d+)$",
views.UnpaidParticipants.as_view(),
name="bda-unpaid",
),
re_path(
url(
r"^spectacles/autocomplete$",
views.spectacle_autocomplete,
name="bda-spectacle-autocomplete",
),
re_path(
url(
r"^participants/autocomplete$",
views.participant_autocomplete,
name="bda-participant-autocomplete",
),
# Urls BdA-Revente
re_path(
url(
r"^revente/(?P<tirage_id>\d+)/manage$",
views.revente_manage,
name="bda-revente-manage",
),
re_path(
url(
r"^revente/(?P<tirage_id>\d+)/subscribe$",
views.revente_subscribe,
name="bda-revente-subscribe",
),
re_path(
url(
r"^revente/(?P<tirage_id>\d+)/tirages$",
views.revente_tirages,
name="bda-revente-tirages",
),
re_path(
url(
r"^revente/(?P<spectacle_id>\d+)/buy$",
views.revente_buy,
name="bda-revente-buy",
),
re_path(
url(
r"^revente/(?P<revente_id>\d+)/confirm$",
views.revente_confirm,
name="bda-revente-confirm",
),
re_path(
url(
r"^revente/(?P<tirage_id>\d+)/shotgun$",
views.revente_shotgun,
name="bda-revente-shotgun",
),
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"
),
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"),
]

View file

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

View file

@ -1,4 +1,5 @@
from django.apps import AppConfig, apps as global_apps
from django import apps as global_apps
from django.apps import AppConfig
from django.db.models import Q
from django.db.models.signals import post_migrate

View file

@ -1,4 +1,4 @@
{% load static %}
{% load staticfiles %}
{% 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.assertEqual(resp.status_code, 200)
self.assertEquals(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.assertEqual(resp.status_code, 403)
self.assertEquals(resp.status_code, 403)
# Burô user GET
give_bds_buro_permissions(user)
resp = client.get(url)
self.assertEqual(resp.status_code, 200)
self.assertEquals(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.assertEqual(resp.status_code, 403)
self.assertEquals(resp.status_code, 403)
# Burô user GET
give_bds_buro_permissions(user)
resp = client.get(url)
self.assertEqual(resp.status_code, 200)
self.assertEquals(resp.status_code, 200)

View file

@ -1,63 +0,0 @@
# 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,13 +72,9 @@ 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
@ -91,13 +87,9 @@ 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
@ -126,9 +118,7 @@ class ExtraField(models.Model):
field_type = models.CharField(_("type de champ"), max_length=9, choices=FIELD_TYPE)
class Meta:
constraints = [
models.UniqueConstraint(fields=["event", "name"], name="unique_extra_field")
]
unique_together = [["event", "name"]]
class ExtraFieldContent(models.Model):
@ -147,13 +137,9 @@ 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
@ -177,13 +163,9 @@ 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,15 +1,8 @@
"""
ASGI entrypoint. Configures Django and then runs the application
defined in the ASGI_APPLICATION setting.
"""
import os
import django
from channels.routing import get_default_application
from channels.asgi import get_channel_layer
if "DJANGO_SETTINGS_MODULE" not in os.environ:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings.local")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings")
django.setup()
application = get_default_application()
channel_layer = get_channel_layer()

View file

@ -1,20 +1,3 @@
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from django.urls import path
from channels.routing import include
from kfet.routing import KFRouter
application = ProtocolTypeRouter(
{
# WebSocket chat handler
"websocket": AuthMiddlewareStack(
URLRouter(
[
path("ws/k-fet", KFRouter),
]
)
),
"http": get_asgi_application(),
}
)
routing = [include("kfet.routing.routing", path=r"^/ws/k-fet")]

View file

@ -67,8 +67,8 @@ INSTALLED_APPS = (
"wagtail.images",
"wagtail.search",
"wagtail.admin",
"wagtail",
# "wagtail.contrib.modeladmin",
"wagtail.core",
"wagtail.contrib.modeladmin",
"wagtail.contrib.routable_page",
"wagtailmenus",
"modelcluster",
@ -85,6 +85,7 @@ MIDDLEWARE = (
+ MIDDLEWARE
+ [
"djconfig.middleware.DjConfigMiddleware",
"wagtail.core.middleware.SiteMiddleware",
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
]
)
@ -108,8 +109,6 @@ 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
# ---
@ -148,7 +147,7 @@ CACHES = {
CHANNEL_LAYERS = {
"default": {
"BACKEND": "shared.channels.ChannelLayer",
"BACKEND": "asgi_redis.RedisChannelLayer",
"CONFIG": {
"hosts": [
(
@ -161,9 +160,11 @@ CHANNEL_LAYERS = {
)
]
},
"ROUTING": "gestioasso.routing.routing",
}
}
# ---
# reCAPTCHA settings
# https://github.com/praekelt/django-recaptcha
@ -205,7 +206,7 @@ MAIL_DATA = {
"REPLYTO": "cof@ens.fr",
},
"rappels": {"FROM": "Le BdA <bda@ens.fr>", "REPLYTO": "Le BdA <bda@ens.fr>"},
"kfet": {
"rappel_negatif": {
"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",
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": DBNAME,
"USER": DBUSER,
"PASSWORD": DBPASSWD,
@ -111,7 +111,6 @@ DATABASES = {
SITE_ID = 1
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
# ---
# Internationalization

View file

@ -47,7 +47,8 @@ CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"
# Use the default in memory asgi backend for local development
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels.layers.InMemoryChannelLayer",
"BACKEND": "asgiref.inmemory.ChannelLayer",
"ROUTING": "gestioasso.routing.routing",
}
}

View file

@ -1,194 +0,0 @@
"""
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"
##
# 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

View file

@ -1,318 +0,0 @@
"""
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"
##
# 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",
}
},
)
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/"
##
# 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>",
},
"rappel_negatif": {
"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,7 +1,6 @@
"""
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
@ -59,10 +58,10 @@ if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# Wagtail URLs (wagtail urls must be last, as catch-all)
if "wagtail" in settings.INSTALLED_APPS:
from wagtail import urls as wagtail_urls
# Wagtail URLs (wagtail.core urls must be last, as catch-all)
if "wagtail.core" in settings.INSTALLED_APPS:
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

@ -0,0 +1 @@
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 gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from gestioncof.models import (
Club,

View file

@ -0,0 +1 @@
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.fields
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.images.blocks
from django.db import migrations, models
@ -72,14 +72,18 @@ class Migration(migrations.Migration):
blank=True, null=True, verbose_name="Description rapide"
),
),
("body", wagtail.fields.RichTextField(verbose_name="Contenu")),
("body", wagtail.core.fields.RichTextField(verbose_name="Contenu")),
(
"body_fr",
wagtail.fields.RichTextField(null=True, verbose_name="Contenu"),
wagtail.core.fields.RichTextField(
null=True, verbose_name="Contenu"
),
),
(
"body_en",
wagtail.fields.RichTextField(null=True, verbose_name="Contenu"),
wagtail.core.fields.RichTextField(
null=True, verbose_name="Contenu"
),
),
(
"is_event",
@ -134,40 +138,46 @@ class Migration(migrations.Migration):
to="wagtailcore.Page",
),
),
("body", wagtail.fields.RichTextField(verbose_name="Description")),
("body", wagtail.core.fields.RichTextField(verbose_name="Description")),
(
"body_fr",
wagtail.fields.RichTextField(null=True, verbose_name="Description"),
wagtail.core.fields.RichTextField(
null=True, verbose_name="Description"
),
),
(
"body_en",
wagtail.fields.RichTextField(null=True, verbose_name="Description"),
wagtail.core.fields.RichTextField(
null=True, verbose_name="Description"
),
),
(
"links",
wagtail.fields.StreamField(
wagtail.core.fields.StreamField(
[
(
"lien",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"url",
wagtail.blocks.URLBlock(required=True),
wagtail.core.blocks.URLBlock(required=True),
),
("texte", wagtail.blocks.CharBlock()),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"email",
wagtail.blocks.EmailBlock(required=True),
wagtail.core.blocks.EmailBlock(
required=True
),
),
("texte", wagtail.blocks.CharBlock()),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
@ -176,29 +186,31 @@ class Migration(migrations.Migration):
),
(
"links_fr",
wagtail.fields.StreamField(
wagtail.core.fields.StreamField(
[
(
"lien",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"url",
wagtail.blocks.URLBlock(required=True),
wagtail.core.blocks.URLBlock(required=True),
),
("texte", wagtail.blocks.CharBlock()),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"email",
wagtail.blocks.EmailBlock(required=True),
wagtail.core.blocks.EmailBlock(
required=True
),
),
("texte", wagtail.blocks.CharBlock()),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
@ -208,29 +220,31 @@ class Migration(migrations.Migration):
),
(
"links_en",
wagtail.fields.StreamField(
wagtail.core.fields.StreamField(
[
(
"lien",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"url",
wagtail.blocks.URLBlock(required=True),
wagtail.core.blocks.URLBlock(required=True),
),
("texte", wagtail.blocks.CharBlock()),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"email",
wagtail.blocks.EmailBlock(required=True),
wagtail.core.blocks.EmailBlock(
required=True
),
),
("texte", wagtail.blocks.CharBlock()),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
@ -272,17 +286,17 @@ class Migration(migrations.Migration):
),
(
"introduction",
wagtail.fields.RichTextField(verbose_name="Introduction"),
wagtail.core.fields.RichTextField(verbose_name="Introduction"),
),
(
"introduction_fr",
wagtail.fields.RichTextField(
wagtail.core.fields.RichTextField(
null=True, verbose_name="Introduction"
),
),
(
"introduction_en",
wagtail.fields.RichTextField(
wagtail.core.fields.RichTextField(
null=True, verbose_name="Introduction"
),
),
@ -315,27 +329,27 @@ class Migration(migrations.Migration):
),
(
"body",
wagtail.fields.StreamField(
wagtail.core.fields.StreamField(
[
(
"heading",
wagtail.blocks.CharBlock(classname="full title"),
wagtail.core.blocks.CharBlock(classname="full title"),
),
("paragraph", wagtail.blocks.RichTextBlock()),
("paragraph", wagtail.core.blocks.RichTextBlock()),
("image", wagtail.images.blocks.ImageChooserBlock()),
(
"iframe",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"url",
wagtail.blocks.URLBlock(
wagtail.core.blocks.URLBlock(
"Adresse de la page"
),
),
(
"height",
wagtail.blocks.CharBlock(
wagtail.core.blocks.CharBlock(
"Hauteur (en pixels)"
),
),
@ -347,27 +361,27 @@ class Migration(migrations.Migration):
),
(
"body_fr",
wagtail.fields.StreamField(
wagtail.core.fields.StreamField(
[
(
"heading",
wagtail.blocks.CharBlock(classname="full title"),
wagtail.core.blocks.CharBlock(classname="full title"),
),
("paragraph", wagtail.blocks.RichTextBlock()),
("paragraph", wagtail.core.blocks.RichTextBlock()),
("image", wagtail.images.blocks.ImageChooserBlock()),
(
"iframe",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"url",
wagtail.blocks.URLBlock(
wagtail.core.blocks.URLBlock(
"Adresse de la page"
),
),
(
"height",
wagtail.blocks.CharBlock(
wagtail.core.blocks.CharBlock(
"Hauteur (en pixels)"
),
),
@ -380,27 +394,27 @@ class Migration(migrations.Migration):
),
(
"body_en",
wagtail.fields.StreamField(
wagtail.core.fields.StreamField(
[
(
"heading",
wagtail.blocks.CharBlock(classname="full title"),
wagtail.core.blocks.CharBlock(classname="full title"),
),
("paragraph", wagtail.blocks.RichTextBlock()),
("paragraph", wagtail.core.blocks.RichTextBlock()),
("image", wagtail.images.blocks.ImageChooserBlock()),
(
"iframe",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"url",
wagtail.blocks.URLBlock(
wagtail.core.blocks.URLBlock(
"Adresse de la page"
),
),
(
"height",
wagtail.blocks.CharBlock(
wagtail.core.blocks.CharBlock(
"Hauteur (en pixels)"
),
),
@ -434,17 +448,17 @@ class Migration(migrations.Migration):
),
(
"introduction",
wagtail.fields.RichTextField(verbose_name="Introduction"),
wagtail.core.fields.RichTextField(verbose_name="Introduction"),
),
(
"introduction_fr",
wagtail.fields.RichTextField(
wagtail.core.fields.RichTextField(
null=True, verbose_name="Introduction"
),
),
(
"introduction_en",
wagtail.fields.RichTextField(
wagtail.core.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.blocks
import wagtail.fields
import wagtail.core.blocks
import wagtail.core.fields
from django.db import migrations
@ -14,26 +14,26 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name="cofdirectoryentrypage",
name="links",
field=wagtail.fields.StreamField(
field=wagtail.core.fields.StreamField(
[
(
"lien",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
("url", wagtail.blocks.URLBlock(required=True)),
("texte", wagtail.blocks.CharBlock()),
("url", wagtail.core.blocks.URLBlock(required=True)),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"email",
wagtail.blocks.EmailBlock(required=True),
wagtail.core.blocks.EmailBlock(required=True),
),
("texte", wagtail.blocks.CharBlock()),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
@ -44,26 +44,26 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name="cofdirectoryentrypage",
name="links_en",
field=wagtail.fields.StreamField(
field=wagtail.core.fields.StreamField(
[
(
"lien",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
("url", wagtail.blocks.URLBlock(required=True)),
("texte", wagtail.blocks.CharBlock()),
("url", wagtail.core.blocks.URLBlock(required=True)),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"email",
wagtail.blocks.EmailBlock(required=True),
wagtail.core.blocks.EmailBlock(required=True),
),
("texte", wagtail.blocks.CharBlock()),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
@ -75,26 +75,26 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name="cofdirectoryentrypage",
name="links_fr",
field=wagtail.fields.StreamField(
field=wagtail.core.fields.StreamField(
[
(
"lien",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
("url", wagtail.blocks.URLBlock(required=True)),
("texte", wagtail.blocks.CharBlock()),
("url", wagtail.core.blocks.URLBlock(required=True)),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"email",
wagtail.blocks.EmailBlock(required=True),
wagtail.core.blocks.EmailBlock(required=True),
),
("texte", wagtail.blocks.CharBlock()),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),

View file

@ -1,7 +1,7 @@
# Generated by Django 2.2.15 on 2020-08-29 21:14
import wagtail.blocks
import wagtail.fields
import wagtail.core.blocks
import wagtail.core.fields
from django.db import migrations
@ -14,35 +14,35 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name="cofdirectoryentrypage",
name="links",
field=wagtail.fields.StreamField(
field=wagtail.core.fields.StreamField(
[
(
"lien",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
("url", wagtail.blocks.URLBlock(required=True)),
("texte", wagtail.blocks.CharBlock()),
("url", wagtail.core.blocks.URLBlock(required=True)),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"email",
wagtail.blocks.EmailBlock(required=True),
wagtail.core.blocks.EmailBlock(required=True),
),
("texte", wagtail.blocks.CharBlock()),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
(
"info",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
("nom", wagtail.blocks.CharBlock(required=False)),
("texte", wagtail.blocks.CharBlock(required=True)),
("nom", wagtail.core.blocks.CharBlock(required=False)),
("texte", wagtail.core.blocks.CharBlock(required=True)),
]
),
),
@ -53,35 +53,35 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name="cofdirectoryentrypage",
name="links_en",
field=wagtail.fields.StreamField(
field=wagtail.core.fields.StreamField(
[
(
"lien",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
("url", wagtail.blocks.URLBlock(required=True)),
("texte", wagtail.blocks.CharBlock()),
("url", wagtail.core.blocks.URLBlock(required=True)),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"email",
wagtail.blocks.EmailBlock(required=True),
wagtail.core.blocks.EmailBlock(required=True),
),
("texte", wagtail.blocks.CharBlock()),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
(
"info",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
("nom", wagtail.blocks.CharBlock(required=False)),
("texte", wagtail.blocks.CharBlock(required=True)),
("nom", wagtail.core.blocks.CharBlock(required=False)),
("texte", wagtail.core.blocks.CharBlock(required=True)),
]
),
),
@ -93,35 +93,35 @@ class Migration(migrations.Migration):
migrations.AlterField(
model_name="cofdirectoryentrypage",
name="links_fr",
field=wagtail.fields.StreamField(
field=wagtail.core.fields.StreamField(
[
(
"lien",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
("url", wagtail.blocks.URLBlock(required=True)),
("texte", wagtail.blocks.CharBlock()),
("url", wagtail.core.blocks.URLBlock(required=True)),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
(
"contact",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
(
"email",
wagtail.blocks.EmailBlock(required=True),
wagtail.core.blocks.EmailBlock(required=True),
),
("texte", wagtail.blocks.CharBlock()),
("texte", wagtail.core.blocks.CharBlock()),
]
),
),
(
"info",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
[
("nom", wagtail.blocks.CharBlock(required=False)),
("texte", wagtail.blocks.CharBlock(required=True)),
("nom", wagtail.core.blocks.CharBlock(required=False)),
("texte", wagtail.core.blocks.CharBlock(required=True)),
]
),
),

View file

@ -1,203 +0,0 @@
# 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,11 +1,12 @@
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db import models
from wagtail import blocks
from wagtail.admin.panels import FieldPanel
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
from wagtail.fields import RichTextField, StreamField
from wagtail.core import blocks
from wagtail.core.fields import RichTextField, StreamField
from wagtail.core.models import Page
from wagtail.images.blocks import ImageChooserBlock
from wagtail.models import Page
from wagtail.images.edit_handlers import ImageChooserPanel
# Page pouvant afficher des actualités
@ -68,11 +69,10 @@ class COFPage(Page):
("paragraph", blocks.RichTextBlock()),
("image", ImageChooserBlock()),
("iframe", IFrameBlock()),
],
use_json_field=True,
]
)
content_panels = Page.content_panels + [FieldPanel("body")]
content_panels = Page.content_panels + [StreamFieldPanel("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 + [
FieldPanel("image"),
ImageChooserPanel("image"),
FieldPanel("chapo"),
FieldPanel("body", classname="full"),
FieldPanel("is_event"),
@ -204,7 +204,6 @@ class COFDirectoryEntryPage(Page):
),
],
blank=True,
use_json_field=True,
)
image = models.ForeignKey(
@ -217,9 +216,9 @@ class COFDirectoryEntryPage(Page):
)
content_panels = Page.content_panels + [
FieldPanel("image"),
ImageChooserPanel("image"),
FieldPanel("body", classname="full"),
FieldPanel("links"),
StreamFieldPanel("links"),
]
subpage_types = []

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 gettext as _
from django.utils.translation import ugettext 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 gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from djconfig.forms import ConfigForm
from bda.models import Spectacle
@ -276,9 +276,7 @@ class RegistrationProfileForm(forms.ModelForm):
self.fields["mailing_bda_revente"].initial = True
self.fields["mailing_unernestaparis"].initial = True
class Meta:
model = CofProfile
fields = [
self.fields.keyOrder = [
"login_clipper",
"phone",
"occupation",
@ -292,6 +290,22 @@ 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

@ -1,43 +0,0 @@
# 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

@ -1,13 +0,0 @@
# 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 gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from bda.models import Spectacle
from shared.utils import choices_length
@ -194,12 +194,8 @@ 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)
@ -251,12 +247,8 @@ 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 gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from django_cas_ng.signals import cas_user_authenticated

View file

@ -1,4 +1,4 @@
{% load static %}
{% load staticfiles %}
<!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 static %}
{% load staticfiles %}
{% block page_size %}col-sm-8{% endblock %}

View file

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

View file

@ -194,9 +194,7 @@ class RegistrationViewTests(ViewTestCaseMixin, TestCase):
)
er = e.eventregistration_set.get(user=self.users["user"])
self.assertQuerysetEqual(
er.options.all(), map(repr, [oc1, oc3]), transform=repr, ordered=False
)
self.assertQuerysetEqual(er.options.all(), map(repr, [oc1, oc3]), ordered=False)
self.assertCountEqual(
er.comments.values_list("content", flat=True), ["comment 1"]
)
@ -301,10 +299,10 @@ class RegistrationAutocompleteViewTests(MockLDAPMixin, ViewTestCaseMixin, TestCa
raise ValueError("Unexpected section name: {}".format(section.name))
self.assertQuerysetEqual(
others, map(str, expected_others), transform=str, ordered=False
others, map(str, expected_others), ordered=False, transform=str
)
self.assertQuerysetEqual(
members, map(str, expected_members), transform=str, ordered=False
members, map(str, expected_members), ordered=False, transform=str
)
self.assertSetEqual(
set(clippers), set(map(LDAPSearch().result_verbose_name, expected_clippers))
@ -650,10 +648,7 @@ class ClubListViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200)
self.assertQuerysetEqual(
r.context["owned_clubs"],
map(repr, [self.c1, self.c2]),
transform=repr,
ordered=False,
r.context["owned_clubs"], map(repr, [self.c1, self.c2]), ordered=False
)
@ -955,10 +950,7 @@ 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]),
transform=repr,
ordered=False,
er.options.all(), map(repr, [self.oc1, self.oc3, self.oc4]), ordered=False
)
# TODO: Make the view care about comments.
# self.assertQuerysetEqual(
@ -983,9 +975,7 @@ 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]), transform=repr, ordered=False
)
self.assertQuerysetEqual(er.options.all(), map(repr, [self.oc3]), ordered=False)
# TODO: Make the view care about comments.
# self.assertQuerysetEqual(
# er.comments.all(), map(repr, []),
@ -1039,10 +1029,7 @@ class EventStatusViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200)
self.assertQuerysetEqual(
r.context["user_choices"],
map(repr, expected),
transform=repr,
ordered=False,
r.context["user_choices"], map(repr, expected), ordered=False
)
def test_filter_none(self):
@ -1109,10 +1096,7 @@ 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]),
transform=repr,
ordered=False,
a.answers.all(), map(repr, [self.qa1, self.qa3, self.qa4]), ordered=False
)
def test_post_edit(self):
@ -1131,9 +1115,7 @@ 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]), transform=repr, ordered=False
)
self.assertQuerysetEqual(a.answers.all(), map(repr, [self.qa3]), ordered=False)
def test_post_delete(self):
a = self.s.surveyanswer_set.create(user=self.users["user"])
@ -1214,10 +1196,7 @@ class SurveyStatusViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200)
self.assertQuerysetEqual(
r.context["user_answers"],
map(repr, expected),
transform=repr,
ordered=False,
r.context["user_answers"], map(repr, expected), ordered=False
)
def test_filter_none(self):

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 gettext_lazy as _
from django.utils.translation import ugettext_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,2 +1,3 @@
default_app_config = "kfet.apps.KFetConfig"
KFET_DELETED_TRIGRAMME = "☠☠☠"
KFET_DELETED_USERNAME = "kfet_deleted_user"

View file

@ -1,2 +1,4 @@
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 gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
class KFetAuthConfig(AppConfig):

View file

@ -1,5 +1,5 @@
from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ugettext_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 gettext_lazy as _
from django.utils.translation import ugettext_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 gettext_lazy as _
from django.utils.translation import ugettext as _
from .utils import get_kfet_generic_user

View file

@ -40,7 +40,6 @@ 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 gettext_lazy as _
from django.utils.translation import ugettext_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

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

View file

@ -1,6 +1,6 @@
from django.templatetags.static import static
from django.contrib.staticfiles.templatetags.staticfiles import static
from django.utils.html import format_html
from wagtail import hooks
from wagtail.core 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.models import Page, Site
from wagtail.core.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.blocks
import wagtail.fields
import wagtail.core.blocks
import wagtail.core.fields
import wagtail.snippets.blocks
from django.db import migrations, models
@ -41,20 +41,20 @@ class Migration(migrations.Migration):
),
(
"content",
wagtail.fields.StreamField(
wagtail.core.fields.StreamField(
(
(
"rich",
wagtail.blocks.RichTextBlock(label="Éditeur"),
wagtail.core.blocks.RichTextBlock(label="Éditeur"),
),
("carte", kfet.cms.models.MenuBlock()),
(
"group_team",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
(
(
"show_only",
wagtail.blocks.IntegerBlock(
wagtail.core.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.blocks.ListBlock(
wagtail.core.blocks.ListBlock(
wagtail.snippets.blocks.SnippetChooserBlock( # noqa
kfet.cms.models.MemberTeam
),
@ -75,22 +75,22 @@ class Migration(migrations.Migration):
),
(
"group",
wagtail.blocks.StreamBlock(
wagtail.core.blocks.StreamBlock(
(
(
"rich",
wagtail.blocks.RichTextBlock(
wagtail.core.blocks.RichTextBlock(
label="Éditeur"
),
),
("carte", kfet.cms.models.MenuBlock()),
(
"group_team",
wagtail.blocks.StructBlock(
wagtail.core.blocks.StructBlock(
(
(
"show_only",
wagtail.blocks.IntegerBlock( # noqa
wagtail.core.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.blocks.ListBlock(
wagtail.core.blocks.ListBlock(
wagtail.snippets.blocks.SnippetChooserBlock( # noqa
kfet.cms.models.MemberTeam # noqa
),

View file

@ -1,90 +0,0 @@
# 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,9 +1,15 @@
from django.db import models
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 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 wagtail.snippets.blocks import SnippetChooserBlock
from wagtail.snippets.models import register_snippet
@ -37,7 +43,7 @@ class MemberTeam(models.Model):
FieldPanel("first_name"),
FieldPanel("last_name"),
FieldPanel("nick_name"),
FieldPanel("photo"),
ImageChooserPanel("photo"),
]
def __str__(self):
@ -91,9 +97,7 @@ class KFetStreamBlock(ChoicesStreamBlock):
class KFetPage(Page):
content = StreamField(
KFetStreamBlock, verbose_name=_("Contenu"), use_json_field=True
)
content = StreamField(KFetStreamBlock, verbose_name=_("Contenu"))
# Layout fields
@ -131,7 +135,7 @@ class KFetPage(Page):
# Panels
content_panels = Page.content_panels + [FieldPanel("content")]
content_panels = Page.content_panels + [StreamFieldPanel("content")]
layout_panel = [
FieldPanel("no_header"),

View file

@ -1,18 +1,6 @@
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 gettext_lazy as _
from django.utils.translation import ugettext_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 <= 7 and now.year - 1 or now.year
return now.month <= 8 and now.year - 1 or now.year
def get_promo_choices():

View file

@ -4,6 +4,7 @@ from __future__ import unicode_literals
import datetime
from django.db import migrations, models
from django.utils.timezone import utc
class Migration(migrations.Migration):
@ -16,9 +17,7 @@ class Migration(migrations.Migration):
name="at",
field=models.DateTimeField(
auto_now_add=True,
default=datetime.datetime(
2016, 8, 29, 18, 35, 3, 419033, tzinfo=datetime.timezone.utc
),
default=datetime.datetime(2016, 8, 29, 18, 35, 3, 419033, tzinfo=utc),
),
preserve_default=False,
),

View file

@ -2,7 +2,6 @@ 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
@ -10,7 +9,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 gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from gestioncof.models import CofProfile
from shared.utils import choices_length
@ -270,36 +269,6 @@ 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)
@ -329,7 +298,7 @@ class AccountNegative(models.Model):
"""
Envoie un mail de rappel signalant que la personne est en négatif.
"""
mail_data = settings.MAIL_DATA["kfet"]
mail_data = settings.MAIL_DATA["rappel_negatif"]
email = EmailMessage(
subject="Compte K-Psul négatif",
@ -352,6 +321,7 @@ 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

@ -12,15 +12,13 @@ class OpenKfetConsumer(PermConsumerMixin, DjangoJsonWebsocketConsumer):
"""
async def open_status(self, event):
await self.send_json(event)
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 connect(self):
def connect(self, message, *args, **kwargs):
"""Send current status on connect."""
await super().connect()
group = "team" if 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))
super().connect(message, *args, **kwargs)
self.send(kfet_open.export(message.user))

View file

@ -1,6 +1,5 @@
from datetime import timedelta
from channels.layers import get_channel_layer
from django.utils import timezone
from ..decorators import kfet_is_team
@ -78,7 +77,7 @@ class OpenKfet(CachedMixin, object):
"""
status = self.status()
base = {"status": status, "type": "open.status"}
base = {"status": status}
restrict = {
"admin_status": self.admin_status(status),
"force_close": self.force_close,
@ -96,14 +95,13 @@ class OpenKfet(CachedMixin, object):
base, team = self._export()
return team if kfet_is_team(user) else base
async def send_ws(self):
def send_ws(self):
"""Send internal state to websocket channels."""
from .consumers import OpenKfetConsumer
base, team = self._export()
channel_layer = get_channel_layer()
await channel_layer.group_send("kfet.open.base", base)
await channel_layer.group_send("kfet.open.team", team)
OpenKfetConsumer.group_send("kfet.open.base", base)
OpenKfetConsumer.group_send("kfet.open.team", team)
kfet_open = OpenKfet()

View file

@ -1,10 +1,5 @@
from channels.routing import URLRouter
from django.urls import path
from channels.routing import route_class
from .consumers import OpenKfetConsumer
from . import consumers
OpenRouter = URLRouter(
[
path("", OpenKfetConsumer.as_asgi()),
]
)
routing = [route_class(consumers.OpenKfetConsumer)]

View file

@ -1,24 +1,19 @@
import json
import random
from datetime import timedelta
from unittest import mock
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 channels.channel import Group
from channels.test import ChannelTestCase, WSClient
from django.contrib.auth.models import AnonymousUser, Permission, User
from django.test import Client, TestCase
from django.test import Client
from django.utils import timezone
from . import OpenKfet
from .consumers import OpenKfetConsumer
def ws_communicator(cls, path: str, headers=[]):
return WebsocketCommunicator(AuthMiddlewareStack(cls.as_asgi()), path, headers)
class OpenKfetTest(TestCase):
class OpenKfetTest(ChannelTestCase):
"""OpenKfet object unit-tests suite."""
def setUp(self):
@ -84,7 +79,7 @@ class OpenKfetTest(TestCase):
def test_export_user(self):
"""Export is limited for an anonymous user."""
export = self.kfet_open.export(AnonymousUser())
self.assertSetEqual(set(["status", "type"]), set(export))
self.assertSetEqual(set(["status"]), set(export))
def test_export_team(self):
"""Export all values for a team member."""
@ -94,32 +89,24 @@ class OpenKfetTest(TestCase):
)
user.user_permissions.add(is_team)
export = self.kfet_open.export(user)
self.assertSetEqual(
set(["status", "admin_status", "force_close", "type"]), set(export)
)
self.assertSetEqual(set(["status", "admin_status", "force_close"]), set(export))
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()
def test_send_ws(self):
Group("kfet.open.base").add("test.open.base")
Group("kfet.open.team").add("test.open.team")
await channel_layer.group_add("kfet.open.base", base_channel)
await channel_layer.group_add("kfet.open.team", team_channel)
self.kfet_open.send_ws()
await self.kfet_open.send_ws()
recv_base = self.get_next_message("test.open.base", require=True)
base = json.loads(recv_base["text"])
self.assertSetEqual(set(["status"]), set(base))
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)
)
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))
class OpenKfetViewsTest(TestCase):
class OpenKfetViewsTest(ChannelTestCase):
"""OpenKfet views unit-tests suite."""
def setUp(self):
@ -190,136 +177,119 @@ class OpenKfetViewsTest(TestCase):
self.assertEqual(403, resp.status_code)
class OpenKfetConsumerTest(TestCase):
class OpenKfetConsumerTest(ChannelTestCase):
"""OpenKfet consumer unit-tests suite."""
@classmethod
def setUpTestData(cls):
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
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")
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)
# connect
c.send_and_consume(
"websocket.connect", path="/ws/k-fet/open", fail_on_none=True
)
# initialization data is replied on connection
message = await c.receive_json_from()
self.assertIsNotNone(message)
self.assertIsNotNone(c.receive())
# client belongs to the 'kfet.open' group...
channel_layer = get_channel_layer()
# client belongs to the 'kfet.open.admin' group...
OpenKfetConsumer.group_send("kfet.open.team", {"test": "plop"})
self.assertEqual(c.receive(), {"test": "plop"})
await channel_layer.group_send(
"kfet.open.base", {"test": "plop", "type": "open.status"}
)
message = await c.receive_json_from()
self.assertEqual(message, {"test": "plop"})
# ...but not to the 'kfet.open.admin' one
await channel_layer.group_send(
"kfet.open.team", {"test": "plop", "type": "open.status"}
)
self.assertTrue(await c.receive_nothing())
async def test_team_user(self):
"""Team user is added to kfet.open.team group."""
# On simule l'appartenance de l'user à la team kfet car l'utilisation de
# tests async avec postgres fait tout planter si on modifie la db dans un
# des sous tests.
with mock.patch("gestioncof.signals.messages"), mock.patch(
"kfet.open.consumers.kfet_is_team", return_value=True
):
c = ws_communicator(OpenKfetConsumer, "/ws/k-fet/open")
connected, _ = await c.connect()
channel_layer = get_channel_layer()
self.assertTrue(connected)
# initialization data is replied on connection
message = await c.receive_json_from()
self.assertIsNotNone(message)
# client belongs to the 'kfet.open.team' group...
await channel_layer.group_send(
"kfet.open.team", {"test": "plop", "type": "open.status"}
)
message = await c.receive_json_from()
self.assertEqual(message, {"test": "plop"})
# ...but not to the 'kfet.open' one
await channel_layer.group_send(
"kfet.open.base", {"test": "plop", "type": "open.status"}
)
self.assertTrue(await c.receive_nothing())
# ... but not to the 'kfet.open' one
OpenKfetConsumer.group_send("kfet.open.base", {"test": "plop"})
self.assertIsNone(c.receive())
class OpenKfetScenarioTest(TestCase):
class OpenKfetScenarioTest(ChannelTestCase):
"""OpenKfet functionnal tests suite."""
@classmethod
def setUpTestData(cls):
# root user
cls.r = User.objects.create_superuser("team", "", "team")
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)
# anonymous client (for views)
cls.c = Client()
self.c = Client()
# anonymous client (for websockets)
self.c_ws = WSClient()
# 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)
# 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"
)
self.kfet_open = OpenKfet(
cache_prefix="test_kfetopen_%s" % random.randrange(2**20)
)
self.addCleanup(self.kfet_open.clear_cache)
async def ws_connect(self, ws_communicator):
c, _ = await ws_communicator.connect()
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)
self.assertTrue(c)
return await ws_communicator.receive_json_from()
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))
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.receive_msg(self.channel)
msg = self.c_ws.receive(json=True)
self.assertEqual(OpenKfet.OPENED, msg["status"])
# root user too
msg = self.receive_msg(self.team_channel)
msg = self.r_c_ws.receive(json=True)
self.assertEqual(OpenKfet.OPENED, msg["status"])
self.assertEqual(OpenKfet.OPENED, msg["admin_status"])
@ -327,11 +297,11 @@ class OpenKfetScenarioTest(TestCase):
self.r_c.post("/k-fet/open/force_close", {"force_close": True})
# so anonymous user see it's closed
msg = self.receive_msg(self.channel)
msg = self.c_ws.receive(json=True)
self.assertEqual(OpenKfet.CLOSED, msg["status"])
# root user too
msg = self.receive_msg(self.team_channel)
msg = self.r_c_ws.receive(json=True)
self.assertEqual(OpenKfet.CLOSED, msg["status"])
# but root knows things
self.assertEqual(OpenKfet.FAKE_CLOSED, msg["admin_status"])
@ -342,42 +312,20 @@ class OpenKfetScenarioTest(TestCase):
self.kfet_open.raw_open = True
self.kfet_open.force_close = True
async_to_sync(OpenKfet().send_ws)()
msg = self.receive_msg(self.channel)
msg = self.ws_connect(self.c_ws)
self.assertEqual(OpenKfet.CLOSED, msg["status"])
msg = self.receive_msg(self.team_channel)
msg = self.ws_connect(self.r_c_ws)
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.receive_msg(self.channel)
msg = self.c_ws.receive(json=True)
self.assertEqual(OpenKfet.OPENED, msg["status"])
msg = self.receive_msg(self.team_channel)
msg = self.r_c_ws.receive(json=True)
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,4 +1,3 @@
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
@ -19,7 +18,7 @@ def raw_open(request):
raise PermissionDenied
raw_open = request.POST.get("raw_open") in TRUE_STR
kfet_open.raw_open = raw_open
async_to_sync(kfet_open.send_ws)()
kfet_open.send_ws()
return HttpResponse()
@ -28,5 +27,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
async_to_sync(kfet_open.send_ws)()
kfet_open.send_ws()
return HttpResponse()

View file

@ -1,13 +1,8 @@
from channels.routing import URLRouter
from django.urls import path
from channels.routing import include, route_class
from kfet.open.routing import OpenRouter
from . import consumers
from .consumers import KPsul
KFRouter = URLRouter(
[
path("k-psul/", KPsul.as_asgi()),
path("open", OpenRouter),
]
)
routing = [
route_class(consumers.KPsul, path=r"^/k-psul/$"),
include("kfet.open.routing.routing", path=r"^/open"),
]

View file

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

View file

@ -1,5 +1,5 @@
{% extends "kfet/base_form.html" %}
{% load static %}
{% load staticfiles %}
{% 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 static %}
{% load staticfiles %}
{% 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 static %}
{% load staticfiles %}
{% load widget_tweaks %}
{% block title %}Permissions - Édition{% endblock %}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -94,7 +94,6 @@ class PermHelpersTest(TestCaseMixin, TestCase):
self.assertQuerysetEqual(
user.user_permissions.all(),
map(repr, [self.perm1, self.perm2, self.perm_team]),
transform=repr,
ordered=False,
)

View file

@ -3,8 +3,6 @@ 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
@ -520,7 +518,6 @@ 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,
)
@ -574,7 +571,6 @@ 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,
)
@ -602,7 +598,6 @@ class AccountNegativeListViewTests(ViewTestCaseMixin, TestCase):
self.assertQuerysetEqual(
r.context["negatives"],
map(repr, [self.accounts["user"].negative]),
transform=repr,
ordered=False,
)
@ -703,7 +698,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.assertEqual(stat, {**stat, **expected})
self.assertDictContainsSubset(expected, stat)
class AccountStatOperationViewTests(ViewTestCaseMixin, TestCase):
@ -812,7 +807,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.assertEqual(stat, {**stat, **expected})
self.assertDictContainsSubset(expected, stat)
class AccountStatBalanceViewTests(ViewTestCaseMixin, TestCase):
@ -880,7 +875,6 @@ class CheckoutListViewTests(ViewTestCaseMixin, TestCase):
self.assertQuerysetEqual(
r.context["checkouts"],
map(repr, [self.checkout1, self.checkout2]),
transform=repr,
ordered=False,
)
@ -1071,7 +1065,6 @@ class CheckoutStatementListViewTests(ViewTestCaseMixin, TestCase):
self.assertQuerysetEqual(
r.context["checkoutstatements"],
map(repr, expected_statements),
transform=repr,
ordered=False,
)
@ -1298,9 +1291,7 @@ class ArticleCategoryListViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200)
self.assertQuerysetEqual(
r.context["categories"],
map(repr, [self.category1, self.category2]),
transform=repr,
r.context["categories"], map(repr, [self.category1, self.category2])
)
@ -1375,9 +1366,7 @@ 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]),
transform=repr,
r.context["articles"], map(repr, [self.article1, self.article2])
)
@ -1647,7 +1636,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.assertEqual(stat, {**stat, **expected})
self.assertDictContainsSubset(expected, stat)
class ArticleStatSalesViewTests(ViewTestCaseMixin, TestCase):
@ -1716,7 +1705,7 @@ class KPsulCheckoutDataViewTests(ViewTestCaseMixin, TestCase):
expected = {"name": "Checkout", "balance": "10.00"}
self.assertEqual(content, {**content, **expected})
self.assertDictContainsSubset(expected, content)
self.assertSetEqual(
set(content.keys()),
@ -1819,13 +1808,10 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.account.balance = Decimal("50.00")
self.account.save()
# 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)
# 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)
# Reset cache of kfet config
kfet_config._conf_init = False
@ -2057,12 +2043,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(self.article.stock, 18)
# Check websocket data
ws_data = self.receive_msg()
self.assertDictEqual(
ws_data,
self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul",
{
"type": "kpsul",
"groups": [
{
"add": True,
@ -2324,12 +2307,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("110.75"))
ws_data = self.receive_msg()
self.assertDictEqual(
ws_data,
self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul",
{
"type": "kpsul",
"groups": [
{
"add": True,
@ -2498,12 +2478,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("89.25"))
ws_data = self.receive_msg()
self.assertDictEqual(
ws_data,
self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul",
{
"type": "kpsul",
"groups": [
{
"add": True,
@ -2658,12 +2635,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00"))
ws_data = self.receive_msg()
self.assertDictEqual(
ws_data,
self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul",
{
"type": "kpsul",
"groups": [
{
"add": True,
@ -2776,9 +2750,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00"))
ws_data = self.receive_msg()
ws_data_ope = ws_data["groups"][0]["entries"][0]
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
"entries"
][0]
self.assertEqual(ws_data_ope["addcost_amount"], Decimal("1.00"))
self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD")
@ -2816,9 +2790,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00"))
ws_data = self.receive_msg()
ws_data_ope = ws_data["groups"][0]["entries"][0]
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
"entries"
][0]
self.assertEqual(ws_data_ope["addcost_amount"], Decimal("0.80"))
self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD")
@ -2854,9 +2828,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("106.00"))
ws_data = self.receive_msg()
ws_data_ope = ws_data["groups"][0]["entries"][0]
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
"entries"
][0]
self.assertEqual(ws_data_ope["addcost_amount"], Decimal("1.00"))
self.assertEqual(ws_data_ope["addcost_for__trigramme"], "ADD")
@ -2890,9 +2864,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.accounts["addcost"].refresh_from_db()
self.assertEqual(self.accounts["addcost"].balance, Decimal("15.00"))
ws_data = self.receive_msg()
ws_data_ope = ws_data["groups"][0]["entries"][0]
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
"entries"
][0]
self.assertEqual(ws_data_ope["addcost_amount"], None)
self.assertEqual(ws_data_ope["addcost_for__trigramme"], None)
@ -2925,9 +2899,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.accounts["addcost"].refresh_from_db()
self.assertEqual(self.accounts["addcost"].balance, Decimal("0.00"))
ws_data = self.receive_msg()
ws_data_ope = ws_data["groups"][0]["entries"][0]
ws_data_ope = self.kpsul_consumer_mock.group_send.call_args[0][1]["groups"][0][
"entries"
][0]
self.assertEqual(ws_data_ope["addcost_amount"], None)
self.assertEqual(ws_data_ope["addcost_for__trigramme"], None)
@ -3149,12 +3123,9 @@ class KPsulPerformOperationsViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(article2.stock, -6)
# Check websocket data
ws_data = self.receive_msg()
self.assertDictEqual(
ws_data,
self.kpsul_consumer_mock.group_send.assert_called_once_with(
"kfet.kpsul",
{
"type": "kpsul",
"groups": [
{
"add": True,
@ -3247,14 +3218,10 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.account.balance = Decimal("50.00")
self.account.save()
# 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)
# 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)
def _assertResponseOk(self, response):
"""
@ -3304,11 +3271,7 @@ 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()
@ -3382,15 +3345,9 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00"))
ws_data = self.receive_msg()
self.assertDictEqual(
ws_data,
{
"type": "kpsul",
"checkouts": [],
"articles": [{"id": self.article.pk, "stock": 22}],
},
self.kpsul_consumer_mock.group_send.assert_called_with(
"kfet.kpsul",
{"checkouts": [], "articles": [{"id": self.article.pk, "stock": 22}]},
)
def test_purchase_with_addcost(self):
@ -3450,11 +3407,11 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("95.00"))
ws_data = self.receive_msg()
ws_data_checkouts = self.kpsul_consumer_mock.group_send.call_args[0][1][
"checkouts"
]
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):
@ -3490,11 +3447,11 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
addcost_account.refresh_from_db()
self.assertEqual(addcost_account.balance, Decimal("9.00"))
ws_data = self.receive_msg()
ws_data_checkouts = self.kpsul_consumer_mock.group_send.call_args[0][1][
"checkouts"
]
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")
@ -3576,12 +3533,9 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("89.25"))
ws_data = self.receive_msg()
self.assertDictEqual(
ws_data,
self.kpsul_consumer_mock.group_send.assert_called_with(
"kfet.kpsul",
{
"type": "kpsul",
"checkouts": [{"id": self.checkout.pk, "balance": Decimal("89.25")}],
"articles": [],
},
@ -3666,12 +3620,9 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("110.75"))
ws_data = self.receive_msg()
self.assertDictEqual(
ws_data,
self.kpsul_consumer_mock.group_send.assert_called_with(
"kfet.kpsul",
{
"type": "kpsul",
"checkouts": [{"id": self.checkout.pk, "balance": Decimal("110.75")}],
"articles": [],
},
@ -3756,11 +3707,9 @@ class KPsulCancelOperationsViewTests(ViewTestCaseMixin, TestCase):
self.checkout.refresh_from_db()
self.assertEqual(self.checkout.balance, Decimal("100.00"))
ws_data = self.receive_msg()
self.assertDictEqual(
ws_data,
{"type": "kpsul", "checkouts": [], "articles": []},
self.kpsul_consumer_mock.group_send.assert_called_with(
"kfet.kpsul",
{"checkouts": [], "articles": []},
)
@mock.patch("django.utils.timezone.now")
@ -4100,7 +4049,7 @@ class KPsulArticlesData(ViewTestCaseMixin, TestCase):
]
for expected, article in zip(expected_list, articles):
self.assertEqual(article, {**article, **expected})
self.assertDictContainsSubset(expected, article)
self.assertSetEqual(
set(article.keys()),
set(
@ -4200,7 +4149,7 @@ class AccountReadJSONViewTests(ViewTestCaseMixin, TestCase):
content = json.loads(r.content.decode("utf-8"))
expected = {"name": "first last", "trigramme": "000", "balance": "0.00"}
self.assertEqual(content, {**content, **expected})
self.assertDictContainsSubset(expected, content)
self.assertSetEqual(
set(content.keys()),
@ -4444,9 +4393,7 @@ class InventoryListViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200)
inventories = r.context["inventories"]
self.assertQuerysetEqual(
inventories, map(repr, [self.inventory]), transform=repr
)
self.assertQuerysetEqual(inventories, map(repr, [self.inventory]))
class InventoryCreateViewTests(ViewTestCaseMixin, TestCase):
@ -4633,7 +4580,7 @@ class OrderListViewTests(ViewTestCaseMixin, TestCase):
self.assertEqual(r.status_code, 200)
orders = r.context["orders"]
self.assertQuerysetEqual(orders, map(repr, [self.order]), transform=repr)
self.assertQuerysetEqual(orders, map(repr, [self.order]))
class OrderReadViewTests(ViewTestCaseMixin, TestCase):
@ -4848,9 +4795,7 @@ class OrderToInventoryViewTests(ViewTestCaseMixin, TestCase):
inventory,
{"by": self.accounts["team1"], "at": self.now, "order": self.order},
)
self.assertQuerysetEqual(
inventory.articles.all(), map(repr, [self.article]), transform=repr
)
self.assertQuerysetEqual(inventory.articles.all(), map(repr, [self.article]))
compte = InventoryArticle.objects.get(article=self.article)

View file

@ -1,7 +1,8 @@
import json
import math
from channels.generic.websocket import AsyncJsonWebsocketConsumer
from channels.channel import Group
from channels.generic.websockets import JsonWebsocketConsumer
from django.core.cache import cache
from django.core.serializers.json import DjangoJSONEncoder
@ -62,7 +63,7 @@ class CachedMixin:
# Consumers
class DjangoJsonWebsocketConsumer(AsyncJsonWebsocketConsumer):
class DjangoJsonWebsocketConsumer(JsonWebsocketConsumer):
"""Custom Json Websocket Consumer.
Encode to JSON with DjangoJSONEncoder.
@ -70,10 +71,7 @@ class DjangoJsonWebsocketConsumer(AsyncJsonWebsocketConsumer):
"""
@classmethod
async def encode_json(cls, content):
# Remove the type value, only used by Channels to choose the group to send to
content.pop("type")
def encode_json(cls, content):
return json.dumps(content, cls=DjangoJSONEncoder)
@ -91,11 +89,31 @@ class PermConsumerMixin:
http_user = True # Enable message.user
perms_connect = []
async def connect(self):
def connect(self, message, **kwargs):
"""Check permissions on connection."""
self.user = self.scope["user"]
if self.user.has_perms(self.perms_connect):
await super().connect()
if message.user.has_perms(self.perms_connect):
super().connect(message, **kwargs)
else:
await self.close()
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)

View file

@ -44,11 +44,10 @@ 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
from kfet import KFET_DELETED_TRIGRAMME, consumers
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,
@ -244,7 +243,6 @@ def account_create(request):
account_form = AccountNoTriForm(request.POST, instance=account)
account_form.save()
messages.success(request, "Compte créé : %s" % account.trigramme)
account.send_creation_email()
return redirect("kfet.account.create")
except Account.UserHasAccount as e:
messages.error(
@ -1055,13 +1053,8 @@ 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},
"type": "kpsul",
}
KPsul.group_send("kfet.kpsul", data)
data = {"addcost": {"for": account and account.trigramme or None, "amount": amount}}
consumers.KPsul.group_send("kfet.kpsul", data)
return JsonResponse(data)
@ -1245,7 +1238,7 @@ def kpsul_perform_operations(request):
)
# Websocket data
websocket_data = {"type": "kpsul"}
websocket_data = {}
websocket_data["groups"] = [
{
"add": True,
@ -1292,9 +1285,7 @@ def kpsul_perform_operations(request):
websocket_data["articles"].append(
{"id": article["id"], "stock": article["stock"]}
)
KPsul.group_send("kfet.kpsul", websocket_data)
consumers.KPsul.group_send("kfet.kpsul", websocket_data)
return JsonResponse(data)
@ -1489,7 +1480,7 @@ def cancel_operations(request):
articles = Article.objects.values("id", "stock").filter(pk__in=articles_pk)
# Websocket data
websocket_data = {"checkouts": [], "articles": [], "type": "kpsul"}
websocket_data = {"checkouts": [], "articles": []}
for checkout in checkouts:
websocket_data["checkouts"].append(
{"id": checkout["id"], "balance": checkout["balance"]}
@ -1498,8 +1489,7 @@ def cancel_operations(request):
websocket_data["articles"].append(
{"id": article["id"], "stock": article["stock"]}
)
KPsul.group_send("kfet.kpsul", websocket_data)
consumers.KPsul.group_send("kfet.kpsul", websocket_data)
data["canceled"] = list(opes)
data["opegroups_to_update"] = list(opegroups)

View file

@ -3,7 +3,7 @@ import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gestioasso.settings.local")
from django.core.management import execute_from_command_line

View file

@ -1,81 +0,0 @@
# Generated by npins. Do not modify; will be overwritten regularly
let
data = builtins.fromJSON (builtins.readFile ./sources.json);
version = data.version;
mkSource =
spec:
assert spec ? type;
let
path =
if spec.type == "Git" then
mkGitSource spec
else if spec.type == "GitRelease" then
mkGitSource spec
else if spec.type == "PyPi" then
mkPyPiSource spec
else if spec.type == "Channel" then
mkChannelSource spec
else
builtins.throw "Unknown source type ${spec.type}";
in
spec // { outPath = path; };
mkGitSource =
{
repository,
revision,
url ? null,
hash,
branch ? null,
...
}:
assert repository ? type;
# At the moment, either it is a plain git repository (which has an url), or it is a GitHub/GitLab repository
# In the latter case, there we will always be an url to the tarball
if url != null then
(builtins.fetchTarball {
inherit url;
sha256 = hash; # FIXME: check nix version & use SRI hashes
})
else
assert repository.type == "Git";
let
urlToName =
url: rev:
let
matched = builtins.match "^.*/([^/]*)(\\.git)?$" repository.url;
short = builtins.substring 0 7 rev;
appendShort = if (builtins.match "[a-f0-9]*" rev) != null then "-${short}" else "";
in
"${if matched == null then "source" else builtins.head matched}${appendShort}";
name = urlToName repository.url revision;
in
builtins.fetchGit {
url = repository.url;
rev = revision;
inherit name;
allRefs = true;
# hash = hash;
};
mkPyPiSource =
{ url, hash, ... }:
builtins.fetchurl {
inherit url;
sha256 = hash;
};
mkChannelSource =
{ url, hash, ... }:
builtins.fetchTarball {
inherit url;
sha256 = hash;
};
in
if version == 3 then
builtins.mapAttrs (_: mkSource) data.pins
else
throw "Unsupported format version ${toString version} in sources.json. Try running `npins upgrade`"

View file

@ -1,33 +0,0 @@
{
"pins": {
"kat-pkgs": {
"type": "Git",
"repository": {
"type": "Git",
"url": "https://git.dgnum.eu/lbailly/kat-pkgs.git"
},
"branch": "master",
"revision": "6b600b716f409c6012b424de006eac3b02148b81",
"url": null,
"hash": "0204f91vxa5qglihpfkf3j5w3k7v98wry861xf2skl024faf9idf"
},
"nix-pkgs": {
"type": "Git",
"repository": {
"type": "Git",
"url": "https://git.hubrecht.ovh/hubrecht/nix-pkgs"
},
"branch": "main",
"revision": "ac4ff5a34789ae3398aff9501735b67b6a5a285a",
"url": null,
"hash": "16n37f74p6h30hhid98vab9w5b08xqj4qcshz2kc1jh67z5n49p6"
},
"nixpkgs": {
"type": "Channel",
"name": "nixos-unstable",
"url": "https://releases.nixos.org/nixos/unstable/nixos-25.05beta719504.a73246e2eef4/nixexprs.tar.xz",
"hash": "1jjmg13jzbqxm5m5ql51n2kq1qggfyb0rhmjwhqhvqxhl350z58a"
}
},
"version": 3
}

View file

@ -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 gettext_lazy as _
from django.utils.translation import ugettext_lazy as _
from shared.utils import choices_length
@ -44,13 +44,9 @@ 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(

View file

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

View file

@ -1,5 +1,5 @@
{% extends "petitscours/base_title.html" %}
{% load static %}
{% load staticfiles %}
{% block realcontent %}
<h2>Demandes de petits cours</h2>

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