1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-04-26 00:22:38 +00:00

Compare commits

...

9 Commits

Author SHA1 Message Date
alexismdr
5b717c9892 Merge branch 'fixed_crop_pp_dim' into 'main'
bootstrap: fix minor issues with profile picture cropping

See merge request bde/nk20!301
2025-03-25 22:52:58 +01:00
quark
7ba5c76a89 Merge branch 'guests_schools' into 'main'
add school field to guest

See merge request bde/nk20!302
2025-03-25 18:59:46 +01:00
quark
702ddb5679 add school field to guest 2025-03-25 17:39:31 +01:00
bleizi
60355196ce Merge branch 'openid-connect' into 'main'
Openid connect

See merge request bde/nk20!293
2025-03-20 18:42:51 +01:00
bleizi
9bffb32a5e
documentation 2025-03-20 17:36:38 +01:00
quark
5ef019c5c2 Merge branch 'notekfet_wrapped' into 'main'
Rewrite script and add test

See merge request bde/nk20!300
2025-03-18 16:11:33 +01:00
quark
8da62e62fb Rewrite script and add test 2025-03-18 15:53:02 +01:00
Nicolas Margulies
6c63c6417c Typesetting 2025-03-08 16:08:40 +01:00
Nicolas Margulies
4563b2b640 Added configusation for OpenID support, along with installation information 2025-03-08 16:04:25 +01:00
17 changed files with 263 additions and 106 deletions

View File

