1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-07-22 16:56:48 +02:00

Compare commits

..

6 Commits

Author SHA1 Message Date
c411197af3 multiline support for RSA key in env 2025-06-27 22:13:43 +02:00
cdc6f0a3f8 Fix jwks.json 2025-06-27 12:13:54 +02:00
df0d886db9 linters 2025-06-17 11:46:33 +02:00
092cc37320 OIDC 0 Quark 1 2025-06-17 00:38:11 +02:00
d71105976f oidc 2025-06-14 18:01:42 +02:00
89cc03141b allow search with club name 2025-06-12 18:48:29 +02:00
32 changed files with 2375 additions and 7422 deletions

View File

@ -21,3 +21,6 @@ EMAIL_PASSWORD=CHANGE_ME
# Wiki configuration # Wiki configuration
WIKI_USER=NoteKfet2020 WIKI_USER=NoteKfet2020
WIKI_PASSWORD= WIKI_PASSWORD=
# OIDC
OIDC_RSA_PRIVATE_KEY=CHANGE_ME

View File

@ -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** 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 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 exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et copier la clé dans .env dans le champ
emplacement dans `OIDC_RSA_PRIVATE_KEY` (par défaut `/var/secrets/oidc.key`). `OIDC_RSA_PRIVATE_KEY`.
7. Enjoy : 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** 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 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 exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner le champ
emplacement dans `OIDC_RSA_PRIVATE_KEY` (par défaut `/var/secrets/oidc.key`). `OIDC_RSA_PRIVATE_KEY` dans le .env (par défaut `/var/secrets/oidc.key`).
8. *Enjoy \o/* 8. *Enjoy \o/*

View File

@ -1,19 +0,0 @@
# Generated by Django 4.2.20 on 2025-05-08 19:07
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('activity', '0006_guest_school'),
]
operations = [
migrations.AlterField(
model_name='guest',
name='activity',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='activity.activity'),
),
]

View File

@ -234,7 +234,7 @@ class Guest(models.Model):
""" """
activity = models.ForeignKey( activity = models.ForeignKey(
Activity, Activity,
on_delete=models.CASCADE, on_delete=models.PROTECT,
related_name='+', related_name='+',
) )

View File

@ -95,23 +95,5 @@ SPDX-License-Identifier: GPL-3.0-or-later
errMsg(xhr.responseJSON); errMsg(xhr.responseJSON);
}); });
}); });
$("#delete_activity").click(function () {
if (!confirm("{% trans 'Are you sure you want to delete this activity?' %}")) {
return;
}
$.ajax({
url: "/api/activity/activity/{{ activity.pk }}/",
type: "DELETE",
headers: {
"X-CSRFTOKEN": CSRF_TOKEN
}
}).done(function () {
addMsg("{% trans 'Activity deleted' %}", "success");
window.location.href = "/activity/"; // Redirige vers la liste des activités
}).fail(function (xhr) {
errMsg(xhr.responseJSON);
});
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -70,10 +70,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% if ".change_"|has_perm:activity %} {% if ".change_"|has_perm:activity %}
<a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_update' pk=activity.pk %}" data-turbolinks="false"> {% trans "edit"|capfirst %}</a> <a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_update' pk=activity.pk %}" data-turbolinks="false"> {% trans "edit"|capfirst %}</a>
{% endif %} {% endif %}
{% if not activity.valid and ".delete_"|has_perm:activity %} {% if activity.activity_type.can_invite and not activity_started %}
<a class="btn btn-danger btn-sm my-1" id="delete_activity"> {% trans "delete"|capfirst %} </a>
{% endif %}
{% if activity.activity_type.can_invite and not activity_started and activity.valid %}
<a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_invite' pk=activity.pk %}" data-turbolinks="false"> {% trans "Invite" %}</a> <a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_invite' pk=activity.pk %}" data-turbolinks="false"> {% trans "Invite" %}</a>
{% endif %} {% endif %}
{% endif %} {% endif %}

View File

@ -15,5 +15,4 @@ urlpatterns = [
path('<int:pk>/update/', views.ActivityUpdateView.as_view(), name='activity_update'), path('<int:pk>/update/', views.ActivityUpdateView.as_view(), name='activity_update'),
path('new/', views.ActivityCreateView.as_view(), name='activity_create'), path('new/', views.ActivityCreateView.as_view(), name='activity_create'),
path('calendar.ics', views.CalendarView.as_view(), name='calendar_ics'), path('calendar.ics', views.CalendarView.as_view(), name='calendar_ics'),
path('<int:pk>/delete', views.ActivityDeleteView.as_view(), name='delete_activity'),
] ]

View File

@ -9,7 +9,7 @@ from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import transaction from django.db import transaction
from django.db.models import F, Q from django.db.models import F, Q
from django.http import HttpResponse, JsonResponse from django.http import HttpResponse
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@ -153,34 +153,6 @@ class ActivityUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]}) return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]})
class ActivityDeleteView(View):
"""
Deletes an Activity
"""
def delete(self, request, pk):
try:
activity = Activity.objects.get(pk=pk)
activity.delete()
return JsonResponse({"message": "Activity deleted"})
except Activity.DoesNotExist:
return JsonResponse({"error": "Activity not found"}, status=404)
def dispatch(self, *args, **kwargs):
"""
Don't display the delete button if the user has no right to delete.
"""
if not self.request.user.is_authenticated:
return self.handle_no_permission()
activity = Activity.objects.get(pk=self.kwargs["pk"])
if not PermissionBackend.check_perm(self.request, "activity.delete_activity", activity):
raise PermissionDenied(_("You are not allowed to delete this activity."))
if activity.valid:
raise PermissionDenied(_("This activity is valid."))
return super().dispatch(*args, **kwargs)
class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView): class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
""" """
Invite a Guest, The rules to invites someone are defined in `forms:activity.GuestForm` Invite a Guest, The rules to invites someone are defined in `forms:activity.GuestForm`

View File

@ -63,7 +63,8 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li
valid_regex = is_regex(pattern) valid_regex = is_regex(pattern)
suffix = '__iregex' if valid_regex else '__istartswith' suffix = '__iregex' if valid_regex else '__istartswith'
prefix = '^' if valid_regex else '' 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: else:
qs = qs.none() qs = qs.none()
search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view')) search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view'))
@ -168,8 +169,7 @@ class BasicFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
template_name = "food/food_update.html" template_name = "food/food_update.html"
def get_sample_object(self): def get_sample_object(self):
# We choose a club which may work or BDE else return BasicFood(
food = BasicFood(
name="", name="",
owner_id=1, owner_id=1,
expiry_date=timezone.now(), expiry_date=timezone.now(),
@ -178,14 +178,6 @@ class BasicFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
date_type='DLC', date_type='DLC',
) )
for membership in self.request.user.memberships.all():
club_id = membership.club.id
food.owner_id = club_id
if PermissionBackend.check_perm(self.request, "food.add_basicfood", food):
return food
return food
@transaction.atomic @transaction.atomic
def form_valid(self, form): def form_valid(self, form):
if QRCode.objects.filter(qr_code_number=self.kwargs['slug']).count() > 0: if QRCode.objects.filter(qr_code_number=self.kwargs['slug']).count() > 0:
@ -236,22 +228,13 @@ class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
template_name = "food/food_update.html" template_name = "food/food_update.html"
def get_sample_object(self): def get_sample_object(self):
# We choose a club which may work or BDE else return TransformedFood(
food = TransformedFood(
name="", name="",
owner_id=1, owner_id=1,
expiry_date=timezone.now(), expiry_date=timezone.now(),
is_ready=True, is_ready=True,
) )
for membership in self.request.user.memberships.all():
club_id = membership.club.id
food.owner_id = club_id
if PermissionBackend.check_perm(self.request, "food.add_transformedfood", food):
return food
return food
@transaction.atomic @transaction.atomic
def form_valid(self, form): def form_valid(self, form):
form.instance.expiry_date = timezone.now() + timedelta(days=3) form.instance.expiry_date = timezone.now() + timedelta(days=3)
@ -263,10 +246,10 @@ class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk}) return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk})
MAX_FORMS = 100 MAX_FORMS = 10
class ManageIngredientsView(LoginRequiredMixin, UpdateView): class ManageIngredientsView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
""" """
A view to manage ingredient for a transformed food A view to manage ingredient for a transformed food
""" """
@ -297,14 +280,6 @@ class ManageIngredientsView(LoginRequiredMixin, UpdateView):
ingredient.end_of_life = _('Fully used in {meal}'.format( ingredient.end_of_life = _('Fully used in {meal}'.format(
meal=self.object.name)) meal=self.object.name))
ingredient.save() ingredient.save()
# We recalculate new expiry date and allergens
self.object.expiry_date = self.object.creation_date + self.object.shelf_life
self.object.allergens.clear()
for ingredient in self.object.ingredients.iterator():
if not (ingredient.polymorphic_ctype.model == 'basicfood' and ingredient.date_type == 'DDM'):
self.object.expiry_date = min(self.object.expiry_date, ingredient.expiry_date)
self.object.allergens.set(self.object.allergens.union(ingredient.allergens.all()))
self.object.save(old_ingredients=old_ingredients, old_allergens=old_allergens) self.object.save(old_ingredients=old_ingredients, old_allergens=old_allergens)
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())

View File

@ -1,46 +0,0 @@
from django.db import migrations
def create_bda(apps, schema_editor):
"""
The club BDA is now pre-injected.
"""
Club = apps.get_model("member", "club")
NoteClub = apps.get_model("note", "noteclub")
Alias = apps.get_model("note", "alias")
ContentType = apps.get_model('contenttypes', 'ContentType')
polymorphic_ctype_id = ContentType.objects.get_for_model(NoteClub).id
Club.objects.get_or_create(
id=10,
name="BDA",
email="bda.ensparissaclay@gmail.com",
require_memberships=True,
membership_fee_paid=750,
membership_fee_unpaid=750,
membership_duration=396,
membership_start="2024-08-01",
membership_end="2025-09-30",
)
NoteClub.objects.get_or_create(
id=1937,
club_id=10,
polymorphic_ctype_id=polymorphic_ctype_id,
)
Alias.objects.get_or_create(
id=1937,
note_id=1937,
name="BDA",
normalized_name="bda",
)
class Migration(migrations.Migration):
dependencies = [
('member', '0013_auto_20240801_1436'),
]
operations = [
migrations.RunPython(create_bda),
]

View File

@ -3998,342 +3998,6 @@
"description": "Créer une transaction de ou vers la note d'un club tant que la source reste au dessus de -50 €" "description": "Créer une transaction de ou vers la note d'un club tant que la source reste au dessus de -50 €"
} }
}, },
{
"model": "permission.permission",
"pk": 271,
"fields": {
"model": [
"wei",
"bus"
],
"query": "{\"wei\": [\"club\"]}",
"type": "change",
"mask": 3,
"field": "",
"permanent": false,
"description": "Modifier n'importe quel bus du wei"
}
},
{
"model": "permission.permission",
"pk": 272,
"fields": {
"model": [
"wei",
"bus"
],
"query": "{\"wei\": [\"club\"]}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir tous les bus du wei"
}
},
{
"model": "permission.permission",
"pk": 273,
"fields": {
"model": [
"wei",
"busteam"
],
"query": "{\"bus__wei\": [\"club\"], \"bus__wei__membership_end__gte\": [\"today\"]}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir toutes les équipes WEI"
}
},
{
"model": "permission.permission",
"pk": 274,
"fields": {
"model": [
"member",
"club"
],
"query": "{\"bus__wei\": [\"club\"]}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir les informations de clubs des bus"
}
},
{
"model": "permission.permission",
"pk": 275,
"fields": {
"model": [
"member",
"club"
],
"query": "{\"bus__wei\": [\"club\"]}",
"type": "change",
"mask": 3,
"field": "",
"permanent": false,
"description": "Modifier les clubs des bus"
}
},
{
"model": "permission.permission",
"pk": 276,
"fields": {
"model": [
"member",
"membership"
],
"query": "{\"club__bus__wei\": [\"club\"]}",
"type": "add",
"mask": 3,
"field": "",
"permanent": false,
"description": "Ajouter un⋅e membre à un club de bus"
}
},
{
"model": "permission.permission",
"pk": 277,
"fields": {
"model": [
"member",
"membership"
],
"query": "{\"club__bus__wei\": [\"club\"]}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir les adhérents d'un club de bus"
}
},
{
"model": "permission.permission",
"pk": 278,
"fields": {
"model": [
"member",
"membership"
],
"query": "{\"club__bus__wei\": [\"club\"]}",
"type": "change",
"mask": 3,
"field": "",
"permanent": false,
"description": "Modifier l'adhésion d'un club de bus"
}
},
{
"model": "permission.permission",
"pk": 279,
"fields": {
"model": [
"note",
"note"
],
"query": "{\"noteclub__club__bus__wei\": [\"club\"]}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir la note d'un club de bus"
}
},
{
"model": "permission.permission",
"pk": 280,
"fields": {
"model": [
"note",
"transaction"
],
"query": "[\"OR\", {\"source__noteclub__club__bus__wei\": [\"club\"]}, {\"destination__noteclub__club__bus__wei\": [\"club\"]}]",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir les transactions d'un club de bus"
}
},
{
"model": "permission.permission",
"pk": 281,
"fields": {
"model": [
"note",
"transaction"
],
"query": "[\"AND\", [\"OR\", {\"source__noteclub__club__bus__wei\": [\"club\"]}, {\"destination__noteclub__club__bus__wei\": [\"club\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]]",
"type": "add",
"mask": 3,
"field": "",
"permanent": false,
"description": "Créer une transaction d'un club de bus tant que la source reste au dessus de -20 €"
}
},
{
"model": "permission.permission",
"pk": 282,
"fields": {
"model": [
"note",
"transaction"
],
"query": "[\"AND\", [\"OR\", {\"source__noteclub__club\": [\"club\"]}, {\"destination__noteclub__club\": [\"club\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]]",
"type": "add",
"mask": 3,
"field": "",
"permanent": false,
"description": "Créer une transaction d'un WEI tant que la source reste au dessus de -20 €"
}
},
{
"model": "permission.permission",
"pk": 283,
"fields": {
"model": [
"auth",
"user"
],
"query": "{\"memberships__club__name\": \"Kfet\", \"memberships__roles__name\": \"Adh\u00e9rent\u22c5e Kfet\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir n'importe quel⋅le utilisateur⋅rice qui est adhérent⋅e Kfet"
}
},
{
"model": "permission.permission",
"pk": 284,
"fields": {
"model": [
"member",
"club"
],
"query": "{\"bus\": [\"membership\", \"weimembership\", \"bus\"]}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir les informations de club de son bus"
}
},
{
"model": "permission.permission",
"pk": 285,
"fields": {
"model": [
"member",
"club"
],
"query": "{\"bus\": [\"membership\", \"weimembership\", \"bus\"]}",
"type": "change",
"mask": 3,
"field": "",
"permanent": false,
"description": "Modifier le club de son bus"
}
},
{
"model": "permission.permission",
"pk": 286,
"fields": {
"model": [
"member",
"membership"
],
"query": "{\"club__bus\": [\"membership\", \"weimembership\", \"bus\"]}",
"type": "add",
"mask": 3,
"field": "",
"permanent": false,
"description": "Ajouter un⋅e membre au club de son bus"
}
},
{
"model": "permission.permission",
"pk": 287,
"fields": {
"model": [
"member",
"membership"
],
"query": "{\"club__bus\": [\"membership\", \"weimembership\", \"bus\"]}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir les adhérents du club de son bus"
}
},
{
"model": "permission.permission",
"pk": 288,
"fields": {
"model": [
"member",
"membership"
],
"query": "{\"club__bus\": [\"membership\", \"weimembership\", \"bus\"]}",
"type": "change",
"mask": 3,
"field": "",
"permanent": false,
"description": "Modifier l'adhésion au club de son bus"
}
},
{
"model": "permission.permission",
"pk": 289,
"fields": {
"model": [
"note",
"note"
],
"query": "{\"noteclub__club__bus\": [\"membership\", \"weimembership\", \"bus\"]}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir la note du club de son bus"
}
},
{
"model": "permission.permission",
"pk": 290,
"fields": {
"model": [
"note",
"transaction"
],
"query": "[\"OR\", {\"source__noteclub__club__bus\": [\"membership\", \"weimembership\", \"bus\"]}, {\"destination__noteclub__club__bus\": [\"membership\", \"weimembership\", \"bus\"]}]",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir les transactions du club de son bus"
}
},
{
"model": "permission.permission",
"pk": 291,
"fields": {
"model": [
"wei",
"bus"
],
"query": "{\"pk\": [\"membership\", \"weimembership\", \"bus\", \"pk\"], \"wei__date_end__gte\": [\"today\"]}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir mon bus"
}
},
{ {
"model": "permission.role", "model": "permission.role",
"pk": 1, "pk": 1,
@ -4488,8 +4152,8 @@
"name": "Pr\u00e9sident\u22c5e de club", "name": "Pr\u00e9sident\u22c5e de club",
"permissions": [ "permissions": [
62, 62,
135, 142,
142 135
] ]
} }
}, },
@ -4718,20 +4382,7 @@
112, 112,
113, 113,
128, 128,
130, 130
271,
272,
273,
274,
275,
276,
277,
278,
279,
280,
281,
282,
283
] ]
} }
}, },
@ -4750,15 +4401,7 @@
119, 119,
120, 120,
121, 121,
122, 122
284,
285,
286,
287,
288,
289,
290,
291
] ]
} }
}, },
@ -4919,141 +4562,6 @@
] ]
} }
}, },
{
"model": "permission.role",
"pk": 23,
"fields": {
"for_club": 2,
"name": "Darbonne",
"permissions": [
30,
31,
32
]
}
},
{
"model": "permission.role",
"pk": 24,
"fields": {
"for_club": null,
"name": "Staffeur⋅euse (S&L,Respo Tech,...)",
"permissions": []
}
},
{
"model": "permission.role",
"pk": 25,
"fields": {
"for_club": null,
"name": "Référent⋅e Bus",
"permissions": [
22,
84,
115,
117,
118,
119,
120,
121,
122,
284,
285,
286,
287,
288,
289,
290,
291
]
}
},
{
"model": "permission.role",
"pk": 28,
"fields": {
"for_club": 10,
"name": "Trésorièr⸱e BDA",
"permissions": [
55,
56,
57,
58,
135,
143,
176,
177,
178,
243,
260,
261,
262,
263,
264,
265,
266,
267,
268,
269
]
}
},
{
"model": "permission.role",
"pk": 30,
"fields": {
"for_club": 10,
"name": "Respo sorties",
"permissions": [
49,
62,
141,
241,
242,
243
]
}
},
{
"model": "permission.role",
"pk": 31,
"fields": {
"for_club": 1,
"name": "Respo comm",
"permissions": [
135,
244
]
}
},
{
"model": "permission.role",
"pk": 32,
"fields": {
"for_club": 10,
"name": "Respo comm Art",
"permissions": [
135,
245
]
}
},
{
"model": "permission.role",
"pk": 33,
"fields": {
"for_club": 10,
"name": "Respo Jam",
"permissions": [
247,
250,
251,
252,
253,
254
]
}
},
{ {
"model": "wei.weirole", "model": "wei.weirole",
"pk": 12, "pk": 12,
@ -5088,15 +4596,5 @@
"model": "wei.weirole", "model": "wei.weirole",
"pk": 18, "pk": 18,
"fields": {} "fields": {}
},
{
"model": "wei.weirole",
"pk": 24,
"fields": {}
},
{
"model": "wei.weirole",
"pk": 25,
"fields": {}
} }
] ]

View File

@ -1,8 +1,10 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from oauth2_provider.oauth2_validators import OAuth2Validator from oauth2_provider.oauth2_validators import OAuth2Validator
from oauth2_provider.scopes import BaseScopes from oauth2_provider.scopes import BaseScopes
from member.models import Club from member.models import Club
from note.models import Alias
from note_kfet.middlewares import get_current_request from note_kfet.middlewares import get_current_request
from .backends import PermissionBackend from .backends import PermissionBackend
@ -17,25 +19,46 @@ class PermissionScopes(BaseScopes):
""" """
def get_all_scopes(self): def get_all_scopes(self):
return {f"{p.id}_{club.id}": f"{p.description} (club {club.name})" scopes = {f"{p.id}_{club.id}": f"{p.description} (club {club.name})"
for p in Permission.objects.all() for club in Club.objects.all()} 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): def get_available_scopes(self, application=None, request=None, *args, **kwargs):
if not application: if not application:
return [] return []
return [f"{p.id}_{p.membership.club.id}" scopes = [f"{p.id}_{p.membership.club.id}"
for t in Permission.PERMISSION_TYPES for t in Permission.PERMISSION_TYPES
for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0])] 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): def get_default_scopes(self, application=None, request=None, *args, **kwargs):
if not application: if not application:
return [] return []
return [f"{p.id}_{p.membership.club.id}" scopes = [f"{p.id}_{p.membership.club.id}"
for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')] for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')]
scopes.append('openid')
return scopes
class PermissionOAuth2Validator(OAuth2Validator): 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): def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs):
""" """
@ -54,6 +77,8 @@ class PermissionOAuth2Validator(OAuth2Validator):
if scope in scopes: if scope in scopes:
valid_scopes.add(scope) valid_scopes.add(scope)
request.scopes = valid_scopes if 'openid' in scopes:
valid_scopes.add('openid')
request.scopes = valid_scopes
return valid_scopes return valid_scopes

View File

@ -19,6 +19,7 @@ EXCLUDED = [
'oauth2_provider.accesstoken', 'oauth2_provider.accesstoken',
'oauth2_provider.grant', 'oauth2_provider.grant',
'oauth2_provider.refreshtoken', 'oauth2_provider.refreshtoken',
'oauth2_provider.idtoken',
'sessions.session', 'sessions.session',
] ]

View File

@ -10,7 +10,7 @@ from django.utils import timezone
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from activity.models import Activity from activity.models import Activity
from member.models import Club, Membership from member.models import Club, Membership
from note.models import NoteUser, NoteClub from note.models import NoteUser
from wei.models import WEIClub, Bus, WEIRegistration from wei.models import WEIClub, Bus, WEIRegistration
@ -122,13 +122,10 @@ class TestPermissionDenied(TestCase):
def test_validate_weiregistration(self): def test_validate_weiregistration(self):
wei = WEIClub.objects.create( wei = WEIClub.objects.create(
name="WEI Test",
membership_start=date.today(), membership_start=date.today(),
date_start=date.today() + timedelta(days=1), date_start=date.today() + timedelta(days=1),
date_end=date.today() + timedelta(days=1), date_end=date.today() + timedelta(days=1),
parent_club=Club.objects.get(name="Kfet"),
) )
NoteClub.objects.create(club=wei)
registration = WEIRegistration.objects.create(wei=wei, user=self.user, birth_date="2000-01-01") registration = WEIRegistration.objects.create(wei=wei, user=self.user, birth_date="2000-01-01")
response = self.client.get(reverse("wei:validate_registration", kwargs=dict(pk=registration.pk))) response = self.client.get(reverse("wei:validate_registration", kwargs=dict(pk=registration.pk)))
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)

View File

@ -171,7 +171,7 @@ class ScopesView(LoginRequiredMixin, TemplateView):
available_scopes = scopes.get_available_scopes(app) available_scopes = scopes.get_available_scopes(app)
context["scopes"][app] = OrderedDict() context["scopes"][app] = OrderedDict()
items = [(k, v) for (k, v) in all_scopes.items() if k in available_scopes] items = [(k, v) for (k, v) in all_scopes.items() if k in available_scopes]
items.sort(key=lambda x: (int(x[0].split("_")[1]), int(x[0].split("_")[0]))) # items.sort(key=lambda x: (int(x[0].split("_")[1]), int(x[0].split("_")[0])))
for k, v in items: for k, v in items:
context["scopes"][app][k] = v context["scopes"][app][k] = v

View File

@ -39,11 +39,7 @@ class WEIRegistrationForm(forms.ModelForm):
class Meta: class Meta:
model = WEIRegistration model = WEIRegistration
fields = [ exclude = ('wei', 'clothing_cut')
'user', 'soge_credit', 'birth_date', 'gender', 'clothing_size',
'health_issues', 'emergency_contact_name', 'emergency_contact_phone',
'first_year', 'information_json', 'caution_check'
]
widgets = { widgets = {
"user": Autocomplete( "user": Autocomplete(
User, User,
@ -53,14 +49,8 @@ class WEIRegistrationForm(forms.ModelForm):
'placeholder': 'Nom ...', 'placeholder': 'Nom ...',
}, },
), ),
"birth_date": DatePickerInput(options={ "birth_date": DatePickerInput(options={'minDate': '1900-01-01',
'minDate': '1900-01-01', 'maxDate': '2100-01-01'}),
'maxDate': '2100-01-01'
}),
"caution_check": forms.BooleanField(
label=_("I confirm that I have read the caution and that I am aware of the risks involved."),
required=False,
),
} }
@ -91,6 +81,11 @@ class WEIChooseBusForm(forms.Form):
class WEIMembershipForm(forms.ModelForm): class WEIMembershipForm(forms.ModelForm):
caution_check = forms.BooleanField(
required=False,
label=_("Caution check given"),
)
roles = forms.ModelMultipleChoiceField( roles = forms.ModelMultipleChoiceField(
queryset=WEIRole.objects, queryset=WEIRole.objects,
label=_("WEI Roles"), label=_("WEI Roles"),
@ -199,4 +194,3 @@ class BusTeamForm(forms.ModelForm):
), ),
"color": ColorWidget(), "color": ColorWidget(),
} }
# "color": ColorWidget(),

View File

@ -1,18 +0,0 @@
# Generated by Django 4.2.21 on 2025-05-25 12:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('wei', '0010_remove_weiregistration_specific_diet'),
]
operations = [
migrations.AlterField(
model_name='weiclub',
name='year',
field=models.PositiveIntegerField(default=2025, unique=True, verbose_name='year'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 4.2.21 on 2025-05-29 16:16
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('member', '0014_create_bda'),
('wei', '0011_alter_weiclub_year'),
]
operations = [
migrations.AddField(
model_name='bus',
name='club',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bus', to='member.club', verbose_name='club'),
),
]

View File

@ -72,15 +72,6 @@ class Bus(models.Model):
default=50, default=50,
) )
club = models.OneToOneField(
Club,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="bus",
verbose_name=_("club"),
)
description = models.TextField( description = models.TextField(
blank=True, blank=True,
default="", default="",

View File

@ -98,7 +98,7 @@ class WEIRegistrationTable(tables.Table):
if not hasperm: if not hasperm:
return format_html("<span class='no-perm'></span>") return format_html("<span class='no-perm'></span>")
url = reverse_lazy('wei:wei_update_registration', args=(record.pk,)) + '?validate=true' url = reverse_lazy('wei:validate_registration', args=(record.pk,))
text = _('Validate') text = _('Validate')
if record.fee > record.user.note.balance and not record.soge_credit: if record.fee > record.user.note.balance and not record.soge_credit:
btn_class = 'btn-secondary' btn_class = 'btn-secondary'

View File

@ -40,12 +40,19 @@ SPDX-License-Identifier: GPL-3.0-or-later
<dt class="col-xl-6">{% trans 'membership fee'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'membership fee'|capfirst %}</dt>
<dd class="col-xl-6">{{ club.membership_fee_paid|pretty_money }}</dd> <dd class="col-xl-6">{{ club.membership_fee_paid|pretty_money }}</dd>
{% else %} {% else %}
{% with bde_kfet_fee=club.parent_club.membership_fee_paid|add:club.parent_club.parent_club.membership_fee_paid %}
<dt class="col-xl-6">{% trans 'WEI fee (paid students)'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'WEI fee (paid students)'|capfirst %}</dt>
<dd class="col-xl-6">{{ club.membership_fee_paid|pretty_money }} <dd class="col-xl-6">{{ club.membership_fee_paid|add:bde_kfet_fee|pretty_money }}
<i class="fa fa-question-circle"
title="{% trans "The BDE membership is included in the WEI registration." %}"></i></dd>
{% endwith %}
{% with bde_kfet_fee=club.parent_club.membership_fee_unpaid|add:club.parent_club.parent_club.membership_fee_unpaid %}
<dt class="col-xl-6">{% trans 'WEI fee (unpaid students)'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'WEI fee (unpaid students)'|capfirst %}</dt>
<dd class="col-xl-6">{{ club.membership_fee_unpaid|pretty_money }} <dd class="col-xl-6">{{ club.membership_fee_unpaid|add:bde_kfet_fee|pretty_money }}
<i class="fa fa-question-circle"
title="{% trans "The BDE membership is included in the WEI registration." %}"></i></dd>
{% endwith %}
{% endif %} {% endif %}
{% endif %} {% endif %}

View File

@ -16,10 +16,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div> </div>
<div class="card-footer text-center"> <div class="card-footer text-center">
{% if object.club %}
<a class="btn btn-primary btn-sm my-1" href="{% url 'member:club_detail' pk=object.club.pk %}"
data-turbolinks="false">{% trans "View club" %}</a>
{% endif %}
<a class="btn btn-primary btn-sm my-1" href="{% url 'wei:update_bus' pk=object.pk %}" <a class="btn btn-primary btn-sm my-1" href="{% url 'wei:update_bus' pk=object.pk %}"
data-turbolinks="false">{% trans "Edit" %}</a> data-turbolinks="false">{% trans "Edit" %}</a>
<a class="btn btn-primary btn-sm my-1" href="{% url 'wei:add_team' pk=object.pk %}" <a class="btn btn-primary btn-sm my-1" href="{% url 'wei:add_team' pk=object.pk %}"

View File

@ -18,8 +18,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card-footer text-center"> <div class="card-footer text-center">
<a class="btn btn-primary btn-sm my-1" href="{% url 'wei:update_bus' pk=bus.pk %}" <a class="btn btn-primary btn-sm my-1" href="{% url 'wei:update_bus' pk=bus.pk %}"
data-turbolinks="false">{% trans "Edit" %}</a> data-turbolinks="false">{% trans "Edit" %}</a>
<a class="btn btn-primary btn-sm my-1" href="{% url 'wei:manage_bus' pk=bus.pk %}"
data-turbolinks="false">{% trans "View" %}</a>
<a class="btn btn-primary btn-sm my-1" href="{% url 'wei:add_team' pk=bus.pk %}" <a class="btn btn-primary btn-sm my-1" href="{% url 'wei:add_team' pk=bus.pk %}"
data-turbolinks="false">{% trans "Add team" %}</a> data-turbolinks="false">{% trans "Add team" %}</a>
</div> </div>

View File

@ -13,17 +13,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
<div class="card-body"> <div class="card-body">
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
{{ form.media }}
{{ form|crispy }} {{ form|crispy }}
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button> <button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
</form> </form>
</div> </div>
</div> </div>
<script>
document.addEventListener("DOMContentLoaded", function () {
if (window.jscolor && jscolor.install) {
jscolor.install();
}
});
</script>
{% endblock %} {% endblock %}

View File

@ -510,7 +510,7 @@ class TestWEIRegistration(TestCase):
) )
qs = WEIRegistration.objects.filter(user_id=self.user.id, soge_credit=False, clothing_size="M") qs = WEIRegistration.objects.filter(user_id=self.user.id, soge_credit=False, clothing_size="M")
self.assertTrue(qs.exists()) self.assertTrue(qs.exists())
self.assertRedirects(response, reverse("wei:wei_detail", kwargs=dict(pk=qs.get().wei.pk)), 302, 200) self.assertRedirects(response, reverse("wei:validate_registration", kwargs=dict(pk=qs.get().pk)), 302, 200)
# Check the page when the registration is already validated # Check the page when the registration is already validated
membership = WEIMembership( membership = WEIMembership(
@ -564,7 +564,7 @@ class TestWEIRegistration(TestCase):
) )
qs = WEIRegistration.objects.filter(user_id=self.user.id, clothing_size="L") qs = WEIRegistration.objects.filter(user_id=self.user.id, clothing_size="L")
self.assertTrue(qs.exists()) self.assertTrue(qs.exists())
self.assertRedirects(response, reverse("wei:wei_detail", kwargs=dict(pk=qs.get().wei.pk)), 302, 200) self.assertRedirects(response, reverse("wei:validate_registration", kwargs=dict(pk=qs.get().pk)), 302, 200)
# Test invalid form # Test invalid form
response = self.client.post( response = self.client.post(
@ -632,7 +632,6 @@ class TestWEIRegistration(TestCase):
last_name="admin", last_name="admin",
first_name="admin", first_name="admin",
bank="Société générale", bank="Société générale",
caution_check=True,
)) ))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertFalse(response.context["form"].is_valid()) self.assertFalse(response.context["form"].is_valid())
@ -647,10 +646,8 @@ class TestWEIRegistration(TestCase):
last_name="admin", last_name="admin",
first_name="admin", first_name="admin",
bank="Société générale", bank="Société générale",
caution_check=True,
)) ))
self.assertRedirects(response, reverse("wei:wei_registrations", kwargs=dict(pk=self.registration.wei.pk)), 302, 200) self.assertRedirects(response, reverse("wei:wei_registrations", kwargs=dict(pk=self.registration.wei.pk)), 302, 200)
# Check if the membership is successfully created # Check if the membership is successfully created
membership = WEIMembership.objects.filter(user_id=self.user.id, club=self.wei) membership = WEIMembership.objects.filter(user_id=self.user.id, club=self.wei)
self.assertTrue(membership.exists()) self.assertTrue(membership.exists())

View File

@ -4,18 +4,16 @@
import os import os
import shutil import shutil
import subprocess import subprocess
from datetime import date from datetime import date, timedelta
from tempfile import mkdtemp from tempfile import mkdtemp
from django.conf import settings from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import transaction from django.db import transaction
from django.db.models import Q, Count from django.db.models import Q, Count
from django.db.models.functions.text import Lower from django.db.models.functions.text import Lower
from django import forms
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from django.template.loader import render_to_string from django.template.loader import render_to_string
@ -443,10 +441,6 @@ class BusTeamCreateView(ProtectQuerysetMixin, ProtectedCreateView):
self.object.refresh_from_db() self.object.refresh_from_db()
return reverse_lazy("wei:manage_bus_team", kwargs={"pk": self.object.pk}) return reverse_lazy("wei:manage_bus_team", kwargs={"pk": self.object.pk})
def get_template_names(self):
names = super().get_template_names()
return names
class BusTeamUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): class BusTeamUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
""" """
@ -479,10 +473,6 @@ class BusTeamUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
self.object.refresh_from_db() self.object.refresh_from_db()
return reverse_lazy("wei:manage_bus_team", kwargs={"pk": self.object.pk}) return reverse_lazy("wei:manage_bus_team", kwargs={"pk": self.object.pk})
def get_template_names(self):
names = super().get_template_names()
return names
class BusTeamManageView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): class BusTeamManageView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
""" """
@ -556,15 +546,9 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView):
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = super().get_form(form_class) form = super().get_form(form_class)
form.fields["user"].initial = self.request.user form.fields["user"].initial = self.request.user
# Cacher les champs pendant l'inscription initiale
if "first_year" in form.fields:
del form.fields["first_year"] del form.fields["first_year"]
if "caution_check" in form.fields:
del form.fields["caution_check"] del form.fields["caution_check"]
if "information_json" in form.fields:
del form.fields["information_json"] del form.fields["information_json"]
return form return form
@transaction.atomic @transaction.atomic
@ -660,12 +644,8 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView):
form.fields["soge_credit"].disabled = True form.fields["soge_credit"].disabled = True
form.fields["soge_credit"].help_text = _("You already opened an account in the Société générale.") form.fields["soge_credit"].help_text = _("You already opened an account in the Société générale.")
# Cacher les champs pendant l'inscription initiale
if "first_year" in form.fields:
del form.fields["first_year"]
if "caution_check" in form.fields:
del form.fields["caution_check"] del form.fields["caution_check"]
if "information_json" in form.fields: del form.fields["first_year"]
del form.fields["information_json"] del form.fields["information_json"]
return form return form
@ -722,15 +702,11 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
# We can't update a registration once the WEI is started and before the membership start date # We can't update a registration once the WEI is started and before the membership start date
if today >= wei.date_start or today < wei.membership_start: if today >= wei.date_start or today < wei.membership_start:
return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,))) return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
# Store the validate parameter in the view's state
self.should_validate = request.GET.get('validate', False)
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["club"] = self.object.wei context["club"] = self.object.wei
# Pass the validate parameter to the template
context["should_validate"] = self.should_validate
if self.object.is_validated: if self.object.is_validated:
membership_form = self.get_membership_form(instance=self.object.membership, membership_form = self.get_membership_form(instance=self.object.membership,
@ -764,9 +740,6 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
# The auto-json-format may cause issues with the default field remove # The auto-json-format may cause issues with the default field remove
if not PermissionBackend.check_perm(self.request, 'wei.change_weiregistration_information_json', self.object): if not PermissionBackend.check_perm(self.request, 'wei.change_weiregistration_information_json', self.object):
del form.fields["information_json"] del form.fields["information_json"]
# Masquer le champ caution_check pour tout le monde dans le formulaire de modification
if "caution_check" in form.fields:
del form.fields["caution_check"]
return form return form
def get_membership_form(self, data=None, instance=None): def get_membership_form(self, data=None, instance=None):
@ -786,30 +759,10 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
def form_valid(self, form): def form_valid(self, form):
# If the membership is already validated, then we update the bus and the team (and the roles) # If the membership is already validated, then we update the bus and the team (and the roles)
if form.instance.is_validated: if form.instance.is_validated:
try: membership_form = self.get_membership_form(self.request.POST, form.instance.membership)
membership = form.instance.membership
if membership is None:
raise ValueError(_("No membership found for this registration"))
membership_form = self.get_membership_form(self.request.POST, instance=membership)
if not membership_form.is_valid(): if not membership_form.is_valid():
return self.form_invalid(form) return self.form_invalid(form)
# Vérifier que l'utilisateur a la permission de modifier le membership
# On vérifie d'abord si l'utilisateur a la permission générale de modification
if not self.request.user.has_perm("wei.change_weimembership"):
raise PermissionDenied(_("You don't have the permission to update memberships"))
# On vérifie ensuite les permissions spécifiques pour chaque champ modifié
for field_name in membership_form.changed_data:
perm = f"wei.change_weimembership_{field_name}"
if not self.request.user.has_perm(perm):
raise PermissionDenied(_("You don't have the permission to update the field %(field)s") % {'field': field_name})
membership_form.save() membership_form.save()
except (WEIMembership.DoesNotExist, ValueError, PermissionDenied) as e:
form.add_error(None, str(e))
return self.form_invalid(form)
# If it is not validated and if this is an old member, then we update the choices # If it is not validated and if this is an old member, then we update the choices
elif not form.instance.first_year and PermissionBackend.check_perm( elif not form.instance.first_year and PermissionBackend.check_perm(
self.request, "wei.change_weiregistration_information_json", self.object): self.request, "wei.change_weiregistration_information_json", self.object):
@ -834,8 +787,14 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
survey = CurrentSurvey(self.object) survey = CurrentSurvey(self.object)
if not survey.is_complete(): if not survey.is_complete():
return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk}) return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk})
# On redirige vers la validation uniquement si c'est explicitement demandé (et stocké dans la vue) if PermissionBackend.check_perm(self.request, "wei.add_weimembership", WEIMembership(
if self.should_validate and self.request.user.has_perm("wei.add_weimembership"): club=self.object.wei,
user=self.object.user,
date_start=date.today(),
date_end=date.today(),
fee=0,
registration=self.object,
)):
return reverse_lazy("wei:validate_registration", kwargs={"pk": self.object.pk}) return reverse_lazy("wei:validate_registration", kwargs={"pk": self.object.pk})
return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.wei.pk}) return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.wei.pk})
@ -877,23 +836,18 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
extra_context = {"title": _("Validate WEI registration")} extra_context = {"title": _("Validate WEI registration")}
def get_sample_object(self): def get_sample_object(self):
"""
Return a sample object for permission checking
"""
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"]) registration = WEIRegistration.objects.get(pk=self.kwargs["pk"])
return WEIMembership( return WEIMembership(
user=registration.user,
club=registration.wei, club=registration.wei,
date_start=registration.wei.date_start, user=registration.user,
fee=registration.wei.membership_fee_paid if registration.user.profile.paid else registration.wei.membership_fee_unpaid, date_start=date.today(),
# Add any fields needed for proper permission checking date_end=date.today() + timedelta(days=1),
fee=0,
registration=registration, registration=registration,
) )
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
registration = WEIRegistration.objects.get(pk=self.kwargs["pk"]) wei = WEIRegistration.objects.get(pk=self.kwargs["pk"]).wei
wei = registration.wei
today = date.today() today = date.today()
# We can't validate anyone once the WEI is started and before the membership start date # We can't validate anyone once the WEI is started and before the membership start date
if today >= wei.date_start or today < wei.membership_start: if today >= wei.date_start or today < wei.membership_start:
@ -946,14 +900,8 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
form.fields["last_name"].initial = registration.user.last_name form.fields["last_name"].initial = registration.user.last_name
form.fields["first_name"].initial = registration.user.first_name form.fields["first_name"].initial = registration.user.first_name
# Ajouter le champ caution_check uniquement pour les non-première année et le rendre obligatoire if "caution_check" in form.fields:
if not registration.first_year: form.fields["caution_check"].initial = registration.caution_check
form.fields["caution_check"] = forms.BooleanField(
required=True,
initial=registration.caution_check,
label=_("Caution check given"),
help_text=_("Please make sure the check is given before validating the registration")
)
if registration.soge_credit: if registration.soge_credit:
form.fields["credit_type"].disabled = True form.fields["credit_type"].disabled = True
@ -1341,22 +1289,8 @@ class WEIAttributeBus1ANextView(LoginRequiredMixin, RedirectView):
if not wei.exists(): if not wei.exists():
raise Http404 raise Http404
wei = wei.get() wei = wei.get()
qs = WEIRegistration.objects.filter(wei=wei, membership__isnull=False, membership__bus__isnull=True)
# On cherche d'abord les 1A qui ont une inscription validée (membership) mais pas de bus qs = qs.filter(information_json__contains='selected_bus_pk') # not perfect, but works...
qs = WEIRegistration.objects.filter( if qs.exists():
wei=wei,
first_year=True,
membership__isnull=False,
membership__bus__isnull=True
)
# Parmi eux, on prend ceux qui ont répondu au questionnaire (ont un bus préféré)
qs = qs.filter(information_json__contains='selected_bus_pk')
if not qs.exists():
# Si on ne trouve personne, on affiche un message et on retourne à la liste
messages.info(self.request, _("No first year student without a bus found. Either all of them have a bus, or none has filled the survey yet."))
return reverse_lazy('wei:wei_1A_list', args=(wei.pk,))
# On redirige vers la page d'attribution pour le premier étudiant trouvé
return reverse_lazy('wei:wei_bus_1A', args=(qs.first().pk, )) return reverse_lazy('wei:wei_bus_1A', args=(qs.first().pk, ))
return reverse_lazy('wei:wei_1A_list', args=(wei.pk, ))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -63,16 +63,8 @@ class ColorWidget(Widget):
def format_value(self, value): def format_value(self, value):
if value is None: if value is None:
value = 0xFFFFFF value = 0xFFFFFF
if isinstance(value, str):
return value # Assume it's already a hex string like "#FFAA33"
try:
return "#{:06X}".format(value) return "#{:06X}".format(value)
except Exception:
return "#FFFFFF"
def value_from_datadict(self, data, files, name): def value_from_datadict(self, data, files, name):
val = super().value_from_datadict(data, files, name) val = super().value_from_datadict(data, files, name)
if val:
return int(val[1:], 16) return int(val[1:], 16)
return None

View File

@ -270,7 +270,7 @@ OAUTH2_PROVIDER = {
'PKCE_REQUIRED': False, # PKCE (fix a breaking change of django-oauth-toolkit 2.0.0) 'PKCE_REQUIRED': False, # PKCE (fix a breaking change of django-oauth-toolkit 2.0.0)
'OIDC_ENABLED': True, 'OIDC_ENABLED': True,
'OIDC_RSA_PRIVATE_KEY': '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" }, 'SCOPES': { 'openid': "OpenID Connect scope" },
} }

View File

@ -1,5 +0,0 @@
<input type="text"
name="{{ widget.name }}"
value="{{ widget.value }}"
class="jscolor"
{% include "django/forms/widgets/attrs.html" %}>