1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-21 18:08:21 +02:00

Compare commits

..

20 Commits

Author SHA1 Message Date
763535bea4 Merge branch 'oidc' into 'main'
OIDC 0 Quark 1

See merge request bde/nk20!322
2025-06-17 16:02:40 +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
16b55e23af Merge branch 'thomasl-main-patch-84944' into 'main'
Update doc about scripts

See merge request bde/nk20!321
2025-06-14 20:24:49 +02:00
97621e8704 Update doc about scripts 2025-06-14 20:07:29 +02:00
cf4c23d1ac Merge branch 'oidc' into 'main'
oidc

See merge request bde/nk20!320
2025-06-14 18:36:24 +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
ff812a028c Merge branch 'darbonne' into 'main'
Darbonne

See merge request bde/nk20!318
2025-05-26 16:47:03 +02:00
5a8acbde00 Trez TaT en moins 2025-05-25 00:07:07 +02:00
f60dc8cfa0 Pré-injection du BDA 2025-05-25 00:05:13 +02:00
067dd6f9d1 WEI-Roles 2025-05-24 22:41:53 +02:00
7b1e32e514 Réécriture des rôles pertinents 2025-05-24 22:29:11 +02:00
e88dbfd597 Merge branch 'darbonne' into 'main'
Faute de frappe

See merge request bde/nk20!317
2025-05-23 23:57:18 +02:00
3d34270959 Faute de frappe 2025-05-23 23:38:06 +02:00
3bb99671ec Merge branch 'ehouarn-main-patch-70724' into 'main'
Update views.py

See merge request bde/nk20!316
2025-05-19 18:03:00 +02:00
0d69383dfd Update views.py 2025-05-19 17:45:01 +02:00
7b9ff119e8 Merge branch 'food_bugs' into 'main'
Corrections de quelques bugs (par Quark)

See merge request bde/nk20!315
2025-05-10 19:46:44 +02:00
108a56745c Corrections de quelques bugs (par Quark) 2025-05-10 19:24:05 +02:00
9643d7652b Merge branch 'delete_activity' into 'main'
migrations

See merge request bde/nk20!314
2025-05-09 20:15:46 +02:00
7 changed files with 274 additions and 35 deletions

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,7 +169,8 @@ 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):
return BasicFood( # We choose a club which may work or BDE else
food = BasicFood(
name="", name="",
owner_id=1, owner_id=1,
expiry_date=timezone.now(), expiry_date=timezone.now(),
@ -177,6 +179,14 @@ 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:
@ -227,13 +237,22 @@ 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):
return TransformedFood( # We choose a club which may work or BDE else
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)
@ -245,10 +264,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 = 10 MAX_FORMS = 100
class ManageIngredientsView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): class ManageIngredientsView(LoginRequiredMixin, UpdateView):
""" """
A view to manage ingredient for a transformed food A view to manage ingredient for a transformed food
""" """
@ -279,6 +298,14 @@ class ManageIngredientsView(ProtectQuerysetMixin, 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

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

@ -4152,8 +4152,8 @@
"name": "Pr\u00e9sident\u22c5e de club", "name": "Pr\u00e9sident\u22c5e de club",
"permissions": [ "permissions": [
62, 62,
142, 135,
135 142
] ]
} }
}, },
@ -4562,6 +4562,133 @@
] ]
} }
}, },
{
"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
]
}
},
{
"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,
@ -4596,5 +4723,15 @@
"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

@ -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

@ -136,7 +136,7 @@ de diffusion utiles.
Faîtes attention, donc où la sortie est stockée. 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``, * ``--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 ``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 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. 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 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. malheureusement pas aussi simple que de simplement supposer que ces listes sont exhaustives.