@ -58,7 +58,13 @@ Bien que cela permette de créer une instance sur toutes les distributions,
(env)$ ./manage.py createsuperuser # Création d'un⋅e utilisateur⋅rice initial
```
6. Enjoy :
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`).
7. Enjoy :
```bash
(env)$ ./manage.py runserver 0.0.0.0:8000
@ -228,7 +234,13 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
(env)$ ./manage.py check # pas de bêtise qui traine
(env)$ ./manage.py migrate
7. *Enjoy \o/*
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`).
8. *Enjoy \o/*
### Installation avec Docker

View File

@ -35,7 +35,7 @@ class GuestAdmin(admin.ModelAdmin):
"""
Admin customisation for Guest
"""
list_display = ('last_name', 'first_name', 'activity', 'inviter')
list_display = ('last_name', 'first_name', 'school', 'activity', 'inviter')
form = GuestForm

View File

@ -51,9 +51,9 @@ class GuestViewSet(ReadProtectedModelViewSet):
queryset = Guest.objects.order_by('id')
serializer_class = GuestSerializer
filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter]
filterset_fields = ['activity', 'activity__name', 'last_name', 'first_name', 'inviter', 'inviter__alias__name',
filterset_fields = ['activity', 'activity__name', 'last_name', 'first_name', 'school', 'inviter', 'inviter__alias__name',
'inviter__alias__normalized_name', ]
search_fields = ['$activity__name', '$last_name', '$first_name', '$inviter__user__email', '$inviter__alias__name',
search_fields = ['$activity__name', '$last_name', '$first_name', '$school', '$inviter__user__email', '$inviter__alias__name',
'$inviter__alias__normalized_name', ]

View File

@ -107,7 +107,7 @@ class GuestForm(forms.ModelForm):
class Meta:
model = Guest
fields = ('last_name', 'first_name', 'inviter', )
fields = ('last_name', 'first_name', 'school', 'inviter', )
widgets = {
"inviter": Autocomplete(
NoteUser,

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.20 on 2025-03-25 09:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("activity", "0005_alter_opener_options_alter_opener_opener"),
]
operations = [
migrations.AddField(
model_name="guest",
name="school",
field=models.CharField(default="", max_length=255, verbose_name="school"),
preserve_default=False,
),
]

View File

@ -247,6 +247,11 @@ class Guest(models.Model):
verbose_name=_("first name"),
)
school = models.CharField(
max_length=255,
verbose_name=_("school"),
)
inviter = models.ForeignKey(
NoteUser,
on_delete=models.PROTECT,

View File

@ -51,7 +51,7 @@ class GuestTable(tables.Table):
}
model = Guest
template_name = 'django_tables2/bootstrap4.html'
fields = ("last_name", "first_name", "inviter", )
fields = ("last_name", "first_name", "inviter", "school")
def render_entry(self, record):
if record.has_entry:

View File

@ -50,6 +50,7 @@ class TestActivities(TestCase):
inviter=self.user.note,
last_name="GUEST",
first_name="Guest",
school="School",
)
def test_activity_list(self):
@ -156,6 +157,7 @@ class TestActivities(TestCase):
inviter=self.user.note.id,
last_name="GUEST2",
first_name="Guest",
school="School",
))
self.assertEqual(response.status_code, 200)
@ -167,6 +169,7 @@ class TestActivities(TestCase):
inviter=self.user.note.id,
last_name="GUEST2",
first_name="Guest",
school="School",
))
self.assertRedirects(response, reverse("activity:activity_detail", args=(self.activity.pk,)), 302, 200)
@ -200,6 +203,7 @@ class TestActivityAPI(TestAPI):
inviter=self.user.note,
last_name="GUEST",
first_name="Guest",
school="School",
)
self.entry = Entry.objects.create(

View File

@ -168,6 +168,7 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
activity=activity,
first_name="",
last_name="",
school="",
inviter=self.request.user.note,
)

View File

@ -38,7 +38,7 @@ class Command(BaseCommand):
required=False,
help="""User will have their(s) wrapped generated,
all = all users
adh = all users who have a valid memberships to BDE during the BDE considered
adh = all users who have a valid cd memberships to BDE during the BDE considered
supersuser = all superusers
custom user1,user2,... = a list of username,
custom_id id1,id2,... = a list of user id""",
@ -70,15 +70,7 @@ class Command(BaseCommand):
dest='create',
)
def handle(self, *args, **options):
# useful string for output
red = '\033[31;1m'
yellow = '\033[33;1m'
green = '\033[32;1m'
abort = red + 'ABORT'
warning = yellow + 'WARNING'
success = green + 'SUCCESS'
def handle(self, *args, **options): # NOQA
# Traitement des paramètres
verb = options['verbosity']
bde = []
@ -89,11 +81,11 @@ class Command(BaseCommand):
if options['bde_id']:
if bde:
if verb >= 1:
print(warning)
print(yellow + 'You already defined bde with their name !')
self.stdout.write(self.style.WARNING(
"WARNING\nYou already defined bde with their name !"))
if verb >= 0:
print(abort)
return
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
bde_id = options['bde_id'].split(',')
bde = [Bde.objects.get(pk=i) for i in bde_id]
@ -113,11 +105,11 @@ class Command(BaseCommand):
user = ['custom_id', [User.objects.get(pk=u) for u in user_id]]
else:
if verb >= 1:
print(warning)
print(yellow + 'You user option is not recognized')
self.sdtout.write(self.style.WARNING(
"WARNING\nYou user option is not recognized"))
if verb >= 0:
print(abort)
return
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
club = []
if options['club']:
@ -133,11 +125,11 @@ class Command(BaseCommand):
club = ['custom_id', [Club.objects.get(pk=c) for c in club_id]]
else:
if verb >= 1:
print(warning)
print(yellow + 'You club option is not recognized')
self.stdout.write(self.style.WARNING(
"WARNING\nYou club option is not recognized"))
if verb >= 0:
print(abort)
return
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
change = options['change']
create = options['create']
@ -145,72 +137,75 @@ class Command(BaseCommand):
# check if parameters are sufficient for generate wrapped with the desired option
if not bde:
if verb >= 1:
print(warning)
print(yellow + 'You have not selectionned a BDE !')
self.stdout.write(self.style.WARNING(
"WARNING\nYou have not selectionned a BDE !"))
if verb >= 0:
print(abort)
return
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
if not (user or club):
if verb >= 1:
print(warning)
print(yellow + 'No club or user selected !')
self.stdout.write(self.style.WARNING(
"WARNING\nNo club or user selected !"))
if verb >= 0:
print(abort)
return
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
if verb >= 3:
print('\033[1mOptions:\033[m')
self.stdout.write("Options:")
bde_str = ''
for b in bde:
bde_str += str(b)
print('BDE: ' + bde_str)
bde_str += str(b) + '\n'
self.stdout.write("BDE: " + bde_str)
if user:
print('User: ' + user[0])
self.stdout.write('User: ' + user[0])
if club:
print('Club: ' + club[0])
print('change: ' + str(change))
print('create: ' + str(create))
print('')
self.stdout.write('Club: ' + club[0])
self.stdout.write('change: ' + str(change))
self.stdout.write('create: ' + str(create) + '\n')
if not (change or create):
if verb >= 1:
print(warning)
print(yellow + 'change and create is set to false, none wrapped will be created')
self.stdout.write(self.style.WARNING(
"WARNING\nchange and create is set to false, none wrapped will be created"))
if verb >= 0:
print(abort)
return
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
if verb >= 1 and change:
print(warning)
print(yellow + 'change is set to true, some wrapped may be replaced !')
self.stdout.write(self.style.WARNING(
"WARNING\nchange is set to true, some wrapped may be replaced !"))
if verb >= 1 and not create:
print(warning)
print(yellow + 'create is set to false, wrapped will not be created !')
self.stdout.write(self.style.WARNING(
"WARNING\ncreate is set to false, wrapped will not be created !"))
if verb >= 3 or change or not create:
a = str(input('\033[mContinue ? (y/n) ')).lower()
if a in ['n', 'no', 'non', '0']:
if verb >= 0:
print(abort)
return
self.stdout.write(self.style.ERROR("ABORT"))
exit(1)
note = self.convert_to_note(change, create, bde=bde, user=user, club=club, verb=verb)
if verb >= 1:
print("\033[32mUser and/or Club given has successfully convert in their note\033[m")
self.stdout.write(self.style.SUCCESS(
"User and/or Club given has successfully convert in their note"))
global_data = self.global_data(bde, verb=verb)
if verb >= 1:
print("\033[32mGlobal data has been successfully generated\033[m")
self.stdout.write(self.style.SUCCESS(
"Global data has been successfully generated"))
unique_data = self.unique_data(bde, note, global_data=global_data, verb=verb)
if verb >= 1:
print("\033[32mUnique data has been successfully generated\033[m")
self.stdout.write(self.style.SUCCESS(
"Unique data has been successfully generated"))
self.make_wrapped(unique_data, note, bde, change, create, verb=verb)
if verb >= 1:
print(green + "The wrapped has been generated !")
self.stdout.write(self.style.SUCCESS(
"The wrapped has been generated !"))
if verb >= 0:
print(success)
self.stdout.write(self.style.SUCCESS("SUCCESS"))
exit(0)
return
def convert_to_note(self, change, create, bde=None, user=None, club=None, verb=1):
def convert_to_note(self, change, create, bde=None, user=None, club=None, verb=1): # NOQA
notes = []
for b in bde:
note_for_bde = Note.objects.filter(pk__lte=-1)
@ -253,17 +248,17 @@ class Command(BaseCommand):
note_for_bde = self.filter_note(b, note_for_bde, change, create, verb=verb)
notes.append(note_for_bde)
if verb >= 2:
print("\033[m{nb} note selectionned for bde {bde}".format(nb=len(note_for_bde), bde=b.name))
self.stdout.write(f"{len(note_for_bde)} note selectionned for bde {b.name}")
return notes
def global_data(self, bde, verb=1):
def global_data(self, bde, verb=1): # NOQA
data = {}
for b in bde:
if b.name == 'Rave Part[list]':
if verb >= 2:
print("Begin to make global data")
self.stdout.write("Begin to make global data")
if verb >= 3:
print('nb_transaction')
self.stdout.write("nb_transaction")
# nb total de transactions
data['nb_transaction'] = Transaction.objects.filter(
created_at__gte=b.date_start,
@ -271,7 +266,7 @@ class Command(BaseCommand):
valid=True).count()
if verb >= 3:
print('nb_vieux_con')
self.stdout.write("nb_vieux_con")
# nb total de vielleux con·ne·s derrière le bar
button_id = [2884, 2585]
transactions = Transaction.objects.filter(
@ -286,7 +281,7 @@ class Command(BaseCommand):
data['nb_vieux_con'] = q
if verb >= 3:
print('nb_soiree')
self.stdout.write("nb_soiree")
# nb total de soirée
a_type_id = [1, 2, 4, 5, 7, 10]
data['nb_soiree'] = Activity.objects.filter(
@ -296,7 +291,7 @@ class Command(BaseCommand):
activity_type__pk__in=a_type_id).count()
if verb >= 3:
print('pots, nb_entree_pot')
self.stdout.write('pots, nb_entree_pot')
# nb d'entrée totale aux pots
pot_id = [1, 4, 10]
pots = Activity.objects.filter(
@ -310,7 +305,7 @@ class Command(BaseCommand):
data['nb_entree_pot'] += Entry.objects.filter(activity=pot).count()
if verb >= 3:
print('top3_buttons')
self.stdout.write('top3_buttons')
# top 3 des boutons les plus cliqués
transactions = Transaction.objects.filter(
created_at__gte=b.date_start,
@ -329,7 +324,7 @@ class Command(BaseCommand):
data['top3_buttons'] = list(sorted(d.items(), key=lambda item: item[1], reverse=True))[:3]
if verb >= 3:
print('class_conso_all')
self.stdout.write('class_conso_all')
# le classement des plus gros consommateurs (BDE + club)
transactions = Transaction.objects.filter(
created_at__gte=b.date_start,
@ -348,7 +343,7 @@ class Command(BaseCommand):
data['class_conso_all'] = dict(sorted(d.items(), key=lambda item: item[1], reverse=True))
if verb >= 3:
print('class_conso_bde')
self.stdout.write('class_conso_bde')
# le classement des plus gros consommateurs BDE
transactions = Transaction.objects.filter(
created_at__gte=b.date_start,
@ -368,11 +363,10 @@ class Command(BaseCommand):
else:
# make your wrapped or reuse previous wrapped
raise NotImplementedError("The BDE: {bde_name} has not personalized wrapped, make it !"
.format(bde_name=b.name))
raise NotImplementedError(f"The BDE: {b.name} has not personalized wrapped, make it !")
return data
def unique_data(self, bde, note, global_data=None, verb=1):
def unique_data(self, bde, note, global_data=None, verb=1): # NOQA
data = []
for i in range(len(bde)):
data_bde = []
@ -380,8 +374,7 @@ class Command(BaseCommand):
if verb >= 3:
total = len(note[i])
current = 0
print('Make {nb} data for wrapped sponsored by {bde}'
.format(nb=total, bde=bde[i].name))
self.stdout.write(f"Make {total} data for wrapped sponsored by {bde[i].name}")
for n in note[i]:
d = {}
if 'user' in n.__dir__():
@ -542,12 +535,11 @@ class Command(BaseCommand):
data_bde.append(json.dumps(d))
if verb >= 3:
current += 1
print('\033[2K' + '({c}/{t})'.format(c=current, t=total) + '\033[1A')
self.stdout.write("\033[2K" + f"({current}/{total})" + "\033[1A")
else:
# make your wrapped or reuse previous wrapped
raise NotImplementedError("The BDE: {bde_name} has not personalized wrapped, make it !"
.format(bde_name=bde[i].name))
raise NotImplementedError(f"The BDE: {bde[i].name} has not personalized wrapped, make it !")
data.append(data_bde)
return data
@ -557,7 +549,7 @@ class Command(BaseCommand):
total = 0
for n in note:
total += len(n)
print('\033[mMake {nb} wrapped'.format(nb=total))
self.stdout.write(f"Make {total} wrapped")
for i in range(len(bde)):
for j in range(len(note[i])):
if create and not Wrapped.objects.filter(bde=bde[i], note=note[i][j]):
@ -572,7 +564,7 @@ class Command(BaseCommand):
w.save()
if verb >= 3:
current += 1
print('\033[2K' + '({c}/{t})'.format(c=current, t=total) + '\033[1A')
self.stdout.write("\033[2K" + f"({current}/{total})" + "\033[1A")
return
def filter_note(self, bde, note, change, create, verb=1):

View File

View File

@ -0,0 +1,91 @@
# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta
from api.tests import TestAPI
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
from django.utils import timezone
from ..api.views import WrappedViewSet, BdeViewSet
from ..models import Bde, Wrapped
class TestWrapped(TestCase):
"""
Test activities
"""
fixtures = ('initial',)
def setUp(self):
self.user = User.objects.create_superuser(
username="admintoto",
password="tototototo",
email="toto@example.com"
)
self.client.force_login(self.user)
sess = self.client.session
sess["permission_mask"] = 42
sess.save()
self.bde = Bde.objects.create(
name="The best BDE",
date_start=timezone.now() - timedelta(days=365),
date_end=timezone.now(),
)
self.wrapped = Wrapped.objects.create(
generated=True,
public=False,
bde=self.bde,
note=self.user.note,
data_json="{}",
)
def test_wrapped_list(self):
"""
Display the list of all wrapped
"""
response = self.client.get(reverse("wrapped:wrapped_list"))
self.assertEqual(response.status_code, 200)
def test_wrapped_detail(self):
"""
Display the detail of an wrapped
"""
response = self.client.get(reverse("wrapped:wrapped_detail", args=(self.wrapped.pk,)))
self.assertEqual(response.status_code, 200)
class TestWrappedAPI(TestAPI):
def setUp(self) -> None:
super().setUp()
self.bde = Bde.objects.create(
name="The best BDE",
date_start=timezone.now() - timedelta(days=365),
date_end=timezone.now(),
)
self.wrapped = Wrapped.objects.create(
generated=True,
public=False,
bde=self.bde,
note=self.user.note,
data_json="{}",
)
def test_bde_api(self):
"""
Load Bde API page and test all filters and permissions
"""
self.check_viewset(BdeViewSet, "/api/wrapped/bde/")
def test_wrapped_api(self):
"""
Load Wrapped API page and test all filters and permissions
"""
self.check_viewset(WrappedViewSet, "/api/wrapped/wrapped/")

View File

@ -55,6 +55,7 @@ Les adhérent⋅es ont la possibilité d'inviter des ami⋅es. Pour cela, les di
* Activité concernée (clé étrangère)
* Nom de famille
* Prénom
* École
* Note de la personne ayant invité
Certaines contraintes s'appliquent :

View File

@ -43,6 +43,11 @@ On a ensuite besoin de définir nos propres scopes afin d'avoir des permissions
'SCOPES_BACKEND_CLASS': 'permission.scopes.PermissionScopes',
'OAUTH2_VALIDATOR_CLASS': "permission.scopes.PermissionOAuth2Validator",
'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=14),
'PKCE_REQUIRED': False,
'OIDC_ENABLED': True,
'OIDC_RSA_PRIVATE_KEY':
os.getenv('OIDC_RSA_PRIVATE_KEY', '/var/secrets/oidc.key'),
'SCOPES': { 'openid': "OpenID Connect scope" },
}
Cela a pour effet d'avoir des scopes sous la forme ``PERMISSION_CLUB``,
@ -57,6 +62,14 @@ On ajoute enfin les routes dans ``urls.py`` :
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider'))
)
Enfin pour utiliser OIDC, il faut générer une clé privé que l'on va, par défaut,
mettre dans `/var/secrets/oidc.key` :
.. code:: bash
cd /var/secrets/
openssl genrsa -out oidc.key 4096
L'OAuth2 est désormais prêt à être utilisé.

View File

@ -227,6 +227,22 @@ En production, ce fichier contient :
)
Génération d'une clé privé pour OIDC
------------------------------------
Pour pouvoir proposer le service de connexion Openid Connect (OIDC) par OAuth2, il y a
besoin d'une clé privé. Par défaut, elle est cherché dans le fichier `/var/secrets/oidc.key`
(sinon, il faut modifier l'emplacement dans les fichiers de configurations).
Pour générer la clé, il faut aller dans le dossier `/var/secrets` (à créer, si nécessaire) puis
utiliser la commande de génération :
.. code:: bash
cd /var/secrets
openssl genrsa -out oidc.key 4096
Configuration des tâches récurrentes
------------------------------------

View File

@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-13 21:08+0100\n"
"POT-Creation-Date: 2025-03-25 11:16+0100\n"
"PO-Revision-Date: 2022-04-11 22:05+0200\n"
"Last-Translator: bleizi <bleizi@crans.org>\n"
"Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
@ -25,7 +25,7 @@ msgid "This opener already exists"
msgstr "Cette amitié existe déjà"
#: apps/activity/apps.py:10 apps/activity/models.py:129
#: apps/activity/models.py:169 apps/activity/models.py:323
#: apps/activity/models.py:169 apps/activity/models.py:328
msgid "activity"
msgstr "activité"
@ -37,24 +37,24 @@ msgstr "La note du club est inactive."
msgid "The end date must be after the start date."
msgstr "La date de fin doit être après celle de début."
#: apps/activity/forms.py:83 apps/activity/models.py:271
#: apps/activity/forms.py:83 apps/activity/models.py:276
msgid "You can't invite someone once the activity is started."
msgstr ""
"Vous ne pouvez pas inviter quelqu'un une fois que l'activité a démarré."
#: apps/activity/forms.py:86 apps/activity/models.py:274
#: apps/activity/forms.py:86 apps/activity/models.py:279
msgid "This activity is not validated yet."
msgstr "Cette activité n'est pas encore validée."
#: apps/activity/forms.py:96 apps/activity/models.py:282
#: apps/activity/forms.py:96 apps/activity/models.py:287
msgid "This person has been already invited 5 times this year."
msgstr "Cette personne a déjà été invitée 5 fois cette année."
#: apps/activity/forms.py:100 apps/activity/models.py:286
#: apps/activity/forms.py:100 apps/activity/models.py:291
msgid "This person is already invited."
msgstr "Cette personne est déjà invitée."
#: apps/activity/forms.py:104 apps/activity/models.py:290
#: apps/activity/forms.py:104 apps/activity/models.py:295
msgid "You can't invite more than 3 people to this activity."
msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité."
@ -228,32 +228,36 @@ msgstr "nom de famille"
msgid "first name"
msgstr "prénom"
#: apps/activity/models.py:254
#: apps/activity/models.py:252
msgid "school"
msgstr "école"
#: apps/activity/models.py:259
msgid "inviter"
msgstr "hôte"
#: apps/activity/models.py:258
#: apps/activity/models.py:263
msgid "guest"
msgstr "invité·e"
#: apps/activity/models.py:259
#: apps/activity/models.py:264
msgid "guests"
msgstr "invité·e·s"
#: apps/activity/models.py:312
#: apps/activity/models.py:317
msgid "Invitation"
msgstr "Invitation"
#: apps/activity/models.py:330 apps/activity/models.py:334
#: apps/activity/models.py:335 apps/activity/models.py:339
msgid "Opener"
msgstr "Ouvreur⋅se"
#: apps/activity/models.py:335
#: apps/activity/models.py:340
#: apps/activity/templates/activity/activity_detail.html:16
msgid "Openers"
msgstr "Ouvreur⋅ses"
#: apps/activity/models.py:339
#: apps/activity/models.py:344
#, fuzzy, python-brace-format
#| msgid "Entry for {note} to the activity {activity}"
msgid "{opener} is opener of activity {acivity}"
@ -463,25 +467,25 @@ msgstr "Détails de l'activité"
msgid "Update activity"
msgstr "Modifier l'activité"
#: apps/activity/views.py:177
#: apps/activity/views.py:178
msgid "Invite guest to the activity \"{}\""
msgstr "Invitation pour l'activité « {} »"
#: apps/activity/views.py:217
#: apps/activity/views.py:218
msgid "You are not allowed to display the entry interface for this activity."
msgstr ""
"Vous n'êtes pas autorisé·e à afficher l'interface des entrées pour cette "
"activité."
#: apps/activity/views.py:220
#: apps/activity/views.py:221
msgid "This activity does not support activity entries."
msgstr "Cette activité ne requiert pas d'entrées."
#: apps/activity/views.py:223
#: apps/activity/views.py:224
msgid "This activity is closed."
msgstr "Cette activité est fermée."
#: apps/activity/views.py:328
#: apps/activity/views.py:329
msgid "Entry for activity \"{}\""
msgstr "Entrées pour l'activité « {} »"
@ -1989,10 +1993,6 @@ 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/horaires.html:35
#: apps/scripts/templates/scripts/horaires.txt:17
#: apps/scripts/templates/scripts/intro_mail.html:49
#: apps/scripts/templates/scripts/intro_mail.txt:25
msgid "Mail generated by the Note Kfet on the"
msgstr "Mail généré par la Note Kfet le"

View File

@ -268,6 +268,10 @@ OAUTH2_PROVIDER = {
'OAUTH2_VALIDATOR_CLASS': "permission.scopes.PermissionOAuth2Validator",
'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=14),
'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'),
'SCOPES': { 'openid': "OpenID Connect scope" },
}
# Take control on how widget templates are sourced