From 2f49ab8124c61cf03e5f5194859d516d55d385a4 Mon Sep 17 00:00:00 2001 From: catvayor Date: Mon, 17 Feb 2025 16:22:27 +0100 Subject: [PATCH 01/15] doc(readme): missing depencies --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28c6686d..de0779bd 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Il vous faudra installer pip, les librairies de développement de python ainsi que sqlite3, un moteur de base de données léger et simple d'utilisation. Sous Debian et dérivées (Ubuntu, ...) : - sudo apt-get install python3-pip python3-dev python3-venv sqlite3 + sudo apt-get install python3-pip python3-dev python3-venv sqlite3 libsasl2-dev python-dev-is-python3 libldap2-dev libssl-dev Si vous décidez d'utiliser un environnement virtuel Python (virtualenv; fortement conseillé), déplacez-vous dans le dossier où est installé GestioCOF From 99fa61bc3eebb015abe952692aca0ade97f82c5e Mon Sep 17 00:00:00 2001 From: catvayor Date: Mon, 17 Feb 2025 17:05:28 +0100 Subject: [PATCH 02/15] doc(readme): adapt to settings refactor --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index de0779bd..5708277c 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,15 @@ Pour l'activer, il faut taper . venv/bin/activate -depuis le même dossier. +depuis le même dossier. Pour préparer l'environnement à l'utilisation de `./manage.py` +(qui permet de faire des tests en local), il faut également taper + + export CREDENTIALS_DIRECTORY=$(realpath .credentials) + export DJANGO_SETTINGS_MODULE=gestioasso.settings.local + export GESTIOCOF_DEBUG=true + export GESTIOCOF_STATIC_ROOT=$(realpath .static) + export GESTIOBDS_DEBUG=true + export GESTIOBDS_STATIC_ROOT=$(realpath .static) Vous pouvez maintenant installer les dépendances Python depuis le fichier `requirements-devel.txt` : From 3539c8fcbf52bedab912081eddf32757a6fff47e Mon Sep 17 00:00:00 2001 From: catvayor Date: Mon, 17 Feb 2025 17:11:10 +0100 Subject: [PATCH 03/15] fix(prepare_django): make the .static dir --- provisioning/prepare_django.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/provisioning/prepare_django.sh b/provisioning/prepare_django.sh index 87cc70b3..324deaf1 100644 --- a/provisioning/prepare_django.sh +++ b/provisioning/prepare_django.sh @@ -2,6 +2,7 @@ set -euC +mkdir -p .static python manage.py migrate --noinput python manage.py sync_page_translation_fields python manage.py update_translation_fields From 74cda538ff3255cbed7fb753166018da61ec3089 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Thu, 16 Jan 2025 17:21:16 +0100 Subject: [PATCH 04/15] fix(cof_settings): Set ASGI_APPLICATION --- gestioasso/settings_cof.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gestioasso/settings_cof.py b/gestioasso/settings_cof.py index 4d24caf2..95f4136b 100644 --- a/gestioasso/settings_cof.py +++ b/gestioasso/settings_cof.py @@ -201,6 +201,8 @@ CHANNEL_LAYERS = credentials.get_json( }, ) +ASGI_APPLICATION = "gestioasso.routing.application" + CORS_ORIGIN_WHITELIST = credentials.get("CORS_ORIGIN_WHITELIST", []) From bed4313df315adcd7fed5cb06c1864440f8ad7fd Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Thu, 16 Jan 2025 18:16:42 +0100 Subject: [PATCH 05/15] fix(settings_cof): Update variables --- gestioasso/settings_cof.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gestioasso/settings_cof.py b/gestioasso/settings_cof.py index 95f4136b..cc54dcf1 100644 --- a/gestioasso/settings_cof.py +++ b/gestioasso/settings_cof.py @@ -203,7 +203,8 @@ CHANNEL_LAYERS = credentials.get_json( ASGI_APPLICATION = "gestioasso.routing.application" -CORS_ORIGIN_WHITELIST = credentials.get("CORS_ORIGIN_WHITELIST", []) +CORS_ALLOWED_ORIGINS = credentials.get("CORS_ALLOWED_ORIGINS", []) +CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS SITE_ID = 1 From b7d8af144b016a587d5ba9fc30840dcd126f38ef Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Thu, 16 Jan 2025 18:18:36 +0100 Subject: [PATCH 06/15] fix(settings_cof): Origins must have a scheme --- gestioasso/settings_cof.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gestioasso/settings_cof.py b/gestioasso/settings_cof.py index cc54dcf1..6bccefc2 100644 --- a/gestioasso/settings_cof.py +++ b/gestioasso/settings_cof.py @@ -204,7 +204,7 @@ CHANNEL_LAYERS = credentials.get_json( ASGI_APPLICATION = "gestioasso.routing.application" CORS_ALLOWED_ORIGINS = credentials.get("CORS_ALLOWED_ORIGINS", []) -CSRF_TRUSTED_ORIGINS = ALLOWED_HOSTS +CSRF_TRUSTED_ORIGINS = [f"https://{host}" for host in ALLOWED_HOSTS] SITE_ID = 1 From 2a13f685ec4f3947d9146cfdd26e4a7ab071c637 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Thu, 16 Jan 2025 18:47:11 +0100 Subject: [PATCH 07/15] fix(kfet/open): Wrap sync function for async use --- kfet/open/consumers.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kfet/open/consumers.py b/kfet/open/consumers.py index 1b190212..4b21bef2 100644 --- a/kfet/open/consumers.py +++ b/kfet/open/consumers.py @@ -1,3 +1,5 @@ +from asgiref.sync import sync_to_async + from ..decorators import kfet_is_team from ..utils import DjangoJsonWebsocketConsumer, PermConsumerMixin from .open import kfet_open @@ -19,7 +21,7 @@ class OpenKfetConsumer(PermConsumerMixin, DjangoJsonWebsocketConsumer): """Send current status on connect.""" await super().connect() - group = "team" if kfet_is_team(self.user) else "base" + group = "team" if await sync_to_async(kfet_is_team)(self.user) else "base" await self.channel_layer.group_add(f"kfet.open.{group}", self.channel_name) From 371580015c6c54657b1f6b4a54608002b727d441 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Fri, 17 Jan 2025 08:42:07 +0100 Subject: [PATCH 08/15] fix(gestioncof/cms): The tag ifnotequal has been deprecated for 5 years and has now been removed --- gestioncof/cms/templates/cofcms/cof_actu_index_page.html | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gestioncof/cms/templates/cofcms/cof_actu_index_page.html b/gestioncof/cms/templates/cofcms/cof_actu_index_page.html index 4508a66c..a1ea29ed 100644 --- a/gestioncof/cms/templates/cofcms/cof_actu_index_page.html +++ b/gestioncof/cms/templates/cofcms/cof_actu_index_page.html @@ -22,10 +22,10 @@
{% if actus.has_previous %} - {% trans "Actualités plus récentes" %} + {% trans "Actualités plus récentes" %} {% endif %} {% if actus.has_next %} - {% trans "Actualités plus anciennes" %} + {% trans "Actualités plus anciennes" %} {% endif %} {% for actu in page.actus %} @@ -44,10 +44,10 @@ {% endfor %} {% if actus.has_previous %} - {% trans "Actualités plus récentes" %} + {% trans "Actualités plus récentes" %} {% endif %} {% if actus.has_next %} - {% trans "Actualités plus anciennes" %} + {% trans "Actualités plus anciennes" %} {% endif %}
{% endblock %} From ce4d87571ddd719dcff097da09bc3264542a1bad Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Fri, 17 Jan 2025 10:20:46 +0100 Subject: [PATCH 09/15] feat(kfet): Add a summary of the account balances --- kfet/templates/kfet/account.html | 22 ++++++++++++++++++++++ kfet/views.py | 11 ++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/kfet/templates/kfet/account.html b/kfet/templates/kfet/account.html index 9b63c1da..682eafde 100644 --- a/kfet/templates/kfet/account.html +++ b/kfet/templates/kfet/account.html @@ -31,6 +31,28 @@ {% endif %} + + + + {% endblock %} {% block main %} diff --git a/kfet/views.py b/kfet/views.py index 83d1a9e2..2f9ae6f9 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -184,7 +184,16 @@ class DemandeSoireeView(FormView): @teamkfet_required def account(request): accounts = Account.objects.select_related("cofprofile__user").order_by("trigramme") - return render(request, "kfet/account.html", {"accounts": accounts}) + positive_accounts = Account.objects.filter(balance__gte=0) + negative_accounts = Account.objects.filter(balance__lt=0) + + return render(request, "kfet/account.html", { + "accounts": accounts, + "positive_count": positive_accounts.count(), + "positives_sum": sum(acc.balance for acc in positive_accounts), + "negative_count": negative_accounts.count(), + "negatives_sum": sum(acc.balance for acc in negative_accounts), + }) @login_required From 6c095470f3e061afde5bf6bed9b3fb7df807f021 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Mon, 20 Jan 2025 20:14:35 +0100 Subject: [PATCH 10/15] fix(kfet/accounts): Exclude #13 from the statistics --- kfet/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kfet/views.py b/kfet/views.py index 2f9ae6f9..a91e884e 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -184,8 +184,8 @@ class DemandeSoireeView(FormView): @teamkfet_required def account(request): accounts = Account.objects.select_related("cofprofile__user").order_by("trigramme") - positive_accounts = Account.objects.filter(balance__gte=0) - negative_accounts = Account.objects.filter(balance__lt=0) + positive_accounts = Account.objects.filter(balance__gte=0).exclude(trigramme="#13") + negative_accounts = Account.objects.filter(balance__lt=0).exclude(trigramme="#13") return render(request, "kfet/account.html", { "accounts": accounts, From 9bd4486151ff2c2aef9d14ac38ed72029fbfceda Mon Sep 17 00:00:00 2001 From: catvayor Date: Thu, 23 Jan 2025 19:27:56 +0100 Subject: [PATCH 11/15] feat(kfet): block self deposit --- kfet/views.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/kfet/views.py b/kfet/views.py index a91e884e..1f27fa78 100644 --- a/kfet/views.py +++ b/kfet/views.py @@ -1180,6 +1180,13 @@ def kpsul_perform_operations(request): operationgroup.amount += operation.amount if operation.type == Operation.DEPOSIT: required_perms.add("kfet.perform_deposit") + if request.user.profile.account_kfet == on_acc: + data["errors"].append( + { + "code": "auto_deposit", + "message": ("Impossible de charger son propre trigramme"), + } + ) if operation.type == Operation.EDIT: required_perms.add("kfet.edit_balance_account") need_comment = True From c6e68d13f4ab56ca0012ecd89e18f46beaaac1c9 Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Thu, 23 Jan 2025 22:52:22 +0100 Subject: [PATCH 12/15] fix(kfet/utils): Add sync_to_async --- kfet/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kfet/utils.py b/kfet/utils.py index 540f260c..e7ec91c2 100644 --- a/kfet/utils.py +++ b/kfet/utils.py @@ -1,6 +1,7 @@ import json import math +from asgiref.sync import sync_to_async from channels.generic.websocket import AsyncJsonWebsocketConsumer from django.core.cache import cache from django.core.serializers.json import DjangoJSONEncoder @@ -95,7 +96,7 @@ class PermConsumerMixin: """Check permissions on connection.""" self.user = self.scope["user"] - if self.user.has_perms(self.perms_connect): + if await sync_to_async(self.user.has_perms)(self.perms_connect): await super().connect() else: await self.close() From 2d95cea3a6a7799bde15319f3971c3538fc614cd Mon Sep 17 00:00:00 2001 From: Tom Hubrecht Date: Tue, 18 Feb 2025 21:57:02 +0100 Subject: [PATCH 13/15] feat(shared): Add a view for exporting members to a mailing-list --- bds/urls.py | 7 ++++++ gestioasso/settings/local.py | 3 +++ gestioasso/settings_bds.py | 3 +++ gestioasso/settings_cof.py | 3 +++ gestioncof/urls.py | 14 +++++++++++ shared/views.py | 45 ++++++++++++++++++++++++++++++++++-- 6 files changed, 73 insertions(+), 2 deletions(-) diff --git a/bds/urls.py b/bds/urls.py index 95065773..68d780df 100644 --- a/bds/urls.py +++ b/bds/urls.py @@ -1,6 +1,7 @@ from django.urls import path from bds import views +from shared.views import SympaListView app_name = "bds" urlpatterns = [ @@ -21,4 +22,10 @@ urlpatterns = [ name="members.expired", ), path("members/reset", views.ResetMembershipView.as_view(), name="members.reset"), + # Sympa export view + path( + "sympa/members/", + SympaListView.as_view(filters={"bds__is_member": True}), + name="export.sympa", + ), ] diff --git a/gestioasso/settings/local.py b/gestioasso/settings/local.py index 2cba6a10..c6a22e64 100644 --- a/gestioasso/settings/local.py +++ b/gestioasso/settings/local.py @@ -27,6 +27,9 @@ ALLOWED_HOSTS = [] DEBUG = True EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" +SYMPA_PASSWORD = b"sympa" +SYMPA_USERNAME = b"sympa" + if TESTING: PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"] diff --git a/gestioasso/settings_bds.py b/gestioasso/settings_bds.py index 231ae101..e640b222 100644 --- a/gestioasso/settings_bds.py +++ b/gestioasso/settings_bds.py @@ -26,6 +26,9 @@ EMAIL_HOST = credentials.get("EMAIL_HOST") DEFAULT_AUTO_FIELD = "django.db.models.AutoField" +SYMPA_PASSWORD = credentials["SYMPA_PASSWORD"].encode() +SYMPA_USERNAME = credentials["SYMPA_USERNAME"].encode() + ## # Installed Apps configuration diff --git a/gestioasso/settings_cof.py b/gestioasso/settings_cof.py index 6bccefc2..893aaec4 100644 --- a/gestioasso/settings_cof.py +++ b/gestioasso/settings_cof.py @@ -30,6 +30,9 @@ LDAP_SERVER_URL = credentials.get("LDAP_SERVER_URL") DEFAULT_AUTO_FIELD = "django.db.models.AutoField" +SYMPA_PASSWORD = credentials["SYMPA_PASSWORD"].encode() +SYMPA_USERNAME = credentials["SYMPA_USERNAME"].encode() + ## # Installed Apps configuration diff --git a/gestioncof/urls.py b/gestioncof/urls.py index d0ba75c7..624f8e22 100644 --- a/gestioncof/urls.py +++ b/gestioncof/urls.py @@ -4,6 +4,16 @@ from django.views.generic.base import TemplateView from django_cas_ng import views as django_cas_views from gestioncof import csv_views, views +from shared.views import SympaListView + +sympa_patterns = [ + path( + f"{mailing}/", + SympaListView.as_view(filters={f"profile__mailing_{mailing}": True}), + name=f"sympa.{mailing}", + ) + for mailing in ["bda", "bda_revente", "cof", "unernestaparis"] +] export_patterns = [ path("members", views.export_members, name="export.members"), @@ -162,4 +172,8 @@ urlpatterns = [ # Clubs # ----- path("clubs/", include(clubs_patterns)), + # ----- + # Sympa export + # ----- + path("sympa/", include(sympa_patterns)), ] diff --git a/shared/views.py b/shared/views.py index a1ffb185..fbff4ec0 100644 --- a/shared/views.py +++ b/shared/views.py @@ -1,12 +1,53 @@ +import base64 from collections import namedtuple +from typing import Any from dal import autocomplete +from django.conf import settings +from django.contrib.auth import get_user_model from django.core.exceptions import ImproperlyConfigured -from django.http import Http404 -from django.views.generic import TemplateView +from django.http import Http404, HttpResponse +from django.views.generic import TemplateView, View from shared.autocomplete import ModelSearch +User = get_user_model() + + +class SympaListView(View): + realm = "sympa" + + username = settings.SYMPA_USERNAME + password = settings.SYMPA_PASSWORD + + filters: dict[str, Any] = {} + + def dispatch(self, request, *args, **kwargs): + if "HTTP_AUTHORIZATION" in request.META: + auth = request.META["HTTP_AUTHORIZATION"].split() + + if len(auth) == 2 and auth[0].lower() == "basic": + name, passwd = base64.b64decode(auth[1]).split(b":") + + if name == self.username and passwd == self.password: + return self.render_to_response(request, *args, **kwargs) + + return HttpResponse( + status=401, headers={"WWW-Authenticate": f'Basic realm="{self.realm}"'} + ) + + def render_to_response(self, request, *args, **kwargs): + """ + Renders a list of emails in a text response. + """ + + users = User.objects.filter(**self.filters) + + return HttpResponse( + b"\n".join(u.email.encode("utf-8") for u in users if u.email), + content_type="text/plain", + ) + class Select2QuerySetView(ModelSearch, autocomplete.Select2QuerySetView): """Compatibility layer between ModelSearch and Select2QuerySetView.""" From 991dc79a4fe8452d752b35b4932beb11470699b7 Mon Sep 17 00:00:00 2001 From: catvayor Date: Tue, 25 Feb 2025 13:26:50 +0100 Subject: [PATCH 14/15] fix(mails/kfet): use right dict key --- gestioasso/settings_cof.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gestioasso/settings_cof.py b/gestioasso/settings_cof.py index 893aaec4..057019a2 100644 --- a/gestioasso/settings_cof.py +++ b/gestioasso/settings_cof.py @@ -270,7 +270,7 @@ MAIL_DATA = { "FROM": "Le BdA ", "REPLYTO": "Le BdA ", }, - "rappel_negatif": { + "kfet": { "FROM": "La K-Fêt ", "REPLYTO": "La K-Fêt ", }, From 025f183ff2f080dd37ef237833c694e0e1a50e75 Mon Sep 17 00:00:00 2001 From: catvayor Date: Tue, 25 Feb 2025 17:05:57 +0100 Subject: [PATCH 15/15] fix(kfet/ws): 'type' field is required --- kfet/utils.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/kfet/utils.py b/kfet/utils.py index e7ec91c2..d5df3228 100644 --- a/kfet/utils.py +++ b/kfet/utils.py @@ -72,9 +72,6 @@ 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") - return json.dumps(content, cls=DjangoJSONEncoder)