diff --git a/apps/activity/views.py b/apps/activity/views.py
index d559255d..17446947 100644
--- a/apps/activity/views.py
+++ b/apps/activity/views.py
@@ -329,7 +329,7 @@ class ActivityEntryView(LoginRequiredMixin, SingleTableMixin, TemplateView):
context["noteuser_ctype"] = ContentType.objects.get_for_model(NoteUser).pk
context["notespecial_ctype"] = ContentType.objects.get_for_model(NoteSpecial).pk
- activities_open = Activity.objects.filter(open=True).filter(
+ activities_open = Activity.objects.filter(open=True, activity_type__manage_entries=True).filter(
PermissionBackend.filter_queryset(self.request, Activity, "view")).distinct().all()
context["activities_open"] = [a for a in activities_open
if PermissionBackend.check_perm(self.request,
diff --git a/apps/api/urls.py b/apps/api/urls.py
index ef631004..ad2daf5f 100644
--- a/apps/api/urls.py
+++ b/apps/api/urls.py
@@ -47,6 +47,10 @@ if "wei" in settings.INSTALLED_APPS:
from wei.api.urls import register_wei_urls
register_wei_urls(router, 'wei')
+if "wrapped" in settings.INSTALLED_APPS:
+ from wrapped.api.urls import register_wrapped_urls
+ register_wrapped_urls(router, 'wrapped')
+
app_name = 'api'
# Wire up our API using automatic URL routing.
diff --git a/apps/member/forms.py b/apps/member/forms.py
index a74ddb90..ef9cb24d 100644
--- a/apps/member/forms.py
+++ b/apps/member/forms.py
@@ -44,6 +44,7 @@ class ProfileForm(forms.ModelForm):
"""
A form for the extras field provided by the :model:`member.Profile` model.
"""
+ # Remove widget=forms.HiddenInput() if you want to use report frequency.
report_frequency = forms.IntegerField(required=False, initial=0, label=_("Report frequency"))
last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date"))
@@ -76,7 +77,8 @@ class ProfileForm(forms.ModelForm):
class Meta:
model = Profile
fields = '__all__'
- exclude = ('user', 'email_confirmed', 'registration_valid', )
+ # Remove ml_[asso]_registration from exclude if the concerned association uses nk20 to manage its mailing list.
+ exclude = ('user', 'email_confirmed', 'registration_valid', 'ml_sport_registration', )
class ImageForm(forms.Form):
diff --git a/apps/member/views.py b/apps/member/views.py
index 348bf089..4d18a40a 100644
--- a/apps/member/views.py
+++ b/apps/member/views.py
@@ -26,6 +26,7 @@ from note_kfet.middlewares import _set_current_request
from permission.backends import PermissionBackend
from permission.models import Role
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
+from django import forms
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \
CustomAuthenticationForm, MembershipRolesForm
@@ -72,11 +73,24 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
form.fields['email'].required = True
form.fields['email'].help_text = _("This address must be valid.")
- if PermissionBackend.check_perm(self.request, "member.change_profile", context['user_object'].profile):
- context['profile_form'] = self.profile_form(instance=context['user_object'].profile,
- data=self.request.POST if self.request.POST else None)
- if not self.object.profile.report_frequency:
- del context['profile_form'].fields["last_report"]
+ profile_form = self.profile_form(instance=context['user_object'].profile,
+ data=self.request.POST if self.request.POST else None)
+
+ if not self.object.profile.report_frequency:
+ del profile_form.fields["last_report"]
+
+ fields_to_check = list(profile_form.fields.keys())
+ fields_modifiable = False
+
+ # Delete the fields for which the user does not have the permission to modify
+ for field_name in fields_to_check:
+ if not PermissionBackend.check_perm(self.request, f"member.change_profile_{field_name}", context['user_object'].profile):
+ profile_form.fields[field_name].widget = forms.HiddenInput()
+ else:
+ fields_modifiable = True
+
+ if fields_modifiable:
+ context['profile_form'] = profile_form
return context
diff --git a/apps/permission/admin.py b/apps/permission/admin.py
index a6fc713c..82f8d4ab 100644
--- a/apps/permission/admin.py
+++ b/apps/permission/admin.py
@@ -31,3 +31,4 @@ class RoleAdmin(admin.ModelAdmin):
Admin customisation for Role
"""
list_display = ('name', )
+ filter_horizontal = ('permissions',)
diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json
index baed9a8d..bf9171fc 100644
--- a/apps/permission/fixtures/initial.json
+++ b/apps/permission/fixtures/initial.json
@@ -127,7 +127,7 @@
"auth",
"user"
],
- "query": "{\"pk\": [\"user\", \"pk\"]}",
+ "query": "[\"AND\", {\"pk\": [\"user\", \"pk\"]}, {\"memberships__club__parent_club__isnull\": true}]",
"type": "change",
"mask": 1,
"field": "last_login",
@@ -3800,6 +3800,294 @@
"description": "Voir les utilisateurs adhérents au club parent"
}
},
+ {
+ "model": "permission.permission",
+ "pk": 242,
+ "fields": {
+ "model": [
+ "note",
+ "transaction"
+ ],
+ "query": "[\"AND\", {\"destination\": [\"club\", \"note\"]}, [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]]",
+ "type": "add",
+ "mask": 2,
+ "field": "",
+ "permanent": false,
+ "description": "Créer une transaction vers la note d'un club"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 243,
+ "fields": {
+ "model": [
+ "member",
+ "profile"
+ ],
+ "query": "{\"user__memberships__club\": [\"club\"], \"user__memberships__date_start__lte\": [\"today\"],\"user__memberships__date_end__gte\": [\"today\"]}",
+ "type": "view",
+ "mask": 3,
+ "field": "",
+ "permanent": false,
+ "description": "Voir les profils des membres du club"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 244,
+ "fields": {
+ "model": [
+ "member",
+ "profile"
+ ],
+ "query": "{}",
+ "type": "change",
+ "mask": 3,
+ "field": "ml_events_registration",
+ "permanent": false,
+ "description": "Modifier l'abonnement à la Newsletter BDE pour n'importe quel profil"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 245,
+ "fields": {
+ "model": [
+ "member",
+ "profile"
+ ],
+ "query": "{}",
+ "type": "change",
+ "mask": 3,
+ "field": "ml_art_registration",
+ "permanent": false,
+ "description": "Modifier l'abonnement à la Newsletter Art pour n'importe quel profil"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 246,
+ "fields": {
+ "model": [
+ "member",
+ "profile"
+ ],
+ "query": "{}",
+ "type": "change",
+ "mask": 3,
+ "field": "ml_sport_registration",
+ "permanent": false,
+ "description": "Modifier l'abonnement à la Newsletter Sport pour n'importe quel profil"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 247,
+ "fields": {
+ "model": [
+ "activity",
+ "guest"
+ ],
+ "query": "{\"activity__organizer\": [\"club\"]}",
+ "type": "view",
+ "mask": 2,
+ "field": "",
+ "permanent": false,
+ "description": "Voir les personnes invitées aux événements organisés par son club"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 248,
+ "fields": {
+ "model": [
+ "auth",
+ "user"
+ ],
+ "query": "[\"NOT\", {\"pk__isnull\": [\"user\", \"note\", \"activity_responsible\", [\"filter\", {\"activity__open\": true, \"activity__activity_type__manage_entries\":true}], [\"exists\"]]}]",
+ "type": "view",
+ "mask": 3,
+ "field": "",
+ "permanent": false,
+ "description": "Voir n'importe quel⋅le utilisateur⋅rice pour les ouvreur⋅ses"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 249,
+ "fields": {
+ "model": [
+ "note",
+ "note"
+ ],
+ "query": "[\"NOT\", {\"pk__isnull\": [\"user\", \"note\", \"activity_responsible\", [\"filter\", {\"activity__open\": true, \"activity__activity_type__manage_entries\":true}], [\"exists\"]]}]",
+ "type": "view",
+ "mask": 2,
+ "field": "",
+ "permanent": false,
+ "description": "Voir toutes les notes lorsque utilisateur⋅rice est ouvreur⋅ses"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 250,
+ "fields": {
+ "model": [
+ "activity",
+ "guest"
+ ],
+ "query": "{\"activity__organizer\": [\"club\"]}",
+ "type": "delete",
+ "mask": 1,
+ "field": "",
+ "permanent": false,
+ "description": "Supprimer des personnes invitées aux événements organisés par son club"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 251,
+ "fields": {
+ "model": [
+ "activity",
+ "opener"
+ ],
+ "query": "{\"activity__organizer\": [\"club\"]}",
+ "type": "view",
+ "mask": 2,
+ "field": "",
+ "permanent": false,
+ "description": "Voir les ouvreur⋅ses des activités organisées par son club"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 252,
+ "fields": {
+ "model": [
+ "activity",
+ "opener"
+ ],
+ "query": "{\"activity__organizer\": [\"club\"]}",
+ "type": "add",
+ "mask": 2,
+ "field": "",
+ "permanent": false,
+ "description": "Ajouter des ouvreur⋅ses aux activités organisées par son club"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 253,
+ "fields": {
+ "model": [
+ "activity",
+ "opener"
+ ],
+ "query": "{\"activity__organizer\": [\"club\"]}",
+ "type": "delete",
+ "mask": 2,
+ "field": "",
+ "permanent": false,
+ "description": "Supprimer des ouvreur⋅ses aux activités organisées par son club"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 254,
+ "fields": {
+ "model": [
+ "activity",
+ "activity"
+ ],
+ "query": "{\"organizer\": [\"club\"]}",
+ "type": "change",
+ "mask": 2,
+ "field": "opener",
+ "permanent": false,
+ "description": "Voir le tableau des ouvreur⋅ses pour les activités organisées par son club"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 255,
+ "fields": {
+ "model": [
+ "wrapped",
+ "wrapped"
+ ],
+ "query": "{\"public\": true}",
+ "type": "view",
+ "mask": 1,
+ "field": "",
+ "permanent": false,
+ "description": "Voir les wrapped public"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 256,
+ "fields": {
+ "model": [
+ "wrapped",
+ "wrapped"
+ ],
+ "query": "{\"note__noteuser__user\": [\"user\"]}",
+ "type": "view",
+ "mask": 1,
+ "field": "",
+ "permanent": true,
+ "description": "Voir ses propres wrapped, pour toujours"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 257,
+ "fields": {
+ "model": [
+ "wrapped",
+ "wrapped"
+ ],
+ "query": "{\"note__noteuser__user\": [\"user\"]}",
+ "type": "change",
+ "mask": 1,
+ "field": "public",
+ "permanent": true,
+ "description": "Modifier la visibilité de ses wrapped, pour toujours"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 258,
+ "fields": {
+ "model": [
+ "wrapped",
+ "wrapped"
+ ],
+ "query": "{\"note__noteclub__club\": [\"club\"]}",
+ "type": "view",
+ "mask": 1,
+ "field": "",
+ "permanent": false,
+ "description": "Voir les wrapped de son club"
+ }
+ },
+ {
+ "model": "permission.permission",
+ "pk": 259,
+ "fields": {
+ "model": [
+ "wrapped",
+ "wrapped"
+ ],
+ "query": "{\"note__noteclub__club\": [\"club\"]}",
+ "type": "change",
+ "mask": 1,
+ "field": "public",
+ "permanent": false,
+ "description": "Modifier la visibilité des wrapped de son club"
+ }
+ },
{
"model": "permission.role",
"pk": 1,
@@ -3849,7 +4137,12 @@
203,
204,
205,
- 206
+ 206,
+ 248,
+ 249,
+ 255,
+ 256,
+ 257
]
}
},
@@ -3938,7 +4231,10 @@
227,
233,
234,
- 237
+ 237,
+ 247,
+ 258,
+ 259
]
}
},
@@ -4377,7 +4673,7 @@
238
]
}
- },
+ },
{
"model": "wei.weirole",
"pk": 12,
diff --git a/apps/wei/forms/surveys/wei2024.py b/apps/wei/forms/surveys/wei2024.py
index 20d9ba1c..518d58f0 100644
--- a/apps/wei/forms/surveys/wei2024.py
+++ b/apps/wei/forms/surveys/wei2024.py
@@ -291,7 +291,7 @@ class WEISurveyAlgorithm2024(WEISurveyAlgorithm):
for survey in surveys:
survey.free()
if survey.registration.user_id in hardcoded_first_year.keys():
- survey.select_bus(hardcoded_first_year[s.registration.user_id])
+ survey.select_bus(hardcoded_first_year[survey.registration.user_id])
survey.save()
non_men = [s for s in surveys if s.registration.gender != 'male']
diff --git a/apps/wrapped/__init__.py b/apps/wrapped/__init__.py
new file mode 100644
index 00000000..e9c45ef0
--- /dev/null
+++ b/apps/wrapped/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+default_app_config = 'activity.apps.WrappedConfig'
diff --git a/apps/wrapped/admin.py b/apps/wrapped/admin.py
new file mode 100644
index 00000000..a50ec0ad
--- /dev/null
+++ b/apps/wrapped/admin.py
@@ -0,0 +1,17 @@
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from django.contrib import admin
+from note_kfet.admin import admin_site
+
+from .models import Bde, Wrapped
+
+
+@admin.register(Bde, site=admin_site)
+class BdeAdmin(admin.ModelAdmin):
+ pass
+
+
+@admin.register(Wrapped, site=admin_site)
+class WrappedAdmin(admin.ModelAdmin):
+ pass
diff --git a/apps/wrapped/api/__init__.py b/apps/wrapped/api/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/apps/wrapped/api/serializers.py b/apps/wrapped/api/serializers.py
new file mode 100644
index 00000000..d156ae75
--- /dev/null
+++ b/apps/wrapped/api/serializers.py
@@ -0,0 +1,28 @@
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from rest_framework import serializers
+
+from ..models import Wrapped, Bde
+
+
+class WrappedSerializer(serializers.ModelSerializer):
+ """
+ REST API Serializer for Wrapped.
+ The djangorestframework plugin will analyse the model `Wrapped` and parse all fields in the API.
+ """
+
+ class Meta:
+ model = Wrapped
+ fields = '__all__'
+
+
+class BdeSerializer(serializers.ModelSerializer):
+ """
+ REST API Serializer for Bde.
+ The djangorestframework plugin will analyse the model `Bde` and parse all fields in the API.
+ """
+
+ class Meta:
+ model = Bde
+ fields = '__all__'
diff --git a/apps/wrapped/api/urls.py b/apps/wrapped/api/urls.py
new file mode 100644
index 00000000..b06cddb9
--- /dev/null
+++ b/apps/wrapped/api/urls.py
@@ -0,0 +1,12 @@
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from .views import WrappedViewSet, BdeViewSet
+
+
+def register_wrapped_urls(router, path):
+ """
+ Configure router for Wrapped REST API.
+ """
+ router.register(path + '/wrapped', WrappedViewSet)
+ router.register(path + '/bde', BdeViewSet)
diff --git a/apps/wrapped/api/views.py b/apps/wrapped/api/views.py
new file mode 100644
index 00000000..72294230
--- /dev/null
+++ b/apps/wrapped/api/views.py
@@ -0,0 +1,35 @@
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from api.viewsets import ReadProtectedModelViewSet
+from django_filters.rest_framework import DjangoFilterBackend
+from rest_framework.filters import SearchFilter
+
+from .serializers import WrappedSerializer, BdeSerializer
+from ..models import Wrapped, Bde
+
+
+class WrappedViewSet(ReadProtectedModelViewSet):
+ """
+ REST API View set.
+ The djangorestframework plugin will get all `Wrapped` objects, serialize it to JSON with the given
+ serializer, then render it on /api/wrapped/wrapped/
+ """
+ queryset = Wrapped.objects.order_by('id')
+ serializer_class = WrappedSerializer
+ filter_backends = [DjangoFilterBackend, SearchFilter]
+ filterset_fields = ['note', 'bde', ]
+ search_fields = ['$note', ]
+
+
+class BdeViewSet(ReadProtectedModelViewSet):
+ """
+ REST API View set.
+ The djangorestframework plugin will get all `Bde` objects, serialize it to JSON with the given
+ serializer, then render it on /api/wrapped/bde/
+ """
+ queryset = Bde.objects.order_by('id')
+ serializer_class = BdeSerializer
+ filter_backends = [DjangoFilterBackend, SearchFilter]
+ filterset_fields = ['name', ]
+ search_fields = ['$name', ]
diff --git a/apps/wrapped/apps.py b/apps/wrapped/apps.py
new file mode 100644
index 00000000..83630287
--- /dev/null
+++ b/apps/wrapped/apps.py
@@ -0,0 +1,10 @@
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from django.apps import AppConfig
+from django.utils.translation import gettext_lazy as _
+
+
+class WrappedConfig(AppConfig):
+ name = 'wrapped'
+ verbose_name = _('wrapped')
diff --git a/apps/wrapped/management/commands/generate_wrapped.py b/apps/wrapped/management/commands/generate_wrapped.py
new file mode 100644
index 00000000..9e5c19d2
--- /dev/null
+++ b/apps/wrapped/management/commands/generate_wrapped.py
@@ -0,0 +1,592 @@
+# Copyright (C) 2028-2024 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+import json
+from argparse import ArgumentParser
+
+from django.core.management import BaseCommand
+from django.db.models import Q
+from note.models import Note, Transaction
+from member.models import User, Club, Membership
+from activity.models import Activity, Entry
+from wei.models import WEIClub
+
+from ...models import Bde, Wrapped
+
+
+class Command(BaseCommand):
+ help = "Generate wrapper for the annual BDE change"
+
+ def add_arguments(self, parser: ArgumentParser):
+ parser.add_argument(
+ '-b', '--bde',
+ type=str,
+ required=False,
+ help="A list of BDE name, BDE1,BDE2,... (a BDE name cannot have ',')",
+ dest='bde',
+ )
+ parser.add_argument(
+ '-i', '--id',
+ type=str,
+ required=False,
+ help="A list of BDE id, id1,id2,...",
+ dest='bde_id',
+ )
+ parser.add_argument(
+ '-u', '--users',
+ type=str,
+ 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
+ supersuser = all superusers
+ custom user1,user2,... = a list of username,
+ custom_id id1,id2,... = a list of user id""",
+ dest='user',
+ )
+ parser.add_argument(
+ '-c', '--club',
+ type=str,
+ required=False,
+ help="""Club will have their(s) wrapped generated,
+ all = all clubs,
+ active = all clubs with at least one transaction during the BDE mandate considered,
+ custom club1,club2,... = a list of club name,
+ custom_id id1,id2,... = a list of club id""",
+ dest='club',
+ )
+ parser.add_argument(
+ '-f', '--force-change',
+ required=False,
+ action='store_true',
+ help="if wrapped already exist change data_json",
+ dest='change',
+ )
+ parser.add_argument(
+ '-n', '--no-creation',
+ required=False,
+ action='store_false',
+ help="if wrapped don't already exist, don't generate it",
+ 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'
+
+ # Traitement des paramètres
+ verb = options['verbosity']
+ bde = []
+ if options['bde']:
+ bde_list = options['bde'].split(',')
+ bde = [Bde.objects.get(name=bde_name) for bde_name in bde_list]
+
+ if options['bde_id']:
+ if bde:
+ if verb >= 1:
+ print(warning)
+ print(yellow + 'You already defined bde with their name !')
+ if verb >= 0:
+ print(abort)
+ return
+ bde_id = options['bde_id'].split(',')
+ bde = [Bde.objects.get(pk=i) for i in bde_id]
+
+ user = []
+ if options['user']:
+ if options['user'] == 'all':
+ user = ['all', None]
+ elif options['user'] == 'adh':
+ user = ['adh', None]
+ elif options['user'] == 'superuser':
+ user = ['superuser', None]
+ elif options['user'].split(' ')[0] == 'custom':
+ user_list = options['user'].split(' ')[1].split(',')
+ user = ['custom', [User.objects.get(username=u) for u in user_list]]
+ elif options['user'].split(' ')[0] == 'custom_id':
+ user_id = options['user'].split(' ')[1].split(',')
+ 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')
+ if verb >= 0:
+ print(abort)
+ return
+
+ club = []
+ if options['club']:
+ if options['club'] == 'all':
+ club = ['all', None]
+ elif options['club'] == 'active':
+ club = ['active', None]
+ elif options['club'].split(' ')[0] == 'custom':
+ club_list = options['club'].split(' ')[1].split(',')
+ club = ['custom', [Club.objects.get(name=club_name) for club_name in club_list]]
+ elif options['club'].split(' ')[0] == 'custom_id':
+ club_id = options['club'].split(' ')[1].split(',')
+ 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')
+ if verb >= 0:
+ print(abort)
+ return
+
+ change = options['change']
+ create = options['create']
+
+ # 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 !')
+ if verb >= 0:
+ print(abort)
+ return
+ if not (user or club):
+ if verb >= 1:
+ print(warning)
+ print(yellow + 'No club or user selected !')
+ if verb >= 0:
+ print(abort)
+ return
+
+ if verb >= 3:
+ print('\033[1mOptions:\033[m')
+ bde_str = ''
+ for b in bde:
+ bde_str += str(b)
+ print('BDE: ' + bde_str)
+ if user:
+ print('User: ' + user[0])
+ if club:
+ print('Club: ' + club[0])
+ print('change: ' + str(change))
+ print('create: ' + str(create))
+ print('')
+ if not (change or create):
+ if verb >= 1:
+ print(warning)
+ print(yellow + 'change and create is set to false, none wrapped will be created')
+ if verb >= 0:
+ print(abort)
+ return
+ if verb >= 1 and change:
+ print(warning)
+ print(yellow + 'change 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 !')
+ 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
+
+ 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")
+ global_data = self.global_data(bde, verb=verb)
+ if verb >= 1:
+ print("\033[32mGlobal data has been successfully generated\033[m")
+
+ 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.make_wrapped(unique_data, note, bde, change, create, verb=verb)
+ if verb >= 1:
+ print(green + "The wrapped has been generated !")
+ if verb >= 0:
+ print(success)
+
+ return
+
+ def convert_to_note(self, change, create, bde=None, user=None, club=None, verb=1):
+ notes = []
+ for b in bde:
+ note_for_bde = Note.objects.filter(pk__lte=-1)
+ if user:
+ if 'custom' in user[0]:
+ for u in user[1]:
+ query = Q(noteuser__user=u)
+ note_for_bde |= Note.objects.filter(query)
+ elif user[0] == 'all':
+ query = Q(noteuser__user__pk__gte=-1)
+ note_for_bde |= Note.objects.filter(query)
+ elif user[0] == 'adh':
+ m = Membership.objects.filter(club=1,
+ date_start__lt=b.date_end,
+ date_end__gt=b.date_start,
+ ).distinct('user')
+ for membership in m:
+ note_for_bde |= Note.objects.filter(noteuser__user=membership.user)
+
+ elif user[0] == 'superuser':
+ query |= Q(noteuser__user__is_superuser=True)
+ note_for_bde |= Note.objects.filter(query)
+
+ if club:
+ if 'custom' in club[0]:
+ for c in club[1]:
+ query = Q(noteclub__club=c)
+ note_for_bde |= Note.objects.filter(query)
+ elif club[0] == 'all':
+ query = Q(noteclub__club__pk__gte=-1)
+ note_for_bde |= Note.objects.filter(query)
+ elif club[0] == 'active':
+ nc = Note.objects.filter(noteclub__club__pk__gte=-1)
+ for noteclub in nc:
+ if Transaction.objects.filter(
+ Q(created_at__gte=b.date_start,
+ created_at__lte=b.date_end) & (Q(source=noteclub) | Q(destination=noteclub))):
+ note_for_bde |= Note.objects.filter(pk=noteclub.pk)
+
+ 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))
+ return notes
+
+ def global_data(self, bde, verb=1):
+ data = {}
+ for b in bde:
+ if b.name == 'Rave Part[list]':
+ if verb >= 2:
+ print("Begin to make global data")
+ if verb >= 3:
+ print('nb_transaction')
+ # nb total de transactions
+ data['nb_transaction'] = Transaction.objects.filter(
+ created_at__gte=b.date_start,
+ created_at__lte=b.date_end,
+ valid=True).count()
+
+ if verb >= 3:
+ print('nb_vieux_con')
+ # nb total de vielleux con·ne·s derrière le bar
+ button_id = [2884, 2585]
+ transactions = Transaction.objects.filter(
+ created_at__gte=b.date_start,
+ created_at__lte=b.date_end,
+ valid=True,
+ recurrenttransaction__template__pk__in=button_id)
+
+ q = 0
+ for t in transactions:
+ q += t.quantity
+ data['nb_vieux_con'] = q
+
+ if verb >= 3:
+ print('nb_soiree')
+ # nb total de soirée
+ a_type_id = [1, 2, 4, 5, 7, 10]
+ data['nb_soiree'] = Activity.objects.filter(
+ date_end__gte=b.date_start,
+ date_start__lte=b.date_end,
+ valid=True,
+ activity_type__pk__in=a_type_id).count()
+
+ if verb >= 3:
+ print('pots, nb_entree_pot')
+ # nb d'entrée totale aux pots
+ pot_id = [1, 4, 10]
+ pots = Activity.objects.filter(
+ date_end__gte=b.date_start,
+ date_start__lte=b.date_end,
+ valid=True,
+ activity_type__pk__in=pot_id)
+ data['pots'] = pots # utile dans unique_data
+ data['nb_entree_pot'] = 0
+ for pot in pots:
+ data['nb_entree_pot'] += Entry.objects.filter(activity=pot).count()
+
+ if verb >= 3:
+ print('top3_buttons')
+ # top 3 des boutons les plus cliqués
+ transactions = Transaction.objects.filter(
+ created_at__gte=b.date_start,
+ created_at__lte=b.date_end,
+ valid=True,
+ amount__gt=0,
+ recurrenttransaction__template__pk__gte=-1)
+
+ d = {}
+ for t in transactions:
+ if t.recurrenttransaction.template.name in d:
+ d[t.recurrenttransaction.template.name] += t.quantity
+ else:
+ d[t.recurrenttransaction.template.name] = t.quantity
+
+ data['top3_buttons'] = list(sorted(d.items(), key=lambda item: item[1], reverse=True))[:3]
+
+ if verb >= 3:
+ print('class_conso_all')
+ # le classement des plus gros consommateurs (BDE + club)
+ transactions = Transaction.objects.filter(
+ created_at__gte=b.date_start,
+ created_at__lte=b.date_end,
+ valid=True,
+ source__noteuser__user__pk__gte=-1,
+ destination__noteclub__club__pk__gte=-1)
+
+ d = {}
+ for t in transactions:
+ if t.source in d:
+ d[t.source] += t.total
+ else:
+ d[t.source] = t.total
+
+ data['class_conso_all'] = dict(sorted(d.items(), key=lambda item: item[1], reverse=True))
+
+ if verb >= 3:
+ print('class_conso_bde')
+ # le classement des plus gros consommateurs BDE
+ transactions = Transaction.objects.filter(
+ created_at__gte=b.date_start,
+ created_at__lte=b.date_end,
+ valid=True,
+ source__noteuser__user__pk__gte=-1,
+ destination=5)
+
+ d = {}
+ for t in transactions:
+ if t.source in d:
+ d[t.source] += t.total
+ else:
+ d[t.source] = t.total
+
+ data['class_conso_bde'] = dict(sorted(d.items(), key=lambda item: item[1], reverse=True))
+
+ 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))
+ return data
+
+ def unique_data(self, bde, note, global_data=None, verb=1):
+ data = []
+ for i in range(len(bde)):
+ data_bde = []
+ if bde[i].name == 'Rave Part[list]':
+ 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))
+ for n in note[i]:
+ d = {}
+ if 'user' in n.__dir__():
+ # première conso du mandat
+ transactions = Transaction.objects.filter(
+ valid=True,
+ recurrenttransaction__template__id__gte=-1,
+ created_at__gte=bde[i].date_start,
+ created_at__lte=bde[i].date_end,
+ source=n,
+ destination=5).order_by('created_at')
+ if transactions:
+ d['first_conso'] = transactions[0].template.name
+ else:
+ d['first_conso'] = ''
+ # Wei + bus
+ wei = WEIClub.objects.filter(
+ date_start__lte=bde[i].date_end,
+ date_end__gte=bde[i].date_start)
+ if not wei:
+ d['wei'] = ''
+ d['bus'] = ''
+ else:
+ w = wei[0]
+ memberships = Membership.objects.filter(club=w, user=n.user)
+ if not memberships:
+ d['wei'] = ''
+ d['bus'] = ''
+ else:
+ alias = []
+ for a in w.note.alias.iterator():
+ alias.append(str(a))
+ d['wei'] = alias[-1]
+ d['bus'] = memberships[0].weimembership.bus.name
+ # top3 conso
+ transactions = Transaction.objects.filter(
+ valid=True,
+ created_at__gte=bde[i].date_start,
+ created_at__lte=bde[i].date_end,
+ source=n,
+ amount__gt=0,
+ recurrenttransaction__template__id__gte=-1)
+ dt = {}
+ dc = {}
+ for t in transactions:
+ if t.template.name in dt:
+ dt[t.template.name] += t.quantity
+ else:
+ dt[t.template.name] = t.quantity
+ if t.template.category.name in dc:
+ dc[t.template.category.name] += t.quantity
+ else:
+ dc[t.template.category.name] = t.quantity
+
+ d['top3_conso'] = list(sorted(dt.items(), key=lambda item: item[1], reverse=True))[:3]
+ # catégorie de bouton préférée
+ if dc:
+ d['top_category'] = list(sorted(dc.items(), key=lambda item: item[1], reverse=True))[0][0]
+ else:
+ d['top_category'] = ''
+ # nombre de pot, et nombre d'entrée pot
+ pots = global_data['pots']
+ d['nb_pots'] = pots.count()
+
+ p = 0
+ for pot in pots:
+ if Entry.objects.filter(activity=pot, note=n):
+ p += 1
+ d['nb_pot_entry'] = p
+ # ton nombre de rechargement
+ d['nb_rechargement'] = Transaction.objects.filter(
+ valid=True,
+ created_at__gte=bde[i].date_start,
+ created_at__lte=bde[i].date_end,
+ destination=n,
+ source__pk__in=[1, 2, 3, 4]).count()
+ # ajout info globale spécifique user
+ # classement et montant conso all
+ d['class_part_all'] = len(global_data['class_conso_all'])
+ if n in global_data['class_conso_all']:
+ d['class_conso_all'] = list(global_data['class_conso_all']).index(n) + 1
+ d['amount_conso_all'] = global_data['class_conso_all'][n] / 100
+ else:
+ d['class_conso_all'] = 0
+ d['amount_conso_all'] = 0
+ # classement et montant conso bde
+ d['class_part_bde'] = len(global_data['class_conso_bde'])
+ if n in global_data['class_conso_bde']:
+ d['class_conso_bde'] = list(global_data['class_conso_bde']).index(n) + 1
+ d['amount_conso_bde'] = global_data['class_conso_bde'][n] / 100
+ else:
+ d['class_conso_bde'] = 0
+ d['amount_conso_bde'] = 0
+
+ if 'club' in n.__dir__():
+ # plus gros consommateur
+ transactions = Transaction.objects.filter(
+ valid=True,
+ created_at__lte=bde[i].date_end,
+ created_at__gte=bde[i].date_start,
+ destination=n,
+ source__noteuser__user__pk__gte=-1)
+ dt = {}
+
+ for t in transactions:
+ if t.source.user.username in dt:
+ dt[t.source.user.username] += t.total
+ else:
+ dt[t.source.user.username] = t.total
+ if dt:
+ d['big_consumer'] = list(sorted(dt.items(), key=lambda item: item[1], reverse=True))[0]
+ d['big_consumer'] = (d['big_consumer'][0], d['big_consumer'][1] / 100)
+ else:
+ d['big_consumer'] = ''
+ # plus gros créancier
+ transactions = Transaction.objects.filter(
+ valid=True,
+ created_at__lte=bde[i].date_end,
+ created_at__gte=bde[i].date_start,
+ source=n,
+ destination__noteuser__user__pk__gte=-1)
+ dt = {}
+
+ for t in transactions:
+ if t.destination.user.username in dt:
+ dt[t.destination.user.username] += t.total
+ else:
+ dt[t.destination.user.username] = t.total
+ if dt:
+ d['big_creancier'] = list(sorted(dt.items(), key=lambda item: item[1], reverse=True))[0]
+ d['big_creancier'] = (d['big_creancier'][0], d['big_creancier'][1] / 100)
+ else:
+ d['big_creancier'] = ''
+ # nb de soirée organisée
+ d['nb_soiree_orga'] = Activity.objects.filter(
+ valid=True,
+ date_start__lte=bde[i].date_end,
+ date_end__gte=bde[i].date_start,
+ organizer=n.club).count()
+ # nb de membres cumulé
+ d['nb_member'] = Membership.objects.filter(
+ date_start__lte=bde[i].date_end,
+ date_end__gte=bde[i].date_start,
+ club=n.club).distinct('user').count()
+
+ # ajout info globale
+ # top3 button
+ d['glob_top3_conso'] = global_data['top3_buttons']
+ # nb entree pot
+ d['glob_nb_entree_pot'] = global_data['nb_entree_pot']
+ # nb soiree
+ d['glob_nb_soiree'] = global_data['nb_soiree']
+ # nb vieux con
+ d['glob_nb_vieux_con'] = global_data['nb_vieux_con']
+ # nb transaction
+ d['glob_nb_transaction'] = global_data['nb_transaction']
+
+ data_bde.append(json.dumps(d))
+ if verb >= 3:
+ current += 1
+ print('\033[2K' + '({c}/{t})'.format(c=current, t=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))
+ data.append(data_bde)
+ return data
+
+ def make_wrapped(self, unique_data, note, bde, change, create, verb=1):
+ if verb >= 3:
+ current = 0
+ total = 0
+ for n in note:
+ total += len(n)
+ print('\033[mMake {nb} wrapped'.format(nb=total))
+ 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]):
+ Wrapped(bde=bde[i],
+ note=note[i][j],
+ data_json=unique_data[i][j],
+ public=False,
+ generated=True).save()
+ elif change:
+ w = Wrapped.objects.get(bde=bde[i], note=note[i][j])
+ w.data_json = unique_data[i][j]
+ w.save()
+ if verb >= 3:
+ current += 1
+ print('\033[2K' + '({c}/{t})'.format(c=current, t=total) + '\033[1A')
+ return
+
+ def filter_note(self, bde, note, change, create, verb=1):
+ if change and create:
+ return list(note)
+ if change and not create:
+ note_new = []
+ for n in note:
+ if Wrapped.objects.filter(bde=bde, note=n):
+ note_new.append(n)
+ return note_new
+ if not change and create:
+ note_new = []
+ for n in note:
+ if not Wrapped.objects.filter(bde=bde, note=n):
+ note_new.append(n)
+ return note_new
diff --git a/apps/wrapped/migrations/0001_initial.py b/apps/wrapped/migrations/0001_initial.py
new file mode 100644
index 00000000..865112db
--- /dev/null
+++ b/apps/wrapped/migrations/0001_initial.py
@@ -0,0 +1,86 @@
+# Generated by Django 4.2.15 on 2025-02-13 01:38
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+ initial = True
+
+ dependencies = [
+ ("note", "0007_alter_note_polymorphic_ctype_and_more"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="Bde",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ ("name", models.CharField(max_length=255, verbose_name="name")),
+ ("date_start", models.DateTimeField(verbose_name="date start")),
+ ("date_end", models.DateTimeField(verbose_name="date end")),
+ ],
+ options={
+ "verbose_name": "BDE",
+ "verbose_name_plural": "BDE",
+ },
+ ),
+ migrations.CreateModel(
+ name="Wrapped",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "generated",
+ models.BooleanField(default=False, verbose_name="generated"),
+ ),
+ ("public", models.BooleanField(default=False, verbose_name="public")),
+ (
+ "data_json",
+ models.TextField(
+ default="{}",
+ help_text="data in the wrapped and generated by the script generate_wrapped",
+ verbose_name="data json",
+ ),
+ ),
+ (
+ "bde",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="+",
+ to="wrapped.bde",
+ verbose_name="bde",
+ ),
+ ),
+ (
+ "note",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.PROTECT,
+ related_name="+",
+ to="note.note",
+ verbose_name="note",
+ ),
+ ),
+ ],
+ options={
+ "verbose_name": "Wrapped",
+ "verbose_name_plural": "Wrappeds",
+ "unique_together": {("note", "bde")},
+ },
+ ),
+ ]
diff --git a/apps/wrapped/migrations/__init__.py b/apps/wrapped/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/apps/wrapped/models.py b/apps/wrapped/models.py
new file mode 100644
index 00000000..b9ebc0e2
--- /dev/null
+++ b/apps/wrapped/models.py
@@ -0,0 +1,80 @@
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+from note.models import Note
+
+
+class Bde(models.Model):
+ """
+ describe a BDE
+ """
+
+ name = models.CharField(
+ max_length=255,
+ verbose_name=_('name'),
+ )
+
+ date_start = models.DateTimeField(
+ verbose_name=_('date start'),
+ )
+
+ date_end = models.DateTimeField(
+ verbose_name=_('date end'),
+ )
+
+ class Meta:
+ verbose_name = _('BDE')
+ verbose_name_plural = _('BDE')
+
+ def __str__(self):
+ return self.name
+
+
+class Wrapped(models.Model):
+ """
+ A Wrapped is associated to a note, a BDE year,
+ """
+ generated = models.BooleanField(
+ verbose_name=_('generated'),
+ default=False,
+ )
+
+ public = models.BooleanField(
+ verbose_name=_('public'),
+ default=False,
+ )
+
+ bde = models.ForeignKey(
+ Bde,
+ on_delete=models.PROTECT,
+ related_name='+',
+ verbose_name=_('bde'),
+ )
+
+ note = models.ForeignKey(
+ Note,
+ on_delete=models.PROTECT,
+ related_name='+',
+ verbose_name=_('note'),
+ )
+
+ data_json = models.TextField(
+ default='{}',
+ verbose_name=_('data json'),
+ help_text=_('data in the wrapped and generated by the script generate_wrapped'),
+ )
+
+ class Meta:
+ verbose_name = _('Wrapped')
+ verbose_name_plural = _('Wrappeds')
+ unique_together = ('note', 'bde')
+
+ def __str__(self):
+ return 'NoteKfet Wrapped of {note} sponsored by {bde}'.format(bde=str(self.bde), note=str(self.note))
+
+ def makepublic(self):
+ self.public = not self.public
+ self.save()
+ return
diff --git a/apps/wrapped/static/wrapped/css/1/custom.css b/apps/wrapped/static/wrapped/css/1/custom.css
new file mode 100644
index 00000000..39a2d2c3
--- /dev/null
+++ b/apps/wrapped/static/wrapped/css/1/custom.css
@@ -0,0 +1,73 @@
+:root {
+ --accent-primary: #FF0065;
+ --accent-secondary: #FFCB20;
+}
+@font-face {
+ font-family: "JEMROKtrial-Regular";
+ src: url("/static/wrapped/fonts/1/JEMROKtrial-Regular.ttf");
+}
+body {
+ font-family: "JEMROKtrial-Regular", sans-serif;
+ background: url("/static/wrapped/img/1/bg.png");
+ color: white;
+ text-align: center;
+ padding: 50px;
+}
+#name {
+ font-size: 2em;
+ font-weight: bold;
+ text-shadow: 2px 2px 15px var(--accent-secondary);
+}
+.wrap-container {
+ max-width: 500px;
+ margin: auto;
+ padding: 20px;
+ background: rgba(0, 0, 0, 0.8);
+ border-radius: 10px;
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
+}
+.category {
+ display: flex;
+ justify-content: space-between;
+ padding: 10px;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 5px;
+ margin: 10px 0;
+ padding: 10px;
+}
+h1 {
+ font-size: 2.5em;
+ font-weight: bold;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
+ -webkit-background-clip: text;
+}
+.list {
+ list-style: none;
+ padding: 0;
+}
+.list li {
+ display: flex;
+ justify-content: space-between;
+ background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
+ margin: 10px 0;
+ padding: 10px;
+ border-radius: 5px;
+ font-weight: normal;
+}
+.ranking-bar {
+ width: 100%;
+ height: 20px;
+ background: rgba(255, 255, 255, 0.2);
+ border-radius: 10px;
+ overflow: hidden;
+ margin-top: 10px;
+ position: relative;
+}
+.ranking-progress {
+ height: 100%;
+ width: 0%;
+ background: linear-gradient(90deg, var(--accent-primary), var(--accent-secondary));
+ border-radius: 10px;
+}
diff --git a/apps/wrapped/static/wrapped/favicon/1/android-chrome-192x192.png b/apps/wrapped/static/wrapped/favicon/1/android-chrome-192x192.png
new file mode 100644
index 00000000..40f01b34
Binary files /dev/null and b/apps/wrapped/static/wrapped/favicon/1/android-chrome-192x192.png differ
diff --git a/apps/wrapped/static/wrapped/favicon/1/android-chrome-512x512.png b/apps/wrapped/static/wrapped/favicon/1/android-chrome-512x512.png
new file mode 100644
index 00000000..6bd8616e
Binary files /dev/null and b/apps/wrapped/static/wrapped/favicon/1/android-chrome-512x512.png differ
diff --git a/apps/wrapped/static/wrapped/favicon/1/apple-touch-icon.png b/apps/wrapped/static/wrapped/favicon/1/apple-touch-icon.png
new file mode 100644
index 00000000..ccd657d9
Binary files /dev/null and b/apps/wrapped/static/wrapped/favicon/1/apple-touch-icon.png differ
diff --git a/apps/wrapped/static/wrapped/favicon/1/browserconfig.xml b/apps/wrapped/static/wrapped/favicon/1/browserconfig.xml
new file mode 100644
index 00000000..eb5c9b5e
--- /dev/null
+++ b/apps/wrapped/static/wrapped/favicon/1/browserconfig.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+ #00a300
+
+
+
diff --git a/apps/wrapped/static/wrapped/favicon/1/favicon-16x16.png b/apps/wrapped/static/wrapped/favicon/1/favicon-16x16.png
new file mode 100644
index 00000000..b7a7c124
Binary files /dev/null and b/apps/wrapped/static/wrapped/favicon/1/favicon-16x16.png differ
diff --git a/apps/wrapped/static/wrapped/favicon/1/favicon-32x32.png b/apps/wrapped/static/wrapped/favicon/1/favicon-32x32.png
new file mode 100644
index 00000000..4f3f95f0
Binary files /dev/null and b/apps/wrapped/static/wrapped/favicon/1/favicon-32x32.png differ
diff --git a/apps/wrapped/static/wrapped/favicon/1/favicon.ico b/apps/wrapped/static/wrapped/favicon/1/favicon.ico
new file mode 100644
index 00000000..cbc82dde
Binary files /dev/null and b/apps/wrapped/static/wrapped/favicon/1/favicon.ico differ
diff --git a/apps/wrapped/static/wrapped/favicon/1/mstile-150x150.png b/apps/wrapped/static/wrapped/favicon/1/mstile-150x150.png
new file mode 100644
index 00000000..7cdfc755
Binary files /dev/null and b/apps/wrapped/static/wrapped/favicon/1/mstile-150x150.png differ
diff --git a/apps/wrapped/static/wrapped/favicon/1/safari-pinned-tab.svg b/apps/wrapped/static/wrapped/favicon/1/safari-pinned-tab.svg
new file mode 100644
index 00000000..43a9370f
--- /dev/null
+++ b/apps/wrapped/static/wrapped/favicon/1/safari-pinned-tab.svg
@@ -0,0 +1,503 @@
+
+
+
diff --git a/apps/wrapped/static/wrapped/favicon/1/site.webmanifest b/apps/wrapped/static/wrapped/favicon/1/site.webmanifest
new file mode 100644
index 00000000..79d235df
--- /dev/null
+++ b/apps/wrapped/static/wrapped/favicon/1/site.webmanifest
@@ -0,0 +1,19 @@
+{
+ "name": "",
+ "short_name": "",
+ "icons": [
+ {
+ "src": "/static/favicon/1/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/static/favicon/1/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
diff --git a/apps/wrapped/static/wrapped/fonts/1/JEMROKtrial-Regular.ttf b/apps/wrapped/static/wrapped/fonts/1/JEMROKtrial-Regular.ttf
new file mode 100644
index 00000000..ef731e1a
Binary files /dev/null and b/apps/wrapped/static/wrapped/fonts/1/JEMROKtrial-Regular.ttf differ
diff --git a/apps/wrapped/static/wrapped/img/1/bg.png b/apps/wrapped/static/wrapped/img/1/bg.png
new file mode 100644
index 00000000..9e78673c
Binary files /dev/null and b/apps/wrapped/static/wrapped/img/1/bg.png differ
diff --git a/apps/wrapped/tables.py b/apps/wrapped/tables.py
new file mode 100644
index 00000000..6ec37543
--- /dev/null
+++ b/apps/wrapped/tables.py
@@ -0,0 +1,87 @@
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from django.utils.html import format_html
+from django.utils.translation import gettext_lazy as _
+from note_kfet.middlewares import get_current_request
+import django_tables2 as tables
+from django_tables2 import A
+from permission.backends import PermissionBackend
+
+from .models import Wrapped
+
+
+class WrappedTable(tables.Table):
+ """
+ List all wrapped
+ """
+ class Meta:
+ attrs = {
+ 'class': 'table table-condensed table-striped table-hover',
+ 'id': 'wrapped_table'
+ }
+ row_attrs = {
+ 'class': lambda record: 'bg-danger' if not record.generated else '',
+ }
+ model = Wrapped
+ template_name = 'django_tables2/bootstrap4.html'
+ fields = ('note', 'bde', 'public', )
+
+ view = tables.LinkColumn(
+ 'wrapped:wrapped_detail',
+ args=[A('pk')],
+ attrs={
+ 'td': {'class': 'col-sm-2'},
+ 'a': {
+ 'class': 'btn btn-sm btn-primary',
+ 'data-turbolinks': 'false',
+ }
+ },
+ text=_('view the wrapped'),
+ accessor='pk',
+ verbose_name=_('View'),
+ orderable=False,
+ )
+
+ public = tables.Column(
+ accessor="pk",
+ orderable=False,
+ attrs={
+ "td": {
+ "id": lambda record: "makepublic_" + str(record.pk),
+ "class": 'col-sm-1',
+ "data-toggle": "tooltip",
+ "title": lambda record:
+ (_("Click to make this wrapped private") if record.public else
+ _("Click to make this wrapped public")) if PermissionBackend.check_perm(
+ get_current_request(), "wrapped.change_wrapped_public", record) else None,
+ "onclick": lambda record:
+ 'makepublic(' + str(record.id) + ', ' + str(not record.public).lower() + ')'
+ if PermissionBackend.check_perm(get_current_request(), "wrapped.change_wrapped_public",
+ record) else None
+ }
+ },
+ )
+
+ share = tables.Column(
+ verbose_name=_("Share"),
+ accessor="pk",
+ orderable=False,
+ attrs={
+ "td": {
+ "class": 'col-sm-2',
+ "title": _("Click to copy the link in the press paper"),
+ }
+ },
+ )
+
+ def render_share(self, value, record):
+ val = ''
+ val += _('Copy link')
+ val += ''
+ return format_html(val)
+
+ def render_public(self, value, record):
+ val = "✔" if record.public else "✖"
+ return val
diff --git a/apps/wrapped/templates/wrapped/1/wrapped_base.html b/apps/wrapped/templates/wrapped/1/wrapped_base.html
new file mode 100644
index 00000000..3cd72a99
--- /dev/null
+++ b/apps/wrapped/templates/wrapped/1/wrapped_base.html
@@ -0,0 +1,82 @@
+{% load static i18n pretty_money getenv %}
+{% comment %}
+Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
+SPDX-License-Identifier: GPL-3.0-or-later
+{% endcomment %}
+
+{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
+
+
+
+
+
+
+ {% block title %}{{ title }}{% endblock title %} - {{ request.site.name }}
+
+
+
+ {# Favicon #}
+
+
+
+
+
+
+
+
+
+
+ {# Bootstrap, Font Awesome and custom CSS #}
+
+
+
+
+ {# JQuery, Bootstrap and Turbolinks JavaScript #}
+
+
+
+
+
+
+
+ {# Translation in javascript files #}
+
+
+ {# If extra ressources are needed for a form, load here #}
+ {% if form.media %}
+ {{ form.media }}
+ {% endif %}
+
+ {% block extracss %}{% endblock %}
+
+
+ {% block content %}
+ Default content...
+ {% endblock %}
+
+
+
{% trans "The NoteKfet this year it's also" %}
+
+ - {{ glob_nb_transaction }} {% trans " transactions" %}
+ - {{ glob_nb_soiree }} {% trans " parties" %}
+ - {{ glob_nb_entree_pot }} {% trans " Pot entries" %}
+
+ - {{ glob_nb_vieux_con }} {% trans " old dickhead behind the bar" %}
+
+
+
+
+{% block extrajavascript %}{% endblock %}
+
+
diff --git a/apps/wrapped/templates/wrapped/1/wrapped_view_club.html b/apps/wrapped/templates/wrapped/1/wrapped_view_club.html
new file mode 100644
index 00000000..57da4a3f
--- /dev/null
+++ b/apps/wrapped/templates/wrapped/1/wrapped_view_club.html
@@ -0,0 +1,31 @@
+{% extends "wrapped/1/wrapped_base.html" %}
+{% comment %}
+COPYRIGHT (C) 2018-2024 BDE ENS Paris-Saclay
+SPDX-License-Identifier: GPL-3.0-or-later
+{% endcomment %}
+{% load i18n pretty_money %}
+{% block content %}
+
+
{% trans "NoteKfet Wrapped" %}
+
{{ wrapped.note.club.name }}
+ {% trans "Your best consumer:" %}
+
+ {% trans "Your worst creditor:" %}
+
+
+ - {{ nb_soiree_orga }} {% trans "party·ies organised" %}
+ - {{ nb_member }} {% trans "distinct members" %}
+
+
+
+{% endblock %}
diff --git a/apps/wrapped/templates/wrapped/1/wrapped_view_user.html b/apps/wrapped/templates/wrapped/1/wrapped_view_user.html
new file mode 100644
index 00000000..3e421171
--- /dev/null
+++ b/apps/wrapped/templates/wrapped/1/wrapped_view_user.html
@@ -0,0 +1,69 @@
+{% extends "wrapped/1/wrapped_base.html" %}
+{% comment %}
+COPYRIGHT (C) 2018-2024 BDE ENS Paris-Saclay
+SPDX-License-Identifier: GPL-3.0-or-later
+{% endcomment %}
+{% load i18n pretty_money %}
+{% block content %}
+
+
{% trans "NoteKfet Wrapped" %}
+
{{ wrapped.note.user.username }}
+ {% if wei %}
+
+ {% trans "You participate to the wei: " %} {{ wei }} {% trans "in the" %} {{ bus }}
+
+ {% endif %}
+
+
+ {{ nb_pot_entry }}/{{ nb_pots }} {% trans "pots !" %}
+
+
+
+ {% if first_conso %}
+
+ - {% trans "Your first conso of the year: " %} {{ first_conso }}
+ - {% trans "Your prefered consumtion category: " %} {{ top_category }}
+
+
+ {% endif %}
+
+ {{ nb_rechargement }} {% trans ": it's the number of time your reload your note" %}
+
+ {% if class_conso_all > 0 %}
+ {% trans "Your overall expenses: " %}
+
+
+ {{ class_conso_all }}/{{ class_part_all }} {% trans "with" %} {{ amount_conso_all }}€
+
+
+
+ {% endif %}
+
+ {% if class_conso_bde > 0 %}
+ {% trans "Your expenses to BDE: " %}
+
+
+ {{ class_conso_bde }}/{{ class_part_bde }} {% trans "with" %} {{ amount_conso_bde }}€
+
+
+
+ {% endif %}
+
+{% endblock %}
diff --git a/apps/wrapped/templates/wrapped/wrapped_list.html b/apps/wrapped/templates/wrapped/wrapped_list.html
new file mode 100644
index 00000000..28892de5
--- /dev/null
+++ b/apps/wrapped/templates/wrapped/wrapped_list.html
@@ -0,0 +1,69 @@
+{% extends "base.html" %}
+{% comment %}
+SPDX-License-Identifier: GPL-3.0-or-later
+{% endcomment %}
+{% load render_table from django_tables2 %}
+{% load i18n %}
+
+{% block content %}
+
+
+
+
+
+ {% render_table table %}
+
+
+
+
+{% endblock %}
+
+{% block extrajavascript %}
+
+{% endblock %}
diff --git a/apps/wrapped/urls.py b/apps/wrapped/urls.py
new file mode 100644
index 00000000..dde4458b
--- /dev/null
+++ b/apps/wrapped/urls.py
@@ -0,0 +1,13 @@
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from django.urls import path
+
+from . import views
+
+app_name = 'wrapped'
+
+urlpatterns = [
+ path('', views.WrappedListView.as_view(), name='wrapped_list'),
+ path('/', views.WrappedDetailView.as_view(), name='wrapped_detail'),
+]
diff --git a/apps/wrapped/views.py b/apps/wrapped/views.py
new file mode 100644
index 00000000..0a16fd92
--- /dev/null
+++ b/apps/wrapped/views.py
@@ -0,0 +1,62 @@
+# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+import json
+
+from django.contrib.auth.mixins import LoginRequiredMixin
+from django.utils.translation import gettext_lazy as _
+from django.views.generic import DetailView
+from django_tables2.views import SingleTableView
+from permission.backends import PermissionBackend
+from permission.views import ProtectQuerysetMixin
+
+from .models import Wrapped
+from .tables import WrappedTable
+
+
+class WrappedListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
+ """
+ Display all Wrapped, and classify by year
+ """
+ model = Wrapped
+ table_class = WrappedTable
+ template_name = 'wrapped/wrapped_list.html'
+ extra_context = {'title': _("List of wrapped")}
+
+ def get_queryset(self, **kwargs):
+ return super().get_queryset(**kwargs).distinct()
+
+ def get_table_data(self):
+ return Wrapped.objects.filter(PermissionBackend.filter_queryset(
+ self.request, Wrapped, "change", field='public')).distinct().order_by("-bde__date_start")
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ w = self.object_list.filter(note__noteclub__club__pk__gte=-1, public=False)
+ if w:
+ context['club_not_public'] = 'true'
+ else:
+ context['club_not_public'] = 'false'
+ return context
+
+
+class WrappedDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
+ """
+ View a wrapped
+ """
+ model = Wrapped
+ template_name = 'wrapped/0/wrapped_view.html' # by default
+
+ def get(self, *args, **kwargs):
+ bde_id = Wrapped.objects.get(pk=kwargs['pk']).bde.id
+ note_type = 'user' if 'user' in Wrapped.objects.get(pk=kwargs['pk']).note.__dir__() else 'club'
+ self.template_name = 'wrapped/' + str(bde_id) + '/wrapped_view_' + note_type + '.html'
+ return super().get(*args, **kwargs)
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ d = json.loads(self.object.data_json)
+ for key in d:
+ context[key] = d[key]
+ context['title'] = str(self.object)
+ return context
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index 2af3257e..79814474 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-08-17 11:57+0200\n"
+"POT-Creation-Date: 2025-02-25 13:47+0100\n"
"PO-Revision-Date: 2022-04-11 22:05+0200\n"
"Last-Translator: bleizi \n"
"Language-Team: French \n"
@@ -18,38 +18,43 @@ msgstr ""
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Poedit 3.0\n"
-#: apps/activity/apps.py:10 apps/activity/models.py:127
-#: apps/activity/models.py:167
-#: apps/activity/models.py:323
+#: apps/activity/api/serializers.py:77
+#, fuzzy
+#| msgid "This friendship already exists"
+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
msgid "activity"
msgstr "activité"
-#: apps/activity/forms.py:34
+#: apps/activity/forms.py:35
msgid "The note of this club is inactive."
msgstr "La note du club est inactive."
-#: apps/activity/forms.py:41 apps/activity/models.py:142
+#: apps/activity/forms.py:42 apps/activity/models.py:142
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:82 apps/activity/models.py:271
+#: apps/activity/forms.py:83 apps/activity/models.py:271
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:85 apps/activity/models.py:274
+#: apps/activity/forms.py:86 apps/activity/models.py:274
msgid "This activity is not validated yet."
msgstr "Cette activité n'est pas encore validée."
-#: apps/activity/forms.py:95 apps/activity/models.py:282
+#: apps/activity/forms.py:96 apps/activity/models.py:282
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:99 apps/activity/models.py:286
+#: apps/activity/forms.py:100 apps/activity/models.py:286
msgid "This person is already invited."
msgstr "Cette personne est déjà invitée."
-#: apps/activity/forms.py:103 apps/activity/models.py:290
+#: apps/activity/forms.py:104 apps/activity/models.py:290
msgid "You can't invite more than 3 people to this activity."
msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité."
@@ -63,7 +68,7 @@ msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité."
#: apps/registration/templates/registration/future_profile_detail.html:16
#: apps/wei/models.py:67 apps/wei/models.py:131 apps/wei/tables.py:282
#: apps/wei/templates/wei/base.html:26
-#: apps/wei/templates/wei/weimembership_form.html:14
+#: apps/wei/templates/wei/weimembership_form.html:14 apps/wrapped/models.py:16
msgid "name"
msgstr "nom"
@@ -115,7 +120,7 @@ msgstr "Lieu où l'activité est organisée, par exemple la Kfet."
msgid "type"
msgstr "type"
-#: apps/activity/models.py:91 apps/logs/models.py:22 apps/member/models.py:318
+#: apps/activity/models.py:91 apps/logs/models.py:22 apps/member/models.py:325
#: apps/note/models/notes.py:148 apps/treasury/models.py:293
#: apps/wei/models.py:171 apps/wei/templates/wei/attribute_bus_1A.html:13
#: apps/wei/templates/wei/survey.html:15
@@ -172,7 +177,7 @@ msgid "entry time"
msgstr "heure d'entrée"
#: apps/activity/models.py:180 apps/note/apps.py:14
-#: apps/note/models/notes.py:77
+#: apps/note/models/notes.py:77 apps/wrapped/models.py:60
msgid "note"
msgstr "note"
@@ -200,7 +205,7 @@ msgstr "Entrée de la note {note} pour l'activité « {activity} »"
msgid "Already entered on "
msgstr "Déjà rentré·e le "
-#: apps/activity/models.py:204 apps/activity/tables.py:56
+#: apps/activity/models.py:204 apps/activity/tables.py:58
msgid "{:%Y-%m-%d %H:%M:%S}"
msgstr "{:%d/%m/%Y %H:%M:%S}"
@@ -239,68 +244,96 @@ msgstr "invité·e·s"
msgid "Invitation"
msgstr "Invitation"
-#: apps/activity/models.py:330
-#: apps/activity/models.py:334
+#: apps/activity/models.py:330 apps/activity/models.py:334
msgid "Opener"
msgstr "Ouvreur⋅se"
#: apps/activity/models.py:335
-#: apps/activity/templates/activity_detail.html:16
+#: apps/activity/templates/activity/activity_detail.html:16
msgid "Openers"
msgstr "Ouvreur⋅ses"
-#: apps/activity/tables.py:27
+#: apps/activity/models.py:339
+#, fuzzy, python-brace-format
+#| msgid "Entry for {note} to the activity {activity}"
+msgid "{opener} is opener of activity {acivity}"
+msgstr "Entrée de la note {note} pour l'activité « {activity} »"
+
+#: apps/activity/tables.py:29
msgid "The activity is currently open."
msgstr "Cette activité est actuellement ouverte."
-#: apps/activity/tables.py:28
+#: apps/activity/tables.py:30
msgid "The validation of the activity is pending."
msgstr "La validation de cette activité est en attente."
-#: apps/activity/tables.py:43
+#: apps/activity/tables.py:45
#: apps/member/templates/member/picture_update.html:18
-#: apps/treasury/tables.py:107
+#: apps/treasury/tables.py:110
msgid "Remove"
msgstr "Supprimer"
-#: apps/activity/tables.py:56
+#: apps/activity/tables.py:58
msgid "Entered on "
msgstr "Entré·e le "
-#: apps/activity/tables.py:58
+#: apps/activity/tables.py:60
msgid "remove"
msgstr "supprimer"
-#: apps/activity/tables.py:82 apps/note/forms.py:68 apps/treasury/models.py:208
+#: apps/activity/tables.py:84 apps/note/forms.py:69 apps/treasury/models.py:208
msgid "Type"
msgstr "Type"
-#: apps/activity/tables.py:84 apps/member/forms.py:196
+#: apps/activity/tables.py:86 apps/member/forms.py:199
#: apps/registration/forms.py:91 apps/treasury/forms.py:131
-#: apps/wei/forms/registration.py:104
+#: apps/wei/forms/registration.py:110
msgid "Last name"
msgstr "Nom de famille"
-#: apps/activity/tables.py:86 apps/member/forms.py:201
+#: apps/activity/tables.py:88 apps/member/forms.py:204
#: apps/note/templates/note/transaction_form.html:138
#: apps/registration/forms.py:96 apps/treasury/forms.py:133
-#: apps/wei/forms/registration.py:109
+#: apps/wei/forms/registration.py:115
msgid "First name"
msgstr "Prénom"
-#: apps/activity/tables.py:88 apps/note/models/notes.py:86
+#: apps/activity/tables.py:90 apps/note/models/notes.py:86
msgid "Note"
msgstr "Note"
-#: apps/activity/tables.py:90 apps/member/tables.py:50
+#: apps/activity/tables.py:92 apps/member/tables.py:50
msgid "Balance"
msgstr "Solde du compte"
-#: apps/activity/templates/activity/activity_detail.html:15
+#: apps/activity/tables.py:141 apps/activity/tables.py:148
+#: apps/note/tables.py:166 apps/note/tables.py:173 apps/note/tables.py:234
+#: apps/note/tables.py:281 apps/treasury/tables.py:39
+#: apps/treasury/templates/treasury/invoice_confirm_delete.html:30
+#: apps/treasury/templates/treasury/sogecredit_detail.html:65
+#: apps/wei/tables.py:75 apps/wei/tables.py:118
+#: apps/wei/templates/wei/weiregistration_confirm_delete.html:31
+#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:18
+#: note_kfet/templates/oauth2_provider/application_detail.html:39
+#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:12
+msgid "Delete"
+msgstr "Supprimer"
+
+#: apps/activity/templates/activity/activity_detail.html:24
+#: apps/member/templates/member/club_alias.html:20
+#: apps/member/templates/member/profile_alias.html:19
+#: apps/member/templates/member/profile_trust.html:19
+#: apps/treasury/tables.py:101
+#: apps/treasury/templates/treasury/sogecredit_list.html:34
+#: apps/treasury/templates/treasury/sogecredit_list.html:73
+msgid "Add"
+msgstr "Ajouter"
+
+#: apps/activity/templates/activity/activity_detail.html:35
msgid "Guests list"
msgstr "Liste des invité·e·s"
-#: apps/activity/templates/activity/activity_detail.html:33
+#: apps/activity/templates/activity/activity_detail.html:55
msgid "Guest deleted"
msgstr "Invité·e supprimé·e"
@@ -347,7 +380,7 @@ msgstr "Entrée effectuée !"
#: apps/activity/templates/activity/activity_form.html:16
#: apps/food/templates/food/add_ingredient_form.html:16
#: apps/food/templates/food/basicfood_form.html:16
-#: apps/food/templates/food/create_qrcode_form.html:19
+#: apps/food/templates/food/create_qrcode_form.html:20
#: apps/food/templates/food/transformedfood_form.html:16
#: apps/member/templates/member/add_members.html:46
#: apps/member/templates/member/club_form.html:16
@@ -414,41 +447,41 @@ msgstr "modifier"
msgid "Invite"
msgstr "Inviter"
-#: apps/activity/views.py:37
+#: apps/activity/views.py:38
msgid "Create new activity"
msgstr "Créer une nouvelle activité"
-#: apps/activity/views.py:67 note_kfet/templates/base.html:96
+#: apps/activity/views.py:71 note_kfet/templates/base.html:96
msgid "Activities"
msgstr "Activités"
-#: apps/activity/views.py:108
+#: apps/activity/views.py:105
msgid "Activity detail"
msgstr "Détails de l'activité"
-#: apps/activity/views.py:128
+#: apps/activity/views.py:150
msgid "Update activity"
msgstr "Modifier l'activité"
-#: apps/activity/views.py:155
+#: apps/activity/views.py:177
msgid "Invite guest to the activity \"{}\""
msgstr "Invitation pour l'activité « {} »"
-#: apps/activity/views.py:193
+#: apps/activity/views.py:217
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:196
+#: apps/activity/views.py:220
msgid "This activity does not support activity entries."
msgstr "Cette activité ne requiert pas d'entrées."
-#: apps/activity/views.py:199
+#: apps/activity/views.py:223
msgid "This activity is closed."
msgstr "Cette activité est fermée."
-#: apps/activity/views.py:295
+#: apps/activity/views.py:328
msgid "Entry for activity \"{}\""
msgstr "Entrées pour l'activité « {} »"
@@ -468,7 +501,7 @@ msgstr "Entièrement utilisé"
msgid "Pasta METRO 5kg"
msgstr "Pâtes METRO 5kg"
-#: apps/food/forms.py:96
+#: apps/food/forms.py:100
msgid "Lasagna"
msgstr "Lasagnes"
@@ -554,7 +587,7 @@ msgstr "ingrédients tranformées"
msgid "shelf life"
msgstr "durée de vie"
-#: apps/food/models.py:225 apps/food/views.py:365
+#: apps/food/models.py:225 apps/food/views.py:375
msgid "Transformed food"
msgstr "Aliment transformé"
@@ -562,20 +595,20 @@ msgstr "Aliment transformé"
msgid "Transformed foods"
msgstr "Aliments transformés"
-#: apps/food/templates/food/create_qrcode_form.html:31
#: apps/food/templates/food/basicfood_detail.html:14
+#: apps/food/templates/food/create_qrcode_form.html:31
#: apps/food/templates/food/qrcode_detail.html:15
#: apps/food/templates/food/transformedfood_detail.html:14
msgid "Owner"
msgstr "Propriétaire"
-#: apps/food/templates/food/create_qrcode_form.html:34
#: apps/food/templates/food/basicfood_detail.html:15
+#: apps/food/templates/food/create_qrcode_form.html:34
msgid "Arrival date"
msgstr "Date d'arrivée"
-#: apps/food/templates/food/create_qrcode_form.html:37
#: apps/food/templates/food/basicfood_detail.html:16
+#: apps/food/templates/food/create_qrcode_form.html:37
#: apps/food/templates/food/qrcode_detail.html:16
#: apps/food/templates/food/transformedfood_detail.html:19
msgid "Expiry date"
@@ -604,7 +637,7 @@ msgstr "Modifier"
msgid "Add to a meal"
msgstr "Ajouter à un plat"
-#: apps/food/templates/food/create_qrcode_form.html:14
+#: apps/food/templates/food/create_qrcode_form.html:15
msgid "New basic food"
msgstr "Nouvel aliment basique"
@@ -612,10 +645,6 @@ msgstr "Nouvel aliment basique"
msgid "Copy constructor"
msgstr "Constructeur de copie"
-#: apps/food/templates/food/qrcode_detail.html:10
-msgid "number"
-msgstr "numéro"
-
#: apps/food/templates/food/create_qrcode_form.html:28
#: apps/food/templates/food/qrcode_detail.html:14
#: apps/note/templates/note/transaction_form.html:132
@@ -623,6 +652,10 @@ msgstr "numéro"
msgid "Name"
msgstr "Nom"
+#: apps/food/templates/food/qrcode_detail.html:10
+msgid "number"
+msgstr "numéro"
+
#: apps/food/templates/food/qrcode_detail.html:29
msgid "View details"
msgstr "Voir plus"
@@ -692,19 +725,19 @@ msgstr "Détails de:"
msgid "Add a new basic food with QRCode"
msgstr "Ajouter un nouvel ingrédient avec un QR-code"
-#: apps/food/views.py:185
+#: apps/food/views.py:194
msgid "Add a new QRCode"
msgstr "Ajouter un nouveau QR-code"
-#: apps/food/views.py:235
+#: apps/food/views.py:245
msgid "QRCode"
msgstr "QR-code"
-#: apps/food/views.py:271
+#: apps/food/views.py:281
msgid "Add a new meal"
msgstr "Ajouter un nouveau plat"
-#: apps/food/views.py:337
+#: apps/food/views.py:347
msgid "Update a meal"
msgstr "Modifier le plat"
@@ -736,7 +769,7 @@ msgstr "nouvelles données"
msgid "create"
msgstr "créer"
-#: apps/logs/models.py:65 apps/note/tables.py:230 apps/note/tables.py:277
+#: apps/logs/models.py:65 apps/note/tables.py:230 apps/note/tables.py:279
#: apps/permission/models.py:126 apps/treasury/tables.py:38
#: apps/wei/tables.py:74
msgid "delete"
@@ -777,11 +810,11 @@ msgstr "cotisation pour adhérer (normalien·ne élève)"
msgid "membership fee (unpaid students)"
msgstr "cotisation pour adhérer (normalien·ne étudiant·e)"
-#: apps/member/admin.py:65 apps/member/models.py:330
+#: apps/member/admin.py:65 apps/member/models.py:337
msgid "roles"
msgstr "rôles"
-#: apps/member/admin.py:66 apps/member/models.py:344
+#: apps/member/admin.py:66 apps/member/models.py:351
msgid "fee"
msgstr "cotisation"
@@ -789,24 +822,24 @@ msgstr "cotisation"
msgid "member"
msgstr "adhérent·e"
-#: apps/member/forms.py:24
+#: apps/member/forms.py:25
msgid "Permission mask"
msgstr "Masque de permissions"
-#: apps/member/forms.py:46
-msgid "Report frequency"
-msgstr "Fréquence des rapports (en jours)"
-
#: apps/member/forms.py:48
-msgid "Last report date"
-msgstr "Date de dernier rapport"
+msgid "Report frequency"
+msgstr "Fréquence des relevés (en jours)"
-#: apps/member/forms.py:52
+#: apps/member/forms.py:50
+msgid "Last report date"
+msgstr "Date de dernier relevé"
+
+#: apps/member/forms.py:54
msgid ""
"Anti-VSS (Violences Sexistes et Sexuelles) charter read and approved"
msgstr "Charte Anti-VSS (Violences Sexistes et Sexuelles) lue et approuvée"
-#: apps/member/forms.py:53
+#: apps/member/forms.py:55
msgid ""
"Tick after having read and accepted the anti-VSS charter "
@@ -816,65 +849,65 @@ msgstr ""
"crans.org/club-bde/Charte-anti-VSS.pdf target=_blank> disponible en pdf ici"
"a>"
-#: apps/member/forms.py:60
+#: apps/member/forms.py:62
msgid "You can't register to the note if you come from the future."
msgstr "Vous ne pouvez pas vous inscrire à la note si vous venez du futur."
-#: apps/member/forms.py:86
+#: apps/member/forms.py:89
msgid "select an image"
msgstr "choisissez une image"
-#: apps/member/forms.py:87
+#: apps/member/forms.py:90
msgid "Maximal size: 2MB"
msgstr "Taille maximale : 2 Mo"
-#: apps/member/forms.py:112
+#: apps/member/forms.py:115
msgid "This image cannot be loaded."
msgstr "Cette image ne peut pas être chargée."
-#: apps/member/forms.py:151 apps/member/views.py:102
-#: apps/registration/forms.py:33 apps/registration/views.py:276
+#: apps/member/forms.py:154 apps/member/views.py:103
+#: apps/registration/forms.py:33 apps/registration/views.py:282
msgid "An alias with a similar name already exists."
msgstr "Un alias avec un nom similaire existe déjà."
-#: apps/member/forms.py:175
+#: apps/member/forms.py:178
msgid "Inscription paid by Société Générale"
msgstr "Inscription payée par la Société générale"
-#: apps/member/forms.py:177
+#: apps/member/forms.py:180
msgid "Check this case if the Société Générale paid the inscription."
msgstr "Cochez cette case si la Société Générale a payé l'inscription."
-#: apps/member/forms.py:182 apps/registration/forms.py:78
-#: apps/wei/forms/registration.py:91
+#: apps/member/forms.py:185 apps/registration/forms.py:78
+#: apps/wei/forms/registration.py:97
msgid "Credit type"
msgstr "Type de rechargement"
-#: apps/member/forms.py:183 apps/registration/forms.py:79
-#: apps/wei/forms/registration.py:92
+#: apps/member/forms.py:186 apps/registration/forms.py:79
+#: apps/wei/forms/registration.py:98
msgid "No credit"
msgstr "Pas de rechargement"
-#: apps/member/forms.py:185
+#: apps/member/forms.py:188
msgid "You can credit the note of the user."
msgstr "Vous pouvez créditer la note de l'utilisateur⋅rice avant l'adhésion."
-#: apps/member/forms.py:189 apps/registration/forms.py:84
-#: apps/wei/forms/registration.py:97
+#: apps/member/forms.py:192 apps/registration/forms.py:84
+#: apps/wei/forms/registration.py:103
msgid "Credit amount"
msgstr "Montant à créditer"
-#: apps/member/forms.py:206 apps/note/templates/note/transaction_form.html:144
+#: apps/member/forms.py:209 apps/note/templates/note/transaction_form.html:144
#: apps/registration/forms.py:101 apps/treasury/forms.py:135
-#: apps/wei/forms/registration.py:114
+#: apps/wei/forms/registration.py:120
msgid "Bank"
msgstr "Banque"
-#: apps/member/forms.py:233
+#: apps/member/forms.py:236
msgid "User"
msgstr "Utilisateur⋅rice"
-#: apps/member/forms.py:247
+#: apps/member/forms.py:250
msgid "Roles"
msgstr "Rôles"
@@ -1007,7 +1040,7 @@ msgstr "payé⋅e"
msgid "Tells if the user receive a salary."
msgstr "Indique si l'utilisateur⋅rice perçoit un salaire."
-#: apps/member/models.py:99 apps/treasury/tables.py:143
+#: apps/member/models.py:99 apps/treasury/tables.py:149
msgid "No"
msgstr "Non"
@@ -1045,11 +1078,11 @@ msgstr ""
#: apps/member/models.py:117
msgid "report frequency (in days)"
-msgstr "fréquence des rapports (en jours)"
+msgstr "fréquence des relevés (en jours)"
#: apps/member/models.py:122
msgid "last report date"
-msgstr "date de dernier rapport"
+msgstr "date de dernier relevé"
#: apps/member/models.py:127
msgid "email confirmed"
@@ -1126,7 +1159,7 @@ msgstr ""
msgid "add to registration form"
msgstr "ajouter au formulaire d'inscription"
-#: apps/member/models.py:268 apps/member/models.py:324
+#: apps/member/models.py:268 apps/member/models.py:331
#: apps/note/models/notes.py:176
msgid "club"
msgstr "club"
@@ -1135,37 +1168,37 @@ msgstr "club"
msgid "clubs"
msgstr "clubs"
-#: apps/member/models.py:335
+#: apps/member/models.py:342
msgid "membership starts on"
msgstr "l'adhésion commence le"
-#: apps/member/models.py:339
+#: apps/member/models.py:346
msgid "membership ends on"
msgstr "l'adhésion finit le"
-#: apps/member/models.py:348 apps/note/models/transactions.py:385
+#: apps/member/models.py:355 apps/note/models/transactions.py:385
msgid "membership"
msgstr "adhésion"
-#: apps/member/models.py:349
+#: apps/member/models.py:356
msgid "memberships"
msgstr "adhésions"
-#: apps/member/models.py:353
+#: apps/member/models.py:360
#, python-brace-format
msgid "Membership of {user} for the club {club}"
msgstr "Adhésion de {user} pour le club {club}"
-#: apps/member/models.py:372
+#: apps/member/models.py:379
#, python-brace-format
msgid "The role {role} does not apply to the club {club}."
msgstr "Le rôle {role} ne s'applique pas au club {club}."
-#: apps/member/models.py:381 apps/member/views.py:715
+#: apps/member/models.py:388 apps/member/views.py:745
msgid "User is already a member of the club"
msgstr "L'utilisateur·rice est déjà membre du club"
-#: apps/member/models.py:393 apps/member/views.py:724
+#: apps/member/models.py:400 apps/member/views.py:754
msgid "User is not a member of the parent club"
msgstr "L'utilisateur·rice n'est pas membre du club parent"
@@ -1180,8 +1213,8 @@ msgid ""
"%(pretty_fee)s will be charged to renew automatically the membership in this/"
"these club·s."
msgstr ""
-"Cet·te utilisateur·rice n'est pas membre du/des club·s parent·s %(clubs)s. Un "
-"montant supplémentaire de %(pretty_fee)s sera débité afin de renouveler "
+"Cet·te utilisateur·rice n'est pas membre du/des club·s parent·s %(clubs)s. "
+"Un montant supplémentaire de %(pretty_fee)s sera débité afin de renouveler "
"automatiquement l'adhésion dans ce·s club·s."
#: apps/member/templates/member/add_members.html:22
@@ -1208,8 +1241,9 @@ msgid ""
"This club has parents %(clubs)s. Please make sure that the user is a member "
"of this or these club·s, otherwise the creation of this membership will fail."
msgstr ""
-"Ce club a pour parents %(clubs)s. Merci de vous assurer que l'utilisateur⋅rice "
-"est membre de ce·s club·s, sinon la création de cette adhésion va échouer."
+"Ce club a pour parents %(clubs)s. Merci de vous assurer que "
+"l'utilisateur⋅rice est membre de ce·s club·s, sinon la création de cette "
+"adhésion va échouer."
#: apps/member/templates/member/base.html:17
#: apps/registration/templates/registration/future_profile_detail.html:12
@@ -1217,7 +1251,7 @@ msgid "Account #"
msgstr "Compte n°"
#: apps/member/templates/member/base.html:48
-#: apps/member/templates/member/base.html:62 apps/member/views.py:59
+#: apps/member/templates/member/base.html:62 apps/member/views.py:60
#: apps/registration/templates/registration/future_profile_detail.html:48
#: apps/wei/templates/wei/weimembership_form.html:117
msgid "Update Profile"
@@ -1278,20 +1312,11 @@ msgstr ""
"seront à nouveau possible."
#: apps/member/templates/member/club_alias.html:10
-#: apps/member/templates/member/profile_alias.html:10 apps/member/views.py:287
-#: apps/member/views.py:520
+#: apps/member/templates/member/profile_alias.html:10 apps/member/views.py:304
+#: apps/member/views.py:545
msgid "Note aliases"
msgstr "Alias de la note"
-#: apps/member/templates/member/club_alias.html:20
-#: apps/member/templates/member/profile_alias.html:19
-#: apps/member/templates/member/profile_trust.html:19
-#: apps/treasury/tables.py:99
-#: apps/treasury/templates/treasury/sogecredit_list.html:34
-#: apps/treasury/templates/treasury/sogecredit_list.html:73
-msgid "Add"
-msgstr "Ajouter"
-
#: apps/member/templates/member/club_detail.html:13
#: apps/permission/templates/permission/all_rights.html:32
msgid "Club managers"
@@ -1480,51 +1505,51 @@ msgstr "Sauvegarder les changements"
msgid "Registrations"
msgstr "Inscriptions"
-#: apps/member/views.py:72 apps/registration/forms.py:23
+#: apps/member/views.py:73 apps/registration/forms.py:23
msgid "This address must be valid."
msgstr "Cette adresse doit être valide."
-#: apps/member/views.py:139
+#: apps/member/views.py:140
msgid "Profile detail"
msgstr "Détails de l'utilisateur⋅rice"
-#: apps/member/views.py:205
+#: apps/member/views.py:206
msgid "Search user"
msgstr "Chercher un·e utilisateur·rice"
-#: apps/member/views.py:253
+#: apps/member/views.py:258
msgid "Note friendships"
msgstr "Amitiés note"
-#: apps/member/views.py:308
+#: apps/member/views.py:328
msgid "Update note picture"
msgstr "Modifier la photo de la note"
-#: apps/member/views.py:357
+#: apps/member/views.py:377
msgid "Manage auth token"
msgstr "Gérer les jetons d'authentification"
-#: apps/member/views.py:384
+#: apps/member/views.py:404
msgid "Create new club"
msgstr "Créer un nouveau club"
-#: apps/member/views.py:403
+#: apps/member/views.py:423
msgid "Search club"
msgstr "Chercher un club"
-#: apps/member/views.py:436
+#: apps/member/views.py:461
msgid "Club detail"
msgstr "Détails du club"
-#: apps/member/views.py:543
+#: apps/member/views.py:573
msgid "Update club"
msgstr "Modifier le club"
-#: apps/member/views.py:577
+#: apps/member/views.py:607
msgid "Add new member to the club"
msgstr "Ajouter un·e nouvelleau membre au club"
-#: apps/member/views.py:706 apps/wei/views.py:973
+#: apps/member/views.py:736 apps/wei/views.py:991
msgid ""
"This user don't have enough money to join this club, and can't have a "
"negative balance."
@@ -1532,19 +1557,19 @@ msgstr ""
"Cet⋅te utilisateur⋅rice n'a pas assez d'argent pour rejoindre ce club et ne "
"peut pas avoir un solde négatif."
-#: apps/member/views.py:728
+#: apps/member/views.py:758
msgid "The membership must start after {:%m-%d-%Y}."
msgstr "L'adhésion doit commencer après le {:%d/%m/%Y}."
-#: apps/member/views.py:733
+#: apps/member/views.py:763
msgid "The membership must begin before {:%m-%d-%Y}."
msgstr "L'adhésion doit commencer avant le {:%d/%m/%Y}."
-#: apps/member/views.py:883
+#: apps/member/views.py:913
msgid "Manage roles of an user in the club"
msgstr "Gérer les rôles d'un⋅e utilisateur⋅rice dans le club"
-#: apps/member/views.py:908
+#: apps/member/views.py:938
msgid "Members of the club"
msgstr "Membres du club"
@@ -1575,35 +1600,35 @@ msgstr ""
"La transaction ne peut pas être sauvegardée puisque la note source ou la "
"note de destination n'est pas active."
-#: apps/note/forms.py:39
+#: apps/note/forms.py:40
msgid "Source"
msgstr "Source"
-#: apps/note/forms.py:53
+#: apps/note/forms.py:54
msgid "Destination"
msgstr "Destination"
-#: apps/note/forms.py:74 apps/note/templates/note/transaction_form.html:123
+#: apps/note/forms.py:75 apps/note/templates/note/transaction_form.html:123
msgid "Reason"
msgstr "Raison"
-#: apps/note/forms.py:79 apps/treasury/tables.py:136
+#: apps/note/forms.py:80 apps/treasury/tables.py:141
msgid "Valid"
msgstr "Valide"
-#: apps/note/forms.py:85
+#: apps/note/forms.py:86
msgid "Total amount greater than"
msgstr "Montant total supérieur à"
-#: apps/note/forms.py:93
+#: apps/note/forms.py:94
msgid "Total amount less than"
msgstr "Montant total inférieur à"
-#: apps/note/forms.py:99
+#: apps/note/forms.py:100
msgid "Created after"
msgstr "Créé après"
-#: apps/note/forms.py:106
+#: apps/note/forms.py:107
msgid "Created before"
msgstr "Créé avant"
@@ -1656,7 +1681,6 @@ msgstr ""
"La note est bloquée de force par le BDE et ne peut pas être débloquée par læ "
"propriétaire de la note."
-
#: apps/note/models/notes.py:78
msgid "notes"
msgstr "notes"
@@ -1708,7 +1732,9 @@ msgid "trusted"
msgstr "ami·e"
#: apps/note/models/notes.py:243
-msgid "friendship"
+#, fuzzy
+#| msgid "friendship"
+msgid "frienship"
msgstr "amitié"
#: apps/note/models/notes.py:248
@@ -1860,8 +1886,8 @@ msgstr ""
"mode de paiement et un⋅e utilisateur⋅rice ou un club"
#: apps/note/models/transactions.py:357 apps/note/models/transactions.py:360
-#: apps/note/models/transactions.py:363 apps/wei/views.py:978
-#: apps/wei/views.py:982
+#: apps/note/models/transactions.py:363 apps/wei/views.py:996
+#: apps/wei/views.py:1000
msgid "This field is required."
msgstr "Ce champ est requis."
@@ -1885,18 +1911,6 @@ msgstr "Cliquez pour valider"
msgid "No reason specified"
msgstr "Pas de motif spécifié"
-#: apps/note/tables.py:166 apps/note/tables.py:173 apps/note/tables.py:234
-#: apps/note/tables.py:279 apps/treasury/tables.py:39
-#: apps/treasury/templates/treasury/invoice_confirm_delete.html:30
-#: apps/treasury/templates/treasury/sogecredit_detail.html:65
-#: apps/wei/tables.py:75 apps/wei/tables.py:118
-#: apps/wei/templates/wei/weiregistration_confirm_delete.html:31
-#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:18
-#: note_kfet/templates/oauth2_provider/application_detail.html:39
-#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:12
-msgid "Delete"
-msgstr "Supprimer"
-
#: apps/note/tables.py:191
msgid "Trust back"
msgstr "Ajouter en ami·e"
@@ -1915,7 +1929,7 @@ msgstr "Ajouter"
msgid "Edit"
msgstr "Éditer"
-#: apps/note/tables.py:266 apps/note/tables.py:293
+#: apps/note/tables.py:267 apps/note/tables.py:296
msgid "Hide/Show"
msgstr "Afficher/Masquer"
@@ -1975,6 +1989,10 @@ 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"
@@ -1997,8 +2015,8 @@ msgid "Action"
msgstr "Action"
#: apps/note/templates/note/transaction_form.html:116
-#: apps/treasury/forms.py:137 apps/treasury/tables.py:67
-#: apps/treasury/tables.py:132
+#: apps/treasury/forms.py:137 apps/treasury/tables.py:68
+#: apps/treasury/tables.py:136
#: apps/treasury/templates/treasury/remittance_form.html:23
msgid "Amount"
msgstr "Montant"
@@ -2040,15 +2058,21 @@ msgid "New button"
msgstr "Nouveau bouton"
#: apps/note/templates/note/transactiontemplate_list.html:22
-msgid "buttons listing"
+#, fuzzy
+#| msgid "buttons listing"
+msgid "buttons listing "
msgstr "liste des boutons"
#: apps/note/templates/note/transactiontemplate_list.html:73
-msgid "button successfully deleted"
+#, fuzzy
+#| msgid "button successfully deleted"
+msgid "button successfully deleted "
msgstr "le bouton a bien été supprimé"
#: apps/note/templates/note/transactiontemplate_list.html:77
-msgid "Unable to delete button"
+#, fuzzy
+#| msgid "Unable to delete button"
+msgid "Unable to delete button "
msgstr "Impossible de supprimer le bouton"
#: apps/note/templates/note/transactiontemplate_list.html:95
@@ -2060,34 +2084,35 @@ msgid "Button displayed"
msgstr "Bouton affiché"
#: apps/note/templates/note/transactiontemplate_list.html:100
+#: apps/wrapped/templates/wrapped/wrapped_list.html:63
msgid "An error occured"
msgstr "Une erreur s'est produite"
-#: apps/note/views.py:36
+#: apps/note/views.py:37
msgid "Transfer money"
msgstr "Transférer de l'argent"
-#: apps/note/views.py:74
+#: apps/note/views.py:75
msgid "Create new button"
msgstr "Créer un nouveau bouton"
-#: apps/note/views.py:83
+#: apps/note/views.py:84
msgid "Search button"
msgstr "Chercher un bouton"
-#: apps/note/views.py:111
+#: apps/note/views.py:116
msgid "Update button"
msgstr "Modifier le bouton"
-#: apps/note/views.py:151 note_kfet/templates/base.html:66
+#: apps/note/views.py:156 note_kfet/templates/base.html:66
msgid "Consumptions"
msgstr "Consommations"
-#: apps/note/views.py:165
+#: apps/note/views.py:170
msgid "You can't see any button."
msgstr "Vous ne pouvez pas voir le moindre bouton."
-#: apps/note/views.py:204
+#: apps/note/views.py:209
msgid "Search transactions"
msgstr "Rechercher des transactions"
@@ -2181,7 +2206,7 @@ msgstr ""
"Vous n'avez pas la permission de modifier le champ {field} sur l'instance du "
"modèle {app_label}.{model_name}."
-#: apps/permission/signals.py:83 apps/permission/views.py:105
+#: apps/permission/signals.py:83 apps/permission/views.py:104
#, python-brace-format
msgid ""
"You don't have the permission to add an instance of model {app_label}."
@@ -2206,7 +2231,8 @@ msgstr "Liste des utilisateur·rice·s ayant des droits surnormaux"
#: apps/permission/templates/permission/all_rights.html:16
msgid "Superusers have all rights on everything, to manage the website."
msgstr ""
-"Les super-utilisateur·rice·s ont tous les droits sur tout, afin de gérer le site."
+"Les super-utilisateur·rice·s ont tous les droits sur tout, afin de gérer le "
+"site."
#: apps/permission/templates/permission/all_rights.html:21
msgid "Superusers"
@@ -2259,7 +2285,7 @@ msgstr "Cliquez ici"
msgid "if you want to register a new one"
msgstr "si vous voulez en enregistrer une nouvelle"
-#: apps/permission/views.py:72
+#: apps/permission/views.py:71
#, python-brace-format
msgid ""
"You don't have the permission to update this instance of the model "
@@ -2268,7 +2294,7 @@ msgstr ""
"Vous n'avez pas la permission de modifier cette instance du modèle « {model} "
"» avec ces paramètres. Merci de les corriger et de réessayer."
-#: apps/permission/views.py:76
+#: apps/permission/views.py:75
#, python-brace-format
msgid ""
"You don't have the permission to create an instance of the model \"{model}\" "
@@ -2277,11 +2303,11 @@ msgstr ""
"Vous n'avez pas la permission d'ajouter une instance du modèle « {model} » "
"avec ces paramètres. Merci de les corriger et de réessayer."
-#: apps/permission/views.py:112 note_kfet/templates/base.html:114
+#: apps/permission/views.py:111 note_kfet/templates/base.html:120
msgid "Rights"
msgstr "Droits"
-#: apps/permission/views.py:117
+#: apps/permission/views.py:137
msgid "All rights"
msgstr "Tous les droits"
@@ -2365,7 +2391,8 @@ msgstr "Valider le compte"
#: apps/registration/templates/registration/future_profile_detail.html:63
msgid ""
"The user declared that he/she opened a bank account in the Société générale."
-msgstr "L'utilisateur·rice a déclaré avoir ouvert un compte à la société générale."
+msgstr ""
+"L'utilisateur·rice a déclaré avoir ouvert un compte à la société générale."
#: apps/registration/templates/registration/future_profile_detail.html:73
#: apps/wei/templates/wei/weimembership_form.html:127
@@ -2417,59 +2444,67 @@ msgstr "Merci"
msgid "The Note Kfet team."
msgstr "L'équipe de la Note Kfet."
-#: apps/registration/views.py:42
+#: apps/registration/views.py:43
msgid "Register new user"
msgstr "Enregistrer un⋅e nouvel⋅le utilisateur⋅rice"
-#: apps/registration/views.py:100
+#: apps/registration/views.py:101
msgid "Email validation"
msgstr "Validation de l'adresse e-mail"
-#: apps/registration/views.py:102
+#: apps/registration/views.py:103
msgid "Validate email"
msgstr "Valider l'adresse e-mail"
-#: apps/registration/views.py:146
+#: apps/registration/views.py:147
msgid "Email validation unsuccessful"
msgstr "La validation de l'adresse e-mail a échoué"
-#: apps/registration/views.py:157
+#: apps/registration/views.py:158
msgid "Email validation email sent"
msgstr "L'e-mail de vérification de l'adresse e-mail a bien été envoyé"
-#: apps/registration/views.py:165
+#: apps/registration/views.py:166
msgid "Resend email validation link"
msgstr "Renvoyer le lien de validation"
-#: apps/registration/views.py:183
+#: apps/registration/views.py:184
msgid "Pre-registered users list"
msgstr "Liste des utilisateur⋅rices en attente d'inscription"
-#: apps/registration/views.py:207
+#: apps/registration/views.py:213
msgid "Unregistered users"
msgstr "Utilisateur·rices en attente d'inscription"
-#: apps/registration/views.py:220
+#: apps/registration/views.py:226
msgid "Registration detail"
msgstr "Détails de l'inscription"
-#: apps/registration/views.py:256
+#: apps/registration/views.py:262
#, python-format
msgid "Join %(club)s Club"
msgstr "Adhérer au club %(club)s"
-#: apps/registration/views.py:299
-msgid "You must join the BDE."
+#: apps/registration/views.py:305
+#, fuzzy
+#| msgid "You must join the BDE."
+msgid "You must join a club."
msgstr "Vous devez adhérer au BDE."
-#: apps/registration/views.py:330
+#: apps/registration/views.py:309
+#, fuzzy
+#| msgid "You must join the BDE."
+msgid "You must also join the parent club BDE."
+msgstr "Vous devez adhérer au BDE."
+
+#: apps/registration/views.py:340
msgid ""
"The entered amount is not enough for the memberships, should be at least {}"
msgstr ""
"Le montant crédité est trop faible pour adhérer, il doit être au minimum de "
"{}"
-#: apps/registration/views.py:425
+#: apps/registration/views.py:435
msgid "Invalidate pre-registration"
msgstr "Invalider l'inscription"
@@ -2491,7 +2526,7 @@ msgid "You can't change the type of the remittance."
msgstr "Vous ne pouvez pas changer le type de la remise."
#: apps/treasury/forms.py:125 apps/treasury/models.py:275
-#: apps/treasury/tables.py:97 apps/treasury/tables.py:105
+#: apps/treasury/tables.py:99 apps/treasury/tables.py:108
#: apps/treasury/templates/treasury/invoice_list.html:16
#: apps/treasury/templates/treasury/remittance_list.html:16
#: apps/treasury/templates/treasury/sogecredit_list.html:17
@@ -2506,7 +2541,8 @@ msgstr "Pas de remise associée"
msgid "Invoice identifier"
msgstr "Numéro de facture"
-#: apps/treasury/models.py:42
+#: apps/treasury/models.py:42 apps/wrapped/models.py:28
+#: apps/wrapped/models.py:29
msgid "BDE"
msgstr "BDE"
@@ -2642,8 +2678,9 @@ msgid ""
"This user doesn't have enough money to pay the memberships with its note. "
"Please ask her/him to credit the note before invalidating this credit."
msgstr ""
-"Cet·te utilisateur·rice n'a pas assez d'argent pour payer les adhésions avec sa "
-"note. Merci de lui demander de recharger sa note avant d'invalider ce crédit."
+"Cet·te utilisateur·rice n'a pas assez d'argent pour payer les adhésions avec "
+"sa note. Merci de lui demander de recharger sa note avant d'invalider ce "
+"crédit."
#: apps/treasury/tables.py:20
msgid "Invoice #{:d}"
@@ -2656,25 +2693,26 @@ msgstr "Facture n°{:d}"
msgid "Invoice"
msgstr "Facture"
-#: apps/treasury/tables.py:65
+#: apps/treasury/tables.py:66
msgid "Transaction count"
msgstr "Nombre de transactions"
-#: apps/treasury/tables.py:70 apps/treasury/tables.py:72
+#: apps/treasury/tables.py:71 apps/treasury/tables.py:73
+#: apps/wrapped/tables.py:42
msgid "View"
msgstr "Voir"
-#: apps/treasury/tables.py:143
+#: apps/treasury/tables.py:149
msgid "Yes"
msgstr "Oui"
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:10
-#: apps/treasury/views.py:173
+#: apps/treasury/views.py:174
msgid "Delete invoice"
msgstr "Supprimer la facture"
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:15
-#: apps/treasury/views.py:177
+#: apps/treasury/views.py:178
msgid "This invoice is locked and can't be deleted."
msgstr "Cette facture est verrouillée et ne peut pas être supprimée."
@@ -2794,8 +2832,8 @@ msgid ""
"If this credit is validated, then the user won't be able to ask for a credit "
"from the Société générale."
msgstr ""
-"Si ce crédit est validé, alors l'utilisateur·rice ne pourra plus demander d'être "
-"crédité·e par la Société générale à l'avenir."
+"Si ce crédit est validé, alors l'utilisateur·rice ne pourra plus demander "
+"d'être crédité·e par la Société générale à l'avenir."
#: apps/treasury/templates/treasury/sogecredit_detail.html:44
msgid "If you think there is an error, please contact the \"respos info\"."
@@ -2811,14 +2849,14 @@ msgid ""
"Warning: if you don't validate this credit, the note of the user doesn't "
"have enough money to pay its memberships."
msgstr ""
-"Attention : si vous ne validez pas ce crédit, la note de l'utilisateur·rice n'a "
-"pas assez d'argent pour payer les adhésions."
+"Attention : si vous ne validez pas ce crédit, la note de l'utilisateur·rice "
+"n'a pas assez d'argent pour payer les adhésions."
#: apps/treasury/templates/treasury/sogecredit_detail.html:56
msgid "Please ask the user to credit its note before deleting this credit."
msgstr ""
-"Merci de demander à l'utilisateur·rice de recharger sa note avant de supprimer la "
-"demande de crédit."
+"Merci de demander à l'utilisateur·rice de recharger sa note avant de "
+"supprimer la demande de crédit."
#: apps/treasury/templates/treasury/sogecredit_detail.html:63
#: apps/wei/tables.py:60 apps/wei/tables.py:102
@@ -2836,8 +2874,8 @@ msgstr "Filtrer avec uniquement les crédits non valides"
#: apps/treasury/templates/treasury/sogecredit_list.html:50
msgid "There is no matched user that have asked for a Société générale credit."
msgstr ""
-"Il n'y a pas d'utilisateur·rice trouvé·e ayant demandé un crédit de la Société "
-"générale."
+"Il n'y a pas d'utilisateur·rice trouvé·e ayant demandé un crédit de la "
+"Société générale."
#: apps/treasury/templates/treasury/sogecredit_list.html:63
msgid "Add credit from the Société générale"
@@ -2847,44 +2885,44 @@ msgstr "Ajouter un crédit de la Société générale"
msgid "Credit successfully registered"
msgstr "Le crédit a bien été enregistré"
-#: apps/treasury/views.py:40
+#: apps/treasury/views.py:41
msgid "Create new invoice"
msgstr "Créer une nouvelle facture"
-#: apps/treasury/views.py:97
+#: apps/treasury/views.py:98
msgid "Invoices list"
msgstr "Liste des factures"
-#: apps/treasury/views.py:105 apps/treasury/views.py:275
-#: apps/treasury/views.py:401
+#: apps/treasury/views.py:106 apps/treasury/views.py:281
+#: apps/treasury/views.py:394
msgid "You are not able to see the treasury interface."
msgstr "Vous n'êtes pas autorisé·e à voir l'interface de trésorerie."
-#: apps/treasury/views.py:115
+#: apps/treasury/views.py:116
msgid "Update an invoice"
msgstr "Modifier la facture"
-#: apps/treasury/views.py:240
+#: apps/treasury/views.py:241
msgid "Create a new remittance"
msgstr "Créer une nouvelle remise"
-#: apps/treasury/views.py:267
+#: apps/treasury/views.py:265
msgid "Remittances list"
msgstr "Liste des remises"
-#: apps/treasury/views.py:326
+#: apps/treasury/views.py:320
msgid "Update a remittance"
msgstr "Modifier la remise"
-#: apps/treasury/views.py:349
+#: apps/treasury/views.py:342
msgid "Attach a transaction to a remittance"
msgstr "Joindre une transaction à une remise"
-#: apps/treasury/views.py:393
+#: apps/treasury/views.py:386
msgid "List of credits from the Société générale"
msgstr "Liste des crédits de la Société générale"
-#: apps/treasury/views.py:438
+#: apps/treasury/views.py:436
msgid "Manage credits from the Société générale"
msgstr "Gérer les crédits de la Société générale"
@@ -2894,31 +2932,31 @@ msgstr "Gérer les crédits de la Société générale"
msgid "WEI"
msgstr "WEI"
-#: apps/wei/forms/registration.py:35
+#: apps/wei/forms/registration.py:36
msgid "The selected user is not validated. Please validate its account first"
msgstr ""
-"L'utilisateur·rice sélectionné·e n'est pas validé·e. Merci de d'abord valider son "
-"compte"
+"L'utilisateur·rice sélectionné·e n'est pas validé·e. Merci de d'abord "
+"valider son compte"
-#: apps/wei/forms/registration.py:59 apps/wei/models.py:126
+#: apps/wei/forms/registration.py:60 apps/wei/models.py:126
#: apps/wei/models.py:324
msgid "bus"
msgstr "bus"
-#: apps/wei/forms/registration.py:60
+#: apps/wei/forms/registration.py:61
msgid ""
"This choice is not definitive. The WEI organizers are free to attribute for "
"you a bus and a team, in particular if you are a free eletron."
msgstr ""
-"Ce choix n'est pas définitif. Les organisateur·rice·s du WEI sont libres de vous "
-"attribuer un bus et une équipe, en particulier si vous êtes un·e électron "
-"libre."
+"Ce choix n'est pas définitif. Les organisateur·rice·s du WEI sont libres de "
+"vous attribuer un bus et une équipe, en particulier si vous êtes un·e "
+"électron libre."
-#: apps/wei/forms/registration.py:67
+#: apps/wei/forms/registration.py:68
msgid "Team"
msgstr "Équipe"
-#: apps/wei/forms/registration.py:69
+#: apps/wei/forms/registration.py:70
msgid ""
"Leave this field empty if you won't be in a team (staff, bus chief, free "
"electron)"
@@ -2926,16 +2964,20 @@ msgstr ""
"Laissez ce champ vide si vous ne serez pas dans une équipe (staff, chef de "
"bus ou électron libre)"
-#: apps/wei/forms/registration.py:75 apps/wei/forms/registration.py:85
+#: apps/wei/forms/registration.py:76 apps/wei/forms/registration.py:91
#: apps/wei/models.py:160
msgid "WEI Roles"
msgstr "Rôles au WEI"
-#: apps/wei/forms/registration.py:76
+#: apps/wei/forms/registration.py:77
msgid "Select the roles that you are interested in."
msgstr "Sélectionnez les rôles qui vous intéressent."
-#: apps/wei/forms/registration.py:122
+#: apps/wei/forms/registration.py:86 apps/wei/models.py:188
+msgid "Caution check given"
+msgstr "Chèque de caution donné"
+
+#: apps/wei/forms/registration.py:128
msgid "This team doesn't belong to the given bus."
msgstr "Cette équipe n'appartient pas à ce bus."
@@ -2948,10 +2990,12 @@ msgid "year"
msgstr "année"
#: apps/wei/models.py:29 apps/wei/templates/wei/base.html:30
+#: apps/wrapped/models.py:20
msgid "date start"
msgstr "début"
#: apps/wei/models.py:33 apps/wei/templates/wei/base.html:33
+#: apps/wrapped/models.py:24
msgid "date end"
msgstr "fin"
@@ -3001,11 +3045,6 @@ msgstr "Rôle au WEI"
msgid "Credit from Société générale"
msgstr "Crédit de la Société générale"
-#: apps/wei/models.py:188
-#: apps/wei/forms/registration.py:84
-msgid "Caution check given"
-msgstr "Chèque de caution donné"
-
#: apps/wei/models.py:192 apps/wei/templates/wei/weimembership_form.html:64
msgid "birth date"
msgstr "date de naissance"
@@ -3039,8 +3078,7 @@ msgstr "coupe de vêtement"
msgid "clothing size"
msgstr "taille de vêtement"
-#: apps/wei/models.py:232 apps/wei/templates/wei/attribute_bus_1A.html:28
-#: apps/wei/templates/wei/weimembership_form.html:67
+#: apps/wei/models.py:232
msgid "health issues"
msgstr "problèmes de santé"
@@ -3160,13 +3198,22 @@ msgid "Attribute first year members into buses"
msgstr "Attribuer les 1A dans les bus"
#: apps/wei/templates/wei/1A_list.html:15
-msgid "Start attribution!"
+#, fuzzy
+#| msgid "Start attribution!"
+msgid "Start attribution !"
msgstr "Démarrer l'attribution !"
#: apps/wei/templates/wei/attribute_bus_1A.html:8
msgid "Bus attribution"
msgstr "Répartition des bus"
+#: apps/wei/templates/wei/attribute_bus_1A.html:28
+#: apps/wei/templates/wei/weimembership_form.html:67
+#, fuzzy
+#| msgid "health issues"
+msgid "health issues or specific diet"
+msgstr "problèmes de santé"
+
#: apps/wei/templates/wei/attribute_bus_1A.html:31
msgid "suggested bus"
msgstr "bus suggéré"
@@ -3195,11 +3242,11 @@ msgstr "Prix du WEI (étudiant⋅es)"
msgid "WEI list"
msgstr "Liste des WEI"
-#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:528
+#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:540
msgid "Register 1A"
msgstr "Inscrire un⋅e 1A"
-#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:614
+#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:626
msgid "Register 2A+"
msgstr "Inscrire un⋅e 2A+"
@@ -3228,8 +3275,8 @@ msgstr "Télécharger au format PDF"
#: apps/wei/templates/wei/survey.html:11
#: apps/wei/templates/wei/survey_closed.html:11
-#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1028
-#: apps/wei/views.py:1083 apps/wei/views.py:1130
+#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1046
+#: apps/wei/views.py:1101 apps/wei/views.py:1148
msgid "Survey WEI"
msgstr "Questionnaire WEI"
@@ -3274,7 +3321,7 @@ msgstr "Inscriptions non validées"
msgid "Attribute buses"
msgstr "Répartition dans les bus"
-#: apps/wei/templates/wei/weiclub_list.html:14 apps/wei/views.py:79
+#: apps/wei/templates/wei/weiclub_list.html:14 apps/wei/views.py:80
msgid "Create WEI"
msgstr "Créer un WEI"
@@ -3408,70 +3455,72 @@ msgid "There is no pre-registration found with this pattern."
msgstr "Il n'y a pas de pré-inscription en attente avec cette entrée."
#: apps/wei/templates/wei/weiregistration_list.html:27
-msgid "View validated membershipis..."
+#, fuzzy
+#| msgid "View validated membershipis..."
+msgid "View validated memberships..."
msgstr "Voir les adhésions validées..."
-#: apps/wei/views.py:58
+#: apps/wei/views.py:59
msgid "Search WEI"
msgstr "Chercher un WEI"
-#: apps/wei/views.py:109
+#: apps/wei/views.py:110
msgid "WEI Detail"
msgstr "Détails du WEI"
-#: apps/wei/views.py:208
+#: apps/wei/views.py:210
msgid "View members of the WEI"
msgstr "Voir les membres du WEI"
-#: apps/wei/views.py:236
+#: apps/wei/views.py:243
msgid "Find WEI Membership"
msgstr "Trouver une adhésion au WEI"
-#: apps/wei/views.py:246
+#: apps/wei/views.py:253
msgid "View registrations to the WEI"
msgstr "Voir les inscriptions au WEI"
-#: apps/wei/views.py:270
+#: apps/wei/views.py:282
msgid "Find WEI Registration"
msgstr "Trouver une inscription au WEI"
-#: apps/wei/views.py:281
+#: apps/wei/views.py:293
msgid "Update the WEI"
msgstr "Modifier le WEI"
-#: apps/wei/views.py:302
+#: apps/wei/views.py:314
msgid "Create new bus"
msgstr "Ajouter un nouveau bus"
-#: apps/wei/views.py:340
+#: apps/wei/views.py:352
msgid "Update bus"
msgstr "Modifier le bus"
-#: apps/wei/views.py:372
+#: apps/wei/views.py:384
msgid "Manage bus"
msgstr "Gérer le bus"
-#: apps/wei/views.py:399
+#: apps/wei/views.py:411
msgid "Create new team"
msgstr "Créer une nouvelle équipe"
-#: apps/wei/views.py:439
+#: apps/wei/views.py:451
msgid "Update team"
msgstr "Modifier l'équipe"
-#: apps/wei/views.py:470
+#: apps/wei/views.py:482
msgid "Manage WEI team"
msgstr "Gérer l'équipe WEI"
-#: apps/wei/views.py:492
+#: apps/wei/views.py:504
msgid "Register first year student to the WEI"
msgstr "Inscrire un⋅e 1A au WEI"
-#: apps/wei/views.py:550 apps/wei/views.py:649
+#: apps/wei/views.py:562 apps/wei/views.py:661
msgid "This user is already registered to this WEI."
msgstr "Cette personne est déjà inscrite au WEI."
-#: apps/wei/views.py:555
+#: apps/wei/views.py:567
msgid ""
"This user can't be in her/his first year since he/she has already "
"participated to a WEI."
@@ -3479,51 +3528,214 @@ msgstr ""
"Cet⋅te utilisateur⋅rice ne peut pas être en première année puisqu'iel a déjà "
"participé à un WEI."
-#: apps/wei/views.py:578
+#: apps/wei/views.py:590
msgid "Register old student to the WEI"
msgstr "Inscrire un⋅e 2A+ au WEI"
-#: apps/wei/views.py:633 apps/wei/views.py:721
+#: apps/wei/views.py:645 apps/wei/views.py:733
msgid "You already opened an account in the Société générale."
msgstr "Vous avez déjà ouvert un compte auprès de la société générale."
-#: apps/wei/views.py:685
+#: apps/wei/views.py:697
msgid "Update WEI Registration"
msgstr "Modifier l'inscription WEI"
-#: apps/wei/views.py:795
+#: apps/wei/views.py:807
msgid "Delete WEI registration"
msgstr "Supprimer l'inscription WEI"
-#: apps/wei/views.py:806
+#: apps/wei/views.py:818
msgid "You don't have the right to delete this WEI registration."
msgstr "Vous n'avez pas la permission de supprimer cette inscription au WEI."
-#: apps/wei/views.py:824
+#: apps/wei/views.py:836
msgid "Validate WEI registration"
msgstr "Valider l'inscription WEI"
-#: apps/wei/views.py:1223
+#: apps/wei/views.py:1241
msgid "Attribute buses to first year members"
msgstr "Répartir les 1A dans les bus"
-#: apps/wei/views.py:1248
+#: apps/wei/views.py:1266
msgid "Attribute bus"
msgstr "Attribuer un bus"
-#: note_kfet/settings/base.py:173
+#: apps/wrapped/apps.py:10
+msgid "wrapped"
+msgstr "wrapped"
+
+#: apps/wrapped/models.py:40
+msgid "generated"
+msgstr "generé"
+
+#: apps/wrapped/models.py:45
+msgid "public"
+msgstr "public"
+
+#: apps/wrapped/models.py:53
+msgid "bde"
+msgstr "bde"
+
+#: apps/wrapped/models.py:65
+msgid "data json"
+msgstr "donnée json"
+
+#: apps/wrapped/models.py:66
+msgid "data in the wrapped and generated by the script generate_wrapped"
+msgstr "donnée dans le wrapped et générée par le script generate_wrapped"
+
+#: apps/wrapped/models.py:70 note_kfet/templates/base.html:114
+msgid "Wrapped"
+msgstr "Wrapped"
+
+#: apps/wrapped/models.py:71
+msgid "Wrappeds"
+msgstr "Wrappeds"
+
+#: apps/wrapped/tables.py:40
+msgid "view the wrapped"
+msgstr "voir le wrapped"
+
+#: apps/wrapped/tables.py:55
+msgid "Click to make this wrapped private"
+msgstr "Cliquer pour rendre ce wrapped privé"
+
+#: apps/wrapped/tables.py:56
+msgid "Click to make this wrapped public"
+msgstr "Cliquer pour rendre ce wrapped public"
+
+#: apps/wrapped/tables.py:67
+msgid "Share"
+msgstr "Partager"
+
+#: apps/wrapped/tables.py:73
+msgid "Click to copy the link in the press paper"
+msgstr "Cliquer pour copier le lien"
+
+#: apps/wrapped/tables.py:81
+msgid "Copy link"
+msgstr "Copier le lien"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_base.html:16
+#: note_kfet/templates/base.html:14
+msgid "The ENS Paris-Saclay BDE note."
+msgstr "La note du BDE de l'ENS Paris-Saclay."
+
+#: apps/wrapped/templates/wrapped/1/wrapped_base.html:58
+msgid "The NoteKfet this year it's also"
+msgstr "La NoteKfet cette année, c'est aussi :"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_base.html:60
+msgid " transactions"
+msgstr " transactions"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_base.html:61
+msgid " parties"
+msgstr " soirées"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_base.html:62
+msgid " Pot entries"
+msgstr " entrées au Pot"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_base.html:72
+msgid " old dickhead behind the bar"
+msgstr " vieilleux con·ne·s derrière le bar"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:9
+#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:9
+msgid "NoteKfet Wrapped"
+msgstr "NoteKfet Wrapped"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:11
+msgid "Your best consumer:"
+msgstr "Ton plus gros consommateur :"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:13
+msgid "Your worst creditor:"
+msgstr "Ton pire créancier :"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:16
+msgid "party·ies organised"
+msgstr "soirée·s organisée·s"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:17
+msgid "distinct members"
+msgstr "Membres distinct·e·s"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:13
+msgid "You participate to the wei: "
+msgstr "Tu as participé au wei : "
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:13
+msgid "in the"
+msgstr "dans le"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:18
+msgid "pots !"
+msgstr "pots !"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:27
+msgid "Your first conso of the year: "
+msgstr "Ta première conso de l'année : "
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:28
+msgid "Your prefered consumtion category: "
+msgstr "Ta catégorie de bouton préférée : "
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:41
+msgid ": it's the number of time your reload your note"
+msgstr ": c'est le nombre de fois où tu as rechargé·e ta note"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:44
+msgid "Your overall expenses: "
+msgstr "Tes dépenses totales : "
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:47
+#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:60
+msgid "with"
+msgstr "avec"
+
+#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:57
+msgid "Your expenses to BDE: "
+msgstr "Tes dépenses au BDE : "
+
+#: apps/wrapped/templates/wrapped/wrapped_list.html:26
+msgid ""
+"Do not forget to ask permission to people who are in your wrapped before to "
+"make them public"
+msgstr ""
+"N'oublies pas de demander la permission des personnes apparaissant dans un "
+"wrapped avant de le rendre public"
+
+#: apps/wrapped/templates/wrapped/wrapped_list.html:33
+msgid "Link copied"
+msgstr "Lien copié"
+
+#: apps/wrapped/templates/wrapped/wrapped_list.html:58
+msgid "Wrapped is private"
+msgstr "Le wrapped est privé"
+
+#: apps/wrapped/templates/wrapped/wrapped_list.html:59
+msgid "Wrapped is public"
+msgstr "Le wrapped est public"
+
+#: apps/wrapped/views.py:24
+msgid "List of wrapped"
+msgstr "Liste des wrapped"
+
+#: note_kfet/settings/base.py:177
msgid "German"
msgstr "Allemand"
-#: note_kfet/settings/base.py:174
+#: note_kfet/settings/base.py:178
msgid "English"
msgstr "Anglais"
-#: note_kfet/settings/base.py:175
+#: note_kfet/settings/base.py:179
msgid "Spanish"
msgstr "Espagnol"
-#: note_kfet/settings/base.py:176
+#: note_kfet/settings/base.py:180
msgid "French"
msgstr "Français"
@@ -3575,19 +3787,15 @@ msgid ""
"sent to webmasters with the detail of the error, and this will be fixed "
"soon. You can now drink a beer."
msgstr ""
-"Désolé, une erreur est survenue lors de l'analyse de votre requête. Un e-mail "
-"a été envoyé aux responsables de la plateforme avec les détails de cette "
-"erreur, qui sera corrigée rapidement. Vous pouvez désormais aller boire une "
-"bière, avec modération."
+"Désolé, une erreur est survenue lors de l'analyse de votre requête. Un e-"
+"mail a été envoyé aux responsables de la plateforme avec les détails de "
+"cette erreur, qui sera corrigée rapidement. Vous pouvez désormais aller "
+"boire une bière, avec modération."
#: note_kfet/templates/autocomplete_model.html:15
msgid "Reset"
msgstr "Réinitialiser"
-#: note_kfet/templates/base.html:14
-msgid "The ENS Paris-Saclay BDE note."
-msgstr "La note du BDE de l'ENS Paris-Saclay."
-
#: note_kfet/templates/base.html:72
msgid "Food"
msgstr "Bouffe"
@@ -3600,26 +3808,26 @@ msgstr "Utilisateur·rices"
msgid "Clubs"
msgstr "Clubs"
-#: note_kfet/templates/base.html:119
+#: note_kfet/templates/base.html:125
msgid "Admin"
msgstr "Admin"
-#: note_kfet/templates/base.html:133
+#: note_kfet/templates/base.html:139
msgid "My account"
msgstr "Mon compte"
-#: note_kfet/templates/base.html:136
+#: note_kfet/templates/base.html:142
msgid "Log out"
msgstr "Se déconnecter"
-#: note_kfet/templates/base.html:144
+#: note_kfet/templates/base.html:150
#: note_kfet/templates/registration/signup.html:6
#: note_kfet/templates/registration/signup.html:11
#: note_kfet/templates/registration/signup.html:28
msgid "Sign up"
msgstr "Inscription"
-#: note_kfet/templates/base.html:151
+#: note_kfet/templates/base.html:157
#: note_kfet/templates/registration/login.html:6
#: note_kfet/templates/registration/login.html:15
#: note_kfet/templates/registration/login.html:38
@@ -3627,7 +3835,7 @@ msgstr "Inscription"
msgid "Log in"
msgstr "Se connecter"
-#: note_kfet/templates/base.html:165
+#: note_kfet/templates/base.html:171
msgid ""
"You are not a BDE member anymore. Please renew your membership if you want "
"to use the note."
@@ -3635,7 +3843,7 @@ msgstr ""
"Vous n'êtes plus adhérent·e BDE. Merci de réadhérer si vous voulez profiter "
"de la note."
-#: note_kfet/templates/base.html:171
+#: note_kfet/templates/base.html:177
msgid ""
"Your e-mail address is not validated. Please check your mail inbox and click "
"on the validation link."
@@ -3643,7 +3851,7 @@ msgstr ""
"Votre adresse e-mail n'est pas validée. Merci de vérifier votre boîte mail "
"et de cliquer sur le lien de validation."
-#: note_kfet/templates/base.html:177
+#: note_kfet/templates/base.html:183
msgid ""
"You declared that you opened a bank account in the Société générale. The "
"bank did not validate the creation of the account to the BDE, so the "
@@ -3657,19 +3865,19 @@ msgstr ""
"vérification peut durer quelques jours. Merci de vous assurer de bien aller "
"au bout de vos démarches."
-#: note_kfet/templates/base.html:200
+#: note_kfet/templates/base.html:206
msgid "Contact us"
msgstr "Nous contacter"
-#: note_kfet/templates/base.html:202
+#: note_kfet/templates/base.html:208
msgid "Technical Support"
msgstr "Support technique"
-#: note_kfet/templates/base.html:204
+#: note_kfet/templates/base.html:210
msgid "Charte Info (FR)"
msgstr "Charte Info (FR)"
-#: note_kfet/templates/base.html:206
+#: note_kfet/templates/base.html:212
msgid "FAQ (FR)"
msgstr "FAQ (FR)"
diff --git a/locale/fr/LC_MESSAGES/djangojs.po b/locale/fr/LC_MESSAGES/djangojs.po
index 90f85fc4..59989ae6 100644
--- a/locale/fr/LC_MESSAGES/djangojs.po
+++ b/locale/fr/LC_MESSAGES/djangojs.po
@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2022-10-07 09:07+0200\n"
+"POT-Creation-Date: 2025-02-25 13:27+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -17,11 +17,11 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
-#: apps/member/static/member/js/alias.js:17
+#: apps/activity/static/activity/js/opener.js:31
msgid "Opener successfully added"
msgstr "Ouvreureuse ajouté avec succès"
-#: apps/member/static/member/js/alias.js:17
+#: apps/activity/static/activity/js/opener.js:47
msgid "Opener successfully deleted"
msgstr "Ouvreureuse supprimé avec succès"
diff --git a/note.cron b/note.cron
index dc1f6460..5509deaf 100644
--- a/note.cron
+++ b/note.cron
@@ -26,3 +26,6 @@ MAILTO=notekfet2020@lists.crans.org
00 9 * * * root cd /var/www/note_kfet && env/bin/python manage.py refresh_highlighted_buttons -v 0
# Vider les tokens Oauth2
00 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py cleartokens -v 0
+# Envoyer la liste des abonnés à la NL BDA
+ 00 10 * * 0 root cd /var/www/note_kfet && env/bin/python manage.py extract_ml_registrations -t art
+
\ No newline at end of file
diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py
index 8378448d..113cf626 100644
--- a/note_kfet/settings/base.py
+++ b/note_kfet/settings/base.py
@@ -79,6 +79,7 @@ INSTALLED_APPS = [
'scripts',
'treasury',
'wei',
+ 'wrapped',
]
MIDDLEWARE = [
diff --git a/note_kfet/templates/base.html b/note_kfet/templates/base.html
index 68cbf542..1c601c50 100644
--- a/note_kfet/templates/base.html
+++ b/note_kfet/templates/base.html
@@ -107,6 +107,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% url 'wei:current_wei_detail' as url %}
{% trans 'WEI' %}
+ {% endif %}
+ {% if "wrapped.wrapped"|model_list_length >= 1 %}
+
+ {% url 'wrapped:wrapped_list' as url %}
+ {% trans 'Wrapped' %}
+
{% endif %}
{% if request.user.is_authenticated %}
@@ -158,7 +164,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
-
+
{% if user.is_authenticated %}
{% if not user|is_member:"BDE" %}
diff --git a/note_kfet/urls.py b/note_kfet/urls.py
index b2b64dcf..3bc3da41 100644
--- a/note_kfet/urls.py
+++ b/note_kfet/urls.py
@@ -22,6 +22,7 @@ urlpatterns = [
path('treasury/', include('treasury.urls')),
path('wei/', include('wei.urls')),
path('food/',include('food.urls')),
+ path('wrapped/',include('wrapped.urls')),
# Include Django Contrib and Core routers
path('i18n/', include('django.conf.urls.i18n')),
diff --git a/tox.ini b/tox.ini
index 924ea514..1bfeb593 100644
--- a/tox.ini
+++ b/tox.ini
@@ -32,7 +32,8 @@ deps =
pep8-naming
pyflakes
commands =
- flake8 apps --extend-exclude apps/scripts
+ flake8 apps --extend-exclude apps/scripts,apps/wrapped/management/commands
+ flake8 apps/wrapped/management/commands --extend-ignore=C901
[flake8]
ignore = W503, I100, I101, B019