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 update %} - - {% trans "Update" %} - + + {% trans "Update" %} + {% endif %} {% if add_ingredient %} - - {% trans "Add to a meal" %} - + + {% trans "Add to a meal" %} + {% endif %} {% if manage_ingredients %} - {% trans "Manage ingredients" %} - + {% trans "Manage ingredients" %} + {% endif %} - - {% trans "Return to the food list" %} - + + {% trans "Return to the food list" %} +
-{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/apps/food/templates/food/food_list.html b/apps/food/templates/food/food_list.html index efc7a554..bd54ece9 100644 --- a/apps/food/templates/food/food_list.html +++ b/apps/food/templates/food/food_list.html @@ -7,7 +7,52 @@ SPDX-License-Identifier: GPL-3.0-or-later {% load i18n %} {% block content %} -{{ block.super }} +
+

+ {{ title }} +

+
+ +
+
+ + +
+
+
+
+ +
+ + {% block extra_inside_card %} + {% endblock %} + +
+ {% if table.data %} + {% render_table table %} + {% else %} +
+
+ {% trans "There is no results." %} +
+
+ {% endif %} +
+

@@ -68,4 +113,20 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endfor %} {% 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" %} - +
+ {% csrf_token %} + +
{% 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 %} +
+ {{ config.BANNER_MESSAGE }} +
+ {% endif %} {% block content %}

Default content...

@@ -210,6 +218,10 @@ SPDX-License-Identifier: GPL-3.0-or-later class="text-muted">{% trans "Charte Info (FR)" %} — {% trans "FAQ (FR)" %} — + {% trans "Managed by BDE" %} — + {% trans "Hosted by Cr@ns" %} — {% csrf_token %} {% trans "Log me out from all my sessions" %} + + + {% if settings.CAS_FEDERATE and request.COOKIES.remember_provider %} +
+ +
+ {% endif %} + + + + +{% endblock %} diff --git a/note_kfet/templates/cas/login.html b/note_kfet/templates/cas/login.html new file mode 100644 index 00000000..84fd0cf8 --- /dev/null +++ b/note_kfet/templates/cas/login.html @@ -0,0 +1,42 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) by BDE ENS-Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n %} + +{% block ante_messages %} + {% if auto_submit %}{% endif %} +{% endblock %} + +{% block content %} +
+
+
+ +
+ +{% endblock %} + +{% block javascript_inline %} +jQuery(function( $ ){ + $("#id_warn").click(function(e){ + if($("#id_warn").is(':checked')){ + createCookie("warn", "on", 10 * 365); + } else { + eraseCookie("warn"); + } + }); +}); +{% if auto_submit %}document.getElementById('login_form').submit(); // SUBMIT FORM{% endif %} +{% endblock %} diff --git a/note_kfet/templates/cas/logout.html b/note_kfet/templates/cas/logout.html new file mode 100644 index 00000000..814f7d33 --- /dev/null +++ b/note_kfet/templates/cas/logout.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) by BDE ENS-Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n static %} +{% block content %} + +{% endblock %} + diff --git a/note_kfet/templates/cas/warn.html b/note_kfet/templates/cas/warn.html new file mode 100644 index 00000000..89ee1815 --- /dev/null +++ b/note_kfet/templates/cas/warn.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) by BDE ENS-Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n static %} + +{% block content %} +
+
+ +
+
+{% endblock %} + diff --git a/requirements.txt b/requirements.txt index f4a32c97..c40bf0bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,21 @@ -beautifulsoup4~=4.12.3 -crispy-bootstrap4~=2023.1 -Django~=4.2.9 +beautifulsoup4~=4.13.4 +crispy-bootstrap4~=2025.6 +Django~=5.2.4 django-bootstrap-datepicker-plus~=5.0.5 -#django-cas-server~=2.0.0 -django-colorfield~=0.11.0 -django-crispy-forms~=2.1.0 -django-extensions>=3.2.3 -django-filter~=23.5 +django-cas-server~=3.1.0 +django-colorfield~=0.14.0 +django-constance~=4.3.2 +django-crispy-forms~=2.4.0 +django-extensions>=4.1.0 +django-filter~=25.1 #django-htcpcp-tea~=0.8.1 -django-mailer~=2.3.1 -django-oauth-toolkit~=2.3.0 -django-phonenumber-field~=7.3.0 +django-mailer~=2.3.2 +django-oauth-toolkit~=3.0.1 +django-phonenumber-field~=8.1.0 django-polymorphic~=3.1.0 -djangorestframework~=3.14.0 +djangorestframework~=3.16.0 django-rest-polymorphic~=0.1.10 -django-tables2~=2.7.0 +django-tables2~=2.7.5 python-memcached~=1.62 -phonenumbers~=8.13.28 -Pillow>=10.2.0 +phonenumbers~=9.0.8 +Pillow>=11.3.0 diff --git a/tox.ini b/tox.ini index 1bfeb593..1fa7cf7a 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,13 @@ [tox] envlist = # Ubuntu 22.04 Python - py310-django42 + py310-django52 # Debian Bookworm Python - py311-django42 + py311-django52 # Ubuntu 24.04 Python - py312-django42 + py312-django52 linters skipsdist = True @@ -32,8 +32,7 @@ deps = pep8-naming pyflakes commands = - flake8 apps --extend-exclude apps/scripts,apps/wrapped/management/commands - flake8 apps/wrapped/management/commands --extend-ignore=C901 + flake8 apps --extend-exclude apps/scripts [flake8] ignore = W503, I100, I101, B019