diff --git a/.env_example b/.env_example
index 7e1dbd3b..da0b4efa 100644
--- a/.env_example
+++ b/.env_example
@@ -21,3 +21,6 @@ EMAIL_PASSWORD=CHANGE_ME
# Wiki configuration
WIKI_USER=NoteKfet2020
WIKI_PASSWORD=
+
+# OIDC
+OIDC_RSA_PRIVATE_KEY=CHANGE_ME
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2ba35d31..4cf8dab9 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -8,7 +8,7 @@ variables:
GIT_SUBMODULE_STRATEGY: recursive
# Ubuntu 22.04
-py310-django42:
+py310-django52:
stage: test
image: ubuntu:22.04
before_script:
@@ -22,10 +22,10 @@ py310-django42:
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
python3-bs4 python3-setuptools tox texlive-xetex
- script: tox -e py310-django42
+ script: tox -e py310-django52
# Debian Bookworm
-py311-django42:
+py311-django52:
stage: test
image: debian:bookworm
before_script:
@@ -37,7 +37,7 @@ py311-django42:
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
python3-bs4 python3-setuptools tox texlive-xetex
- script: tox -e py311-django42
+ script: tox -e py311-django52
linters:
stage: quality-assurance
diff --git a/README.md b/README.md
index 4ba19356..c340d58c 100644
--- a/README.md
+++ b/README.md
@@ -61,8 +61,8 @@ Bien que cela permette de créer une instance sur toutes les distributions,
6. (Optionnel) **Création d'une clé privée OpenID Connect**
Pour activer le support d'OpenID Connect, il faut générer une clé privée, par
-exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner son
-emplacement dans `OIDC_RSA_PRIVATE_KEY` (par défaut `/var/secrets/oidc.key`).
+exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et copier la clé dans .env dans le champ
+`OIDC_RSA_PRIVATE_KEY`.
7. Enjoy :
@@ -237,8 +237,8 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
7. **Création d'une clé privée OpenID Connect**
Pour activer le support d'OpenID Connect, il faut générer une clé privée, par
-exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner son
-emplacement dans `OIDC_RSA_PRIVATE_KEY` (par défaut `/var/secrets/oidc.key`).
+exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner le champ
+`OIDC_RSA_PRIVATE_KEY` dans le .env (par défaut `/var/secrets/oidc.key`).
8. *Enjoy \o/*
diff --git a/apps/food/forms.py b/apps/food/forms.py
index dfa32008..13c5cba3 100644
--- a/apps/food/forms.py
+++ b/apps/food/forms.py
@@ -145,7 +145,7 @@ class AddIngredientForms(forms.ModelForm):
polymorphic_ctype__model="transformedfood",
is_ready=False,
end_of_life='',
- ).filter(PermissionBackend.filter_queryset(get_current_request(), TransformedFood, "change")).exclude(pk=pk)
+ ).filter(PermissionBackend.filter_queryset(get_current_request(), Food, "change")).exclude(pk=pk)
class Meta:
model = TransformedFood
diff --git a/apps/food/templates/food/food_detail.html b/apps/food/templates/food/food_detail.html
index 9343f6d1..e82cc907 100644
--- a/apps/food/templates/food/food_detail.html
+++ b/apps/food/templates/food/food_detail.html
@@ -12,18 +12,21 @@ SPDX-License-Identifier: GPL-3.0-or-later
+ {% if QR_code %}
+
{{QR_code}}
+ {% endif %}
{% for field, value in fields %}
{{ field }} : {{ value }}
{% endfor %}
{% if meals %}
-
{% trans "Contained in" %} :
+
{% trans "Contained in" %} :
{% for meal in meals %}
- {{ meal.name }}{% if not forloop.last %},{% endif %}
+ {{ meal.name }}{% if not forloop.last %},{% endif %}
{% endfor %}
{% endif %}
{% if foods %}
-
{% trans "Contain" %} :
+
{% trans "Contain" %} :
{% for food in foods %}
{{ food.name }}{% if not forloop.last %},{% endif %}
{% endfor %}
@@ -31,23 +34,23 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endif %}
-{% endblock %}
+
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/apps/food/urls.py b/apps/food/urls.py
index 81acccdd..82a7f22e 100644
--- a/apps/food/urls.py
+++ b/apps/food/urls.py
@@ -18,4 +18,5 @@ urlpatterns = [
path('detail/basic/', views.BasicFoodDetailView.as_view(), name='basicfood_view'),
path('detail/transformed/', views.TransformedFoodDetailView.as_view(), name='transformedfood_view'),
path('add/ingredient/', views.AddIngredientView.as_view(), name='add_ingredient'),
+ path('redirect/', views.QRCodeRedirectView.as_view(), name='redirect_view'),
]
diff --git a/apps/food/views.py b/apps/food/views.py
index f25b0ca6..bebf1939 100644
--- a/apps/food/views.py
+++ b/apps/food/views.py
@@ -10,6 +10,7 @@ from django.db.models import Q
from django.http import HttpResponseRedirect, Http404
from django.views.generic import DetailView, UpdateView, CreateView
from django.views.generic.list import ListView
+from django.views.generic.base import RedirectView
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
@@ -63,7 +64,8 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li
valid_regex = is_regex(pattern)
suffix = '__iregex' if valid_regex else '__istartswith'
prefix = '^' if valid_regex else ''
- qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern}))
+ qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern})
+ | Q(**{f'owner__name{suffix}': prefix + pattern}))
else:
qs = qs.none()
search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view'))
@@ -453,6 +455,8 @@ class FoodDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
context["fields"] = [(
Food._meta.get_field(field).verbose_name.capitalize(),
value) for field, value in fields.items()]
+ if self.object.QR_code.exists():
+ context["QR_code"] = self.object.QR_code.first()
context["meals"] = self.object.transformed_ingredient_inv.all()
context["update"] = PermissionBackend.check_perm(self.request, "food.change_food")
context["add_ingredient"] = (self.object.end_of_life == '' and PermissionBackend.check_perm(self.request, "food.change_transformedfood"))
@@ -506,3 +510,14 @@ class TransformedFoodDetailView(FoodDetailView):
if Food.objects.filter(pk=kwargs['pk']).count() == 1:
kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'transformedfood')
return super().get(*args, **kwargs)
+
+
+class QRCodeRedirectView(RedirectView):
+ """
+ Redirects to the QR code creation page from Food List
+ """
+ def get_redirect_url(self, *args, **kwargs):
+ slug = self.request.GET.get('slug')
+ if slug:
+ return reverse_lazy('food:qrcode_create', kwargs={'slug': slug})
+ return reverse_lazy('food:list')
diff --git a/apps/member/tests/test_login.py b/apps/member/tests/test_login.py
index b8873a14..ce5de1cf 100644
--- a/apps/member/tests/test_login.py
+++ b/apps/member/tests/test_login.py
@@ -44,7 +44,7 @@ class TemplateLoggedInTests(TestCase):
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302)
def test_logout(self):
- response = self.client.get(reverse("logout"))
+ response = self.client.post(reverse("logout"))
self.assertEqual(response.status_code, 200)
def test_admin_index(self):
diff --git a/apps/note/api/urls.py b/apps/note/api/urls.py
index 67d7371e..c50e68e4 100644
--- a/apps/note/api/urls.py
+++ b/apps/note/api/urls.py
@@ -13,7 +13,7 @@ def register_note_urls(router, path):
router.register(path + '/note', NotePolymorphicViewSet)
router.register(path + '/alias', AliasViewSet)
router.register(path + '/trust', TrustViewSet)
- router.register(path + '/consumer', ConsumerViewSet)
+ router.register(path + '/consumer', ConsumerViewSet, basename='alias2')
router.register(path + '/transaction/category', TemplateCategoryViewSet)
router.register(path + '/transaction/transaction', TransactionViewSet)
diff --git a/apps/permission/scopes.py b/apps/permission/scopes.py
index 6ee5818f..2842546f 100644
--- a/apps/permission/scopes.py
+++ b/apps/permission/scopes.py
@@ -1,8 +1,10 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
+
from oauth2_provider.oauth2_validators import OAuth2Validator
from oauth2_provider.scopes import BaseScopes
from member.models import Club
+from note.models import Alias
from note_kfet.middlewares import get_current_request
from .backends import PermissionBackend
@@ -16,26 +18,58 @@ class PermissionScopes(BaseScopes):
and can be useful to make queries through the API with limited privileges.
"""
- def get_all_scopes(self):
- return {f"{p.id}_{club.id}": f"{p.description} (club {club.name})"
- for p in Permission.objects.all() for club in Club.objects.all()}
+ def get_all_scopes(self, **kwargs):
+ scopes = {}
+ if 'scopes' in kwargs:
+ for scope in kwargs['scopes']:
+ if scope == 'openid':
+ scopes['openid'] = "OpenID Connect"
+ else:
+ p = Permission.objects.get(id=scope.split('_')[0])
+ club = Club.objects.get(id=scope.split('_')[1])
+ scopes[scope] = f"{p.description} (club {club.name})"
+ return scopes
+
+ scopes = {f"{p.id}_{club.id}": f"{p.description} (club {club.name})"
+ for p in Permission.objects.all() for club in Club.objects.all()}
+ scopes['openid'] = "OpenID Connect"
+ return scopes
def get_available_scopes(self, application=None, request=None, *args, **kwargs):
if not application:
return []
- return [f"{p.id}_{p.membership.club.id}"
- for t in Permission.PERMISSION_TYPES
- for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0])]
+ scopes = [f"{p.id}_{p.membership.club.id}"
+ for t in Permission.PERMISSION_TYPES
+ for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0])]
+ scopes.append('openid')
+ return scopes
def get_default_scopes(self, application=None, request=None, *args, **kwargs):
if not application:
return []
- return [f"{p.id}_{p.membership.club.id}"
- for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')]
+ scopes = [f"{p.id}_{p.membership.club.id}"
+ for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')]
+ scopes.append('openid')
+ return scopes
class PermissionOAuth2Validator(OAuth2Validator):
- oidc_claim_scope = None # fix breaking change of django-oauth-toolkit 2.0.0
+ oidc_claim_scope = OAuth2Validator.oidc_claim_scope
+ oidc_claim_scope.update({"name": 'openid',
+ "normalized_name": 'openid',
+ "email": 'openid',
+ })
+
+ def get_additional_claims(self, request):
+ return {
+ "name": request.user.username,
+ "normalized_name": Alias.normalize(request.user.username),
+ "email": request.user.email,
+ }
+
+ def get_discovery_claims(self, request):
+ claims = super().get_discovery_claims(self)
+ return claims + ["name", "normalized_name", "email"]
def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
"""
@@ -54,6 +88,8 @@ class PermissionOAuth2Validator(OAuth2Validator):
if scope in scopes:
valid_scopes.add(scope)
- request.scopes = valid_scopes
+ if 'openid' in scopes:
+ valid_scopes.add('openid')
+ request.scopes = valid_scopes
return valid_scopes
diff --git a/apps/permission/signals.py b/apps/permission/signals.py
index b2394c6f..af5ab4ce 100644
--- a/apps/permission/signals.py
+++ b/apps/permission/signals.py
@@ -13,12 +13,14 @@ EXCLUDED = [
'cas_server.serviceticket',
'cas_server.user',
'cas_server.userattributes',
+ 'constance.constance',
'contenttypes.contenttype',
'logs.changelog',
'migrations.migration',
'oauth2_provider.accesstoken',
'oauth2_provider.grant',
'oauth2_provider.refreshtoken',
+ 'oauth2_provider.idtoken',
'sessions.session',
]
diff --git a/apps/permission/views.py b/apps/permission/views.py
index e7de920e..30b13316 100644
--- a/apps/permission/views.py
+++ b/apps/permission/views.py
@@ -164,14 +164,24 @@ class ScopesView(LoginRequiredMixin, TemplateView):
from oauth2_provider.models import Application
from .scopes import PermissionScopes
- scopes = PermissionScopes()
+ oidc = False
context["scopes"] = {}
- all_scopes = scopes.get_all_scopes()
for app in Application.objects.filter(user=self.request.user).all():
- available_scopes = scopes.get_available_scopes(app)
+ available_scopes = PermissionScopes().get_available_scopes(app)
context["scopes"][app] = OrderedDict()
- items = [(k, v) for (k, v) in all_scopes.items() if k in available_scopes]
+ all_scopes = PermissionScopes().get_all_scopes(scopes=available_scopes)
+ scopes = {}
+ for scope in available_scopes:
+ scopes[scope] = all_scopes[scope]
+ # remove OIDC scope for sort
+ if 'openid' in scopes:
+ del scopes['openid']
+ oidc = True
+ items = [(k, v) for (k, v) in scopes.items()]
items.sort(key=lambda x: (int(x[0].split("_")[1]), int(x[0].split("_")[0])))
+ # add oidc if necessary
+ if oidc:
+ items.append(('openid', PermissionScopes().get_all_scopes(scopes=['openid'])['openid']))
for k, v in items:
context["scopes"][app][k] = v
diff --git a/docs/scripts.rst b/docs/scripts.rst
index fc85468c..7ddda96d 100644
--- a/docs/scripts.rst
+++ b/docs/scripts.rst
@@ -136,7 +136,7 @@ de diffusion utiles.
Faîtes attention, donc où la sortie est stockée.
-Il prend 2 options :
+Il prend 4 options :
* ``--type``, qui prend en argument ``members`` (défaut), ``clubs``, ``events``, ``art``,
``sport``, qui permet respectivement de sortir la liste des adresses mails des adhérent⋅es
@@ -149,7 +149,10 @@ Il prend 2 options :
pour la ML Adhérents, pour exporter les mails des adhérents au BDE pendant n'importe
laquelle des ``n+1`` dernières années.
-Le script sort sur la sortie standard la liste des adresses mails à inscrire.
+* ``--email``, qui prend en argument une chaine de caractère contenant une adresse email.
+
+Si aucun email n'est renseigné, le script sort sur la sortie standard la liste des adresses mails à inscrire.
+Dans le cas contraire, la liste est envoyée à l'adresse passée en argument.
Attention : il y a parfois certains cas particuliers à prendre en compte, il n'est
malheureusement pas aussi simple que de simplement supposer que ces listes sont exhaustives.
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index 07121046..b62c48bc 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -357,7 +357,7 @@ msgstr "Détails de l'activité"
#: apps/note/models/transactions.py:261
#: apps/note/templates/note/transaction_form.html:17
#: apps/note/templates/note/transaction_form.html:152
-#: note_kfet/templates/base.html:78
+#: note_kfet/templates/base.html:79
msgid "Transfer"
msgstr "Virement"
@@ -474,7 +474,7 @@ msgstr "Inviter"
msgid "Create new activity"
msgstr "Créer une nouvelle activité"
-#: apps/activity/views.py:71 note_kfet/templates/base.html:96
+#: apps/activity/views.py:71 note_kfet/templates/base.html:97
msgid "Activities"
msgstr "Activités"
@@ -563,7 +563,7 @@ msgstr "Nom"
#, fuzzy
#| msgid "QR-code number"
msgid "QR code number"
-msgstr "numéro de QR-code"
+msgstr "Numéro de QR-code"
#: apps/food/models.py:23
msgid "Allergen"
@@ -597,7 +597,7 @@ msgstr "est prêt"
msgid "order"
msgstr "consigne"
-#: apps/food/models.py:107 apps/food/views.py:34
+#: apps/food/models.py:107 apps/food/views.py:35
#: note_kfet/templates/base.html:72
msgid "Food"
msgstr "Bouffe"
@@ -657,61 +657,75 @@ msgstr "QR-codes"
#: apps/food/models.py:286
#: apps/food/templates/food/transformedfood_update.html:24
msgid "QR-code number"
-msgstr "numéro de QR-code"
+msgstr "Numéro de QR-code"
-#: apps/food/templates/food/food_detail.html:19
+#: apps/food/templates/food/food_detail.html:22
msgid "Contained in"
msgstr "Contenu dans"
-#: apps/food/templates/food/food_detail.html:26
+#: apps/food/templates/food/food_detail.html:29
msgid "Contain"
msgstr "Contient"
-#: apps/food/templates/food/food_detail.html:35
+#: apps/food/templates/food/food_detail.html:38
msgid "Update"
msgstr "Modifier"
-#: apps/food/templates/food/food_detail.html:40
+#: apps/food/templates/food/food_detail.html:43
msgid "Add to a meal"
msgstr "Ajouter à un plat"
-#: apps/food/templates/food/food_detail.html:45
+#: apps/food/templates/food/food_detail.html:48
msgid "Manage ingredients"
msgstr "Gérer les ingrédients"
-#: apps/food/templates/food/food_detail.html:49
+#: apps/food/templates/food/food_detail.html:52
msgid "Return to the food list"
msgstr "Retour à la liste de nourriture"
-#: apps/food/templates/food/food_list.html:14
+#: apps/food/templates/food/food_list.html:32
+msgid "View food"
+msgstr "Voir l'aliment"
+
+#: apps/food/templates/food/food_list.html:37
+#: note_kfet/templates/base_search.html:15
+msgid "Search by attribute such as name..."
+msgstr "Chercher par un attribut tel que le nom..."
+
+#: apps/food/templates/food/food_list.html:49
+#: note_kfet/templates/base_search.html:23
+msgid "There is no results."
+msgstr "Il n'y a pas de résultat."
+
+#: apps/food/templates/food/food_list.html:58
msgid "Meal served"
msgstr "Plat servis"
-#: apps/food/templates/food/food_list.html:19
+#: apps/food/templates/food/food_list.html:63
msgid "New meal"
msgstr "Nouveau plat"
-#: apps/food/templates/food/food_list.html:28
+#: apps/food/templates/food/food_list.html:72
msgid "There is no meal served."
msgstr "Il n'y a pas de plat servi."
-#: apps/food/templates/food/food_list.html:35
+#: apps/food/templates/food/food_list.html:79
msgid "Free food"
msgstr "Open"
-#: apps/food/templates/food/food_list.html:42
+#: apps/food/templates/food/food_list.html:86
msgid "There is no free food."
msgstr "Il n'y a pas de bouffe en open"
-#: apps/food/templates/food/food_list.html:50
+#: apps/food/templates/food/food_list.html:94
msgid "Food of your clubs"
msgstr "Bouffe de tes clubs"
-#: apps/food/templates/food/food_list.html:56
+#: apps/food/templates/food/food_list.html:100
msgid "Food of club"
msgstr "Bouffe du club"
-#: apps/food/templates/food/food_list.html:63
+#: apps/food/templates/food/food_list.html:107
msgid "Yours club has not food yet."
msgstr "Ton club n'a pas de bouffe pour l'instant"
@@ -785,49 +799,49 @@ msgstr "semaines"
msgid "and"
msgstr "et"
-#: apps/food/views.py:118
+#: apps/food/views.py:120
msgid "Add a new QRCode"
msgstr "Ajouter un nouveau QR-code"
-#: apps/food/views.py:167
+#: apps/food/views.py:169
msgid "Add an aliment"
msgstr "Ajouter un nouvel aliment"
-#: apps/food/views.py:235
+#: apps/food/views.py:228
msgid "Add a meal"
msgstr "Ajouter un plat"
-#: apps/food/views.py:275
+#: apps/food/views.py:259
msgid "Manage ingredients of:"
msgstr "Gestion des ingrédienrs de :"
-#: apps/food/views.py:289 apps/food/views.py:297
+#: apps/food/views.py:273 apps/food/views.py:281
#, python-brace-format
msgid "Fully used in {meal}"
msgstr "Aliment entièrement utilisé dans : {meal}"
-#: apps/food/views.py:344
+#: apps/food/views.py:320
msgid "Add the ingredient:"
msgstr "Ajouter l'ingrédient"
-#: apps/food/views.py:370
+#: apps/food/views.py:346
#, python-brace-format
msgid "Food fully used in : {meal.name}"
msgstr "Aliment entièrement utilisé dans : {meal.name}"
-#: apps/food/views.py:389
+#: apps/food/views.py:365
msgid "Update an aliment"
msgstr "Modifier un aliment"
-#: apps/food/views.py:437
+#: apps/food/views.py:413
msgid "Details of:"
msgstr "Détails de :"
-#: apps/food/views.py:447 apps/treasury/tables.py:149
+#: apps/food/views.py:423 apps/treasury/tables.py:149
msgid "Yes"
msgstr "Oui"
-#: apps/food/views.py:449 apps/member/models.py:99 apps/treasury/tables.py:149
+#: apps/food/views.py:425 apps/member/models.py:99 apps/treasury/tables.py:149
msgid "No"
msgstr "Non"
@@ -2065,6 +2079,8 @@ msgstr "Historique des transactions récentes"
#: apps/note/templates/note/mails/weekly_report.txt:32
#: apps/registration/templates/registration/mails/email_validation_email.html:40
#: apps/registration/templates/registration/mails/email_validation_email.txt:16
+#: apps/scripts/templates/scripts/food_report.html:48
+#: apps/scripts/templates/scripts/food_report.txt:14
msgid "Mail generated by the Note Kfet on the"
msgstr "Mail généré par la Note Kfet le"
@@ -2176,7 +2192,7 @@ msgstr "Chercher un bouton"
msgid "Update button"
msgstr "Modifier le bouton"
-#: apps/note/views.py:156 note_kfet/templates/base.html:66
+#: apps/note/views.py:156 note_kfet/templates/base.html:67
msgid "Consumptions"
msgstr "Consommations"
@@ -2269,7 +2285,7 @@ msgstr "s'applique au club"
msgid "role permissions"
msgstr "permissions par rôles"
-#: apps/permission/signals.py:73
+#: apps/permission/signals.py:75
#, python-brace-format
msgid ""
"You don't have the permission to change the field {field} on this instance "
@@ -2278,7 +2294,7 @@ msgstr ""
"Vous n'avez pas la permission de modifier le champ {field} sur l'instance du "
"modèle {app_label}.{model_name}."
-#: apps/permission/signals.py:83 apps/permission/views.py:104
+#: apps/permission/signals.py:85 apps/permission/views.py:104
#, python-brace-format
msgid ""
"You don't have the permission to add an instance of model {app_label}."
@@ -2287,7 +2303,7 @@ msgstr ""
"Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}."
"{model_name}."
-#: apps/permission/signals.py:112
+#: apps/permission/signals.py:114
#, python-brace-format
msgid ""
"You don't have the permission to delete this instance of model {app_label}."
@@ -2375,7 +2391,7 @@ msgstr ""
"Vous n'avez pas la permission d'ajouter une instance du modèle « {model} » "
"avec ces paramètres. Merci de les corriger et de réessayer."
-#: apps/permission/views.py:111 note_kfet/templates/base.html:120
+#: apps/permission/views.py:111 note_kfet/templates/base.html:121
msgid "Rights"
msgstr "Droits"
@@ -2580,7 +2596,7 @@ msgstr ""
msgid "Invalidate pre-registration"
msgstr "Invalider l'inscription"
-#: apps/treasury/apps.py:12 note_kfet/templates/base.html:102
+#: apps/treasury/apps.py:12 note_kfet/templates/base.html:103
msgid "Treasury"
msgstr "Trésorerie"
@@ -3748,13 +3764,13 @@ msgstr "bde"
#: apps/wrapped/models.py:65
msgid "data json"
-msgstr "donnée json"
+msgstr "données json"
#: apps/wrapped/models.py:66
msgid "data in the wrapped and generated by the script generate_wrapped"
msgstr "donnée dans le wrapped et générée par le script generate_wrapped"
-#: apps/wrapped/models.py:70 note_kfet/templates/base.html:114
+#: apps/wrapped/models.py:70 note_kfet/templates/base.html:115
msgid "Wrapped"
msgstr "Wrapped"
@@ -3787,7 +3803,7 @@ msgid "Copy link"
msgstr "Copier le lien"
#: apps/wrapped/templates/wrapped/1/wrapped_base.html:16
-#: note_kfet/templates/base.html:14
+#: note_kfet/templates/base.html:15
msgid "The ENS Paris-Saclay BDE note."
msgstr "La note du BDE de l'ENS Paris-Saclay."
@@ -3890,7 +3906,7 @@ msgid ""
"Do not forget to ask permission to people who are in your wrapped before to "
"make them public"
msgstr ""
-"N'oublies pas de demander la permission des personnes apparaissant dans un "
+"N'oublie pas de demander la permission des personnes apparaissant dans un "
"wrapped avant de le rendre public"
#: apps/wrapped/templates/wrapped/wrapped_list.html:40
@@ -3909,19 +3925,19 @@ msgstr "Le wrapped est public"
msgid "List of wrapped"
msgstr "Liste des wrapped"
-#: note_kfet/settings/base.py:177
+#: note_kfet/settings/base.py:180
msgid "German"
msgstr "Allemand"
-#: note_kfet/settings/base.py:178
+#: note_kfet/settings/base.py:181
msgid "English"
msgstr "Anglais"
-#: note_kfet/settings/base.py:179
+#: note_kfet/settings/base.py:182
msgid "Spanish"
msgstr "Espagnol"
-#: note_kfet/settings/base.py:180
+#: note_kfet/settings/base.py:183
msgid "French"
msgstr "Français"
@@ -3982,34 +3998,34 @@ msgstr ""
msgid "Reset"
msgstr "Réinitialiser"
-#: note_kfet/templates/base.html:84
+#: note_kfet/templates/base.html:85
msgid "Users"
msgstr "Utilisateur·rices"
-#: note_kfet/templates/base.html:90
+#: note_kfet/templates/base.html:91
msgid "Clubs"
msgstr "Clubs"
-#: note_kfet/templates/base.html:125
+#: note_kfet/templates/base.html:126
msgid "Admin"
msgstr "Admin"
-#: note_kfet/templates/base.html:139
+#: note_kfet/templates/base.html:140
msgid "My account"
msgstr "Mon compte"
-#: note_kfet/templates/base.html:142
+#: note_kfet/templates/base.html:145
msgid "Log out"
msgstr "Se déconnecter"
-#: note_kfet/templates/base.html:150
+#: note_kfet/templates/base.html:154
#: note_kfet/templates/registration/signup.html:6
#: note_kfet/templates/registration/signup.html:11
#: note_kfet/templates/registration/signup.html:28
msgid "Sign up"
msgstr "Inscription"
-#: note_kfet/templates/base.html:157
+#: note_kfet/templates/base.html:161
#: note_kfet/templates/registration/login.html:6
#: note_kfet/templates/registration/login.html:15
#: note_kfet/templates/registration/login.html:38
@@ -4017,7 +4033,7 @@ msgstr "Inscription"
msgid "Log in"
msgstr "Se connecter"
-#: note_kfet/templates/base.html:171
+#: note_kfet/templates/base.html:175
msgid ""
"You are not a BDE member anymore. Please renew your membership if you want "
"to use the note."
@@ -4025,7 +4041,7 @@ msgstr ""
"Vous n'êtes plus adhérent·e BDE. Merci de réadhérer si vous voulez profiter "
"de la note."
-#: note_kfet/templates/base.html:177
+#: note_kfet/templates/base.html:181
msgid ""
"Your e-mail address is not validated. Please check your mail inbox and click "
"on the validation link."
@@ -4033,7 +4049,7 @@ msgstr ""
"Votre adresse e-mail n'est pas validée. Merci de vérifier votre boîte mail "
"et de cliquer sur le lien de validation."
-#: note_kfet/templates/base.html:183
+#: note_kfet/templates/base.html:187
msgid ""
"You declared that you opened a bank account in the Société générale. The "
"bank did not validate the creation of the account to the BDE, so the "
@@ -4047,22 +4063,38 @@ msgstr ""
"vérification peut durer quelques jours. Merci de vous assurer de bien aller "
"au bout de vos démarches."
-#: note_kfet/templates/base.html:206
+#: note_kfet/templates/base.html:214
msgid "Contact us"
msgstr "Nous contacter"
-#: note_kfet/templates/base.html:208
+#: note_kfet/templates/base.html:216
msgid "Technical Support"
msgstr "Support technique"
-#: note_kfet/templates/base.html:210
+#: note_kfet/templates/base.html:218
msgid "Charte Info (FR)"
msgstr "Charte Info (FR)"
-#: note_kfet/templates/base.html:212
+#: note_kfet/templates/base.html:220
msgid "FAQ (FR)"
msgstr "FAQ (FR)"
+#: note_kfet/templates/base.html:222
+msgid "Managed by BDE"
+msgstr "Géré par le BDE"
+
+#: note_kfet/templates/base.html:224
+msgid "Hosted by Cr@ns"
+msgstr "Hébergé par le Cr@ans"
+
+#: note_kfet/templates/base.html:266
+msgid "The note is not available for now"
+msgstr "La note est indisponible pour le moment"
+
+#: note_kfet/templates/base.html:268
+msgid "Thank you for your understanding -- The Respos Info of BDE"
+msgstr "Merci de votre compréhension -- Les Respos Info du BDE"
+
#: note_kfet/templates/base_search.html:15
msgid "Search by attribute such as name..."
msgstr "Chercher par un attribut tel que le nom..."
@@ -4071,6 +4103,41 @@ msgstr "Chercher par un attribut tel que le nom..."
msgid "There is no results."
msgstr "Il n'y a pas de résultat."
+#: note_kfet/templates/cas/logged.html:8
+msgid ""
+"
Log In Successful
You have successfully logged into the Central "
+"Authentication Service. For security reasons, please Log Out and Exit "
+"your web browser when you are done accessing services that require "
+"authentication!"
+msgstr ""
+"
Connection réussie
Vous vous êtes bien connecté au Service Central d'Authentification."
+" Pour des raisons de sécurité, veuillez vous déconnecter et fermer votre navigateur internet "
+"une fois que vous aurez fini d'accéder aux services qui requiert une authentification !"
+
+#: note_kfet/templates/cas/logged.html:14
+msgid "Log me out from all my sessions"
+msgstr "Me déconnecter de toutes mes sessions"
+
+#: note_kfet/templates/cas/logged.html:20
+msgid "Forget the identity provider"
+msgstr "Oublier le fournisseur d'identité"
+
+#: note_kfet/templates/cas/logged.html:24
+msgid "Logout"
+msgstr "Déconnexion"
+
+#: note_kfet/templates/cas/login.html:11
+msgid "Please log in"
+msgstr "Veuillez vous connecter"
+
+#: note_kfet/templates/cas/login.html:23
+msgid "Login"
+msgstr "Connexion"
+
+#: note_kfet/templates/cas/warn.html:14
+msgid "Connect to the service"
+msgstr "Connexion au service"
+
#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8
msgid "Are you sure to delete the application"
msgstr "Êtes-vous sûr⋅e de vouloir supprimer l'application"
@@ -4716,7 +4783,7 @@ msgstr ""
#, python-brace-format
#~ msgid "QR-code number {qr_code_number}"
-#~ msgstr "numéro du QR-code {qr_code_number}"
+#~ msgstr "Numéro du QR-code {qr_code_number}"
#~ msgid "was eaten"
#~ msgstr "a été mangé"
diff --git a/note.cron b/note.cron
index fb45a4b3..6c2b94c6 100644
--- a/note.cron
+++ b/note.cron
@@ -27,5 +27,6 @@ MAILTO=notekfet2020@lists.crans.org
# Vider les tokens Oauth2
00 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py cleartokens -v 0
# Envoyer la liste des abonnés à la NL BDA
- 00 10 * * 0 root cd /var/www/note_kfet && env/bin/python manage.py extract_ml_registrations -t art -e "bda.ensparissaclay@gmail.com"
-
\ No newline at end of file
+ 00 10 * * 0 root cd /var/www/note_kfet && env/bin/python manage.py extract_ml_registrations -t art -e "bda.ensparissaclay@gmail.com"
+# Envoyer la liste de la bouffe au club et aux GCKs
+ 00 8 * * 1 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_for_food --report --club
diff --git a/note_kfet/admin.py b/note_kfet/admin.py
index 6bda409f..7f4effe3 100644
--- a/note_kfet/admin.py
+++ b/note_kfet/admin.py
@@ -56,3 +56,8 @@ if "cas_server" in settings.INSTALLED_APPS:
from cas_server.models import *
admin_site.register(ServicePattern, ServicePatternAdmin)
admin_site.register(FederatedIendityProvider, FederatedIendityProviderAdmin)
+
+if "constance" in settings.INSTALLED_APPS:
+ from constance.admin import *
+ from constance.models import *
+ admin_site.register([Config], ConstanceAdmin)
diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py
index 0f3a757c..1fa0a0ea 100644
--- a/note_kfet/settings/base.py
+++ b/note_kfet/settings/base.py
@@ -39,7 +39,9 @@ SECURE_HSTS_PRELOAD = True
INSTALLED_APPS = [
# External apps
'bootstrap_datepicker_plus',
+ 'cas_server',
'colorfield',
+ 'constance',
'crispy_bootstrap4',
'crispy_forms',
# 'django_htcpcp_tea',
@@ -111,6 +113,7 @@ TEMPLATES = [
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
+ 'constance.context_processors.config',
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
@@ -270,7 +273,7 @@ OAUTH2_PROVIDER = {
'PKCE_REQUIRED': False, # PKCE (fix a breaking change of django-oauth-toolkit 2.0.0)
'OIDC_ENABLED': True,
'OIDC_RSA_PRIVATE_KEY':
- os.getenv('OIDC_RSA_PRIVATE_KEY', '/var/secrets/oidc.key'),
+ os.getenv('OIDC_RSA_PRIVATE_KEY', 'CHANGE_ME_IN_ENV_SETTINGS').replace('\\n', '\n'), # for multilines
'SCOPES': { 'openid': "OpenID Connect scope" },
}
@@ -307,6 +310,30 @@ PHONENUMBER_DEFAULT_REGION = 'FR'
# We add custom information to CAS, in order to give a normalized name to other services
CAS_AUTH_CLASS = 'member.auth.CustomAuthUser'
+CAS_LOGIN_TEMPLATE = 'cas/login.html'
+CAS_LOGOUT_TEMPLATE = 'cas/logout.html'
+CAS_WARN_TEMPLATE = 'cas/warn.html'
+CAS_LOGGED_TEMPLATE = 'cas/logged.html'
# Default field for primary key
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
+
+# Constance settings
+CONSTANCE_ADDITIONAL_FIELDS = {
+ 'banner_type': ['django.forms.fields.ChoiceField', {
+ 'widget': 'django.forms.Select',
+ 'choices': (('info', 'Info'), ('success', 'Success'), ('warning', 'Warning'), ('danger', 'Danger'))
+ }],
+}
+CONSTANCE_CONFIG = {
+ 'BANNER_MESSAGE': ('', 'Some message', str),
+ 'BANNER_TYPE': ('info', 'Banner type', 'banner_type'),
+ 'MAINTENANCE': (False, 'check for mainteance mode', bool),
+ 'MAINTENANCE_MESSAGE': ('', 'Some maintenance message', str),
+}
+CONSTANCE_CONFIG_FIELDSETS = {
+ 'Maintenance': ('MAINTENANCE_MESSAGE', 'MAINTENANCE'),
+ 'Banner': ('BANNER_MESSAGE', 'BANNER_TYPE'),
+}
+CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
+CONSTANCE_SUPERUSER_ONLY = True
diff --git a/note_kfet/templates/base.html b/note_kfet/templates/base.html
index 1c601c50..b6762c9b 100644
--- a/note_kfet/templates/base.html
+++ b/note_kfet/templates/base.html
@@ -5,6 +5,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
+{% if not config.MAINTENANCE %}
@@ -138,9 +139,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% trans "My account" %}
-
- {% trans "Log out" %}
-
+
{% else %}
@@ -188,7 +192,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endblocktrans %}
{% endif %}
- {# TODO Add banners #}
+ {% if config.BANNER_MESSAGE and user.is_authenticated %}
+