From 485d0930024471f9b0289884a686c7707da7f4f3 Mon Sep 17 00:00:00 2001 From: quark Date: Wed, 16 Apr 2025 17:26:00 +0200 Subject: [PATCH 001/153] here we go again (better this time) --- apps/food/admin.py | 37 -- apps/food/api/__init__.py | 0 apps/food/api/serializers.py | 50 --- apps/food/api/urls.py | 14 - apps/food/api/views.py | 61 --- apps/food/forms.py | 114 ----- apps/food/migrations/0001_initial.py | 84 ---- .../0002_transformedfood_shelf_life.py | 19 - .../0003_create_14_allergens_mandatory.py | 62 --- .../migrations/0004_auto_20240813_2358.py | 28 -- .../0005_alter_food_polymorphic_ctype.py | 20 - apps/food/migrations/__init__.py | 0 apps/food/models.py | 226 ---------- apps/food/tables.py | 19 - .../templates/food/add_ingredient_form.html | 20 - .../food/templates/food/basicfood_detail.html | 37 -- apps/food/templates/food/basicfood_form.html | 20 - .../templates/food/create_qrcode_form.html | 55 --- apps/food/templates/food/qrcode_detail.html | 39 -- .../food/transformedfood_detail.html | 51 --- .../templates/food/transformedfood_form.html | 20 - .../templates/food/transformedfood_list.html | 60 --- apps/food/tests.py | 3 - apps/food/urls.py | 21 - apps/food/views.py | 421 ------------------ 25 files changed, 1481 deletions(-) delete mode 100644 apps/food/admin.py delete mode 100644 apps/food/api/__init__.py delete mode 100644 apps/food/api/serializers.py delete mode 100644 apps/food/api/urls.py delete mode 100644 apps/food/api/views.py delete mode 100644 apps/food/forms.py delete mode 100644 apps/food/migrations/0001_initial.py delete mode 100644 apps/food/migrations/0002_transformedfood_shelf_life.py delete mode 100644 apps/food/migrations/0003_create_14_allergens_mandatory.py delete mode 100644 apps/food/migrations/0004_auto_20240813_2358.py delete mode 100644 apps/food/migrations/0005_alter_food_polymorphic_ctype.py delete mode 100644 apps/food/migrations/__init__.py delete mode 100644 apps/food/models.py delete mode 100644 apps/food/tables.py delete mode 100644 apps/food/templates/food/add_ingredient_form.html delete mode 100644 apps/food/templates/food/basicfood_detail.html delete mode 100644 apps/food/templates/food/basicfood_form.html delete mode 100644 apps/food/templates/food/create_qrcode_form.html delete mode 100644 apps/food/templates/food/qrcode_detail.html delete mode 100644 apps/food/templates/food/transformedfood_detail.html delete mode 100644 apps/food/templates/food/transformedfood_form.html delete mode 100644 apps/food/templates/food/transformedfood_list.html delete mode 100644 apps/food/tests.py delete mode 100644 apps/food/urls.py delete mode 100644 apps/food/views.py diff --git a/apps/food/admin.py b/apps/food/admin.py deleted file mode 100644 index 89f042e1..00000000 --- a/apps/food/admin.py +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay -# SPDX-License-Identifier: GPL-3.0-or-later - -from django.contrib import admin -from django.db import transaction -from note_kfet.admin import admin_site - -from .models import Allergen, BasicFood, QRCode, TransformedFood - - -@admin.register(QRCode, site=admin_site) -class QRCodeAdmin(admin.ModelAdmin): - pass - - -@admin.register(BasicFood, site=admin_site) -class BasicFoodAdmin(admin.ModelAdmin): - @transaction.atomic - def save_related(self, *args, **kwargs): - ans = super().save_related(*args, **kwargs) - args[1].instance.update() - return ans - - -@admin.register(TransformedFood, site=admin_site) -class TransformedFoodAdmin(admin.ModelAdmin): - exclude = ["allergens", "expiry_date"] - - @transaction.atomic - def save_related(self, request, form, *args, **kwargs): - super().save_related(request, form, *args, **kwargs) - form.instance.update() - - -@admin.register(Allergen, site=admin_site) -class AllergenAdmin(admin.ModelAdmin): - pass diff --git a/apps/food/api/__init__.py b/apps/food/api/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/food/api/serializers.py b/apps/food/api/serializers.py deleted file mode 100644 index acac2ba9..00000000 --- a/apps/food/api/serializers.py +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay -# SPDX-License-Identifier: GPL-3.0-or-later - -from rest_framework import serializers - -from ..models import Allergen, BasicFood, QRCode, TransformedFood - - -class AllergenSerializer(serializers.ModelSerializer): - """ - REST API Serializer for Allergen. - The djangorestframework plugin will analyse the model `Allergen` and parse all fields in the API. - """ - - class Meta: - model = Allergen - fields = '__all__' - - -class BasicFoodSerializer(serializers.ModelSerializer): - """ - REST API Serializer for BasicFood. - The djangorestframework plugin will analyse the model `BasicFood` and parse all fields in the API. - """ - - class Meta: - model = BasicFood - fields = '__all__' - - -class QRCodeSerializer(serializers.ModelSerializer): - """ - REST API Serializer for QRCode. - The djangorestframework plugin will analyse the model `QRCode` and parse all fields in the API. - """ - - class Meta: - model = QRCode - fields = '__all__' - - -class TransformedFoodSerializer(serializers.ModelSerializer): - """ - REST API Serializer for TransformedFood. - The djangorestframework plugin will analyse the model `TransformedFood` and parse all fields in the API. - """ - - class Meta: - model = TransformedFood - fields = '__all__' diff --git a/apps/food/api/urls.py b/apps/food/api/urls.py deleted file mode 100644 index 23c67bdd..00000000 --- a/apps/food/api/urls.py +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay -# SPDX-License-Identifier: GPL-3.0-or-later - -from .views import AllergenViewSet, BasicFoodViewSet, QRCodeViewSet, TransformedFoodViewSet - - -def register_food_urls(router, path): - """ - Configure router for Food REST API. - """ - router.register(path + '/allergen', AllergenViewSet) - router.register(path + '/basic_food', BasicFoodViewSet) - router.register(path + '/qrcode', QRCodeViewSet) - router.register(path + '/transformed_food', TransformedFoodViewSet) diff --git a/apps/food/api/views.py b/apps/food/api/views.py deleted file mode 100644 index af616074..00000000 --- a/apps/food/api/views.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright (C) 2018-2025 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 AllergenSerializer, BasicFoodSerializer, QRCodeSerializer, TransformedFoodSerializer -from ..models import Allergen, BasicFood, QRCode, TransformedFood - - -class AllergenViewSet(ReadProtectedModelViewSet): - """ - REST API View set. - The djangorestframework plugin will get all `Allergen` objects, serialize it to JSON with the given serializer, - then render it on /api/food/allergen/ - """ - queryset = Allergen.objects.order_by('id') - serializer_class = AllergenSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] - filterset_fields = ['name', ] - search_fields = ['$name', ] - - -class BasicFoodViewSet(ReadProtectedModelViewSet): - """ - REST API View set. - The djangorestframework plugin will get all `BasicFood` objects, serialize it to JSON with the given serializer, - then render it on /api/food/basic_food/ - """ - queryset = BasicFood.objects.order_by('id') - serializer_class = BasicFoodSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] - filterset_fields = ['name', ] - search_fields = ['$name', ] - - -class QRCodeViewSet(ReadProtectedModelViewSet): - """ - REST API View set. - The djangorestframework plugin will get all `QRCode` objects, serialize it to JSON with the given serializer, - then render it on /api/food/qrcode/ - """ - queryset = QRCode.objects.order_by('id') - serializer_class = QRCodeSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] - filterset_fields = ['qr_code_number', ] - search_fields = ['$qr_code_number', ] - - -class TransformedFoodViewSet(ReadProtectedModelViewSet): - """ - REST API View set. - The djangorestframework plugin will get all `TransformedFood` objects, serialize it to JSON with the given serializer, - then render it on /api/food/transformed_food/ - """ - queryset = TransformedFood.objects.order_by('id') - serializer_class = TransformedFoodSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] - filterset_fields = ['name', ] - search_fields = ['$name', ] diff --git a/apps/food/forms.py b/apps/food/forms.py deleted file mode 100644 index af468c7f..00000000 --- a/apps/food/forms.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay -# SPDX-License-Identifier: GPL-3.0-or-later - -from random import shuffle - -from django import forms -from django.utils.translation import gettext_lazy as _ -from django.utils import timezone -from member.models import Club -from bootstrap_datepicker_plus.widgets import DateTimePickerInput -from note_kfet.inputs import Autocomplete -from note_kfet.middlewares import get_current_request -from permission.backends import PermissionBackend - -from .models import BasicFood, QRCode, TransformedFood - - -class AddIngredientForms(forms.ModelForm): - """ - Form for add an ingredient - """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['ingredient'].queryset = self.fields['ingredient'].queryset.filter( - polymorphic_ctype__model='transformedfood', - is_ready=False, - is_active=True, - was_eaten=False, - ) - # Caution, the logic is inverted here, we flip the logic on saving in AddIngredientView - self.fields['is_active'].initial = True - self.fields['is_active'].label = _("Fully used") - - class Meta: - model = TransformedFood - fields = ('ingredient', 'is_active') - - -class BasicFoodForms(forms.ModelForm): - """ - Form for add non-transformed food - """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['name'].widget.attrs.update({"autofocus": "autofocus"}) - self.fields['name'].required = True - self.fields['owner'].required = True - - # Some example - self.fields['name'].widget.attrs.update({"placeholder": _("Pasta METRO 5kg")}) - clubs = list(Club.objects.filter(PermissionBackend.filter_queryset(get_current_request(), Club, "change")).all()) - shuffle(clubs) - self.fields['owner'].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ", ..." - - class Meta: - model = BasicFood - fields = ('name', 'owner', 'date_type', 'expiry_date', 'is_active', 'was_eaten', 'allergens',) - widgets = { - "owner": Autocomplete( - model=Club, - attrs={"api_url": "/api/members/club/"}, - ), - 'expiry_date': DateTimePickerInput(), - } - - -class QRCodeForms(forms.ModelForm): - """ - Form for create QRCode - """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['food_container'].queryset = self.fields['food_container'].queryset.filter( - is_active=True, - was_eaten=False, - polymorphic_ctype__model='transformedfood', - ) - - class Meta: - model = QRCode - fields = ('food_container',) - - -class TransformedFoodForms(forms.ModelForm): - """ - Form for add transformed food - """ - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['name'].widget.attrs.update({"autofocus": "autofocus"}) - self.fields['name'].required = True - self.fields['owner'].required = True - self.fields['creation_date'].required = True - self.fields['creation_date'].initial = timezone.now - self.fields['is_active'].initial = True - self.fields['is_ready'].initial = False - self.fields['was_eaten'].initial = False - - # Some example - self.fields['name'].widget.attrs.update({"placeholder": _("Lasagna")}) - clubs = list(Club.objects.filter(PermissionBackend.filter_queryset(get_current_request(), Club, "change")).all()) - shuffle(clubs) - self.fields['owner'].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ", ..." - - class Meta: - model = TransformedFood - fields = ('name', 'creation_date', 'owner', 'is_active', 'is_ready', 'was_eaten', 'shelf_life') - widgets = { - "owner": Autocomplete( - model=Club, - attrs={"api_url": "/api/members/club/"}, - ), - 'creation_date': DateTimePickerInput(), - } diff --git a/apps/food/migrations/0001_initial.py b/apps/food/migrations/0001_initial.py deleted file mode 100644 index 011d0f3f..00000000 --- a/apps/food/migrations/0001_initial.py +++ /dev/null @@ -1,84 +0,0 @@ -# Generated by Django 2.2.28 on 2024-07-05 08:57 - -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('member', '0011_profile_vss_charter_read'), - ] - - operations = [ - migrations.CreateModel( - name='Allergen', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='name')), - ], - options={ - 'verbose_name': 'Allergen', - 'verbose_name_plural': 'Allergens', - }, - ), - migrations.CreateModel( - name='Food', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255, verbose_name='name')), - ('expiry_date', models.DateTimeField(verbose_name='expiry date')), - ('was_eaten', models.BooleanField(default=False, verbose_name='was eaten')), - ('is_ready', models.BooleanField(default=False, verbose_name='is ready')), - ('allergens', models.ManyToManyField(blank=True, to='food.Allergen', verbose_name='allergen')), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='owner')), - ('polymorphic_ctype', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_food.food_set+', to='contenttypes.ContentType')), - ], - options={ - 'verbose_name': 'foods', - }, - ), - migrations.CreateModel( - name='BasicFood', - fields=[ - ('food_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='food.Food')), - ('date_type', models.CharField(choices=[('DLC', 'DLC'), ('DDM', 'DDM')], max_length=255)), - ('arrival_date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='arrival date')), - ], - options={ - 'verbose_name': 'Basic food', - 'verbose_name_plural': 'Basic foods', - }, - bases=('food.food',), - ), - migrations.CreateModel( - name='QRCode', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('qr_code_number', models.PositiveIntegerField(unique=True, verbose_name='QR-code number')), - ('food_container', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='QR_code', to='food.Food', verbose_name='food container')), - ], - options={ - 'verbose_name': 'QR-code', - 'verbose_name_plural': 'QR-codes', - }, - ), - migrations.CreateModel( - name='TransformedFood', - fields=[ - ('food_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='food.Food')), - ('creation_date', models.DateTimeField(verbose_name='creation date')), - ('is_active', models.BooleanField(default=True, verbose_name='is active')), - ('ingredient', models.ManyToManyField(blank=True, related_name='transformed_ingredient_inv', to='food.Food', verbose_name='transformed ingredient')), - ], - options={ - 'verbose_name': 'Transformed food', - 'verbose_name_plural': 'Transformed foods', - }, - bases=('food.food',), - ), - ] diff --git a/apps/food/migrations/0002_transformedfood_shelf_life.py b/apps/food/migrations/0002_transformedfood_shelf_life.py deleted file mode 100644 index 46673643..00000000 --- a/apps/food/migrations/0002_transformedfood_shelf_life.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2.28 on 2024-07-06 20:37 - -import datetime -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('food', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='transformedfood', - name='shelf_life', - field=models.DurationField(default=datetime.timedelta(days=3), verbose_name='shelf life'), - ), - ] diff --git a/apps/food/migrations/0003_create_14_allergens_mandatory.py b/apps/food/migrations/0003_create_14_allergens_mandatory.py deleted file mode 100644 index 236eaea4..00000000 --- a/apps/food/migrations/0003_create_14_allergens_mandatory.py +++ /dev/null @@ -1,62 +0,0 @@ -from django.db import migrations - -def create_14_mandatory_allergens(apps, schema_editor): - """ - There are 14 mandatory allergens, they are pre-injected - """ - - Allergen = apps.get_model("food", "allergen") - - Allergen.objects.get_or_create( - name="Gluten", - ) - Allergen.objects.get_or_create( - name="Fruits à coques", - ) - Allergen.objects.get_or_create( - name="Crustacés", - ) - Allergen.objects.get_or_create( - name="Céléri", - ) - Allergen.objects.get_or_create( - name="Oeufs", - ) - Allergen.objects.get_or_create( - name="Moutarde", - ) - Allergen.objects.get_or_create( - name="Poissons", - ) - Allergen.objects.get_or_create( - name="Soja", - ) - Allergen.objects.get_or_create( - name="Lait", - ) - Allergen.objects.get_or_create( - name="Sulfites", - ) - Allergen.objects.get_or_create( - name="Sésame", - ) - Allergen.objects.get_or_create( - name="Lupin", - ) - Allergen.objects.get_or_create( - name="Arachides", - ) - Allergen.objects.get_or_create( - name="Mollusques", - ) - -class Migration(migrations.Migration): - dependencies = [ - ('food', '0002_transformedfood_shelf_life'), - ] - - operations = [ - migrations.RunPython(create_14_mandatory_allergens), - ] - - diff --git a/apps/food/migrations/0004_auto_20240813_2358.py b/apps/food/migrations/0004_auto_20240813_2358.py deleted file mode 100644 index d7fdf200..00000000 --- a/apps/food/migrations/0004_auto_20240813_2358.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 2.2.28 on 2024-08-13 21:58 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('food', '0003_create_14_allergens_mandatory'), - ] - - operations = [ - migrations.RemoveField( - model_name='transformedfood', - name='is_active', - ), - migrations.AddField( - model_name='food', - name='is_active', - field=models.BooleanField(default=True, verbose_name='is active'), - ), - migrations.AlterField( - model_name='qrcode', - name='food_container', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='QR_code', to='food.Food', verbose_name='food container'), - ), - ] diff --git a/apps/food/migrations/0005_alter_food_polymorphic_ctype.py b/apps/food/migrations/0005_alter_food_polymorphic_ctype.py deleted file mode 100644 index 5473bffc..00000000 --- a/apps/food/migrations/0005_alter_food_polymorphic_ctype.py +++ /dev/null @@ -1,20 +0,0 @@ -# Generated by Django 4.2.15 on 2024-08-28 08:00 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - ('food', '0004_auto_20240813_2358'), - ] - - operations = [ - migrations.AlterField( - model_name='food', - name='polymorphic_ctype', - field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'), - ), - ] diff --git a/apps/food/migrations/__init__.py b/apps/food/migrations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/food/models.py b/apps/food/models.py deleted file mode 100644 index 199dcdd7..00000000 --- a/apps/food/models.py +++ /dev/null @@ -1,226 +0,0 @@ -# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay -# SPDX-License-Identifier: GPL-3.0-or-later - -from datetime import timedelta - -from django.db import models, transaction -from django.utils import timezone -from django.utils.translation import gettext_lazy as _ -from member.models import Club -from polymorphic.models import PolymorphicModel - - -class QRCode(models.Model): - """ - An QRCode model - """ - qr_code_number = models.PositiveIntegerField( - verbose_name=_("QR-code number"), - unique=True, - ) - - food_container = models.ForeignKey( - 'Food', - on_delete=models.CASCADE, - related_name='QR_code', - verbose_name=_('food container'), - ) - - class Meta: - verbose_name = _("QR-code") - verbose_name_plural = _("QR-codes") - - def __str__(self): - return _("QR-code number {qr_code_number}").format(qr_code_number=self.qr_code_number) - - -class Allergen(models.Model): - """ - A list of allergen and alimentary restrictions - """ - name = models.CharField( - verbose_name=_('name'), - max_length=255, - ) - - class Meta: - verbose_name = _('Allergen') - verbose_name_plural = _('Allergens') - - def __str__(self): - return self.name - - -class Food(PolymorphicModel): - name = models.CharField( - verbose_name=_('name'), - max_length=255, - ) - - owner = models.ForeignKey( - Club, - on_delete=models.PROTECT, - related_name='+', - verbose_name=_('owner'), - ) - - allergens = models.ManyToManyField( - Allergen, - blank=True, - verbose_name=_('allergen'), - ) - - expiry_date = models.DateTimeField( - verbose_name=_('expiry date'), - null=False, - ) - - was_eaten = models.BooleanField( - default=False, - verbose_name=_('was eaten'), - ) - - # is_ready != is_active : is_ready signifie que la nourriture est prête à être manger, - # is_active signifie que la nourriture n'est pas encore archivé - # il sert dans les cas où il est plus intéressant que de l'open soit conservé (confiture par ex) - - is_ready = models.BooleanField( - default=False, - verbose_name=_('is ready'), - ) - - is_active = models.BooleanField( - default=True, - verbose_name=_('is active'), - ) - - def __str__(self): - return self.name - - @transaction.atomic - def save(self, force_insert=False, force_update=False, using=None, update_fields=None): - return super().save(force_insert, force_update, using, update_fields) - - class Meta: - verbose_name = _('food') - verbose_name = _('foods') - - -class BasicFood(Food): - """ - Food which has been directly buy on supermarket - """ - date_type = models.CharField( - max_length=255, - choices=( - ("DLC", "DLC"), - ("DDM", "DDM"), - ) - ) - - arrival_date = models.DateTimeField( - verbose_name=_('arrival date'), - default=timezone.now, - ) - - # label = models.ImageField( - # verbose_name=_('food label'), - # max_length=255, - # blank=False, - # null=False, - # upload_to='label/', - # ) - - @transaction.atomic - def update_allergens(self): - # update parents - for parent in self.transformed_ingredient_inv.iterator(): - parent.update_allergens() - - @transaction.atomic - def update_expiry_date(self): - # update parents - for parent in self.transformed_ingredient_inv.iterator(): - parent.update_expiry_date() - - @transaction.atomic - def update(self): - self.update_allergens() - self.update_expiry_date() - - class Meta: - verbose_name = _('Basic food') - verbose_name_plural = _('Basic foods') - - -class TransformedFood(Food): - """ - Transformed food are a mix between basic food and meal - """ - creation_date = models.DateTimeField( - verbose_name=_('creation date'), - ) - - ingredient = models.ManyToManyField( - Food, - blank=True, - symmetrical=False, - related_name='transformed_ingredient_inv', - verbose_name=_('transformed ingredient'), - ) - - # Without microbiological analyzes, the storage time is 3 days - shelf_life = models.DurationField( - verbose_name=_("shelf life"), - default=timedelta(days=3), - ) - - @transaction.atomic - def archive(self): - # When a meal are archived, if it was eaten, update ingredient fully used for this meal - raise NotImplementedError - - @transaction.atomic - def update_allergens(self): - # When allergens are changed, simply update the parents' allergens - old_allergens = list(self.allergens.all()) - self.allergens.clear() - for ingredient in self.ingredient.iterator(): - self.allergens.set(self.allergens.union(ingredient.allergens.all())) - - if old_allergens == list(self.allergens.all()): - return - super().save() - - # update parents - for parent in self.transformed_ingredient_inv.iterator(): - parent.update_allergens() - - @transaction.atomic - def update_expiry_date(self): - # When expiry_date is changed, simply update the parents' expiry_date - old_expiry_date = self.expiry_date - self.expiry_date = self.creation_date + self.shelf_life - for ingredient in self.ingredient.iterator(): - self.expiry_date = min(self.expiry_date, ingredient.expiry_date) - - if old_expiry_date == self.expiry_date: - return - super().save() - - # update parents - for parent in self.transformed_ingredient_inv.iterator(): - parent.update_expiry_date() - - @transaction.atomic - def update(self): - self.update_allergens() - self.update_expiry_date() - - @transaction.atomic - def save(self, *args, **kwargs): - super().save(*args, **kwargs) - - class Meta: - verbose_name = _('Transformed food') - verbose_name_plural = _('Transformed foods') diff --git a/apps/food/tables.py b/apps/food/tables.py deleted file mode 100644 index 4ab15879..00000000 --- a/apps/food/tables.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay -# SPDX-License-Identifier: GPL-3.0-or-later - -import django_tables2 as tables -from django_tables2 import A - -from .models import TransformedFood - - -class TransformedFoodTable(tables.Table): - name = tables.LinkColumn( - 'food:food_view', - args=[A('pk'), ], - ) - - class Meta: - model = TransformedFood - template_name = 'django_tables2/bootstrap4.html' - fields = ('name', "owner", "allergens", "expiry_date") diff --git a/apps/food/templates/food/add_ingredient_form.html b/apps/food/templates/food/add_ingredient_form.html deleted file mode 100644 index 395928e4..00000000 --- a/apps/food/templates/food/add_ingredient_form.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n crispy_forms_tags %} - -{% block content %} -
-

- {{ title }} -

-
-
- {% csrf_token %} - {{ form|crispy }} - -
-
-
-{% endblock %} diff --git a/apps/food/templates/food/basicfood_detail.html b/apps/food/templates/food/basicfood_detail.html deleted file mode 100644 index 846fadba..00000000 --- a/apps/food/templates/food/basicfood_detail.html +++ /dev/null @@ -1,37 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n crispy_forms_tags %} - -{% block content %} -
-

- {{ title }} {{ food.name }} -

-
-
    -
  • {% trans 'Owner' %} : {{ food.owner }}

  • -
  • {% trans 'Arrival date' %} : {{ food.arrival_date }}

  • -
  • {% trans 'Expiry date' %} : {{ food.expiry_date }} ({{ food.date_type }})

  • -
  • {% trans 'Allergens' %} :
  • -
      - {% for allergen in food.allergens.iterator %} -
    • {{ allergen.name }}
    • - {% endfor %} -
    -

    -

  • {% trans 'Active' %} : {{ food.is_active }}

  • -
  • {% trans 'Eaten' %} : {{ food.was_eaten }}

  • -
- {% if can_update %} - {% trans 'Update' %} - {% endif %} - {% if can_add_ingredient %} - - {% trans 'Add to a meal' %} - - {% endif %} -
-
-{% endblock %} diff --git a/apps/food/templates/food/basicfood_form.html b/apps/food/templates/food/basicfood_form.html deleted file mode 100644 index 6fe6f06f..00000000 --- a/apps/food/templates/food/basicfood_form.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n crispy_forms_tags %} - -{% block content %} -
-

- {{ title }} -

-
-
- {% csrf_token %} - {{ form | crispy }} - -
-
-
-{% endblock %} diff --git a/apps/food/templates/food/create_qrcode_form.html b/apps/food/templates/food/create_qrcode_form.html deleted file mode 100644 index 456b9970..00000000 --- a/apps/food/templates/food/create_qrcode_form.html +++ /dev/null @@ -1,55 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load render_table from django_tables2 %} -{% load i18n crispy_forms_tags %} - -{% block content %} -
-

- {{ title }} -

-
- - {% trans 'New basic food' %} - -
- {% csrf_token %} - {{ form|crispy }} - -
-
-

{% trans "Copy constructor" %}

- - - - - - - - - - - {% for basic in last_basic %} - - - - - - - {% endfor %} - -
- {% trans "Name" %} - - {% trans "Owner" %} - - {% trans "Arrival date" %} - - {% trans "Expiry date" %} -
{{ basic.name }}{{ basic.owner }}{{ basic.arrival_date }}{{ basic.expiry_date }}
-
-
-
-{% endblock %} diff --git a/apps/food/templates/food/qrcode_detail.html b/apps/food/templates/food/qrcode_detail.html deleted file mode 100644 index 6e3e8110..00000000 --- a/apps/food/templates/food/qrcode_detail.html +++ /dev/null @@ -1,39 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n crispy_forms_tags %} - -{% block content %} -
-

- {{ title }} {% trans 'number' %} {{ qrcode.qr_code_number }} -

-
-
    -
  • {% trans 'Name' %} : {{ qrcode.food_container.name }}

  • -
  • {% trans 'Owner' %} : {{ qrcode.food_container.owner }}

  • -
  • {% trans 'Expiry date' %} : {{ qrcode.food_container.expiry_date }}

  • -
- {% if qrcode.food_container.polymorphic_ctype.model == 'basicfood' and can_update_basic %} - - {% trans 'Update' %} - - {% elif can_update_transformed %} - - {% trans 'Update' %} - - {% endif %} - {% if can_view_detail %} - - {% trans 'View details' %} - - {% endif %} - {% if can_add_ingredient %} - - {% trans 'Add to a meal' %} - - {% endif %} -
-
-{% endblock %} diff --git a/apps/food/templates/food/transformedfood_detail.html b/apps/food/templates/food/transformedfood_detail.html deleted file mode 100644 index ca32bc06..00000000 --- a/apps/food/templates/food/transformedfood_detail.html +++ /dev/null @@ -1,51 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n crispy_forms_tags %} - -{% block content %} -
-

- {{ title }} {{ food.name }} -

-
-
    -
  • {% trans 'Owner' %} : {{ food.owner }}

  • - {% if can_see_ready %} -
  • {% trans 'Ready' %} : {{ food.is_ready }}

  • - {% endif %} -
  • {% trans 'Creation date' %} : {{ food.creation_date }}

  • -
  • {% trans 'Expiry date' %} : {{ food.expiry_date }}

  • -
  • {% trans 'Allergens' %} :
  • -
      - {% for allergen in food.allergens.iterator %} -
    • {{ allergen.name }}
    • - {% endfor %} -
    -

    -

  • {% trans 'Ingredients' %} :
  • - -

    -

  • {% trans 'Shelf life' %} : {{ food.shelf_life }}

  • -
  • {% trans 'Ready' %} : {{ food.is_ready }}

  • -
  • {% trans 'Active' %} : {{ food.is_active }}

  • -
  • {% trans 'Eaten' %} : {{ food.was_eaten }}

  • -
- {% if can_update %} - - {% trans 'Update' %} - - {% endif %} - {% if can_add_ingredient %} - - {% trans 'Add to a meal' %} - - {% endif %} -
-
-{% endblock %} diff --git a/apps/food/templates/food/transformedfood_form.html b/apps/food/templates/food/transformedfood_form.html deleted file mode 100644 index 395928e4..00000000 --- a/apps/food/templates/food/transformedfood_form.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n crispy_forms_tags %} - -{% block content %} -
-

- {{ title }} -

-
-
- {% csrf_token %} - {{ form|crispy }} - -
-
-
-{% endblock %} diff --git a/apps/food/templates/food/transformedfood_list.html b/apps/food/templates/food/transformedfood_list.html deleted file mode 100644 index 4416cdb7..00000000 --- a/apps/food/templates/food/transformedfood_list.html +++ /dev/null @@ -1,60 +0,0 @@ -{% extends "base.html" %} -{% comment %} -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load render_table from django_tables2 %} -{% load i18n %} - -{% block content %} -
-

- {% trans "Meal served" %} -

- {% if can_create_meal %} - - {% endif %} - {% if served.data %} - {% render_table served %} - {% else %} -
-
- {% trans "There is no meal served." %} -
-
- {% endif %} -
- -
-

- {% trans "Open" %} -

- {% if open.data %} - {% render_table open %} - {% else %} -
-
- {% trans "There is no free meal." %} -
-
- {% endif %} -
- -
-

- {% trans "All meals" %} -

- {% if table.data %} - {% render_table table %} - {% else %} -
-
- {% trans "There is no meal." %} -
-
- {% endif %} -
-{% endblock %} diff --git a/apps/food/tests.py b/apps/food/tests.py deleted file mode 100644 index a79ca8be..00000000 --- a/apps/food/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -# from django.test import TestCase - -# Create your tests here. diff --git a/apps/food/urls.py b/apps/food/urls.py deleted file mode 100644 index 59063cfe..00000000 --- a/apps/food/urls.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay -# SPDX-License-Identifier: GPL-3.0-or-later - -from django.urls import path - -from . import views - -app_name = 'food' - -urlpatterns = [ - path('', views.TransformedListView.as_view(), name='food_list'), - path('', views.QRCodeView.as_view(), name='qrcode_view'), - path('detail/', views.FoodView.as_view(), name='food_view'), - - path('/create_qrcode', views.QRCodeCreateView.as_view(), name='qrcode_create'), - path('/create_qrcode/basic', views.QRCodeBasicFoodCreateView.as_view(), name='qrcode_basic_create'), - path('create/transformed', views.TransformedFoodCreateView.as_view(), name='transformed_create'), - path('update/basic/', views.BasicFoodUpdateView.as_view(), name='basic_update'), - path('update/transformed/', views.TransformedFoodUpdateView.as_view(), name='transformed_update'), - path('add/', views.AddIngredientView.as_view(), name='add_ingredient'), -] diff --git a/apps/food/views.py b/apps/food/views.py deleted file mode 100644 index 8c63530c..00000000 --- a/apps/food/views.py +++ /dev/null @@ -1,421 +0,0 @@ -# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay -# SPDX-License-Identifier: GPL-3.0-or-later - -from django.db import transaction -from django.contrib.auth.mixins import LoginRequiredMixin -from django.http import HttpResponseRedirect -from django_tables2.views import MultiTableMixin -from django.urls import reverse -from django.utils.translation import gettext_lazy as _ -from django.utils import timezone -from django.views.generic import DetailView, UpdateView -from django.views.generic.list import ListView -from django.forms import HiddenInput -from permission.backends import PermissionBackend -from permission.views import ProtectQuerysetMixin, ProtectedCreateView - -from .forms import AddIngredientForms, BasicFoodForms, QRCodeForms, TransformedFoodForms -from .models import BasicFood, Food, QRCode, TransformedFood -from .tables import TransformedFoodTable - - -class AddIngredientView(ProtectQuerysetMixin, UpdateView): - """ - A view to add an ingredient - """ - model = Food - template_name = 'food/add_ingredient_form.html' - extra_context = {"title": _("Add the ingredient")} - form_class = AddIngredientForms - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["pk"] = self.kwargs["pk"] - return context - - @transaction.atomic - def form_valid(self, form): - form.instance.creater = self.request.user - food = Food.objects.get(pk=self.kwargs['pk']) - add_ingredient_form = AddIngredientForms(data=self.request.POST) - if food.is_ready: - form.add_error(None, _("The product is already prepared")) - return self.form_invalid(form) - if not add_ingredient_form.is_valid(): - return self.form_invalid(form) - - # We flip logic ""fully used = not is_active"" - food.is_active = not food.is_active - # Save the aliment and the allergens associed - for transformed_pk in self.request.POST.getlist('ingredient'): - transformed = TransformedFood.objects.get(pk=transformed_pk) - if not transformed.is_ready: - transformed.ingredient.add(food) - transformed.update() - food.save() - - return HttpResponseRedirect(self.get_success_url()) - - def get_success_url(self, **kwargs): - return reverse('food:food_list') - - -class BasicFoodUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): - """ - A view to update a basic food - """ - model = BasicFood - form_class = BasicFoodForms - template_name = 'food/basicfood_form.html' - extra_context = {"title": _("Update an aliment")} - - @transaction.atomic - def form_valid(self, form): - form.instance.creater = self.request.user - basic_food_form = BasicFoodForms(data=self.request.POST) - if not basic_food_form.is_valid(): - return self.form_invalid(form) - - ans = super().form_valid(form) - form.instance.update() - return ans - - def get_success_url(self, **kwargs): - self.object.refresh_from_db() - return reverse('food:food_view', kwargs={"pk": self.object.pk}) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - return context - - -class FoodView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): - """ - A view to see a food - """ - model = Food - extra_context = {"title": _("Details of:")} - context_object_name = "food" - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - - context["can_update"] = PermissionBackend.check_perm(self.request, "food.change_food") - context["can_add_ingredient"] = PermissionBackend.check_perm(self.request, "food.change_transformedfood") - return context - - -class QRCodeBasicFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView): - ##################################################################### - # TO DO - # - this feature is very pratical for meat or fish, nevertheless we can implement this later - # - fix picture save - # - implement solution crop and convert image (reuse or recode ImageForm from members apps) - ##################################################################### - """ - A view to add a basic food with a qrcode - """ - model = BasicFood - form_class = BasicFoodForms - template_name = 'food/basicfood_form.html' - extra_context = {"title": _("Add a new basic food with QRCode")} - - @transaction.atomic - def form_valid(self, form): - form.instance.creater = self.request.user - basic_food_form = BasicFoodForms(data=self.request.POST) - if not basic_food_form.is_valid(): - return self.form_invalid(form) - - # Save the aliment and the allergens associed - basic_food = form.save(commit=False) - # We assume the date of labeling and the same as the date of arrival - basic_food.arrival_date = timezone.now() - basic_food.is_ready = False - basic_food.is_active = True - basic_food.was_eaten = False - basic_food._force_save = True - basic_food.save() - basic_food.refresh_from_db() - - qrcode = QRCode() - qrcode.qr_code_number = self.kwargs['slug'] - qrcode.food_container = basic_food - qrcode.save() - - return super().form_valid(form) - - def get_success_url(self, **kwargs): - self.object.refresh_from_db() - return reverse('food:qrcode_view', kwargs={"slug": self.kwargs['slug']}) - - def get_sample_object(self): - - # We choose a club which may work or BDE else - owner_id = 1 - for membership in self.request.user.memberships.all(): - club_id = membership.club.id - food = BasicFood(name="", expiry_date=timezone.now(), owner_id=club_id) - if PermissionBackend.check_perm(self.request, "food.add_basicfood", food): - owner_id = club_id - - return BasicFood( - name="", - expiry_date=timezone.now(), - owner_id=owner_id, - ) - - def get_context_data(self, **kwargs): - # Some field are hidden on create - context = super().get_context_data(**kwargs) - - form = context['form'] - form.fields['is_active'].widget = HiddenInput() - form.fields['was_eaten'].widget = HiddenInput() - - copy = self.request.GET.get('copy', None) - if copy is not None: - basic = BasicFood.objects.get(pk=copy) - for field in ['date_type', 'expiry_date', 'name', 'owner']: - form.fields[field].initial = getattr(basic, field) - for field in ['allergens']: - form.fields[field].initial = getattr(basic, field).all() - - return context - - -class QRCodeCreateView(ProtectQuerysetMixin, ProtectedCreateView): - """ - A view to add a new qrcode - """ - model = QRCode - template_name = 'food/create_qrcode_form.html' - form_class = QRCodeForms - extra_context = {"title": _("Add a new QRCode")} - - def get(self, *args, **kwargs): - qrcode = kwargs["slug"] - if self.model.objects.filter(qr_code_number=qrcode).count() > 0: - return HttpResponseRedirect(reverse("food:qrcode_view", kwargs=kwargs)) - else: - return super().get(*args, **kwargs) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["slug"] = self.kwargs["slug"] - - context["last_basic"] = BasicFood.objects.order_by('-pk').all()[:10] - - return context - - @transaction.atomic - def form_valid(self, form): - form.instance.creater = self.request.user - qrcode_food_form = QRCodeForms(data=self.request.POST) - if not qrcode_food_form.is_valid(): - return self.form_invalid(form) - - # Save the qrcode - qrcode = form.save(commit=False) - qrcode.qr_code_number = self.kwargs["slug"] - qrcode._force_save = True - qrcode.save() - qrcode.refresh_from_db() - - qrcode.food_container.save() - - return super().form_valid(form) - - def get_success_url(self, **kwargs): - self.object.refresh_from_db() - return reverse('food:qrcode_view', kwargs={"slug": self.kwargs['slug']}) - - def get_sample_object(self): - return QRCode( - qr_code_number=self.kwargs["slug"], - food_container_id=1 - ) - - -class QRCodeView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): - """ - A view to see a qrcode - """ - model = QRCode - extra_context = {"title": _("QRCode")} - context_object_name = "qrcode" - slug_field = "qr_code_number" - - def get(self, *args, **kwargs): - qrcode = kwargs["slug"] - if self.model.objects.filter(qr_code_number=qrcode).count() > 0: - return super().get(*args, **kwargs) - else: - return HttpResponseRedirect(reverse("food:qrcode_create", kwargs=kwargs)) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - - qr_code_number = self.kwargs['slug'] - qrcode = self.model.objects.get(qr_code_number=qr_code_number) - - model = qrcode.food_container.polymorphic_ctype.model - - if model == "basicfood": - context["can_update_basic"] = PermissionBackend.check_perm(self.request, "food.change_basicfood") - context["can_view_detail"] = PermissionBackend.check_perm(self.request, "food.view_basicfood") - if model == "transformedfood": - context["can_update_transformed"] = PermissionBackend.check_perm(self.request, "food.change_transformedfood") - context["can_view_detail"] = PermissionBackend.check_perm(self.request, "food.view_transformedfood") - context["can_add_ingredient"] = PermissionBackend.check_perm(self.request, "food.change_transformedfood") - return context - - -class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView): - """ - A view to add a tranformed food - """ - model = TransformedFood - template_name = 'food/transformedfood_form.html' - form_class = TransformedFoodForms - extra_context = {"title": _("Add a new meal")} - - @transaction.atomic - def form_valid(self, form): - form.instance.creater = self.request.user - transformed_food_form = TransformedFoodForms(data=self.request.POST) - if not transformed_food_form.is_valid(): - return self.form_invalid(form) - - # Save the aliment and allergens associated - transformed_food = form.save(commit=False) - transformed_food.expiry_date = transformed_food.creation_date - transformed_food.is_active = True - transformed_food.is_ready = False - transformed_food.was_eaten = False - transformed_food._force_save = True - transformed_food.save() - transformed_food.refresh_from_db() - ans = super().form_valid(form) - transformed_food.update() - return ans - - def get_success_url(self, **kwargs): - self.object.refresh_from_db() - return reverse('food:food_view', kwargs={"pk": self.object.pk}) - - def get_sample_object(self): - # We choose a club which may work or BDE else - owner_id = 1 - for membership in self.request.user.memberships.all(): - club_id = membership.club.id - food = TransformedFood(name="", - creation_date=timezone.now(), - expiry_date=timezone.now(), - owner_id=club_id) - if PermissionBackend.check_perm(self.request, "food.add_transformedfood", food): - owner_id = club_id - break - - return TransformedFood( - name="", - owner_id=owner_id, - creation_date=timezone.now(), - expiry_date=timezone.now(), - ) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - - # Some field are hidden on create - form = context['form'] - form.fields['is_active'].widget = HiddenInput() - form.fields['is_ready'].widget = HiddenInput() - form.fields['was_eaten'].widget = HiddenInput() - form.fields['shelf_life'].widget = HiddenInput() - - return context - - -class TransformedFoodUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): - """ - A view to update transformed product - """ - model = TransformedFood - template_name = 'food/transformedfood_form.html' - form_class = TransformedFoodForms - extra_context = {'title': _('Update a meal')} - - @transaction.atomic - def form_valid(self, form): - form.instance.creater = self.request.user - transformedfood_form = TransformedFoodForms(data=self.request.POST) - if not transformedfood_form.is_valid(): - return self.form_invalid(form) - - ans = super().form_valid(form) - form.instance.update() - return ans - - def get_success_url(self, **kwargs): - self.object.refresh_from_db() - return reverse('food:food_view', kwargs={"pk": self.object.pk}) - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - return context - - -class TransformedListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView): - """ - Displays ready TransformedFood - """ - model = TransformedFood - tables = [TransformedFoodTable, TransformedFoodTable, TransformedFoodTable] - extra_context = {"title": _("Transformed food")} - - def get_queryset(self, **kwargs): - return super().get_queryset(**kwargs).distinct() - - def get_tables(self): - tables = super().get_tables() - - tables[0].prefix = "all-" - tables[1].prefix = "open-" - tables[2].prefix = "served-" - return tables - - def get_tables_data(self): - # first table = all transformed food, second table = free, third = served - return [ - self.get_queryset().order_by("-creation_date"), - TransformedFood.objects.filter(is_ready=True, is_active=True, was_eaten=False, expiry_date__lt=timezone.now()) - .filter(PermissionBackend.filter_queryset(self.request, TransformedFood, "view")) - .distinct() - .order_by("-creation_date"), - TransformedFood.objects.filter(is_ready=True, is_active=True, was_eaten=False, expiry_date__gte=timezone.now()) - .filter(PermissionBackend.filter_queryset(self.request, TransformedFood, "view")) - .distinct() - .order_by("-creation_date") - ] - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - - # We choose a club which should work - for membership in self.request.user.memberships.all(): - club_id = membership.club.id - food = TransformedFood( - name="", - owner_id=club_id, - creation_date=timezone.now(), - expiry_date=timezone.now(), - ) - if PermissionBackend.check_perm(self.request, "food.add_transformedfood", food): - context['can_create_meal'] = True - break - - tables = context["tables"] - for name, table in zip(["table", "open", "served"], tables): - context[name] = table - return context From 6d6583bfe698ceb1dda3d0a3bb856c1b8ac838e3 Mon Sep 17 00:00:00 2001 From: quark Date: Tue, 22 Apr 2025 19:52:32 +0200 Subject: [PATCH 002/153] Rewrite food apps, new feature some changes to model --- apps/food/admin.py | 59 ++++ apps/food/api/__init__.py | 0 apps/food/api/serializers.py | 46 +++ apps/food/api/urls.py | 14 + apps/food/api/views.py | 61 ++++ apps/food/forms.py | 153 ++++++++ apps/food/migrations/0001_initial.py | 199 +++++++++++ apps/food/migrations/__init__.py | 0 apps/food/models.py | 286 +++++++++++++++ apps/food/tables.py | 21 ++ apps/food/templates/food/food_detail.html | 48 +++ apps/food/templates/food/food_list.html | 71 ++++ apps/food/templates/food/food_update.html | 21 ++ apps/food/templates/food/qrcode.html | 52 +++ apps/food/tests/test_food.py | 170 +++++++++ apps/food/urls.py | 20 ++ apps/food/utils.py | 53 +++ apps/food/views.py | 402 ++++++++++++++++++++++ apps/permission/fixtures/initial.json | 51 +-- 19 files changed, 1693 insertions(+), 34 deletions(-) create mode 100644 apps/food/admin.py create mode 100644 apps/food/api/__init__.py create mode 100644 apps/food/api/serializers.py create mode 100644 apps/food/api/urls.py create mode 100644 apps/food/api/views.py create mode 100644 apps/food/forms.py create mode 100644 apps/food/migrations/0001_initial.py create mode 100644 apps/food/migrations/__init__.py create mode 100644 apps/food/models.py create mode 100644 apps/food/tables.py create mode 100644 apps/food/templates/food/food_detail.html create mode 100644 apps/food/templates/food/food_list.html create mode 100644 apps/food/templates/food/food_update.html create mode 100644 apps/food/templates/food/qrcode.html create mode 100644 apps/food/tests/test_food.py create mode 100644 apps/food/urls.py create mode 100644 apps/food/utils.py create mode 100644 apps/food/views.py diff --git a/apps/food/admin.py b/apps/food/admin.py new file mode 100644 index 00000000..613ebade --- /dev/null +++ b/apps/food/admin.py @@ -0,0 +1,59 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.contrib import admin +from polymorphic.admin import PolymorphicChildModelAdmin, PolymorphicParentModelAdmin +from note_kfet.admin import admin_site + +from .models import Allergen, Food, BasicFood, TransformedFood, QRCode + + +@admin.register(Allergen, site=admin_site) +class AllergenAdmin(admin.ModelAdmin): + """ + Admin customisation for Allergen + """ + ordering = ['name'] + + +@admin.register(Food, site=admin_site) +class FoodAdmin(PolymorphicParentModelAdmin): + """ + Admin customisation for Food + """ + child_models = (Food, BasicFood, TransformedFood) + list_display = ('name', 'expiry_date', 'owner', 'is_ready') + list_filter = ('is_ready', 'end_of_life') + search_fields = ['name'] + ordering = ['expiry_date', 'name'] + + +@admin.register(BasicFood, site=admin_site) +class BasicFood(PolymorphicChildModelAdmin): + """ + Admin customisation for BasicFood + """ + list_display = ('name', 'expiry_date', 'date_type', 'owner', 'is_ready') + list_filter = ('is_ready', 'date_type', 'end_of_life') + search_fields = ['name'] + ordering = ['expiry_date', 'name'] + + +@admin.register(TransformedFood, site=admin_site) +class TransformedFood(PolymorphicChildModelAdmin): + """ + Admin customisation for TransformedFood + """ + list_display = ('name', 'expiry_date', 'shelf_life', 'owner', 'is_ready') + list_filter = ('is_ready', 'end_of_life', 'shelf_life') + search_fields = ['name'] + ordering = ['expiry_date', 'name'] + + +@admin.register(QRCode, site=admin_site) +class QRCodeAdmin(admin.ModelAdmin): + """ + Admin customisation for QRCode + """ + list_diplay = ('qr_code_number', 'food_container') + search_fields = ['food_container__name'] diff --git a/apps/food/api/__init__.py b/apps/food/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/food/api/serializers.py b/apps/food/api/serializers.py new file mode 100644 index 00000000..fa0641e8 --- /dev/null +++ b/apps/food/api/serializers.py @@ -0,0 +1,46 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from rest_framework import serializers + +from ..models import Allergen, BasicFood, TransformedFood, QRCode + + +class AllergenSerializer(serializers.ModelSerializer): + """ + REST API Serializer for Allergen. + The djangorestframework plugin will analyse the model `Allergen` and parse all fields in the API. + """ + class Meta: + model = Allergen + fields = '__all__' + + +class BasicFoodSerializer(serializers.ModelSerializer): + """ + REST API Serializer for BasicFood. + The djangorestframework plugin will analyse the model `BasicFood` and parse all fields in the API. + """ + class Meta: + model = BasicFood + fields = '__all__' + + +class TransformedFoodSerializer(serializers.ModelSerializer): + """ + REST API Serializer for TransformedFood. + The djangorestframework plugin will analyse the model `TransformedFood` and parse all fields in the API. + """ + class Meta: + model = TransformedFood + fields = '__all__' + + +class QRCodeSerializer(serializers.ModelSerializer): + """ + REST API Serializer for QRCode. + The djangorestframework plugin will analyse the model `QRCode` and parse all fields in the API. + """ + class Meta: + model = QRCode + fields = '__all__' diff --git a/apps/food/api/urls.py b/apps/food/api/urls.py new file mode 100644 index 00000000..5a8ce881 --- /dev/null +++ b/apps/food/api/urls.py @@ -0,0 +1,14 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from .views import AllergenViewSet, BasicFoodViewSet, TransformedFoodViewSet, QRCodeViewSet + + +def register_food_urls(router, path): + """ + Configure router for Food REST API. + """ + router.register(path + '/allergen', AllergenViewSet) + router.register(path + '/basicfood', BasicFoodViewSet) + router.register(path + '/transformedfood', TransformedFoodViewSet) + router.register(path + '/qrcode', QRCodeViewSet) diff --git a/apps/food/api/views.py b/apps/food/api/views.py new file mode 100644 index 00000000..2c75a570 --- /dev/null +++ b/apps/food/api/views.py @@ -0,0 +1,61 @@ +# Copyright (C) 2018-2025 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 AllergenSerializer, BasicFoodSerializer, TransformedFoodSerializer, QRCodeSerializer +from ..models import Allergen, BasicFood, TransformedFood, QRCode + + +class AllergenViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Allergen` objects, serialize it to JSON with the given serializer, + then render it on /api/food/allergen/ + """ + queryset = Allergen.objects.order_by('id') + serializer_class = AllergenSerializer + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name', ] + search_fields = ['$name', ] + + +class BasicFoodViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `BasicFood` objects, serialize it to JSON with the given serializer, + then render it on /api/food/basicfood/ + """ + queryset = BasicFood.objects.order_by('id') + serializer_class = BasicFoodSerializer + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name', ] + search_fields = ['$name', ] + + +class TransformedFoodViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `TransformedFood` objects, serialize it to JSON with the given serializer, + then render it on /api/food/transformedfood/ + """ + queryset = TransformedFood.objects.order_by('id') + serializer_class = TransformedFoodSerializer + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name', ] + search_fields = ['$name', ] + + +class QRCodeViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `QRCode` objects, serialize it to JSON with the given serializer, + then render it on /api/food/qrcode/ + """ + queryset = QRCode.objects.order_by('id') + serializer_class = QRCodeSerializer + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['qr_code_number', ] + search_fields = ['$qr_code_number', ] diff --git a/apps/food/forms.py b/apps/food/forms.py new file mode 100644 index 00000000..c823b0b1 --- /dev/null +++ b/apps/food/forms.py @@ -0,0 +1,153 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from random import shuffle + +from bootstrap_datepicker_plus.widgets import DateTimePickerInput +from django import forms +from django.forms.widgets import NumberInput +from django.utils.translation import gettext_lazy as _ +from member.models import Club +from note_kfet.inputs import Autocomplete +from note_kfet.middlewares import get_current_request +from permission.backends import PermissionBackend + +from .models import BasicFood, TransformedFood, QRCode + + +class QRCodeForms(forms.ModelForm): + """ + Form for create QRCode for container + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['food_container'].queryset = self.fields['food_container'].queryset.filter( + is_ready=False, + end_of_life__isnull=True, + polymorphic_ctype__model='transformedfood', + ).filter(PermissionBackend.filter_queryset( + get_current_request(), + TransformedFood, + "view", + )) + + class Meta: + model = QRCode + fields = ('food_container',) + + +class BasicFoodForms(forms.ModelForm): + """ + Form for add basicfood + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['name'].widget.attrs.update({"autofocus": "autofocus"}) + self.fields['name'].required = True + self.fields['owner'].required = True + + # Some example + self.fields['name'].widget.attrs.update({"placeholder": _("Pasta METRO 5kg")}) + clubs = list(Club.objects.filter(PermissionBackend.filter_queryset(get_current_request(), Club, "change")).all()) + shuffle(clubs) + self.fields['owner'].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ", ..." + self.fields['order'].widget.attrs["placeholder"] = _("Specific order given to GCKs") + + class Meta: + model = BasicFood + fields = ('name', 'owner', 'date_type', 'expiry_date', 'allergens', 'order',) + widgets = { + "owner": Autocomplete( + model=Club, + attrs={"api_url": "/api/members/club/"}, + ), + "expiry_date": DateTimePickerInput(), + } + + +class TransformedFoodForms(forms.ModelForm): + """ + Form for add transformedfood + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['name'].required = True + self.fields['owner'].required = True + + # Some example + self.fields['name'].widget.attrs.update({"placeholder": _("Lasagna")}) + clubs = list(Club.objects.filter(PermissionBackend.filter_queryset(get_current_request(), Club, "change")).all()) + shuffle(clubs) + self.fields['owner'].widget.attrs["placeholder"] = ", ".join(club.name for club in clubs[:4]) + ", ..." + self.fields['order'].widget.attrs["placeholder"] = _("Specific order given to GCKs") + + class Meta: + model = TransformedFood + fields = ('name', 'owner', 'order',) + widgets = { + "owner": Autocomplete( + model=Club, + attrs={"api_url": "/api/members/club/"}, + ), + } + + +class BasicFoodUpdateForms(forms.ModelForm): + """ + Form for update basicfood object + """ + class Meta: + model = BasicFood + fields = ('name', 'owner', 'date_type', 'expiry_date', 'end_of_life', 'is_ready', 'order', 'allergens') + widgets = { + "owner": Autocomplete( + model=Club, + attrs={"api_url": "/api/members/club/"}, + ), + "expiry_date": DateTimePickerInput(), + } + + +class TransformedFoodUpdateForms(forms.ModelForm): + """ + Form for update transformedfood object + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['shelf_life'].label = _('Shelf life (in hours)') + + class Meta: + model = TransformedFood + fields = ('name', 'owner', 'end_of_life', 'is_ready', 'order', 'shelf_life') + widgets = { + "owner": Autocomplete( + model=Club, + attrs={"api_url": "/api/members/club/"}, + ), + "expiry_date": DateTimePickerInput(), + "shelf_life": NumberInput(), + } + + +class AddIngredientForms(forms.ModelForm): + """ + Form for add an ingredient + """ + fully_used = forms.BooleanField() + fully_used.initial = True + fully_used.required = False + fully_used.label = _("Fully used") + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # TODO find a better way to get pk (be not url scheme dependant) + pk = get_current_request().path.split('/')[-1] + self.fields['ingredients'].queryset = self.fields['ingredients'].queryset.filter( + polymorphic_ctype__model="transformedfood", + is_ready=False, + end_of_life='', + ).filter(PermissionBackend.filter_queryset(get_current_request(), TransformedFood, "change")).exclude(pk=pk) + + class Meta: + model = TransformedFood + fields = ('ingredients',) diff --git a/apps/food/migrations/0001_initial.py b/apps/food/migrations/0001_initial.py new file mode 100644 index 00000000..706a0590 --- /dev/null +++ b/apps/food/migrations/0001_initial.py @@ -0,0 +1,199 @@ +# Generated by Django 4.2.20 on 2025-04-17 21:43 + +import datetime +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("member", "0013_auto_20240801_1436"), + ] + + operations = [ + migrations.CreateModel( + name="Allergen", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255, verbose_name="name")), + ], + options={ + "verbose_name": "Allergen", + "verbose_name_plural": "Allergens", + }, + ), + migrations.CreateModel( + name="Food", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255, verbose_name="name")), + ("expiry_date", models.DateTimeField(verbose_name="expiry date")), + ( + "end_of_life", + models.CharField(max_length=255, verbose_name="end of life"), + ), + ( + "is_ready", + models.BooleanField(max_length=255, verbose_name="is ready"), + ), + ("order", models.CharField(max_length=255, verbose_name="order")), + ( + "allergens", + models.ManyToManyField( + blank=True, to="food.allergen", verbose_name="allergens" + ), + ), + ( + "owner", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + related_name="+", + to="member.club", + verbose_name="owner", + ), + ), + ( + "polymorphic_ctype", + models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="polymorphic_%(app_label)s.%(class)s_set+", + to="contenttypes.contenttype", + ), + ), + ], + options={ + "verbose_name": "Food", + "verbose_name_plural": "Foods", + }, + ), + migrations.CreateModel( + name="BasicFood", + fields=[ + ( + "food_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="food.food", + ), + ), + ( + "arrival_date", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="arrival date" + ), + ), + ( + "date_type", + models.CharField( + choices=[("DLC", "DLC"), ("DDM", "DDM")], max_length=255 + ), + ), + ], + options={ + "verbose_name": "Basic food", + "verbose_name_plural": "Basic foods", + }, + bases=("food.food",), + ), + migrations.CreateModel( + name="QRCode", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "qr_code_number", + models.PositiveIntegerField( + unique=True, verbose_name="qr code number" + ), + ), + ( + "food_container", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="QR_code", + to="food.food", + verbose_name="food container", + ), + ), + ], + options={ + "verbose_name": "QR-code", + "verbose_name_plural": "QR-codes", + }, + ), + migrations.CreateModel( + name="TransformedFood", + fields=[ + ( + "food_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="food.food", + ), + ), + ( + "creation_date", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="creation date" + ), + ), + ( + "shelf_life", + models.DurationField( + default=datetime.timedelta(days=3), verbose_name="shelf life" + ), + ), + ( + "ingredients", + models.ManyToManyField( + blank=True, + related_name="transformed_ingredient_inv", + to="food.food", + verbose_name="transformed ingredient", + ), + ), + ], + options={ + "verbose_name": "Transformed food", + "verbose_name_plural": "Transformed foods", + }, + bases=("food.food",), + ), + ] diff --git a/apps/food/migrations/__init__.py b/apps/food/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/food/models.py b/apps/food/models.py new file mode 100644 index 00000000..c0b25078 --- /dev/null +++ b/apps/food/models.py @@ -0,0 +1,286 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from datetime import timedelta + +from django.db import models, transaction +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ +from polymorphic.models import PolymorphicModel +from member.models import Club + + +class Allergen(models.Model): + """ + Allergen and alimentary restrictions + """ + name = models.CharField( + verbose_name=_('name'), + max_length=255, + ) + + class Meta: + verbose_name = _("Allergen") + verbose_name_plural = _("Allergens") + + def __str__(self): + return self.name + + +class Food(PolymorphicModel): + """ + Describe any type of food + """ + name = models.CharField( + verbose_name=_("name"), + max_length=255, + ) + + owner = models.ForeignKey( + Club, + on_delete=models.PROTECT, + related_name='+', + verbose_name=_('owner'), + ) + + allergens = models.ManyToManyField( + Allergen, + blank=True, + verbose_name=_('allergens'), + ) + + expiry_date = models.DateTimeField( + verbose_name=_('expiry date'), + null=False, + ) + + end_of_life = models.CharField( + blank=True, + verbose_name=_('end of life'), + max_length=255, + ) + + is_ready = models.BooleanField( + verbose_name=_('is ready'), + max_length=255, + ) + + order = models.CharField( + blank=True, + verbose_name=_('order'), + max_length=255, + ) + + def __str__(self): + return self.name + + @transaction.atomic + def update_allergens(self): + # update parents + for parent in self.transformed_ingredient_inv.iterator(): + old_allergens = list(parent.allergens.all()).copy() + parent.allergens.clear() + for child in parent.ingredients.iterator(): + if child.pk != self.pk: + parent.allergens.set(parent.allergens.union(child.allergens.all())) + parent.allergens.set(parent.allergens.union(self.allergens.all())) + if old_allergens != list(parent.allergens.all()): + parent.save(old_allergens=old_allergens) + + def update_expiry_date(self): + # update parents + for parent in self.transformed_ingredient_inv.iterator(): + old_expiry_date = parent.expiry_date + parent.expiry_date = parent.shelf_life + parent.creation_date + for child in parent.ingredients.iterator(): + if (child.pk != self.pk + and not (child.polymorphic_ctype.model == 'basicfood' + and child.date_type == 'DDM')): + parent.expiry_date = min(parent.expiry_date, child.expiry_date) + + if self.polymorphic_ctype.model == 'basicfood' and self.date_type == 'DLC': + parent.expiry_date = min(parent.expiry_date, self.expiry_date) + if old_expiry_date != parent.expiry_date: + parent.save() + + class Meta: + verbose_name = _('Food') + verbose_name_plural = _('Foods') + + +class BasicFood(Food): + """ + A basic food is a food directly buy and stored + """ + arrival_date = models.DateTimeField( + default=timezone.now, + verbose_name=_('arrival date'), + ) + + date_type = models.CharField( + max_length=255, + choices=( + ("DLC", "DLC"), + ("DDM", "DDM"), + ) + ) + + @transaction.atomic + def save(self, force_insert=False, force_update=False, using=None, update_fields=None, **kwargs): + created = self.pk is None + if not created: + # Check if important fields are updated + old_food = Food.objects.select_for_update().get(pk=self.pk) + if not hasattr(self, "_force_save"): + # Allergens + + if ('old_allergens' in kwargs + and list(self.allergens.all()) != kwargs['old_allergens']): + self.update_allergens() + + # Expiry date + if ((self.expiry_date != old_food.expiry_date + and self.date_type == 'DLC') + or old_food.date_type != self.date_type): + self.update_expiry_date() + + return super().save(force_insert, force_update, using, update_fields) + + @staticmethod + def get_lastests_objects(number, distinct_field, order_by_field): + """ + Get the last object with distinct field and ranked with order_by + This methods exist because we can't distinct with one field and + order with another + """ + foods = BasicFood.objects.order_by(order_by_field).all() + field = [] + for food in foods: + if getattr(food, distinct_field) in field: + continue + else: + field.append(getattr(food, distinct_field)) + number -= 1 + yield food + if not number: + return + + class Meta: + verbose_name = _('Basic food') + verbose_name_plural = _('Basic foods') + + def __str__(self): + return self.name + + +class TransformedFood(Food): + """ + A transformed food is a food with ingredients + """ + creation_date = models.DateTimeField( + default=timezone.now, + verbose_name=_('creation date'), + ) + + # Without microbiological analyzes, the storage time is 3 days + shelf_life = models.DurationField( + default=timedelta(days=3), + verbose_name=_('shelf life'), + ) + + ingredients = models.ManyToManyField( + Food, + blank=True, + symmetrical=False, + related_name='transformed_ingredient_inv', + verbose_name=_('transformed ingredient'), + ) + + def check_cycle(self, ingredients, origin, checked): + for ingredient in ingredients: + if ingredient == origin: + # We break the cycle + self.ingredients.remove(ingredient) + if ingredient.polymorphic_ctype.model == 'transformedfood' and ingredient not in checked: + ingredient.check_cycle(ingredient.ingredients.all(), origin, checked) + checked.append(ingredient) + + @transaction.atomic + def save(self, force_insert=False, force_update=False, using=None, update_fields=None, **kwargs): + created = self.pk is None + if not created: + # Check if important fields are updated + update = {'allergens': False, 'expiry_date': False} + old_food = Food.objects.select_for_update().get(pk=self.pk) + if not hasattr(self, "_force_save"): + # Allergens + # Unfortunately with the many-to-many relation we can't access + # to old allergens + if ('old_allergens' in kwargs + and list(self.allergens.all()) != kwargs['old_allergens']): + update['allergens'] = True + + # Expiry date + update['expiry_date'] = (self.shelf_life != old_food.shelf_life + or self.creation_date != old_food.creation_date) + if update['expiry_date']: + self.expiry_date = self.creation_date + self.shelf_life + # Unfortunately with the set method ingredients are already save, + # we check cycle after if possible + if ('old_ingredients' in kwargs + and list(self.ingredients.all()) != list(kwargs['old_ingredients'])): + update['allergens'] = True + update['expiry_date'] = True + + # it's preferable to keep a queryset but we allow list too + if type(kwargs['old_ingredients']) is list: + kwargs['old_ingredients'] = Food.objects.filter( + pk__in=[food.pk for food in kwargs['old_ingredients']]) + self.check_cycle(self.ingredients.all().difference(kwargs['old_ingredients']), self, []) + if update['allergens']: + self.update_allergens() + if update['expiry_date']: + self.update_expiry_date() + + if created: + self.expiry_date = self.shelf_life + self.creation_date + + # We save here because we need pk for many-to-many relation + super().save(force_insert, force_update, using, update_fields) + + for child in self.ingredients.iterator(): + self.allergens.set(self.allergens.union(child.allergens.all())) + if not (child.polymorphic_ctype.model == 'basicfood' and child.date_type == 'DDM'): + self.expiry_date = min(self.expiry_date, child.expiry_date) + return super().save(force_insert, force_update, using, update_fields) + + class Meta: + verbose_name = _('Transformed food') + verbose_name_plural = _('Transformed foods') + + def __str__(self): + return self.name + + +class QRCode(models.Model): + """ + QR-code for register food + """ + qr_code_number = models.PositiveIntegerField( + unique=True, + verbose_name=_('qr code number'), + ) + + food_container = models.ForeignKey( + Food, + on_delete=models.CASCADE, + related_name='QR_code', + verbose_name=_('food container'), + ) + + class Meta: + verbose_name = _('QR-code') + verbose_name_plural = _('QR-codes') + + def __str__(self): + return _('QR-code number') + ' ' + str(self.qr_code_number) diff --git a/apps/food/tables.py b/apps/food/tables.py new file mode 100644 index 00000000..7789ad76 --- /dev/null +++ b/apps/food/tables.py @@ -0,0 +1,21 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +import django_tables2 as tables + +from .models import Food + + +class FoodTable(tables.Table): + """ + List all foods. + """ + class Meta: + model = Food + template_name = 'django_tables2/bootstrap4.html' + fields = ('name', 'owner', 'allergens', 'expiry_date') + row_attrs = { + 'class': 'table-row', + 'data-href': lambda record: 'detail/' + str(record.pk), + 'style': 'cursor:pointer', + } diff --git a/apps/food/templates/food/food_detail.html b/apps/food/templates/food/food_detail.html new file mode 100644 index 00000000..d330ad64 --- /dev/null +++ b/apps/food/templates/food/food_detail.html @@ -0,0 +1,48 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n crispy_forms_tags %} + +{% block content %} +
+

+ {{ title }} {{ food.name }} +

+
+
    + {% for field, value in fields %} +
  • {{ field }} : {{ value }}
  • + {% endfor %} + {% if meals %} +
  • {% trans "Contained in" %} : + {% for meal in meals %} + {{ meal.name }}{% if not forloop.last %},{% endif %} + {% endfor %} +
  • + {% endif %} + {% if foods %} +
  • {% trans "Contain" %} : + {% for food in foods %} + {{ food.name }}{% if not forloop.last %},{% endif %} + {% endfor %} +
  • + {% endif %} +
+ {% if update %} + + {% trans "Update" %} + + {% endif %} + {% if add_ingredient %} + + {% trans "Add to a meal" %} + + {% endif %} + + {% trans "Return to the food list" %} + +
+
+{% endblock %} diff --git a/apps/food/templates/food/food_list.html b/apps/food/templates/food/food_list.html new file mode 100644 index 00000000..efc7a554 --- /dev/null +++ b/apps/food/templates/food/food_list.html @@ -0,0 +1,71 @@ +{% extends "base_search.html" %} +{% comment %} +Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load render_table from django_tables2 %} +{% load i18n %} + +{% block content %} +{{ block.super }} +
+
+

+ {% trans "Meal served" %} +

+ {% if can_add_meal %} + + {% endif %} + {% if served.data %} + {% render_table served %} + {% else %} +
+
+ {% trans "There is no meal served." %} +
+
+
+ {% endif %} +
+

+ {% trans "Free food" %} +

+ {% if open.data %} + {% render_table open %} + {% else %} +
+
+ {% trans "There is no free food." %} +
+
+ {% endif %} +
+{% if club_tables %} +
+

+ {% trans "Food of your clubs" %} +

+
+ {% for table in club_tables %} +
+

+ {% trans "Food of club" %} {{ table.prefix }} +

+ {% if table.data %} + {% render_table table %} + {% else %} +
+
+ {% trans "Yours club has not food yet." %} +
+
+ {% endif %} +
+ {% endfor %} + {% endif %} + +{% endblock %} diff --git a/apps/food/templates/food/food_update.html b/apps/food/templates/food/food_update.html new file mode 100644 index 00000000..67de3e27 --- /dev/null +++ b/apps/food/templates/food/food_update.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n crispy_forms_tags %} + +{% block content %} +
+

+ {{ title }} +

+
+
+ {% csrf_token %} + {{ form | crispy }} + +
+
+
+{% endblock %} diff --git a/apps/food/templates/food/qrcode.html b/apps/food/templates/food/qrcode.html new file mode 100644 index 00000000..49c9eccb --- /dev/null +++ b/apps/food/templates/food/qrcode.html @@ -0,0 +1,52 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n crispy_forms_tags %} +{% load render_table from django_tables2 %} + +{% block content %} +
+

+ {{ title }} +

+
+
+ {% csrf_token %} + {{ form | crispy }} + +
+
+

+ {% trans "Copy constructor" %} + {% trans "New food" %} +

+ + + + + + + + + + {% for food in last_items %} + + + + + + {% endfor %} + +
+ {% trans "Name" %} + + {% trans "Owner" %} + + {% trans "Expiry date" %} +
{{ food.name }}{{ food.owner }}{{ food.expiry_date }}
+
+
+
+{% endblock %} diff --git a/apps/food/tests/test_food.py b/apps/food/tests/test_food.py new file mode 100644 index 00000000..9c314bf7 --- /dev/null +++ b/apps/food/tests/test_food.py @@ -0,0 +1,170 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from api.tests import TestAPI +from django.contrib.auth.models import User +from django.test import TestCase +from django.urls import reverse +from django.utils import timezone + +from ..api.views import AllergenViewSet, BasicFoodViewSet, TransformedFoodViewSet, QRCodeViewSet +from ..models import Allergen, BasicFood, TransformedFood, QRCode + + +class TestFood(TestCase): + """ + Test food + """ + fixtures = ('initial',) + + def setUp(self): + self.user = User.objects.create_superuser( + username='admintoto', + password='toto1234', + email='toto@example.com' + ) + self.client.force_login(self.user) + + sess = self.client.session + sess['permission_mask'] = 42 + sess.save() + + self.allergen = Allergen.objects.create( + name='allergen', + ) + + self.basicfood = BasicFood.objects.create( + name='basicfood', + owner_id=1, + expiry_date=timezone.now(), + is_ready=False, + date_type='DLC', + ) + + self.transformedfood = TransformedFood.objects.create( + name='transformedfood', + owner_id=1, + expiry_date=timezone.now(), + is_ready=False, + ) + + self.qrcode = QRCode.objects.create( + qr_code_number=1, + food_container=self.basicfood, + ) + + def test_food_list(self): + """ + Display food list + """ + response = self.client.get(reverse('food:food_list')) + self.assertEqual(response.status_code, 200) + + def test_qrcode_create(self): + """ + Display QRCode creation + """ + response = self.client.get(reverse('food:qrcode_create')) + self.assertEqual(response.status_code, 200) + + def test_basicfood_create(self): + """ + Display BasicFood creation + """ + response = self.client.get(reverse('food:basicfood_create')) + self.assertEqual(response.status_code, 200) + + def test_transformedfood_create(self): + """ + Display TransformedFood creation + """ + response = self.client.get(reverse('food:transformedfood_create')) + self.assertEqual(response.status_code, 200) + + def test_food_create(self): + """ + Display Food update + """ + response = self.client.get(reverse('food:food_update')) + self.assertEqual(response.status_code, 200) + + def test_food_view(self): + """ + Display Food detail + """ + response = self.client.get(reverse('food:food_view')) + self.assertEqual(response.status_code, 302) + + def test_basicfood_view(self): + """ + Display BasicFood detail + """ + response = self.client.get(reverse('food:basicfood_view')) + self.assertEqual(response.status_code, 200) + + def test_transformedfood_view(self): + """ + Display TransformedFood detail + """ + response = self.client.get(reverse('food:transformedfood_view')) + self.assertEqual(response.status_code, 200) + + def test_add_ingredient(self): + """ + Display add ingredient view + """ + response = self.client.get(reverse('food:add_ingredient')) + self.assertEqual(response.status_code, 200) + + +class TestFoodAPI(TestAPI): + def setUp(self) -> None: + super().setUP() + + self.allergen = Allergen.objects.create( + name='name', + ) + + self.basicfood = BasicFood.objects.create( + name='basicfood', + owner_id=1, + expiry_date=timezone.now(), + is_ready=False, + date_type='DLC', + ) + + self.transformedfood = TransformedFood.objects.create( + name='transformedfood', + owner_id=1, + expiry_date=timezone.now(), + is_ready=False, + ) + + self.qrcode = QRCode.objects.create( + qr_code_number=1, + food_container=self.basicfood, + ) + + def test_allergen_api(self): + """ + Load Allergen API page and test all filters and permissions + """ + self.check_viewset(AllergenViewSet, '/api/food/allergen/') + + def test_basicfood_api(self): + """ + Load BasicFood API page and test all filters and permissions + """ + self.check_viewset(BasicFoodViewSet, '/api/food/basicfood/') + + def test_transformedfood_api(self): + """ + Load TransformedFood API page and test all filters and permissions + """ + self.check_viewset(TransformedFoodViewSet, '/api/food/transformedfood/') + + def test_qrcode_api(self): + """ + Load QRCode API page and test all filters and permissions + """ + self.check_viewset(QRCodeViewSet, '/api/food/qrcode/') diff --git a/apps/food/urls.py b/apps/food/urls.py new file mode 100644 index 00000000..8137a6f1 --- /dev/null +++ b/apps/food/urls.py @@ -0,0 +1,20 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.urls import path + +from . import views + +app_name = 'food' + +urlpatterns = [ + path('', views.FoodListView.as_view(), name='food_list'), + path('', views.QRCodeCreateView.as_view(), name='qrcode_create'), + path('/add/basic', views.BasicFoodCreateView.as_view(), name='basicfood_create'), + path('add/transformed', views.TransformedFoodCreateView.as_view(), name='transformedfood_create'), + path('update/', views.FoodUpdateView.as_view(), name='food_update'), + path('detail/', views.FoodDetailView.as_view(), name='food_view'), + path('detail/basic/', views.BasicFoodDetailView.as_view(), name='basicfood_view'), + path('detail/transformed/', views.TransformedFoodDetailView.as_view(), name='transformedfood_view'), + path('add/ingredient/', views.AddIngredientView.as_view(), name='add_ingredient'), +] diff --git a/apps/food/utils.py b/apps/food/utils.py new file mode 100644 index 00000000..a08d949a --- /dev/null +++ b/apps/food/utils.py @@ -0,0 +1,53 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.utils.translation import gettext_lazy as _ + +seconds = (_('second'), _('seconds')) +minutes = (_('minute'), _('minutes')) +hours = (_('hour'), _('hours')) +days = (_('day'), _('days')) +weeks = (_('week'), _('weeks')) + + +def plural(x): + if x == 1: + return 0 + return 1 + + +def pretty_duration(duration): + """ + I receive datetime.timedelta object + You receive string object + """ + text = [] + sec = duration.seconds + d = duration.days + + if d >= 7: + w = d // 7 + text.append(str(w) + ' ' + weeks[plural(w)]) + d -= w * 7 + if d > 0: + text.append(str(d) + ' ' + days[plural(d)]) + + if sec >= 3600: + h = sec // 3600 + text.append(str(h) + ' ' + hours[plural(h)]) + sec -= h * 3600 + + if sec >= 60: + m = sec // 60 + text.append(str(m) + ' ' + minutes[plural(m)]) + sec -= m * 60 + + if sec > 0: + text.append(str(sec) + ' ' + seconds[plural(sec)]) + + if len(text) == 0: + return '' + if len(text) == 1: + return text[0] + if len(text) >= 2: + return ', '.join(t for t in text[:-1]) + ' ' + _('and') + ' ' + text[-1] diff --git a/apps/food/views.py b/apps/food/views.py new file mode 100644 index 00000000..a0efd6df --- /dev/null +++ b/apps/food/views.py @@ -0,0 +1,402 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from datetime import timedelta + +from api.viewsets import is_regex +from django_tables2.views import MultiTableMixin +from django.db import transaction +from django.db.models import Q +from django.http import HttpResponseRedirect +from django.views.generic import DetailView, UpdateView +from django.views.generic.list import ListView +from django.urls import reverse_lazy +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ +from member.models import Club, Membership +from permission.backends import PermissionBackend +from permission.views import ProtectQuerysetMixin, ProtectedCreateView, LoginRequiredMixin + +from .models import Food, BasicFood, TransformedFood, QRCode +from .forms import AddIngredientForms, BasicFoodForms, TransformedFoodForms, BasicFoodUpdateForms, TransformedFoodUpdateForms, QRCodeForms +from .tables import FoodTable +from .utils import pretty_duration + + +class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView): + """ + Display Food + """ + model = Food + tables = [FoodTable, FoodTable, FoodTable, ] + extra_context = {"title": _('Food')} + template_name = 'food/food_list.html' + + def get_queryset(self, **kwargs): + return super().get_queryset(**kwargs).distinct() + + def get_tables(self): + bureau_role_pk = 4 + clubs = Club.objects.filter(membership__in=Membership.objects.filter( + user=self.request.user, roles=bureau_role_pk).filter( + date_end__gte=timezone.now())) + + tables = [FoodTable] * (clubs.count() + 3) + self.tables = tables + tables = super().get_tables() + tables[0].prefix = 'search-' + tables[1].prefix = 'open-' + tables[2].prefix = 'served-' + for i in range(clubs.count()): + tables[i + 3].prefix = clubs[i].name + return tables + + def get_tables_data(self): + # table search + qs = self.get_queryset().order_by('name') + if "search" in self.request.GET and self.request.GET['search']: + pattern = self.request.GET['search'] + + # check regex + valid_regex = is_regex(pattern) + suffix = '__iregex' if valid_regex else '__istartswith' + prefix = '^' if valid_regex else '' + qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern})) + else: + qs = qs.none() + search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view')) + # table open + open_table = self.get_queryset().order_by('expiry_date').filter( + Q(polymorphic_ctype__model='transformedfood') + | Q(polymorphic_ctype__model='basicfood', basicfood__date_type='DLC')).filter( + expiry_date__lt=timezone.now()).filter( + PermissionBackend.filter_queryset(self.request, Food, 'view')) + # table served + served_table = self.get_queryset().order_by('-pk').filter( + end_of_life='', is_ready=True) + # tables club + bureau_role_pk = 4 + clubs = Club.objects.filter(membership__in=Membership.objects.filter( + user=self.request.user, roles=bureau_role_pk).filter( + date_end__gte=timezone.now())) + club_table = [] + for club in clubs: + club_table.append(self.get_queryset().order_by('expiry_date').filter( + owner=club, end_of_life='').filter( + PermissionBackend.filter_queryset(self.request, Food, 'view') + )) + return [search_table, open_table, served_table] + club_table + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + tables = context['tables'] + # for extends base_search.html we need to name 'search_table' in 'table' + for name, table in zip(['table', 'open', 'served'], tables): + context[name] = table + context['club_tables'] = tables[3:] + + context['can_add_meal'] = PermissionBackend.check_perm(self.request, 'food.transformedfood_add') + return context + + +class QRCodeCreateView(ProtectQuerysetMixin, ProtectedCreateView): + """ + A view to add qrcode + """ + model = QRCode + template_name = 'food/qrcode.html' + form_class = QRCodeForms + extra_context = {"title": _("Add a new QRCode")} + + def get(self, *args, **kwargs): + qrcode = kwargs["slug"] + if self.model.objects.filter(qr_code_number=qrcode).count() > 0: + pk = self.model.objects.get(qr_code_number=qrcode).food_container.pk + return HttpResponseRedirect(reverse_lazy("food:food_view", kwargs={"pk": pk})) + else: + return super().get(*args, **kwargs) + + @transaction.atomic + def form_valid(self, form): + qrcode_food_form = QRCodeForms(data=self.request.POST) + if not qrcode_food_form.is_valid(): + return self.form_invalid(form) + + qrcode = form.save(commit=False) + qrcode.qr_code_number = self.kwargs['slug'] + qrcode._force_save = True + qrcode.save() + qrcode.refresh_from_db() + return super().form_valid(form) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['slug'] = self.kwargs['slug'] + + # get last 10 BasicFood objects with distincts 'name' ordered by '-pk' + # we can't use .distinct and .order_by with differents columns hence the generator + context['last_items'] = [food for food in BasicFood.get_lastests_objects(10, 'name', '-pk')] + return context + + def get_success_url(self, **kwargs): + self.object.refresh_from_db() + return reverse_lazy('food:food_view', kwargs={'pk': self.object.food_container.pk}) + + def get_sample_object(self): + return QRCode( + qr_code_number=self.kwargs['slug'], + food_container_id=1, + ) + + +class BasicFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView): + """ + A view to add basicfood + """ + model = BasicFood + form_class = BasicFoodForms + extra_context = {"title": _("Add an aliment")} + template_name = "food/food_update.html" + + def get_sample_object(self): + return BasicFood( + name="", + owner_id=1, + expiry_date=timezone.now(), + is_ready=True, + arrival_date=timezone.now(), + date_type='DLC', + ) + + @transaction.atomic + def form_valid(self, form): + if QRCode.objects.filter(qr_code_number=self.kwargs['slug']).count() > 0: + return HttpResponseRedirect(reverse_lazy('food:qrcode_create', kwargs={'slug': self.kwargs['slug']})) + food_form = BasicFoodForms(data=self.request.POST) + if not food_form.is_valid(): + return self.form_invalid(form) + + food = form.save(commit=False) + food.is_ready = False + food.save() + food.refresh_from_db() + + qrcode = QRCode() + qrcode.qr_code_number = self.kwargs['slug'] + qrcode.food_container = food + qrcode.save() + + return super().form_valid(form) + + def get_success_url(self, **kwargs): + self.object.refresh_from_db() + return reverse_lazy('food:basicfood_view', kwargs={"pk": self.object.pk}) + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + + copy = self.request.GET.get('copy', None) + if copy is not None: + food = BasicFood.objects.get(pk=copy) + print(context['form'].fields) + for field in context['form'].fields: + if field == 'allergens': + context['form'].fields[field].initial = getattr(food, field).all() + else: + context['form'].fields[field].initial = getattr(food, field) + + return context + + +class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView): + """ + A view to add transformedfood + """ + model = TransformedFood + form_class = TransformedFoodForms + extra_context = {"title": _("Add a meal")} + template_name = "food/food_update.html" + + def get_sample_object(self): + return TransformedFood( + name="", + owner_id=1, + expiry_date=timezone.now(), + is_ready=True, + ) + + @transaction.atomic + def form_valid(self, form): + form.instance.expiry_date = timezone.now() + timedelta(days=3) + form.instance.is_ready = False + return super().form_valid(form) + + def get_success_url(self, **kwargs): + self.object.refresh_from_db() + return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk}) + + +class AddIngredientView(ProtectQuerysetMixin, UpdateView): + """ + A view to add ingredient to a meal + """ + model = Food + extra_context = {"title": _("Add the ingredient:")} + form_class = AddIngredientForms + template_name = 'food/food_update.html' + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + context['title'] += ' ' + self.object.name + return context + + @transaction.atomic + def form_valid(self, form): + meals = TransformedFood.objects.filter(pk__in=form.data.getlist('ingredients')).all() + for meal in meals: + old_ingredients = list(meal.ingredients.all()).copy() + old_allergens = list(meal.allergens.all()).copy() + meal.ingredients.add(self.object.pk) + # update allergen and expiry date if necessary + if not (self.object.polymorphic_ctype.model == 'basicfood' + and self.object.date_type == 'DDM'): + meal.expiry_date = min(meal.expiry_date, self.object.expiry_date) + meal.allergens.set(meal.allergens.union(self.object.allergens.all())) + meal.save(old_ingredients=old_ingredients, old_allergens=old_allergens) + if 'fully_used' in form.data: + if not self.object.end_of_life: + self.object.end_of_life = _(f'Food fully used in : {meal.name}') + else: + self.object.end_of_life += ', ' + meal.name + if 'fully_used' in form.data: + self.object.is_ready = False + self.object.save() + # We redirect only the first parent + parent_pk = meals[0].pk + return HttpResponseRedirect(self.get_success_url(parent_pk=parent_pk)) + + def get_success_url(self, **kwargs): + return reverse_lazy('food:transformedfood_view', kwargs={"pk": kwargs['parent_pk']}) + + +class FoodUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): + """ + A view to update Food + """ + model = Food + extra_context = {"title": _("Update an aliment")} + template_name = 'food/food_update.html' + + @transaction.atomic + def form_valid(self, form): + form.instance.creater = self.request.user + food = Food.objects.get(pk=self.kwargs['pk']) + old_allergens = list(food.allergens.all()).copy() + + if food.polymorphic_ctype.model == 'transformedfood': + old_ingredients = food.ingredients.all() + form.instance.shelf_life = timedelta( + seconds=int(form.data['shelf_life']) * 60 * 60) + + food_form = self.get_form_class()(data=self.request.POST) + if not food_form.is_valid(): + return self.form_invalid(form) + ans = super().form_valid(form) + if food.polymorphic_ctype.model == 'transformedfood': + form.instance.save(old_ingredients=old_ingredients) + else: + form.instance.save(old_allergens=old_allergens) + return ans + + def get_form_class(self, **kwargs): + food = Food.objects.get(pk=self.kwargs['pk']) + if food.polymorphic_ctype.model == 'basicfood': + return BasicFoodUpdateForms + else: + return TransformedFoodUpdateForms + + def get_form(self, **kwargs): + form = super().get_form(**kwargs) + if 'shelf_life' in form.initial: + hours = form.initial['shelf_life'].days * 24 + form.initial['shelf_life'].seconds // 3600 + form.initial['shelf_life'] = hours + return form + + def get_success_url(self, **kwargs): + self.object.refresh_from_db() + return reverse_lazy('food:food_view', kwargs={"pk": self.object.pk}) + + +class FoodDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): + """ + A view to see a food + """ + model = Food + extra_context = {"title": _('Details of:')} + context_object_name = "food" + template_name = "food/food_detail.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + fields = ["name", "owner", "expiry_date", "allergens", "is_ready", "end_of_life", "order"] + + fields = dict([(field, getattr(self.object, field)) for field in fields]) + if fields["is_ready"]: + fields["is_ready"] = _("Yes") + else: + fields["is_ready"] = _("No") + fields["allergens"] = ", ".join( + allergen.name for allergen in fields["allergens"].all()) + + context["fields"] = [( + Food._meta.get_field(field).verbose_name.capitalize(), + value) for field, value in fields.items()] + context["meals"] = self.object.transformed_ingredient_inv.all() + context["update"] = PermissionBackend.check_perm(self.request, "food.change_food") + context["add_ingredient"] = self.object.end_of_life = '' and PermissionBackend.check_perm(self.request, "food.change_transformedfood") + return context + + def get(self, *args, **kwargs): + model = Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model + if 'stop_redirect' in kwargs and kwargs['stop_redirect']: + return super().get(*args, **kwargs) + kwargs = {'pk': kwargs['pk']} + if model == 'basicfood': + return HttpResponseRedirect(reverse_lazy("food:basicfood_view", kwargs=kwargs)) + return HttpResponseRedirect(reverse_lazy("food:transformedfood_view", kwargs=kwargs)) + + +class BasicFoodDetailView(FoodDetailView): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + fields = ['arrival_date', 'date_type'] + for field in fields: + context["fields"].append(( + BasicFood._meta.get_field(field).verbose_name.capitalize(), + getattr(self.object, field) + )) + return context + + def get(self, *args, **kwargs): + kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'basicfood') + return super().get(*args, **kwargs) + + +class TransformedFoodDetailView(FoodDetailView): + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["fields"].append(( + TransformedFood._meta.get_field("creation_date").verbose_name.capitalize(), + self.object.creation_date + )) + context["fields"].append(( + TransformedFood._meta.get_field("shelf_life").verbose_name.capitalize(), + pretty_duration(self.object.shelf_life) + )) + context["foods"] = self.object.ingredients.all() + return context + + def get(self, *args, **kwargs): + kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'transformedfood') + return super().get(*args, **kwargs) diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index dc2ca4c0..e6433f82 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -3346,7 +3346,7 @@ "food", "transformedfood" ], - "query": "{\"is_ready\": true, \"is_active\": true, \"was_eaten\": false}", + "query": "{\"is_ready\": true}", "type": "view", "mask": 1, "field": "", @@ -3426,7 +3426,7 @@ "food", "basicfood" ], - "query": "{\"is_active\": true}", + "query": "{\"is_ready\": true}", "type": "view", "mask": 3, "field": "", @@ -3442,7 +3442,7 @@ "food", "basicfood" ], - "query": "{\"is_active\": true, \"owner\": [\"club\"]}", + "query": "{\"is_ready\": true, \"owner\": [\"club\"]}", "type": "view", "mask": 3, "field": "", @@ -3474,7 +3474,7 @@ "food", "basicfood" ], - "query": "{\"is_active\": true, \"was_eaten\": false}", + "query": "{\"is_ready\": true}", "type": "change", "mask": 3, "field": "allergens", @@ -3490,7 +3490,7 @@ "food", "basicfood" ], - "query": "{\"is_active\": true, \"was_eaten\": false, \"owner\": [\"club\"]}", + "query": "{\"is_ready\": true, \"owner\": [\"club\"]}", "type": "change", "mask": 3, "field": "allergens", @@ -3554,10 +3554,10 @@ "food", "transformedfood" ], - "query": "{\"is_active\": true}", + "query": "{\"is_ready\": true}", "type": "change", "mask": 3, - "field": "was_eaten", + "field": "end_of_life", "permanent": false, "description": "Indiquer si un plat a été mangé" } @@ -3570,7 +3570,7 @@ "food", "transformedfood" ], - "query": "{\"is_active\": true, \"owner\": [\"club\"]}", + "query": "{\"is_ready\": true, \"owner\": [\"club\"]}", "type": "change", "mask": 3, "field": "is_ready", @@ -3586,10 +3586,10 @@ "food", "transformedfood" ], - "query": "{\"is_active\": true}", + "query": "{\"is_ready\": true}", "type": "change", "mask": 3, - "field": "is_active", + "field": "is_ready", "permanent": false, "description": "Archiver un plat" } @@ -3602,30 +3602,14 @@ "food", "basicfood" ], - "query": "{\"is_active\": true}", + "query": "{\"is_ready\": true}", "type": "change", "mask": 3, - "field": "is_active", + "field": "is_ready", "permanent": false, "description": "Archiver de la bouffe" } }, - { - "model": "permission.permission", - "pk": 230, - "fields": { - "model": [ - "food", - "transformedfood" - ], - "query": "{\"is_active\": true}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir tout les plats actifs" - } - }, { "model": "permission.permission", "pk": 231, @@ -3650,7 +3634,7 @@ "food", "qrcode" ], - "query": "{\"food_container__is_active\": true}", + "query": "{\"food_container__is_ready\": true}", "type": "view", "mask": 3, "field": "", @@ -3666,7 +3650,7 @@ "food", "qrcode" ], - "query": "{\"food_container__owner\": [\"club\"], \"food_container__is_active\": true}", + "query": "{\"food_container__owner\": [\"club\"], \"food_container__is_ready\": true}", "type": "view", "mask": 3, "field": "", @@ -3682,7 +3666,7 @@ "food", "transformedfood" ], - "query": "{\"owner\": [\"club\"], \"is_active\": true}", + "query": "{\"owner\": [\"club\"]}", "type": "change", "mask": 3, "field": "ingredients", @@ -3714,7 +3698,7 @@ "food", "food" ], - "query": "{\"is_active\": true}", + "query": "[]", "type": "view", "mask": 3, "field": "", @@ -3730,7 +3714,7 @@ "food", "food" ], - "query": "{\"is_active\": true, \"owner\": [\"club\"]}", + "query": "{\"owner\": [\"club\"]}", "type": "view", "mask": 3, "field": "", @@ -4601,7 +4585,6 @@ 227, 228, 229, - 230, 232, 234, 236 From a2b42c5329095833eedc79f1f91bf74d115846d8 Mon Sep 17 00:00:00 2001 From: quark Date: Thu, 24 Apr 2025 20:50:32 +0200 Subject: [PATCH 003/153] permission, fixture, translation (fr), bug fixes --- apps/food/fixtures/initial.json | 100 +++++ apps/food/views.py | 10 +- apps/permission/fixtures/initial.json | 614 +++++++------------------- locale/fr/LC_MESSAGES/django.po | 559 ++++++++++++----------- 4 files changed, 585 insertions(+), 698 deletions(-) create mode 100644 apps/food/fixtures/initial.json diff --git a/apps/food/fixtures/initial.json b/apps/food/fixtures/initial.json new file mode 100644 index 00000000..43a0ffe1 --- /dev/null +++ b/apps/food/fixtures/initial.json @@ -0,0 +1,100 @@ +[ + { + "model": "food.allergen", + "pk": 1, + "fields": { + "name": "Lait" + } + }, + { + "model": "food.allergen", + "pk": 2, + "fields": { + "name": "Oeufs" + } + }, + { + "model": "food.allergen", + "pk": 3, + "fields": { + "name": "Gluten" + } + }, + { + "model": "food.allergen", + "pk": 4, + "fields": { + "name": "Fruits à coques" + } + }, + { + "model": "food.allergen", + "pk": 5, + "fields": { + "name": "Arachides" + } + }, + { + "model": "food.allergen", + "pk": 6, + "fields": { + "name": "Sésame" + } + }, + { + "model": "food.allergen", + "pk": 7, + "fields": { + "name": "Soja" + } + }, + { + "model": "food.allergen", + "pk": 8, + "fields": { + "name": "Céléri" + } + }, + { + "model": "food.allergen", + "pk": 9, + "fields": { + "name": "Lupin" + } + }, + { + "model": "food.allergen", + "pk": 10, + "fields": { + "name": "Moutarde" + } + }, + { + "model": "food.allergen", + "pk": 11, + "fields": { + "name": "Sulfites" + } + }, + { + "model": "food.allergen", + "pk": 12, + "fields": { + "name": "Crustacés" + } + }, + { + "model": "food.allergen", + "pk": 13, + "fields": { + "name": "Mollusques" + } + }, + { + "model": "food.allergen", + "pk": 14, + "fields": { + "name": "Poissons" + } + } +] diff --git a/apps/food/views.py b/apps/food/views.py index a0efd6df..96c6b89e 100644 --- a/apps/food/views.py +++ b/apps/food/views.py @@ -73,7 +73,13 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li PermissionBackend.filter_queryset(self.request, Food, 'view')) # table served served_table = self.get_queryset().order_by('-pk').filter( - end_of_life='', is_ready=True) + end_of_life='', is_ready=True).filter( + Q(polymorphic_ctype__model='basicfood', + basicfood__date_type='DLC', + expiry_date__lte=timezone.now(),) + | Q(polymorphic_ctype__model='transformedfood', + expiry_date__lte=timezone.now(), + )) # tables club bureau_role_pk = 4 clubs = Club.objects.filter(membership__in=Membership.objects.filter( @@ -354,7 +360,7 @@ class FoodDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): value) for field, value in fields.items()] context["meals"] = self.object.transformed_ingredient_inv.all() context["update"] = PermissionBackend.check_perm(self.request, "food.change_food") - context["add_ingredient"] = self.object.end_of_life = '' and PermissionBackend.check_perm(self.request, "food.change_transformedfood") + context["add_ingredient"] = (self.object.end_of_life == '' and PermissionBackend.check_perm(self.request, "food.change_transformedfood")) return context def get(self, *args, **kwargs): diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index e6433f82..f1f01dcc 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -3307,436 +3307,184 @@ } }, { - "model": "permission.permission", - "pk": 211, - "fields": { - "model": [ - "food", - "transformedfood" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir tout les plats" - } + "model": "permission.permission", + "pk": 211, + "fields": { + "model": [ + "food", + "qrcode" + ], + "query": "{}", + "type": "view", + "mask": 2, + "permanent": false, + "description": "Voir n'importe quel QR-code" + } }, { - "model": "permission.permission", - "pk": 212, - "fields": { - "model": [ - "food", - "transformedfood" - ], - "query": "{\"owner\": [\"club\"]}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir tout les plats de son club" - } + "model": "permission.permission", + "pk": 212, + "fields": { + "model": [ + "food", + "allergen" + ], + "query": "{}", + "type": "view", + "mask": 1, + "permanent": false, + "description": "Voir n'importe quel allergène" + } }, { - "model": "permission.permission", - "pk": 213, - "fields": { - "model": [ - "food", - "transformedfood" - ], - "query": "{\"is_ready\": true}", - "type": "view", - "mask": 1, - "field": "", - "permanent": false, - "description": "Voir les plats préparés actifs servis" - } + "model": "permission.permission", + "pk": 213, + "fields": { + "model": [ + "food", + "food" + ], + "query": "{}", + "type": "view", + "mask": 2, + "permanent": false, + "description": "Voir n'importe quelle bouffe" + } }, { - "model": "permission.permission", - "pk": 214, - "fields": { - "model": [ - "food", - "qrcode" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Initialiser un QR code de traçabilité" - } + "model": "permission.permission", + "pk": 214, + "fields": { + "model": [ + "food", + "qrcode" + ], + "query": "{}", + "type": "add", + "mask": 2, + "permanent": false, + "description": "Ajouter n'importe quel QR-code" + } }, { - "model": "permission.permission", - "pk": 215, - "fields": { - "model": [ - "food", - "basicfood" - ], - "query": "{\"owner\": [\"club\"]}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Créer un nouvel ingrédient pour son club" - } + "model": "permission.permission", + "pk": 215, + "fields": { + "model": [ + "food", + "food" + ], + "query": "{}", + "type": "add", + "mask": 2, + "permanent": false, + "description": "Ajouter n'importe quelle bouffe" + } }, { - "model": "permission.permission", - "pk": 216, - "fields": { - "model": [ - "food", - "basicfood" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Créer un nouvel ingrédient" - } + "model": "permission.permission", + "pk": 216, + "fields": { + "model": [ + "food", + "food" + ], + "query": "{}", + "type": "change", + "mask": 2, + "permanent": false, + "description": "Modifier n'importe quelle bouffe" + } }, { - "model": "permission.permission", - "pk": 217, - "fields": { - "model": [ - "food", - "basicfood" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir toute la bouffe" - } + "model": "permission.permission", + "pk": 217, + "fields": { + "model": [ + "food", + "qrcode" + ], + "query": "{\"food_container__owner\": [\"club\"]}", + "type": "view", + "mask": 2, + "permanent": false, + "description": "Voir un QR-code lié à son club" + } }, { - "model": "permission.permission", - "pk": 218, - "fields": { - "model": [ - "food", - "basicfood" - ], - "query": "{\"is_ready\": true}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir toute la bouffe active" - } + "model": "permission.permission", + "pk": 218, + "fields": { + "model": [ + "food", + "food" + ], + "query": "{\"owner\": [\"club\"]}", + "type": "view", + "mask": 2, + "permanent": false, + "description": "Voir la bouffe de son club" + } }, { - "model": "permission.permission", - "pk": 219, - "fields": { - "model": [ - "food", - "basicfood" - ], - "query": "{\"is_ready\": true, \"owner\": [\"club\"]}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir la bouffe active de son club" - } + "model": "permission.permission", + "pk": 219, + "fields": { + "model": [ + "food", + "qrcode" + ], + "query": "{\"food_container__owner\": [\"club\"]}", + "type": "add", + "mask": 2, + "permanent": false, + "description": "Ajouter un QR-code pour son club" + } }, { - "model": "permission.permission", - "pk": 220, - "fields": { - "model": [ - "food", - "basicfood" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier de la bouffe" - } + "model": "permission.permission", + "pk": 220, + "fields": { + "model": [ + "food", + "food" + ], + "query": "{\"owner\": [\"club\"]}", + "type": "add", + "mask": 2, + "permanent": false, + "description": "Ajouter de la bouffe appartenant à son club" + } }, { - "model": "permission.permission", - "pk": 221, - "fields": { - "model": [ - "food", - "basicfood" - ], - "query": "{\"is_ready\": true}", - "type": "change", - "mask": 3, - "field": "allergens", - "permanent": false, - "description": "Modifier les allergènes de la bouffe existante" - } + "model": "permission.permission", + "pk": 221, + "fields": { + "model": [ + "food", + "food" + ], + "query": "{\"owner\": [\"club\"]}", + "type": "change", + "mask": 2, + "permanent": false, + "description": "Modifier la bouffe appartenant à son club" + } }, { - "model": "permission.permission", - "pk": 222, - "fields": { - "model": [ - "food", - "basicfood" - ], - "query": "{\"is_ready\": true, \"owner\": [\"club\"]}", - "type": "change", - "mask": 3, - "field": "allergens", - "permanent": false, - "description": "Modifier les allergènes de la bouffe appartenant à son club" - } - }, - { - "model": "permission.permission", - "pk": 223, - "fields": { - "model": [ - "food", - "transformedfood" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Créer un plat" - } - }, - { - "model": "permission.permission", - "pk": 224, - "fields": { - "model": [ - "food", - "transformedfood" - ], - "query": "{\"owner\": [\"club\"]}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Créer un plat pour son club" - } - }, - { - "model": "permission.permission", - "pk": 225, - "fields": { - "model": [ - "food", - "transformedfood" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier tout les plats" - } - }, - { - "model": "permission.permission", - "pk": 226, - "fields": { - "model": [ - "food", - "transformedfood" - ], - "query": "{\"is_ready\": true}", - "type": "change", - "mask": 3, - "field": "end_of_life", - "permanent": false, - "description": "Indiquer si un plat a été mangé" - } - }, - { - "model": "permission.permission", - "pk": 227, - "fields": { - "model": [ - "food", - "transformedfood" - ], - "query": "{\"is_ready\": true, \"owner\": [\"club\"]}", - "type": "change", - "mask": 3, - "field": "is_ready", - "permanent": false, - "description": "Indiquer si un plat de son club est prêt" - } - }, - { - "model": "permission.permission", - "pk": 228, - "fields": { - "model": [ - "food", - "transformedfood" - ], - "query": "{\"is_ready\": true}", - "type": "change", - "mask": 3, - "field": "is_ready", - "permanent": false, - "description": "Archiver un plat" - } - }, - { - "model": "permission.permission", - "pk": 229, - "fields": { - "model": [ - "food", - "basicfood" - ], - "query": "{\"is_ready\": true}", - "type": "change", - "mask": 3, - "field": "is_ready", - "permanent": false, - "description": "Archiver de la bouffe" - } - }, - { - "model": "permission.permission", - "pk": 231, - "fields": { - "model": [ - "food", - "qrcode" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir tous les QR codes" - } - }, - { - "model": "permission.permission", - "pk": 232, - "fields": { - "model": [ - "food", - "qrcode" - ], - "query": "{\"food_container__is_ready\": true}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir tous les QR codes actifs" - } - }, - { - "model": "permission.permission", - "pk": 233, - "fields": { - "model": [ - "food", - "qrcode" - ], - "query": "{\"food_container__owner\": [\"club\"], \"food_container__is_ready\": true}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir tous les QR codes actifs de son club" - } - }, - { - "model": "permission.permission", - "pk" : 234, - "fields": { - "model": [ - "food", - "transformedfood" - ], - "query": "{\"owner\": [\"club\"]}", - "type": "change", - "mask": 3, - "field": "ingredients", - "permanent": false, - "description": "Changer les ingrédients d'un plat actif de son club" - } - }, - { - "model": "permission.permission", - "pk": 235, - "fields": { - "model": [ - "food", - "food" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir bouffe" - } - }, - { - "model": "permission.permission", - "pk": 236, - "fields": { - "model": [ - "food", - "food" - ], - "query": "[]", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir bouffe active" - } - }, - { - "model": "permission.permission", - "pk": 237, - "fields": { - "model": [ - "food", - "food" - ], - "query": "{\"owner\": [\"club\"]}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir bouffe active de son club" - } - }, - { - "model": "permission.permission", - "pk": 238, - "fields": { - "model": [ - "food", - "food" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier bouffe" - } + "model": "permission.permission", + "pk": 222, + "fields": { + "model": [ + "food", + "food" + ], + "query": "{\"end_of_life\": \"\"}", + "type": "view", + "mask": 1, + "permanent": false, + "description": "Voir la bouffe servie" + } }, { "model": "permission.permission", @@ -4343,7 +4091,8 @@ 158, 159, 160, - 213 + 212, + 222 ] } }, @@ -4384,16 +4133,11 @@ 50, 141, 169, - 212, - 214, - 215, - 219, - 222, - 224, - 227, - 233, - 234, - 237, + 217, + 218, + 219, + 220, + 221, 247, 258, 259 @@ -4574,20 +4318,7 @@ 166, 167, 168, - 182, - 212, - 214, - 215, - 218, - 221, - 224, - 226, - 227, - 228, - 229, - 232, - 234, - 236 + 182 ] } }, @@ -4795,8 +4526,7 @@ 168, 176, 177, - 197, - 211 + 197 ] } }, @@ -4824,15 +4554,11 @@ "permissions": [ 137, 211, - 214, - 216, - 217, - 220, - 223, - 225, - 231, - 235, - 238 + 212, + 213, + 214, + 215, + 216 ] } }, diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index e33ee287..14dc6a1e 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: 2025-03-25 11:16+0100\n" +"POT-Creation-Date: 2025-04-24 18:22+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n" "Last-Translator: bleizi \n" "Language-Team: French \n" @@ -25,7 +25,7 @@ msgid "This opener already exists" msgstr "Cette amitié existe déjà" #: apps/activity/apps.py:10 apps/activity/models.py:129 -#: apps/activity/models.py:169 apps/activity/models.py:328 +#: apps/activity/models.py:169 apps/activity/models.py:329 msgid "activity" msgstr "activité" @@ -37,29 +37,29 @@ msgstr "La note du club est inactive." msgid "The end date must be after the start date." msgstr "La date de fin doit être après celle de début." -#: apps/activity/forms.py:83 apps/activity/models.py:276 +#: apps/activity/forms.py:83 apps/activity/models.py:277 msgid "You can't invite someone once the activity is started." msgstr "" "Vous ne pouvez pas inviter quelqu'un une fois que l'activité a démarré." -#: apps/activity/forms.py:86 apps/activity/models.py:279 +#: apps/activity/forms.py:86 apps/activity/models.py:280 msgid "This activity is not validated yet." msgstr "Cette activité n'est pas encore validée." -#: apps/activity/forms.py:96 apps/activity/models.py:287 +#: apps/activity/forms.py:96 apps/activity/models.py:288 msgid "This person has been already invited 5 times this year." msgstr "Cette personne a déjà été invitée 5 fois cette année." -#: apps/activity/forms.py:100 apps/activity/models.py:291 +#: apps/activity/forms.py:100 apps/activity/models.py:292 msgid "This person is already invited." msgstr "Cette personne est déjà invitée." -#: apps/activity/forms.py:104 apps/activity/models.py:295 +#: apps/activity/forms.py:104 apps/activity/models.py:296 msgid "You can't invite more than 3 people to this activity." msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité." -#: apps/activity/models.py:28 apps/activity/models.py:63 apps/food/models.py:42 -#: apps/food/models.py:56 apps/member/models.py:203 +#: apps/activity/models.py:28 apps/activity/models.py:63 apps/food/models.py:18 +#: apps/food/models.py:35 apps/member/models.py:203 #: apps/member/templates/member/includes/club_info.html:4 #: apps/member/templates/member/includes/profile_info.html:4 #: apps/note/models/notes.py:263 apps/note/models/transactions.py:26 @@ -121,7 +121,7 @@ msgid "type" msgstr "type" #: 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/note/models/notes.py:148 apps/treasury/models.py:294 #: apps/wei/models.py:171 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/templates/wei/survey.html:15 msgid "user" @@ -205,21 +205,21 @@ 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:58 +#: apps/activity/models.py:205 apps/activity/tables.py:58 msgid "{:%Y-%m-%d %H:%M:%S}" msgstr "{:%d/%m/%Y %H:%M:%S}" -#: apps/activity/models.py:212 +#: apps/activity/models.py:213 msgid "The balance is negative." msgstr "La note est en négatif." -#: apps/activity/models.py:242 +#: apps/activity/models.py:243 #: apps/treasury/templates/treasury/sogecredit_detail.html:14 #: apps/wei/templates/wei/attribute_bus_1A.html:16 msgid "last name" msgstr "nom de famille" -#: apps/activity/models.py:247 +#: apps/activity/models.py:248 #: apps/member/templates/member/includes/profile_info.html:4 #: apps/registration/templates/registration/future_profile_detail.html:16 #: apps/treasury/templates/treasury/sogecredit_detail.html:17 @@ -228,36 +228,36 @@ msgstr "nom de famille" msgid "first name" msgstr "prénom" -#: apps/activity/models.py:252 +#: apps/activity/models.py:253 msgid "school" msgstr "école" -#: apps/activity/models.py:259 +#: apps/activity/models.py:260 msgid "inviter" msgstr "hôte" -#: apps/activity/models.py:263 +#: apps/activity/models.py:264 msgid "guest" msgstr "invité·e" -#: apps/activity/models.py:264 +#: apps/activity/models.py:265 msgid "guests" msgstr "invité·e·s" -#: apps/activity/models.py:317 +#: apps/activity/models.py:318 msgid "Invitation" msgstr "Invitation" -#: apps/activity/models.py:335 apps/activity/models.py:339 +#: apps/activity/models.py:336 apps/activity/models.py:340 msgid "Opener" msgstr "Ouvreur⋅se" -#: apps/activity/models.py:340 +#: apps/activity/models.py:341 #: apps/activity/templates/activity/activity_detail.html:16 msgid "Openers" msgstr "Ouvreur⋅ses" -#: apps/activity/models.py:344 +#: apps/activity/models.py:345 #, fuzzy, python-brace-format #| msgid "Entry for {note} to the activity {activity}" msgid "{opener} is opener of activity {acivity}" @@ -285,7 +285,7 @@ msgstr "Entré·e le " msgid "remove" msgstr "supprimer" -#: apps/activity/tables.py:84 apps/note/forms.py:69 apps/treasury/models.py:208 +#: apps/activity/tables.py:84 apps/note/forms.py:69 apps/treasury/models.py:209 msgid "Type" msgstr "Type" @@ -382,10 +382,8 @@ msgid "Entry done!" 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:20 -#: apps/food/templates/food/transformedfood_form.html:16 +#: apps/food/templates/food/food_update.html:17 +#: apps/food/templates/food/qrcode.html:18 #: apps/member/templates/member/add_members.html:46 #: apps/member/templates/member/club_form.html:16 #: apps/note/templates/note/transactiontemplate_form.html:18 @@ -493,257 +491,277 @@ msgstr "Entrées pour l'activité « {} »" msgid "API" msgstr "API" -#: apps/food/apps.py:11 apps/food/models.py:105 +#: apps/food/apps.py:11 msgid "food" msgstr "bouffe" -#: apps/food/forms.py:32 -msgid "Fully used" -msgstr "Entièrement utilisé" - #: apps/food/forms.py:50 msgid "Pasta METRO 5kg" msgstr "Pâtes METRO 5kg" -#: apps/food/forms.py:100 +#: apps/food/forms.py:54 apps/food/forms.py:82 +msgid "Specific order given to GCKs" +msgstr "" + +#: apps/food/forms.py:78 msgid "Lasagna" msgstr "Lasagnes" -#: apps/food/models.py:18 -msgid "QR-code number" -msgstr "numéro de QR-code" +#: apps/food/forms.py:117 +msgid "Shelf life (in hours)" +msgstr "Durée de vie (en heure)" -#: apps/food/models.py:26 -msgid "food container" -msgstr "récipient" +#: apps/food/forms.py:139 +msgid "Fully used" +msgstr "Entièrement utilisé" -#: apps/food/models.py:30 -msgid "QR-code" -msgstr "QR-code" - -#: apps/food/models.py:31 -msgid "QR-codes" -msgstr "QR-codes" - -#: apps/food/models.py:34 -#, python-brace-format -msgid "QR-code number {qr_code_number}" -msgstr "numéro du QR-code {qr_code_number}" - -#: apps/food/models.py:47 +#: apps/food/models.py:23 msgid "Allergen" msgstr "Allergène" -#: apps/food/models.py:48 apps/food/templates/food/basicfood_detail.html:17 -#: apps/food/templates/food/transformedfood_detail.html:20 +#: apps/food/models.py:24 msgid "Allergens" msgstr "Allergènes" -#: apps/food/models.py:64 +#: apps/food/models.py:43 msgid "owner" msgstr "propriétaire" -#: apps/food/models.py:70 -msgid "allergen" -msgstr "allergène" +#: apps/food/models.py:49 +msgid "allergens" +msgstr "allergènes" -#: apps/food/models.py:74 +#: apps/food/models.py:53 msgid "expiry date" msgstr "date de péremption" -#: apps/food/models.py:80 -msgid "was eaten" -msgstr "a été mangé" +#: apps/food/models.py:59 +msgid "end of life" +msgstr "fin de vie" -#: apps/food/models.py:89 +#: apps/food/models.py:64 msgid "is ready" msgstr "est prêt" -#: apps/food/models.py:94 -msgid "is active" -msgstr "est en cours" +#: apps/food/models.py:70 +msgid "order" +msgstr "consigne" -#: apps/food/models.py:106 -msgid "foods" -msgstr "bouffes" +#: apps/food/models.py:107 apps/food/views.py:32 +#: note_kfet/templates/base.html:72 +msgid "Food" +msgstr "Bouffe" -#: apps/food/models.py:122 +#: apps/food/models.py:108 +msgid "Foods" +msgstr "Bouffes" + +#: apps/food/models.py:117 msgid "arrival date" msgstr "date d'arrivée" -#: apps/food/models.py:152 +#: apps/food/models.py:169 msgid "Basic food" msgstr "Aliment basique" -#: apps/food/models.py:153 +#: apps/food/models.py:170 msgid "Basic foods" msgstr "Aliments basiques" -#: apps/food/models.py:161 +#: apps/food/models.py:182 msgid "creation date" msgstr "date de création" -#: apps/food/models.py:169 -msgid "transformed ingredient" -msgstr "ingrédients tranformées" - -#: apps/food/models.py:174 +#: apps/food/models.py:188 msgid "shelf life" msgstr "durée de vie" -#: apps/food/models.py:225 apps/food/views.py:375 +#: apps/food/models.py:196 +msgid "transformed ingredient" +msgstr "ingrédients tranformées" + +#: apps/food/models.py:258 msgid "Transformed food" msgstr "Aliment transformé" -#: apps/food/models.py:226 +#: apps/food/models.py:259 msgid "Transformed foods" msgstr "Aliments transformés" -#: 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/models.py:271 +msgid "qr code number" +msgstr "numéro de QR-code" -#: 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/models.py:278 +msgid "food container" +msgstr "récipient" -#: 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" -msgstr "Date de péremption" +#: apps/food/models.py:282 +msgid "QR-code" +msgstr "QR-code" -#: apps/food/templates/food/basicfood_detail.html:24 -#: apps/food/templates/food/transformedfood_detail.html:36 -msgid "Active" -msgstr "Actif" +#: apps/food/models.py:283 +msgid "QR-codes" +msgstr "QR-codes" -#: apps/food/templates/food/basicfood_detail.html:25 -#: apps/food/templates/food/transformedfood_detail.html:37 -msgid "Eaten" -msgstr "Mangé" +#: apps/food/models.py:286 +msgid "QR-code number" +msgstr "numéro de QR-code" -#: apps/food/templates/food/basicfood_detail.html:28 -#: apps/food/templates/food/qrcode_detail.html:20 -#: apps/food/templates/food/qrcode_detail.html:24 -#: apps/food/templates/food/transformedfood_detail.html:41 +#: apps/food/templates/food/food_detail.html:19 +msgid "Contained in" +msgstr "Contenu dans" + +#: apps/food/templates/food/food_detail.html:26 +msgid "Contain" +msgstr "Contient" + +#: apps/food/templates/food/food_detail.html:35 msgid "Update" msgstr "Modifier" -#: apps/food/templates/food/basicfood_detail.html:32 -#: apps/food/templates/food/qrcode_detail.html:34 -#: apps/food/templates/food/transformedfood_detail.html:46 +#: apps/food/templates/food/food_detail.html:40 msgid "Add to a meal" msgstr "Ajouter à un plat" -#: apps/food/templates/food/create_qrcode_form.html:15 -msgid "New basic food" -msgstr "Nouvel aliment basique" +#: apps/food/templates/food/food_detail.html:44 +msgid "Return to the food list" +msgstr "Retour à la liste de nourriture" -#: apps/food/templates/food/create_qrcode_form.html:23 -msgid "Copy constructor" -msgstr "Constructeur de copie" - -#: 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 -#: apps/treasury/models.py:60 -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" - -#: apps/food/templates/food/transformedfood_detail.html:16 -#: apps/food/templates/food/transformedfood_detail.html:35 -msgid "Ready" -msgstr "Prêt" - -#: apps/food/templates/food/transformedfood_detail.html:18 -msgid "Creation date" -msgstr "Date de création" - -#: apps/food/templates/food/transformedfood_detail.html:27 -msgid "Ingredients" -msgstr "Ingrédients" - -#: apps/food/templates/food/transformedfood_detail.html:34 -msgid "Shelf life" -msgstr "Durée de vie" - -#: apps/food/templates/food/transformedfood_list.html:11 +#: apps/food/templates/food/food_list.html:14 msgid "Meal served" msgstr "Plat servis" -#: apps/food/templates/food/transformedfood_list.html:16 +#: apps/food/templates/food/food_list.html:19 msgid "New meal" msgstr "Nouveau plat" -#: apps/food/templates/food/transformedfood_list.html:25 +#: apps/food/templates/food/food_list.html:28 msgid "There is no meal served." msgstr "Il n'y a pas de plat servi." -#: apps/food/templates/food/transformedfood_list.html:33 -msgid "Open" +#: apps/food/templates/food/food_list.html:35 +msgid "Free food" msgstr "Open" -#: apps/food/templates/food/transformedfood_list.html:40 -msgid "There is no free meal." -msgstr "Il n'y a pas de plat en open" +#: apps/food/templates/food/food_list.html:42 +msgid "There is no free food." +msgstr "Il n'y a pas de bouffe en open" -#: apps/food/templates/food/transformedfood_list.html:48 -msgid "All meals" -msgstr "Tout les plats" +#: apps/food/templates/food/food_list.html:50 +msgid "Food of your clubs" +msgstr "Bouffe de tes clubs" -#: apps/food/templates/food/transformedfood_list.html:55 -msgid "There is no meal." -msgstr "Il n'y a pas de plat" +#: apps/food/templates/food/food_list.html:56 +msgid "Food of club" +msgstr "Bouffe du club" -#: apps/food/views.py:28 -msgid "Add the ingredient" -msgstr "Ajouter un ingrédient" +#: apps/food/templates/food/food_list.html:63 +msgid "Yours club has not food yet." +msgstr "Ton club n'a pas de bouffe pour l'instant" -#: apps/food/views.py:42 -msgid "The product is already prepared" -msgstr "Le produit est déjà prêt" +#: apps/food/templates/food/qrcode.html:22 +msgid "Copy constructor" +msgstr "Constructeur de copie" -#: apps/food/views.py:70 -msgid "Update an aliment" -msgstr "Modifier un aliment" +#: apps/food/templates/food/qrcode.html:23 +msgid "New food" +msgstr "Nouvel aliment" -#: apps/food/views.py:97 -msgid "Details of:" -msgstr "Détails de:" +#: apps/food/templates/food/qrcode.html:29 +#: apps/note/templates/note/transaction_form.html:132 +#: apps/treasury/models.py:61 +msgid "Name" +msgstr "Nom" -#: apps/food/views.py:121 -msgid "Add a new basic food with QRCode" -msgstr "Ajouter un nouvel ingrédient avec un QR-code" +#: apps/food/templates/food/qrcode.html:32 +msgid "Owner" +msgstr "Propriétaire" -#: apps/food/views.py:194 +#: apps/food/templates/food/qrcode.html:35 +msgid "Expiry date" +msgstr "Date de péremption" + +#: apps/food/utils.py:6 +msgid "second" +msgstr "seconde" + +#: apps/food/utils.py:6 +msgid "seconds" +msgstr "secondes" + +#: apps/food/utils.py:7 +msgid "minute" +msgstr "minute" + +#: apps/food/utils.py:7 +msgid "minutes" +msgstr "minutes" + +#: apps/food/utils.py:8 +msgid "hour" +msgstr "heure" + +#: apps/food/utils.py:8 +msgid "hours" +msgstr "heures" + +#: apps/food/utils.py:9 +msgid "day" +msgstr "jour" + +#: apps/food/utils.py:9 apps/member/templates/member/includes/club_info.html:27 +msgid "days" +msgstr "jours" + +#: apps/food/utils.py:10 +msgid "week" +msgstr "semaine" + +#: apps/food/utils.py:10 +msgid "weeks" +msgstr "semaines" + +#: apps/food/utils.py:53 +msgid "and" +msgstr "et" + +#: apps/food/views.py:116 msgid "Add a new QRCode" msgstr "Ajouter un nouveau QR-code" -#: apps/food/views.py:245 -msgid "QRCode" -msgstr "QR-code" +#: apps/food/views.py:165 +msgid "Add an aliment" +msgstr "Ajouter un nouvel aliment" -#: apps/food/views.py:281 -msgid "Add a new meal" -msgstr "Ajouter un nouveau plat" +#: apps/food/views.py:224 +msgid "Add a meal" +msgstr "Ajouter un plat" -#: apps/food/views.py:347 -msgid "Update a meal" -msgstr "Modifier le plat" +#: apps/food/views.py:251 +msgid "Add the ingredient:" +msgstr "Ajouter l'ingrédient" + +#: apps/food/views.py:275 +#, python-brace-format +msgid "Food fully used in : {meal.name}" +msgstr "Aliment entièrement utilisé dans : {meal.name}" + +#: apps/food/views.py:294 +msgid "Update an aliment" +msgstr "Modifier un aliment" + +#: apps/food/views.py:342 +msgid "Details of:" +msgstr "Détails de :" + +#: apps/food/views.py:352 apps/treasury/tables.py:149 +msgid "Yes" +msgstr "Oui" + +#: apps/food/views.py:354 apps/member/models.py:99 apps/treasury/tables.py:149 +msgid "No" +msgstr "Non" #: apps/logs/apps.py:11 msgid "Logs" @@ -1044,10 +1062,6 @@ 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:149 -msgid "No" -msgstr "Non" - #: apps/member/models.py:100 msgid "Yes (receive them in french)" msgstr "Oui (les recevoir en français)" @@ -1357,10 +1371,6 @@ msgstr "Il n'y a pas d'adhésion trouvée avec cette entrée." msgid "Club Parent" msgstr "Club parent" -#: apps/member/templates/member/includes/club_info.html:27 -msgid "days" -msgstr "jours" - #: apps/member/templates/member/includes/club_info.html:31 #: apps/wei/templates/wei/base.html:40 msgid "membership fee" @@ -1456,11 +1466,11 @@ msgstr "Introspection :" msgid "Show my applications" msgstr "Voir mes applications" -#: apps/member/templates/member/picture_update.html:38 +#: apps/member/templates/member/picture_update.html:40 msgid "Nevermind" msgstr "Annuler" -#: apps/member/templates/member/picture_update.html:39 +#: apps/member/templates/member/picture_update.html:41 msgid "Crop and upload" msgstr "Recadrer et envoyer" @@ -1899,7 +1909,7 @@ msgstr "Ce champ est requis." msgid "membership transaction" msgstr "transaction d'adhésion" -#: apps/note/models/transactions.py:381 apps/treasury/models.py:300 +#: apps/note/models/transactions.py:381 apps/treasury/models.py:301 msgid "membership transactions" msgstr "transactions d'adhésion" @@ -2512,7 +2522,7 @@ msgstr "Invalider l'inscription" msgid "Treasury" msgstr "Trésorerie" -#: apps/treasury/forms.py:26 apps/treasury/models.py:112 +#: apps/treasury/forms.py:26 apps/treasury/models.py:113 #: apps/treasury/templates/treasury/invoice_form.html:22 msgid "This invoice is locked and can no longer be edited." msgstr "Cette facture est verrouillée et ne peut plus être éditée." @@ -2525,7 +2535,7 @@ msgstr "La remise est déjà fermée." 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/forms.py:125 apps/treasury/models.py:276 #: 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 @@ -2541,139 +2551,139 @@ msgstr "Pas de remise associée" msgid "Invoice identifier" msgstr "Numéro de facture" -#: apps/treasury/models.py:42 apps/wrapped/models.py:28 +#: apps/treasury/models.py:43 apps/wrapped/models.py:28 #: apps/wrapped/models.py:29 msgid "BDE" msgstr "BDE" -#: apps/treasury/models.py:46 +#: apps/treasury/models.py:47 msgid "Quotation" msgstr "Devis" -#: apps/treasury/models.py:51 +#: apps/treasury/models.py:52 msgid "Object" msgstr "Objet" -#: apps/treasury/models.py:55 +#: apps/treasury/models.py:56 msgid "Description" msgstr "Description" -#: apps/treasury/models.py:64 +#: apps/treasury/models.py:65 msgid "Address" msgstr "Adresse" -#: apps/treasury/models.py:69 apps/treasury/models.py:202 +#: apps/treasury/models.py:70 apps/treasury/models.py:203 msgid "Date" msgstr "Date" -#: apps/treasury/models.py:75 +#: apps/treasury/models.py:76 msgid "Payment date" msgstr "Date de paiement" -#: apps/treasury/models.py:79 +#: apps/treasury/models.py:80 msgid "Acquitted" msgstr "Acquittée" -#: apps/treasury/models.py:84 +#: apps/treasury/models.py:85 msgid "Locked" msgstr "Verrouillée" -#: apps/treasury/models.py:85 +#: apps/treasury/models.py:86 msgid "An invoice can't be edited when it is locked." msgstr "Une facture ne peut plus être modifiée si elle est verrouillée." -#: apps/treasury/models.py:91 +#: apps/treasury/models.py:92 msgid "tex source" msgstr "fichier TeX source" -#: apps/treasury/models.py:95 apps/treasury/models.py:140 +#: apps/treasury/models.py:96 apps/treasury/models.py:141 msgid "invoice" msgstr "facture" -#: apps/treasury/models.py:96 +#: apps/treasury/models.py:97 msgid "invoices" msgstr "factures" -#: apps/treasury/models.py:99 +#: apps/treasury/models.py:100 #, python-brace-format msgid "Invoice #{id}" msgstr "Facture n°{id}" -#: apps/treasury/models.py:145 +#: apps/treasury/models.py:146 msgid "Designation" msgstr "Désignation" -#: apps/treasury/models.py:151 +#: apps/treasury/models.py:152 msgid "Quantity" msgstr "Quantité" -#: apps/treasury/models.py:156 +#: apps/treasury/models.py:157 msgid "Unit price" msgstr "Prix unitaire" -#: apps/treasury/models.py:160 +#: apps/treasury/models.py:161 msgid "product" msgstr "produit" -#: apps/treasury/models.py:161 +#: apps/treasury/models.py:162 msgid "products" msgstr "produits" -#: apps/treasury/models.py:189 +#: apps/treasury/models.py:190 msgid "remittance type" msgstr "type de remise" -#: apps/treasury/models.py:190 +#: apps/treasury/models.py:191 msgid "remittance types" msgstr "types de remises" -#: apps/treasury/models.py:213 +#: apps/treasury/models.py:214 msgid "Comment" msgstr "Commentaire" -#: apps/treasury/models.py:218 +#: apps/treasury/models.py:219 msgid "Closed" msgstr "Fermée" -#: apps/treasury/models.py:222 +#: apps/treasury/models.py:223 msgid "remittance" msgstr "remise" -#: apps/treasury/models.py:223 +#: apps/treasury/models.py:224 msgid "remittances" msgstr "remises" -#: apps/treasury/models.py:226 +#: apps/treasury/models.py:227 msgid "Remittance #{:d}: {}" msgstr "Remise n°{:d} : {}" -#: apps/treasury/models.py:279 +#: apps/treasury/models.py:280 msgid "special transaction proxy" msgstr "proxy de transaction spéciale" -#: apps/treasury/models.py:280 +#: apps/treasury/models.py:281 msgid "special transaction proxies" msgstr "proxys de transactions spéciales" -#: apps/treasury/models.py:306 +#: apps/treasury/models.py:307 msgid "credit transaction" msgstr "transaction de crédit" -#: apps/treasury/models.py:311 +#: apps/treasury/models.py:312 #: apps/treasury/templates/treasury/sogecredit_detail.html:10 msgid "Credit from the Société générale" msgstr "Crédit de la Société générale" -#: apps/treasury/models.py:312 +#: apps/treasury/models.py:313 msgid "Credits from the Société générale" msgstr "Crédits de la Société générale" -#: apps/treasury/models.py:315 +#: apps/treasury/models.py:316 #, python-brace-format msgid "Soge credit for {user}" msgstr "Crédit de la société générale pour l'utilisateur·rice {user}" -#: apps/treasury/models.py:445 +#: apps/treasury/models.py:446 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." @@ -2702,10 +2712,6 @@ msgstr "Nombre de transactions" msgid "View" msgstr "Voir" -#: apps/treasury/tables.py:149 -msgid "Yes" -msgstr "Oui" - #: apps/treasury/templates/treasury/invoice_confirm_delete.html:10 #: apps/treasury/views.py:174 msgid "Delete invoice" @@ -3812,10 +3818,6 @@ msgstr "" msgid "Reset" msgstr "Réinitialiser" -#: note_kfet/templates/base.html:72 -msgid "Food" -msgstr "Bouffe" - #: note_kfet/templates/base.html:84 msgid "Users" msgstr "Utilisateur·rices" @@ -4125,6 +4127,67 @@ msgstr "" "d'adhésion. Vous devez également valider votre adresse email en suivant le " "lien que vous avez reçu." +#, python-brace-format +#~ msgid "QR-code number {qr_code_number}" +#~ msgstr "numéro du QR-code {qr_code_number}" + +#~ msgid "was eaten" +#~ msgstr "a été mangé" + +#~ msgid "is active" +#~ msgstr "est en cours" + +#~ msgid "foods" +#~ msgstr "bouffes" + +#~ msgid "Arrival date" +#~ msgstr "Date d'arrivée" + +#~ msgid "Active" +#~ msgstr "Actif" + +#~ msgid "Eaten" +#~ msgstr "Mangé" + +#~ msgid "number" +#~ msgstr "numéro" + +#~ msgid "View details" +#~ msgstr "Voir plus" + +#~ msgid "Ready" +#~ msgstr "Prêt" + +#~ msgid "Creation date" +#~ msgstr "Date de création" + +#~ msgid "Ingredients" +#~ msgstr "Ingrédients" + +#~ msgid "Open" +#~ msgstr "Open" + +#~ msgid "All meals" +#~ msgstr "Tout les plats" + +#~ msgid "There is no meal." +#~ msgstr "Il n'y a pas de plat" + +#~ msgid "The product is already prepared" +#~ msgstr "Le produit est déjà prêt" + +#~ msgid "Add a new basic food with QRCode" +#~ msgstr "Ajouter un nouvel ingrédient avec un QR-code" + +#~ msgid "QRCode" +#~ msgstr "QR-code" + +#~ msgid "Add a new meal" +#~ msgstr "Ajouter un nouveau plat" + +#~ msgid "Update a meal" +#~ msgstr "Modifier le plat" + #, fuzzy #~| msgid "invalidate" #~ msgid "Enter a valid color." @@ -4180,11 +4243,6 @@ msgstr "" #~ msgid "Enter a number." #~ msgstr "numéro de téléphone" -#, fuzzy -#~| msgid "add" -#~ msgid "and" -#~ msgstr "ajouter" - #, fuzzy, python-format #~| msgid "A template with this name already exist" #~ msgid "%(model_name)s with this %(field_labels)s already exists." @@ -4446,9 +4504,6 @@ msgstr "" #~ msgid "Free" #~ msgstr "Open" -#~ msgid "Add a new aliment" -#~ msgstr "Ajouter un nouvel aliment" - #, fuzzy #~| msgid "Transformed food" #~ msgid "New transformed food" From b4f3a158a64d95f69e56b986f7cad91a4fe83c6a Mon Sep 17 00:00:00 2001 From: quark Date: Sun, 27 Apr 2025 16:54:58 +0200 Subject: [PATCH 004/153] fix permission bug --- apps/food/fixtures/initial.json | 2 +- apps/food/views.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/food/fixtures/initial.json b/apps/food/fixtures/initial.json index 43a0ffe1..2442b92f 100644 --- a/apps/food/fixtures/initial.json +++ b/apps/food/fixtures/initial.json @@ -52,7 +52,7 @@ "model": "food.allergen", "pk": 8, "fields": { - "name": "Céléri" + "name": "Céleri" } }, { diff --git a/apps/food/views.py b/apps/food/views.py index 96c6b89e..90d6e8b7 100644 --- a/apps/food/views.py +++ b/apps/food/views.py @@ -8,7 +8,7 @@ from django_tables2.views import MultiTableMixin from django.db import transaction from django.db.models import Q from django.http import HttpResponseRedirect -from django.views.generic import DetailView, UpdateView +from django.views.generic import DetailView, UpdateView, CreateView from django.views.generic.list import ListView from django.urls import reverse_lazy from django.utils import timezone @@ -106,7 +106,7 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li return context -class QRCodeCreateView(ProtectQuerysetMixin, ProtectedCreateView): +class QRCodeCreateView(ProtectQuerysetMixin, CreateView): """ A view to add qrcode """ @@ -260,6 +260,8 @@ class AddIngredientView(ProtectQuerysetMixin, UpdateView): @transaction.atomic def form_valid(self, form): meals = TransformedFood.objects.filter(pk__in=form.data.getlist('ingredients')).all() + if not meals: + return HttpResponseRedirect(reverse_lazy('food:food_view', kwargs={"pk": self.object.pk})) for meal in meals: old_ingredients = list(meal.ingredients.all()).copy() old_allergens = list(meal.allergens.all()).copy() @@ -385,7 +387,8 @@ class BasicFoodDetailView(FoodDetailView): return context def get(self, *args, **kwargs): - kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'basicfood') + if Food.objects.filter(pk=kwargs['pk']).count() == 1: + kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'basicfood') return super().get(*args, **kwargs) @@ -404,5 +407,6 @@ class TransformedFoodDetailView(FoodDetailView): return context def get(self, *args, **kwargs): - kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'transformedfood') + if Food.objects.filter(pk=kwargs['pk']).count() == 1: + kwargs['stop_redirect'] = (Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model == 'transformedfood') return super().get(*args, **kwargs) From ad0a219ed3ba7dd8fd6a1a39096b14198c7707e2 Mon Sep 17 00:00:00 2001 From: quark Date: Wed, 30 Apr 2025 11:59:08 +0200 Subject: [PATCH 005/153] Add manage ingredient feature, fix some bug --- apps/food/api/serializers.py | 12 +- apps/food/api/urls.py | 3 +- apps/food/api/views.py | 17 ++- apps/food/forms.py | 38 +++++- apps/food/templates/food/food_detail.html | 5 + .../templates/food/manage_ingredients.html | 116 ++++++++++++++++++ .../food/transformedfood_update.html | 87 +++++++++++++ apps/food/urls.py | 1 + apps/food/views.py | 81 +++++++++++- locale/fr/LC_MESSAGES/django.po | 80 ++++++++---- 10 files changed, 407 insertions(+), 33 deletions(-) create mode 100644 apps/food/templates/food/manage_ingredients.html create mode 100644 apps/food/templates/food/transformedfood_update.html diff --git a/apps/food/api/serializers.py b/apps/food/api/serializers.py index fa0641e8..eb1621b6 100644 --- a/apps/food/api/serializers.py +++ b/apps/food/api/serializers.py @@ -3,7 +3,7 @@ from rest_framework import serializers -from ..models import Allergen, BasicFood, TransformedFood, QRCode +from ..models import Allergen, Food, BasicFood, TransformedFood, QRCode class AllergenSerializer(serializers.ModelSerializer): @@ -16,6 +16,16 @@ class AllergenSerializer(serializers.ModelSerializer): fields = '__all__' +class FoodSerializer(serializers.ModelSerializer): + """ + REST API Serializer for Food. + The djangorestframework plugin will analyse the model `Food` and parse all fields in the API. + """ + class Meta: + model = Food + fields = '__all__' + + class BasicFoodSerializer(serializers.ModelSerializer): """ REST API Serializer for BasicFood. diff --git a/apps/food/api/urls.py b/apps/food/api/urls.py index 5a8ce881..8fa6995d 100644 --- a/apps/food/api/urls.py +++ b/apps/food/api/urls.py @@ -1,7 +1,7 @@ # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .views import AllergenViewSet, BasicFoodViewSet, TransformedFoodViewSet, QRCodeViewSet +from .views import AllergenViewSet, FoodViewSet, BasicFoodViewSet, TransformedFoodViewSet, QRCodeViewSet def register_food_urls(router, path): @@ -9,6 +9,7 @@ def register_food_urls(router, path): Configure router for Food REST API. """ router.register(path + '/allergen', AllergenViewSet) + router.register(path + '/food', FoodViewSet) router.register(path + '/basicfood', BasicFoodViewSet) router.register(path + '/transformedfood', TransformedFoodViewSet) router.register(path + '/qrcode', QRCodeViewSet) diff --git a/apps/food/api/views.py b/apps/food/api/views.py index 2c75a570..0aead0de 100644 --- a/apps/food/api/views.py +++ b/apps/food/api/views.py @@ -5,8 +5,8 @@ from api.viewsets import ReadProtectedModelViewSet from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import SearchFilter -from .serializers import AllergenSerializer, BasicFoodSerializer, TransformedFoodSerializer, QRCodeSerializer -from ..models import Allergen, BasicFood, TransformedFood, QRCode +from .serializers import AllergenSerializer, FoodSerializer, BasicFoodSerializer, TransformedFoodSerializer, QRCodeSerializer +from ..models import Allergen, Food, BasicFood, TransformedFood, QRCode class AllergenViewSet(ReadProtectedModelViewSet): @@ -22,6 +22,19 @@ class AllergenViewSet(ReadProtectedModelViewSet): search_fields = ['$name', ] +class FoodViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Food` objects, serialize it to JSON with the given serializer, + then render it on /api/food/food/ + """ + queryset = Food.objects.order_by('id') + serializer_class = FoodSerializer + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name', ] + search_fields = ['$name', ] + + class BasicFoodViewSet(ReadProtectedModelViewSet): """ REST API View set. diff --git a/apps/food/forms.py b/apps/food/forms.py index c823b0b1..dfa32008 100644 --- a/apps/food/forms.py +++ b/apps/food/forms.py @@ -12,7 +12,7 @@ from note_kfet.inputs import Autocomplete from note_kfet.middlewares import get_current_request from permission.backends import PermissionBackend -from .models import BasicFood, TransformedFood, QRCode +from .models import Food, BasicFood, TransformedFood, QRCode class QRCodeForms(forms.ModelForm): @@ -22,7 +22,6 @@ class QRCodeForms(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['food_container'].queryset = self.fields['food_container'].queryset.filter( - is_ready=False, end_of_life__isnull=True, polymorphic_ctype__model='transformedfood', ).filter(PermissionBackend.filter_queryset( @@ -151,3 +150,38 @@ class AddIngredientForms(forms.ModelForm): class Meta: model = TransformedFood fields = ('ingredients',) + + +class ManageIngredientsForm(forms.Form): + """ + Form to manage ingredient + """ + fully_used = forms.BooleanField() + fully_used.initial = True + fully_used.required = True + fully_used.label = _('Fully used') + + name = forms.CharField() + name.widget = Autocomplete( + model=Food, + resetable=True, + attrs={"api_url": "/api/food/food", + "class": "autocomplete"}, + ) + name.label = _('Name') + + qrcode = forms.IntegerField() + qrcode.widget = Autocomplete( + model=QRCode, + resetable=True, + attrs={"api_url": "/api/food/qrcode/", + "name_field": "qr_code_number", + "class": "autocomplete"}, + ) + qrcode.label = _('QR code number') + + +ManageIngredientsFormSet = forms.formset_factory( + ManageIngredientsForm, + extra=1, +) diff --git a/apps/food/templates/food/food_detail.html b/apps/food/templates/food/food_detail.html index d330ad64..9343f6d1 100644 --- a/apps/food/templates/food/food_detail.html +++ b/apps/food/templates/food/food_detail.html @@ -39,6 +39,11 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "Add to a meal" %} + {% endif %} + {% if manage_ingredients %} + + {% trans "Manage ingredients" %} + {% endif %} {% trans "Return to the food list" %} diff --git a/apps/food/templates/food/manage_ingredients.html b/apps/food/templates/food/manage_ingredients.html new file mode 100644 index 00000000..0dd7acb5 --- /dev/null +++ b/apps/food/templates/food/manage_ingredients.html @@ -0,0 +1,116 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n crispy_forms_tags %} + +{% block content %} +
+

+ {{ title }} +

+
+
+ {% csrf_token %} + + {# Fill initial data #} + {% for display, form in formset %} + {% if forloop.first %} + + + + + + + + + {% endif %} + {% if display %} + + {% else %} + + {% endif %} + + + + + {% endfor %} + +
{{ form.name.label }}{{ form.qrcode.label }}{{ form.fully_used.label }}
+ + {# Display buttons to add and remove ingredients #} +
+
+ + +
+ +
+
+
+ +{% endblock %} +{% block extrajavascript %} + +{% endblock %} diff --git a/apps/food/templates/food/transformedfood_update.html b/apps/food/templates/food/transformedfood_update.html new file mode 100644 index 00000000..820970b7 --- /dev/null +++ b/apps/food/templates/food/transformedfood_update.html @@ -0,0 +1,87 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n crispy_forms_tags %} + +{% block content %} +
+

+ {{ title }} +

+
+
+ {% csrf_token %} + {{ form | crispy }} + + {# Fill initial data #} + {% for ingredient_form in formset %} + {% if forloop.first %} + + + + + + + + {% endif %} + + {{ ingredient_form | crispy }} + + + + + {% endfor %} + +
{% trans "Name" %}{% trans "QR-code number" %}{% trans "Fully used" %} +
{{ ingredient_form.name }}{{ ingredient_form.qrcode }}{{ ingredient_form.fully_used }}
+ {# Display buttons to add and remove products #} +
+
+ + +
+ +
+
+
+
+ +{# Hidden div that store an empty product form, to be copied into new forms #} + +{% endblock %} +{% block extrajavascript %} + +{% endblock %} diff --git a/apps/food/urls.py b/apps/food/urls.py index 8137a6f1..81acccdd 100644 --- a/apps/food/urls.py +++ b/apps/food/urls.py @@ -13,6 +13,7 @@ urlpatterns = [ path('/add/basic', views.BasicFoodCreateView.as_view(), name='basicfood_create'), path('add/transformed', views.TransformedFoodCreateView.as_view(), name='transformedfood_create'), path('update/', views.FoodUpdateView.as_view(), name='food_update'), + path('update/ingredients/', views.ManageIngredientsView.as_view(), name='manage_ingredients'), path('detail/', views.FoodDetailView.as_view(), name='food_view'), path('detail/basic/', views.BasicFoodDetailView.as_view(), name='basicfood_view'), path('detail/transformed/', views.TransformedFoodDetailView.as_view(), name='transformedfood_view'), diff --git a/apps/food/views.py b/apps/food/views.py index 90d6e8b7..4c6ad57e 100644 --- a/apps/food/views.py +++ b/apps/food/views.py @@ -18,7 +18,9 @@ from permission.backends import PermissionBackend from permission.views import ProtectQuerysetMixin, ProtectedCreateView, LoginRequiredMixin from .models import Food, BasicFood, TransformedFood, QRCode -from .forms import AddIngredientForms, BasicFoodForms, TransformedFoodForms, BasicFoodUpdateForms, TransformedFoodUpdateForms, QRCodeForms +from .forms import QRCodeForms, BasicFoodForms, TransformedFoodForms, \ + ManageIngredientsForm, ManageIngredientsFormSet, AddIngredientForms, \ + BasicFoodUpdateForms, TransformedFoodUpdateForms from .tables import FoodTable from .utils import pretty_duration @@ -73,7 +75,7 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li PermissionBackend.filter_queryset(self.request, Food, 'view')) # table served served_table = self.get_queryset().order_by('-pk').filter( - end_of_life='', is_ready=True).filter( + end_of_life='', is_ready=True).exclude( Q(polymorphic_ctype__model='basicfood', basicfood__date_type='DLC', expiry_date__lte=timezone.now(),) @@ -106,7 +108,7 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li return context -class QRCodeCreateView(ProtectQuerysetMixin, CreateView): +class QRCodeCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): """ A view to add qrcode """ @@ -238,12 +240,82 @@ class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView): form.instance.is_ready = False return super().form_valid(form) + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + context['title'] += ' ' + self.object.name + return context + def get_success_url(self, **kwargs): self.object.refresh_from_db() return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk}) -class AddIngredientView(ProtectQuerysetMixin, UpdateView): +MAX_FORMS = 10 + + +class ManageIngredientsView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): + """ + A view to manage ingredient for a transformed food + """ + model = TransformedFood + fields = ['ingredients'] + extra_context = {"title": _("Manage ingredients of:")} + template_name = 'food/manage_ingredients.html' + + @transaction.atomic + def form_valid(self, form): + old_ingredients = list(self.object.ingredients.all()).copy() + old_allergens = list(self.object.allergens.all()).copy() + self.object.ingredients.clear() + for i in range(self.object.ingredients.all().count() + 1 + MAX_FORMS): + prefix = 'form-' + str(i) + '-' + if form.data[prefix + 'qrcode'] not in ['0', '']: + ingredient = QRCode.objects.get(pk=form.data[prefix + 'qrcode']).food_container + self.object.ingredients.add(ingredient) + if (prefix + 'fully_used') in form.data and form.data[prefix + 'fully_used'] == 'on': + ingredient.end_of_life = _('Fully used in {meal}'.format( + meal=self.object.name)) + ingredient.save() + + elif form.data[prefix + 'name'] != '': + ingredient = Food.objects.get(pk=form.data[prefix + 'name']) + self.object.ingredients.add(ingredient) + if (prefix + 'fully_used') in form.data and form.data[prefix + 'fully_used'] == 'on': + ingredient.end_of_life = _('Fully used in {meal}'.format( + meal=self.object.name)) + ingredient.save() + + self.object.save(old_ingredients=old_ingredients, old_allergens=old_allergens) + return HttpResponseRedirect(self.get_success_url()) + + def get_context_data(self, *args, **kwargs): + context = super().get_context_data(*args, **kwargs) + context['title'] += ' ' + self.object.name + formset = ManageIngredientsFormSet() + ingredients = self.object.ingredients.all() + formset.extra += ingredients.count() + MAX_FORMS + context['form'] = ManageIngredientsForm() + context['ingredients_count'] = ingredients.count() + display = [True] * (1 + ingredients.count()) + [False] * (formset.extra - ingredients.count() - 1) + context['formset'] = zip(display, formset) + context['ingredients'] = [] + for ingredient in ingredients: + qr = QRCode.objects.filter(food_container=ingredient) + + context['ingredients'].append({ + 'food_pk': ingredient.pk, + 'food_name': ingredient.name, + 'qr_pk': '' if qr.count() == 0 else qr[0].pk, + 'qr_number': '' if qr.count() == 0 else qr[0].qr_code_number, + 'fully_used': 'true' if ingredient.end_of_life else '', + }) + return context + + def get_success_url(self, **kwargs): + return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk}) + + +class AddIngredientView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): """ A view to add ingredient to a meal """ @@ -404,6 +476,7 @@ class TransformedFoodDetailView(FoodDetailView): pretty_duration(self.object.shelf_life) )) context["foods"] = self.object.ingredients.all() + context["manage_ingredients"] = True return context def get(self, *args, **kwargs): diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 14dc6a1e..23ba06dd 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: 2025-04-24 18:22+0200\n" +"POT-Creation-Date: 2025-04-30 11:44+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n" "Last-Translator: bleizi \n" "Language-Team: French \n" @@ -383,7 +383,9 @@ msgstr "Entrée effectuée !" #: apps/activity/templates/activity/activity_form.html:16 #: apps/food/templates/food/food_update.html:17 +#: apps/food/templates/food/manage_ingredients.html:48 #: apps/food/templates/food/qrcode.html:18 +#: apps/food/templates/food/transformedfood_update.html:45 #: apps/member/templates/member/add_members.html:46 #: apps/member/templates/member/club_form.html:16 #: apps/note/templates/note/transactiontemplate_form.html:18 @@ -495,26 +497,40 @@ msgstr "API" msgid "food" msgstr "bouffe" -#: apps/food/forms.py:50 +#: apps/food/forms.py:49 msgid "Pasta METRO 5kg" msgstr "Pâtes METRO 5kg" -#: apps/food/forms.py:54 apps/food/forms.py:82 +#: apps/food/forms.py:53 apps/food/forms.py:81 msgid "Specific order given to GCKs" msgstr "" -#: apps/food/forms.py:78 +#: apps/food/forms.py:77 msgid "Lasagna" msgstr "Lasagnes" -#: apps/food/forms.py:117 +#: apps/food/forms.py:116 msgid "Shelf life (in hours)" msgstr "Durée de vie (en heure)" -#: apps/food/forms.py:139 +#: apps/food/forms.py:138 apps/food/forms.py:162 +#: apps/food/templates/food/transformedfood_update.html:25 msgid "Fully used" msgstr "Entièrement utilisé" +#: apps/food/forms.py:171 apps/food/templates/food/qrcode.html:29 +#: apps/food/templates/food/transformedfood_update.html:23 +#: apps/note/templates/note/transaction_form.html:132 +#: apps/treasury/models.py:61 +msgid "Name" +msgstr "Nom" + +#: apps/food/forms.py:181 +#, fuzzy +#| msgid "QR-code number" +msgid "QR code number" +msgstr "numéro de QR-code" + #: apps/food/models.py:23 msgid "Allergen" msgstr "Allergène" @@ -547,7 +563,7 @@ msgstr "est prêt" msgid "order" msgstr "consigne" -#: apps/food/models.py:107 apps/food/views.py:32 +#: apps/food/models.py:107 apps/food/views.py:34 #: note_kfet/templates/base.html:72 msgid "Food" msgstr "Bouffe" @@ -605,6 +621,7 @@ msgid "QR-codes" msgstr "QR-codes" #: apps/food/models.py:286 +#: apps/food/templates/food/transformedfood_update.html:24 msgid "QR-code number" msgstr "numéro de QR-code" @@ -624,7 +641,11 @@ msgstr "Modifier" msgid "Add to a meal" msgstr "Ajouter à un plat" -#: apps/food/templates/food/food_detail.html:44 +#: apps/food/templates/food/food_detail.html:45 +msgid "Manage ingredients" +msgstr "Gérer les ingrédients" + +#: apps/food/templates/food/food_detail.html:49 msgid "Return to the food list" msgstr "Retour à la liste de nourriture" @@ -660,6 +681,16 @@ msgstr "Bouffe du club" msgid "Yours club has not food yet." msgstr "Ton club n'a pas de bouffe pour l'instant" +#: apps/food/templates/food/manage_ingredients.html:45 +#: apps/food/templates/food/transformedfood_update.html:42 +msgid "Add ingredient" +msgstr "Ajouter un ingrédient" + +#: apps/food/templates/food/manage_ingredients.html:46 +#: apps/food/templates/food/transformedfood_update.html:43 +msgid "Remove ingredient" +msgstr "Enlever un ingrédient" + #: apps/food/templates/food/qrcode.html:22 msgid "Copy constructor" msgstr "Constructeur de copie" @@ -668,12 +699,6 @@ msgstr "Constructeur de copie" msgid "New food" msgstr "Nouvel aliment" -#: apps/food/templates/food/qrcode.html:29 -#: apps/note/templates/note/transaction_form.html:132 -#: apps/treasury/models.py:61 -msgid "Name" -msgstr "Nom" - #: apps/food/templates/food/qrcode.html:32 msgid "Owner" msgstr "Propriétaire" @@ -726,40 +751,49 @@ msgstr "semaines" msgid "and" msgstr "et" -#: apps/food/views.py:116 +#: apps/food/views.py:118 msgid "Add a new QRCode" msgstr "Ajouter un nouveau QR-code" -#: apps/food/views.py:165 +#: apps/food/views.py:167 msgid "Add an aliment" msgstr "Ajouter un nouvel aliment" -#: apps/food/views.py:224 +#: apps/food/views.py:226 msgid "Add a meal" msgstr "Ajouter un plat" -#: apps/food/views.py:251 +#: apps/food/views.py:262 +msgid "Manage ingredients of:" +msgstr "Gestion des ingrédienrs de :" + +#: apps/food/views.py:276 apps/food/views.py:284 +#, python-brace-format +msgid "Fully used in {meal}" +msgstr "Aliment entièrement utilisé dans : {meal}" + +#: apps/food/views.py:323 msgid "Add the ingredient:" msgstr "Ajouter l'ingrédient" -#: apps/food/views.py:275 +#: apps/food/views.py:349 #, python-brace-format msgid "Food fully used in : {meal.name}" msgstr "Aliment entièrement utilisé dans : {meal.name}" -#: apps/food/views.py:294 +#: apps/food/views.py:368 msgid "Update an aliment" msgstr "Modifier un aliment" -#: apps/food/views.py:342 +#: apps/food/views.py:416 msgid "Details of:" msgstr "Détails de :" -#: apps/food/views.py:352 apps/treasury/tables.py:149 +#: apps/food/views.py:426 apps/treasury/tables.py:149 msgid "Yes" msgstr "Oui" -#: apps/food/views.py:354 apps/member/models.py:99 apps/treasury/tables.py:149 +#: apps/food/views.py:428 apps/member/models.py:99 apps/treasury/tables.py:149 msgid "No" msgstr "Non" From dc6a40de025eda8cf43c8b644d07371f9b21a92e Mon Sep 17 00:00:00 2001 From: quark Date: Sun, 4 May 2025 17:56:44 +0200 Subject: [PATCH 006/153] bug fix and doc --- apps/food/views.py | 9 +++------ docs/apps/food.rst | 5 +++-- docs/apps/index.rst | 3 +++ docs/faq.rst | 1 + 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/food/views.py b/apps/food/views.py index 4c6ad57e..ff7089c8 100644 --- a/apps/food/views.py +++ b/apps/food/views.py @@ -7,7 +7,7 @@ from api.viewsets import is_regex from django_tables2.views import MultiTableMixin from django.db import transaction from django.db.models import Q -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, Http404 from django.views.generic import DetailView, UpdateView, CreateView from django.views.generic.list import ListView from django.urls import reverse_lazy @@ -240,11 +240,6 @@ class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView): form.instance.is_ready = False return super().form_valid(form) - def get_context_data(self, *args, **kwargs): - context = super().get_context_data(*args, **kwargs) - context['title'] += ' ' + self.object.name - return context - def get_success_url(self, **kwargs): self.object.refresh_from_db() return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk}) @@ -438,6 +433,8 @@ class FoodDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): return context def get(self, *args, **kwargs): + if Food.objects.filter(pk=kwargs['pk']).count() != 1: + return Http404 model = Food.objects.get(pk=kwargs['pk']).polymorphic_ctype.model if 'stop_redirect' in kwargs and kwargs['stop_redirect']: return super().get(*args, **kwargs) diff --git a/docs/apps/food.rst b/docs/apps/food.rst index e34ad40f..2eac3e8c 100644 --- a/docs/apps/food.rst +++ b/docs/apps/food.rst @@ -19,8 +19,9 @@ Le modèle regroupe : * Propriétaire (doit-être un Club) * Allergènes (ManyToManyField) * date d'expiration -* a été mangé (booléen) +* fin de vie * est prêt (booléen) +* consigne (pour les GCKs) BasicFood ~~~~~~~~~ @@ -40,7 +41,7 @@ Les TransformedFood correspondent aux produits préparés à la Kfet. Ils peuven Le modèle regroupe : -* Durée de consommation (par défaut 3 jours) +* Durée de conservation (par défaut 3 jours) * Ingrédients (ManyToManyField vers Food) * Date de création * Champs de Food diff --git a/docs/apps/index.rst b/docs/apps/index.rst index 95315eb8..96f4cfbf 100644 --- a/docs/apps/index.rst +++ b/docs/apps/index.rst @@ -12,6 +12,7 @@ Applications de la Note Kfet 2020 ../api/index registration logs + food treasury wei wrapped @@ -66,6 +67,8 @@ Applications facultatives Serveur central d'authentification, permet d'utiliser son compte de la NoteKfet2020 pour se connecter à d'autre application ayant intégrer un client. * `Scripts `_ Ensemble de commande `./manage.py` pour la gestion de la note: import de données, verification d'intégrité, etc... +* `Food `_ : + Gestion de la nourriture dans Kfet pour les clubs. * `Treasury `_ : Interface de gestion pour les trésorièr⋅es, émission de factures, remises de chèque, statistiques... * `WEI `_ : diff --git a/docs/faq.rst b/docs/faq.rst index bdc4028d..68727dd8 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -183,6 +183,7 @@ Contributeur⋅rices * korenst1 * nicomarg * PAC + * Quark * ÿnérant From 4445dd4a96c6f8738bc1dfe43ff7e0b1050bbf9b Mon Sep 17 00:00:00 2001 From: quark Date: Wed, 7 May 2025 18:04:47 +0200 Subject: [PATCH 007/153] Remove food with end_of_life not null from open table --- apps/food/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/food/views.py b/apps/food/views.py index ff7089c8..d9556ef6 100644 --- a/apps/food/views.py +++ b/apps/food/views.py @@ -71,7 +71,7 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li open_table = self.get_queryset().order_by('expiry_date').filter( Q(polymorphic_ctype__model='transformedfood') | Q(polymorphic_ctype__model='basicfood', basicfood__date_type='DLC')).filter( - expiry_date__lt=timezone.now()).filter( + expiry_date__lt=timezone.now(), end_of_life='').filter( PermissionBackend.filter_queryset(self.request, Food, 'view')) # table served served_table = self.get_queryset().order_by('-pk').filter( From 0cf11c63486142d846fe013cae526a60a9167002 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Thu, 8 May 2025 18:34:23 +0200 Subject: [PATCH 008/153] ok --- apps/activity/models.py | 2 +- .../templates/activity/activity_detail.html | 18 ++++++++++++++++++ .../activity/includes/activity_info.html | 5 ++++- apps/activity/urls.py | 1 + apps/activity/views.py | 15 +++++++++++++++ 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/apps/activity/models.py b/apps/activity/models.py index c7c92e8d..4e313a57 100644 --- a/apps/activity/models.py +++ b/apps/activity/models.py @@ -234,7 +234,7 @@ class Guest(models.Model): """ activity = models.ForeignKey( Activity, - on_delete=models.PROTECT, + on_delete=models.CASCADE, related_name='+', ) diff --git a/apps/activity/templates/activity/activity_detail.html b/apps/activity/templates/activity/activity_detail.html index a94d1e37..077acb29 100644 --- a/apps/activity/templates/activity/activity_detail.html +++ b/apps/activity/templates/activity/activity_detail.html @@ -95,5 +95,23 @@ SPDX-License-Identifier: GPL-3.0-or-later errMsg(xhr.responseJSON); }); }); + $("#delete_activity").click(function () { + if (!confirm("{% trans 'Are you sure you want to delete this activity?' %}")) { + return; + } + + $.ajax({ + url: "/api/activity/activity/{{ activity.pk }}/", + type: "DELETE", + headers: { + "X-CSRFTOKEN": CSRF_TOKEN + } + }).done(function () { + addMsg("{% trans 'Activity deleted' %}", "success"); + window.location.href = "/activity/"; // Redirige vers la liste des activités (à adapter) + }).fail(function (xhr) { + errMsg(xhr.responseJSON); + }); + }); {% endblock %} diff --git a/apps/activity/templates/activity/includes/activity_info.html b/apps/activity/templates/activity/includes/activity_info.html index a16ad33b..f9ea634b 100644 --- a/apps/activity/templates/activity/includes/activity_info.html +++ b/apps/activity/templates/activity/includes/activity_info.html @@ -70,7 +70,10 @@ SPDX-License-Identifier: GPL-3.0-or-later {% if ".change_"|has_perm:activity %}
{% trans "edit"|capfirst %} {% endif %} - {% if activity.activity_type.can_invite and not activity_started %} + {% if not activity.valid and ".delete_"|has_perm:activity %} + {% trans "delete"|capfirst %} + {% endif %} + {% if activity.activity_type.can_invite and not activity_started and activity.valid %} {% trans "Invite" %} {% endif %} {% endif %} diff --git a/apps/activity/urls.py b/apps/activity/urls.py index 962be72a..63a3a169 100644 --- a/apps/activity/urls.py +++ b/apps/activity/urls.py @@ -15,4 +15,5 @@ urlpatterns = [ path('/update/', views.ActivityUpdateView.as_view(), name='activity_update'), path('new/', views.ActivityCreateView.as_view(), name='activity_create'), path('calendar.ics', views.CalendarView.as_view(), name='calendar_ics'), + path('/delete', views.ActivityDeleteView.as_view(), name='delete_activity'), ] diff --git a/apps/activity/views.py b/apps/activity/views.py index 80bc1506..02a98254 100644 --- a/apps/activity/views.py +++ b/apps/activity/views.py @@ -152,6 +152,21 @@ class ActivityUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): def get_success_url(self, **kwargs): return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]}) +class ActivityDeleteView(View): + def delete(self, request, pk): + try: + activity = Activity.objects.get(pk=pk) + activity.delete() + return JsonResponse({"message": "Activity deleted"}) + except ProtectedError as e: + return JsonResponse({"error": "Cannot delete this activity because it is still referenced by guests."}, status=400) + except Activity.DoesNotExist: + return JsonResponse({"error": "Activity not found"}, status=404) + + def dispatch(self, *args, **kwargs): + # Optionnel : restreindre à utilisateur connecté ou permissions + return super().dispatch(*args, **kwargs) + class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView): """ From 71ef3aedd8f1c46e5ec3a4b42c5bcd6d7ccfe53e Mon Sep 17 00:00:00 2001 From: ehouarn Date: Thu, 8 May 2025 19:09:22 +0200 Subject: [PATCH 009/153] Update views.py --- apps/activity/views.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/activity/views.py b/apps/activity/views.py index 02a98254..bebc7899 100644 --- a/apps/activity/views.py +++ b/apps/activity/views.py @@ -158,8 +158,6 @@ class ActivityDeleteView(View): activity = Activity.objects.get(pk=pk) activity.delete() return JsonResponse({"message": "Activity deleted"}) - except ProtectedError as e: - return JsonResponse({"error": "Cannot delete this activity because it is still referenced by guests."}, status=400) except Activity.DoesNotExist: return JsonResponse({"error": "Activity not found"}, status=404) From 3065eacc96431eadc01098cf9253bae98bfbf736 Mon Sep 17 00:00:00 2001 From: ehouarn Date: Thu, 8 May 2025 19:38:40 +0200 Subject: [PATCH 010/153] Update views.py --- apps/activity/views.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/apps/activity/views.py b/apps/activity/views.py index bebc7899..08e53077 100644 --- a/apps/activity/views.py +++ b/apps/activity/views.py @@ -9,7 +9,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.db import transaction from django.db.models import F, Q -from django.http import HttpResponse +from django.http import HttpResponse,JsonResponse from django.urls import reverse_lazy from django.utils import timezone from django.utils.decorators import method_decorator @@ -153,6 +153,9 @@ class ActivityUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]}) class ActivityDeleteView(View): + """ + Deletes an Activity + """ def delete(self, request, pk): try: activity = Activity.objects.get(pk=pk) @@ -162,7 +165,18 @@ class ActivityDeleteView(View): return JsonResponse({"error": "Activity not found"}, status=404) def dispatch(self, *args, **kwargs): - # Optionnel : restreindre à utilisateur connecté ou permissions + """ + Don't display the delete button if the user has no right to delete. + """ + if not self.request.user.is_authenticated: + return self.handle_no_permission() + + activity = Activity.objects.get(pk=self.kwargs["pk"]) + if not PermissionBackend.check_perm(self.request, "activity.delete_activity", activity): + raise PermissionDenied(_("You are not allowed to delete this activity.")) + + if activity.valid: + raise PermissionDenied(_("This activity is valid.")) return super().dispatch(*args, **kwargs) From 4afafceba1ecd616b1dc61c0ef515ec2a2ee86a3 Mon Sep 17 00:00:00 2001 From: ehouarn Date: Thu, 8 May 2025 19:39:59 +0200 Subject: [PATCH 011/153] Update activity_detail.html --- apps/activity/templates/activity/activity_detail.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/activity/templates/activity/activity_detail.html b/apps/activity/templates/activity/activity_detail.html index 077acb29..1a8d01ee 100644 --- a/apps/activity/templates/activity/activity_detail.html +++ b/apps/activity/templates/activity/activity_detail.html @@ -108,7 +108,7 @@ SPDX-License-Identifier: GPL-3.0-or-later } }).done(function () { addMsg("{% trans 'Activity deleted' %}", "success"); - window.location.href = "/activity/"; // Redirige vers la liste des activités (à adapter) + window.location.href = "/activity/"; // Redirige vers la liste des activités }).fail(function (xhr) { errMsg(xhr.responseJSON); }); From cdd81c144438967d4939435d67336932ee01f19a Mon Sep 17 00:00:00 2001 From: ehouarn Date: Thu, 8 May 2025 20:14:24 +0200 Subject: [PATCH 012/153] Update views.py --- apps/activity/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/activity/views.py b/apps/activity/views.py index 08e53077..3081c45b 100644 --- a/apps/activity/views.py +++ b/apps/activity/views.py @@ -9,7 +9,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.db import transaction from django.db.models import F, Q -from django.http import HttpResponse,JsonResponse +from django.http import HttpResponse, JsonResponse from django.urls import reverse_lazy from django.utils import timezone from django.utils.decorators import method_decorator @@ -152,6 +152,7 @@ class ActivityUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): def get_success_url(self, **kwargs): return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]}) + class ActivityDeleteView(View): """ Deletes an Activity From fadb289ed7e77e3eb6e429fa060f5bd00db30792 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 9 May 2025 19:48:04 +0200 Subject: [PATCH 013/153] migrations --- .../migrations/0007_alter_guest_activity.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 apps/activity/migrations/0007_alter_guest_activity.py diff --git a/apps/activity/migrations/0007_alter_guest_activity.py b/apps/activity/migrations/0007_alter_guest_activity.py new file mode 100644 index 00000000..9badcc1b --- /dev/null +++ b/apps/activity/migrations/0007_alter_guest_activity.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.20 on 2025-05-08 19:07 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('activity', '0006_guest_school'), + ] + + operations = [ + migrations.AlterField( + model_name='guest', + name='activity', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='activity.activity'), + ), + ] From 108a56745ca9d3ff8d0f887c6ddf92d383d40599 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 10 May 2025 19:24:05 +0200 Subject: [PATCH 014/153] Corrections de quelques bugs (par Quark) --- apps/food/views.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/apps/food/views.py b/apps/food/views.py index d9556ef6..c0362df2 100644 --- a/apps/food/views.py +++ b/apps/food/views.py @@ -168,7 +168,8 @@ class BasicFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView): template_name = "food/food_update.html" def get_sample_object(self): - return BasicFood( + # We choose a club which may work or BDE else + food = BasicFood( name="", owner_id=1, expiry_date=timezone.now(), @@ -177,6 +178,14 @@ class BasicFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView): date_type='DLC', ) + for membership in self.request.user.memberships.all(): + club_id = membership.club.id + food.owner_id = club_id + if PermissionBackend.check_perm(self.request, "food.add_basicfood", food): + return food + + return food + @transaction.atomic def form_valid(self, form): if QRCode.objects.filter(qr_code_number=self.kwargs['slug']).count() > 0: @@ -227,13 +236,22 @@ class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView): template_name = "food/food_update.html" def get_sample_object(self): - return TransformedFood( + # We choose a club which may work or BDE else + food = TransformedFood( name="", owner_id=1, expiry_date=timezone.now(), is_ready=True, ) + for membership in self.request.user.memberships.all(): + club_id = membership.club.id + food.owner_id = club_id + if PermissionBackend.check_perm(self.request, "food.add_transformedfood", food): + return food + + return food + @transaction.atomic def form_valid(self, form): form.instance.expiry_date = timezone.now() + timedelta(days=3) @@ -248,7 +266,7 @@ class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView): MAX_FORMS = 10 -class ManageIngredientsView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): +class ManageIngredientsView(LoginRequiredMixin, UpdateView): """ A view to manage ingredient for a transformed food """ @@ -279,6 +297,14 @@ class ManageIngredientsView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView ingredient.end_of_life = _('Fully used in {meal}'.format( meal=self.object.name)) ingredient.save() + # We recalculate new expiry date and allergens + self.object.expiry_date = self.object.creation_date + self.object.shelf_life + self.object.allergens.clear() + + for ingredient in self.object.ingredients.iterator(): + if not (ingredient.polymorphic_ctype.model == 'basicfood' and ingredient.date_type == 'DDM'): + self.object.expiry_date = min(self.object.expiry_date, ingredient.expiry_date) + self.object.allergens.set(self.object.allergens.union(ingredient.allergens.all())) self.object.save(old_ingredients=old_ingredients, old_allergens=old_allergens) return HttpResponseRedirect(self.get_success_url()) From 0d69383dfde844f625fda97104a64d38f9dc94cf Mon Sep 17 00:00:00 2001 From: ehouarn Date: Mon, 19 May 2025 17:45:01 +0200 Subject: [PATCH 015/153] Update views.py --- apps/food/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/food/views.py b/apps/food/views.py index c0362df2..f25b0ca6 100644 --- a/apps/food/views.py +++ b/apps/food/views.py @@ -263,7 +263,7 @@ class TransformedFoodCreateView(ProtectQuerysetMixin, ProtectedCreateView): return reverse_lazy('food:transformedfood_view', kwargs={"pk": self.object.pk}) -MAX_FORMS = 10 +MAX_FORMS = 100 class ManageIngredientsView(LoginRequiredMixin, UpdateView): From 3d3427095903ff3e1098f33f6fbc8478864138ad Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 23 May 2025 23:38:06 +0200 Subject: [PATCH 016/153] Faute de frappe --- apps/permission/fixtures/initial.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index f1f01dcc..e6be2fda 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -4562,6 +4562,19 @@ ] } }, + { + "model": "permission.role", + "pk": 23, + "fields": { + "for_club": 2, + "name": "Darbonne", + "permissions": [ + 30, + 31, + 32 + ] + } + }, { "model": "wei.weirole", "pk": 12, From 7b1e32e51463f824fa9bc3521a7f44bda5718cdc Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 24 May 2025 22:29:11 +0200 Subject: [PATCH 017/153] =?UTF-8?q?R=C3=A9=C3=A9criture=20des=20r=C3=B4les?= =?UTF-8?q?=20pertinents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/permission/fixtures/initial.json | 155 +++++++++++++++++++++++--- 1 file changed, 137 insertions(+), 18 deletions(-) diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index e6be2fda..e2536f66 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -4091,8 +4091,8 @@ 158, 159, 160, - 212, - 222 + 212, + 222 ] } }, @@ -4133,14 +4133,14 @@ 50, 141, 169, - 217, - 218, - 219, - 220, - 221, - 247, - 258, - 259 + 217, + 218, + 219, + 220, + 221, + 247, + 258, + 259 ] } }, @@ -4152,8 +4152,8 @@ "name": "Pr\u00e9sident\u22c5e de club", "permissions": [ 62, - 142, - 135 + 135, + 142 ] } }, @@ -4538,8 +4538,8 @@ "name": "GC anti-VSS", "permissions": [ 42, - 135, - 150, + 135, + 150, 163, 164 ] @@ -4555,10 +4555,10 @@ 137, 211, 212, - 213, - 214, - 215, - 216 + 213, + 214, + 215, + 216 ] } }, @@ -4575,6 +4575,125 @@ ] } }, + { + "model": "permission.role", + "pk": 24, + "fields": { + "for_club": null, + "name": "Staffeur⋅euse (S&L,Respo Tech,...)", + "permissions": [] + } + }, + { + "model": "permission.role", + "pk": 28, + "fields": { + "for_club": 10, + "name": "Trésorièr⸱e BDA", + "permissions": [ + 55, + 56, + 57, + 58, + 135, + 143, + 176, + 177, + 178, + 243, + 260, + 261, + 262, + 263, + 264, + 265, + 266, + 267, + 268, + 269 + ] + } + }, + { + "model": "permission.role", + "pk": 29, + "fields": { + "for_club": 267, + "name": "Trésorièr⸱e Terre à terre", + "permissions": [ + 55, + 56, + 57, + 58, + 135, + 143, + 176, + 177, + 178, + 266, + 267, + 268, + 269, + 270 + ] + } + }, + { + "model": "permission.role", + "pk": 30, + "fields": { + "for_club": 10, + "name": "Respo sorties", + "permissions": [ + 49, + 62, + 141, + 241, + 242, + 243 + ] + } + }, + { + "model": "permission.role", + "pk": 31, + "fields": { + "for_club": 1, + "name": "Respo comm", + "permissions": [ + 135, + 244 + ] + } + }, + { + "model": "permission.role", + "pk": 32, + "fields": { + "for_club": 10, + "name": "Respo comm Art", + "permissions": [ + 135, + 245 + ] + } + }, + { + "model": "permission.role", + "pk": 33, + "fields": { + "for_club": 10, + "name": "Respo Jam", + "permissions": [ + 247, + 250, + 251, + 252, + 253, + 254 + ] + } + }, { "model": "wei.weirole", "pk": 12, From 067dd6f9d14b9c7095853347cdaf1bf24d1dd6ba Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 24 May 2025 22:41:53 +0200 Subject: [PATCH 018/153] WEI-Roles --- apps/permission/fixtures/initial.json | 29 +++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index e2536f66..0b496d4a 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -4584,6 +4584,25 @@ "permissions": [] } }, + { + "model": "permission.role", + "pk": 25, + "fields": { + "for_club": null, + "name": "Référent⋅e Bus", + "permissions": [ + 22, + 84, + 115, + 117, + 118, + 119, + 120, + 121, + 122 + ] + } + }, { "model": "permission.role", "pk": 28, @@ -4728,5 +4747,15 @@ "model": "wei.weirole", "pk": 18, "fields": {} + }, + { + "model": "wei.weirole", + "pk": 24, + "fields": {} + }, + { + "model": "wei.weirole", + "pk": 25, + "fields": {} } ] From f60dc8cfa0bbfff3216c0f9e1a46bc8d29ee646c Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sun, 25 May 2025 00:05:13 +0200 Subject: [PATCH 019/153] =?UTF-8?q?Pr=C3=A9-injection=20du=20BDA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/member/migrations/0014_create_bda.py | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 apps/member/migrations/0014_create_bda.py diff --git a/apps/member/migrations/0014_create_bda.py b/apps/member/migrations/0014_create_bda.py new file mode 100644 index 00000000..3bebdf5d --- /dev/null +++ b/apps/member/migrations/0014_create_bda.py @@ -0,0 +1,46 @@ +from django.db import migrations + +def create_bda(apps, schema_editor): + """ + The club BDA is now pre-injected. + """ + Club = apps.get_model("member", "club") + NoteClub = apps.get_model("note", "noteclub") + Alias = apps.get_model("note", "alias") + ContentType = apps.get_model('contenttypes', 'ContentType') + polymorphic_ctype_id = ContentType.objects.get_for_model(NoteClub).id + + Club.objects.get_or_create( + id=10, + name="BDA", + email="bda.ensparissaclay@gmail.com", + require_memberships=True, + membership_fee_paid=750, + membership_fee_unpaid=750, + membership_duration=396, + membership_start="2024-08-01", + membership_end="2025-09-30", + ) + NoteClub.objects.get_or_create( + id=1937, + club_id=10, + polymorphic_ctype_id=polymorphic_ctype_id, + ) + Alias.objects.get_or_create( + id=1937, + note_id=1937, + name="BDA", + normalized_name="bda", + ) + + +class Migration(migrations.Migration): + + dependencies = [ + ('member', '0013_auto_20240801_1436'), + ] + + operations = [ + migrations.RunPython(create_bda), + ] + From 5a8acbde0050e4feea4d13ecf5888f4efc6df338 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sun, 25 May 2025 00:07:07 +0200 Subject: [PATCH 020/153] Trez TaT en moins --- apps/permission/fixtures/initial.json | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 0b496d4a..181f2540 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -4633,30 +4633,6 @@ ] } }, - { - "model": "permission.role", - "pk": 29, - "fields": { - "for_club": 267, - "name": "Trésorièr⸱e Terre à terre", - "permissions": [ - 55, - 56, - 57, - 58, - 135, - 143, - 176, - 177, - 178, - 266, - 267, - 268, - 269, - 270 - ] - } - }, { "model": "permission.role", "pk": 30, From 136f636fda379a6bdb785f21c1c9353177aec5f3 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sun, 25 May 2025 23:31:09 +0200 Subject: [PATCH 021/153] =?UTF-8?q?Fix=20de=20l'ajout=20d'=C3=A9quipe,=20l?= =?UTF-8?q?e=20ColorWidget=20=C3=A9tait=20d=C3=A9faillant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wei/forms/registration.py | 1 + apps/wei/migrations/0011_alter_weiclub_year.py | 18 ++++++++++++++++++ apps/wei/templates/wei/busteam_detail.html | 2 ++ apps/wei/templates/wei/busteam_form.html | 8 ++++++++ apps/wei/views.py | 14 ++++++++++++++ note_kfet/inputs.py | 12 ++++++++++-- note_kfet/templates/colorfield/color.html | 5 +++++ 7 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 apps/wei/migrations/0011_alter_weiclub_year.py create mode 100644 note_kfet/templates/colorfield/color.html diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index 38568b93..4890fdce 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -194,3 +194,4 @@ class BusTeamForm(forms.ModelForm): ), "color": ColorWidget(), } + # "color": ColorWidget(), diff --git a/apps/wei/migrations/0011_alter_weiclub_year.py b/apps/wei/migrations/0011_alter_weiclub_year.py new file mode 100644 index 00000000..086ea4eb --- /dev/null +++ b/apps/wei/migrations/0011_alter_weiclub_year.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.21 on 2025-05-25 12:23 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wei', '0010_remove_weiregistration_specific_diet'), + ] + + operations = [ + migrations.AlterField( + model_name='weiclub', + name='year', + field=models.PositiveIntegerField(default=2025, unique=True, verbose_name='year'), + ), + ] diff --git a/apps/wei/templates/wei/busteam_detail.html b/apps/wei/templates/wei/busteam_detail.html index 27348d03..1b5dc3c3 100644 --- a/apps/wei/templates/wei/busteam_detail.html +++ b/apps/wei/templates/wei/busteam_detail.html @@ -18,6 +18,8 @@ SPDX-License-Identifier: GPL-3.0-or-later diff --git a/apps/wei/templates/wei/busteam_form.html b/apps/wei/templates/wei/busteam_form.html index c62fec40..24522a80 100644 --- a/apps/wei/templates/wei/busteam_form.html +++ b/apps/wei/templates/wei/busteam_form.html @@ -13,9 +13,17 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% csrf_token %} + {{ form.media }} {{ form|crispy }}
+ {% endblock %} \ No newline at end of file diff --git a/apps/wei/views.py b/apps/wei/views.py index a2e8ccff..5683478a 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -440,6 +440,13 @@ class BusTeamCreateView(ProtectQuerysetMixin, ProtectedCreateView): def get_success_url(self): self.object.refresh_from_db() return reverse_lazy("wei:manage_bus_team", kwargs={"pk": self.object.pk}) + + def get_template_names(self): + names = super().get_template_names() + return names + + + class BusTeamUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): @@ -472,6 +479,13 @@ class BusTeamUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): def get_success_url(self): self.object.refresh_from_db() return reverse_lazy("wei:manage_bus_team", kwargs={"pk": self.object.pk}) + + def get_template_names(self): + names = super().get_template_names() + return names + + + class BusTeamManageView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): diff --git a/note_kfet/inputs.py b/note_kfet/inputs.py index a05b61e4..6de3c7d9 100644 --- a/note_kfet/inputs.py +++ b/note_kfet/inputs.py @@ -63,8 +63,16 @@ class ColorWidget(Widget): def format_value(self, value): if value is None: value = 0xFFFFFF - return "#{:06X}".format(value) + if isinstance(value, str): + return value # Assume it's already a hex string like "#FFAA33" + try: + return "#{:06X}".format(value) + except Exception: + return "#FFFFFF" + def value_from_datadict(self, data, files, name): val = super().value_from_datadict(data, files, name) - return int(val[1:], 16) + if val: + return int(val[1:], 16) + return None \ No newline at end of file diff --git a/note_kfet/templates/colorfield/color.html b/note_kfet/templates/colorfield/color.html new file mode 100644 index 00000000..5c0457c5 --- /dev/null +++ b/note_kfet/templates/colorfield/color.html @@ -0,0 +1,5 @@ + \ No newline at end of file From 53d0480a1284c9d2eb00be8b43031d4e48c5cc93 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Mon, 26 May 2025 17:29:34 +0200 Subject: [PATCH 022/153] Ajout de permissions --- apps/permission/fixtures/initial.json | 53 ++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index e6be2fda..d35bdf00 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -3998,6 +3998,54 @@ "description": "Créer une transaction de ou vers la note d'un club tant que la source reste au dessus de -50 €" } }, + { + "model": "permission.permission", + "pk": 271, + "fields": { + "model": [ + "wei", + "bus" + ], + "query": "{\"wei\": [\"club\"]}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier n'importe quel bus du wei" + } + }, + { + "model": "permission.permission", + "pk": 272, + "fields": { + "model": [ + "wei", + "bus" + ], + "query": "{\"wei\": [\"club\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir tous les bus du wei" + } + }, + { + "model": "permission.permission", + "pk": 273, + "fields": { + "model": [ + "wei", + "busteam" + ], + "query": "{\"bus__wei\": [\"club\"], \"bus__wei__membership_end__gte\": [\"today\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir toutes les équipes WEI" + } + }, { "model": "permission.role", "pk": 1, @@ -4382,7 +4430,10 @@ 112, 113, 128, - 130 + 130, + 271, + 272, + 273 ] } }, From 16cfaa809a867b3e06d652747347cf9bd08bf3bb Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Tue, 27 May 2025 18:56:49 +0200 Subject: [PATCH 023/153] Fix de la plupart des bugs --- apps/wei/forms/registration.py | 11 +- apps/wei/tables.py | 2 +- apps/wei/views.py | 125 +- locale/de/LC_MESSAGES/django.po | 3230 +++++++++++++++++++++++-------- locale/es/LC_MESSAGES/django.po | 3213 ++++++++++++++++++++++-------- locale/fr/LC_MESSAGES/django.po | 2110 +++++++++++++++----- 6 files changed, 6513 insertions(+), 2178 deletions(-) diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index 4890fdce..fb497730 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -39,7 +39,9 @@ class WEIRegistrationForm(forms.ModelForm): class Meta: model = WEIRegistration - exclude = ('wei', 'clothing_cut') + fields = ['user', 'soge_credit', 'birth_date', 'gender', 'clothing_size', + 'health_issues', 'emergency_contact_name', 'emergency_contact_phone', 'first_year', + 'information_json'] widgets = { "user": Autocomplete( User, @@ -50,7 +52,7 @@ class WEIRegistrationForm(forms.ModelForm): }, ), "birth_date": DatePickerInput(options={'minDate': '1900-01-01', - 'maxDate': '2100-01-01'}), + 'maxDate': '2100-01-01'}), } @@ -81,11 +83,6 @@ class WEIChooseBusForm(forms.Form): class WEIMembershipForm(forms.ModelForm): - caution_check = forms.BooleanField( - required=False, - label=_("Caution check given"), - ) - roles = forms.ModelMultipleChoiceField( queryset=WEIRole.objects, label=_("WEI Roles"), diff --git a/apps/wei/tables.py b/apps/wei/tables.py index de5c84af..c8837e44 100644 --- a/apps/wei/tables.py +++ b/apps/wei/tables.py @@ -98,7 +98,7 @@ class WEIRegistrationTable(tables.Table): if not hasperm: return format_html("") - url = reverse_lazy('wei:validate_registration', args=(record.pk,)) + url = reverse_lazy('wei:wei_update_registration', args=(record.pk,)) + '?validate=true' text = _('Validate') if record.fee > record.user.note.balance and not record.soge_credit: btn_class = 'btn-secondary' diff --git a/apps/wei/views.py b/apps/wei/views.py index 5683478a..087e5eb0 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -8,18 +8,20 @@ from datetime import date, timedelta from tempfile import mkdtemp from django.conf import settings +from django.contrib import messages from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User from django.core.exceptions import PermissionDenied from django.db import transaction from django.db.models import Q, Count from django.db.models.functions.text import Lower +from django import forms from django.http import HttpResponse, Http404 from django.shortcuts import redirect from django.template.loader import render_to_string from django.urls import reverse_lazy from django.views import View -from django.views.generic import DetailView, UpdateView, RedirectView, TemplateView +from django.views.generic import DetailView, UpdateView, RedirectView, TemplateView, CreateView from django.utils.translation import gettext_lazy as _ from django.views.generic.edit import BaseFormView, DeleteView from django_tables2 import SingleTableView, MultiTableMixin @@ -37,6 +39,7 @@ from .forms import WEIForm, WEIRegistrationForm, BusForm, BusTeamForm, WEIMember WEIMembershipForm, CurrentSurvey from .tables import BusRepartitionTable, BusTable, BusTeamTable, WEITable, WEIRegistrationTable, \ WEIRegistration1ATable, WEIMembershipTable +from .forms.surveys import CurrentSurvey class CurrentWEIDetailView(LoginRequiredMixin, RedirectView): @@ -560,9 +563,15 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView): def get_form(self, form_class=None): form = super().get_form(form_class) form.fields["user"].initial = self.request.user - del form.fields["first_year"] - del form.fields["caution_check"] - del form.fields["information_json"] + + # Cacher les champs pendant l'inscription initiale + if "first_year" in form.fields: + del form.fields["first_year"] + if "caution_check" in form.fields: + del form.fields["caution_check"] + if "information_json" in form.fields: + del form.fields["information_json"] + return form @transaction.atomic @@ -658,9 +667,13 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView): form.fields["soge_credit"].disabled = True form.fields["soge_credit"].help_text = _("You already opened an account in the Société générale.") - del form.fields["caution_check"] - del form.fields["first_year"] - del form.fields["information_json"] + # Cacher les champs pendant l'inscription initiale + if "first_year" in form.fields: + del form.fields["first_year"] + if "caution_check" in form.fields: + del form.fields["caution_check"] + if "information_json" in form.fields: + del form.fields["information_json"] return form @@ -716,11 +729,15 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update # We can't update a registration once the WEI is started and before the membership start date if today >= wei.date_start or today < wei.membership_start: return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,))) + # Store the validate parameter in the view's state + self.should_validate = request.GET.get('validate', False) return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["club"] = self.object.wei + # Pass the validate parameter to the template + context["should_validate"] = self.should_validate if self.object.is_validated: membership_form = self.get_membership_form(instance=self.object.membership, @@ -754,6 +771,9 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update # The auto-json-format may cause issues with the default field remove if not PermissionBackend.check_perm(self.request, 'wei.change_weiregistration_information_json', self.object): del form.fields["information_json"] + # Masquer le champ caution_check pour tout le monde dans le formulaire de modification + if "caution_check" in form.fields: + del form.fields["caution_check"] return form def get_membership_form(self, data=None, instance=None): @@ -773,10 +793,30 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update def form_valid(self, form): # If the membership is already validated, then we update the bus and the team (and the roles) if form.instance.is_validated: - membership_form = self.get_membership_form(self.request.POST, form.instance.membership) - if not membership_form.is_valid(): + try: + membership = form.instance.membership + if membership is None: + raise ValueError(_("No membership found for this registration")) + + membership_form = self.get_membership_form(self.request.POST, instance=membership) + if not membership_form.is_valid(): + return self.form_invalid(form) + + # Vérifier que l'utilisateur a la permission de modifier le membership + # On vérifie d'abord si l'utilisateur a la permission générale de modification + if not self.request.user.has_perm("wei.change_weimembership"): + raise PermissionDenied(_("You don't have the permission to update memberships")) + + # On vérifie ensuite les permissions spécifiques pour chaque champ modifié + for field_name in membership_form.changed_data: + perm = f"wei.change_weimembership_{field_name}" + if not self.request.user.has_perm(perm): + raise PermissionDenied(_("You don't have the permission to update the field %(field)s") % {'field': field_name}) + + membership_form.save() + except (WEIMembership.DoesNotExist, ValueError, PermissionDenied) as e: + form.add_error(None, str(e)) return self.form_invalid(form) - membership_form.save() # If it is not validated and if this is an old member, then we update the choices elif not form.instance.first_year and PermissionBackend.check_perm( self.request, "wei.change_weiregistration_information_json", self.object): @@ -801,14 +841,8 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update survey = CurrentSurvey(self.object) if not survey.is_complete(): return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk}) - if PermissionBackend.check_perm(self.request, "wei.add_weimembership", WEIMembership( - club=self.object.wei, - user=self.object.user, - date_start=date.today(), - date_end=date.today(), - fee=0, - registration=self.object, - )): + # On redirige vers la validation uniquement si c'est explicitement demandé (et stocké dans la vue) + if self.should_validate and self.request.user.has_perm("wei.add_weimembership"): return reverse_lazy("wei:validate_registration", kwargs={"pk": self.object.pk}) return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.wei.pk}) @@ -842,26 +876,21 @@ class WEIDeleteRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Delete return reverse_lazy('wei:wei_detail', args=(self.object.wei.pk,)) -class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): +class WEIValidateRegistrationView(LoginRequiredMixin, CreateView): """ Validate WEI Registration """ model = WEIMembership extra_context = {"title": _("Validate WEI registration")} - def get_sample_object(self): - registration = WEIRegistration.objects.get(pk=self.kwargs["pk"]) - return WEIMembership( - club=registration.wei, - user=registration.user, - date_start=date.today(), - date_end=date.today() + timedelta(days=1), - fee=0, - registration=registration, - ) - def dispatch(self, request, *args, **kwargs): - wei = WEIRegistration.objects.get(pk=self.kwargs["pk"]).wei + # Vérifier d'abord si l'utilisateur a la permission générale + if not request.user.has_perm("wei.add_weimembership"): + raise PermissionDenied(_("You don't have the permission to validate registrations")) + + registration = WEIRegistration.objects.get(pk=self.kwargs["pk"]) + + wei = registration.wei today = date.today() # We can't validate anyone once the WEI is started and before the membership start date if today >= wei.date_start or today < wei.membership_start: @@ -914,8 +943,14 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): form.fields["last_name"].initial = registration.user.last_name form.fields["first_name"].initial = registration.user.first_name - if "caution_check" in form.fields: - form.fields["caution_check"].initial = registration.caution_check + # Ajouter le champ caution_check uniquement pour les non-première année et le rendre obligatoire + if not registration.first_year: + form.fields["caution_check"] = forms.BooleanField( + required=True, + initial=registration.caution_check, + label=_("Caution check given"), + help_text=_("Please make sure the check is given before validating the registration") + ) if registration.soge_credit: form.fields["credit_type"].disabled = True @@ -1303,8 +1338,22 @@ class WEIAttributeBus1ANextView(LoginRequiredMixin, RedirectView): if not wei.exists(): raise Http404 wei = wei.get() - qs = WEIRegistration.objects.filter(wei=wei, membership__isnull=False, membership__bus__isnull=True) - qs = qs.filter(information_json__contains='selected_bus_pk') # not perfect, but works... - if qs.exists(): - return reverse_lazy('wei:wei_bus_1A', args=(qs.first().pk, )) - return reverse_lazy('wei:wei_1A_list', args=(wei.pk, )) + + # On cherche d'abord les 1A qui ont une inscription validée (membership) mais pas de bus + qs = WEIRegistration.objects.filter( + wei=wei, + first_year=True, + membership__isnull=False, + membership__bus__isnull=True + ) + + # Parmi eux, on prend ceux qui ont répondu au questionnaire (ont un bus préféré) + qs = qs.filter(information_json__contains='selected_bus_pk') + + if not qs.exists(): + # Si on ne trouve personne, on affiche un message et on retourne à la liste + messages.info(self.request, _("No first year student without a bus found. Either all of them have a bus, or none has filled the survey yet.")) + return reverse_lazy('wei:wei_1A_list', args=(wei.pk,)) + + # On redirige vers la page d'attribution pour le premier étudiant trouvé + return reverse_lazy('wei:wei_bus_1A', args=(qs.first().pk,)) diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 055b0093..702e52da 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/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-05-27 16:46+0200\n" "PO-Revision-Date: 2020-11-16 20:02+0000\n" "Last-Translator: bleizi \n" "Language-Team: German \n" @@ -18,42 +18,48 @@ msgstr "" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.3.2\n" +#: apps/activity/api/serializers.py:77 +#, fuzzy +#| msgid "This credit is already validated." +msgid "This opener already exists" +msgstr "Dieser Kredit ist bereits validiert." + #: apps/activity/apps.py:10 apps/activity/models.py:129 -#: apps/activity/models.py:169 +#: apps/activity/models.py:169 apps/activity/models.py:329 msgid "activity" msgstr "Veranstaltung" -#: apps/activity/forms.py:34 +#: apps/activity/forms.py:35 msgid "The note of this club is inactive." msgstr "" -#: 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 "Das Abschlussdatum muss nach das Anfangsdatum sein." -#: apps/activity/forms.py:82 apps/activity/models.py:271 +#: apps/activity/forms.py:83 apps/activity/models.py:277 msgid "You can't invite someone once the activity is started." msgstr "" "Sie dürfen nicht jemandem einladen wenn die Veranstaltung angefangen hat." -#: apps/activity/forms.py:85 apps/activity/models.py:274 +#: apps/activity/forms.py:86 apps/activity/models.py:280 msgid "This activity is not validated yet." msgstr "Diese Veranstaltung ist noch nicht bestätigt." -#: apps/activity/forms.py:95 apps/activity/models.py:282 +#: apps/activity/forms.py:96 apps/activity/models.py:288 msgid "This person has been already invited 5 times this year." msgstr "Diese Person wurde schon 5 mal dieses Jahr eingeladen." -#: apps/activity/forms.py:99 apps/activity/models.py:286 +#: apps/activity/forms.py:100 apps/activity/models.py:292 msgid "This person is already invited." msgstr "Diese Person wurde schon eingeladen." -#: apps/activity/forms.py:103 apps/activity/models.py:290 +#: apps/activity/forms.py:104 apps/activity/models.py:296 msgid "You can't invite more than 3 people to this activity." msgstr "Sie dürfen höchstens 3 Leute zu dieser Veranstaltung einladen." -#: apps/activity/models.py:28 apps/activity/models.py:63 apps/food/models.py:42 -#: apps/food/models.py:56 apps/member/models.py:203 +#: apps/activity/models.py:28 apps/activity/models.py:63 apps/food/models.py:18 +#: apps/food/models.py:35 apps/member/models.py:203 #: apps/member/templates/member/includes/club_info.html:4 #: apps/member/templates/member/includes/profile_info.html:4 #: apps/note/models/notes.py:263 apps/note/models/transactions.py:26 @@ -62,7 +68,7 @@ msgstr "Sie dürfen höchstens 3 Leute zu dieser Veranstaltung einladen." #: 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 "Name" @@ -114,8 +120,8 @@ msgstr "Wo findet die Veranstaltung statt ? (z.B Kfet)." msgid "type" msgstr "Type" -#: apps/activity/models.py:91 apps/logs/models.py:22 apps/member/models.py:318 -#: apps/note/models/notes.py:148 apps/treasury/models.py:293 +#: 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:294 #: apps/wei/models.py:171 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/templates/wei/survey.html:15 msgid "user" @@ -171,7 +177,7 @@ msgid "entry time" msgstr "Eintrittzeit" #: 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" @@ -199,21 +205,21 @@ msgstr "Eintritt von {note} zur Veranstaltung {activity}" msgid "Already entered on " msgstr "Schon eingetretten " -#: apps/activity/models.py:204 apps/activity/tables.py:56 +#: apps/activity/models.py:205 apps/activity/tables.py:58 msgid "{:%Y-%m-%d %H:%M:%S}" msgstr "{:%Y-%m-%d %H:%M:%S}" -#: apps/activity/models.py:212 +#: apps/activity/models.py:213 msgid "The balance is negative." msgstr "Kontostand ist im Rot." -#: apps/activity/models.py:242 +#: apps/activity/models.py:243 #: apps/treasury/templates/treasury/sogecredit_detail.html:14 #: apps/wei/templates/wei/attribute_bus_1A.html:16 msgid "last name" msgstr "Nachname" -#: apps/activity/models.py:247 +#: apps/activity/models.py:248 #: apps/member/templates/member/includes/profile_info.html:4 #: apps/registration/templates/registration/future_profile_detail.html:16 #: apps/treasury/templates/treasury/sogecredit_detail.html:17 @@ -222,79 +228,144 @@ msgstr "Nachname" msgid "first name" msgstr "Vorname" -#: apps/activity/models.py:254 +#: apps/activity/models.py:253 +msgid "school" +msgstr "" + +#: apps/activity/models.py:260 msgid "inviter" msgstr "Einlader" -#: apps/activity/models.py:258 +#: apps/activity/models.py:264 msgid "guest" msgstr "Gast" -#: apps/activity/models.py:259 +#: apps/activity/models.py:265 msgid "guests" msgstr "Gäste" -#: apps/activity/models.py:312 +#: apps/activity/models.py:318 msgid "Invitation" msgstr "Einladung" -#: apps/activity/tables.py:27 +#: apps/activity/models.py:336 apps/activity/models.py:340 +#, fuzzy +#| msgid "opened" +msgid "Opener" +msgstr "geöffnet" + +#: apps/activity/models.py:341 +#: apps/activity/templates/activity/activity_detail.html:16 +#, fuzzy +#| msgid "opened" +msgid "Openers" +msgstr "geöffnet" + +#: apps/activity/models.py:345 +#, fuzzy, python-brace-format +#| msgid "Entry for {note} to the activity {activity}" +msgid "{opener} is opener of activity {acivity}" +msgstr "Eintritt von {note} zur Veranstaltung {activity}" + +#: apps/activity/tables.py:29 msgid "The activity is currently open." msgstr "Die Veranstaltung ist geöffnet." -#: apps/activity/tables.py:28 +#: apps/activity/tables.py:30 msgid "The validation of the activity is pending." msgstr "Diese Veranstaltung ist noch nicht bestätigt." -#: 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 "Entfernen" -#: apps/activity/tables.py:56 +#: apps/activity/tables.py:58 msgid "Entered on " msgstr "Eingetreten um " -#: apps/activity/tables.py:58 +#: apps/activity/tables.py:60 msgid "remove" msgstr "entfernen" -#: 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:209 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:107 msgid "Last name" msgstr "Nachname" -#: 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:112 msgid "First name" msgstr "Vorname" -#: 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 "Kontostand" -#: 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 +#: env/lib/python3.11/site-packages/django/forms/formsets.py:499 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:13 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:38 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:7 +#: 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 "Löschen" + +#: 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 "Neue" + +#: apps/activity/templates/activity/activity_detail.html:35 msgid "Guests list" msgstr "Gastliste" -#: apps/activity/templates/activity/activity_detail.html:33 +#: apps/activity/templates/activity/activity_detail.html:55 #, fuzzy #| msgid "Guests list" msgid "Guest deleted" msgstr "Gastliste" +#: apps/activity/templates/activity/activity_detail.html:99 +#, fuzzy +#| msgid "" +#| "Are you sure you want to delete this invoice? This action can't be undone." +msgid "Are you sure you want to delete this activity?" +msgstr "" +"Möchten Sie diese Rechnung wirklich löschen? Diese Aktion kann nicht " +"rückgängig gemacht werden." + +#: apps/activity/templates/activity/activity_detail.html:110 +#, fuzzy +#| msgid "Activity detail" +msgid "Activity deleted" +msgstr "Veranstaltunginfo" + #: apps/activity/templates/activity/activity_entry.html:14 #: apps/note/models/transactions.py:261 #: apps/note/templates/note/transaction_form.html:17 @@ -336,17 +407,17 @@ msgid "Entry done!" msgstr "Eintrittseite" #: 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/transformedfood_form.html:16 +#: apps/food/templates/food/food_update.html:17 +#: apps/food/templates/food/manage_ingredients.html:48 +#: apps/food/templates/food/qrcode.html:18 +#: apps/food/templates/food/transformedfood_update.html:45 #: apps/member/templates/member/add_members.html:46 #: apps/member/templates/member/club_form.html:16 #: apps/note/templates/note/transactiontemplate_form.html:18 #: apps/treasury/forms.py:89 apps/treasury/forms.py:143 #: apps/treasury/templates/treasury/invoice_form.html:74 #: apps/wei/templates/wei/bus_form.html:17 -#: apps/wei/templates/wei/busteam_form.html:17 +#: apps/wei/templates/wei/busteam_form.html:18 #: apps/wei/templates/wei/weiclub_form.html:17 #: apps/wei/templates/wei/weiregistration_form.html:18 msgid "Submit" @@ -402,42 +473,62 @@ msgid "edit" msgstr "bearbeiten" #: apps/activity/templates/activity/includes/activity_info.html:74 +#: 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" +msgstr "entfernen" + +#: apps/activity/templates/activity/includes/activity_info.html:77 msgid "Invite" msgstr "Einladen" -#: apps/activity/views.py:37 +#: apps/activity/views.py:38 msgid "Create new activity" msgstr "Neue Veranstaltung schaffen" -#: 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 "Veranstaltungen" -#: apps/activity/views.py:108 +#: apps/activity/views.py:105 msgid "Activity detail" msgstr "Veranstaltunginfo" -#: apps/activity/views.py:128 +#: apps/activity/views.py:150 msgid "Update activity" msgstr "Veranstaltung bearbeiten" -#: apps/activity/views.py:155 +#: apps/activity/views.py:177 +#, fuzzy +#| msgid "" +#| "You are not allowed to display the entry interface for this activity." +msgid "You are not allowed to delete this activity." +msgstr "Sie haben nicht das Recht diese Seite zu benuzten." + +#: apps/activity/views.py:180 +#, fuzzy +#| msgid "This activity is closed." +msgid "This activity is valid." +msgstr "Diese Veranstaltung ist geschlossen." + +#: apps/activity/views.py:206 msgid "Invite guest to the activity \"{}\"" msgstr "Gast zur Veranstaltung \"{}\" einladen" -#: apps/activity/views.py:193 +#: apps/activity/views.py:246 msgid "You are not allowed to display the entry interface for this activity." msgstr "Sie haben nicht das Recht diese Seite zu benuzten." -#: apps/activity/views.py:196 +#: apps/activity/views.py:249 msgid "This activity does not support activity entries." msgstr "Diese Veranstaltung braucht nicht Eintritt." -#: apps/activity/views.py:199 +#: apps/activity/views.py:252 msgid "This activity is closed." msgstr "Diese Veranstaltung ist geschlossen." -#: apps/activity/views.py:295 +#: apps/activity/views.py:357 msgid "Entry for activity \"{}\"" msgstr "Eintritt zur Veranstaltung \"{}\"" @@ -445,297 +536,365 @@ msgstr "Eintritt zur Veranstaltung \"{}\"" msgid "API" msgstr "API" -#: apps/food/apps.py:11 apps/food/models.py:105 +#: apps/food/apps.py:11 msgid "food" msgstr "" -#: apps/food/forms.py:32 -msgid "Fully used" -msgstr "" - -#: apps/food/forms.py:50 +#: apps/food/forms.py:49 msgid "Pasta METRO 5kg" msgstr "" -#: apps/food/forms.py:96 +#: apps/food/forms.py:53 apps/food/forms.py:81 +msgid "Specific order given to GCKs" +msgstr "" + +#: apps/food/forms.py:77 msgid "Lasagna" msgstr "" -#: apps/food/models.py:18 +#: apps/food/forms.py:116 +msgid "Shelf life (in hours)" +msgstr "" + +#: apps/food/forms.py:138 apps/food/forms.py:162 +#: apps/food/templates/food/transformedfood_update.html:25 +msgid "Fully used" +msgstr "" + +#: apps/food/forms.py:171 apps/food/templates/food/qrcode.html:29 +#: apps/food/templates/food/transformedfood_update.html:23 +#: apps/note/templates/note/transaction_form.html:132 +#: apps/treasury/models.py:61 +msgid "Name" +msgstr "Name" + +#: apps/food/forms.py:181 #, fuzzy #| msgid "phone number" -msgid "QR-code number" +msgid "QR code number" msgstr "Telefonnummer" -#: apps/food/models.py:26 -msgid "food container" -msgstr "" - -#: apps/food/models.py:30 -msgid "QR-code" -msgstr "" - -#: apps/food/models.py:31 -msgid "QR-codes" -msgstr "" - -#: apps/food/models.py:34 -#, python-brace-format -msgid "QR-code number {qr_code_number}" -msgstr "" - -#: apps/food/models.py:47 +#: apps/food/models.py:23 msgid "Allergen" msgstr "" -#: apps/food/models.py:48 apps/food/templates/food/basicfood_detail.html:17 -#: apps/food/templates/food/transformedfood_detail.html:20 +#: apps/food/models.py:24 msgid "Allergens" msgstr "" -#: apps/food/models.py:64 +#: apps/food/models.py:43 msgid "owner" msgstr "" -#: apps/food/models.py:70 -msgid "allergen" +#: apps/food/models.py:49 +msgid "allergens" msgstr "" -#: apps/food/models.py:74 +#: apps/food/models.py:53 #, fuzzy #| msgid "birth date" msgid "expiry date" msgstr "Geburtsdatum" -#: apps/food/models.py:80 -msgid "was eaten" +#: apps/food/models.py:59 +msgid "end of life" msgstr "" -#: apps/food/models.py:89 +#: apps/food/models.py:64 msgid "is ready" msgstr "" -#: apps/food/models.py:94 -#, fuzzy -#| msgid "active" -msgid "is active" -msgstr "Aktiv" - -#: apps/food/models.py:106 -msgid "foods" +#: apps/food/models.py:70 +msgid "order" msgstr "" -#: apps/food/models.py:122 +#: apps/food/models.py:107 apps/food/views.py:34 +#: note_kfet/templates/base.html:72 +msgid "Food" +msgstr "" + +#: apps/food/models.py:108 +msgid "Foods" +msgstr "" + +#: apps/food/models.py:117 #, fuzzy #| msgid "start date" msgid "arrival date" msgstr "Anfangsdatum" -#: apps/food/models.py:152 +#: apps/food/models.py:169 msgid "Basic food" msgstr "" -#: apps/food/models.py:153 +#: apps/food/models.py:170 msgid "Basic foods" msgstr "" -#: apps/food/models.py:161 +#: apps/food/models.py:182 #, fuzzy #| msgid "created at" msgid "creation date" msgstr "erschafft am" -#: apps/food/models.py:169 -msgid "transformed ingredient" -msgstr "" - -#: apps/food/models.py:174 +#: apps/food/models.py:188 msgid "shelf life" msgstr "" -#: apps/food/models.py:225 apps/food/views.py:365 +#: apps/food/models.py:196 +msgid "transformed ingredient" +msgstr "" + +#: apps/food/models.py:258 #, fuzzy #| msgid "Transfer money" msgid "Transformed food" msgstr "Geld überweisen" -#: apps/food/models.py:226 +#: apps/food/models.py:259 msgid "Transformed foods" msgstr "" -#: apps/food/templates/food/basicfood_detail.html:14 -#: apps/food/templates/food/qrcode_detail.html:15 -#: apps/food/templates/food/transformedfood_detail.html:14 +#: apps/food/models.py:271 #, fuzzy -#| msgid "Owned" -msgid "Owner" -msgstr "Besetzt" +#| msgid "phone number" +msgid "qr code number" +msgstr "Telefonnummer" -#: apps/food/templates/food/basicfood_detail.html:15 -#, fuzzy -#| msgid "start date" -msgid "Arrival date" -msgstr "Anfangsdatum" - -#: apps/food/templates/food/basicfood_detail.html:16 -#: apps/food/templates/food/qrcode_detail.html:16 -#: apps/food/templates/food/transformedfood_detail.html:19 -#, fuzzy -#| msgid "birth date" -msgid "Expiry date" -msgstr "Geburtsdatum" - -#: apps/food/templates/food/basicfood_detail.html:24 -#: apps/food/templates/food/transformedfood_detail.html:36 -#, fuzzy -#| msgid "active" -msgid "Active" -msgstr "Aktiv" - -#: apps/food/templates/food/basicfood_detail.html:25 -#: apps/food/templates/food/transformedfood_detail.html:37 -msgid "Eaten" +#: apps/food/models.py:278 +msgid "food container" msgstr "" -#: apps/food/templates/food/basicfood_detail.html:28 -#: apps/food/templates/food/qrcode_detail.html:20 -#: apps/food/templates/food/qrcode_detail.html:24 -#: apps/food/templates/food/transformedfood_detail.html:41 +#: apps/food/models.py:282 +msgid "QR-code" +msgstr "" + +#: apps/food/models.py:283 +msgid "QR-codes" +msgstr "" + +#: apps/food/models.py:286 +#: apps/food/templates/food/transformedfood_update.html:24 +#, fuzzy +#| msgid "phone number" +msgid "QR-code number" +msgstr "Telefonnummer" + +#: apps/food/templates/food/food_detail.html:19 +msgid "Contained in" +msgstr "" + +#: apps/food/templates/food/food_detail.html:26 +msgid "Contain" +msgstr "" + +#: apps/food/templates/food/food_detail.html:35 #, fuzzy #| msgid "Update bus" msgid "Update" msgstr "Bus bearbeiten" -#: apps/food/templates/food/basicfood_detail.html:32 -#: apps/food/templates/food/qrcode_detail.html:34 -#: apps/food/templates/food/transformedfood_detail.html:46 +#: apps/food/templates/food/food_detail.html:40 #, fuzzy #| msgid "Add team" msgid "Add to a meal" msgstr "Neue Team" -#: apps/food/templates/food/create_qrcode_form.html:14 +#: apps/food/templates/food/food_detail.html:45 #, fuzzy -#| msgid "Transfer money" -msgid "New basic food" -msgstr "Geld überweisen" +#| msgid "manage entries" +msgid "Manage ingredients" +msgstr "Einträge verwalten" -#: apps/food/templates/food/qrcode_detail.html:10 +#: apps/food/templates/food/food_detail.html:49 #, fuzzy -#| msgid "phone number" -msgid "number" -msgstr "Telefonnummer" +#| msgid "Return to credit list" +msgid "Return to the food list" +msgstr "Zurück zur Kreditlist" -#: apps/food/templates/food/qrcode_detail.html:14 -#: apps/note/templates/note/transaction_form.html:132 -#: apps/treasury/models.py:60 -msgid "Name" -msgstr "Name" - -#: apps/food/templates/food/qrcode_detail.html:29 -#, fuzzy -#| msgid "Profile detail" -msgid "View details" -msgstr "Profile detail" - -#: apps/food/templates/food/transformedfood_detail.html:16 -#: apps/food/templates/food/transformedfood_detail.html:35 -msgid "Ready" -msgstr "" - -#: apps/food/templates/food/transformedfood_detail.html:18 -#, fuzzy -#| msgid "created at" -msgid "Creation date" -msgstr "erschafft am" - -#: apps/food/templates/food/transformedfood_detail.html:27 -msgid "Ingredients" -msgstr "" - -#: apps/food/templates/food/transformedfood_detail.html:34 -msgid "Shelf life" -msgstr "" - -#: apps/food/templates/food/transformedfood_list.html:11 +#: apps/food/templates/food/food_list.html:14 msgid "Meal served" msgstr "" -#: apps/food/templates/food/transformedfood_list.html:16 +#: apps/food/templates/food/food_list.html:19 #, fuzzy #| msgid "New user" msgid "New meal" msgstr "Neue User" -#: apps/food/templates/food/transformedfood_list.html:25 +#: apps/food/templates/food/food_list.html:28 #, fuzzy #| msgid "There is no results." msgid "There is no meal served." msgstr "Es gibt keine Ergebnisse." -#: apps/food/templates/food/transformedfood_list.html:33 -msgid "Open" +#: apps/food/templates/food/food_list.html:35 +msgid "Free food" msgstr "" -#: apps/food/templates/food/transformedfood_list.html:40 +#: apps/food/templates/food/food_list.html:42 #, fuzzy #| msgid "There is no results." -msgid "There is no free meal." +msgid "There is no free food." msgstr "Es gibt keine Ergebnisse." -#: apps/food/templates/food/transformedfood_list.html:48 -msgid "All meals" +#: apps/food/templates/food/food_list.html:50 +#, fuzzy +#| msgid "for club" +msgid "Food of your clubs" +msgstr "Für Club" + +#: apps/food/templates/food/food_list.html:56 +#, fuzzy +#| msgid "for club" +msgid "Food of club" +msgstr "Für Club" + +#: apps/food/templates/food/food_list.html:63 +msgid "Yours club has not food yet." msgstr "" -#: apps/food/templates/food/transformedfood_list.html:55 -#, fuzzy -#| msgid "There is no results." -msgid "There is no meal." -msgstr "Es gibt keine Ergebnisse." - -#: apps/food/views.py:28 -msgid "Add the ingredient" +#: apps/food/templates/food/manage_ingredients.html:45 +#: apps/food/templates/food/transformedfood_update.html:42 +msgid "Add ingredient" msgstr "" -#: apps/food/views.py:42 +#: apps/food/templates/food/manage_ingredients.html:46 +#: apps/food/templates/food/transformedfood_update.html:43 #, fuzzy -#| msgid "This credit is already validated." -msgid "The product is already prepared" -msgstr "Dieser Kredit ist bereits validiert." +#| msgid "Remove product" +msgid "Remove ingredient" +msgstr "Produkt entfernen" -#: apps/food/views.py:70 +#: apps/food/templates/food/qrcode.html:22 +msgid "Copy constructor" +msgstr "" + +#: apps/food/templates/food/qrcode.html:23 +#, fuzzy +#| msgid "Transfer money" +msgid "New food" +msgstr "Geld überweisen" + +#: apps/food/templates/food/qrcode.html:32 +#, fuzzy +#| msgid "Owned" +msgid "Owner" +msgstr "Besetzt" + +#: apps/food/templates/food/qrcode.html:35 +#, fuzzy +#| msgid "birth date" +msgid "Expiry date" +msgstr "Geburtsdatum" + +#: apps/food/utils.py:6 +msgid "second" +msgstr "" + +#: apps/food/utils.py:6 +msgid "seconds" +msgstr "" + +#: apps/food/utils.py:7 +msgid "minute" +msgstr "" + +#: apps/food/utils.py:7 +msgid "minutes" +msgstr "" + +#: apps/food/utils.py:8 +msgid "hour" +msgstr "" + +#: apps/food/utils.py:8 +msgid "hours" +msgstr "" + +#: apps/food/utils.py:9 +#, fuzzy +#| msgid "days" +msgid "day" +msgstr "Tagen" + +#: apps/food/utils.py:9 apps/member/templates/member/includes/club_info.html:27 +msgid "days" +msgstr "Tagen" + +#: apps/food/utils.py:10 +msgid "week" +msgstr "" + +#: apps/food/utils.py:10 +msgid "weeks" +msgstr "" + +#: apps/food/utils.py:53 +#: env/lib/python3.11/site-packages/django/db/models/base.py:1423 +#: env/lib/python3.11/site-packages/django/forms/models.py:893 +#, fuzzy +#| msgid "add" +msgid "and" +msgstr "hinzufügen" + +#: apps/food/views.py:118 +msgid "Add a new QRCode" +msgstr "" + +#: apps/food/views.py:167 +#, fuzzy +#| msgid "Update an invoice" +msgid "Add an aliment" +msgstr "Rechnung bearbeiten" + +#: apps/food/views.py:235 +#, fuzzy +#| msgid "Add team" +msgid "Add a meal" +msgstr "Neue Team" + +#: apps/food/views.py:275 +#, fuzzy +#| msgid "manage entries" +msgid "Manage ingredients of:" +msgstr "Einträge verwalten" + +#: apps/food/views.py:289 apps/food/views.py:297 +#, python-brace-format +msgid "Fully used in {meal}" +msgstr "" + +#: apps/food/views.py:344 +msgid "Add the ingredient:" +msgstr "" + +#: apps/food/views.py:370 +#, python-brace-format +msgid "Food fully used in : {meal.name}" +msgstr "" + +#: apps/food/views.py:389 #, fuzzy #| msgid "Update an invoice" msgid "Update an aliment" msgstr "Rechnung bearbeiten" -#: apps/food/views.py:97 +#: apps/food/views.py:437 #, fuzzy #| msgid "WEI Detail" msgid "Details of:" msgstr "WEI Infos" -#: apps/food/views.py:121 -msgid "Add a new basic food with QRCode" -msgstr "" +#: apps/food/views.py:447 apps/treasury/tables.py:149 +#: env/lib/python3.11/site-packages/django/forms/widgets.py:795 +msgid "Yes" +msgstr "Ja" -#: apps/food/views.py:185 -msgid "Add a new QRCode" -msgstr "" - -#: apps/food/views.py:235 -msgid "QRCode" -msgstr "" - -#: apps/food/views.py:271 -msgid "Add a new meal" -msgstr "" - -#: apps/food/views.py:337 -#, fuzzy -#| msgid "Update team" -msgid "Update a meal" -msgstr "Team bearbeiten" +#: apps/food/views.py:449 apps/member/models.py:99 apps/treasury/tables.py:149 +#: env/lib/python3.11/site-packages/django/forms/widgets.py:796 +msgid "No" +msgstr "Nein" #: apps/logs/apps.py:11 msgid "Logs" @@ -765,12 +924,6 @@ msgstr "neue Daten" msgid "create" msgstr "schaffen" -#: apps/logs/models.py:65 apps/note/tables.py:230 apps/note/tables.py:277 -#: apps/permission/models.py:126 apps/treasury/tables.py:38 -#: apps/wei/tables.py:74 -msgid "delete" -msgstr "entfernen" - #: apps/logs/models.py:68 msgid "action" msgstr "Aktion" @@ -806,11 +959,11 @@ msgstr "Mitgliedschaftpreis (bezahlte Studenten)" msgid "membership fee (unpaid students)" msgstr "Mitgliedschaftpreis (unbezahlte Studenten)" -#: apps/member/admin.py:65 apps/member/models.py:330 +#: apps/member/admin.py:65 apps/member/models.py:337 msgid "roles" msgstr "Rollen" -#: apps/member/admin.py:66 apps/member/models.py:344 +#: apps/member/admin.py:66 apps/member/models.py:351 msgid "fee" msgstr "Preis" @@ -818,26 +971,26 @@ msgstr "Preis" msgid "member" msgstr "Mitglied" -#: apps/member/forms.py:24 +#: apps/member/forms.py:25 msgid "Permission mask" msgstr "Berechtigungsmaske" -#: apps/member/forms.py:46 +#: apps/member/forms.py:48 msgid "Report frequency" msgstr "Bericht Frequenz" -#: apps/member/forms.py:48 +#: apps/member/forms.py:50 msgid "Last report date" msgstr "Letzen Bericht Datum" -#: apps/member/forms.py:52 +#: apps/member/forms.py:54 msgid "" "Anti-VSS (Violences Sexistes et Sexuelles) charter read and approved" msgstr "" "Anti-VSS (Violences Sexistes et Sexuelles) Charta gelesen und " "angenommen" -#: apps/member/forms.py:53 +#: apps/member/forms.py:55 msgid "" "Tick after having read and accepted the anti-VSS charter " @@ -847,65 +1000,65 @@ msgstr "" "haben, die hier als pdf-Datei verfügbar ist" -#: 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 "Sie dürfen nicht einloggen wenn sie aus der Zukunft kommen." -#: apps/member/forms.py:86 +#: apps/member/forms.py:89 msgid "select an image" msgstr "Wählen sie ein Bild aus" -#: apps/member/forms.py:87 +#: apps/member/forms.py:90 msgid "Maximal size: 2MB" msgstr "Maximal Größe: 2MB" -#: apps/member/forms.py:112 +#: apps/member/forms.py:115 msgid "This image cannot be loaded." msgstr "Dieses Bild kann nicht geladen werden." -#: 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:117 +#: apps/registration/forms.py:33 apps/registration/views.py:282 msgid "An alias with a similar name already exists." msgstr "Ein ähnliches Alias ist schon benutzt." -#: apps/member/forms.py:175 +#: apps/member/forms.py:178 msgid "Inscription paid by Société Générale" msgstr "Mitgliedschaft von der Société Générale bezahlt" -#: 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 "Die Société Générale die Mitgliedschaft bezahlt." -#: 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:94 msgid "Credit type" msgstr "Kredittype" -#: 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:95 msgid "No credit" msgstr "Kein Kredit" -#: apps/member/forms.py:185 +#: apps/member/forms.py:188 msgid "You can credit the note of the user." msgstr "Sie dûrfen diese Note kreditieren." -#: 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:100 msgid "Credit amount" msgstr "Kreditanzahl" -#: 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:117 msgid "Bank" msgstr "Bank" -#: apps/member/forms.py:233 +#: apps/member/forms.py:236 msgid "User" msgstr "User" -#: apps/member/forms.py:247 +#: apps/member/forms.py:250 msgid "Roles" msgstr "Rollen" @@ -1040,10 +1193,6 @@ msgstr "bezahlt" msgid "Tells if the user receive a salary." msgstr "User ist bezahlt." -#: apps/member/models.py:99 apps/treasury/tables.py:143 -msgid "No" -msgstr "Nein" - #: apps/member/models.py:100 msgid "Yes (receive them in french)" msgstr "Ja (auf Fränzosich)" @@ -1161,7 +1310,7 @@ msgstr "" msgid "add to registration form" msgstr "Registrierung validieren" -#: 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" @@ -1170,37 +1319,37 @@ msgstr "Club" msgid "clubs" msgstr "Clubs" -#: apps/member/models.py:335 +#: apps/member/models.py:342 msgid "membership starts on" msgstr "Mitgliedschaft fängt an" -#: apps/member/models.py:339 +#: apps/member/models.py:346 msgid "membership ends on" msgstr "Mitgliedschaft endet am" -#: 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 "Mitgliedschaft" -#: apps/member/models.py:349 +#: apps/member/models.py:356 msgid "memberships" msgstr "Mitgliedschaften" -#: apps/member/models.py:353 +#: apps/member/models.py:360 #, python-brace-format msgid "Membership of {user} for the club {club}" msgstr "Mitgliedschaft von {user} für das 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 "Die Rolle {role} ist nicht erlaubt für das Club {club}." -#: apps/member/models.py:381 apps/member/views.py:715 +#: apps/member/models.py:388 apps/member/views.py:759 msgid "User is already a member of the club" msgstr "User ist schon ein Mitglied dieser club" -#: apps/member/models.py:393 apps/member/views.py:724 +#: apps/member/models.py:400 apps/member/views.py:768 msgid "User is not a member of the parent club" msgstr "User ist noch nicht Mitglied des Urclubs" @@ -1255,7 +1404,7 @@ msgid "Account #" msgstr "Konto #" #: 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:61 #: apps/registration/templates/registration/future_profile_detail.html:48 #: apps/wei/templates/wei/weimembership_form.html:117 msgid "Update Profile" @@ -1316,20 +1465,11 @@ msgstr "" "erlaubt." #: 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:318 +#: apps/member/views.py:559 msgid "Note aliases" msgstr "Note Aliases" -#: 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 "Neue" - #: apps/member/templates/member/club_detail.html:13 #: apps/permission/templates/permission/all_rights.html:32 msgid "Club managers" @@ -1366,10 +1506,6 @@ msgstr "Keine Mitgliedschaft mit diesem pattern gefunden." msgid "Club Parent" msgstr "Urclub" -#: apps/member/templates/member/includes/club_info.html:27 -msgid "days" -msgstr "Tagen" - #: apps/member/templates/member/includes/club_info.html:31 #: apps/wei/templates/wei/base.html:40 msgid "membership fee" @@ -1473,11 +1609,11 @@ msgstr "" msgid "Show my applications" msgstr "" -#: apps/member/templates/member/picture_update.html:38 +#: apps/member/templates/member/picture_update.html:40 msgid "Nevermind" msgstr "Vergessen" -#: apps/member/templates/member/picture_update.html:39 +#: apps/member/templates/member/picture_update.html:41 msgid "Crop and upload" msgstr "Beschneiden und hochladen" @@ -1522,51 +1658,51 @@ msgstr "Speichern" msgid "Registrations" msgstr "Anmeldung" -#: apps/member/views.py:72 apps/registration/forms.py:23 +#: apps/member/views.py:74 apps/registration/forms.py:23 msgid "This address must be valid." msgstr "Diese Adresse muss gültig sein." -#: apps/member/views.py:139 +#: apps/member/views.py:154 msgid "Profile detail" msgstr "Profile detail" -#: apps/member/views.py:205 +#: apps/member/views.py:220 msgid "Search user" msgstr "User finden" -#: apps/member/views.py:253 +#: apps/member/views.py:272 msgid "Note friendships" msgstr "" -#: apps/member/views.py:308 +#: apps/member/views.py:342 msgid "Update note picture" msgstr "Notebild ändern" -#: apps/member/views.py:357 +#: apps/member/views.py:391 msgid "Manage auth token" msgstr "Auth token bearbeiten" -#: apps/member/views.py:384 +#: apps/member/views.py:418 msgid "Create new club" msgstr "Neue Club" -#: apps/member/views.py:403 +#: apps/member/views.py:437 msgid "Search club" msgstr "Club finden" -#: apps/member/views.py:436 +#: apps/member/views.py:475 msgid "Club detail" msgstr "Club Details" -#: apps/member/views.py:543 +#: apps/member/views.py:587 msgid "Update club" msgstr "Club bearbeiten" -#: apps/member/views.py:577 +#: apps/member/views.py:621 msgid "Add new member to the club" msgstr "Neue Mitglieder" -#: apps/member/views.py:706 apps/wei/views.py:973 +#: apps/member/views.py:750 apps/wei/views.py:1040 msgid "" "This user don't have enough money to join this club, and can't have a " "negative balance." @@ -1574,19 +1710,19 @@ msgstr "" "Diese User hat nicht genug Geld um Mitglied zu werden, und darf nich im Rot " "sein." -#: apps/member/views.py:728 +#: apps/member/views.py:772 msgid "The membership must start after {:%m-%d-%Y}." msgstr "Die Mitgliedschaft muss nach {:%m-%d-Y} anfängen." -#: apps/member/views.py:733 +#: apps/member/views.py:777 msgid "The membership must begin before {:%m-%d-%Y}." msgstr "Die Mitgliedschaft muss vor {:%m-%d-Y} anfängen." -#: apps/member/views.py:883 +#: apps/member/views.py:927 msgid "Manage roles of an user in the club" msgstr "Rollen in diesen Club bearbeiten" -#: apps/member/views.py:908 +#: apps/member/views.py:952 msgid "Members of the club" msgstr "Mitlglieder dieses Club" @@ -1619,35 +1755,35 @@ msgstr "" "Diese Transaktion ist nicht möglich weil die Note des Sender oder des " "Empfänger inaktiv ist." -#: apps/note/forms.py:39 +#: apps/note/forms.py:40 msgid "Source" msgstr "Sender" -#: apps/note/forms.py:53 +#: apps/note/forms.py:54 msgid "Destination" msgstr "Empfänger" -#: 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 "Grund" -#: apps/note/forms.py:79 apps/treasury/tables.py:136 +#: apps/note/forms.py:80 apps/treasury/tables.py:141 msgid "Valid" msgstr "Gültig" -#: apps/note/forms.py:85 +#: apps/note/forms.py:86 msgid "Total amount greater than" msgstr "Totalanzahl größer als" -#: apps/note/forms.py:93 +#: apps/note/forms.py:94 msgid "Total amount less than" msgstr "Totalanzahl kleiner als" -#: apps/note/forms.py:99 +#: apps/note/forms.py:100 msgid "Created after" msgstr "Erschafft nacht" -#: apps/note/forms.py:106 +#: apps/note/forms.py:107 msgid "Created before" msgstr "Erschafft vor" @@ -1752,7 +1888,7 @@ msgstr "" #: apps/note/models/notes.py:243 #, fuzzy #| msgid "Manage aliases" -msgid "friendship" +msgid "frienship" msgstr "Aliases bearbeiten" #: apps/note/models/notes.py:248 @@ -1902,8 +2038,9 @@ msgstr "" "Zahlungsmethode zugeordnet ist, und einem User oder einem Club möglich" #: 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:1045 +#: apps/wei/views.py:1049 +#: env/lib/python3.11/site-packages/django/forms/fields.py:91 msgid "This field is required." msgstr "Dies ist ein Pflichtfeld." @@ -1911,7 +2048,7 @@ msgstr "Dies ist ein Pflichtfeld." msgid "membership transaction" msgstr "Mitgliedschafttransaktion" -#: apps/note/models/transactions.py:381 apps/treasury/models.py:300 +#: apps/note/models/transactions.py:381 apps/treasury/models.py:301 msgid "membership transactions" msgstr "Mitgliedschaftttransaktionen" @@ -1927,18 +2064,6 @@ msgstr "Klicken Sie zum gültigmachen" msgid "No reason specified" msgstr "Kein Grund gegeben" -#: 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 "Löschen" - #: apps/note/tables.py:191 msgid "Trust back" msgstr "" @@ -1954,12 +2079,13 @@ msgstr "Neue Bus" #: apps/wei/templates/wei/base.html:89 #: apps/wei/templates/wei/bus_detail.html:20 #: apps/wei/templates/wei/busteam_detail.html:20 -#: apps/wei/templates/wei/busteam_detail.html:40 +#: apps/wei/templates/wei/busteam_detail.html:42 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:37 #: note_kfet/templates/oauth2_provider/application_detail.html:38 msgid "Edit" msgstr "Bearbeiten" -#: apps/note/tables.py:266 apps/note/tables.py:293 +#: apps/note/tables.py:267 apps/note/tables.py:296 msgid "Hide/Show" msgstr "" @@ -2045,8 +2171,8 @@ msgid "Action" msgstr "Aktion" #: 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 "Anzahl" @@ -2112,34 +2238,35 @@ msgid "Button displayed" msgstr "Tastenliste" #: apps/note/templates/note/transactiontemplate_list.html:100 +#: apps/wrapped/templates/wrapped/wrapped_list.html:70 msgid "An error occured" msgstr "" -#: apps/note/views.py:36 +#: apps/note/views.py:37 msgid "Transfer money" msgstr "Geld überweisen" -#: apps/note/views.py:74 +#: apps/note/views.py:75 msgid "Create new button" msgstr "Neue Tatse berstellen" -#: apps/note/views.py:83 +#: apps/note/views.py:84 msgid "Search button" msgstr "Tatsen finden" -#: apps/note/views.py:111 +#: apps/note/views.py:116 msgid "Update button" msgstr "Tatse bearbeiten" -#: 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 "Verbräuche" -#: apps/note/views.py:165 +#: apps/note/views.py:170 msgid "You can't see any button." msgstr "Sie können keine Taste sehen." -#: apps/note/views.py:204 +#: apps/note/views.py:209 msgid "Search transactions" msgstr "Transaktion finden" @@ -2231,7 +2358,7 @@ msgstr "" "Sie haben nicht die Berechtigung, das Feld {field} in dieser Instanz von " "Modell {app_label} zu ändern. {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}." @@ -2294,21 +2421,24 @@ msgid "Available scopes" msgstr "" #: apps/permission/templates/permission/scopes.html:42 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:24 msgid "No applications defined" msgstr "" #: apps/permission/templates/permission/scopes.html:43 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:25 msgid "Click here" msgstr "" #: apps/permission/templates/permission/scopes.html:43 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:25 msgid "if you want to register a new one" msgstr "" -#: 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 " @@ -2318,7 +2448,7 @@ msgstr "" "diesen Parametern zu aktualisieren. Bitte korrigieren Sie Ihre Daten und " "versuchen Sie es erneut." -#: 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}\" " @@ -2328,11 +2458,11 @@ msgstr "" "diesen Parametern zu erstellen. Bitte korrigieren Sie Ihre Daten und " "versuchen Sie es erneut." -#: 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 "Rechten" -#: apps/permission/views.py:117 +#: apps/permission/views.py:137 msgid "All rights" msgstr "Alle Rechten" @@ -2472,60 +2602,68 @@ msgstr "Danke" msgid "The Note Kfet team." msgstr "Die NoteKfet Team." -#: apps/registration/views.py:42 +#: apps/registration/views.py:43 msgid "Register new user" msgstr "Neuen User registrieren" -#: apps/registration/views.py:100 +#: apps/registration/views.py:101 msgid "Email validation" msgstr "Email validierung" -#: apps/registration/views.py:102 +#: apps/registration/views.py:103 msgid "Validate email" msgstr "Email validieren" -#: apps/registration/views.py:146 +#: apps/registration/views.py:147 msgid "Email validation unsuccessful" msgstr "Email validierung unerfolgreich" -#: apps/registration/views.py:157 +#: apps/registration/views.py:158 msgid "Email validation email sent" msgstr "Validierungsemail wurde gesendet" -#: apps/registration/views.py:165 +#: apps/registration/views.py:166 msgid "Resend email validation link" msgstr "E-Mail-Validierungslink erneut senden" -#: apps/registration/views.py:183 +#: apps/registration/views.py:184 msgid "Pre-registered users list" msgstr "Vorregistrierte Userliste" -#: apps/registration/views.py:207 +#: apps/registration/views.py:213 msgid "Unregistered users" msgstr "Unregistrierte Users" -#: apps/registration/views.py:220 +#: apps/registration/views.py:226 msgid "Registration detail" msgstr "Registrierung Detailen" -#: apps/registration/views.py:256 +#: apps/registration/views.py:262 #, fuzzy, python-format #| msgid "Note of %(club)s club" msgid "Join %(club)s Club" msgstr "%(club)s Note" -#: 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 "Sie müssen die BDE beitreten." -#: 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 "Sie müssen die BDE beitreten." + +#: apps/registration/views.py:340 msgid "" "The entered amount is not enough for the memberships, should be at least {}" msgstr "" "Der eingegebene Betrag reicht für die Mitgliedschaft nicht aus, sollte " "mindestens {} betragen" -#: apps/registration/views.py:425 +#: apps/registration/views.py:435 msgid "Invalidate pre-registration" msgstr "Ungültige Vorregistrierung" @@ -2533,7 +2671,7 @@ msgstr "Ungültige Vorregistrierung" msgid "Treasury" msgstr "Quaestor" -#: apps/treasury/forms.py:26 apps/treasury/models.py:112 +#: apps/treasury/forms.py:26 apps/treasury/models.py:113 #: apps/treasury/templates/treasury/invoice_form.html:22 msgid "This invoice is locked and can no longer be edited." msgstr "Diese Rechnung ist gesperrt und kann nicht mehr bearbeitet werden." @@ -2546,8 +2684,8 @@ msgstr "Überweisung ist bereits geschlossen." msgid "You can't change the type of the remittance." msgstr "Sie können die Art der Überweisung nicht ändern." -#: apps/treasury/forms.py:125 apps/treasury/models.py:275 -#: apps/treasury/tables.py:97 apps/treasury/tables.py:105 +#: apps/treasury/forms.py:125 apps/treasury/models.py:276 +#: 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 @@ -2562,142 +2700,143 @@ msgstr "Keine beigefügte Überweisung" msgid "Invoice identifier" msgstr "Rechnungskennung" -#: apps/treasury/models.py:42 +#: apps/treasury/models.py:43 apps/wrapped/models.py:28 +#: apps/wrapped/models.py:29 msgid "BDE" msgstr "BDE" -#: apps/treasury/models.py:46 +#: apps/treasury/models.py:47 #, fuzzy #| msgid "location" msgid "Quotation" msgstr "Ort" -#: apps/treasury/models.py:51 +#: apps/treasury/models.py:52 msgid "Object" msgstr "Objekt" -#: apps/treasury/models.py:55 +#: apps/treasury/models.py:56 msgid "Description" msgstr "Beschreibung" -#: apps/treasury/models.py:64 +#: apps/treasury/models.py:65 msgid "Address" msgstr "Adresse" -#: apps/treasury/models.py:69 apps/treasury/models.py:202 +#: apps/treasury/models.py:70 apps/treasury/models.py:203 msgid "Date" msgstr "Datum" -#: apps/treasury/models.py:75 +#: apps/treasury/models.py:76 #, fuzzy #| msgid "end date" msgid "Payment date" msgstr "Abschlussdatum" -#: apps/treasury/models.py:79 +#: apps/treasury/models.py:80 msgid "Acquitted" msgstr "Bezahlt" -#: apps/treasury/models.py:84 +#: apps/treasury/models.py:85 msgid "Locked" msgstr "Gesperrt" -#: apps/treasury/models.py:85 +#: apps/treasury/models.py:86 msgid "An invoice can't be edited when it is locked." msgstr "Eine Rechnung kann nicht bearbeitet werden, wenn sie gesperrt ist." -#: apps/treasury/models.py:91 +#: apps/treasury/models.py:92 msgid "tex source" msgstr "Tex Quelle" -#: apps/treasury/models.py:95 apps/treasury/models.py:140 +#: apps/treasury/models.py:96 apps/treasury/models.py:141 msgid "invoice" msgstr "Rechnung" -#: apps/treasury/models.py:96 +#: apps/treasury/models.py:97 msgid "invoices" msgstr "Rechnungen" -#: apps/treasury/models.py:99 +#: apps/treasury/models.py:100 #, python-brace-format msgid "Invoice #{id}" msgstr "Rechnung #{id}" -#: apps/treasury/models.py:145 +#: apps/treasury/models.py:146 msgid "Designation" msgstr "Bezeichnung" -#: apps/treasury/models.py:151 +#: apps/treasury/models.py:152 msgid "Quantity" msgstr "Qualität" -#: apps/treasury/models.py:156 +#: apps/treasury/models.py:157 msgid "Unit price" msgstr "Einzelpreis" -#: apps/treasury/models.py:160 +#: apps/treasury/models.py:161 msgid "product" msgstr "Produkt" -#: apps/treasury/models.py:161 +#: apps/treasury/models.py:162 msgid "products" msgstr "Produkten" -#: apps/treasury/models.py:189 +#: apps/treasury/models.py:190 msgid "remittance type" msgstr "Überweisungstyp" -#: apps/treasury/models.py:190 +#: apps/treasury/models.py:191 msgid "remittance types" msgstr "Überweisungstypen" -#: apps/treasury/models.py:213 +#: apps/treasury/models.py:214 msgid "Comment" msgstr "Kommentar" -#: apps/treasury/models.py:218 +#: apps/treasury/models.py:219 msgid "Closed" msgstr "Geschlossen" -#: apps/treasury/models.py:222 +#: apps/treasury/models.py:223 msgid "remittance" msgstr "Überweisung" -#: apps/treasury/models.py:223 +#: apps/treasury/models.py:224 msgid "remittances" msgstr "Überweisungen" -#: apps/treasury/models.py:226 +#: apps/treasury/models.py:227 msgid "Remittance #{:d}: {}" msgstr "Überweisung #{:d}:{}" -#: apps/treasury/models.py:279 +#: apps/treasury/models.py:280 msgid "special transaction proxy" msgstr "spezielle Transaktion Proxy" -#: apps/treasury/models.py:280 +#: apps/treasury/models.py:281 msgid "special transaction proxies" msgstr "spezielle Transaktion Proxies" -#: apps/treasury/models.py:306 +#: apps/treasury/models.py:307 msgid "credit transaction" msgstr "Kredit Transaktion" -#: apps/treasury/models.py:311 +#: apps/treasury/models.py:312 #: apps/treasury/templates/treasury/sogecredit_detail.html:10 msgid "Credit from the Société générale" msgstr "Kredit von der Société générale" -#: apps/treasury/models.py:312 +#: apps/treasury/models.py:313 msgid "Credits from the Société générale" msgstr "Krediten von der Société générale" -#: apps/treasury/models.py:315 +#: apps/treasury/models.py:316 #, python-brace-format msgid "Soge credit for {user}" msgstr "Kredit von der Société générale für {user}" -#: apps/treasury/models.py:445 +#: apps/treasury/models.py:446 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." @@ -2716,25 +2855,22 @@ msgstr "Rechnung #{:d}" msgid "Invoice" msgstr "Rechnung" -#: apps/treasury/tables.py:65 +#: apps/treasury/tables.py:66 msgid "Transaction count" msgstr "Transaktionanzahl" -#: apps/treasury/tables.py:70 apps/treasury/tables.py:72 +#: apps/treasury/tables.py:71 apps/treasury/tables.py:73 +#: apps/wei/templates/wei/busteam_detail.html:22 apps/wrapped/tables.py:42 msgid "View" msgstr "Schauen" -#: apps/treasury/tables.py:143 -msgid "Yes" -msgstr "Ja" - #: apps/treasury/templates/treasury/invoice_confirm_delete.html:10 -#: apps/treasury/views.py:173 +#: apps/treasury/views.py:174 msgid "Delete invoice" msgstr "Rechnung löschen" #: 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 "Eine Rechnung kann nicht gelöscht werden, wenn sie gesperrt ist." @@ -2912,44 +3048,44 @@ msgstr "Kredit von der Société générale" msgid "Credit successfully registered" msgstr "Taste erfolgreich gelöscht " -#: apps/treasury/views.py:40 +#: apps/treasury/views.py:41 msgid "Create new invoice" msgstr "Neue Rechnung" -#: apps/treasury/views.py:97 +#: apps/treasury/views.py:98 msgid "Invoices list" msgstr "Rechnunglist" -#: 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 "Sie können die Quaestor-App nicht sehen." -#: apps/treasury/views.py:115 +#: apps/treasury/views.py:116 msgid "Update an invoice" msgstr "Rechnung bearbeiten" -#: apps/treasury/views.py:240 +#: apps/treasury/views.py:241 msgid "Create a new remittance" msgstr "Neue Überweisung" -#: apps/treasury/views.py:267 +#: apps/treasury/views.py:265 msgid "Remittances list" msgstr "Überweisungliste" -#: apps/treasury/views.py:326 +#: apps/treasury/views.py:320 msgid "Update a remittance" msgstr "Überweisung bearbeiten" -#: apps/treasury/views.py:349 +#: apps/treasury/views.py:342 msgid "Attach a transaction to a remittance" msgstr "Fügen Sie einer Überweisung eine Transaktion hinzu" -#: apps/treasury/views.py:393 +#: apps/treasury/views.py:386 msgid "List of credits from the Société générale" msgstr "Kreditliste von 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 "Krediten von der Société générale handeln" @@ -2959,16 +3095,16 @@ msgstr "Krediten von der Société générale handeln" 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 "" -#: apps/wei/forms/registration.py:59 apps/wei/models.py:126 +#: apps/wei/forms/registration.py:62 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:63 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." @@ -2977,11 +3113,11 @@ msgstr "" "einen Bus und ein Team zuzuweisen, insbesondere wenn Sie ein freies Elektron " "sind." -#: apps/wei/forms/registration.py:67 +#: apps/wei/forms/registration.py:70 msgid "Team" msgstr "Team" -#: apps/wei/forms/registration.py:69 +#: apps/wei/forms/registration.py:72 msgid "" "Leave this field empty if you won't be in a team (staff, bus chief, free " "electron)" @@ -2989,16 +3125,16 @@ msgstr "" "Lassen Sie dieses Feld leer, wenn Sie nicht in einem Team sind (Mitarbeiter, " "Buschef, freies Elektron)" -#: apps/wei/forms/registration.py:75 apps/wei/forms/registration.py:85 +#: apps/wei/forms/registration.py:78 apps/wei/forms/registration.py:88 #: apps/wei/models.py:160 msgid "WEI Roles" msgstr "WEI Rollen" -#: apps/wei/forms/registration.py:76 +#: apps/wei/forms/registration.py:79 msgid "Select the roles that you are interested in." msgstr "Wählen Sie die Rollen aus, an denen Sie interessiert sind." -#: apps/wei/forms/registration.py:122 +#: apps/wei/forms/registration.py:125 msgid "This team doesn't belong to the given bus." msgstr "Dieses Team gehört nicht zum angegebenen Bus." @@ -3011,10 +3147,12 @@ msgid "year" msgstr "Jahr" #: apps/wei/models.py:29 apps/wei/templates/wei/base.html:30 +#: apps/wrapped/models.py:20 msgid "date start" msgstr "Anfangsdatum" #: apps/wei/models.py:33 apps/wei/templates/wei/base.html:33 +#: apps/wrapped/models.py:24 msgid "date end" msgstr "Abschlussdatum" @@ -3064,7 +3202,7 @@ msgstr "WEI Rolle" msgid "Credit from Société générale" msgstr "Kredit von der Société générale" -#: apps/wei/models.py:188 +#: apps/wei/models.py:188 apps/wei/views.py:951 msgid "Caution check given" msgstr "Caution check given" @@ -3101,8 +3239,7 @@ msgstr "Kleidung Schnitt" msgid "clothing size" msgstr "Kleidergröße" -#: 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 "Gesundheitsprobleme" @@ -3191,7 +3328,7 @@ msgid "preferred bus" msgstr "bevorzugter Bus" #: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:32 -#: apps/wei/templates/wei/busteam_detail.html:50 +#: apps/wei/templates/wei/busteam_detail.html:52 msgid "Teams" msgstr "Teams" @@ -3230,13 +3367,20 @@ msgid "Attribute first year members into buses" msgstr "" #: apps/wei/templates/wei/1A_list.html:15 -msgid "Start attribution!" +msgid "Start attribution !" msgstr "" #: apps/wei/templates/wei/attribute_bus_1A.html:8 msgid "Bus attribution" msgstr "" +#: 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 "Gesundheitsprobleme" + #: apps/wei/templates/wei/attribute_bus_1A.html:31 msgid "suggested bus" msgstr "" @@ -3269,11 +3413,11 @@ msgstr "WEI Preis (unbezahlte Studenten)" msgid "WEI list" msgstr "WEI Liste" -#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:528 +#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:557 msgid "Register 1A" msgstr "1A Registrieren" -#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:614 +#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:649 msgid "Register 2A+" msgstr "2A+ Registrieren" @@ -3286,7 +3430,7 @@ msgid "View WEI" msgstr "WEI schauen" #: apps/wei/templates/wei/bus_detail.html:22 -#: apps/wei/templates/wei/busteam_detail.html:22 +#: apps/wei/templates/wei/busteam_detail.html:24 msgid "Add team" msgstr "Neue Team" @@ -3295,15 +3439,15 @@ msgid "Members" msgstr "Mitglied" #: apps/wei/templates/wei/bus_detail.html:54 -#: apps/wei/templates/wei/busteam_detail.html:60 +#: apps/wei/templates/wei/busteam_detail.html:62 #: apps/wei/templates/wei/weimembership_list.html:31 msgid "View as PDF" msgstr "Als PDF schauen" #: 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:1095 +#: apps/wei/views.py:1150 apps/wei/views.py:1197 msgid "Survey WEI" msgstr "WEI Umfrage" @@ -3347,7 +3491,7 @@ msgstr "Unvalidierte Registrierungen" msgid "Attribute buses" msgstr "" -#: 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:83 msgid "Create WEI" msgstr "Neue WEI" @@ -3489,67 +3633,67 @@ msgstr "Bei diesem Muster wurde keine Vorregistrierung gefunden." msgid "View validated memberships..." msgstr "Validierte Mitgliedschaften anzeigen ..." -#: apps/wei/views.py:58 +#: apps/wei/views.py:62 msgid "Search WEI" msgstr "WEI finden" -#: apps/wei/views.py:109 +#: apps/wei/views.py:113 msgid "WEI Detail" msgstr "WEI Infos" -#: apps/wei/views.py:208 +#: apps/wei/views.py:213 msgid "View members of the WEI" msgstr "Mitglied der WEI schauen" -#: apps/wei/views.py:236 +#: apps/wei/views.py:246 msgid "Find WEI Membership" msgstr "WEI Mitgliedschaft finden" -#: apps/wei/views.py:246 +#: apps/wei/views.py:256 msgid "View registrations to the WEI" msgstr "Mitglied der WEI schauen" -#: apps/wei/views.py:270 +#: apps/wei/views.py:285 msgid "Find WEI Registration" msgstr "WEI Registrierung finden" -#: apps/wei/views.py:281 +#: apps/wei/views.py:296 msgid "Update the WEI" msgstr "WEI bearbeiten" -#: apps/wei/views.py:302 +#: apps/wei/views.py:317 msgid "Create new bus" msgstr "Neue Bus" -#: apps/wei/views.py:340 +#: apps/wei/views.py:355 msgid "Update bus" msgstr "Bus bearbeiten" -#: apps/wei/views.py:372 +#: apps/wei/views.py:387 msgid "Manage bus" msgstr "Bus ändern" -#: apps/wei/views.py:399 +#: apps/wei/views.py:414 msgid "Create new team" msgstr "Neue Bus Team" -#: apps/wei/views.py:439 +#: apps/wei/views.py:461 msgid "Update team" msgstr "Team bearbeiten" -#: apps/wei/views.py:470 +#: apps/wei/views.py:499 msgid "Manage WEI team" msgstr "WEI Team bearbeiten" -#: apps/wei/views.py:492 +#: apps/wei/views.py:521 msgid "Register first year student to the WEI" msgstr "Registrieren Sie den Erstsemester beim WEI" -#: apps/wei/views.py:550 apps/wei/views.py:649 +#: apps/wei/views.py:585 apps/wei/views.py:688 msgid "This user is already registered to this WEI." msgstr "Dieser Benutzer ist bereits bei dieser WEI registriert." -#: apps/wei/views.py:555 +#: apps/wei/views.py:590 msgid "" "This user can't be in her/his first year since he/she has already " "participated to a WEI." @@ -3557,51 +3701,1785 @@ msgstr "" "Dieser Benutzer kann nicht in seinem ersten Jahr sein, da er bereits an " "einer WEI teilgenommen hat." -#: apps/wei/views.py:578 +#: apps/wei/views.py:613 msgid "Register old student to the WEI" msgstr "Registrieren Sie einen alten Studenten beim WEI" -#: apps/wei/views.py:633 apps/wei/views.py:721 +#: apps/wei/views.py:668 apps/wei/views.py:764 msgid "You already opened an account in the Société générale." msgstr "Sie haben bereits ein Konto in der Société générale eröffnet." -#: apps/wei/views.py:685 +#: apps/wei/views.py:724 msgid "Update WEI Registration" msgstr "WEI Registrierung aktualisieren" -#: apps/wei/views.py:795 +#: apps/wei/views.py:799 +#, fuzzy +#| msgid "The BDE membership is included in the WEI registration." +msgid "No membership found for this registration" +msgstr "Die BDE-Mitgliedschaft ist in der WEI-Registrierung enthalten." + +#: apps/wei/views.py:808 +#, fuzzy +#| msgid "" +#| "You don't have the permission to add an instance of model {app_label}." +#| "{model_name}." +msgid "You don't have the permission to update memberships" +msgstr "" +"Sie haben nicht die Berechtigung, eine Instanz von model {app_label}. " +"{model_name} hinzufügen." + +#: apps/wei/views.py:814 +#, fuzzy, python-format +#| msgid "" +#| "You don't have the permission to delete this instance of model " +#| "{app_label}.{model_name}." +msgid "You don't have the permission to update the field %(field)s" +msgstr "" +"Sie haben nicht die Berechtigung, eine Instanz von model {app_label}. " +"{model_name} zulöschen." + +#: apps/wei/views.py:855 msgid "Delete WEI registration" msgstr "WEI Registrierung löschen" -#: apps/wei/views.py:806 +#: apps/wei/views.py:866 msgid "You don't have the right to delete this WEI registration." msgstr "Sie haben nicht das Recht, diese WEI-Registrierung zu löschen." -#: apps/wei/views.py:824 +#: apps/wei/views.py:884 msgid "Validate WEI registration" msgstr "Überprüfen Sie die WEI-Registrierung" -#: apps/wei/views.py:1223 +#: apps/wei/views.py:889 +#, fuzzy +#| msgid "You don't have the right to delete this WEI registration." +msgid "You don't have the permission to validate registrations" +msgstr "Sie haben nicht das Recht, diese WEI-Registrierung zu löschen." + +#: apps/wei/views.py:952 +#, fuzzy +#| msgid "Please ask the user to credit its note before deleting this credit." +msgid "Please make sure the check is given before validating the registration" +msgstr "" +"Bitte bitten Sie den Benutzer, seine Note gutzuschreiben, bevor Sie diese " +"Kredit löschen." + +#: apps/wei/views.py:1290 msgid "Attribute buses to first year members" msgstr "" -#: apps/wei/views.py:1248 +#: apps/wei/views.py:1315 msgid "Attribute bus" msgstr "" -#: note_kfet/settings/base.py:173 +#: apps/wrapped/apps.py:10 +msgid "wrapped" +msgstr "" + +#: apps/wrapped/models.py:40 +#, fuzzy +#| msgid "Regenerate token" +msgid "generated" +msgstr "Token erneuern" + +#: apps/wrapped/models.py:45 +msgid "public" +msgstr "" + +#: apps/wrapped/models.py:53 +msgid "bde" +msgstr "" + +#: apps/wrapped/models.py:65 +msgid "data json" +msgstr "" + +#: apps/wrapped/models.py:66 +msgid "data in the wrapped and generated by the script generate_wrapped" +msgstr "" + +#: apps/wrapped/models.py:70 note_kfet/templates/base.html:114 +msgid "Wrapped" +msgstr "" + +#: apps/wrapped/models.py:71 +msgid "Wrappeds" +msgstr "" + +#: apps/wrapped/tables.py:40 +msgid "view the wrapped" +msgstr "" + +#: apps/wrapped/tables.py:55 +msgid "Click to make this wrapped private" +msgstr "" + +#: apps/wrapped/tables.py:56 +msgid "Click to make this wrapped public" +msgstr "" + +#: apps/wrapped/tables.py:67 +msgid "Share" +msgstr "" + +#: apps/wrapped/tables.py:73 +msgid "Click to copy the link in the press paper" +msgstr "" + +#: apps/wrapped/tables.py:81 +msgid "Copy link" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_base.html:16 +#: note_kfet/templates/base.html:14 +msgid "The ENS Paris-Saclay BDE note." +msgstr "Die BDE ENS-Paris-Saclay Note." + +#: apps/wrapped/templates/wrapped/1/wrapped_base.html:58 +#, fuzzy +#| msgid "The Note Kfet team." +msgid "The NoteKfet this year it's also" +msgstr "Die NoteKfet Team." + +#: apps/wrapped/templates/wrapped/1/wrapped_base.html:60 +#, fuzzy +#| msgid "transactions" +msgid " transactions" +msgstr "Transaktionen" + +#: apps/wrapped/templates/wrapped/1/wrapped_base.html:61 +msgid " parties" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_base.html:62 +#, fuzzy +#| msgid "entries" +msgid " Pot entries" +msgstr "Eintritte" + +#: apps/wrapped/templates/wrapped/1/wrapped_base.html:72 +msgid " old dickhead behind the bar" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:9 +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:9 +msgid "NoteKfet Wrapped" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:11 +msgid "Your best consumer:" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:13 +msgid "Your worst creditor:" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:16 +msgid "party·ies organised" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:17 +#, fuzzy +#| msgid "Add member" +msgid "distinct members" +msgstr "Neue Mitglied" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:26 +msgid "Infortunately, you doesn't have consumer this year" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:28 +msgid "Congratulations you are a real rat !" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:13 +msgid "You participate to the wei: " +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:13 +#, fuzzy +#| msgid "in the team" +msgid "in the" +msgstr "In der Team" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:18 +msgid "pots !" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:27 +msgid "Your first conso of the year: " +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:28 +msgid "Your prefered consumtion category: " +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:41 +msgid ": it's the number of time your reload your note" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:44 +msgid "Your overall expenses: " +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:47 +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:60 +msgid "with" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:57 +msgid "Your expenses to BDE: " +msgstr "" + +#: apps/wrapped/templates/wrapped/wrapped_list.html:13 +msgid "My wrapped" +msgstr "" + +#: apps/wrapped/templates/wrapped/wrapped_list.html:22 +msgid "Public wrapped" +msgstr "" + +#: apps/wrapped/templates/wrapped/wrapped_list.html:33 +msgid "" +"Do not forget to ask permission to people who are in your wrapped before to " +"make them public" +msgstr "" + +#: apps/wrapped/templates/wrapped/wrapped_list.html:40 +msgid "Link copied" +msgstr "" + +#: apps/wrapped/templates/wrapped/wrapped_list.html:65 +msgid "Wrapped is private" +msgstr "" + +#: apps/wrapped/templates/wrapped/wrapped_list.html:66 +msgid "Wrapped is public" +msgstr "" + +#: apps/wrapped/views.py:28 +msgid "List of wrapped" +msgstr "" + +#: env/lib/python3.11/site-packages/django/contrib/messages/apps.py:15 +msgid "Messages" +msgstr "" + +#: env/lib/python3.11/site-packages/django/contrib/sitemaps/apps.py:8 +msgid "Site Maps" +msgstr "" + +#: env/lib/python3.11/site-packages/django/contrib/staticfiles/apps.py:9 +msgid "Static Files" +msgstr "" + +#: env/lib/python3.11/site-packages/django/contrib/syndication/apps.py:7 +#, fuzzy +#| msgid "Invitation" +msgid "Syndication" +msgstr "Einladung" + +#. Translators: String used to replace omitted page numbers in elided page +#. range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10]. +#: env/lib/python3.11/site-packages/django/core/paginator.py:30 +msgid "…" +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/paginator.py:50 +msgid "That page number is not an integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/paginator.py:52 +msgid "That page number is less than 1" +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/paginator.py:54 +#, fuzzy +#| msgid "There is no results." +msgid "That page contains no results" +msgstr "Es gibt keine Ergebnisse." + +#: env/lib/python3.11/site-packages/django/core/validators.py:22 +#, fuzzy +#| msgid "Email validation" +msgid "Enter a valid value." +msgstr "Email validierung" + +#: env/lib/python3.11/site-packages/django/core/validators.py:104 +#: env/lib/python3.11/site-packages/django/forms/fields.py:752 +#, fuzzy +#| msgid "Email validation" +msgid "Enter a valid URL." +msgstr "Email validierung" + +#: env/lib/python3.11/site-packages/django/core/validators.py:165 +#, fuzzy +#| msgid "Email validation" +msgid "Enter a valid integer." +msgstr "Email validierung" + +#: env/lib/python3.11/site-packages/django/core/validators.py:176 +#, fuzzy +#| msgid "Email validation" +msgid "Enter a valid email address." +msgstr "Email validierung" + +#. Translators: "letters" means latin letters: a-z and A-Z. +#: env/lib/python3.11/site-packages/django/core/validators.py:259 +msgid "" +"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:267 +msgid "" +"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or " +"hyphens." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:281 +#: env/lib/python3.11/site-packages/django/core/validators.py:289 +#: env/lib/python3.11/site-packages/django/core/validators.py:318 +#, fuzzy +#| msgid "This activity is not validated yet." +msgid "Enter a valid IPv4 address." +msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#: env/lib/python3.11/site-packages/django/core/validators.py:298 +#: env/lib/python3.11/site-packages/django/core/validators.py:319 +#, fuzzy +#| msgid "This activity is not validated yet." +msgid "Enter a valid IPv6 address." +msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#: env/lib/python3.11/site-packages/django/core/validators.py:310 +#: env/lib/python3.11/site-packages/django/core/validators.py:317 +#, fuzzy +#| msgid "This activity is not validated yet." +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#: env/lib/python3.11/site-packages/django/core/validators.py:353 +msgid "Enter only digits separated by commas." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:359 +#, python-format +msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:394 +#, python-format +msgid "Ensure this value is less than or equal to %(limit_value)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:403 +#, python-format +msgid "Ensure this value is greater than or equal to %(limit_value)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:412 +#, python-format +msgid "Ensure this value is a multiple of step size %(limit_value)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:422 +#, python-format +msgid "" +"Ensure this value has at least %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at least %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:440 +#, python-format +msgid "" +"Ensure this value has at most %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at most %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:463 +#: env/lib/python3.11/site-packages/django/forms/fields.py:347 +#: env/lib/python3.11/site-packages/django/forms/fields.py:386 +#, fuzzy +#| msgid "phone number" +msgid "Enter a number." +msgstr "Telefonnummer" + +#: env/lib/python3.11/site-packages/django/core/validators.py:465 +#, python-format +msgid "Ensure that there are no more than %(max)s digit in total." +msgid_plural "Ensure that there are no more than %(max)s digits in total." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:470 +#, python-format +msgid "Ensure that there are no more than %(max)s decimal place." +msgid_plural "Ensure that there are no more than %(max)s decimal places." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:475 +#, python-format +msgid "" +"Ensure that there are no more than %(max)s digit before the decimal point." +msgid_plural "" +"Ensure that there are no more than %(max)s digits before the decimal point." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:546 +#, python-format +msgid "" +"File extension “%(extension)s” is not allowed. Allowed extensions are: " +"%(allowed_extensions)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:607 +msgid "Null characters are not allowed." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/base.py:1425 +#, fuzzy, python-format +#| msgid "A template with this name already exist" +msgid "%(model_name)s with this %(field_labels)s already exists." +msgstr "Eine Vorlage mit diesem Namen ist bereits vorhanden" + +#: env/lib/python3.11/site-packages/django/db/models/constraints.py:17 +#, python-format +msgid "Constraint “%(name)s” is violated." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:128 +#, fuzzy, python-format +#| msgid "This activity is not validated yet." +msgid "Value %(value)r is not a valid choice." +msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:129 +#, fuzzy +#| msgid "This image cannot be loaded." +msgid "This field cannot be null." +msgstr "Dieses Bild kann nicht geladen werden." + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:130 +#, fuzzy +#| msgid "This image cannot be loaded." +msgid "This field cannot be blank." +msgstr "Dieses Bild kann nicht geladen werden." + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:131 +#, fuzzy, python-format +#| msgid "A template with this name already exist" +msgid "%(model_name)s with this %(field_label)s already exists." +msgstr "Eine Vorlage mit diesem Namen ist bereits vorhanden" + +#. Translators: The 'lookup_type' is one of 'date', 'year' or +#. 'month'. Eg: "Title must be unique for pub_date year" +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:135 +#, python-format +msgid "" +"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:173 +#, python-format +msgid "Field of type: %(field_type)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1094 +#, python-format +msgid "“%(value)s” value must be either True or False." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1095 +#, python-format +msgid "“%(value)s” value must be either True, False, or None." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1097 +msgid "Boolean (Either True or False)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1147 +#, python-format +msgid "String (up to %(max_length)s)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1149 +msgid "String (unlimited)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1253 +msgid "Comma-separated integers" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1354 +#, python-format +msgid "" +"“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD " +"format." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1358 +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1493 +#, python-format +msgid "" +"“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " +"date." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1362 +msgid "Date (without time)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1489 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." +"uuuuuu]][TZ] format." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1497 +#, python-format +msgid "" +"“%(value)s” value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" +"[TZ]) but it is an invalid date/time." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1502 +msgid "Date (with time)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1626 +#, python-format +msgid "“%(value)s” value must be a decimal number." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1628 +#, fuzzy +#| msgid "phone number" +msgid "Decimal number" +msgstr "Telefonnummer" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1789 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in [DD] [[HH:]MM:]ss[." +"uuuuuu] format." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1793 +#, fuzzy +#| msgid "action" +msgid "Duration" +msgstr "Aktion" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1845 +#, fuzzy +#| msgid "address" +msgid "Email address" +msgstr "Adresse" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1870 +msgid "File path" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1948 +#, python-format +msgid "“%(value)s” value must be a float." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1950 +#, fuzzy +#| msgid "phone number" +msgid "Floating point number" +msgstr "Telefonnummer" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1990 +#, python-format +msgid "“%(value)s” value must be an integer." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1992 +msgid "Integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2088 +msgid "Big (8 byte) integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2105 +msgid "Small integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2113 +#, fuzzy +#| msgid "IP Address" +msgid "IPv4 address" +msgstr "IP Adresse" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2144 +#, fuzzy +#| msgid "IP Address" +msgid "IP address" +msgstr "IP Adresse" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2237 +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2238 +#, python-format +msgid "“%(value)s” value must be either None, True or False." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2240 +msgid "Boolean (Either True, False or None)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2291 +msgid "Positive big integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2306 +msgid "Positive integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2321 +msgid "Positive small integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2337 +#, python-format +msgid "Slug (up to %(max_length)s)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2373 +msgid "Text" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2448 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " +"format." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2452 +#, python-format +msgid "" +"“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " +"invalid time." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2456 +msgid "Time" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2564 +msgid "URL" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2588 +msgid "Raw binary data" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2653 +#, fuzzy, python-format +#| msgid "This activity is not validated yet." +msgid "“%(value)s” is not a valid UUID." +msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2655 +#, fuzzy +#| msgid "Invoice identifier" +msgid "Universally unique identifier" +msgstr "Rechnungskennung" + +#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:232 +msgid "File" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:393 +msgid "Image" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:26 +#, fuzzy +#| msgid "Object" +msgid "A JSON object" +msgstr "Objekt" + +#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:28 +#, fuzzy +#| msgid "This address must be valid." +msgid "Value must be valid JSON." +msgstr "Diese Adresse muss gültig sein." + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:919 +#, fuzzy, python-format +#| msgid "A template with this name already exist" +msgid "%(model)s instance with %(field)s %(value)r does not exist." +msgstr "Eine Vorlage mit diesem Namen ist bereits vorhanden" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:921 +msgid "Foreign Key (type determined by related field)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1212 +msgid "One-to-one relationship" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1269 +#, python-format +msgid "%(from)s-%(to)s relationship" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1271 +#, python-format +msgid "%(from)s-%(to)s relationships" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1319 +msgid "Many-to-many relationship" +msgstr "" + +#. Translators: If found as last label character, these punctuation +#. characters will prevent the default label_suffix to be appended to the label +#: env/lib/python3.11/site-packages/django/forms/boundfield.py:184 +msgid ":?.!" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:298 +#, fuzzy +#| msgid "phone number" +msgid "Enter a whole number." +msgstr "Telefonnummer" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:467 +#: env/lib/python3.11/site-packages/django/forms/fields.py:1241 +#, fuzzy +#| msgid "Email validation" +msgid "Enter a valid date." +msgstr "Email validierung" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:490 +#: env/lib/python3.11/site-packages/django/forms/fields.py:1242 +#, fuzzy +#| msgid "Email validation" +msgid "Enter a valid time." +msgstr "Email validierung" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:517 +#, fuzzy +#| msgid "Email validation" +msgid "Enter a valid date/time." +msgstr "Email validierung" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:551 +#, fuzzy +#| msgid "Email validation" +msgid "Enter a valid duration." +msgstr "Email validierung" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:552 +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:621 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:622 +msgid "No file was submitted." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:623 +msgid "The submitted file is empty." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:625 +#, python-format +msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." +msgid_plural "" +"Ensure this filename has at most %(max)d characters (it has %(length)d)." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:630 +msgid "Please either submit a file or check the clear checkbox, not both." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:694 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:857 +#: env/lib/python3.11/site-packages/django/forms/fields.py:949 +#: env/lib/python3.11/site-packages/django/forms/models.py:1566 +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:951 +#: env/lib/python3.11/site-packages/django/forms/fields.py:1070 +#: env/lib/python3.11/site-packages/django/forms/models.py:1564 +#, fuzzy +#| msgid "Email validation" +msgid "Enter a list of values." +msgstr "Email validierung" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:1071 +#, fuzzy +#| msgid "phone number" +msgid "Enter a complete value." +msgstr "Telefonnummer" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:1313 +#, fuzzy +#| msgid "Email validation" +msgid "Enter a valid UUID." +msgstr "Email validierung" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:1343 +#, fuzzy +#| msgid "Email validation" +msgid "Enter a valid JSON." +msgstr "Email validierung" + +#. Translators: This is the default suffix added to form field labels +#: env/lib/python3.11/site-packages/django/forms/forms.py:98 +msgid ":" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/forms.py:244 +#: env/lib/python3.11/site-packages/django/forms/forms.py:328 +#, python-format +msgid "(Hidden field %(name)s) %(error)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/formsets.py:63 +#, python-format +msgid "" +"ManagementForm data is missing or has been tampered with. Missing fields: " +"%(field_names)s. You may need to file a bug report if the issue persists." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/formsets.py:67 +#, python-format +msgid "Please submit at most %(num)d form." +msgid_plural "Please submit at most %(num)d forms." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/forms/formsets.py:72 +#, python-format +msgid "Please submit at least %(num)d form." +msgid_plural "Please submit at least %(num)d forms." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/forms/formsets.py:484 +#: env/lib/python3.11/site-packages/django/forms/formsets.py:491 +msgid "Order" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:886 +#, python-format +msgid "Please correct the duplicate data for %(field)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:891 +#, python-format +msgid "Please correct the duplicate data for %(field)s, which must be unique." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:898 +#, python-format +msgid "" +"Please correct the duplicate data for %(field_name)s which must be unique " +"for the %(lookup)s in %(date_field)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:907 +msgid "Please correct the duplicate values below." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:1338 +msgid "The inline value did not match the parent instance." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:1429 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:1568 +#, fuzzy, python-format +#| msgid "This activity is not validated yet." +msgid "“%(pk)s” is not a valid value." +msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#: env/lib/python3.11/site-packages/django/forms/utils.py:226 +#, python-format +msgid "" +"%(datetime)s couldn’t be interpreted in time zone %(current_timezone)s; it " +"may be ambiguous or it may not exist." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/widgets.py:463 +msgid "Clear" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/widgets.py:464 +#, fuzzy +#| msgid "Current activity" +msgid "Currently" +msgstr "Aktuelle Veranstaltung" + +#: env/lib/python3.11/site-packages/django/forms/widgets.py:465 +#, fuzzy +#| msgid "change" +msgid "Change" +msgstr "bearbeiten" + +#: env/lib/python3.11/site-packages/django/forms/widgets.py:794 +msgid "Unknown" +msgstr "" + +#. Translators: Please do not add spaces around commas. +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:874 +msgid "yes,no,maybe" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:904 +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:921 +#, python-format +msgid "%(size)d byte" +msgid_plural "%(size)d bytes" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:923 +#, python-format +msgid "%s KB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:925 +#, python-format +msgid "%s MB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:927 +#, python-format +msgid "%s GB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:929 +#, python-format +msgid "%s TB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:931 +#, python-format +msgid "%s PB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:73 +msgid "p.m." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:74 +msgid "a.m." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:79 +msgid "PM" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:80 +msgid "AM" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:152 +msgid "midnight" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:154 +msgid "noon" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:7 +msgid "Monday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:8 +msgid "Tuesday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:9 +msgid "Wednesday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:10 +msgid "Thursday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:11 +msgid "Friday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:12 +msgid "Saturday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:13 +msgid "Sunday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:16 +msgid "Mon" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:17 +msgid "Tue" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:18 +msgid "Wed" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:19 +msgid "Thu" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:20 +msgid "Fri" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:21 +msgid "Sat" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:22 +msgid "Sun" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:25 +msgid "January" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:26 +msgid "February" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:27 +#, fuzzy +#| msgid "Search WEI" +msgid "March" +msgstr "WEI finden" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:28 +msgid "April" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:29 +msgid "May" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:30 +msgid "June" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:31 +msgid "July" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:32 +msgid "August" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:33 +#, fuzzy +#| msgid "member" +msgid "September" +msgstr "Mitglied" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:34 +msgid "October" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:35 +#, fuzzy +#| msgid "member" +msgid "November" +msgstr "Mitglied" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:36 +#, fuzzy +#| msgid "member" +msgid "December" +msgstr "Mitglied" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:39 +#, fuzzy +#| msgid "add" +msgid "jan" +msgstr "hinzufügen" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:40 +#, fuzzy +#| msgid "fee" +msgid "feb" +msgstr "Preis" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:41 +msgid "mar" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:42 +msgid "apr" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:43 +msgid "may" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:44 +#, fuzzy +#| msgid "add" +msgid "jun" +msgstr "hinzufügen" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:45 +msgid "jul" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:46 +msgid "aug" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:47 +msgid "sep" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:48 +#, fuzzy +#| msgid "product" +msgid "oct" +msgstr "Produkt" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:49 +msgid "nov" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:50 +msgid "dec" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:53 +msgctxt "abbrev. month" +msgid "Jan." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:54 +msgctxt "abbrev. month" +msgid "Feb." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:55 +#, fuzzy +#| msgid "Search WEI" +msgctxt "abbrev. month" +msgid "March" +msgstr "WEI finden" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:56 +msgctxt "abbrev. month" +msgid "April" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:57 +msgctxt "abbrev. month" +msgid "May" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:58 +msgctxt "abbrev. month" +msgid "June" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:59 +msgctxt "abbrev. month" +msgid "July" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:60 +msgctxt "abbrev. month" +msgid "Aug." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:61 +msgctxt "abbrev. month" +msgid "Sept." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:62 +msgctxt "abbrev. month" +msgid "Oct." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:63 +msgctxt "abbrev. month" +msgid "Nov." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:64 +msgctxt "abbrev. month" +msgid "Dec." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:67 +msgctxt "alt. month" +msgid "January" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:68 +msgctxt "alt. month" +msgid "February" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:69 +#, fuzzy +#| msgid "Search WEI" +msgctxt "alt. month" +msgid "March" +msgstr "WEI finden" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:70 +msgctxt "alt. month" +msgid "April" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:71 +msgctxt "alt. month" +msgid "May" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:72 +msgctxt "alt. month" +msgid "June" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:73 +msgctxt "alt. month" +msgid "July" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:74 +msgctxt "alt. month" +msgid "August" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:75 +#, fuzzy +#| msgid "member" +msgctxt "alt. month" +msgid "September" +msgstr "Mitglied" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:76 +msgctxt "alt. month" +msgid "October" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:77 +#, fuzzy +#| msgid "member" +msgctxt "alt. month" +msgid "November" +msgstr "Mitglied" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:78 +#, fuzzy +#| msgid "member" +msgctxt "alt. month" +msgid "December" +msgstr "Mitglied" + +#: env/lib/python3.11/site-packages/django/utils/ipv6.py:20 +#, fuzzy +#| msgid "This activity is not validated yet." +msgid "This is not a valid IPv6 address." +msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#: env/lib/python3.11/site-packages/django/utils/text.py:138 +#, python-format +msgctxt "String to return when truncating text" +msgid "%(truncated_text)s…" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/text.py:323 +msgid "or" +msgstr "" + +#. Translators: This string is used as a separator between list elements +#: env/lib/python3.11/site-packages/django/utils/text.py:342 +#: env/lib/python3.11/site-packages/django/utils/timesince.py:135 +msgid ", " +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:8 +#, fuzzy, python-format +#| msgid "year" +msgid "%(num)d year" +msgid_plural "%(num)d years" +msgstr[0] "Jahr" +msgstr[1] "Jahr" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:9 +#, python-format +msgid "%(num)d month" +msgid_plural "%(num)d months" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:10 +#, python-format +msgid "%(num)d week" +msgid_plural "%(num)d weeks" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:11 +#, python-format +msgid "%(num)d day" +msgid_plural "%(num)d days" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:12 +#, python-format +msgid "%(num)d hour" +msgid_plural "%(num)d hours" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:13 +#, python-format +msgid "%(num)d minute" +msgid_plural "%(num)d minutes" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:111 +msgid "Forbidden" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:112 +msgid "CSRF verification failed. Request aborted." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:116 +msgid "" +"You are seeing this message because this HTTPS site requires a “Referer " +"header” to be sent by your web browser, but none was sent. This header is " +"required for security reasons, to ensure that your browser is not being " +"hijacked by third parties." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:122 +msgid "" +"If you have configured your browser to disable “Referer” headers, please re-" +"enable them, at least for this site, or for HTTPS connections, or for “same-" +"origin” requests." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:127 +msgid "" +"If you are using the tag or " +"including the “Referrer-Policy: no-referrer” header, please remove them. The " +"CSRF protection requires the “Referer” header to do strict referer checking. " +"If you’re concerned about privacy, use alternatives like for links to third-party sites." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:136 +msgid "" +"You are seeing this message because this site requires a CSRF cookie when " +"submitting forms. This cookie is required for security reasons, to ensure " +"that your browser is not being hijacked by third parties." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:142 +msgid "" +"If you have configured your browser to disable cookies, please re-enable " +"them, at least for this site, or for “same-origin” requests." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:148 +msgid "More information is available with DEBUG=True." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:44 +#, fuzzy +#| msgid "No reason specified" +msgid "No year specified" +msgstr "Kein Grund gegeben" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:64 +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:115 +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:214 +msgid "Date out of range" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:94 +#, fuzzy +#| msgid "No reason specified" +msgid "No month specified" +msgstr "Kein Grund gegeben" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:147 +#, fuzzy +#| msgid "No reason specified" +msgid "No day specified" +msgstr "Kein Grund gegeben" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:194 +#, fuzzy +#| msgid "No reason specified" +msgid "No week specified" +msgstr "Kein Grund gegeben" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:349 +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:380 +#, python-format +msgid "No %(verbose_name_plural)s available" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:652 +#, python-format +msgid "" +"Future %(verbose_name_plural)s not available because %(class_name)s." +"allow_future is False." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:692 +#, python-format +msgid "Invalid date string “%(datestr)s” given format “%(format)s”" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/detail.py:56 +#, python-format +msgid "No %(verbose_name)s found matching the query" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/list.py:70 +msgid "Page is not “last”, nor can it be converted to an int." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/list.py:77 +#, python-format +msgid "Invalid page (%(page_number)s): %(message)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/list.py:169 +#, python-format +msgid "Empty list and “%(class_name)s.allow_empty” is False." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/static.py:38 +msgid "Directory indexes are not allowed here." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/static.py:40 +#, python-format +msgid "“%(path)s” does not exist" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/static.py:79 +#, python-format +msgid "Index of %(directory)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:7 +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:220 +msgid "The install worked successfully! Congratulations!" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:206 +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:221 +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not " +"configured any URLs." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:229 +msgid "Django Documentation" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:230 +msgid "Topics, references, & how-to’s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:238 +msgid "Tutorial: A Polling App" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:239 +msgid "Get started with Django" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:247 +msgid "Django Community" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:248 +msgid "Connect, get help, or contribute" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:69 +msgid "Confidential" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:70 +msgid "Public" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:79 +msgid "Authorization code" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:80 +msgid "Implicit" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:81 +#, fuzzy +#| msgid "Reset my password" +msgid "Resource owner password-based" +msgstr "Mein Passwort zurücksetzen" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:82 +msgid "Client credentials" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:83 +msgid "OpenID connect hybrid" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:90 +msgid "No OIDC support" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:91 +msgid "RSA with SHA-2 256" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:92 +msgid "HMAC with SHA-2 256" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:107 +msgid "Allowed URIs list, space separated" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:111 +msgid "Allowed Post Logout URIs list, space separated" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:120 +msgid "Hashed on Save. Copy it now if this is a new secret." +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:190 +#, python-brace-format +msgid "Unauthorized redirect scheme: {scheme}" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:194 +#, python-brace-format +msgid "redirect_uris cannot be empty with grant_type {grant_type}" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:200 +msgid "You must set OIDC_RSA_PRIVATE_KEY to use RSA algorithm" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:209 +msgid "You cannot use HS256 with public grants or clients" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:211 +#, fuzzy +#| msgid "This address must be valid." +msgid "The access token is invalid." +msgstr "Diese Adresse muss gültig sein." + +#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:218 +#, fuzzy +#| msgid "This address must be valid." +msgid "The access token has expired." +msgstr "Diese Adresse muss gültig sein." + +#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:225 +#, fuzzy +#| msgid "This address must be valid." +msgid "The access token is valid but does not have enough scope." +msgstr "Diese Adresse muss gültig sein." + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:6 +#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8 +#, fuzzy +#| msgid "" +#| "Are you sure you want to delete this invoice? This action can't be undone." +msgid "Are you sure to delete the application" +msgstr "" +"Möchten Sie diese Rechnung wirklich löschen? Diese Aktion kann nicht " +"rückgängig gemacht werden." + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:12 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:29 +#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 +#: note_kfet/templates/oauth2_provider/authorize.html:28 +#, fuzzy +#| msgid "Balance" +msgid "Cancel" +msgstr "Kontostand" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:10 +#: note_kfet/templates/oauth2_provider/application_detail.html:11 +msgid "Client id" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:15 +#: note_kfet/templates/oauth2_provider/application_detail.html:14 +msgid "Client secret" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:20 +#: note_kfet/templates/oauth2_provider/application_detail.html:17 +#, fuzzy +#| msgid "Credit type" +msgid "Client type" +msgstr "Kredittype" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:25 +#: note_kfet/templates/oauth2_provider/application_detail.html:20 +msgid "Authorization Grant Type" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:30 +#: note_kfet/templates/oauth2_provider/application_detail.html:23 +msgid "Redirect Uris" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:36 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:35 +#: note_kfet/templates/oauth2_provider/application_detail.html:37 +#: note_kfet/templates/oauth2_provider/application_form.html:23 +msgid "Go Back" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:9 +#: note_kfet/templates/oauth2_provider/application_form.html:12 +#, fuzzy +#| msgid "Email validation" +msgid "Edit application" +msgstr "Email validierung" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:37 +msgid "Save" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:6 +#: note_kfet/templates/oauth2_provider/application_list.html:7 +msgid "Your applications" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:14 +#: note_kfet/templates/oauth2_provider/application_list.html:30 +#, fuzzy +#| msgid "location" +msgid "New Application" +msgstr "Ort" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_registration_form.html:5 +#: note_kfet/templates/oauth2_provider/application_registration_form.html:5 +#, fuzzy +#| msgid "Registrations" +msgid "Register a new application" +msgstr "Anmeldung" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:8 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:30 +#: note_kfet/templates/oauth2_provider/authorize.html:9 +#: note_kfet/templates/oauth2_provider/authorize.html:29 +msgid "Authorize" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:17 +msgid "Application requires the following permissions" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:6 +#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9 +#, fuzzy +#| msgid "" +#| "Are you sure you want to delete this invoice? This action can't be undone." +msgid "Are you sure you want to delete this token?" +msgstr "" +"Möchten Sie diese Rechnung wirklich löschen? Diese Aktion kann nicht " +"rückgängig gemacht werden." + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:6 +#: note_kfet/templates/oauth2_provider/authorized-tokens.html:7 +#, fuzzy +#| msgid "Token" +msgid "Tokens" +msgstr "Token" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:11 +msgid "revoke" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:19 +#: note_kfet/templates/oauth2_provider/authorized-tokens.html:22 +#, fuzzy +#| msgid "There is no closed remittance yet." +msgid "There are no authorized tokens yet." +msgstr "Es gibt noch keine geschlossene Überweisung." + +#: note_kfet/settings/base.py:177 msgid "German" msgstr "Deutsch" -#: note_kfet/settings/base.py:174 +#: note_kfet/settings/base.py:178 msgid "English" msgstr "English" -#: note_kfet/settings/base.py:175 +#: note_kfet/settings/base.py:179 msgid "Spanish" msgstr "Spanisch" -#: note_kfet/settings/base.py:176 +#: note_kfet/settings/base.py:180 msgid "French" msgstr "Französich" @@ -3661,14 +5539,6 @@ msgstr "" msgid "Reset" msgstr "Reset" -#: note_kfet/templates/base.html:14 -msgid "The ENS Paris-Saclay BDE note." -msgstr "Die BDE ENS-Paris-Saclay Note." - -#: note_kfet/templates/base.html:72 -msgid "Food" -msgstr "" - #: note_kfet/templates/base.html:84 msgid "Users" msgstr "Users" @@ -3677,26 +5547,26 @@ msgstr "Users" 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 "Mein Konto" -#: note_kfet/templates/base.html:136 +#: note_kfet/templates/base.html:142 msgid "Log out" msgstr "Abmelden" -#: 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 "Registrieren" -#: 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 @@ -3704,13 +5574,13 @@ msgstr "Registrieren" msgid "Log in" msgstr "Anmelden" -#: 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." msgstr "" -#: 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." @@ -3718,7 +5588,7 @@ msgstr "" "Ihre E-Mail-Adresse ist nicht validiert. Bitte überprüfen Sie Ihren " "Posteingang und klicken Sie auf den Validierungslink." -#: 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 " @@ -3727,19 +5597,19 @@ msgid "" "creation." msgstr "" -#: note_kfet/templates/base.html:200 +#: note_kfet/templates/base.html:206 msgid "Contact us" msgstr "Kontakt" -#: note_kfet/templates/base.html:202 +#: note_kfet/templates/base.html:208 msgid "Technical Support" msgstr "" -#: note_kfet/templates/base.html:204 +#: note_kfet/templates/base.html:210 msgid "Charte Info (FR)" msgstr "" -#: note_kfet/templates/base.html:206 +#: note_kfet/templates/base.html:212 msgid "FAQ (FR)" msgstr "FAQ (FR)" @@ -3751,44 +5621,6 @@ msgstr "Suche nach Attributen wie Name..." msgid "There is no results." msgstr "Es gibt keine Ergebnisse." -#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8 -#, fuzzy -#| msgid "" -#| "Are you sure you want to delete this invoice? This action can't be undone." -msgid "Are you sure to delete the application" -msgstr "" -"Möchten Sie diese Rechnung wirklich löschen? Diese Aktion kann nicht " -"rückgängig gemacht werden." - -#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 -#: note_kfet/templates/oauth2_provider/authorize.html:28 -#, fuzzy -#| msgid "Balance" -msgid "Cancel" -msgstr "Kontostand" - -#: note_kfet/templates/oauth2_provider/application_detail.html:11 -msgid "Client id" -msgstr "" - -#: note_kfet/templates/oauth2_provider/application_detail.html:14 -msgid "Client secret" -msgstr "" - -#: note_kfet/templates/oauth2_provider/application_detail.html:17 -#, fuzzy -#| msgid "Credit type" -msgid "Client type" -msgstr "Kredittype" - -#: note_kfet/templates/oauth2_provider/application_detail.html:20 -msgid "Authorization Grant Type" -msgstr "" - -#: note_kfet/templates/oauth2_provider/application_detail.html:23 -msgid "Redirect Uris" -msgstr "" - #: note_kfet/templates/oauth2_provider/application_detail.html:29 #, python-format msgid "" @@ -3797,48 +5629,16 @@ msgid "" "that you want to grant for your application." msgstr "" -#: note_kfet/templates/oauth2_provider/application_detail.html:37 -#: note_kfet/templates/oauth2_provider/application_form.html:23 -msgid "Go Back" -msgstr "" - -#: note_kfet/templates/oauth2_provider/application_form.html:12 -#, fuzzy -#| msgid "Email validation" -msgid "Edit application" -msgstr "Email validierung" - -#: note_kfet/templates/oauth2_provider/application_list.html:7 -msgid "Your applications" -msgstr "" - #: note_kfet/templates/oauth2_provider/application_list.html:11 msgid "" "You can find on this page the list of the applications that you already " "registered." msgstr "" -#: note_kfet/templates/oauth2_provider/application_list.html:30 -#, fuzzy -#| msgid "location" -msgid "New Application" -msgstr "Ort" - #: note_kfet/templates/oauth2_provider/application_list.html:31 msgid "Authorized Tokens" msgstr "" -#: note_kfet/templates/oauth2_provider/application_registration_form.html:5 -#, fuzzy -#| msgid "Registrations" -msgid "Register a new application" -msgstr "Anmeldung" - -#: note_kfet/templates/oauth2_provider/authorize.html:9 -#: note_kfet/templates/oauth2_provider/authorize.html:29 -msgid "Authorize" -msgstr "" - #: note_kfet/templates/oauth2_provider/authorize.html:14 msgid "Application requires following permissions:" msgstr "" @@ -3856,27 +5656,6 @@ msgstr "" msgid "Please return to your application and enter this code:" msgstr "" -#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9 -#, fuzzy -#| msgid "" -#| "Are you sure you want to delete this invoice? This action can't be undone." -msgid "Are you sure you want to delete this token?" -msgstr "" -"Möchten Sie diese Rechnung wirklich löschen? Diese Aktion kann nicht " -"rückgängig gemacht werden." - -#: note_kfet/templates/oauth2_provider/authorized-tokens.html:7 -#, fuzzy -#| msgid "Token" -msgid "Tokens" -msgstr "Token" - -#: note_kfet/templates/oauth2_provider/authorized-tokens.html:22 -#, fuzzy -#| msgid "There is no closed remittance yet." -msgid "There are no authorized tokens yet." -msgstr "Es gibt noch keine geschlossene Überweisung." - #: note_kfet/templates/registration/logged_out.html:13 msgid "Thanks for spending some quality time with the Web site today." msgstr "" @@ -3991,292 +5770,55 @@ msgstr "" "Sie erhalten haben." #, fuzzy -#~| msgid "Email validation" -#~ msgid "Enter a valid color." -#~ msgstr "Email validierung" +#~| msgid "active" +#~ msgid "is active" +#~ msgstr "Aktiv" #, fuzzy -#~| msgid "Email validation" -#~ msgid "Enter a valid value." -#~ msgstr "Email validierung" +#~| msgid "start date" +#~ msgid "Arrival date" +#~ msgstr "Anfangsdatum" #, fuzzy -#~| msgid "Invitation" -#~ msgid "Syndication" -#~ msgstr "Einladung" +#~| msgid "active" +#~ msgid "Active" +#~ msgstr "Aktiv" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "number" +#~ msgstr "Telefonnummer" + +#, fuzzy +#~| msgid "Profile detail" +#~ msgid "View details" +#~ msgstr "Profile detail" + +#, fuzzy +#~| msgid "created at" +#~ msgid "Creation date" +#~ msgstr "erschafft am" #, fuzzy #~| msgid "There is no results." -#~ msgid "That page contains no results" +#~ msgid "There is no meal." #~ msgstr "Es gibt keine Ergebnisse." #, fuzzy -#~| msgid "Email validation" -#~ msgid "Enter a valid URL." -#~ msgstr "Email validierung" +#~| msgid "This credit is already validated." +#~ msgid "The product is already prepared" +#~ msgstr "Dieser Kredit ist bereits validiert." + +#, fuzzy +#~| msgid "Update team" +#~ msgid "Update a meal" +#~ msgstr "Team bearbeiten" #, fuzzy #~| msgid "Email validation" -#~ msgid "Enter a valid integer." +#~ msgid "Enter a valid color." #~ msgstr "Email validierung" -#, fuzzy -#~| msgid "Email validation" -#~ msgid "Enter a valid email address." -#~ msgstr "Email validierung" - -#, fuzzy -#~| msgid "This activity is not validated yet." -#~ msgid "Enter a valid IPv4 address." -#~ msgstr "Diese Veranstaltung ist noch nicht bestätigt." - -#, fuzzy -#~| msgid "This activity is not validated yet." -#~ msgid "Enter a valid IPv6 address." -#~ msgstr "Diese Veranstaltung ist noch nicht bestätigt." - -#, fuzzy -#~| msgid "This activity is not validated yet." -#~ msgid "Enter a valid IPv4 or IPv6 address." -#~ msgstr "Diese Veranstaltung ist noch nicht bestätigt." - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Enter a number." -#~ msgstr "Telefonnummer" - -#, fuzzy -#~| msgid "add" -#~ msgid "and" -#~ msgstr "hinzufügen" - -#, fuzzy, python-format -#~| msgid "A template with this name already exist" -#~ msgid "%(model_name)s with this %(field_labels)s already exists." -#~ msgstr "Eine Vorlage mit diesem Namen ist bereits vorhanden" - -#, fuzzy -#~| msgid "This image cannot be loaded." -#~ msgid "This field cannot be null." -#~ msgstr "Dieses Bild kann nicht geladen werden." - -#, fuzzy -#~| msgid "This image cannot be loaded." -#~ msgid "This field cannot be blank." -#~ msgstr "Dieses Bild kann nicht geladen werden." - -#, fuzzy, python-format -#~| msgid "A template with this name already exist" -#~ msgid "%(model_name)s with this %(field_label)s already exists." -#~ msgstr "Eine Vorlage mit diesem Namen ist bereits vorhanden" - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Decimal number" -#~ msgstr "Telefonnummer" - -#, fuzzy -#~| msgid "action" -#~ msgid "Duration" -#~ msgstr "Aktion" - -#, fuzzy -#~| msgid "address" -#~ msgid "Email address" -#~ msgstr "Adresse" - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Floating point number" -#~ msgstr "Telefonnummer" - -#, fuzzy -#~| msgid "IP Address" -#~ msgid "IPv4 address" -#~ msgstr "IP Adresse" - -#, fuzzy -#~| msgid "IP Address" -#~ msgid "IP address" -#~ msgstr "IP Adresse" - -#, fuzzy -#~| msgid "Invoice identifier" -#~ msgid "Universally unique identifier" -#~ msgstr "Rechnungskennung" - -#, fuzzy, python-format -#~| msgid "A template with this name already exist" -#~ msgid "%(model)s instance with %(field)s %(value)r does not exist." -#~ msgstr "Eine Vorlage mit diesem Namen ist bereits vorhanden" - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Enter a whole number." -#~ msgstr "Telefonnummer" - -#, fuzzy -#~| msgid "Email validation" -#~ msgid "Enter a valid date." -#~ msgstr "Email validierung" - -#, fuzzy -#~| msgid "Email validation" -#~ msgid "Enter a valid time." -#~ msgstr "Email validierung" - -#, fuzzy -#~| msgid "Email validation" -#~ msgid "Enter a valid date/time." -#~ msgstr "Email validierung" - -#, fuzzy -#~| msgid "Email validation" -#~ msgid "Enter a valid duration." -#~ msgstr "Email validierung" - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Enter a complete value." -#~ msgstr "Telefonnummer" - -#, fuzzy -#~| msgid "Email validation" -#~ msgid "Enter a valid UUID." -#~ msgstr "Email validierung" - -#, fuzzy, python-format -#~| msgid "This activity is not validated yet." -#~ msgid "\"%(pk)s\" is not a valid value." -#~ msgstr "Diese Veranstaltung ist noch nicht bestätigt." - -#, fuzzy -#~| msgid "Current activity" -#~ msgid "Currently" -#~ msgstr "Aktuelle Veranstaltung" - -#, fuzzy -#~| msgid "change" -#~ msgid "Change" -#~ msgstr "bearbeiten" - -#, fuzzy -#~| msgid "Search WEI" -#~ msgid "March" -#~ msgstr "WEI finden" - -#, fuzzy -#~| msgid "member" -#~ msgid "September" -#~ msgstr "Mitglied" - -#, fuzzy -#~| msgid "member" -#~ msgid "November" -#~ msgstr "Mitglied" - -#, fuzzy -#~| msgid "member" -#~ msgid "December" -#~ msgstr "Mitglied" - -#, fuzzy -#~| msgid "add" -#~ msgid "jan" -#~ msgstr "hinzufügen" - -#, fuzzy -#~| msgid "fee" -#~ msgid "feb" -#~ msgstr "Preis" - -#, fuzzy -#~| msgid "product" -#~ msgid "oct" -#~ msgstr "Produkt" - -#, fuzzy -#~| msgid "Search WEI" -#~ msgctxt "abbrev. month" -#~ msgid "March" -#~ msgstr "WEI finden" - -#, fuzzy -#~| msgid "Search WEI" -#~ msgctxt "alt. month" -#~ msgid "March" -#~ msgstr "WEI finden" - -#, fuzzy -#~| msgid "member" -#~ msgctxt "alt. month" -#~ msgid "September" -#~ msgstr "Mitglied" - -#, fuzzy -#~| msgid "member" -#~ msgctxt "alt. month" -#~ msgid "November" -#~ msgstr "Mitglied" - -#, fuzzy -#~| msgid "member" -#~ msgctxt "alt. month" -#~ msgid "December" -#~ msgstr "Mitglied" - -#, fuzzy -#~| msgid "This activity is not validated yet." -#~ msgid "This is not a valid IPv6 address." -#~ msgstr "Diese Veranstaltung ist noch nicht bestätigt." - -#, fuzzy, python-format -#~| msgid "year" -#~ msgid "%d year" -#~ msgid_plural "%d years" -#~ msgstr[0] "Jahr" -#~ msgstr[1] "Jahr" - -#, fuzzy -#~| msgid "No reason specified" -#~ msgid "No year specified" -#~ msgstr "Kein Grund gegeben" - -#, fuzzy -#~| msgid "No reason specified" -#~ msgid "No month specified" -#~ msgstr "Kein Grund gegeben" - -#, fuzzy -#~| msgid "No reason specified" -#~ msgid "No day specified" -#~ msgstr "Kein Grund gegeben" - -#, fuzzy -#~| msgid "No reason specified" -#~ msgid "No week specified" -#~ msgstr "Kein Grund gegeben" - -#, fuzzy -#~| msgid "Reset my password" -#~ msgid "Resource owner password-based" -#~ msgstr "Mein Passwort zurücksetzen" - -#, fuzzy -#~| msgid "This address must be valid." -#~ msgid "The access token is invalid." -#~ msgstr "Diese Adresse muss gültig sein." - -#, fuzzy -#~| msgid "This address must be valid." -#~ msgid "The access token has expired." -#~ msgstr "Diese Adresse muss gültig sein." - -#, fuzzy -#~| msgid "This address must be valid." -#~ msgid "The access token is valid but does not have enough scope." -#~ msgstr "Diese Adresse muss gültig sein." - #, fuzzy #~| msgid "WEI registration" #~ msgid "In preparation" diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index 6dcf73a1..e1896993 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/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-05-27 16:46+0200\n" "PO-Revision-Date: 2022-04-11 23:12+0200\n" "Last-Translator: bleizi \n" "Language-Team: \n" @@ -18,41 +18,47 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.0.1\n" +#: apps/activity/api/serializers.py:77 +#, fuzzy +#| msgid "This credit is already validated." +msgid "This opener already exists" +msgstr "Este crédito ya fue validado." + #: apps/activity/apps.py:10 apps/activity/models.py:129 -#: apps/activity/models.py:169 +#: apps/activity/models.py:169 apps/activity/models.py:329 msgid "activity" msgstr "actividad" -#: apps/activity/forms.py:34 +#: apps/activity/forms.py:35 msgid "The note of this club is inactive." msgstr "La note del club está inactiva." -#: 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 fecha final tiene que ser después de la fecha de inicio." -#: apps/activity/forms.py:82 apps/activity/models.py:271 +#: apps/activity/forms.py:83 apps/activity/models.py:277 msgid "You can't invite someone once the activity is started." msgstr "No se puede invitar a alguien una vez que arrancó la actividad." -#: apps/activity/forms.py:85 apps/activity/models.py:274 +#: apps/activity/forms.py:86 apps/activity/models.py:280 msgid "This activity is not validated yet." msgstr "Esta actividad no fue validada por ahora." -#: apps/activity/forms.py:95 apps/activity/models.py:282 +#: apps/activity/forms.py:96 apps/activity/models.py:288 msgid "This person has been already invited 5 times this year." msgstr "Esta persona ya fue invitada 5 veces este año." -#: apps/activity/forms.py:99 apps/activity/models.py:286 +#: apps/activity/forms.py:100 apps/activity/models.py:292 msgid "This person is already invited." msgstr "Esta persona ya está invitada." -#: apps/activity/forms.py:103 apps/activity/models.py:290 +#: apps/activity/forms.py:104 apps/activity/models.py:296 msgid "You can't invite more than 3 people to this activity." msgstr "Usted no puede invitar más de 3 persona a esta actividad." -#: apps/activity/models.py:28 apps/activity/models.py:63 apps/food/models.py:42 -#: apps/food/models.py:56 apps/member/models.py:203 +#: apps/activity/models.py:28 apps/activity/models.py:63 apps/food/models.py:18 +#: apps/food/models.py:35 apps/member/models.py:203 #: apps/member/templates/member/includes/club_info.html:4 #: apps/member/templates/member/includes/profile_info.html:4 #: apps/note/models/notes.py:263 apps/note/models/transactions.py:26 @@ -61,7 +67,7 @@ msgstr "Usted no puede invitar más de 3 persona a esta actividad." #: 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 "nombre" @@ -113,8 +119,8 @@ msgstr "Lugar donde se organiza la actividad, por ejemplo la Kfet." msgid "type" msgstr "tipo" -#: apps/activity/models.py:91 apps/logs/models.py:22 apps/member/models.py:318 -#: apps/note/models/notes.py:148 apps/treasury/models.py:293 +#: 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:294 #: apps/wei/models.py:171 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/templates/wei/survey.html:15 msgid "user" @@ -170,7 +176,7 @@ msgid "entry time" msgstr "hora de entrada" #: 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" @@ -198,21 +204,21 @@ msgstr "Entrada para {note} en la actividad {activity}" msgid "Already entered on " msgstr "Entrado ya el " -#: apps/activity/models.py:204 apps/activity/tables.py:56 +#: apps/activity/models.py:205 apps/activity/tables.py:58 msgid "{:%Y-%m-%d %H:%M:%S}" msgstr "{:%d/%m/%Y %H:%M:%S}" -#: apps/activity/models.py:212 +#: apps/activity/models.py:213 msgid "The balance is negative." msgstr "El saldo es negativo." -#: apps/activity/models.py:242 +#: apps/activity/models.py:243 #: apps/treasury/templates/treasury/sogecredit_detail.html:14 #: apps/wei/templates/wei/attribute_bus_1A.html:16 msgid "last name" msgstr "apellido" -#: apps/activity/models.py:247 +#: apps/activity/models.py:248 #: apps/member/templates/member/includes/profile_info.html:4 #: apps/registration/templates/registration/future_profile_detail.html:16 #: apps/treasury/templates/treasury/sogecredit_detail.html:17 @@ -221,77 +227,139 @@ msgstr "apellido" msgid "first name" msgstr "nombre" -#: apps/activity/models.py:254 +#: apps/activity/models.py:253 +msgid "school" +msgstr "" + +#: apps/activity/models.py:260 msgid "inviter" msgstr "huésped" -#: apps/activity/models.py:258 +#: apps/activity/models.py:264 msgid "guest" msgstr "invitado" -#: apps/activity/models.py:259 +#: apps/activity/models.py:265 msgid "guests" msgstr "invitados" -#: apps/activity/models.py:312 +#: apps/activity/models.py:318 msgid "Invitation" msgstr "Invitación" -#: apps/activity/tables.py:27 +#: apps/activity/models.py:336 apps/activity/models.py:340 +#, fuzzy +#| msgid "opened" +msgid "Opener" +msgstr "abierto" + +#: apps/activity/models.py:341 +#: apps/activity/templates/activity/activity_detail.html:16 +#, fuzzy +#| msgid "opened" +msgid "Openers" +msgstr "abierto" + +#: apps/activity/models.py:345 +#, fuzzy, python-brace-format +#| msgid "Entry for {note} to the activity {activity}" +msgid "{opener} is opener of activity {acivity}" +msgstr "Entrada para {note} en la actividad {activity}" + +#: apps/activity/tables.py:29 msgid "The activity is currently open." msgstr "La actividad está actualmente abierta." -#: apps/activity/tables.py:28 +#: apps/activity/tables.py:30 msgid "The validation of the activity is pending." msgstr "La validación de esta actividad es pendiente." -#: 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 "Quitar" -#: apps/activity/tables.py:56 +#: apps/activity/tables.py:58 msgid "Entered on " msgstr "Entrado el " -#: apps/activity/tables.py:58 +#: apps/activity/tables.py:60 msgid "remove" msgstr "quitar" -#: 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:209 msgid "Type" msgstr "Tipo" -#: 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:107 msgid "Last name" msgstr "Apellido" -#: 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:112 msgid "First name" msgstr "Nombre" -#: 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 "Saldo de la cuenta" -#: 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 +#: env/lib/python3.11/site-packages/django/forms/formsets.py:499 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:13 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:38 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:7 +#: 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 "Suprimir" + +#: 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 "Añadir" + +#: apps/activity/templates/activity/activity_detail.html:35 msgid "Guests list" msgstr "Lista de los invitados" -#: apps/activity/templates/activity/activity_detail.html:33 +#: apps/activity/templates/activity/activity_detail.html:55 msgid "Guest deleted" msgstr "Invitados suprimidos" +#: apps/activity/templates/activity/activity_detail.html:99 +#, fuzzy +#| msgid "Are you sure you want to delete this token?" +msgid "Are you sure you want to delete this activity?" +msgstr "¿ Usted está seguro de querer suprimir este token ?" + +#: apps/activity/templates/activity/activity_detail.html:110 +#, fuzzy +#| msgid "Activity detail" +msgid "Activity deleted" +msgstr "Detalles de la actividad" + #: apps/activity/templates/activity/activity_entry.html:14 #: apps/note/models/transactions.py:261 #: apps/note/templates/note/transaction_form.html:17 @@ -331,17 +399,17 @@ msgid "Entry done!" msgstr "Entrada echa !" #: 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/transformedfood_form.html:16 +#: apps/food/templates/food/food_update.html:17 +#: apps/food/templates/food/manage_ingredients.html:48 +#: apps/food/templates/food/qrcode.html:18 +#: apps/food/templates/food/transformedfood_update.html:45 #: apps/member/templates/member/add_members.html:46 #: apps/member/templates/member/club_form.html:16 #: apps/note/templates/note/transactiontemplate_form.html:18 #: apps/treasury/forms.py:89 apps/treasury/forms.py:143 #: apps/treasury/templates/treasury/invoice_form.html:74 #: apps/wei/templates/wei/bus_form.html:17 -#: apps/wei/templates/wei/busteam_form.html:17 +#: apps/wei/templates/wei/busteam_form.html:18 #: apps/wei/templates/wei/weiclub_form.html:17 #: apps/wei/templates/wei/weiregistration_form.html:18 msgid "Submit" @@ -397,44 +465,66 @@ msgid "edit" msgstr "modificar" #: apps/activity/templates/activity/includes/activity_info.html:74 +#: 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" +msgstr "suprimir" + +#: apps/activity/templates/activity/includes/activity_info.html:77 msgid "Invite" msgstr "Invitar" -#: apps/activity/views.py:37 +#: apps/activity/views.py:38 msgid "Create new activity" msgstr "Crear una nueva actividad" -#: 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 "Actividades" -#: apps/activity/views.py:108 +#: apps/activity/views.py:105 msgid "Activity detail" msgstr "Detalles de la actividad" -#: apps/activity/views.py:128 +#: apps/activity/views.py:150 msgid "Update activity" msgstr "Modificar la actividad" -#: apps/activity/views.py:155 +#: apps/activity/views.py:177 +#, fuzzy +#| msgid "" +#| "You are not allowed to display the entry interface for this activity." +msgid "You are not allowed to delete this activity." +msgstr "" +"Usted no tiene derecho a mostrar la interfaz de las entradas para esta " +"actividad." + +#: apps/activity/views.py:180 +#, fuzzy +#| msgid "This activity is closed." +msgid "This activity is valid." +msgstr "Esta actividad esta cerrada." + +#: apps/activity/views.py:206 msgid "Invite guest to the activity \"{}\"" msgstr "Invitar alguien para la actividad \"{}\"" -#: apps/activity/views.py:193 +#: apps/activity/views.py:246 msgid "You are not allowed to display the entry interface for this activity." msgstr "" "Usted no tiene derecho a mostrar la interfaz de las entradas para esta " "actividad." -#: apps/activity/views.py:196 +#: apps/activity/views.py:249 msgid "This activity does not support activity entries." msgstr "Esta actividad no necesita entradas." -#: apps/activity/views.py:199 +#: apps/activity/views.py:252 msgid "This activity is closed." msgstr "Esta actividad esta cerrada." -#: apps/activity/views.py:295 +#: apps/activity/views.py:357 msgid "Entry for activity \"{}\"" msgstr "Entradas para la actividad \"{}\"" @@ -442,297 +532,367 @@ msgstr "Entradas para la actividad \"{}\"" msgid "API" msgstr "API" -#: apps/food/apps.py:11 apps/food/models.py:105 +#: apps/food/apps.py:11 msgid "food" msgstr "" -#: apps/food/forms.py:32 -msgid "Fully used" -msgstr "" - -#: apps/food/forms.py:50 +#: apps/food/forms.py:49 msgid "Pasta METRO 5kg" msgstr "" -#: apps/food/forms.py:96 +#: apps/food/forms.py:53 apps/food/forms.py:81 +msgid "Specific order given to GCKs" +msgstr "" + +#: apps/food/forms.py:77 msgid "Lasagna" msgstr "" -#: apps/food/models.py:18 +#: apps/food/forms.py:116 +msgid "Shelf life (in hours)" +msgstr "" + +#: apps/food/forms.py:138 apps/food/forms.py:162 +#: apps/food/templates/food/transformedfood_update.html:25 +msgid "Fully used" +msgstr "" + +#: apps/food/forms.py:171 apps/food/templates/food/qrcode.html:29 +#: apps/food/templates/food/transformedfood_update.html:23 +#: apps/note/templates/note/transaction_form.html:132 +#: apps/treasury/models.py:61 +msgid "Name" +msgstr "Nombre" + +#: apps/food/forms.py:181 #, fuzzy #| msgid "phone number" -msgid "QR-code number" +msgid "QR code number" msgstr "número de teléfono" -#: apps/food/models.py:26 -msgid "food container" -msgstr "" - -#: apps/food/models.py:30 -msgid "QR-code" -msgstr "" - -#: apps/food/models.py:31 -msgid "QR-codes" -msgstr "" - -#: apps/food/models.py:34 -#, python-brace-format -msgid "QR-code number {qr_code_number}" -msgstr "" - -#: apps/food/models.py:47 +#: apps/food/models.py:23 msgid "Allergen" msgstr "" -#: apps/food/models.py:48 apps/food/templates/food/basicfood_detail.html:17 -#: apps/food/templates/food/transformedfood_detail.html:20 +#: apps/food/models.py:24 msgid "Allergens" msgstr "" -#: apps/food/models.py:64 +#: apps/food/models.py:43 msgid "owner" msgstr "" -#: apps/food/models.py:70 -msgid "allergen" +#: apps/food/models.py:49 +msgid "allergens" msgstr "" -#: apps/food/models.py:74 +#: apps/food/models.py:53 #, fuzzy #| msgid "birth date" msgid "expiry date" msgstr "fecha de nacimiento" -#: apps/food/models.py:80 -msgid "was eaten" +#: apps/food/models.py:59 +msgid "end of life" msgstr "" -#: apps/food/models.py:89 +#: apps/food/models.py:64 msgid "is ready" msgstr "" -#: apps/food/models.py:94 -#, fuzzy -#| msgid "active" -msgid "is active" -msgstr "activo" - -#: apps/food/models.py:106 -msgid "foods" +#: apps/food/models.py:70 +msgid "order" msgstr "" -#: apps/food/models.py:122 +#: apps/food/models.py:107 apps/food/views.py:34 +#: note_kfet/templates/base.html:72 +msgid "Food" +msgstr "" + +#: apps/food/models.py:108 +msgid "Foods" +msgstr "" + +#: apps/food/models.py:117 #, fuzzy #| msgid "invalidate" msgid "arrival date" msgstr "invalidar" -#: apps/food/models.py:152 +#: apps/food/models.py:169 msgid "Basic food" msgstr "" -#: apps/food/models.py:153 +#: apps/food/models.py:170 msgid "Basic foods" msgstr "" -#: apps/food/models.py:161 +#: apps/food/models.py:182 #, fuzzy #| msgid "created at" msgid "creation date" msgstr "creada el" -#: apps/food/models.py:169 -msgid "transformed ingredient" -msgstr "" - -#: apps/food/models.py:174 +#: apps/food/models.py:188 msgid "shelf life" msgstr "" -#: apps/food/models.py:225 apps/food/views.py:365 +#: apps/food/models.py:196 +msgid "transformed ingredient" +msgstr "" + +#: apps/food/models.py:258 #, fuzzy #| msgid "Transfer money" msgid "Transformed food" msgstr "Transferir dinero" -#: apps/food/models.py:226 +#: apps/food/models.py:259 msgid "Transformed foods" msgstr "" -#: apps/food/templates/food/basicfood_detail.html:14 -#: apps/food/templates/food/qrcode_detail.html:15 -#: apps/food/templates/food/transformedfood_detail.html:14 +#: apps/food/models.py:271 #, fuzzy -#| msgid "Owned" -msgid "Owner" -msgstr "Tenido" +#| msgid "phone number" +msgid "qr code number" +msgstr "número de teléfono" -#: apps/food/templates/food/basicfood_detail.html:15 -#, fuzzy -#| msgid "invalidate" -msgid "Arrival date" -msgstr "invalidar" - -#: apps/food/templates/food/basicfood_detail.html:16 -#: apps/food/templates/food/qrcode_detail.html:16 -#: apps/food/templates/food/transformedfood_detail.html:19 -#, fuzzy -#| msgid "birth date" -msgid "Expiry date" -msgstr "fecha de nacimiento" - -#: apps/food/templates/food/basicfood_detail.html:24 -#: apps/food/templates/food/transformedfood_detail.html:36 -#, fuzzy -#| msgid "active" -msgid "Active" -msgstr "activo" - -#: apps/food/templates/food/basicfood_detail.html:25 -#: apps/food/templates/food/transformedfood_detail.html:37 -msgid "Eaten" +#: apps/food/models.py:278 +msgid "food container" msgstr "" -#: apps/food/templates/food/basicfood_detail.html:28 -#: apps/food/templates/food/qrcode_detail.html:20 -#: apps/food/templates/food/qrcode_detail.html:24 -#: apps/food/templates/food/transformedfood_detail.html:41 +#: apps/food/models.py:282 +msgid "QR-code" +msgstr "" + +#: apps/food/models.py:283 +msgid "QR-codes" +msgstr "" + +#: apps/food/models.py:286 +#: apps/food/templates/food/transformedfood_update.html:24 +#, fuzzy +#| msgid "phone number" +msgid "QR-code number" +msgstr "número de teléfono" + +#: apps/food/templates/food/food_detail.html:19 +msgid "Contained in" +msgstr "" + +#: apps/food/templates/food/food_detail.html:26 +msgid "Contain" +msgstr "" + +#: apps/food/templates/food/food_detail.html:35 #, fuzzy #| msgid "Update bus" msgid "Update" msgstr "Modificar el bus" -#: apps/food/templates/food/basicfood_detail.html:32 -#: apps/food/templates/food/qrcode_detail.html:34 -#: apps/food/templates/food/transformedfood_detail.html:46 +#: apps/food/templates/food/food_detail.html:40 #, fuzzy #| msgid "Add team" msgid "Add to a meal" msgstr "Añadir un equipo" -#: apps/food/templates/food/create_qrcode_form.html:14 +#: apps/food/templates/food/food_detail.html:45 #, fuzzy -#| msgid "Transfer money" -msgid "New basic food" -msgstr "Transferir dinero" +#| msgid "manage entries" +msgid "Manage ingredients" +msgstr "gestionar las entradas" -#: apps/food/templates/food/qrcode_detail.html:10 +#: apps/food/templates/food/food_detail.html:49 #, fuzzy -#| msgid "phone number" -msgid "number" -msgstr "número de teléfono" +#| msgid "Return to credit list" +msgid "Return to the food list" +msgstr "Regresar a la lista de los créditos" -#: apps/food/templates/food/qrcode_detail.html:14 -#: apps/note/templates/note/transaction_form.html:132 -#: apps/treasury/models.py:60 -msgid "Name" -msgstr "Nombre" - -#: apps/food/templates/food/qrcode_detail.html:29 -#, fuzzy -#| msgid "Profile detail" -msgid "View details" -msgstr "Detalles del usuario" - -#: apps/food/templates/food/transformedfood_detail.html:16 -#: apps/food/templates/food/transformedfood_detail.html:35 -msgid "Ready" -msgstr "" - -#: apps/food/templates/food/transformedfood_detail.html:18 -#, fuzzy -#| msgid "created at" -msgid "Creation date" -msgstr "creada el" - -#: apps/food/templates/food/transformedfood_detail.html:27 -msgid "Ingredients" -msgstr "" - -#: apps/food/templates/food/transformedfood_detail.html:34 -msgid "Shelf life" -msgstr "" - -#: apps/food/templates/food/transformedfood_list.html:11 +#: apps/food/templates/food/food_list.html:14 msgid "Meal served" msgstr "" -#: apps/food/templates/food/transformedfood_list.html:16 +#: apps/food/templates/food/food_list.html:19 #, fuzzy #| msgid "New user" msgid "New meal" msgstr "Nuevo usuario" -#: apps/food/templates/food/transformedfood_list.html:25 +#: apps/food/templates/food/food_list.html:28 #, fuzzy #| msgid "There is no results." msgid "There is no meal served." msgstr "No hay resultado." -#: apps/food/templates/food/transformedfood_list.html:33 -msgid "Open" +#: apps/food/templates/food/food_list.html:35 +msgid "Free food" msgstr "" -#: apps/food/templates/food/transformedfood_list.html:40 +#: apps/food/templates/food/food_list.html:42 #, fuzzy #| msgid "There is no results." -msgid "There is no free meal." +msgid "There is no free food." msgstr "No hay resultado." -#: apps/food/templates/food/transformedfood_list.html:48 -msgid "All meals" +#: apps/food/templates/food/food_list.html:50 +#, fuzzy +#| msgid "for club" +msgid "Food of your clubs" +msgstr "interesa el club" + +#: apps/food/templates/food/food_list.html:56 +#, fuzzy +#| msgid "for club" +msgid "Food of club" +msgstr "interesa el club" + +#: apps/food/templates/food/food_list.html:63 +msgid "Yours club has not food yet." msgstr "" -#: apps/food/templates/food/transformedfood_list.html:55 +#: apps/food/templates/food/manage_ingredients.html:45 +#: apps/food/templates/food/transformedfood_update.html:42 #, fuzzy -#| msgid "There is no results." -msgid "There is no meal." -msgstr "No hay resultado." +#| msgid "Add friends" +msgid "Add ingredient" +msgstr "Añadir amig@s" -#: apps/food/views.py:28 -msgid "Add the ingredient" +#: apps/food/templates/food/manage_ingredients.html:46 +#: apps/food/templates/food/transformedfood_update.html:43 +#, fuzzy +#| msgid "Remove product" +msgid "Remove ingredient" +msgstr "Quitar un producto" + +#: apps/food/templates/food/qrcode.html:22 +msgid "Copy constructor" msgstr "" -#: apps/food/views.py:42 +#: apps/food/templates/food/qrcode.html:23 #, fuzzy -#| msgid "This credit is already validated." -msgid "The product is already prepared" -msgstr "Este crédito ya fue validado." +#| msgid "Transfer money" +msgid "New food" +msgstr "Transferir dinero" -#: apps/food/views.py:70 +#: apps/food/templates/food/qrcode.html:32 +#, fuzzy +#| msgid "Owned" +msgid "Owner" +msgstr "Tenido" + +#: apps/food/templates/food/qrcode.html:35 +#, fuzzy +#| msgid "birth date" +msgid "Expiry date" +msgstr "fecha de nacimiento" + +#: apps/food/utils.py:6 +msgid "second" +msgstr "" + +#: apps/food/utils.py:6 +msgid "seconds" +msgstr "" + +#: apps/food/utils.py:7 +msgid "minute" +msgstr "" + +#: apps/food/utils.py:7 +msgid "minutes" +msgstr "" + +#: apps/food/utils.py:8 +msgid "hour" +msgstr "" + +#: apps/food/utils.py:8 +msgid "hours" +msgstr "" + +#: apps/food/utils.py:9 +#, fuzzy +#| msgid "days" +msgid "day" +msgstr "días" + +#: apps/food/utils.py:9 apps/member/templates/member/includes/club_info.html:27 +msgid "days" +msgstr "días" + +#: apps/food/utils.py:10 +msgid "week" +msgstr "" + +#: apps/food/utils.py:10 +msgid "weeks" +msgstr "" + +#: apps/food/utils.py:53 +#: env/lib/python3.11/site-packages/django/db/models/base.py:1423 +#: env/lib/python3.11/site-packages/django/forms/models.py:893 +#, fuzzy +#| msgid "add" +msgid "and" +msgstr "añadir" + +#: apps/food/views.py:118 +msgid "Add a new QRCode" +msgstr "" + +#: apps/food/views.py:167 +#, fuzzy +#| msgid "Update an invoice" +msgid "Add an aliment" +msgstr "Modificar una factura" + +#: apps/food/views.py:235 +#, fuzzy +#| msgid "Add team" +msgid "Add a meal" +msgstr "Añadir un equipo" + +#: apps/food/views.py:275 +#, fuzzy +#| msgid "manage entries" +msgid "Manage ingredients of:" +msgstr "gestionar las entradas" + +#: apps/food/views.py:289 apps/food/views.py:297 +#, python-brace-format +msgid "Fully used in {meal}" +msgstr "" + +#: apps/food/views.py:344 +msgid "Add the ingredient:" +msgstr "" + +#: apps/food/views.py:370 +#, python-brace-format +msgid "Food fully used in : {meal.name}" +msgstr "" + +#: apps/food/views.py:389 #, fuzzy #| msgid "Update an invoice" msgid "Update an aliment" msgstr "Modificar una factura" -#: apps/food/views.py:97 +#: apps/food/views.py:437 #, fuzzy #| msgid "WEI Detail" msgid "Details of:" msgstr "Detalles del WEI" -#: apps/food/views.py:121 -msgid "Add a new basic food with QRCode" -msgstr "" +#: apps/food/views.py:447 apps/treasury/tables.py:149 +#: env/lib/python3.11/site-packages/django/forms/widgets.py:795 +msgid "Yes" +msgstr "Sí" -#: apps/food/views.py:185 -msgid "Add a new QRCode" -msgstr "" - -#: apps/food/views.py:235 -msgid "QRCode" -msgstr "" - -#: apps/food/views.py:271 -msgid "Add a new meal" -msgstr "" - -#: apps/food/views.py:337 -#, fuzzy -#| msgid "Update team" -msgid "Update a meal" -msgstr "Modificar el equipo" +#: apps/food/views.py:449 apps/member/models.py:99 apps/treasury/tables.py:149 +#: env/lib/python3.11/site-packages/django/forms/widgets.py:796 +msgid "No" +msgstr "No" #: apps/logs/apps.py:11 msgid "Logs" @@ -762,12 +922,6 @@ msgstr "nuevos datos" msgid "create" msgstr "crear" -#: apps/logs/models.py:65 apps/note/tables.py:230 apps/note/tables.py:277 -#: apps/permission/models.py:126 apps/treasury/tables.py:38 -#: apps/wei/tables.py:74 -msgid "delete" -msgstr "suprimir" - #: apps/logs/models.py:68 msgid "action" msgstr "acción" @@ -803,11 +957,11 @@ msgstr "pago de afiliación (estudiantes pagados)" msgid "membership fee (unpaid students)" msgstr "pago de afiliación (estudiantes no pagados)" -#: apps/member/admin.py:65 apps/member/models.py:330 +#: apps/member/admin.py:65 apps/member/models.py:337 msgid "roles" msgstr "papel" -#: apps/member/admin.py:66 apps/member/models.py:344 +#: apps/member/admin.py:66 apps/member/models.py:351 msgid "fee" msgstr "pago" @@ -815,25 +969,25 @@ msgstr "pago" msgid "member" msgstr "miembro" -#: apps/member/forms.py:24 +#: apps/member/forms.py:25 msgid "Permission mask" msgstr "Antifaz de permisos" -#: apps/member/forms.py:46 +#: apps/member/forms.py:48 msgid "Report frequency" msgstr "Frecuencia de los informes (en días)" -#: apps/member/forms.py:48 +#: apps/member/forms.py:50 msgid "Last report date" msgstr "Fecha del último informe" -#: apps/member/forms.py:52 +#: apps/member/forms.py:54 msgid "" "Anti-VSS (Violences Sexistes et Sexuelles) charter read and approved" msgstr "" "Carta Anti-VSS (Violences Sexistes et Sexuelles) leída y aprobada" -#: apps/member/forms.py:53 +#: apps/member/forms.py:55 msgid "" "Tick after having read and accepted the anti-VSS charter " @@ -843,65 +997,65 @@ msgstr "" "perso.crans.org/club-bde/Charte-anti-VSS.pdf target=_blank> disponible en " "pdf aquí" -#: 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 "Usted no puede registrar si viene del futuro." -#: apps/member/forms.py:86 +#: apps/member/forms.py:89 msgid "select an image" msgstr "elegir una imagen" -#: apps/member/forms.py:87 +#: apps/member/forms.py:90 msgid "Maximal size: 2MB" msgstr "Tamaño máximo : 2Mo" -#: apps/member/forms.py:112 +#: apps/member/forms.py:115 msgid "This image cannot be loaded." msgstr "Esta imagen no puede ser cargada." -#: 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:117 +#: apps/registration/forms.py:33 apps/registration/views.py:282 msgid "An alias with a similar name already exists." msgstr "Un alias similar ya existe." -#: apps/member/forms.py:175 +#: apps/member/forms.py:178 msgid "Inscription paid by Société Générale" msgstr "Registración pagadas por 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 "Marcar esta casilla si Société Générale pagó la registración." -#: 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:94 msgid "Credit type" msgstr "Tipo de crédito" -#: 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:95 msgid "No credit" msgstr "No crédito" -#: apps/member/forms.py:185 +#: apps/member/forms.py:188 msgid "You can credit the note of the user." msgstr "Usted puede acreditar la note del usuario." -#: 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:100 msgid "Credit amount" msgstr "Valor del crédito" -#: 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:117 msgid "Bank" msgstr "Banco" -#: apps/member/forms.py:233 +#: apps/member/forms.py:236 msgid "User" msgstr "Usuario" -#: apps/member/forms.py:247 +#: apps/member/forms.py:250 msgid "Roles" msgstr "Papeles" @@ -1034,10 +1188,6 @@ msgstr "pagado" msgid "Tells if the user receive a salary." msgstr "Indica si el usuario percibe un salario." -#: apps/member/models.py:99 apps/treasury/tables.py:143 -msgid "No" -msgstr "No" - #: apps/member/models.py:100 msgid "Yes (receive them in french)" msgstr "Si (recibirles en francés)" @@ -1154,7 +1304,7 @@ msgstr "" msgid "add to registration form" msgstr "Validar la afiliación" -#: 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" @@ -1163,37 +1313,37 @@ msgstr "club" msgid "clubs" msgstr "clubs" -#: apps/member/models.py:335 +#: apps/member/models.py:342 msgid "membership starts on" msgstr "afiliación empezá el" -#: apps/member/models.py:339 +#: apps/member/models.py:346 msgid "membership ends on" msgstr "afiliación termina el" -#: 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 "afiliación" -#: apps/member/models.py:349 +#: apps/member/models.py:356 msgid "memberships" msgstr "afiliaciones" -#: apps/member/models.py:353 +#: apps/member/models.py:360 #, python-brace-format msgid "Membership of {user} for the club {club}" msgstr "Afiliación of {user} for the 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 "El papel {role} no se encuentra en el club {club}." -#: apps/member/models.py:381 apps/member/views.py:715 +#: apps/member/models.py:388 apps/member/views.py:759 msgid "User is already a member of the club" msgstr "Usuario ya esta un miembro del club" -#: apps/member/models.py:393 apps/member/views.py:724 +#: apps/member/models.py:400 apps/member/views.py:768 msgid "User is not a member of the parent club" msgstr "Usuario no es un miembro del club pariente" @@ -1245,7 +1395,7 @@ msgid "Account #" msgstr "Cuenta 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:61 #: apps/registration/templates/registration/future_profile_detail.html:48 #: apps/wei/templates/wei/weimembership_form.html:117 msgid "Update Profile" @@ -1306,20 +1456,11 @@ msgstr "" "nuevo posibles." #: 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:318 +#: apps/member/views.py:559 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 "Añadir" - #: apps/member/templates/member/club_detail.html:13 #: apps/permission/templates/permission/all_rights.html:32 msgid "Club managers" @@ -1356,10 +1497,6 @@ msgstr "No hay afiliación encontrada con esta entrada." msgid "Club Parent" msgstr "Club pariente" -#: apps/member/templates/member/includes/club_info.html:27 -msgid "days" -msgstr "días" - #: apps/member/templates/member/includes/club_info.html:31 #: apps/wei/templates/wei/base.html:40 msgid "membership fee" @@ -1455,11 +1592,11 @@ msgstr "Introspección :" msgid "Show my applications" msgstr "Mostrar mis aplicaciones" -#: apps/member/templates/member/picture_update.html:38 +#: apps/member/templates/member/picture_update.html:40 msgid "Nevermind" msgstr "No importa" -#: apps/member/templates/member/picture_update.html:39 +#: apps/member/templates/member/picture_update.html:41 msgid "Crop and upload" msgstr "Podar y subir" @@ -1508,51 +1645,51 @@ msgstr "Guardar cambios" msgid "Registrations" msgstr "Registraciones" -#: apps/member/views.py:72 apps/registration/forms.py:23 +#: apps/member/views.py:74 apps/registration/forms.py:23 msgid "This address must be valid." msgstr "Este correo tiene que ser valido." -#: apps/member/views.py:139 +#: apps/member/views.py:154 msgid "Profile detail" msgstr "Detalles del usuario" -#: apps/member/views.py:205 +#: apps/member/views.py:220 msgid "Search user" msgstr "Buscar un usuario" -#: apps/member/views.py:253 +#: apps/member/views.py:272 msgid "Note friendships" msgstr "Amistades de note" -#: apps/member/views.py:308 +#: apps/member/views.py:342 msgid "Update note picture" msgstr "Modificar la imagen de la note" -#: apps/member/views.py:357 +#: apps/member/views.py:391 msgid "Manage auth token" msgstr "Gestionar los token de autentificación" -#: apps/member/views.py:384 +#: apps/member/views.py:418 msgid "Create new club" msgstr "Crear un nuevo club" -#: apps/member/views.py:403 +#: apps/member/views.py:437 msgid "Search club" msgstr "Buscar un club" -#: apps/member/views.py:436 +#: apps/member/views.py:475 msgid "Club detail" msgstr "Detalles del club" -#: apps/member/views.py:543 +#: apps/member/views.py:587 msgid "Update club" msgstr "Modificar el club" -#: apps/member/views.py:577 +#: apps/member/views.py:621 msgid "Add new member to the club" msgstr "Añadir un nuevo miembro al club" -#: apps/member/views.py:706 apps/wei/views.py:973 +#: apps/member/views.py:750 apps/wei/views.py:1040 msgid "" "This user don't have enough money to join this club, and can't have a " "negative balance." @@ -1560,19 +1697,19 @@ msgstr "" "Este usuario no tiene suficiente dinero para unirse a este club, y no puede " "tener un saldo negativo." -#: apps/member/views.py:728 +#: apps/member/views.py:772 msgid "The membership must start after {:%m-%d-%Y}." msgstr "La afiliación tiene que empezar después del {:%d-%m-%Y}." -#: apps/member/views.py:733 +#: apps/member/views.py:777 msgid "The membership must begin before {:%m-%d-%Y}." msgstr "La afiliación tiene que empezar antes del {:%d-%m-%Y}." -#: apps/member/views.py:883 +#: apps/member/views.py:927 msgid "Manage roles of an user in the club" msgstr "Gestionar los papeles de un usuario en el club" -#: apps/member/views.py:908 +#: apps/member/views.py:952 msgid "Members of the club" msgstr "Miembros del club" @@ -1605,35 +1742,35 @@ msgstr "" "La transacción no puede ser guardada puesto que la note fuente o la note " "destino no esta activa." -#: apps/note/forms.py:39 +#: apps/note/forms.py:40 msgid "Source" msgstr "Fuente" -#: apps/note/forms.py:53 +#: apps/note/forms.py:54 msgid "Destination" msgstr "Destino" -#: 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 "Motivo" -#: apps/note/forms.py:79 apps/treasury/tables.py:136 +#: apps/note/forms.py:80 apps/treasury/tables.py:141 msgid "Valid" msgstr "Valido" -#: apps/note/forms.py:85 +#: apps/note/forms.py:86 msgid "Total amount greater than" msgstr "Monto total mayor que" -#: apps/note/forms.py:93 +#: apps/note/forms.py:94 msgid "Total amount less than" msgstr "Monto total menor que" -#: apps/note/forms.py:99 +#: apps/note/forms.py:100 msgid "Created after" msgstr "Creado después" -#: apps/note/forms.py:106 +#: apps/note/forms.py:107 msgid "Created before" msgstr "Creado antes" @@ -1739,7 +1876,7 @@ msgstr "amigo" #: apps/note/models/notes.py:243 #, fuzzy #| msgid "friendships" -msgid "friendship" +msgid "frienship" msgstr "amistades" #: apps/note/models/notes.py:248 @@ -1888,8 +2025,9 @@ msgstr "" "pago y un usuario o 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:1045 +#: apps/wei/views.py:1049 +#: env/lib/python3.11/site-packages/django/forms/fields.py:91 msgid "This field is required." msgstr "Este campo es obligatorio." @@ -1897,7 +2035,7 @@ msgstr "Este campo es obligatorio." msgid "membership transaction" msgstr "transacción de afiliación" -#: apps/note/models/transactions.py:381 apps/treasury/models.py:300 +#: apps/note/models/transactions.py:381 apps/treasury/models.py:301 msgid "membership transactions" msgstr "transacciones de afiliación" @@ -1913,18 +2051,6 @@ msgstr "Hacer clic para validar" msgid "No reason specified" msgstr "Ningún motivo dado" -#: 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 "Suprimir" - #: apps/note/tables.py:191 msgid "Trust back" msgstr "Añadir como amig@" @@ -1938,12 +2064,13 @@ msgstr "Añadir en retorno" #: apps/wei/templates/wei/base.html:89 #: apps/wei/templates/wei/bus_detail.html:20 #: apps/wei/templates/wei/busteam_detail.html:20 -#: apps/wei/templates/wei/busteam_detail.html:40 +#: apps/wei/templates/wei/busteam_detail.html:42 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:37 #: note_kfet/templates/oauth2_provider/application_detail.html:38 msgid "Edit" msgstr "Editar" -#: apps/note/tables.py:266 apps/note/tables.py:293 +#: apps/note/tables.py:267 apps/note/tables.py:296 msgid "Hide/Show" msgstr "Ocultar/Mostrar" @@ -2029,8 +2156,8 @@ msgid "Action" msgstr "Acción" #: 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 "Monto" @@ -2092,34 +2219,35 @@ msgid "Button displayed" msgstr "Botón mostrado" #: apps/note/templates/note/transactiontemplate_list.html:100 +#: apps/wrapped/templates/wrapped/wrapped_list.html:70 msgid "An error occured" msgstr "Un error ocurrió" -#: apps/note/views.py:36 +#: apps/note/views.py:37 msgid "Transfer money" msgstr "Transferir dinero" -#: apps/note/views.py:74 +#: apps/note/views.py:75 msgid "Create new button" msgstr "Crear un nuevo botón" -#: apps/note/views.py:83 +#: apps/note/views.py:84 msgid "Search button" msgstr "Buscar un botón" -#: apps/note/views.py:111 +#: apps/note/views.py:116 msgid "Update button" msgstr "Modificar el botón" -#: 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 "Consumiciones" -#: apps/note/views.py:165 +#: apps/note/views.py:170 msgid "You can't see any button." msgstr "Usted no puede ver ningún botón." -#: apps/note/views.py:204 +#: apps/note/views.py:209 msgid "Search transactions" msgstr "Buscar transacciones" @@ -2213,7 +2341,7 @@ msgstr "" "Usted no tiene permiso a cambiar el campo {field} on this instance of model " "{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}." @@ -2278,39 +2406,42 @@ msgid "Available scopes" msgstr "" #: apps/permission/templates/permission/scopes.html:42 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:24 msgid "No applications defined" msgstr "Ninguna aplicación definida" #: apps/permission/templates/permission/scopes.html:43 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:25 msgid "Click here" msgstr "Pulsar aquí" #: apps/permission/templates/permission/scopes.html:43 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:25 msgid "if you want to register a new one" msgstr "si quiere crear una nueva" -#: 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 " "\"{model}\" with these parameters. Please correct your data and retry." msgstr "" -#: 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}\" " "with these parameters. Please correct your data and retry." msgstr "" -#: 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 "Permisos" -#: apps/permission/views.py:117 +#: apps/permission/views.py:137 msgid "All rights" msgstr "Todos los permisos" @@ -2447,60 +2578,68 @@ msgstr "Gracias" msgid "The Note Kfet team." msgstr "El equipo Note Kfet." -#: apps/registration/views.py:42 +#: apps/registration/views.py:43 msgid "Register new user" msgstr "Registrar un nuevo usuario" -#: apps/registration/views.py:100 +#: apps/registration/views.py:101 msgid "Email validation" msgstr "Validación del correo electrónico" -#: apps/registration/views.py:102 +#: apps/registration/views.py:103 msgid "Validate email" msgstr "Validar el correo electrónico" -#: apps/registration/views.py:146 +#: apps/registration/views.py:147 msgid "Email validation unsuccessful" msgstr "La validación del correo electrónico fracasó" -#: apps/registration/views.py:157 +#: apps/registration/views.py:158 msgid "Email validation email sent" msgstr "Correo de validación enviado" -#: apps/registration/views.py:165 +#: apps/registration/views.py:166 msgid "Resend email validation link" msgstr "Reenviar el enlace de validación" -#: apps/registration/views.py:183 +#: apps/registration/views.py:184 msgid "Pre-registered users list" msgstr "Lista de los usuarios con afiliación pendiente" -#: apps/registration/views.py:207 +#: apps/registration/views.py:213 msgid "Unregistered users" msgstr "Usuarios con afiliación pendiente" -#: apps/registration/views.py:220 +#: apps/registration/views.py:226 msgid "Registration detail" msgstr "Detalles de la afiliación" -#: apps/registration/views.py:256 +#: apps/registration/views.py:262 #, fuzzy, python-format #| msgid "Note of %(club)s club" msgid "Join %(club)s Club" msgstr "Note del 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 "Usted tiene que afiliarse al 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 "Usted tiene que afiliarse al BDE." + +#: apps/registration/views.py:340 msgid "" "The entered amount is not enough for the memberships, should be at least {}" msgstr "" "El monto dado no es suficiente para las afiliaciones, tiene que ser al menos " "{}" -#: apps/registration/views.py:425 +#: apps/registration/views.py:435 msgid "Invalidate pre-registration" msgstr "Invalidar la afiliación" @@ -2508,7 +2647,7 @@ msgstr "Invalidar la afiliación" msgid "Treasury" msgstr "Tesorería" -#: apps/treasury/forms.py:26 apps/treasury/models.py:112 +#: apps/treasury/forms.py:26 apps/treasury/models.py:113 #: apps/treasury/templates/treasury/invoice_form.html:22 msgid "This invoice is locked and can no longer be edited." msgstr "Esta factura esta bloqueada y no puede ser modificada." @@ -2521,8 +2660,8 @@ msgstr "El descuento ya esta cerrado." msgid "You can't change the type of the remittance." msgstr "No puede cambiar el tipo de descuento." -#: apps/treasury/forms.py:125 apps/treasury/models.py:275 -#: apps/treasury/tables.py:97 apps/treasury/tables.py:105 +#: apps/treasury/forms.py:125 apps/treasury/models.py:276 +#: 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 @@ -2537,142 +2676,143 @@ msgstr "No hay descuento relacionado" msgid "Invoice identifier" msgstr "Numero de factura" -#: apps/treasury/models.py:42 +#: apps/treasury/models.py:43 apps/wrapped/models.py:28 +#: apps/wrapped/models.py:29 msgid "BDE" msgstr "BDE" -#: apps/treasury/models.py:46 +#: apps/treasury/models.py:47 #, fuzzy #| msgid "location" msgid "Quotation" msgstr "ubicación" -#: apps/treasury/models.py:51 +#: apps/treasury/models.py:52 msgid "Object" msgstr "Asunto" -#: apps/treasury/models.py:55 +#: apps/treasury/models.py:56 msgid "Description" msgstr "Descripción" -#: apps/treasury/models.py:64 +#: apps/treasury/models.py:65 msgid "Address" msgstr "Dirección" -#: apps/treasury/models.py:69 apps/treasury/models.py:202 +#: apps/treasury/models.py:70 apps/treasury/models.py:203 msgid "Date" msgstr "Fecha" -#: apps/treasury/models.py:75 +#: apps/treasury/models.py:76 #, fuzzy #| msgid "end date" msgid "Payment date" msgstr "fecha de fin" -#: apps/treasury/models.py:79 +#: apps/treasury/models.py:80 msgid "Acquitted" msgstr "Pagada" -#: apps/treasury/models.py:84 +#: apps/treasury/models.py:85 msgid "Locked" msgstr "Bloqueada" -#: apps/treasury/models.py:85 +#: apps/treasury/models.py:86 msgid "An invoice can't be edited when it is locked." msgstr "Une factura no puede ser modificada cuando esta bloqueada." -#: apps/treasury/models.py:91 +#: apps/treasury/models.py:92 msgid "tex source" msgstr "código fuente TeX" -#: apps/treasury/models.py:95 apps/treasury/models.py:140 +#: apps/treasury/models.py:96 apps/treasury/models.py:141 msgid "invoice" msgstr "factura" -#: apps/treasury/models.py:96 +#: apps/treasury/models.py:97 msgid "invoices" msgstr "facturas" -#: apps/treasury/models.py:99 +#: apps/treasury/models.py:100 #, python-brace-format msgid "Invoice #{id}" msgstr "Factura n°{id}" -#: apps/treasury/models.py:145 +#: apps/treasury/models.py:146 msgid "Designation" msgstr "Designación" -#: apps/treasury/models.py:151 +#: apps/treasury/models.py:152 msgid "Quantity" msgstr "Cantidad" -#: apps/treasury/models.py:156 +#: apps/treasury/models.py:157 msgid "Unit price" msgstr "Precio unitario" -#: apps/treasury/models.py:160 +#: apps/treasury/models.py:161 msgid "product" msgstr "producto" -#: apps/treasury/models.py:161 +#: apps/treasury/models.py:162 msgid "products" msgstr "productos" -#: apps/treasury/models.py:189 +#: apps/treasury/models.py:190 msgid "remittance type" msgstr "tipo de descuento" -#: apps/treasury/models.py:190 +#: apps/treasury/models.py:191 msgid "remittance types" msgstr "tipos de descuentos" -#: apps/treasury/models.py:213 +#: apps/treasury/models.py:214 msgid "Comment" msgstr "Comentario" -#: apps/treasury/models.py:218 +#: apps/treasury/models.py:219 msgid "Closed" msgstr "Cerrada" -#: apps/treasury/models.py:222 +#: apps/treasury/models.py:223 msgid "remittance" msgstr "descuento" -#: apps/treasury/models.py:223 +#: apps/treasury/models.py:224 msgid "remittances" msgstr "descuentos" -#: apps/treasury/models.py:226 +#: apps/treasury/models.py:227 msgid "Remittance #{:d}: {}" msgstr "Descuento n°{:d} : {}" -#: apps/treasury/models.py:279 +#: apps/treasury/models.py:280 msgid "special transaction proxy" msgstr "proxy de transacción especial" -#: apps/treasury/models.py:280 +#: apps/treasury/models.py:281 msgid "special transaction proxies" msgstr "proxys de transacciones especiales" -#: apps/treasury/models.py:306 +#: apps/treasury/models.py:307 msgid "credit transaction" msgstr "transacción de crédito" -#: apps/treasury/models.py:311 +#: apps/treasury/models.py:312 #: apps/treasury/templates/treasury/sogecredit_detail.html:10 msgid "Credit from the Société générale" msgstr "Crédito de la Société Générale" -#: apps/treasury/models.py:312 +#: apps/treasury/models.py:313 msgid "Credits from the Société générale" msgstr "Créditos de la Société Générale" -#: apps/treasury/models.py:315 +#: apps/treasury/models.py:316 #, python-brace-format msgid "Soge credit for {user}" msgstr "Crédito de la Société Générale para {user}" -#: apps/treasury/models.py:445 +#: apps/treasury/models.py:446 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." @@ -2692,25 +2832,22 @@ msgstr "Factura n°{:d}" msgid "Invoice" msgstr "Factura" -#: apps/treasury/tables.py:65 +#: apps/treasury/tables.py:66 msgid "Transaction count" msgstr "Cantidad de transacciones" -#: apps/treasury/tables.py:70 apps/treasury/tables.py:72 +#: apps/treasury/tables.py:71 apps/treasury/tables.py:73 +#: apps/wei/templates/wei/busteam_detail.html:22 apps/wrapped/tables.py:42 msgid "View" msgstr "Ver" -#: apps/treasury/tables.py:143 -msgid "Yes" -msgstr "Sí" - #: apps/treasury/templates/treasury/invoice_confirm_delete.html:10 -#: apps/treasury/views.py:173 +#: apps/treasury/views.py:174 msgid "Delete invoice" msgstr "Suprimir la factura" #: 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 "Esta factura esta bloqueada y no puede ser suprimida." @@ -2878,44 +3015,44 @@ msgstr "Anãdir un crédito de la Société Générale" msgid "Credit successfully registered" msgstr "Crédito creado con éxito" -#: apps/treasury/views.py:40 +#: apps/treasury/views.py:41 msgid "Create new invoice" msgstr "Crear una nueva factura" -#: apps/treasury/views.py:97 +#: apps/treasury/views.py:98 msgid "Invoices list" msgstr "Lista de las facturas" -#: 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 "Usted no tiene derecho a ver la interfaz de tesorería." -#: apps/treasury/views.py:115 +#: apps/treasury/views.py:116 msgid "Update an invoice" msgstr "Modificar una factura" -#: apps/treasury/views.py:240 +#: apps/treasury/views.py:241 msgid "Create a new remittance" msgstr "Crear un nuevo descuento" -#: apps/treasury/views.py:267 +#: apps/treasury/views.py:265 msgid "Remittances list" msgstr "Lista de los descuentos" -#: apps/treasury/views.py:326 +#: apps/treasury/views.py:320 msgid "Update a remittance" msgstr "Modificar un descuento" -#: apps/treasury/views.py:349 +#: apps/treasury/views.py:342 msgid "Attach a transaction to a remittance" msgstr "Unir una transacción con un descuento" -#: apps/treasury/views.py:393 +#: apps/treasury/views.py:386 msgid "List of credits from the Société générale" msgstr "Lista de los créditos 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 "Gestionar los créditos de la Société Générale" @@ -2925,17 +3062,17 @@ msgstr "Gestionar los créditos 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 "" "El usuario seleccionado no ha sido validado. Validar esta cuenta primero" -#: apps/wei/forms/registration.py:59 apps/wei/models.py:126 +#: apps/wei/forms/registration.py:62 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:63 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." @@ -2944,11 +3081,11 @@ msgstr "" "derecho de imponer su bus y su equipo, en particular para los electrones " "libres." -#: apps/wei/forms/registration.py:67 +#: apps/wei/forms/registration.py:70 msgid "Team" msgstr "Equipo" -#: apps/wei/forms/registration.py:69 +#: apps/wei/forms/registration.py:72 msgid "" "Leave this field empty if you won't be in a team (staff, bus chief, free " "electron)" @@ -2956,16 +3093,16 @@ msgstr "" "Deje este campo vacío si no quiere estar en un equipo (staff, jefe de bus, " "electrón libre)" -#: apps/wei/forms/registration.py:75 apps/wei/forms/registration.py:85 +#: apps/wei/forms/registration.py:78 apps/wei/forms/registration.py:88 #: apps/wei/models.py:160 msgid "WEI Roles" msgstr "Papeles en el WEI" -#: apps/wei/forms/registration.py:76 +#: apps/wei/forms/registration.py:79 msgid "Select the roles that you are interested in." msgstr "Elegir los papeles que le interesa." -#: apps/wei/forms/registration.py:122 +#: apps/wei/forms/registration.py:125 msgid "This team doesn't belong to the given bus." msgstr "Este equipo no pertenece al bus dado." @@ -2978,10 +3115,12 @@ msgid "year" msgstr "año" #: apps/wei/models.py:29 apps/wei/templates/wei/base.html:30 +#: apps/wrapped/models.py:20 msgid "date start" msgstr "fecha de inicio" #: apps/wei/models.py:33 apps/wei/templates/wei/base.html:33 +#: apps/wrapped/models.py:24 msgid "date end" msgstr "fecha de fin" @@ -3031,7 +3170,7 @@ msgstr "Papeles en el WEI" msgid "Credit from Société générale" msgstr "Crédito de la Société Générale" -#: apps/wei/models.py:188 +#: apps/wei/models.py:188 apps/wei/views.py:951 msgid "Caution check given" msgstr "Cheque de garantía dado" @@ -3068,8 +3207,7 @@ msgstr "forma de ropa" msgid "clothing size" msgstr "medida de ropa" -#: 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 "problemas de salud" @@ -3154,7 +3292,7 @@ msgid "preferred bus" msgstr "bus preferido" #: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:32 -#: apps/wei/templates/wei/busteam_detail.html:50 +#: apps/wei/templates/wei/busteam_detail.html:52 msgid "Teams" msgstr "Equipos" @@ -3187,13 +3325,22 @@ msgid "Attribute first year members into buses" msgstr "Repartir los primer años en los buses" #: apps/wei/templates/wei/1A_list.html:15 -msgid "Start attribution!" +#, fuzzy +#| msgid "Start attribution!" +msgid "Start attribution !" msgstr "Empezar repartición !" #: apps/wei/templates/wei/attribute_bus_1A.html:8 msgid "Bus attribution" msgstr "Repartición de los buses" +#: 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 "problemas de salud" + #: apps/wei/templates/wei/attribute_bus_1A.html:31 msgid "suggested bus" msgstr "bus sugerido" @@ -3222,11 +3369,11 @@ msgstr "Pago de entrada del WEI (estudiantes no pagados)" msgid "WEI list" msgstr "Lista de los WEI" -#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:528 +#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:557 msgid "Register 1A" msgstr "Apuntar un 1A" -#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:614 +#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:649 msgid "Register 2A+" msgstr "Apuntar un 2A+" @@ -3239,7 +3386,7 @@ msgid "View WEI" msgstr "Ver un WEI" #: apps/wei/templates/wei/bus_detail.html:22 -#: apps/wei/templates/wei/busteam_detail.html:22 +#: apps/wei/templates/wei/busteam_detail.html:24 msgid "Add team" msgstr "Añadir un equipo" @@ -3248,15 +3395,15 @@ msgid "Members" msgstr "Miembros" #: apps/wei/templates/wei/bus_detail.html:54 -#: apps/wei/templates/wei/busteam_detail.html:60 +#: apps/wei/templates/wei/busteam_detail.html:62 #: apps/wei/templates/wei/weimembership_list.html:31 msgid "View as PDF" msgstr "Descargar un 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:1095 +#: apps/wei/views.py:1150 apps/wei/views.py:1197 msgid "Survey WEI" msgstr "Cuestionario WEI" @@ -3300,7 +3447,7 @@ msgstr "Inscripciones sin validación" msgid "Attribute buses" msgstr "Repartición en los buses" -#: 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:83 msgid "Create WEI" msgstr "Crear un WEI" @@ -3437,117 +3584,1836 @@ msgstr "No hay pre-inscripción encontrada con esta entrada." msgid "View validated memberships..." msgstr "Ver las inscripciones validadas..." -#: apps/wei/views.py:58 +#: apps/wei/views.py:62 msgid "Search WEI" msgstr "Buscar un WEI" -#: apps/wei/views.py:109 +#: apps/wei/views.py:113 msgid "WEI Detail" msgstr "Detalles del WEI" -#: apps/wei/views.py:208 +#: apps/wei/views.py:213 msgid "View members of the WEI" msgstr "Ver los miembros del WEI" -#: apps/wei/views.py:236 +#: apps/wei/views.py:246 msgid "Find WEI Membership" msgstr "Buscar una afiliación al WEI" -#: apps/wei/views.py:246 +#: apps/wei/views.py:256 msgid "View registrations to the WEI" msgstr "Ver las inscripciones al WEI" -#: apps/wei/views.py:270 +#: apps/wei/views.py:285 msgid "Find WEI Registration" msgstr "Buscar una inscripción al WEI" -#: apps/wei/views.py:281 +#: apps/wei/views.py:296 msgid "Update the WEI" msgstr "Modificar el WEI" -#: apps/wei/views.py:302 +#: apps/wei/views.py:317 msgid "Create new bus" msgstr "Añadir un bus" -#: apps/wei/views.py:340 +#: apps/wei/views.py:355 msgid "Update bus" msgstr "Modificar el bus" -#: apps/wei/views.py:372 +#: apps/wei/views.py:387 msgid "Manage bus" msgstr "Gestionar el bus" -#: apps/wei/views.py:399 +#: apps/wei/views.py:414 msgid "Create new team" msgstr "Añadir un equipo" -#: apps/wei/views.py:439 +#: apps/wei/views.py:461 msgid "Update team" msgstr "Modificar el equipo" -#: apps/wei/views.py:470 +#: apps/wei/views.py:499 msgid "Manage WEI team" msgstr "Gestionar el equipo" -#: apps/wei/views.py:492 +#: apps/wei/views.py:521 msgid "Register first year student to the WEI" msgstr "Registrar un 1A al WEI" -#: apps/wei/views.py:550 apps/wei/views.py:649 +#: apps/wei/views.py:585 apps/wei/views.py:688 msgid "This user is already registered to this WEI." msgstr "Este usuario ya afilió a este WEI." -#: apps/wei/views.py:555 +#: apps/wei/views.py:590 msgid "" "This user can't be in her/his first year since he/she has already " "participated to a WEI." msgstr "Este usuario no puede ser un 1A porque ya participó en un WEI." -#: apps/wei/views.py:578 +#: apps/wei/views.py:613 msgid "Register old student to the WEI" msgstr "Registrar un 2A+ al WEI" -#: apps/wei/views.py:633 apps/wei/views.py:721 +#: apps/wei/views.py:668 apps/wei/views.py:764 msgid "You already opened an account in the Société générale." msgstr "Usted ya abrió una cuenta a la Société Générale." -#: apps/wei/views.py:685 +#: apps/wei/views.py:724 msgid "Update WEI Registration" msgstr "Modificar la inscripción WEI" -#: apps/wei/views.py:795 +#: apps/wei/views.py:799 +#, fuzzy +#| msgid "The BDE membership is included in the WEI registration." +msgid "No membership found for this registration" +msgstr "La afiliación al BDE esta incluida en la afiliación WEI." + +#: apps/wei/views.py:808 +#, fuzzy +#| msgid "" +#| "You don't have the permission to add an instance of model {app_label}." +#| "{model_name}." +msgid "You don't have the permission to update memberships" +msgstr "" +"Usted no tiene permiso a añadir an instance of model {app_label}." +"{model_name}." + +#: apps/wei/views.py:814 +#, fuzzy, python-format +#| msgid "" +#| "You don't have the permission to delete this instance of model " +#| "{app_label}.{model_name}." +msgid "You don't have the permission to update the field %(field)s" +msgstr "" +"Usted no tiene permiso a suprimir este instance of model {app_label}." +"{model_name}." + +#: apps/wei/views.py:855 msgid "Delete WEI registration" msgstr "Suprimir la inscripción WEI" -#: apps/wei/views.py:806 +#: apps/wei/views.py:866 msgid "You don't have the right to delete this WEI registration." msgstr "Usted no tiene derecho a suprimir esta inscripción WEI." -#: apps/wei/views.py:824 +#: apps/wei/views.py:884 msgid "Validate WEI registration" msgstr "Validar la inscripción WEI" -#: apps/wei/views.py:1223 +#: apps/wei/views.py:889 +#, fuzzy +#| msgid "You don't have the right to delete this WEI registration." +msgid "You don't have the permission to validate registrations" +msgstr "Usted no tiene derecho a suprimir esta inscripción WEI." + +#: apps/wei/views.py:952 +#, fuzzy +#| msgid "Please ask the user to credit its note before deleting this credit." +msgid "Please make sure the check is given before validating the registration" +msgstr "" +"Por favor pide al usuario acreditar su note antes de suprimir este crédito." + +#: apps/wei/views.py:1290 msgid "Attribute buses to first year members" msgstr "Repartir los primer años en los buses" -#: apps/wei/views.py:1248 +#: apps/wei/views.py:1315 msgid "Attribute bus" msgstr "Repartir en un bus" -#: note_kfet/settings/base.py:173 +#: apps/wrapped/apps.py:10 +msgid "wrapped" +msgstr "" + +#: apps/wrapped/models.py:40 +#, fuzzy +#| msgid "Regenerate token" +msgid "generated" +msgstr "Regenerar token" + +#: apps/wrapped/models.py:45 +msgid "public" +msgstr "" + +#: apps/wrapped/models.py:53 +msgid "bde" +msgstr "" + +#: apps/wrapped/models.py:65 +msgid "data json" +msgstr "" + +#: apps/wrapped/models.py:66 +msgid "data in the wrapped and generated by the script generate_wrapped" +msgstr "" + +#: apps/wrapped/models.py:70 note_kfet/templates/base.html:114 +msgid "Wrapped" +msgstr "" + +#: apps/wrapped/models.py:71 +msgid "Wrappeds" +msgstr "" + +#: apps/wrapped/tables.py:40 +msgid "view the wrapped" +msgstr "" + +#: apps/wrapped/tables.py:55 +msgid "Click to make this wrapped private" +msgstr "" + +#: apps/wrapped/tables.py:56 +msgid "Click to make this wrapped public" +msgstr "" + +#: apps/wrapped/tables.py:67 +msgid "Share" +msgstr "" + +#: apps/wrapped/tables.py:73 +msgid "Click to copy the link in the press paper" +msgstr "" + +#: apps/wrapped/tables.py:81 +msgid "Copy link" +msgstr "" + +#: 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 del BDE de la ENS Paris-Saclay." + +#: apps/wrapped/templates/wrapped/1/wrapped_base.html:58 +#, fuzzy +#| msgid "The Note Kfet team." +msgid "The NoteKfet this year it's also" +msgstr "El equipo Note Kfet." + +#: apps/wrapped/templates/wrapped/1/wrapped_base.html:60 +#, fuzzy +#| msgid "transactions" +msgid " transactions" +msgstr "transacciones" + +#: apps/wrapped/templates/wrapped/1/wrapped_base.html:61 +msgid " parties" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_base.html:62 +#, fuzzy +#| msgid "entries" +msgid " Pot entries" +msgstr "entradas" + +#: apps/wrapped/templates/wrapped/1/wrapped_base.html:72 +msgid " old dickhead behind the bar" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:9 +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:9 +msgid "NoteKfet Wrapped" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:11 +msgid "Your best consumer:" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:13 +msgid "Your worst creditor:" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:16 +msgid "party·ies organised" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:17 +#, fuzzy +#| msgid "Add member" +msgid "distinct members" +msgstr "Añadir un miembro" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:26 +msgid "Infortunately, you doesn't have consumer this year" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_club.html:28 +msgid "Congratulations you are a real rat !" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:13 +msgid "You participate to the wei: " +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:13 +#, fuzzy +#| msgid "in the team" +msgid "in the" +msgstr "en el equipo" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:18 +msgid "pots !" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:27 +msgid "Your first conso of the year: " +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:28 +msgid "Your prefered consumtion category: " +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:41 +msgid ": it's the number of time your reload your note" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:44 +msgid "Your overall expenses: " +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:47 +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:60 +msgid "with" +msgstr "" + +#: apps/wrapped/templates/wrapped/1/wrapped_view_user.html:57 +msgid "Your expenses to BDE: " +msgstr "" + +#: apps/wrapped/templates/wrapped/wrapped_list.html:13 +msgid "My wrapped" +msgstr "" + +#: apps/wrapped/templates/wrapped/wrapped_list.html:22 +msgid "Public wrapped" +msgstr "" + +#: apps/wrapped/templates/wrapped/wrapped_list.html:33 +msgid "" +"Do not forget to ask permission to people who are in your wrapped before to " +"make them public" +msgstr "" + +#: apps/wrapped/templates/wrapped/wrapped_list.html:40 +msgid "Link copied" +msgstr "" + +#: apps/wrapped/templates/wrapped/wrapped_list.html:65 +msgid "Wrapped is private" +msgstr "" + +#: apps/wrapped/templates/wrapped/wrapped_list.html:66 +msgid "Wrapped is public" +msgstr "" + +#: apps/wrapped/views.py:28 +msgid "List of wrapped" +msgstr "" + +#: env/lib/python3.11/site-packages/django/contrib/messages/apps.py:15 +msgid "Messages" +msgstr "" + +#: env/lib/python3.11/site-packages/django/contrib/sitemaps/apps.py:8 +msgid "Site Maps" +msgstr "" + +#: env/lib/python3.11/site-packages/django/contrib/staticfiles/apps.py:9 +msgid "Static Files" +msgstr "" + +#: env/lib/python3.11/site-packages/django/contrib/syndication/apps.py:7 +#, fuzzy +#| msgid "Invitation" +msgid "Syndication" +msgstr "Invitación" + +#. Translators: String used to replace omitted page numbers in elided page +#. range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10]. +#: env/lib/python3.11/site-packages/django/core/paginator.py:30 +msgid "…" +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/paginator.py:50 +msgid "That page number is not an integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/paginator.py:52 +msgid "That page number is less than 1" +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/paginator.py:54 +#, fuzzy +#| msgid "There is no results." +msgid "That page contains no results" +msgstr "No hay resultado." + +#: env/lib/python3.11/site-packages/django/core/validators.py:22 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid value." +msgstr "invalidar" + +#: env/lib/python3.11/site-packages/django/core/validators.py:104 +#: env/lib/python3.11/site-packages/django/forms/fields.py:752 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid URL." +msgstr "invalidar" + +#: env/lib/python3.11/site-packages/django/core/validators.py:165 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid integer." +msgstr "invalidar" + +#: env/lib/python3.11/site-packages/django/core/validators.py:176 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid email address." +msgstr "invalidar" + +#. Translators: "letters" means latin letters: a-z and A-Z. +#: env/lib/python3.11/site-packages/django/core/validators.py:259 +msgid "" +"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:267 +msgid "" +"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or " +"hyphens." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:281 +#: env/lib/python3.11/site-packages/django/core/validators.py:289 +#: env/lib/python3.11/site-packages/django/core/validators.py:318 +#, fuzzy +#| msgid "This activity is not validated yet." +msgid "Enter a valid IPv4 address." +msgstr "Esta actividad no fue validada por ahora." + +#: env/lib/python3.11/site-packages/django/core/validators.py:298 +#: env/lib/python3.11/site-packages/django/core/validators.py:319 +#, fuzzy +#| msgid "This activity is not validated yet." +msgid "Enter a valid IPv6 address." +msgstr "Esta actividad no fue validada por ahora." + +#: env/lib/python3.11/site-packages/django/core/validators.py:310 +#: env/lib/python3.11/site-packages/django/core/validators.py:317 +#, fuzzy +#| msgid "This activity is not validated yet." +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "Esta actividad no fue validada por ahora." + +#: env/lib/python3.11/site-packages/django/core/validators.py:353 +msgid "Enter only digits separated by commas." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:359 +#, python-format +msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:394 +#, python-format +msgid "Ensure this value is less than or equal to %(limit_value)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:403 +#, python-format +msgid "Ensure this value is greater than or equal to %(limit_value)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:412 +#, python-format +msgid "Ensure this value is a multiple of step size %(limit_value)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:422 +#, python-format +msgid "" +"Ensure this value has at least %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at least %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:440 +#, python-format +msgid "" +"Ensure this value has at most %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at most %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:463 +#: env/lib/python3.11/site-packages/django/forms/fields.py:347 +#: env/lib/python3.11/site-packages/django/forms/fields.py:386 +#, fuzzy +#| msgid "phone number" +msgid "Enter a number." +msgstr "número de teléfono" + +#: env/lib/python3.11/site-packages/django/core/validators.py:465 +#, python-format +msgid "Ensure that there are no more than %(max)s digit in total." +msgid_plural "Ensure that there are no more than %(max)s digits in total." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:470 +#, python-format +msgid "Ensure that there are no more than %(max)s decimal place." +msgid_plural "Ensure that there are no more than %(max)s decimal places." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:475 +#, python-format +msgid "" +"Ensure that there are no more than %(max)s digit before the decimal point." +msgid_plural "" +"Ensure that there are no more than %(max)s digits before the decimal point." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:546 +#, python-format +msgid "" +"File extension “%(extension)s” is not allowed. Allowed extensions are: " +"%(allowed_extensions)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:607 +msgid "Null characters are not allowed." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/base.py:1425 +#, fuzzy, python-format +#| msgid "A template with this name already exist" +msgid "%(model_name)s with this %(field_labels)s already exists." +msgstr "Un plantilla de transacción con un nombre similar ya existe" + +#: env/lib/python3.11/site-packages/django/db/models/constraints.py:17 +#, python-format +msgid "Constraint “%(name)s” is violated." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:128 +#, fuzzy, python-format +#| msgid "This activity is not validated yet." +msgid "Value %(value)r is not a valid choice." +msgstr "Esta actividad no fue validada por ahora." + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:129 +#, fuzzy +#| msgid "This image cannot be loaded." +msgid "This field cannot be null." +msgstr "Esta imagen no puede ser cargada." + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:130 +#, fuzzy +#| msgid "This image cannot be loaded." +msgid "This field cannot be blank." +msgstr "Esta imagen no puede ser cargada." + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:131 +#, fuzzy, python-format +#| msgid "A template with this name already exist" +msgid "%(model_name)s with this %(field_label)s already exists." +msgstr "Un plantilla de transacción con un nombre similar ya existe" + +#. Translators: The 'lookup_type' is one of 'date', 'year' or +#. 'month'. Eg: "Title must be unique for pub_date year" +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:135 +#, python-format +msgid "" +"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:173 +#, python-format +msgid "Field of type: %(field_type)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1094 +#, python-format +msgid "“%(value)s” value must be either True or False." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1095 +#, python-format +msgid "“%(value)s” value must be either True, False, or None." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1097 +msgid "Boolean (Either True or False)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1147 +#, python-format +msgid "String (up to %(max_length)s)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1149 +msgid "String (unlimited)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1253 +msgid "Comma-separated integers" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1354 +#, python-format +msgid "" +"“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD " +"format." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1358 +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1493 +#, python-format +msgid "" +"“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " +"date." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1362 +msgid "Date (without time)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1489 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." +"uuuuuu]][TZ] format." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1497 +#, python-format +msgid "" +"“%(value)s” value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" +"[TZ]) but it is an invalid date/time." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1502 +msgid "Date (with time)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1626 +#, python-format +msgid "“%(value)s” value must be a decimal number." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1628 +#, fuzzy +#| msgid "phone number" +msgid "Decimal number" +msgstr "número de teléfono" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1789 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in [DD] [[HH:]MM:]ss[." +"uuuuuu] format." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1793 +#, fuzzy +#| msgid "action" +msgid "Duration" +msgstr "acción" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1845 +#, fuzzy +#| msgid "address" +msgid "Email address" +msgstr "dirección" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1870 +msgid "File path" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1948 +#, python-format +msgid "“%(value)s” value must be a float." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1950 +#, fuzzy +#| msgid "phone number" +msgid "Floating point number" +msgstr "número de teléfono" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1990 +#, python-format +msgid "“%(value)s” value must be an integer." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1992 +msgid "Integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2088 +msgid "Big (8 byte) integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2105 +msgid "Small integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2113 +#, fuzzy +#| msgid "IP Address" +msgid "IPv4 address" +msgstr "Dirección IP" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2144 +#, fuzzy +#| msgid "IP Address" +msgid "IP address" +msgstr "Dirección IP" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2237 +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2238 +#, python-format +msgid "“%(value)s” value must be either None, True or False." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2240 +msgid "Boolean (Either True, False or None)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2291 +msgid "Positive big integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2306 +msgid "Positive integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2321 +msgid "Positive small integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2337 +#, python-format +msgid "Slug (up to %(max_length)s)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2373 +msgid "Text" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2448 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " +"format." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2452 +#, python-format +msgid "" +"“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " +"invalid time." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2456 +msgid "Time" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2564 +msgid "URL" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2588 +msgid "Raw binary data" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2653 +#, fuzzy, python-format +#| msgid "This activity is not validated yet." +msgid "“%(value)s” is not a valid UUID." +msgstr "Esta actividad no fue validada por ahora." + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2655 +#, fuzzy +#| msgid "Invoice identifier" +msgid "Universally unique identifier" +msgstr "Numero de factura" + +#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:232 +msgid "File" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:393 +msgid "Image" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:26 +#, fuzzy +#| msgid "Object" +msgid "A JSON object" +msgstr "Asunto" + +#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:28 +#, fuzzy +#| msgid "This address must be valid." +msgid "Value must be valid JSON." +msgstr "Este correo tiene que ser valido." + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:919 +#, fuzzy, python-format +#| msgid "A template with this name already exist" +msgid "%(model)s instance with %(field)s %(value)r does not exist." +msgstr "Un plantilla de transacción con un nombre similar ya existe" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:921 +msgid "Foreign Key (type determined by related field)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1212 +msgid "One-to-one relationship" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1269 +#, python-format +msgid "%(from)s-%(to)s relationship" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1271 +#, python-format +msgid "%(from)s-%(to)s relationships" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1319 +msgid "Many-to-many relationship" +msgstr "" + +#. Translators: If found as last label character, these punctuation +#. characters will prevent the default label_suffix to be appended to the label +#: env/lib/python3.11/site-packages/django/forms/boundfield.py:184 +msgid ":?.!" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:298 +#, fuzzy +#| msgid "phone number" +msgid "Enter a whole number." +msgstr "número de teléfono" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:467 +#: env/lib/python3.11/site-packages/django/forms/fields.py:1241 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid date." +msgstr "invalidar" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:490 +#: env/lib/python3.11/site-packages/django/forms/fields.py:1242 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid time." +msgstr "invalidar" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:517 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid date/time." +msgstr "invalidar" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:551 +#, fuzzy +#| msgid "Email validation" +msgid "Enter a valid duration." +msgstr "Validación del correo electrónico" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:552 +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:621 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:622 +msgid "No file was submitted." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:623 +msgid "The submitted file is empty." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:625 +#, python-format +msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." +msgid_plural "" +"Ensure this filename has at most %(max)d characters (it has %(length)d)." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:630 +msgid "Please either submit a file or check the clear checkbox, not both." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:694 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:857 +#: env/lib/python3.11/site-packages/django/forms/fields.py:949 +#: env/lib/python3.11/site-packages/django/forms/models.py:1566 +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:951 +#: env/lib/python3.11/site-packages/django/forms/fields.py:1070 +#: env/lib/python3.11/site-packages/django/forms/models.py:1564 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a list of values." +msgstr "invalidar" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:1071 +#, fuzzy +#| msgid "phone number" +msgid "Enter a complete value." +msgstr "número de teléfono" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:1313 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid UUID." +msgstr "invalidar" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:1343 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid JSON." +msgstr "invalidar" + +#. Translators: This is the default suffix added to form field labels +#: env/lib/python3.11/site-packages/django/forms/forms.py:98 +msgid ":" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/forms.py:244 +#: env/lib/python3.11/site-packages/django/forms/forms.py:328 +#, python-format +msgid "(Hidden field %(name)s) %(error)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/formsets.py:63 +#, python-format +msgid "" +"ManagementForm data is missing or has been tampered with. Missing fields: " +"%(field_names)s. You may need to file a bug report if the issue persists." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/formsets.py:67 +#, python-format +msgid "Please submit at most %(num)d form." +msgid_plural "Please submit at most %(num)d forms." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/forms/formsets.py:72 +#, python-format +msgid "Please submit at least %(num)d form." +msgid_plural "Please submit at least %(num)d forms." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/forms/formsets.py:484 +#: env/lib/python3.11/site-packages/django/forms/formsets.py:491 +msgid "Order" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:886 +#, python-format +msgid "Please correct the duplicate data for %(field)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:891 +#, python-format +msgid "Please correct the duplicate data for %(field)s, which must be unique." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:898 +#, python-format +msgid "" +"Please correct the duplicate data for %(field_name)s which must be unique " +"for the %(lookup)s in %(date_field)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:907 +msgid "Please correct the duplicate values below." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:1338 +msgid "The inline value did not match the parent instance." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:1429 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:1568 +#, fuzzy, python-format +#| msgid "This activity is not validated yet." +msgid "“%(pk)s” is not a valid value." +msgstr "Esta actividad no fue validada por ahora." + +#: env/lib/python3.11/site-packages/django/forms/utils.py:226 +#, python-format +msgid "" +"%(datetime)s couldn’t be interpreted in time zone %(current_timezone)s; it " +"may be ambiguous or it may not exist." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/widgets.py:463 +msgid "Clear" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/widgets.py:464 +#, fuzzy +#| msgid "Current activity" +msgid "Currently" +msgstr "Actividad actual" + +#: env/lib/python3.11/site-packages/django/forms/widgets.py:465 +#, fuzzy +#| msgid "change" +msgid "Change" +msgstr "cambiar" + +#: env/lib/python3.11/site-packages/django/forms/widgets.py:794 +msgid "Unknown" +msgstr "" + +#. Translators: Please do not add spaces around commas. +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:874 +msgid "yes,no,maybe" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:904 +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:921 +#, python-format +msgid "%(size)d byte" +msgid_plural "%(size)d bytes" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:923 +#, python-format +msgid "%s KB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:925 +#, python-format +msgid "%s MB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:927 +#, python-format +msgid "%s GB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:929 +#, python-format +msgid "%s TB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:931 +#, python-format +msgid "%s PB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:73 +msgid "p.m." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:74 +msgid "a.m." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:79 +msgid "PM" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:80 +msgid "AM" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:152 +msgid "midnight" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:154 +msgid "noon" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:7 +msgid "Monday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:8 +msgid "Tuesday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:9 +msgid "Wednesday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:10 +msgid "Thursday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:11 +msgid "Friday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:12 +msgid "Saturday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:13 +msgid "Sunday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:16 +msgid "Mon" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:17 +msgid "Tue" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:18 +msgid "Wed" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:19 +msgid "Thu" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:20 +msgid "Fri" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:21 +msgid "Sat" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:22 +msgid "Sun" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:25 +msgid "January" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:26 +msgid "February" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:27 +#, fuzzy +#| msgid "Search WEI" +msgid "March" +msgstr "Buscar un WEI" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:28 +msgid "April" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:29 +msgid "May" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:30 +msgid "June" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:31 +msgid "July" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:32 +msgid "August" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:33 +#, fuzzy +#| msgid "member" +msgid "September" +msgstr "miembro" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:34 +msgid "October" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:35 +#, fuzzy +#| msgid "member" +msgid "November" +msgstr "miembro" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:36 +#, fuzzy +#| msgid "member" +msgid "December" +msgstr "miembro" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:39 +#, fuzzy +#| msgid "add" +msgid "jan" +msgstr "añadir" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:40 +#, fuzzy +#| msgid "fee" +msgid "feb" +msgstr "pago" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:41 +msgid "mar" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:42 +msgid "apr" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:43 +msgid "may" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:44 +#, fuzzy +#| msgid "add" +msgid "jun" +msgstr "añadir" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:45 +msgid "jul" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:46 +msgid "aug" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:47 +msgid "sep" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:48 +#, fuzzy +#| msgid "product" +msgid "oct" +msgstr "producto" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:49 +msgid "nov" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:50 +msgid "dec" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:53 +msgctxt "abbrev. month" +msgid "Jan." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:54 +msgctxt "abbrev. month" +msgid "Feb." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:55 +#, fuzzy +#| msgid "Search WEI" +msgctxt "abbrev. month" +msgid "March" +msgstr "Buscar un WEI" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:56 +msgctxt "abbrev. month" +msgid "April" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:57 +msgctxt "abbrev. month" +msgid "May" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:58 +msgctxt "abbrev. month" +msgid "June" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:59 +msgctxt "abbrev. month" +msgid "July" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:60 +msgctxt "abbrev. month" +msgid "Aug." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:61 +msgctxt "abbrev. month" +msgid "Sept." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:62 +msgctxt "abbrev. month" +msgid "Oct." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:63 +msgctxt "abbrev. month" +msgid "Nov." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:64 +msgctxt "abbrev. month" +msgid "Dec." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:67 +msgctxt "alt. month" +msgid "January" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:68 +msgctxt "alt. month" +msgid "February" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:69 +#, fuzzy +#| msgid "Search WEI" +msgctxt "alt. month" +msgid "March" +msgstr "Buscar un WEI" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:70 +msgctxt "alt. month" +msgid "April" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:71 +msgctxt "alt. month" +msgid "May" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:72 +msgctxt "alt. month" +msgid "June" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:73 +msgctxt "alt. month" +msgid "July" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:74 +msgctxt "alt. month" +msgid "August" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:75 +#, fuzzy +#| msgid "member" +msgctxt "alt. month" +msgid "September" +msgstr "miembro" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:76 +msgctxt "alt. month" +msgid "October" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:77 +#, fuzzy +#| msgid "member" +msgctxt "alt. month" +msgid "November" +msgstr "miembro" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:78 +#, fuzzy +#| msgid "member" +msgctxt "alt. month" +msgid "December" +msgstr "miembro" + +#: env/lib/python3.11/site-packages/django/utils/ipv6.py:20 +#, fuzzy +#| msgid "This activity is not validated yet." +msgid "This is not a valid IPv6 address." +msgstr "Esta actividad no fue validada por ahora." + +#: env/lib/python3.11/site-packages/django/utils/text.py:138 +#, python-format +msgctxt "String to return when truncating text" +msgid "%(truncated_text)s…" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/text.py:323 +msgid "or" +msgstr "" + +#. Translators: This string is used as a separator between list elements +#: env/lib/python3.11/site-packages/django/utils/text.py:342 +#: env/lib/python3.11/site-packages/django/utils/timesince.py:135 +msgid ", " +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:8 +#, fuzzy, python-format +#| msgid "year" +msgid "%(num)d year" +msgid_plural "%(num)d years" +msgstr[0] "año" +msgstr[1] "año" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:9 +#, python-format +msgid "%(num)d month" +msgid_plural "%(num)d months" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:10 +#, python-format +msgid "%(num)d week" +msgid_plural "%(num)d weeks" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:11 +#, python-format +msgid "%(num)d day" +msgid_plural "%(num)d days" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:12 +#, python-format +msgid "%(num)d hour" +msgid_plural "%(num)d hours" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:13 +#, python-format +msgid "%(num)d minute" +msgid_plural "%(num)d minutes" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:111 +msgid "Forbidden" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:112 +msgid "CSRF verification failed. Request aborted." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:116 +msgid "" +"You are seeing this message because this HTTPS site requires a “Referer " +"header” to be sent by your web browser, but none was sent. This header is " +"required for security reasons, to ensure that your browser is not being " +"hijacked by third parties." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:122 +msgid "" +"If you have configured your browser to disable “Referer” headers, please re-" +"enable them, at least for this site, or for HTTPS connections, or for “same-" +"origin” requests." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:127 +msgid "" +"If you are using the tag or " +"including the “Referrer-Policy: no-referrer” header, please remove them. The " +"CSRF protection requires the “Referer” header to do strict referer checking. " +"If you’re concerned about privacy, use alternatives like for links to third-party sites." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:136 +msgid "" +"You are seeing this message because this site requires a CSRF cookie when " +"submitting forms. This cookie is required for security reasons, to ensure " +"that your browser is not being hijacked by third parties." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:142 +msgid "" +"If you have configured your browser to disable cookies, please re-enable " +"them, at least for this site, or for “same-origin” requests." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:148 +msgid "More information is available with DEBUG=True." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:44 +#, fuzzy +#| msgid "No reason specified" +msgid "No year specified" +msgstr "Ningún motivo dado" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:64 +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:115 +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:214 +msgid "Date out of range" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:94 +#, fuzzy +#| msgid "No reason specified" +msgid "No month specified" +msgstr "Ningún motivo dado" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:147 +#, fuzzy +#| msgid "No reason specified" +msgid "No day specified" +msgstr "Ningún motivo dado" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:194 +#, fuzzy +#| msgid "No reason specified" +msgid "No week specified" +msgstr "Ningún motivo dado" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:349 +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:380 +#, python-format +msgid "No %(verbose_name_plural)s available" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:652 +#, python-format +msgid "" +"Future %(verbose_name_plural)s not available because %(class_name)s." +"allow_future is False." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:692 +#, python-format +msgid "Invalid date string “%(datestr)s” given format “%(format)s”" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/detail.py:56 +#, python-format +msgid "No %(verbose_name)s found matching the query" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/list.py:70 +msgid "Page is not “last”, nor can it be converted to an int." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/list.py:77 +#, python-format +msgid "Invalid page (%(page_number)s): %(message)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/list.py:169 +#, python-format +msgid "Empty list and “%(class_name)s.allow_empty” is False." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/static.py:38 +msgid "Directory indexes are not allowed here." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/static.py:40 +#, python-format +msgid "“%(path)s” does not exist" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/static.py:79 +#, python-format +msgid "Index of %(directory)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:7 +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:220 +msgid "The install worked successfully! Congratulations!" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:206 +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:221 +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not " +"configured any URLs." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:229 +msgid "Django Documentation" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:230 +msgid "Topics, references, & how-to’s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:238 +msgid "Tutorial: A Polling App" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:239 +msgid "Get started with Django" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:247 +msgid "Django Community" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:248 +msgid "Connect, get help, or contribute" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:69 +#, fuzzy +#| msgid "Client secret" +msgid "Confidential" +msgstr "Secreto cliente" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:70 +msgid "Public" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:79 +#, fuzzy +#| msgid "Authorization:" +msgid "Authorization code" +msgstr "Autorizaciones :" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:80 +msgid "Implicit" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:81 +#, fuzzy +#| msgid "Reset my password" +msgid "Resource owner password-based" +msgstr "Reiniciar mi contraseña" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:82 +#, fuzzy +#| msgid "Client secret" +msgid "Client credentials" +msgstr "Secreto cliente" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:83 +msgid "OpenID connect hybrid" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:90 +msgid "No OIDC support" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:91 +msgid "RSA with SHA-2 256" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:92 +msgid "HMAC with SHA-2 256" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:107 +msgid "Allowed URIs list, space separated" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:111 +msgid "Allowed Post Logout URIs list, space separated" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:120 +msgid "Hashed on Save. Copy it now if this is a new secret." +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:190 +#, python-brace-format +msgid "Unauthorized redirect scheme: {scheme}" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:194 +#, python-brace-format +msgid "redirect_uris cannot be empty with grant_type {grant_type}" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:200 +msgid "You must set OIDC_RSA_PRIVATE_KEY to use RSA algorithm" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:209 +msgid "You cannot use HS256 with public grants or clients" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:211 +#, fuzzy +#| msgid "This address must be valid." +msgid "The access token is invalid." +msgstr "Este correo tiene que ser valido." + +#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:218 +#, fuzzy +#| msgid "This address must be valid." +msgid "The access token has expired." +msgstr "Este correo tiene que ser valido." + +#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:225 +#, fuzzy +#| msgid "The user does not have enough money." +msgid "The access token is valid but does not have enough scope." +msgstr "El usuario no tiene suficientemente dinero." + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:6 +#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8 +msgid "Are you sure to delete the application" +msgstr "" +"¿ Usted está seguro de querer suprimir esta aplicación ? Este acto es " +"definitivo" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:12 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:29 +#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 +#: note_kfet/templates/oauth2_provider/authorize.html:28 +msgid "Cancel" +msgstr "Anular" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:10 +#: note_kfet/templates/oauth2_provider/application_detail.html:11 +msgid "Client id" +msgstr "ID cliente" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:15 +#: note_kfet/templates/oauth2_provider/application_detail.html:14 +msgid "Client secret" +msgstr "Secreto cliente" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:20 +#: note_kfet/templates/oauth2_provider/application_detail.html:17 +msgid "Client type" +msgstr "Tipo de cliente" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:25 +#: note_kfet/templates/oauth2_provider/application_detail.html:20 +msgid "Authorization Grant Type" +msgstr "Tipo de autorización" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:30 +#: note_kfet/templates/oauth2_provider/application_detail.html:23 +msgid "Redirect Uris" +msgstr "URL de redirecto" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:36 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:35 +#: note_kfet/templates/oauth2_provider/application_detail.html:37 +#: note_kfet/templates/oauth2_provider/application_form.html:23 +msgid "Go Back" +msgstr "Volver" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:9 +#: note_kfet/templates/oauth2_provider/application_form.html:12 +msgid "Edit application" +msgstr "Editar la aplicación" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:37 +msgid "Save" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:6 +#: note_kfet/templates/oauth2_provider/application_list.html:7 +msgid "Your applications" +msgstr "Sus aplicaciones" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:14 +#: note_kfet/templates/oauth2_provider/application_list.html:30 +msgid "New Application" +msgstr "Nueva aplicación" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_registration_form.html:5 +#: note_kfet/templates/oauth2_provider/application_registration_form.html:5 +msgid "Register a new application" +msgstr "Registrar una nueva aplicación" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:8 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:30 +#: note_kfet/templates/oauth2_provider/authorize.html:9 +#: note_kfet/templates/oauth2_provider/authorize.html:29 +msgid "Authorize" +msgstr "Autorizar" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:17 +#, fuzzy +#| msgid "Application requires following permissions:" +msgid "Application requires the following permissions" +msgstr "La aplicación necesita los derechos siguientes :" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:6 +#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9 +msgid "Are you sure you want to delete this token?" +msgstr "¿ Usted está seguro de querer suprimir este token ?" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:6 +#: note_kfet/templates/oauth2_provider/authorized-tokens.html:7 +msgid "Tokens" +msgstr "Tokens" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:11 +msgid "revoke" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:19 +#: note_kfet/templates/oauth2_provider/authorized-tokens.html:22 +msgid "There are no authorized tokens yet." +msgstr "Por ahora no hay ningún token autorizado." + +#: note_kfet/settings/base.py:177 msgid "German" msgstr "Alemán" -#: note_kfet/settings/base.py:174 +#: note_kfet/settings/base.py:178 msgid "English" msgstr "Ingles" -#: note_kfet/settings/base.py:175 +#: note_kfet/settings/base.py:179 msgid "Spanish" msgstr "Español" -#: note_kfet/settings/base.py:176 +#: note_kfet/settings/base.py:180 msgid "French" msgstr "Francés" @@ -3605,14 +5471,6 @@ msgstr "" msgid "Reset" msgstr "Reiniciar" -#: note_kfet/templates/base.html:14 -msgid "The ENS Paris-Saclay BDE note." -msgstr "La note del BDE de la ENS Paris-Saclay." - -#: note_kfet/templates/base.html:72 -msgid "Food" -msgstr "" - #: note_kfet/templates/base.html:84 msgid "Users" msgstr "Usuarios" @@ -3621,26 +5479,26 @@ msgstr "Usuarios" msgid "Clubs" msgstr "Clubs" -#: note_kfet/templates/base.html:119 +#: note_kfet/templates/base.html:125 msgid "Admin" msgstr "" -#: note_kfet/templates/base.html:133 +#: note_kfet/templates/base.html:139 msgid "My account" msgstr "Mi cuenta" -#: note_kfet/templates/base.html:136 +#: note_kfet/templates/base.html:142 msgid "Log out" msgstr "Desconectarse" -#: 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 "Registrar" -#: 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 @@ -3648,7 +5506,7 @@ msgstr "Registrar" msgid "Log in" msgstr "Conectarse" -#: 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." @@ -3656,7 +5514,7 @@ msgstr "" "Usted ya no está miembro del BDE. Por favor renueva su afiliación si quiere " "usar 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." @@ -3664,7 +5522,7 @@ msgstr "" "Su correo electrónico no fue validado. Por favor mire en sus correos y haga " "clic en el enlace de validación." -#: 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 " @@ -3677,19 +5535,19 @@ msgstr "" "pagados. El proceso de convalidación puede durar unos días. Por favor " "comprueba que fue hasta el final de la creación de la cuenta." -#: note_kfet/templates/base.html:200 +#: note_kfet/templates/base.html:206 msgid "Contact us" msgstr "Contactarnos" -#: note_kfet/templates/base.html:202 +#: note_kfet/templates/base.html:208 msgid "Technical Support" msgstr "Soporte técnico" -#: note_kfet/templates/base.html:204 +#: note_kfet/templates/base.html:210 msgid "Charte Info (FR)" msgstr "" -#: note_kfet/templates/base.html:206 +#: note_kfet/templates/base.html:212 msgid "FAQ (FR)" msgstr "FAQ (FR)" @@ -3701,37 +5559,6 @@ msgstr "Buscar con atributo, como el nombre..." msgid "There is no results." msgstr "No hay resultado." -#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8 -msgid "Are you sure to delete the application" -msgstr "" -"¿ Usted está seguro de querer suprimir esta aplicación ? Este acto es " -"definitivo" - -#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 -#: note_kfet/templates/oauth2_provider/authorize.html:28 -msgid "Cancel" -msgstr "Anular" - -#: note_kfet/templates/oauth2_provider/application_detail.html:11 -msgid "Client id" -msgstr "ID cliente" - -#: note_kfet/templates/oauth2_provider/application_detail.html:14 -msgid "Client secret" -msgstr "Secreto cliente" - -#: note_kfet/templates/oauth2_provider/application_detail.html:17 -msgid "Client type" -msgstr "Tipo de cliente" - -#: note_kfet/templates/oauth2_provider/application_detail.html:20 -msgid "Authorization Grant Type" -msgstr "Tipo de autorización" - -#: note_kfet/templates/oauth2_provider/application_detail.html:23 -msgid "Redirect Uris" -msgstr "URL de redirecto" - #: note_kfet/templates/oauth2_provider/application_detail.html:29 #, python-format msgid "" @@ -3740,19 +5567,6 @@ msgid "" "that you want to grant for your application." msgstr "" -#: note_kfet/templates/oauth2_provider/application_detail.html:37 -#: note_kfet/templates/oauth2_provider/application_form.html:23 -msgid "Go Back" -msgstr "Volver" - -#: note_kfet/templates/oauth2_provider/application_form.html:12 -msgid "Edit application" -msgstr "Editar la aplicación" - -#: note_kfet/templates/oauth2_provider/application_list.html:7 -msgid "Your applications" -msgstr "Sus aplicaciones" - #: note_kfet/templates/oauth2_provider/application_list.html:11 msgid "" "You can find on this page the list of the applications that you already " @@ -3760,23 +5574,10 @@ msgid "" msgstr "" "Puede encontrar en esta pagina la lista de la aplicaciones que ya registró." -#: note_kfet/templates/oauth2_provider/application_list.html:30 -msgid "New Application" -msgstr "Nueva aplicación" - #: note_kfet/templates/oauth2_provider/application_list.html:31 msgid "Authorized Tokens" msgstr "Tokens autorizados" -#: note_kfet/templates/oauth2_provider/application_registration_form.html:5 -msgid "Register a new application" -msgstr "Registrar una nueva aplicación" - -#: note_kfet/templates/oauth2_provider/authorize.html:9 -#: note_kfet/templates/oauth2_provider/authorize.html:29 -msgid "Authorize" -msgstr "Autorizar" - #: note_kfet/templates/oauth2_provider/authorize.html:14 msgid "Application requires following permissions:" msgstr "La aplicación necesita los derechos siguientes :" @@ -3794,18 +5595,6 @@ msgstr "Éxito" msgid "Please return to your application and enter this code:" msgstr "Volver a su aplicación y entrar este código :" -#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9 -msgid "Are you sure you want to delete this token?" -msgstr "¿ Usted está seguro de querer suprimir este token ?" - -#: note_kfet/templates/oauth2_provider/authorized-tokens.html:7 -msgid "Tokens" -msgstr "Tokens" - -#: note_kfet/templates/oauth2_provider/authorized-tokens.html:22 -msgid "There are no authorized tokens yet." -msgstr "Por ahora no hay ningún token autorizado." - #: note_kfet/templates/registration/logged_out.html:13 msgid "Thanks for spending some quality time with the Web site today." msgstr "Gracias por usar la Note Kfet." @@ -3915,317 +5704,55 @@ msgstr "" "enlace que recibió." #, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid color." -#~ msgstr "invalidar" +#~| msgid "active" +#~ msgid "is active" +#~ msgstr "activo" #, fuzzy #~| msgid "invalidate" -#~ msgid "Enter a valid value." +#~ msgid "Arrival date" #~ msgstr "invalidar" #, fuzzy -#~| msgid "Invitation" -#~ msgid "Syndication" -#~ msgstr "Invitación" +#~| msgid "active" +#~ msgid "Active" +#~ msgstr "activo" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "number" +#~ msgstr "número de teléfono" + +#, fuzzy +#~| msgid "Profile detail" +#~ msgid "View details" +#~ msgstr "Detalles del usuario" + +#, fuzzy +#~| msgid "created at" +#~ msgid "Creation date" +#~ msgstr "creada el" #, fuzzy #~| msgid "There is no results." -#~ msgid "That page contains no results" +#~ msgid "There is no meal." #~ msgstr "No hay resultado." #, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid URL." -#~ msgstr "invalidar" +#~| msgid "This credit is already validated." +#~ msgid "The product is already prepared" +#~ msgstr "Este crédito ya fue validado." + +#, fuzzy +#~| msgid "Update team" +#~ msgid "Update a meal" +#~ msgstr "Modificar el equipo" #, fuzzy #~| msgid "invalidate" -#~ msgid "Enter a valid integer." +#~ msgid "Enter a valid color." #~ msgstr "invalidar" -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid email address." -#~ msgstr "invalidar" - -#, fuzzy -#~| msgid "This activity is not validated yet." -#~ msgid "Enter a valid IPv4 address." -#~ msgstr "Esta actividad no fue validada por ahora." - -#, fuzzy -#~| msgid "This activity is not validated yet." -#~ msgid "Enter a valid IPv6 address." -#~ msgstr "Esta actividad no fue validada por ahora." - -#, fuzzy -#~| msgid "This activity is not validated yet." -#~ msgid "Enter a valid IPv4 or IPv6 address." -#~ msgstr "Esta actividad no fue validada por ahora." - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Enter a number." -#~ msgstr "número de teléfono" - -#, fuzzy -#~| msgid "add" -#~ msgid "and" -#~ msgstr "añadir" - -#, fuzzy, python-format -#~| msgid "A template with this name already exist" -#~ msgid "%(model_name)s with this %(field_labels)s already exists." -#~ msgstr "Un plantilla de transacción con un nombre similar ya existe" - -#, fuzzy -#~| msgid "This image cannot be loaded." -#~ msgid "This field cannot be null." -#~ msgstr "Esta imagen no puede ser cargada." - -#, fuzzy -#~| msgid "This image cannot be loaded." -#~ msgid "This field cannot be blank." -#~ msgstr "Esta imagen no puede ser cargada." - -#, fuzzy, python-format -#~| msgid "A template with this name already exist" -#~ msgid "%(model_name)s with this %(field_label)s already exists." -#~ msgstr "Un plantilla de transacción con un nombre similar ya existe" - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Decimal number" -#~ msgstr "número de teléfono" - -#, fuzzy -#~| msgid "action" -#~ msgid "Duration" -#~ msgstr "acción" - -#, fuzzy -#~| msgid "address" -#~ msgid "Email address" -#~ msgstr "dirección" - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Floating point number" -#~ msgstr "número de teléfono" - -#, fuzzy -#~| msgid "IP Address" -#~ msgid "IPv4 address" -#~ msgstr "Dirección IP" - -#, fuzzy -#~| msgid "IP Address" -#~ msgid "IP address" -#~ msgstr "Dirección IP" - -#, fuzzy -#~| msgid "Invoice identifier" -#~ msgid "Universally unique identifier" -#~ msgstr "Numero de factura" - -#, fuzzy, python-format -#~| msgid "A template with this name already exist" -#~ msgid "%(model)s instance with %(field)s %(value)r does not exist." -#~ msgstr "Un plantilla de transacción con un nombre similar ya existe" - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Enter a whole number." -#~ msgstr "número de teléfono" - -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid date." -#~ msgstr "invalidar" - -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid time." -#~ msgstr "invalidar" - -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid date/time." -#~ msgstr "invalidar" - -#, fuzzy -#~| msgid "Email validation" -#~ msgid "Enter a valid duration." -#~ msgstr "Validación del correo electrónico" - -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a list of values." -#~ msgstr "invalidar" - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Enter a complete value." -#~ msgstr "número de teléfono" - -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid UUID." -#~ msgstr "invalidar" - -#, fuzzy, python-format -#~| msgid "This activity is not validated yet." -#~ msgid "\"%(pk)s\" is not a valid value." -#~ msgstr "Esta actividad no fue validada por ahora." - -#, fuzzy -#~| msgid "Current activity" -#~ msgid "Currently" -#~ msgstr "Actividad actual" - -#, fuzzy -#~| msgid "change" -#~ msgid "Change" -#~ msgstr "cambiar" - -#, fuzzy -#~| msgid "Search WEI" -#~ msgid "March" -#~ msgstr "Buscar un WEI" - -#, fuzzy -#~| msgid "member" -#~ msgid "September" -#~ msgstr "miembro" - -#, fuzzy -#~| msgid "member" -#~ msgid "November" -#~ msgstr "miembro" - -#, fuzzy -#~| msgid "member" -#~ msgid "December" -#~ msgstr "miembro" - -#, fuzzy -#~| msgid "add" -#~ msgid "jan" -#~ msgstr "añadir" - -#, fuzzy -#~| msgid "fee" -#~ msgid "feb" -#~ msgstr "pago" - -#, fuzzy -#~| msgid "product" -#~ msgid "oct" -#~ msgstr "producto" - -#, fuzzy -#~| msgid "Search WEI" -#~ msgctxt "abbrev. month" -#~ msgid "March" -#~ msgstr "Buscar un WEI" - -#, fuzzy -#~| msgid "Search WEI" -#~ msgctxt "alt. month" -#~ msgid "March" -#~ msgstr "Buscar un WEI" - -#, fuzzy -#~| msgid "member" -#~ msgctxt "alt. month" -#~ msgid "September" -#~ msgstr "miembro" - -#, fuzzy -#~| msgid "member" -#~ msgctxt "alt. month" -#~ msgid "November" -#~ msgstr "miembro" - -#, fuzzy -#~| msgid "member" -#~ msgctxt "alt. month" -#~ msgid "December" -#~ msgstr "miembro" - -#, fuzzy -#~| msgid "This activity is not validated yet." -#~ msgid "This is not a valid IPv6 address." -#~ msgstr "Esta actividad no fue validada por ahora." - -#, fuzzy, python-format -#~| msgid "year" -#~ msgid "%d year" -#~ msgid_plural "%d years" -#~ msgstr[0] "año" -#~ msgstr[1] "año" - -#, fuzzy -#~| msgid "No reason specified" -#~ msgid "No year specified" -#~ msgstr "Ningún motivo dado" - -#, fuzzy -#~| msgid "No reason specified" -#~ msgid "No month specified" -#~ msgstr "Ningún motivo dado" - -#, fuzzy -#~| msgid "No reason specified" -#~ msgid "No day specified" -#~ msgstr "Ningún motivo dado" - -#, fuzzy -#~| msgid "No reason specified" -#~ msgid "No week specified" -#~ msgstr "Ningún motivo dado" - -#, fuzzy -#~| msgid "Client secret" -#~ msgid "Confidential" -#~ msgstr "Secreto cliente" - -#, fuzzy -#~| msgid "Authorization:" -#~ msgid "Authorization code" -#~ msgstr "Autorizaciones :" - -#, fuzzy -#~| msgid "Reset my password" -#~ msgid "Resource owner password-based" -#~ msgstr "Reiniciar mi contraseña" - -#, fuzzy -#~| msgid "Client secret" -#~ msgid "Client credentials" -#~ msgstr "Secreto cliente" - -#, fuzzy -#~| msgid "This address must be valid." -#~ msgid "The access token is invalid." -#~ msgstr "Este correo tiene que ser valido." - -#, fuzzy -#~| msgid "This address must be valid." -#~ msgid "The access token has expired." -#~ msgstr "Este correo tiene que ser valido." - -#, fuzzy -#~| msgid "The user does not have enough money." -#~ msgid "The access token is valid but does not have enough scope." -#~ msgstr "El usuario no tiene suficientemente dinero." - -#, fuzzy -#~| msgid "Application requires following permissions:" -#~ msgid "Application requires following permissions" -#~ msgstr "La aplicación necesita los derechos siguientes :" - #, fuzzy #~| msgid "WEI registration" #~ msgid "In preparation" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 23ba06dd..03a4ca7b 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: 2025-04-30 11:44+0200\n" +"POT-Creation-Date: 2025-05-27 16:46+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n" "Last-Translator: bleizi \n" "Language-Team: French \n" @@ -291,14 +291,14 @@ msgstr "Type" #: 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:110 +#: apps/wei/forms/registration.py:107 msgid "Last name" msgstr "Nom de famille" #: 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:115 +#: apps/wei/forms/registration.py:112 msgid "First name" msgstr "Prénom" @@ -317,6 +317,10 @@ msgstr "Solde du compte" #: 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 +#: env/lib/python3.11/site-packages/django/forms/formsets.py:499 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:13 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:38 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:7 #: 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 @@ -341,6 +345,18 @@ msgstr "Liste des invité·e·s" msgid "Guest deleted" msgstr "Invité·e supprimé·e" +#: apps/activity/templates/activity/activity_detail.html:99 +#, fuzzy +#| msgid "Are you sure you want to delete this token?" +msgid "Are you sure you want to delete this activity?" +msgstr "Êtes-vous sûr⋅e de vouloir supprimer ce jeton ?" + +#: apps/activity/templates/activity/activity_detail.html:110 +#, fuzzy +#| msgid "Activity detail" +msgid "Activity deleted" +msgstr "Détails de l'activité" + #: apps/activity/templates/activity/activity_entry.html:14 #: apps/note/models/transactions.py:261 #: apps/note/templates/note/transaction_form.html:17 @@ -392,7 +408,7 @@ msgstr "Entrée effectuée !" #: apps/treasury/forms.py:89 apps/treasury/forms.py:143 #: apps/treasury/templates/treasury/invoice_form.html:74 #: apps/wei/templates/wei/bus_form.html:17 -#: apps/wei/templates/wei/busteam_form.html:17 +#: apps/wei/templates/wei/busteam_form.html:18 #: apps/wei/templates/wei/weiclub_form.html:17 #: apps/wei/templates/wei/weiregistration_form.html:18 msgid "Submit" @@ -448,6 +464,13 @@ msgid "edit" msgstr "modifier" #: apps/activity/templates/activity/includes/activity_info.html:74 +#: 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" +msgstr "supprimer" + +#: apps/activity/templates/activity/includes/activity_info.html:77 msgid "Invite" msgstr "Inviter" @@ -467,25 +490,40 @@ msgstr "Détails de l'activité" msgid "Update activity" msgstr "Modifier l'activité" -#: apps/activity/views.py:178 +#: apps/activity/views.py:177 +#, fuzzy +#| msgid "" +#| "You are not allowed to display the entry interface for this activity." +msgid "You are not allowed to delete this activity." +msgstr "" +"Vous n'êtes pas autorisé·e à afficher l'interface des entrées pour cette " +"activité." + +#: apps/activity/views.py:180 +#, fuzzy +#| msgid "This activity is closed." +msgid "This activity is valid." +msgstr "Cette activité est fermée." + +#: apps/activity/views.py:206 msgid "Invite guest to the activity \"{}\"" msgstr "Invitation pour l'activité « {} »" -#: apps/activity/views.py:218 +#: apps/activity/views.py:246 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:221 +#: apps/activity/views.py:249 msgid "This activity does not support activity entries." msgstr "Cette activité ne requiert pas d'entrées." -#: apps/activity/views.py:224 +#: apps/activity/views.py:252 msgid "This activity is closed." msgstr "Cette activité est fermée." -#: apps/activity/views.py:329 +#: apps/activity/views.py:357 msgid "Entry for activity \"{}\"" msgstr "Entrées pour l'activité « {} »" @@ -748,6 +786,8 @@ msgid "weeks" msgstr "semaines" #: apps/food/utils.py:53 +#: env/lib/python3.11/site-packages/django/db/models/base.py:1423 +#: env/lib/python3.11/site-packages/django/forms/models.py:893 msgid "and" msgstr "et" @@ -759,41 +799,43 @@ msgstr "Ajouter un nouveau QR-code" msgid "Add an aliment" msgstr "Ajouter un nouvel aliment" -#: apps/food/views.py:226 +#: apps/food/views.py:235 msgid "Add a meal" msgstr "Ajouter un plat" -#: apps/food/views.py:262 +#: apps/food/views.py:275 msgid "Manage ingredients of:" msgstr "Gestion des ingrédienrs de :" -#: apps/food/views.py:276 apps/food/views.py:284 +#: apps/food/views.py:289 apps/food/views.py:297 #, python-brace-format msgid "Fully used in {meal}" msgstr "Aliment entièrement utilisé dans : {meal}" -#: apps/food/views.py:323 +#: apps/food/views.py:344 msgid "Add the ingredient:" msgstr "Ajouter l'ingrédient" -#: apps/food/views.py:349 +#: apps/food/views.py:370 #, python-brace-format msgid "Food fully used in : {meal.name}" msgstr "Aliment entièrement utilisé dans : {meal.name}" -#: apps/food/views.py:368 +#: apps/food/views.py:389 msgid "Update an aliment" msgstr "Modifier un aliment" -#: apps/food/views.py:416 +#: apps/food/views.py:437 msgid "Details of:" msgstr "Détails de :" -#: apps/food/views.py:426 apps/treasury/tables.py:149 +#: apps/food/views.py:447 apps/treasury/tables.py:149 +#: env/lib/python3.11/site-packages/django/forms/widgets.py:795 msgid "Yes" msgstr "Oui" -#: apps/food/views.py:428 apps/member/models.py:99 apps/treasury/tables.py:149 +#: apps/food/views.py:449 apps/member/models.py:99 apps/treasury/tables.py:149 +#: env/lib/python3.11/site-packages/django/forms/widgets.py:796 msgid "No" msgstr "Non" @@ -825,12 +867,6 @@ msgstr "nouvelles données" msgid "create" msgstr "créer" -#: 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" -msgstr "supprimer" - #: apps/logs/models.py:68 msgid "action" msgstr "action" @@ -935,12 +971,12 @@ 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:185 apps/registration/forms.py:78 -#: apps/wei/forms/registration.py:97 +#: apps/wei/forms/registration.py:94 msgid "Credit type" msgstr "Type de rechargement" #: apps/member/forms.py:186 apps/registration/forms.py:79 -#: apps/wei/forms/registration.py:98 +#: apps/wei/forms/registration.py:95 msgid "No credit" msgstr "Pas de rechargement" @@ -949,13 +985,13 @@ 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:192 apps/registration/forms.py:84 -#: apps/wei/forms/registration.py:103 +#: apps/wei/forms/registration.py:100 msgid "Credit amount" msgstr "Montant à créditer" #: 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:120 +#: apps/wei/forms/registration.py:117 msgid "Bank" msgstr "Banque" @@ -1597,7 +1633,7 @@ msgstr "Modifier le club" msgid "Add new member to the club" msgstr "Ajouter un·e nouvelleau membre au club" -#: apps/member/views.py:750 apps/wei/views.py:991 +#: apps/member/views.py:750 apps/wei/views.py:1040 msgid "" "This user don't have enough money to join this club, and can't have a " "negative balance." @@ -1934,8 +1970,9 @@ 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:996 -#: apps/wei/views.py:1000 +#: apps/note/models/transactions.py:363 apps/wei/views.py:1045 +#: apps/wei/views.py:1049 +#: env/lib/python3.11/site-packages/django/forms/fields.py:91 msgid "This field is required." msgstr "Ce champ est requis." @@ -1972,7 +2009,8 @@ msgstr "Ajouter" #: apps/wei/templates/wei/base.html:89 #: apps/wei/templates/wei/bus_detail.html:20 #: apps/wei/templates/wei/busteam_detail.html:20 -#: apps/wei/templates/wei/busteam_detail.html:40 +#: apps/wei/templates/wei/busteam_detail.html:42 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:37 #: note_kfet/templates/oauth2_provider/application_detail.html:38 msgid "Edit" msgstr "Éditer" @@ -2315,16 +2353,19 @@ msgid "Available scopes" msgstr "Scopes disponibles" #: apps/permission/templates/permission/scopes.html:42 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:24 msgid "No applications defined" msgstr "Pas d'application définie" #: apps/permission/templates/permission/scopes.html:43 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:25 msgid "Click here" msgstr "Cliquez ici" #: apps/permission/templates/permission/scopes.html:43 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:25 msgid "if you want to register a new one" msgstr "si vous voulez en enregistrer une nouvelle" @@ -2742,7 +2783,7 @@ msgid "Transaction count" msgstr "Nombre de transactions" #: apps/treasury/tables.py:71 apps/treasury/tables.py:73 -#: apps/wrapped/tables.py:42 +#: apps/wei/templates/wei/busteam_detail.html:22 apps/wrapped/tables.py:42 msgid "View" msgstr "Voir" @@ -2978,12 +3019,12 @@ msgstr "" "L'utilisateur·rice sélectionné·e n'est pas validé·e. Merci de d'abord " "valider son compte" -#: apps/wei/forms/registration.py:60 apps/wei/models.py:126 +#: apps/wei/forms/registration.py:62 apps/wei/models.py:126 #: apps/wei/models.py:324 msgid "bus" msgstr "bus" -#: apps/wei/forms/registration.py:61 +#: apps/wei/forms/registration.py:63 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." @@ -2992,11 +3033,11 @@ msgstr "" "vous attribuer un bus et une équipe, en particulier si vous êtes un·e " "électron libre." -#: apps/wei/forms/registration.py:68 +#: apps/wei/forms/registration.py:70 msgid "Team" msgstr "Équipe" -#: apps/wei/forms/registration.py:70 +#: apps/wei/forms/registration.py:72 msgid "" "Leave this field empty if you won't be in a team (staff, bus chief, free " "electron)" @@ -3004,20 +3045,16 @@ 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:76 apps/wei/forms/registration.py:91 +#: apps/wei/forms/registration.py:78 apps/wei/forms/registration.py:88 #: apps/wei/models.py:160 msgid "WEI Roles" msgstr "Rôles au WEI" -#: apps/wei/forms/registration.py:77 +#: apps/wei/forms/registration.py:79 msgid "Select the roles that you are interested in." msgstr "Sélectionnez les rôles qui vous intéressent." -#: 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 +#: apps/wei/forms/registration.py:125 msgid "This team doesn't belong to the given bus." msgstr "Cette équipe n'appartient pas à ce bus." @@ -3085,6 +3122,10 @@ 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/views.py:951 +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" @@ -3205,7 +3246,7 @@ msgid "preferred bus" msgstr "bus préféré" #: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:32 -#: apps/wei/templates/wei/busteam_detail.html:50 +#: apps/wei/templates/wei/busteam_detail.html:52 msgid "Teams" msgstr "Équipes" @@ -3282,11 +3323,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:540 +#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:557 msgid "Register 1A" msgstr "Inscrire un⋅e 1A" -#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:626 +#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:649 msgid "Register 2A+" msgstr "Inscrire un⋅e 2A+" @@ -3299,7 +3340,7 @@ msgid "View WEI" msgstr "Voir le WEI" #: apps/wei/templates/wei/bus_detail.html:22 -#: apps/wei/templates/wei/busteam_detail.html:22 +#: apps/wei/templates/wei/busteam_detail.html:24 msgid "Add team" msgstr "Ajouter une équipe" @@ -3308,15 +3349,15 @@ msgid "Members" msgstr "Membres" #: apps/wei/templates/wei/bus_detail.html:54 -#: apps/wei/templates/wei/busteam_detail.html:60 +#: apps/wei/templates/wei/busteam_detail.html:62 #: apps/wei/templates/wei/weimembership_list.html:31 msgid "View as PDF" 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:1046 -#: apps/wei/views.py:1101 apps/wei/views.py:1148 +#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1095 +#: apps/wei/views.py:1150 apps/wei/views.py:1197 msgid "Survey WEI" msgstr "Questionnaire WEI" @@ -3361,7 +3402,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:80 +#: apps/wei/templates/wei/weiclub_list.html:14 apps/wei/views.py:83 msgid "Create WEI" msgstr "Créer un WEI" @@ -3500,67 +3541,67 @@ msgstr "Il n'y a pas de pré-inscription en attente avec cette entrée." msgid "View validated memberships..." msgstr "Voir les adhésions validées..." -#: apps/wei/views.py:59 +#: apps/wei/views.py:62 msgid "Search WEI" msgstr "Chercher un WEI" -#: apps/wei/views.py:110 +#: apps/wei/views.py:113 msgid "WEI Detail" msgstr "Détails du WEI" -#: apps/wei/views.py:210 +#: apps/wei/views.py:213 msgid "View members of the WEI" msgstr "Voir les membres du WEI" -#: apps/wei/views.py:243 +#: apps/wei/views.py:246 msgid "Find WEI Membership" msgstr "Trouver une adhésion au WEI" -#: apps/wei/views.py:253 +#: apps/wei/views.py:256 msgid "View registrations to the WEI" msgstr "Voir les inscriptions au WEI" -#: apps/wei/views.py:282 +#: apps/wei/views.py:285 msgid "Find WEI Registration" msgstr "Trouver une inscription au WEI" -#: apps/wei/views.py:293 +#: apps/wei/views.py:296 msgid "Update the WEI" msgstr "Modifier le WEI" -#: apps/wei/views.py:314 +#: apps/wei/views.py:317 msgid "Create new bus" msgstr "Ajouter un nouveau bus" -#: apps/wei/views.py:352 +#: apps/wei/views.py:355 msgid "Update bus" msgstr "Modifier le bus" -#: apps/wei/views.py:384 +#: apps/wei/views.py:387 msgid "Manage bus" msgstr "Gérer le bus" -#: apps/wei/views.py:411 +#: apps/wei/views.py:414 msgid "Create new team" msgstr "Créer une nouvelle équipe" -#: apps/wei/views.py:451 +#: apps/wei/views.py:461 msgid "Update team" msgstr "Modifier l'équipe" -#: apps/wei/views.py:482 +#: apps/wei/views.py:499 msgid "Manage WEI team" msgstr "Gérer l'équipe WEI" -#: apps/wei/views.py:504 +#: apps/wei/views.py:521 msgid "Register first year student to the WEI" msgstr "Inscrire un⋅e 1A au WEI" -#: apps/wei/views.py:562 apps/wei/views.py:661 +#: apps/wei/views.py:585 apps/wei/views.py:688 msgid "This user is already registered to this WEI." msgstr "Cette personne est déjà inscrite au WEI." -#: apps/wei/views.py:567 +#: apps/wei/views.py:590 msgid "" "This user can't be in her/his first year since he/she has already " "participated to a WEI." @@ -3568,35 +3609,75 @@ 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:590 +#: apps/wei/views.py:613 msgid "Register old student to the WEI" msgstr "Inscrire un⋅e 2A+ au WEI" -#: apps/wei/views.py:645 apps/wei/views.py:733 +#: apps/wei/views.py:668 apps/wei/views.py:764 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:697 +#: apps/wei/views.py:724 msgid "Update WEI Registration" msgstr "Modifier l'inscription WEI" -#: apps/wei/views.py:807 +#: apps/wei/views.py:799 +#, fuzzy +#| msgid "The BDE membership is included in the WEI registration." +msgid "No membership found for this registration" +msgstr "L'adhésion au BDE est offerte avec l'inscription au WEI." + +#: apps/wei/views.py:808 +#, fuzzy +#| msgid "" +#| "You don't have the permission to add an instance of model {app_label}." +#| "{model_name}." +msgid "You don't have the permission to update memberships" +msgstr "" +"Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}." +"{model_name}." + +#: apps/wei/views.py:814 +#, fuzzy, python-format +#| msgid "" +#| "You don't have the permission to delete this instance of model " +#| "{app_label}.{model_name}." +msgid "You don't have the permission to update the field %(field)s" +msgstr "" +"Vous n'avez pas la permission de supprimer cette instance du modèle " +"{app_label}.{model_name}." + +#: apps/wei/views.py:855 msgid "Delete WEI registration" msgstr "Supprimer l'inscription WEI" -#: apps/wei/views.py:818 +#: apps/wei/views.py:866 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:836 +#: apps/wei/views.py:884 msgid "Validate WEI registration" msgstr "Valider l'inscription WEI" -#: apps/wei/views.py:1241 +#: apps/wei/views.py:889 +#, fuzzy +#| msgid "You don't have the right to delete this WEI registration." +msgid "You don't have the permission to validate registrations" +msgstr "Vous n'avez pas la permission de supprimer cette inscription au WEI." + +#: apps/wei/views.py:952 +#, fuzzy +#| msgid "Please ask the user to credit its note before deleting this credit." +msgid "Please make sure the check is given before validating the registration" +msgstr "" +"Merci de demander à l'utilisateur·rice de recharger sa note avant de " +"supprimer la demande de crédit." + +#: apps/wei/views.py:1290 msgid "Attribute buses to first year members" msgstr "Répartir les 1A dans les bus" -#: apps/wei/views.py:1266 +#: apps/wei/views.py:1315 msgid "Attribute bus" msgstr "Attribuer un bus" @@ -3779,6 +3860,1514 @@ msgstr "Le wrapped est public" msgid "List of wrapped" msgstr "Liste des wrapped" +#: env/lib/python3.11/site-packages/django/contrib/messages/apps.py:15 +msgid "Messages" +msgstr "" + +#: env/lib/python3.11/site-packages/django/contrib/sitemaps/apps.py:8 +msgid "Site Maps" +msgstr "" + +#: env/lib/python3.11/site-packages/django/contrib/staticfiles/apps.py:9 +msgid "Static Files" +msgstr "" + +#: env/lib/python3.11/site-packages/django/contrib/syndication/apps.py:7 +#, fuzzy +#| msgid "Invitation" +msgid "Syndication" +msgstr "Invitation" + +#. Translators: String used to replace omitted page numbers in elided page +#. range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10]. +#: env/lib/python3.11/site-packages/django/core/paginator.py:30 +msgid "…" +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/paginator.py:50 +msgid "That page number is not an integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/paginator.py:52 +msgid "That page number is less than 1" +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/paginator.py:54 +#, fuzzy +#| msgid "There is no results." +msgid "That page contains no results" +msgstr "Il n'y a pas de résultat." + +#: env/lib/python3.11/site-packages/django/core/validators.py:22 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid value." +msgstr "dévalider" + +#: env/lib/python3.11/site-packages/django/core/validators.py:104 +#: env/lib/python3.11/site-packages/django/forms/fields.py:752 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid URL." +msgstr "dévalider" + +#: env/lib/python3.11/site-packages/django/core/validators.py:165 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid integer." +msgstr "dévalider" + +#: env/lib/python3.11/site-packages/django/core/validators.py:176 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid email address." +msgstr "dévalider" + +#. Translators: "letters" means latin letters: a-z and A-Z. +#: env/lib/python3.11/site-packages/django/core/validators.py:259 +msgid "" +"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:267 +msgid "" +"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or " +"hyphens." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:281 +#: env/lib/python3.11/site-packages/django/core/validators.py:289 +#: env/lib/python3.11/site-packages/django/core/validators.py:318 +#, fuzzy +#| msgid "This activity is not validated yet." +msgid "Enter a valid IPv4 address." +msgstr "Cette activité n'est pas encore validée." + +#: env/lib/python3.11/site-packages/django/core/validators.py:298 +#: env/lib/python3.11/site-packages/django/core/validators.py:319 +#, fuzzy +#| msgid "This activity is not validated yet." +msgid "Enter a valid IPv6 address." +msgstr "Cette activité n'est pas encore validée." + +#: env/lib/python3.11/site-packages/django/core/validators.py:310 +#: env/lib/python3.11/site-packages/django/core/validators.py:317 +#, fuzzy +#| msgid "This activity is not validated yet." +msgid "Enter a valid IPv4 or IPv6 address." +msgstr "Cette activité n'est pas encore validée." + +#: env/lib/python3.11/site-packages/django/core/validators.py:353 +msgid "Enter only digits separated by commas." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:359 +#, python-format +msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:394 +#, python-format +msgid "Ensure this value is less than or equal to %(limit_value)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:403 +#, python-format +msgid "Ensure this value is greater than or equal to %(limit_value)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:412 +#, python-format +msgid "Ensure this value is a multiple of step size %(limit_value)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:422 +#, python-format +msgid "" +"Ensure this value has at least %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at least %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:440 +#, python-format +msgid "" +"Ensure this value has at most %(limit_value)d character (it has " +"%(show_value)d)." +msgid_plural "" +"Ensure this value has at most %(limit_value)d characters (it has " +"%(show_value)d)." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:463 +#: env/lib/python3.11/site-packages/django/forms/fields.py:347 +#: env/lib/python3.11/site-packages/django/forms/fields.py:386 +#, fuzzy +#| msgid "phone number" +msgid "Enter a number." +msgstr "numéro de téléphone" + +#: env/lib/python3.11/site-packages/django/core/validators.py:465 +#, python-format +msgid "Ensure that there are no more than %(max)s digit in total." +msgid_plural "Ensure that there are no more than %(max)s digits in total." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:470 +#, python-format +msgid "Ensure that there are no more than %(max)s decimal place." +msgid_plural "Ensure that there are no more than %(max)s decimal places." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:475 +#, python-format +msgid "" +"Ensure that there are no more than %(max)s digit before the decimal point." +msgid_plural "" +"Ensure that there are no more than %(max)s digits before the decimal point." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:546 +#, python-format +msgid "" +"File extension “%(extension)s” is not allowed. Allowed extensions are: " +"%(allowed_extensions)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/core/validators.py:607 +msgid "Null characters are not allowed." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/base.py:1425 +#, fuzzy, python-format +#| msgid "A template with this name already exist" +msgid "%(model_name)s with this %(field_labels)s already exists." +msgstr "Un modèle de transaction avec un nom similaire existe déjà" + +#: env/lib/python3.11/site-packages/django/db/models/constraints.py:17 +#, python-format +msgid "Constraint “%(name)s” is violated." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:128 +#, fuzzy, python-format +#| msgid "This activity is not validated yet." +msgid "Value %(value)r is not a valid choice." +msgstr "Cette activité n'est pas encore validée." + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:129 +#, fuzzy +#| msgid "This image cannot be loaded." +msgid "This field cannot be null." +msgstr "Cette image ne peut pas être chargée." + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:130 +#, fuzzy +#| msgid "This image cannot be loaded." +msgid "This field cannot be blank." +msgstr "Cette image ne peut pas être chargée." + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:131 +#, fuzzy, python-format +#| msgid "A template with this name already exist" +msgid "%(model_name)s with this %(field_label)s already exists." +msgstr "Un modèle de transaction avec un nom similaire existe déjà" + +#. Translators: The 'lookup_type' is one of 'date', 'year' or +#. 'month'. Eg: "Title must be unique for pub_date year" +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:135 +#, python-format +msgid "" +"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:173 +#, python-format +msgid "Field of type: %(field_type)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1094 +#, python-format +msgid "“%(value)s” value must be either True or False." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1095 +#, python-format +msgid "“%(value)s” value must be either True, False, or None." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1097 +msgid "Boolean (Either True or False)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1147 +#, python-format +msgid "String (up to %(max_length)s)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1149 +msgid "String (unlimited)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1253 +msgid "Comma-separated integers" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1354 +#, python-format +msgid "" +"“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD " +"format." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1358 +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1493 +#, python-format +msgid "" +"“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " +"date." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1362 +msgid "Date (without time)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1489 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." +"uuuuuu]][TZ] format." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1497 +#, python-format +msgid "" +"“%(value)s” value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" +"[TZ]) but it is an invalid date/time." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1502 +msgid "Date (with time)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1626 +#, python-format +msgid "“%(value)s” value must be a decimal number." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1628 +#, fuzzy +#| msgid "phone number" +msgid "Decimal number" +msgstr "numéro de téléphone" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1789 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in [DD] [[HH:]MM:]ss[." +"uuuuuu] format." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1793 +#, fuzzy +#| msgid "action" +msgid "Duration" +msgstr "action" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1845 +#, fuzzy +#| msgid "address" +msgid "Email address" +msgstr "adresse" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1870 +msgid "File path" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1948 +#, python-format +msgid "“%(value)s” value must be a float." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1950 +#, fuzzy +#| msgid "phone number" +msgid "Floating point number" +msgstr "numéro de téléphone" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1990 +#, python-format +msgid "“%(value)s” value must be an integer." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1992 +msgid "Integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2088 +msgid "Big (8 byte) integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2105 +msgid "Small integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2113 +#, fuzzy +#| msgid "IP Address" +msgid "IPv4 address" +msgstr "Adresse IP" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2144 +#, fuzzy +#| msgid "IP Address" +msgid "IP address" +msgstr "Adresse IP" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2237 +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2238 +#, python-format +msgid "“%(value)s” value must be either None, True or False." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2240 +msgid "Boolean (Either True, False or None)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2291 +msgid "Positive big integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2306 +msgid "Positive integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2321 +msgid "Positive small integer" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2337 +#, python-format +msgid "Slug (up to %(max_length)s)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2373 +msgid "Text" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2448 +#, python-format +msgid "" +"“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " +"format." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2452 +#, python-format +msgid "" +"“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " +"invalid time." +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2456 +msgid "Time" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2564 +msgid "URL" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2588 +msgid "Raw binary data" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2653 +#, fuzzy, python-format +#| msgid "This activity is not validated yet." +msgid "“%(value)s” is not a valid UUID." +msgstr "Cette activité n'est pas encore validée." + +#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2655 +#, fuzzy +#| msgid "Invoice identifier" +msgid "Universally unique identifier" +msgstr "Numéro de facture" + +#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:232 +msgid "File" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:393 +msgid "Image" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:26 +#, fuzzy +#| msgid "Object" +msgid "A JSON object" +msgstr "Objet" + +#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:28 +#, fuzzy +#| msgid "This address must be valid." +msgid "Value must be valid JSON." +msgstr "Cette adresse doit être valide." + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:919 +#, fuzzy, python-format +#| msgid "A template with this name already exist" +msgid "%(model)s instance with %(field)s %(value)r does not exist." +msgstr "Un modèle de transaction avec un nom similaire existe déjà" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:921 +msgid "Foreign Key (type determined by related field)" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1212 +msgid "One-to-one relationship" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1269 +#, python-format +msgid "%(from)s-%(to)s relationship" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1271 +#, python-format +msgid "%(from)s-%(to)s relationships" +msgstr "" + +#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1319 +msgid "Many-to-many relationship" +msgstr "" + +#. Translators: If found as last label character, these punctuation +#. characters will prevent the default label_suffix to be appended to the label +#: env/lib/python3.11/site-packages/django/forms/boundfield.py:184 +msgid ":?.!" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:298 +#, fuzzy +#| msgid "phone number" +msgid "Enter a whole number." +msgstr "numéro de téléphone" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:467 +#: env/lib/python3.11/site-packages/django/forms/fields.py:1241 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid date." +msgstr "dévalider" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:490 +#: env/lib/python3.11/site-packages/django/forms/fields.py:1242 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid time." +msgstr "dévalider" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:517 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid date/time." +msgstr "dévalider" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:551 +#, fuzzy +#| msgid "Email validation" +msgid "Enter a valid duration." +msgstr "Validation de l'adresse mail" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:552 +#, python-brace-format +msgid "The number of days must be between {min_days} and {max_days}." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:621 +msgid "No file was submitted. Check the encoding type on the form." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:622 +msgid "No file was submitted." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:623 +msgid "The submitted file is empty." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:625 +#, python-format +msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." +msgid_plural "" +"Ensure this filename has at most %(max)d characters (it has %(length)d)." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:630 +msgid "Please either submit a file or check the clear checkbox, not both." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:694 +msgid "" +"Upload a valid image. The file you uploaded was either not an image or a " +"corrupted image." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:857 +#: env/lib/python3.11/site-packages/django/forms/fields.py:949 +#: env/lib/python3.11/site-packages/django/forms/models.py:1566 +#, python-format +msgid "Select a valid choice. %(value)s is not one of the available choices." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:951 +#: env/lib/python3.11/site-packages/django/forms/fields.py:1070 +#: env/lib/python3.11/site-packages/django/forms/models.py:1564 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a list of values." +msgstr "dévalider" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:1071 +#, fuzzy +#| msgid "phone number" +msgid "Enter a complete value." +msgstr "numéro de téléphone" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:1313 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid UUID." +msgstr "dévalider" + +#: env/lib/python3.11/site-packages/django/forms/fields.py:1343 +#, fuzzy +#| msgid "invalidate" +msgid "Enter a valid JSON." +msgstr "dévalider" + +#. Translators: This is the default suffix added to form field labels +#: env/lib/python3.11/site-packages/django/forms/forms.py:98 +msgid ":" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/forms.py:244 +#: env/lib/python3.11/site-packages/django/forms/forms.py:328 +#, python-format +msgid "(Hidden field %(name)s) %(error)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/formsets.py:63 +#, python-format +msgid "" +"ManagementForm data is missing or has been tampered with. Missing fields: " +"%(field_names)s. You may need to file a bug report if the issue persists." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/formsets.py:67 +#, python-format +msgid "Please submit at most %(num)d form." +msgid_plural "Please submit at most %(num)d forms." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/forms/formsets.py:72 +#, python-format +msgid "Please submit at least %(num)d form." +msgid_plural "Please submit at least %(num)d forms." +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/forms/formsets.py:484 +#: env/lib/python3.11/site-packages/django/forms/formsets.py:491 +#, fuzzy +#| msgid "order" +msgid "Order" +msgstr "consigne" + +#: env/lib/python3.11/site-packages/django/forms/models.py:886 +#, python-format +msgid "Please correct the duplicate data for %(field)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:891 +#, python-format +msgid "Please correct the duplicate data for %(field)s, which must be unique." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:898 +#, python-format +msgid "" +"Please correct the duplicate data for %(field_name)s which must be unique " +"for the %(lookup)s in %(date_field)s." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:907 +msgid "Please correct the duplicate values below." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:1338 +msgid "The inline value did not match the parent instance." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:1429 +msgid "Select a valid choice. That choice is not one of the available choices." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/models.py:1568 +#, fuzzy, python-format +#| msgid "This activity is not validated yet." +msgid "“%(pk)s” is not a valid value." +msgstr "Cette activité n'est pas encore validée." + +#: env/lib/python3.11/site-packages/django/forms/utils.py:226 +#, python-format +msgid "" +"%(datetime)s couldn’t be interpreted in time zone %(current_timezone)s; it " +"may be ambiguous or it may not exist." +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/widgets.py:463 +msgid "Clear" +msgstr "" + +#: env/lib/python3.11/site-packages/django/forms/widgets.py:464 +#, fuzzy +#| msgid "Current activity" +msgid "Currently" +msgstr "Activité en cours" + +#: env/lib/python3.11/site-packages/django/forms/widgets.py:465 +#, fuzzy +#| msgid "change" +msgid "Change" +msgstr "modifier" + +#: env/lib/python3.11/site-packages/django/forms/widgets.py:794 +msgid "Unknown" +msgstr "" + +#. Translators: Please do not add spaces around commas. +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:874 +msgid "yes,no,maybe" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:904 +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:921 +#, python-format +msgid "%(size)d byte" +msgid_plural "%(size)d bytes" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:923 +#, python-format +msgid "%s KB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:925 +#, python-format +msgid "%s MB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:927 +#, python-format +msgid "%s GB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:929 +#, python-format +msgid "%s TB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:931 +#, python-format +msgid "%s PB" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:73 +msgid "p.m." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:74 +msgid "a.m." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:79 +msgid "PM" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:80 +msgid "AM" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:152 +msgid "midnight" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dateformat.py:154 +msgid "noon" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:7 +msgid "Monday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:8 +msgid "Tuesday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:9 +msgid "Wednesday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:10 +msgid "Thursday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:11 +msgid "Friday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:12 +msgid "Saturday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:13 +msgid "Sunday" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:16 +msgid "Mon" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:17 +msgid "Tue" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:18 +#, fuzzy +#| msgid "Wrapped" +msgid "Wed" +msgstr "Wrapped" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:19 +msgid "Thu" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:20 +msgid "Fri" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:21 +msgid "Sat" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:22 +msgid "Sun" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:25 +msgid "January" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:26 +msgid "February" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:27 +#, fuzzy +#| msgid "Search" +msgid "March" +msgstr "Recherche" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:28 +msgid "April" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:29 +#, fuzzy +#| msgid "day" +msgid "May" +msgstr "jour" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:30 +msgid "June" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:31 +msgid "July" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:32 +msgid "August" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:33 +#, fuzzy +#| msgid "member" +msgid "September" +msgstr "adhérent·e" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:34 +msgid "October" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:35 +#, fuzzy +#| msgid "member" +msgid "November" +msgstr "adhérent·e" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:36 +#, fuzzy +#| msgid "member" +msgid "December" +msgstr "adhérent·e" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:39 +#, fuzzy +#| msgid "add" +msgid "jan" +msgstr "ajouter" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:40 +#, fuzzy +#| msgid "fee" +msgid "feb" +msgstr "cotisation" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:41 +msgid "mar" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:42 +msgid "apr" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:43 +#, fuzzy +#| msgid "day" +msgid "may" +msgstr "jour" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:44 +#, fuzzy +#| msgid "add" +msgid "jun" +msgstr "ajouter" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:45 +msgid "jul" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:46 +msgid "aug" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:47 +msgid "sep" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:48 +#, fuzzy +#| msgid "product" +msgid "oct" +msgstr "produit" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:49 +msgid "nov" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:50 +#, fuzzy +#| msgid "bde" +msgid "dec" +msgstr "bde" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:53 +msgctxt "abbrev. month" +msgid "Jan." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:54 +msgctxt "abbrev. month" +msgid "Feb." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:55 +#, fuzzy +#| msgid "Search" +msgctxt "abbrev. month" +msgid "March" +msgstr "Recherche" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:56 +msgctxt "abbrev. month" +msgid "April" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:57 +#, fuzzy +#| msgid "day" +msgctxt "abbrev. month" +msgid "May" +msgstr "jour" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:58 +msgctxt "abbrev. month" +msgid "June" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:59 +msgctxt "abbrev. month" +msgid "July" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:60 +msgctxt "abbrev. month" +msgid "Aug." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:61 +msgctxt "abbrev. month" +msgid "Sept." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:62 +msgctxt "abbrev. month" +msgid "Oct." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:63 +msgctxt "abbrev. month" +msgid "Nov." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:64 +msgctxt "abbrev. month" +msgid "Dec." +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:67 +msgctxt "alt. month" +msgid "January" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:68 +msgctxt "alt. month" +msgid "February" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:69 +#, fuzzy +#| msgid "Search" +msgctxt "alt. month" +msgid "March" +msgstr "Recherche" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:70 +msgctxt "alt. month" +msgid "April" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:71 +#, fuzzy +#| msgid "day" +msgctxt "alt. month" +msgid "May" +msgstr "jour" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:72 +msgctxt "alt. month" +msgid "June" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:73 +msgctxt "alt. month" +msgid "July" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:74 +msgctxt "alt. month" +msgid "August" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:75 +#, fuzzy +#| msgid "member" +msgctxt "alt. month" +msgid "September" +msgstr "adhérent·e" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:76 +msgctxt "alt. month" +msgid "October" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:77 +#, fuzzy +#| msgid "member" +msgctxt "alt. month" +msgid "November" +msgstr "adhérent·e" + +#: env/lib/python3.11/site-packages/django/utils/dates.py:78 +#, fuzzy +#| msgid "member" +msgctxt "alt. month" +msgid "December" +msgstr "adhérent·e" + +#: env/lib/python3.11/site-packages/django/utils/ipv6.py:20 +#, fuzzy +#| msgid "This activity is not validated yet." +msgid "This is not a valid IPv6 address." +msgstr "Cette activité n'est pas encore validée." + +#: env/lib/python3.11/site-packages/django/utils/text.py:138 +#, python-format +msgctxt "String to return when truncating text" +msgid "%(truncated_text)s…" +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/text.py:323 +#, fuzzy +#| msgid "hour" +msgid "or" +msgstr "heure" + +#. Translators: This string is used as a separator between list elements +#: env/lib/python3.11/site-packages/django/utils/text.py:342 +#: env/lib/python3.11/site-packages/django/utils/timesince.py:135 +msgid ", " +msgstr "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:8 +#, fuzzy, python-format +#| msgid "year" +msgid "%(num)d year" +msgid_plural "%(num)d years" +msgstr[0] "année" +msgstr[1] "année" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:9 +#, python-format +msgid "%(num)d month" +msgid_plural "%(num)d months" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:10 +#, python-format +msgid "%(num)d week" +msgid_plural "%(num)d weeks" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:11 +#, python-format +msgid "%(num)d day" +msgid_plural "%(num)d days" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:12 +#, python-format +msgid "%(num)d hour" +msgid_plural "%(num)d hours" +msgstr[0] "" +msgstr[1] "" + +#: env/lib/python3.11/site-packages/django/utils/timesince.py:13 +#, fuzzy, python-format +#| msgid "minute" +msgid "%(num)d minute" +msgid_plural "%(num)d minutes" +msgstr[0] "minute" +msgstr[1] "minute" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:111 +msgid "Forbidden" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:112 +msgid "CSRF verification failed. Request aborted." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:116 +msgid "" +"You are seeing this message because this HTTPS site requires a “Referer " +"header” to be sent by your web browser, but none was sent. This header is " +"required for security reasons, to ensure that your browser is not being " +"hijacked by third parties." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:122 +msgid "" +"If you have configured your browser to disable “Referer” headers, please re-" +"enable them, at least for this site, or for HTTPS connections, or for “same-" +"origin” requests." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:127 +msgid "" +"If you are using the tag or " +"including the “Referrer-Policy: no-referrer” header, please remove them. The " +"CSRF protection requires the “Referer” header to do strict referer checking. " +"If you’re concerned about privacy, use alternatives like for links to third-party sites." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:136 +msgid "" +"You are seeing this message because this site requires a CSRF cookie when " +"submitting forms. This cookie is required for security reasons, to ensure " +"that your browser is not being hijacked by third parties." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:142 +msgid "" +"If you have configured your browser to disable cookies, please re-enable " +"them, at least for this site, or for “same-origin” requests." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/csrf.py:148 +msgid "More information is available with DEBUG=True." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:44 +#, fuzzy +#| msgid "No reason specified" +msgid "No year specified" +msgstr "Pas de motif spécifié" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:64 +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:115 +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:214 +msgid "Date out of range" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:94 +#, fuzzy +#| msgid "No reason specified" +msgid "No month specified" +msgstr "Pas de motif spécifié" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:147 +#, fuzzy +#| msgid "No reason specified" +msgid "No day specified" +msgstr "Pas de motif spécifié" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:194 +#, fuzzy +#| msgid "No reason specified" +msgid "No week specified" +msgstr "Pas de motif spécifié" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:349 +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:380 +#, python-format +msgid "No %(verbose_name_plural)s available" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:652 +#, python-format +msgid "" +"Future %(verbose_name_plural)s not available because %(class_name)s." +"allow_future is False." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/dates.py:692 +#, python-format +msgid "Invalid date string “%(datestr)s” given format “%(format)s”" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/detail.py:56 +#, python-format +msgid "No %(verbose_name)s found matching the query" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/list.py:70 +msgid "Page is not “last”, nor can it be converted to an int." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/list.py:77 +#, python-format +msgid "Invalid page (%(page_number)s): %(message)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/generic/list.py:169 +#, python-format +msgid "Empty list and “%(class_name)s.allow_empty” is False." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/static.py:38 +msgid "Directory indexes are not allowed here." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/static.py:40 +#, python-format +msgid "“%(path)s” does not exist" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/static.py:79 +#, python-format +msgid "Index of %(directory)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:7 +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:220 +msgid "The install worked successfully! Congratulations!" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:206 +#, python-format +msgid "" +"View release notes for Django %(version)s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:221 +#, python-format +msgid "" +"You are seeing this page because DEBUG=True is in your settings file and you have not " +"configured any URLs." +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:229 +msgid "Django Documentation" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:230 +msgid "Topics, references, & how-to’s" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:238 +msgid "Tutorial: A Polling App" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:239 +msgid "Get started with Django" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:247 +msgid "Django Community" +msgstr "" + +#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:248 +msgid "Connect, get help, or contribute" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:69 +#, fuzzy +#| msgid "Client secret" +msgid "Confidential" +msgstr "Secret client" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:70 +#, fuzzy +#| msgid "public" +msgid "Public" +msgstr "public" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:79 +#, fuzzy +#| msgid "Authorization:" +msgid "Authorization code" +msgstr "Autorisation :" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:80 +msgid "Implicit" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:81 +#, fuzzy +#| msgid "Reset my password" +msgid "Resource owner password-based" +msgstr "Réinitialiser mon mot de passe" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:82 +#, fuzzy +#| msgid "Client secret" +msgid "Client credentials" +msgstr "Secret client" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:83 +msgid "OpenID connect hybrid" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:90 +msgid "No OIDC support" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:91 +msgid "RSA with SHA-2 256" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:92 +msgid "HMAC with SHA-2 256" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:107 +msgid "Allowed URIs list, space separated" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:111 +msgid "Allowed Post Logout URIs list, space separated" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:120 +msgid "Hashed on Save. Copy it now if this is a new secret." +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:190 +#, python-brace-format +msgid "Unauthorized redirect scheme: {scheme}" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:194 +#, python-brace-format +msgid "redirect_uris cannot be empty with grant_type {grant_type}" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:200 +msgid "You must set OIDC_RSA_PRIVATE_KEY to use RSA algorithm" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/models.py:209 +msgid "You cannot use HS256 with public grants or clients" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:211 +#, fuzzy +#| msgid "This address must be valid." +msgid "The access token is invalid." +msgstr "Cette adresse doit être valide." + +#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:218 +#, fuzzy +#| msgid "This address must be valid." +msgid "The access token has expired." +msgstr "Cette adresse doit être valide." + +#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:225 +#, fuzzy +#| msgid "The user does not have enough money." +msgid "The access token is valid but does not have enough scope." +msgstr "L'utilisateur·ice n'a pas assez d'argent." + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:6 +#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8 +msgid "Are you sure to delete the application" +msgstr "Êtes-vous sûr⋅e de vouloir supprimer l'application" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:12 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:29 +#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 +#: note_kfet/templates/oauth2_provider/authorize.html:28 +msgid "Cancel" +msgstr "Annuler" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:10 +#: note_kfet/templates/oauth2_provider/application_detail.html:11 +msgid "Client id" +msgstr "ID client" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:15 +#: note_kfet/templates/oauth2_provider/application_detail.html:14 +msgid "Client secret" +msgstr "Secret client" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:20 +#: note_kfet/templates/oauth2_provider/application_detail.html:17 +msgid "Client type" +msgstr "Type de client" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:25 +#: note_kfet/templates/oauth2_provider/application_detail.html:20 +msgid "Authorization Grant Type" +msgstr "Type d'autorisation" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:30 +#: note_kfet/templates/oauth2_provider/application_detail.html:23 +msgid "Redirect Uris" +msgstr "URIs de redirection" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:36 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:35 +#: note_kfet/templates/oauth2_provider/application_detail.html:37 +#: note_kfet/templates/oauth2_provider/application_form.html:23 +msgid "Go Back" +msgstr "Retour en arrière" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:9 +#: note_kfet/templates/oauth2_provider/application_form.html:12 +msgid "Edit application" +msgstr "Modifier l'application" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:37 +msgid "Save" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:6 +#: note_kfet/templates/oauth2_provider/application_list.html:7 +msgid "Your applications" +msgstr "Vos applications" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:14 +#: note_kfet/templates/oauth2_provider/application_list.html:30 +msgid "New Application" +msgstr "Nouvelle application" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_registration_form.html:5 +#: note_kfet/templates/oauth2_provider/application_registration_form.html:5 +msgid "Register a new application" +msgstr "Enregistrer une nouvelle application" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:8 +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:30 +#: note_kfet/templates/oauth2_provider/authorize.html:9 +#: note_kfet/templates/oauth2_provider/authorize.html:29 +msgid "Authorize" +msgstr "Autoriser" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:17 +#, fuzzy +#| msgid "Application requires following permissions:" +msgid "Application requires the following permissions" +msgstr "L'application requiert les permissions suivantes :" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:6 +#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9 +msgid "Are you sure you want to delete this token?" +msgstr "Êtes-vous sûr⋅e de vouloir supprimer ce jeton ?" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:6 +#: note_kfet/templates/oauth2_provider/authorized-tokens.html:7 +msgid "Tokens" +msgstr "Jetons" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:11 +msgid "revoke" +msgstr "" + +#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:19 +#: note_kfet/templates/oauth2_provider/authorized-tokens.html:22 +msgid "There are no authorized tokens yet." +msgstr "Il n'y a pas encore de jeton autorisé." + #: note_kfet/settings/base.py:177 msgid "German" msgstr "Allemand" @@ -3941,35 +5530,6 @@ msgstr "Chercher par un attribut tel que le nom..." msgid "There is no results." msgstr "Il n'y a pas de résultat." -#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8 -msgid "Are you sure to delete the application" -msgstr "Êtes-vous sûr⋅e de vouloir supprimer l'application" - -#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 -#: note_kfet/templates/oauth2_provider/authorize.html:28 -msgid "Cancel" -msgstr "Annuler" - -#: note_kfet/templates/oauth2_provider/application_detail.html:11 -msgid "Client id" -msgstr "ID client" - -#: note_kfet/templates/oauth2_provider/application_detail.html:14 -msgid "Client secret" -msgstr "Secret client" - -#: note_kfet/templates/oauth2_provider/application_detail.html:17 -msgid "Client type" -msgstr "Type de client" - -#: note_kfet/templates/oauth2_provider/application_detail.html:20 -msgid "Authorization Grant Type" -msgstr "Type d'autorisation" - -#: note_kfet/templates/oauth2_provider/application_detail.html:23 -msgid "Redirect Uris" -msgstr "URIs de redirection" - #: note_kfet/templates/oauth2_provider/application_detail.html:29 #, python-format msgid "" @@ -3982,19 +5542,6 @@ msgstr "" "de scopes avec les permissions que vous souhaitez attribuer à votre " "application." -#: note_kfet/templates/oauth2_provider/application_detail.html:37 -#: note_kfet/templates/oauth2_provider/application_form.html:23 -msgid "Go Back" -msgstr "Retour en arrière" - -#: note_kfet/templates/oauth2_provider/application_form.html:12 -msgid "Edit application" -msgstr "Modifier l'application" - -#: note_kfet/templates/oauth2_provider/application_list.html:7 -msgid "Your applications" -msgstr "Vos applications" - #: note_kfet/templates/oauth2_provider/application_list.html:11 msgid "" "You can find on this page the list of the applications that you already " @@ -4003,23 +5550,10 @@ msgstr "" "Vous pouvez trouver sur cette page la liste des applications que vous avez " "déjà enregistrées." -#: note_kfet/templates/oauth2_provider/application_list.html:30 -msgid "New Application" -msgstr "Nouvelle application" - #: note_kfet/templates/oauth2_provider/application_list.html:31 msgid "Authorized Tokens" msgstr "Jetons autorisés" -#: note_kfet/templates/oauth2_provider/application_registration_form.html:5 -msgid "Register a new application" -msgstr "Enregistrer une nouvelle application" - -#: note_kfet/templates/oauth2_provider/authorize.html:9 -#: note_kfet/templates/oauth2_provider/authorize.html:29 -msgid "Authorize" -msgstr "Autoriser" - #: note_kfet/templates/oauth2_provider/authorize.html:14 msgid "Application requires following permissions:" msgstr "L'application requiert les permissions suivantes :" @@ -4037,18 +5571,6 @@ msgstr "Succès" msgid "Please return to your application and enter this code:" msgstr "Merci de retourner à votre application et entrez ce code :" -#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9 -msgid "Are you sure you want to delete this token?" -msgstr "Êtes-vous sûr⋅e de vouloir supprimer ce jeton ?" - -#: note_kfet/templates/oauth2_provider/authorized-tokens.html:7 -msgid "Tokens" -msgstr "Jetons" - -#: note_kfet/templates/oauth2_provider/authorized-tokens.html:22 -msgid "There are no authorized tokens yet." -msgstr "Il n'y a pas encore de jeton autorisé." - #: note_kfet/templates/registration/logged_out.html:13 msgid "Thanks for spending some quality time with the Web site today." msgstr "Merci d'avoir utilisé la Note Kfet." @@ -4227,308 +5749,6 @@ msgstr "" #~ msgid "Enter a valid color." #~ msgstr "dévalider" -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid value." -#~ msgstr "dévalider" - -#, fuzzy -#~| msgid "Invitation" -#~ msgid "Syndication" -#~ msgstr "Invitation" - -#, fuzzy -#~| msgid "There is no results." -#~ msgid "That page contains no results" -#~ msgstr "Il n'y a pas de résultat." - -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid URL." -#~ msgstr "dévalider" - -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid integer." -#~ msgstr "dévalider" - -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid email address." -#~ msgstr "dévalider" - -#, fuzzy -#~| msgid "This activity is not validated yet." -#~ msgid "Enter a valid IPv4 address." -#~ msgstr "Cette activité n'est pas encore validée." - -#, fuzzy -#~| msgid "This activity is not validated yet." -#~ msgid "Enter a valid IPv6 address." -#~ msgstr "Cette activité n'est pas encore validée." - -#, fuzzy -#~| msgid "This activity is not validated yet." -#~ msgid "Enter a valid IPv4 or IPv6 address." -#~ msgstr "Cette activité n'est pas encore validée." - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Enter a number." -#~ msgstr "numéro de téléphone" - -#, fuzzy, python-format -#~| msgid "A template with this name already exist" -#~ msgid "%(model_name)s with this %(field_labels)s already exists." -#~ msgstr "Un modèle de transaction avec un nom similaire existe déjà" - -#, fuzzy -#~| msgid "This image cannot be loaded." -#~ msgid "This field cannot be null." -#~ msgstr "Cette image ne peut pas être chargée." - -#, fuzzy -#~| msgid "This image cannot be loaded." -#~ msgid "This field cannot be blank." -#~ msgstr "Cette image ne peut pas être chargée." - -#, fuzzy, python-format -#~| msgid "A template with this name already exist" -#~ msgid "%(model_name)s with this %(field_label)s already exists." -#~ msgstr "Un modèle de transaction avec un nom similaire existe déjà" - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Decimal number" -#~ msgstr "numéro de téléphone" - -#, fuzzy -#~| msgid "action" -#~ msgid "Duration" -#~ msgstr "action" - -#, fuzzy -#~| msgid "address" -#~ msgid "Email address" -#~ msgstr "adresse" - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Floating point number" -#~ msgstr "numéro de téléphone" - -#, fuzzy -#~| msgid "IP Address" -#~ msgid "IPv4 address" -#~ msgstr "Adresse IP" - -#, fuzzy -#~| msgid "IP Address" -#~ msgid "IP address" -#~ msgstr "Adresse IP" - -#, fuzzy -#~| msgid "Invoice identifier" -#~ msgid "Universally unique identifier" -#~ msgstr "Numéro de facture" - -#, fuzzy, python-format -#~| msgid "A template with this name already exist" -#~ msgid "%(model)s instance with %(field)s %(value)r does not exist." -#~ msgstr "Un modèle de transaction avec un nom similaire existe déjà" - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Enter a whole number." -#~ msgstr "numéro de téléphone" - -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid date." -#~ msgstr "dévalider" - -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid time." -#~ msgstr "dévalider" - -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid date/time." -#~ msgstr "dévalider" - -#, fuzzy -#~| msgid "Email validation" -#~ msgid "Enter a valid duration." -#~ msgstr "Validation de l'adresse mail" - -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a list of values." -#~ msgstr "dévalider" - -#, fuzzy -#~| msgid "phone number" -#~ msgid "Enter a complete value." -#~ msgstr "numéro de téléphone" - -#, fuzzy -#~| msgid "invalidate" -#~ msgid "Enter a valid UUID." -#~ msgstr "dévalider" - -#, fuzzy, python-format -#~| msgid "This activity is not validated yet." -#~ msgid "\"%(pk)s\" is not a valid value." -#~ msgstr "Cette activité n'est pas encore validée." - -#, fuzzy -#~| msgid "Current activity" -#~ msgid "Currently" -#~ msgstr "Activité en cours" - -#, fuzzy -#~| msgid "change" -#~ msgid "Change" -#~ msgstr "modifier" - -#, fuzzy -#~| msgid "Search" -#~ msgid "March" -#~ msgstr "Recherche" - -#, fuzzy -#~| msgid "member" -#~ msgid "September" -#~ msgstr "adhérent·e" - -#, fuzzy -#~| msgid "member" -#~ msgid "November" -#~ msgstr "adhérent·e" - -#, fuzzy -#~| msgid "member" -#~ msgid "December" -#~ msgstr "adhérent·e" - -#, fuzzy -#~| msgid "add" -#~ msgid "jan" -#~ msgstr "ajouter" - -#, fuzzy -#~| msgid "fee" -#~ msgid "feb" -#~ msgstr "cotisation" - -#, fuzzy -#~| msgid "product" -#~ msgid "oct" -#~ msgstr "produit" - -#, fuzzy -#~| msgid "Search" -#~ msgctxt "abbrev. month" -#~ msgid "March" -#~ msgstr "Recherche" - -#, fuzzy -#~| msgid "Search" -#~ msgctxt "alt. month" -#~ msgid "March" -#~ msgstr "Recherche" - -#, fuzzy -#~| msgid "member" -#~ msgctxt "alt. month" -#~ msgid "September" -#~ msgstr "adhérent·e" - -#, fuzzy -#~| msgid "member" -#~ msgctxt "alt. month" -#~ msgid "November" -#~ msgstr "adhérent·e" - -#, fuzzy -#~| msgid "member" -#~ msgctxt "alt. month" -#~ msgid "December" -#~ msgstr "adhérent·e" - -#, fuzzy -#~| msgid "This activity is not validated yet." -#~ msgid "This is not a valid IPv6 address." -#~ msgstr "Cette activité n'est pas encore validée." - -#, fuzzy, python-format -#~| msgid "year" -#~ msgid "%d year" -#~ msgid_plural "%d years" -#~ msgstr[0] "année" -#~ msgstr[1] "année" - -#, fuzzy -#~| msgid "No reason specified" -#~ msgid "No year specified" -#~ msgstr "Pas de motif spécifié" - -#, fuzzy -#~| msgid "No reason specified" -#~ msgid "No month specified" -#~ msgstr "Pas de motif spécifié" - -#, fuzzy -#~| msgid "No reason specified" -#~ msgid "No day specified" -#~ msgstr "Pas de motif spécifié" - -#, fuzzy -#~| msgid "No reason specified" -#~ msgid "No week specified" -#~ msgstr "Pas de motif spécifié" - -#, fuzzy -#~| msgid "Client secret" -#~ msgid "Confidential" -#~ msgstr "Secret client" - -#, fuzzy -#~| msgid "Authorization:" -#~ msgid "Authorization code" -#~ msgstr "Autorisation :" - -#, fuzzy -#~| msgid "Reset my password" -#~ msgid "Resource owner password-based" -#~ msgstr "Réinitialiser mon mot de passe" - -#, fuzzy -#~| msgid "Client secret" -#~ msgid "Client credentials" -#~ msgstr "Secret client" - -#, fuzzy -#~| msgid "This address must be valid." -#~ msgid "The access token is invalid." -#~ msgstr "Cette adresse doit être valide." - -#, fuzzy -#~| msgid "This address must be valid." -#~ msgid "The access token has expired." -#~ msgstr "Cette adresse doit être valide." - -#, fuzzy -#~| msgid "The user does not have enough money." -#~ msgid "The access token is valid but does not have enough scope." -#~ msgstr "L'utilisateur·ice n'a pas assez d'argent." - -#, fuzzy -#~| msgid "Application requires following permissions:" -#~ msgid "Application requires following permissions" -#~ msgstr "L'application requiert les permissions suivantes :" - #~ msgid "pasta" #~ msgstr "pâtes" From a3514154944bfd53f3ae827cef170a5caf3537cd Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Wed, 28 May 2025 15:37:37 +0200 Subject: [PATCH 024/153] Fix des tests de apps/wei --- apps/wei/tests/test_wei_registration.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/wei/tests/test_wei_registration.py b/apps/wei/tests/test_wei_registration.py index ffff04c9..7b55095b 100644 --- a/apps/wei/tests/test_wei_registration.py +++ b/apps/wei/tests/test_wei_registration.py @@ -510,7 +510,7 @@ class TestWEIRegistration(TestCase): ) qs = WEIRegistration.objects.filter(user_id=self.user.id, soge_credit=False, clothing_size="M") self.assertTrue(qs.exists()) - self.assertRedirects(response, reverse("wei:validate_registration", kwargs=dict(pk=qs.get().pk)), 302, 200) + self.assertRedirects(response, reverse("wei:wei_detail", kwargs=dict(pk=qs.get().wei.pk)), 302, 200) # Check the page when the registration is already validated membership = WEIMembership( @@ -564,7 +564,7 @@ class TestWEIRegistration(TestCase): ) qs = WEIRegistration.objects.filter(user_id=self.user.id, clothing_size="L") self.assertTrue(qs.exists()) - self.assertRedirects(response, reverse("wei:validate_registration", kwargs=dict(pk=qs.get().pk)), 302, 200) + self.assertRedirects(response, reverse("wei:wei_detail", kwargs=dict(pk=qs.get().wei.pk)), 302, 200) # Test invalid form response = self.client.post( @@ -632,6 +632,7 @@ class TestWEIRegistration(TestCase): last_name="admin", first_name="admin", bank="Société générale", + caution_check=True, )) self.assertEqual(response.status_code, 200) self.assertFalse(response.context["form"].is_valid()) @@ -646,8 +647,10 @@ class TestWEIRegistration(TestCase): last_name="admin", first_name="admin", bank="Société générale", + caution_check=True, )) self.assertRedirects(response, reverse("wei:wei_registrations", kwargs=dict(pk=self.registration.wei.pk)), 302, 200) + # Check if the membership is successfully created membership = WEIMembership.objects.filter(user_id=self.user.id, club=self.wei) self.assertTrue(membership.exists()) From 4479e8f97a0cab8057c2274c61339c2ecda060ae Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Wed, 28 May 2025 16:04:19 +0200 Subject: [PATCH 025/153] Fix de views.py et tests de permissions --- .../tests/test_permission_denied.py | 5 ++++- apps/wei/views.py | 19 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/apps/permission/tests/test_permission_denied.py b/apps/permission/tests/test_permission_denied.py index 792bd1de..c2f3ad3a 100644 --- a/apps/permission/tests/test_permission_denied.py +++ b/apps/permission/tests/test_permission_denied.py @@ -10,7 +10,7 @@ from django.utils import timezone from django.utils.crypto import get_random_string from activity.models import Activity from member.models import Club, Membership -from note.models import NoteUser +from note.models import NoteUser, NoteClub from wei.models import WEIClub, Bus, WEIRegistration @@ -122,10 +122,13 @@ class TestPermissionDenied(TestCase): def test_validate_weiregistration(self): wei = WEIClub.objects.create( + name="WEI Test", membership_start=date.today(), date_start=date.today() + timedelta(days=1), date_end=date.today() + timedelta(days=1), + parent_club=Club.objects.get(name="Kfet"), ) + NoteClub.objects.create(club=wei) registration = WEIRegistration.objects.create(wei=wei, user=self.user, birth_date="2000-01-01") response = self.client.get(reverse("wei:validate_registration", kwargs=dict(pk=registration.pk))) self.assertEqual(response.status_code, 403) diff --git a/apps/wei/views.py b/apps/wei/views.py index 087e5eb0..bfd9dc4d 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -876,18 +876,27 @@ class WEIDeleteRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Delete return reverse_lazy('wei:wei_detail', args=(self.object.wei.pk,)) -class WEIValidateRegistrationView(LoginRequiredMixin, CreateView): +class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): """ Validate WEI Registration """ model = WEIMembership extra_context = {"title": _("Validate WEI registration")} + def get_sample_object(self): + """ + Return a sample object for permission checking + """ + registration = WEIRegistration.objects.get(pk=self.kwargs["pk"]) + return WEIMembership( + user=registration.user, + club=registration.wei, + date_start=registration.wei.date_start, + # Add any fields needed for proper permission checking + registration=registration, + ) + def dispatch(self, request, *args, **kwargs): - # Vérifier d'abord si l'utilisateur a la permission générale - if not request.user.has_perm("wei.add_weimembership"): - raise PermissionDenied(_("You don't have the permission to validate registrations")) - registration = WEIRegistration.objects.get(pk=self.kwargs["pk"]) wei = registration.wei From 02453e07ba9c02e9e97e33e3ce60730d74c39819 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Wed, 28 May 2025 16:31:03 +0200 Subject: [PATCH 026/153] linters --- apps/wei/forms/registration.py | 18 ++++++++++++----- apps/wei/views.py | 37 ++++++++++++++-------------------- 2 files changed, 28 insertions(+), 27 deletions(-) diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index fb497730..0dba2db9 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -39,9 +39,11 @@ class WEIRegistrationForm(forms.ModelForm): class Meta: model = WEIRegistration - fields = ['user', 'soge_credit', 'birth_date', 'gender', 'clothing_size', - 'health_issues', 'emergency_contact_name', 'emergency_contact_phone', 'first_year', - 'information_json'] + fields = [ + 'user', 'soge_credit', 'birth_date', 'gender', 'clothing_size', + 'health_issues', 'emergency_contact_name', 'emergency_contact_phone', + 'first_year', 'information_json', 'caution_check' + ] widgets = { "user": Autocomplete( User, @@ -51,8 +53,14 @@ class WEIRegistrationForm(forms.ModelForm): 'placeholder': 'Nom ...', }, ), - "birth_date": DatePickerInput(options={'minDate': '1900-01-01', - 'maxDate': '2100-01-01'}), + "birth_date": DatePickerInput(options={ + 'minDate': '1900-01-01', + 'maxDate': '2100-01-01' + }), + "caution_check": forms.BooleanField( + label=_("I confirm that I have read the caution and that I am aware of the risks involved."), + required=False, + ), } diff --git a/apps/wei/views.py b/apps/wei/views.py index bfd9dc4d..67d4eee7 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -4,7 +4,7 @@ import os import shutil import subprocess -from datetime import date, timedelta +from datetime import date from tempfile import mkdtemp from django.conf import settings @@ -21,7 +21,7 @@ from django.shortcuts import redirect from django.template.loader import render_to_string from django.urls import reverse_lazy from django.views import View -from django.views.generic import DetailView, UpdateView, RedirectView, TemplateView, CreateView +from django.views.generic import DetailView, UpdateView, RedirectView, TemplateView from django.utils.translation import gettext_lazy as _ from django.views.generic.edit import BaseFormView, DeleteView from django_tables2 import SingleTableView, MultiTableMixin @@ -39,7 +39,6 @@ from .forms import WEIForm, WEIRegistrationForm, BusForm, BusTeamForm, WEIMember WEIMembershipForm, CurrentSurvey from .tables import BusRepartitionTable, BusTable, BusTeamTable, WEITable, WEIRegistrationTable, \ WEIRegistration1ATable, WEIMembershipTable -from .forms.surveys import CurrentSurvey class CurrentWEIDetailView(LoginRequiredMixin, RedirectView): @@ -443,13 +442,10 @@ class BusTeamCreateView(ProtectQuerysetMixin, ProtectedCreateView): def get_success_url(self): self.object.refresh_from_db() return reverse_lazy("wei:manage_bus_team", kwargs={"pk": self.object.pk}) - + def get_template_names(self): names = super().get_template_names() return names - - - class BusTeamUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): @@ -482,13 +478,10 @@ class BusTeamUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): def get_success_url(self): self.object.refresh_from_db() return reverse_lazy("wei:manage_bus_team", kwargs={"pk": self.object.pk}) - + def get_template_names(self): names = super().get_template_names() return names - - - class BusTeamManageView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): @@ -563,7 +556,7 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView): def get_form(self, form_class=None): form = super().get_form(form_class) form.fields["user"].initial = self.request.user - + # Cacher les champs pendant l'inscription initiale if "first_year" in form.fields: del form.fields["first_year"] @@ -571,7 +564,7 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView): del form.fields["caution_check"] if "information_json" in form.fields: del form.fields["information_json"] - + return form @transaction.atomic @@ -797,22 +790,22 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update membership = form.instance.membership if membership is None: raise ValueError(_("No membership found for this registration")) - + membership_form = self.get_membership_form(self.request.POST, instance=membership) if not membership_form.is_valid(): return self.form_invalid(form) - + # Vérifier que l'utilisateur a la permission de modifier le membership # On vérifie d'abord si l'utilisateur a la permission générale de modification if not self.request.user.has_perm("wei.change_weimembership"): raise PermissionDenied(_("You don't have the permission to update memberships")) - + # On vérifie ensuite les permissions spécifiques pour chaque champ modifié for field_name in membership_form.changed_data: perm = f"wei.change_weimembership_{field_name}" if not self.request.user.has_perm(perm): raise PermissionDenied(_("You don't have the permission to update the field %(field)s") % {'field': field_name}) - + membership_form.save() except (WEIMembership.DoesNotExist, ValueError, PermissionDenied) as e: form.add_error(None, str(e)) @@ -898,7 +891,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): def dispatch(self, request, *args, **kwargs): registration = WEIRegistration.objects.get(pk=self.kwargs["pk"]) - + wei = registration.wei today = date.today() # We can't validate anyone once the WEI is started and before the membership start date @@ -1347,7 +1340,7 @@ class WEIAttributeBus1ANextView(LoginRequiredMixin, RedirectView): if not wei.exists(): raise Http404 wei = wei.get() - + # On cherche d'abord les 1A qui ont une inscription validée (membership) mais pas de bus qs = WEIRegistration.objects.filter( wei=wei, @@ -1355,14 +1348,14 @@ class WEIAttributeBus1ANextView(LoginRequiredMixin, RedirectView): membership__isnull=False, membership__bus__isnull=True ) - + # Parmi eux, on prend ceux qui ont répondu au questionnaire (ont un bus préféré) qs = qs.filter(information_json__contains='selected_bus_pk') - + if not qs.exists(): # Si on ne trouve personne, on affiche un message et on retourne à la liste messages.info(self.request, _("No first year student without a bus found. Either all of them have a bus, or none has filled the survey yet.")) return reverse_lazy('wei:wei_1A_list', args=(wei.pk,)) - + # On redirige vers la page d'attribution pour le premier étudiant trouvé return reverse_lazy('wei:wei_bus_1A', args=(qs.first().pk,)) From 70a57bf02d9a22da36a40413a90853abb9ba0d1b Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Thu, 29 May 2025 20:16:43 +0200 Subject: [PATCH 027/153] =?UTF-8?q?Ajout=20d'un=20champ=20club=20au=20mod?= =?UTF-8?q?=C3=A8le=20Bus=20pour=20faciliter=20la=20gestion=20des=20bus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/permission/fixtures/initial.json | 320 ++++++++++++++++++++++++- apps/wei/migrations/0012_bus_club.py | 20 ++ apps/wei/models.py | 9 + apps/wei/templates/wei/bus_detail.html | 4 + 4 files changed, 350 insertions(+), 3 deletions(-) create mode 100644 apps/wei/migrations/0012_bus_club.py diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 5ce57def..08f88f2b 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -4046,6 +4046,294 @@ "description": "Voir toutes les équipes WEI" } }, + { + "model": "permission.permission", + "pk": 274, + "fields": { + "model": [ + "member", + "club" + ], + "query": "{\"bus__wei\": [\"club\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir les informations de clubs des bus" + } + }, + { + "model": "permission.permission", + "pk": 275, + "fields": { + "model": [ + "member", + "club" + ], + "query": "{\"bus__wei\": [\"club\"]}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier les clubs des bus" + } + }, + { + "model": "permission.permission", + "pk": 276, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{\"club__bus__wei\": [\"club\"]}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter un⋅e membre à un club de bus" + } + }, + { + "model": "permission.permission", + "pk": 277, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{\"club__bus__wei\": [\"club\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir les adhérents d'un club de bus" + } + }, + { + "model": "permission.permission", + "pk": 278, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{\"club__bus__wei\": [\"club\"]}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier l'adhésion d'un club de bus" + } + }, + { + "model": "permission.permission", + "pk": 279, + "fields": { + "model": [ + "note", + "note" + ], + "query": "{\"noteclub__club__bus__wei\": [\"club\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir la note d'un club de bus" + } + }, + { + "model": "permission.permission", + "pk": 280, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"OR\", {\"source__noteclub__club__bus__wei\": [\"club\"]}, {\"destination__noteclub__club__bus__wei\": [\"club\"]}]", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir les transactions d'un club de bus" + } + }, + { + "model": "permission.permission", + "pk": 281, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"AND\", [\"OR\", {\"source__noteclub__club__bus__wei\": [\"club\"]}, {\"destination__noteclub__club__bus__wei\": [\"club\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]]", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Créer une transaction d'un club de bus tant que la source reste au dessus de -20 €" + } + }, + { + "model": "permission.permission", + "pk": 282, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"AND\", [\"OR\", {\"source__noteclub__club\": [\"club\"]}, {\"destination__noteclub__club\": [\"club\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]]", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Créer une transaction d'un WEI tant que la source reste au dessus de -20 €" + } + }, + { + "model": "permission.permission", + "pk": 283, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{\"memberships__club__name\": \"Kfet\", \"memberships__roles__name\": \"Adh\u00e9rent\u22c5e Kfet\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir n'importe quel⋅le utilisateur⋅rice qui est adhérent⋅e Kfet" + } + }, + { + "model": "permission.permission", + "pk": 284, + "fields": { + "model": [ + "member", + "club" + ], + "query": "{\"bus\": [\"membership\", \"weimembership\", \"bus\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir les informations de club de son bus" + } + }, + { + "model": "permission.permission", + "pk": 285, + "fields": { + "model": [ + "member", + "club" + ], + "query": "{\"bus\": [\"membership\", \"weimembership\", \"bus\"]}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier le club de son bus" + } + }, + { + "model": "permission.permission", + "pk": 286, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{\"club__bus\": [\"membership\", \"weimembership\", \"bus\"]}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter un⋅e membre au club de son bus" + } + }, + { + "model": "permission.permission", + "pk": 287, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{\"club__bus\": [\"membership\", \"weimembership\", \"bus\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir les adhérents du club de son bus" + } + }, + { + "model": "permission.permission", + "pk": 288, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{\"club__bus\": [\"membership\", \"weimembership\", \"bus\"]}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier l'adhésion au club de son bus" + } + }, + { + "model": "permission.permission", + "pk": 289, + "fields": { + "model": [ + "note", + "note" + ], + "query": "{\"noteclub__club__bus\": [\"membership\", \"weimembership\", \"bus\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir la note du club de son bus" + } + }, + { + "model": "permission.permission", + "pk": 290, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"OR\", {\"source__noteclub__club__bus\": [\"membership\", \"weimembership\", \"bus\"]}, {\"destination__noteclub__club__bus\": [\"membership\", \"weimembership\", \"bus\"]}]", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir les transactions du club de son bus" + } + }, + { + "model": "permission.permission", + "pk": 291, + "fields": { + "model": [ + "wei", + "bus" + ], + "query": "{\"pk\": [\"membership\", \"weimembership\", \"bus\", \"pk\"], \"wei__date_end__gte\": [\"today\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir mon bus" + } + }, { "model": "permission.role", "pk": 1, @@ -4433,7 +4721,17 @@ 130, 271, 272, - 273 + 273, + 274, + 275, + 276, + 277, + 278, + 279, + 280, + 281, + 282, + 283 ] } }, @@ -4452,7 +4750,15 @@ 119, 120, 121, - 122 + 122, + 284, + 285, + 286, + 287, + 288, + 289, + 290, + 291 ] } }, @@ -4650,7 +4956,15 @@ 119, 120, 121, - 122 + 122, + 284, + 285, + 286, + 287, + 288, + 289, + 290, + 291 ] } }, diff --git a/apps/wei/migrations/0012_bus_club.py b/apps/wei/migrations/0012_bus_club.py new file mode 100644 index 00000000..80f2e14b --- /dev/null +++ b/apps/wei/migrations/0012_bus_club.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.21 on 2025-05-29 16:16 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('member', '0014_create_bda'), + ('wei', '0011_alter_weiclub_year'), + ] + + operations = [ + migrations.AddField( + model_name='bus', + name='club', + field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='bus', to='member.club', verbose_name='club'), + ), + ] diff --git a/apps/wei/models.py b/apps/wei/models.py index f4169316..475a3f0c 100644 --- a/apps/wei/models.py +++ b/apps/wei/models.py @@ -72,6 +72,15 @@ class Bus(models.Model): default=50, ) + club = models.OneToOneField( + Club, + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="bus", + verbose_name=_("club"), + ) + description = models.TextField( blank=True, default="", diff --git a/apps/wei/templates/wei/bus_detail.html b/apps/wei/templates/wei/bus_detail.html index c8f3ce20..04ef5f9a 100644 --- a/apps/wei/templates/wei/bus_detail.html +++ b/apps/wei/templates/wei/bus_detail.html @@ -16,6 +16,10 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endif %} - {% if can_validate_1a %} - {% trans "Attribute buses" %} - {% endif %} +{% if can_validate_1a %} + {% trans "Attribute buses" %} +{% endif %} + + {% endblock %} {% block extrajavascript %} diff --git a/apps/wei/templates/wei/weimembership_form.html b/apps/wei/templates/wei/weimembership_form.html index ff3024ca..f0f3d800 100644 --- a/apps/wei/templates/wei/weimembership_form.html +++ b/apps/wei/templates/wei/weimembership_form.html @@ -143,25 +143,35 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endblocktrans %} {% else %} - {% if registration.user.note.balance < fee %} -
- {% with pretty_fee=fee|pretty_money %} - {% blocktrans trimmed with balance=registration.user.note.balance|pretty_money %} - The note don't have enough money ({{ balance }}, {{ pretty_fee }} required). - The registration may fail if you don't credit the note now. - {% endblocktrans %} - {% endwith %} -
- {% else %} -
- {% blocktrans trimmed with pretty_fee=fee|pretty_money %} - The note has enough money ({{ pretty_fee }} required), the registration is possible. - {% endblocktrans %} -
- {% endif %} +
+
{% trans "Required payments:" %}
+
    +
  • {% blocktrans trimmed with amount=fee|pretty_money %} + Membership fees: {{ amount }} + {% endblocktrans %}
  • + {% if registration.caution_type == 'note' %} +
  • {% blocktrans trimmed with amount=club.caution_amount|pretty_money %} + Deposit (by Note transaction): {{ amount }} + {% endblocktrans %}
  • +
  • {% blocktrans trimmed with total=total_needed|pretty_money %} + Total needed: {{ total }} + {% endblocktrans %}
  • + {% else %} +
  • {% blocktrans trimmed with amount=club.caution_amount|pretty_money %} + Deposit (by check): {{ amount }} + {% endblocktrans %}
  • +
  • {% blocktrans trimmed with total=fee|pretty_money %} + Total needed: {{ total }} + {% endblocktrans %}
  • + {% endif %} +
+

{% blocktrans trimmed with balance=registration.user.note.balance|pretty_money %} + Current balance: {{ balance }} + {% endblocktrans %}

+
{% endif %} - {% if not registration.caution_check and not registration.first_year %} + {% if not registration.caution_check and not registration.first_year and registration.caution_type == 'check' %}
{% trans "The user didn't give her/his caution check." %}
diff --git a/apps/wei/views.py b/apps/wei/views.py index 99244846..cb75f8f2 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -564,6 +564,8 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView): del form.fields["caution_check"] if "information_json" in form.fields: del form.fields["information_json"] + if "caution_type" in form.fields: + del form.fields["caution_type"] return form @@ -668,6 +670,12 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView): if "information_json" in form.fields: del form.fields["information_json"] + # S'assurer que le champ caution_type est obligatoire + if "caution_type" in form.fields: + form.fields["caution_type"].required = True + form.fields["caution_type"].help_text = _("Choose how you want to pay the deposit") + form.fields["caution_type"].widget = forms.RadioSelect(choices=form.fields["caution_type"].choices) + return form @transaction.atomic @@ -693,6 +701,9 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView): information["preferred_roles_pk"] = [role.pk for role in choose_bus_form.cleaned_data["roles"]] information["preferred_roles_name"] = [role.name for role in choose_bus_form.cleaned_data["roles"]] form.instance.information = information + + # Sauvegarder le type de caution + form.instance.caution_type = form.cleaned_data["caution_type"] form.instance.save() if 'treasury' in settings.INSTALLED_APPS: @@ -767,6 +778,13 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update # Masquer le champ caution_check pour tout le monde dans le formulaire de modification if "caution_check" in form.fields: del form.fields["caution_check"] + + # S'assurer que le champ caution_type est obligatoire pour les 2A+ + if not self.object.first_year and "caution_type" in form.fields: + form.fields["caution_type"].required = True + form.fields["caution_type"].help_text = _("Choose how you want to pay the deposit") + form.fields["caution_type"].widget = forms.RadioSelect(choices=form.fields["caution_type"].choices) + return form def get_membership_form(self, data=None, instance=None): @@ -824,6 +842,10 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update information["preferred_roles_pk"] = [role.pk for role in choose_bus_form.cleaned_data["roles"]] information["preferred_roles_name"] = [role.name for role in choose_bus_form.cleaned_data["roles"]] form.instance.information = information + + # Sauvegarder le type de caution pour les 2A+ + if "caution_type" in form.cleaned_data: + form.instance.caution_type = form.cleaned_data["caution_type"] form.instance.save() return super().form_valid(form) @@ -924,7 +946,14 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): date_start__gte=bde.membership_start, ).exists() - context["fee"] = registration.fee + fee = registration.fee + context["fee"] = fee + + # Calculer le montant total nécessaire (frais + caution si transaction) + total_needed = fee + if registration.caution_type == 'note': + total_needed += registration.wei.caution_amount + context["total_needed"] = total_needed form = context["form"] if registration.soge_credit: @@ -948,12 +977,22 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): # Ajouter le champ caution_check uniquement pour les non-première année et le rendre obligatoire if not registration.first_year: - form.fields["caution_check"] = forms.BooleanField( - required=True, - initial=registration.caution_check, - label=_("Caution check given"), - help_text=_("Please make sure the check is given before validating the registration") - ) + if registration.caution_type == 'check': + form.fields["caution_check"] = forms.BooleanField( + required=True, + initial=registration.caution_check, + label=_("Caution check given"), + help_text=_("Please make sure the check is given before validating the registration") + ) + else: + form.fields["caution_check"] = forms.BooleanField( + required=True, + initial=False, + label=_("Create deposit transaction"), + help_text=_("A transaction of %(amount).2f€ will be created from the user's Note account") % { + 'amount': registration.wei.caution_amount / 100 + } + ) if registration.soge_credit: form.fields["credit_type"].disabled = True @@ -1037,10 +1076,20 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): if credit_type is None or registration.soge_credit: credit_amount = 0 - if not registration.soge_credit and user.note.balance + credit_amount < fee: - # Users must have money before registering to the WEI. + # Calculer le montant total nécessaire (frais + caution si transaction) + total_needed = fee + if registration.caution_type == 'note': + total_needed += club.caution_amount + + # Vérifier que l'utilisateur a assez d'argent pour tout payer + if not registration.soge_credit and user.note.balance + credit_amount < total_needed: form.add_error('credit_type', - _("This user don't have enough money to join this club, and can't have a negative balance.")) + _("This user doesn't have enough money to join this club and pay the deposit. " + "Current balance: %(balance)d€, credit: %(credit)d€, needed: %(needed)d€") % { + 'balance': user.note.balance, + 'credit': credit_amount, + 'needed': total_needed, + }) return super().form_invalid(form) if credit_amount: @@ -1080,6 +1129,18 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): membership.refresh_from_db() membership.roles.add(WEIRole.objects.get(name="Adhérent⋅e WEI")) + # Créer la transaction de caution si nécessaire + if registration.caution_type == 'note': + from note.models import Transaction + Transaction.objects.create( + source=user.note, + destination=club.note, + quantity=1, + amount=club.caution_amount, + reason=_("Caution %(name)s") % {'name': club.name}, + valid=True, + ) + return super().form_valid(form) def get_success_url(self): diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 702e52da..f4698e23 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-05-27 16:46+0200\n" +"POT-Creation-Date: 2025-06-02 00:58+0200\n" "PO-Revision-Date: 2020-11-16 20:02+0000\n" "Last-Translator: bleizi \n" "Language-Team: German \n" @@ -66,7 +66,7 @@ msgstr "Sie dürfen höchstens 3 Leute zu dieser Veranstaltung einladen." #: apps/note/models/transactions.py:46 apps/note/models/transactions.py:299 #: apps/permission/models.py:329 #: 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/models.py:72 apps/wei/models.py:145 apps/wei/tables.py:282 #: apps/wei/templates/wei/base.html:26 #: apps/wei/templates/wei/weimembership_form.html:14 apps/wrapped/models.py:16 msgid "name" @@ -101,7 +101,7 @@ msgstr "Vearnstaltungarte" #: apps/activity/models.py:68 #: apps/activity/templates/activity/includes/activity_info.html:19 #: apps/note/models/transactions.py:82 apps/permission/models.py:109 -#: apps/permission/models.py:188 apps/wei/models.py:78 apps/wei/models.py:142 +#: apps/permission/models.py:188 apps/wei/models.py:92 apps/wei/models.py:156 msgid "description" msgstr "Beschreibung" @@ -122,7 +122,7 @@ msgstr "Type" #: 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:294 -#: apps/wei/models.py:171 apps/wei/templates/wei/attribute_bus_1A.html:13 +#: apps/wei/models.py:185 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/templates/wei/survey.html:15 msgid "user" msgstr "User" @@ -295,14 +295,14 @@ msgstr "Type" #: 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:107 +#: apps/wei/forms/registration.py:116 msgid "Last name" msgstr "Nachname" #: 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:112 +#: apps/wei/forms/registration.py:121 msgid "First name" msgstr "Vorname" @@ -1030,12 +1030,12 @@ msgid "Check this case if the Société Générale paid the inscription." msgstr "Die Société Générale die Mitgliedschaft bezahlt." #: apps/member/forms.py:185 apps/registration/forms.py:78 -#: apps/wei/forms/registration.py:94 +#: apps/wei/forms/registration.py:103 msgid "Credit type" msgstr "Kredittype" #: apps/member/forms.py:186 apps/registration/forms.py:79 -#: apps/wei/forms/registration.py:95 +#: apps/wei/forms/registration.py:104 msgid "No credit" msgstr "Kein Kredit" @@ -1044,13 +1044,13 @@ msgid "You can credit the note of the user." msgstr "Sie dûrfen diese Note kreditieren." #: apps/member/forms.py:192 apps/registration/forms.py:84 -#: apps/wei/forms/registration.py:100 +#: apps/wei/forms/registration.py:109 msgid "Credit amount" msgstr "Kreditanzahl" #: 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:117 +#: apps/wei/forms/registration.py:126 msgid "Bank" msgstr "Bank" @@ -1257,7 +1257,7 @@ msgstr "Ihre Note Kfet Konto bestätigen" #: apps/member/templates/member/includes/club_info.html:55 #: apps/member/templates/member/includes/profile_info.html:40 #: apps/registration/templates/registration/future_profile_detail.html:22 -#: apps/wei/templates/wei/base.html:70 +#: apps/wei/templates/wei/base.html:68 #: apps/wei/templates/wei/weimembership_form.html:20 msgid "email" msgstr "Email" @@ -1311,7 +1311,7 @@ msgid "add to registration form" msgstr "Registrierung validieren" #: apps/member/models.py:268 apps/member/models.py:331 -#: apps/note/models/notes.py:176 +#: apps/note/models/notes.py:176 apps/wei/models.py:86 msgid "club" msgstr "Club" @@ -1514,13 +1514,13 @@ msgstr "Mitgliedsachftpreis" #: apps/member/templates/member/includes/club_info.html:43 #: apps/member/templates/member/includes/profile_info.html:55 #: apps/treasury/templates/treasury/sogecredit_detail.html:24 -#: apps/wei/templates/wei/base.html:60 +#: apps/wei/templates/wei/base.html:58 msgid "balance" msgstr "Kontostand" #: apps/member/templates/member/includes/club_info.html:47 #: apps/member/templates/member/includes/profile_info.html:20 -#: apps/note/models/notes.py:287 apps/wei/templates/wei/base.html:66 +#: apps/note/models/notes.py:287 apps/wei/templates/wei/base.html:64 msgid "aliases" msgstr "Aliases" @@ -1702,7 +1702,7 @@ msgstr "Club bearbeiten" msgid "Add new member to the club" msgstr "Neue Mitglieder" -#: apps/member/views.py:750 apps/wei/views.py:1040 +#: apps/member/views.py:750 msgid "" "This user don't have enough money to join this club, and can't have a " "negative balance." @@ -2038,8 +2038,8 @@ msgstr "" "Zahlungsmethode zugeordnet ist, und einem User oder einem Club möglich" #: apps/note/models/transactions.py:357 apps/note/models/transactions.py:360 -#: apps/note/models/transactions.py:363 apps/wei/views.py:1045 -#: apps/wei/views.py:1049 +#: apps/note/models/transactions.py:363 apps/wei/views.py:1097 +#: apps/wei/views.py:1101 #: env/lib/python3.11/site-packages/django/forms/fields.py:91 msgid "This field is required." msgstr "Dies ist ein Pflichtfeld." @@ -2076,8 +2076,8 @@ msgstr "Neue Bus" #: apps/note/tables.py:262 apps/note/templates/note/conso_form.html:151 #: apps/wei/tables.py:49 apps/wei/tables.py:50 -#: apps/wei/templates/wei/base.html:89 -#: apps/wei/templates/wei/bus_detail.html:20 +#: apps/wei/templates/wei/base.html:87 +#: apps/wei/templates/wei/bus_detail.html:24 #: apps/wei/templates/wei/busteam_detail.html:20 #: apps/wei/templates/wei/busteam_detail.html:42 #: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:37 @@ -2552,7 +2552,7 @@ msgstr "Sie haben bereits ein Konto in der Société générale eröffnet." #: apps/registration/templates/registration/future_profile_detail.html:73 #: apps/wei/templates/wei/weimembership_form.html:127 -#: apps/wei/templates/wei/weimembership_form.html:186 +#: apps/wei/templates/wei/weimembership_form.html:196 msgid "Validate registration" msgstr "Registrierung validieren" @@ -3089,22 +3089,22 @@ msgstr "Kreditliste von Société générale" msgid "Manage credits from the Société générale" msgstr "Krediten von der Société générale handeln" -#: apps/wei/apps.py:10 apps/wei/models.py:37 apps/wei/models.py:38 -#: apps/wei/models.py:62 apps/wei/models.py:178 +#: apps/wei/apps.py:10 apps/wei/models.py:42 apps/wei/models.py:43 +#: apps/wei/models.py:67 apps/wei/models.py:192 #: note_kfet/templates/base.html:108 msgid "WEI" msgstr "WEI" -#: apps/wei/forms/registration.py:36 +#: apps/wei/forms/registration.py:37 msgid "The selected user is not validated. Please validate its account first" msgstr "" -#: apps/wei/forms/registration.py:62 apps/wei/models.py:126 -#: apps/wei/models.py:324 +#: apps/wei/forms/registration.py:71 apps/wei/models.py:140 +#: apps/wei/models.py:348 msgid "bus" msgstr "Bus" -#: apps/wei/forms/registration.py:63 +#: apps/wei/forms/registration.py:72 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." @@ -3113,11 +3113,11 @@ msgstr "" "einen Bus und ein Team zuzuweisen, insbesondere wenn Sie ein freies Elektron " "sind." -#: apps/wei/forms/registration.py:70 +#: apps/wei/forms/registration.py:79 msgid "Team" msgstr "Team" -#: apps/wei/forms/registration.py:72 +#: apps/wei/forms/registration.py:81 msgid "" "Leave this field empty if you won't be in a team (staff, bus chief, free " "electron)" @@ -3125,16 +3125,16 @@ msgstr "" "Lassen Sie dieses Feld leer, wenn Sie nicht in einem Team sind (Mitarbeiter, " "Buschef, freies Elektron)" -#: apps/wei/forms/registration.py:78 apps/wei/forms/registration.py:88 -#: apps/wei/models.py:160 +#: apps/wei/forms/registration.py:87 apps/wei/forms/registration.py:97 +#: apps/wei/models.py:174 msgid "WEI Roles" msgstr "WEI Rollen" -#: apps/wei/forms/registration.py:79 +#: apps/wei/forms/registration.py:88 msgid "Select the roles that you are interested in." msgstr "Wählen Sie die Rollen aus, an denen Sie interessiert sind." -#: apps/wei/forms/registration.py:125 +#: apps/wei/forms/registration.py:134 msgid "This team doesn't belong to the given bus." msgstr "Dieses Team gehört nicht zum angegebenen Bus." @@ -3156,118 +3156,140 @@ msgstr "Anfangsdatum" msgid "date end" msgstr "Abschlussdatum" -#: apps/wei/models.py:71 apps/wei/tables.py:305 +#: apps/wei/models.py:37 +#, fuzzy +#| msgid "total amount" +msgid "caution amount" +msgstr "Totalanzahlt" + +#: apps/wei/models.py:76 apps/wei/tables.py:305 #, fuzzy #| msgid "The user joined the bus" msgid "seat count in the bus" msgstr "Der Benutzer ist dem Bus beigetreten" -#: apps/wei/models.py:83 +#: apps/wei/models.py:97 msgid "survey information" msgstr "Umfrage Infos" -#: apps/wei/models.py:84 +#: apps/wei/models.py:98 msgid "Information about the survey for new members, encoded in JSON" msgstr "Informationen zur Umfrage für neue Mitglieder, codiert in JSON" -#: apps/wei/models.py:88 +#: apps/wei/models.py:102 msgid "Bus" msgstr "Bus" -#: apps/wei/models.py:89 apps/wei/templates/wei/weiclub_detail.html:51 +#: apps/wei/models.py:103 apps/wei/templates/wei/weiclub_detail.html:51 msgid "Buses" msgstr "Buses" -#: apps/wei/models.py:135 +#: apps/wei/models.py:149 msgid "color" msgstr "Farbe" -#: apps/wei/models.py:136 +#: apps/wei/models.py:150 msgid "The color of the T-Shirt, stored with its number equivalent" msgstr "Die Farbe des T-Shirts, gespeichert mit der entsprechenden Nummer" -#: apps/wei/models.py:147 +#: apps/wei/models.py:161 msgid "Bus team" msgstr "Bus Team" -#: apps/wei/models.py:148 +#: apps/wei/models.py:162 msgid "Bus teams" msgstr "Bus Teams" -#: apps/wei/models.py:159 +#: apps/wei/models.py:173 msgid "WEI Role" msgstr "WEI Rolle" -#: apps/wei/models.py:183 +#: apps/wei/models.py:197 msgid "Credit from Société générale" msgstr "Kredit von der Société générale" -#: apps/wei/models.py:188 apps/wei/views.py:951 +#: apps/wei/models.py:202 apps/wei/views.py:984 msgid "Caution check given" msgstr "Caution check given" -#: apps/wei/models.py:192 apps/wei/templates/wei/weimembership_form.html:64 +#: apps/wei/models.py:208 +msgid "Check" +msgstr "" + +#: apps/wei/models.py:209 +#, fuzzy +#| msgid "transactions" +msgid "Note transaction" +msgstr "Transaktionen" + +#: apps/wei/models.py:212 +#, fuzzy +#| msgid "created at" +msgid "caution type" +msgstr "erschafft am" + +#: apps/wei/models.py:216 apps/wei/templates/wei/weimembership_form.html:64 msgid "birth date" msgstr "Geburtsdatum" -#: apps/wei/models.py:198 apps/wei/models.py:208 +#: apps/wei/models.py:222 apps/wei/models.py:232 msgid "Male" msgstr "Männlich" -#: apps/wei/models.py:199 apps/wei/models.py:209 +#: apps/wei/models.py:223 apps/wei/models.py:233 msgid "Female" msgstr "Weiblich" -#: apps/wei/models.py:200 +#: apps/wei/models.py:224 msgid "Non binary" msgstr "Nicht binär" -#: apps/wei/models.py:202 apps/wei/templates/wei/attribute_bus_1A.html:22 +#: apps/wei/models.py:226 apps/wei/templates/wei/attribute_bus_1A.html:22 #: apps/wei/templates/wei/weimembership_form.html:55 msgid "gender" msgstr "Geschlecht" -#: apps/wei/models.py:210 +#: apps/wei/models.py:234 msgid "Unisex" msgstr "Unisex" -#: apps/wei/models.py:213 apps/wei/templates/wei/weimembership_form.html:58 +#: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:58 msgid "clothing cut" msgstr "Kleidung Schnitt" -#: apps/wei/models.py:226 apps/wei/templates/wei/weimembership_form.html:61 +#: apps/wei/models.py:250 apps/wei/templates/wei/weimembership_form.html:61 msgid "clothing size" msgstr "Kleidergröße" -#: apps/wei/models.py:232 +#: apps/wei/models.py:256 msgid "health issues" msgstr "Gesundheitsprobleme" -#: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:70 +#: apps/wei/models.py:261 apps/wei/templates/wei/weimembership_form.html:70 msgid "emergency contact name" msgstr "Notfall-Kontakt" -#: apps/wei/models.py:238 +#: apps/wei/models.py:262 msgid "The emergency contact must not be a WEI participant" msgstr "Der Notfallkontakt darf kein WEI-Teilnehmer sein" -#: apps/wei/models.py:243 apps/wei/templates/wei/weimembership_form.html:73 +#: apps/wei/models.py:267 apps/wei/templates/wei/weimembership_form.html:73 msgid "emergency contact phone" msgstr "Notfallkontakttelefon" -#: apps/wei/models.py:248 apps/wei/templates/wei/weimembership_form.html:52 +#: apps/wei/models.py:272 apps/wei/templates/wei/weimembership_form.html:52 msgid "first year" msgstr "Erste Jahr" -#: apps/wei/models.py:249 +#: apps/wei/models.py:273 msgid "Tells if the user is new in the school." msgstr "Gibt an, ob der USer neu in der Schule ist." -#: apps/wei/models.py:254 +#: apps/wei/models.py:278 msgid "registration information" msgstr "Registrierung Detailen" -#: apps/wei/models.py:255 +#: apps/wei/models.py:279 msgid "" "Information about the registration (buses for old members, survey for the " "new members), encoded in JSON" @@ -3275,27 +3297,27 @@ msgstr "" "Informationen zur Registrierung (Busse für alte Mitglieder, Umfrage für neue " "Mitglieder), verschlüsselt in JSON" -#: apps/wei/models.py:261 +#: apps/wei/models.py:285 msgid "WEI User" msgstr "WEI User" -#: apps/wei/models.py:262 +#: apps/wei/models.py:286 msgid "WEI Users" msgstr "WEI Users" -#: apps/wei/models.py:334 +#: apps/wei/models.py:358 msgid "team" msgstr "Team" -#: apps/wei/models.py:344 +#: apps/wei/models.py:368 msgid "WEI registration" msgstr "WEI Registrierung" -#: apps/wei/models.py:348 +#: apps/wei/models.py:372 msgid "WEI membership" msgstr "WEI Mitgliedschaft" -#: apps/wei/models.py:349 +#: apps/wei/models.py:373 msgid "WEI memberships" msgstr "WEI Mitgliedschaften" @@ -3327,7 +3349,7 @@ msgstr "Jahr" msgid "preferred bus" msgstr "bevorzugter Bus" -#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:32 +#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:36 #: apps/wei/templates/wei/busteam_detail.html:52 msgid "Teams" msgstr "Teams" @@ -3401,44 +3423,52 @@ msgstr "Tastenliste" msgid "WEI fee (paid students)" msgstr "WEI Preis (bezahlte Studenten)" -#: apps/wei/templates/wei/base.html:47 apps/wei/templates/wei/base.html:54 -msgid "The BDE membership is included in the WEI registration." -msgstr "Die BDE-Mitgliedschaft ist in der WEI-Registrierung enthalten." - -#: apps/wei/templates/wei/base.html:51 +#: apps/wei/templates/wei/base.html:47 msgid "WEI fee (unpaid students)" msgstr "WEI Preis (unbezahlte Studenten)" -#: apps/wei/templates/wei/base.html:76 +#: apps/wei/templates/wei/base.html:53 +#, fuzzy +#| msgid "total amount" +msgid "Caution amount" +msgstr "Totalanzahlt" + +#: apps/wei/templates/wei/base.html:74 msgid "WEI list" msgstr "WEI Liste" -#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:557 +#: apps/wei/templates/wei/base.html:79 apps/wei/views.py:550 msgid "Register 1A" msgstr "1A Registrieren" -#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:649 +#: apps/wei/templates/wei/base.html:83 apps/wei/views.py:644 msgid "Register 2A+" msgstr "2A+ Registrieren" -#: apps/wei/templates/wei/base.html:93 +#: apps/wei/templates/wei/base.html:91 msgid "Add bus" msgstr "Neue Bus" -#: apps/wei/templates/wei/base.html:97 +#: apps/wei/templates/wei/base.html:95 msgid "View WEI" msgstr "WEI schauen" -#: apps/wei/templates/wei/bus_detail.html:22 +#: apps/wei/templates/wei/bus_detail.html:21 +#, fuzzy +#| msgid "club" +msgid "View club" +msgstr "Club" + +#: apps/wei/templates/wei/bus_detail.html:26 #: apps/wei/templates/wei/busteam_detail.html:24 msgid "Add team" msgstr "Neue Team" -#: apps/wei/templates/wei/bus_detail.html:45 +#: apps/wei/templates/wei/bus_detail.html:49 msgid "Members" msgstr "Mitglied" -#: apps/wei/templates/wei/bus_detail.html:54 +#: apps/wei/templates/wei/bus_detail.html:58 #: apps/wei/templates/wei/busteam_detail.html:62 #: apps/wei/templates/wei/weimembership_list.html:31 msgid "View as PDF" @@ -3446,8 +3476,8 @@ msgstr "Als PDF schauen" #: 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:1095 -#: apps/wei/views.py:1150 apps/wei/views.py:1197 +#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1159 +#: apps/wei/views.py:1214 apps/wei/views.py:1261 msgid "Survey WEI" msgstr "WEI Umfrage" @@ -3491,7 +3521,7 @@ msgstr "Unvalidierte Registrierungen" msgid "Attribute buses" msgstr "" -#: apps/wei/templates/wei/weiclub_list.html:14 apps/wei/views.py:83 +#: apps/wei/templates/wei/weiclub_list.html:14 apps/wei/views.py:82 msgid "Create WEI" msgstr "Neue WEI" @@ -3575,29 +3605,42 @@ msgstr "" "validieren, sobald die Bank die Erstellung des Kontos validiert hat, oder " "die Zahlungsmethode ändern." +#: apps/wei/templates/wei/weimembership_form.html:147 +msgid "Required payments:" +msgstr "" + #: apps/wei/templates/wei/weimembership_form.html:149 -#, python-format -msgid "" -"The note don't have enough money (%(balance)s, %(pretty_fee)s required). The " -"registration may fail if you don't credit the note now." -msgstr "" -"Die Note hat nicht genug Geld (%(balance)s,%(pretty_fee)s erforderlich). Die " -"Registrierung kann fehlschlagen, wenn Sie die Note jetzt nicht gutschreiben." +#, fuzzy, python-format +#| msgid "membership fee (paid students)" +msgid "Membership fees: %(amount)s" +msgstr "Mitgliedschaftpreis (bezahlte Studenten)" -#: apps/wei/templates/wei/weimembership_form.html:157 +#: apps/wei/templates/wei/weimembership_form.html:153 #, python-format -msgid "" -"The note has enough money (%(pretty_fee)s required), the registration is " -"possible." +msgid "Deposit (by Note transaction): %(amount)s" msgstr "" -"Die Note hat genug Geld (%(pretty_fee)s erforderlich), die Registrierung ist " -"möglich." -#: apps/wei/templates/wei/weimembership_form.html:166 +#: apps/wei/templates/wei/weimembership_form.html:156 +#: apps/wei/templates/wei/weimembership_form.html:163 +#, python-format +msgid "Total needed: %(total)s" +msgstr "" + +#: apps/wei/templates/wei/weimembership_form.html:160 +#, python-format +msgid "Deposit (by check): %(amount)s" +msgstr "" + +#: apps/wei/templates/wei/weimembership_form.html:168 +#, python-format +msgid "Current balance: %(balance)s" +msgstr "" + +#: apps/wei/templates/wei/weimembership_form.html:176 msgid "The user didn't give her/his caution check." msgstr "Der User hat nicht sein Vorsichtsprüfung gegeben." -#: apps/wei/templates/wei/weimembership_form.html:174 +#: apps/wei/templates/wei/weimembership_form.html:184 msgid "" "This user is not a member of the Kfet club for the coming year. The " "membership will be processed automatically, the WEI registration includes " @@ -3633,67 +3676,67 @@ msgstr "Bei diesem Muster wurde keine Vorregistrierung gefunden." msgid "View validated memberships..." msgstr "Validierte Mitgliedschaften anzeigen ..." -#: apps/wei/views.py:62 +#: apps/wei/views.py:61 msgid "Search WEI" msgstr "WEI finden" -#: apps/wei/views.py:113 +#: apps/wei/views.py:112 msgid "WEI Detail" msgstr "WEI Infos" -#: apps/wei/views.py:213 +#: apps/wei/views.py:212 msgid "View members of the WEI" msgstr "Mitglied der WEI schauen" -#: apps/wei/views.py:246 +#: apps/wei/views.py:245 msgid "Find WEI Membership" msgstr "WEI Mitgliedschaft finden" -#: apps/wei/views.py:256 +#: apps/wei/views.py:255 msgid "View registrations to the WEI" msgstr "Mitglied der WEI schauen" -#: apps/wei/views.py:285 +#: apps/wei/views.py:284 msgid "Find WEI Registration" msgstr "WEI Registrierung finden" -#: apps/wei/views.py:296 +#: apps/wei/views.py:295 msgid "Update the WEI" msgstr "WEI bearbeiten" -#: apps/wei/views.py:317 +#: apps/wei/views.py:316 msgid "Create new bus" msgstr "Neue Bus" -#: apps/wei/views.py:355 +#: apps/wei/views.py:354 msgid "Update bus" msgstr "Bus bearbeiten" -#: apps/wei/views.py:387 +#: apps/wei/views.py:386 msgid "Manage bus" msgstr "Bus ändern" -#: apps/wei/views.py:414 +#: apps/wei/views.py:413 msgid "Create new team" msgstr "Neue Bus Team" -#: apps/wei/views.py:461 +#: apps/wei/views.py:457 msgid "Update team" msgstr "Team bearbeiten" -#: apps/wei/views.py:499 +#: apps/wei/views.py:492 msgid "Manage WEI team" msgstr "WEI Team bearbeiten" -#: apps/wei/views.py:521 +#: apps/wei/views.py:514 msgid "Register first year student to the WEI" msgstr "Registrieren Sie den Erstsemester beim WEI" -#: apps/wei/views.py:585 apps/wei/views.py:688 +#: apps/wei/views.py:580 apps/wei/views.py:689 msgid "This user is already registered to this WEI." msgstr "Dieser Benutzer ist bereits bei dieser WEI registriert." -#: apps/wei/views.py:590 +#: apps/wei/views.py:585 msgid "" "This user can't be in her/his first year since he/she has already " "participated to a WEI." @@ -3701,25 +3744,29 @@ msgstr "" "Dieser Benutzer kann nicht in seinem ersten Jahr sein, da er bereits an " "einer WEI teilgenommen hat." -#: apps/wei/views.py:613 +#: apps/wei/views.py:608 msgid "Register old student to the WEI" msgstr "Registrieren Sie einen alten Studenten beim WEI" -#: apps/wei/views.py:668 apps/wei/views.py:764 +#: apps/wei/views.py:663 apps/wei/views.py:768 msgid "You already opened an account in the Société générale." msgstr "Sie haben bereits ein Konto in der Société générale eröffnet." -#: apps/wei/views.py:724 +#: apps/wei/views.py:676 apps/wei/views.py:785 +msgid "Choose how you want to pay the deposit" +msgstr "" + +#: apps/wei/views.py:728 msgid "Update WEI Registration" msgstr "WEI Registrierung aktualisieren" -#: apps/wei/views.py:799 +#: apps/wei/views.py:810 #, fuzzy #| msgid "The BDE membership is included in the WEI registration." msgid "No membership found for this registration" msgstr "Die BDE-Mitgliedschaft ist in der WEI-Registrierung enthalten." -#: apps/wei/views.py:808 +#: apps/wei/views.py:819 #, fuzzy #| msgid "" #| "You don't have the permission to add an instance of model {app_label}." @@ -3729,7 +3776,7 @@ msgstr "" "Sie haben nicht die Berechtigung, eine Instanz von model {app_label}. " "{model_name} hinzufügen." -#: apps/wei/views.py:814 +#: apps/wei/views.py:825 #, fuzzy, python-format #| msgid "" #| "You don't have the permission to delete this instance of model " @@ -3739,25 +3786,19 @@ msgstr "" "Sie haben nicht die Berechtigung, eine Instanz von model {app_label}. " "{model_name} zulöschen." -#: apps/wei/views.py:855 +#: apps/wei/views.py:870 msgid "Delete WEI registration" msgstr "WEI Registrierung löschen" -#: apps/wei/views.py:866 +#: apps/wei/views.py:881 msgid "You don't have the right to delete this WEI registration." msgstr "Sie haben nicht das Recht, diese WEI-Registrierung zu löschen." -#: apps/wei/views.py:884 +#: apps/wei/views.py:899 msgid "Validate WEI registration" msgstr "Überprüfen Sie die WEI-Registrierung" -#: apps/wei/views.py:889 -#, fuzzy -#| msgid "You don't have the right to delete this WEI registration." -msgid "You don't have the permission to validate registrations" -msgstr "Sie haben nicht das Recht, diese WEI-Registrierung zu löschen." - -#: apps/wei/views.py:952 +#: apps/wei/views.py:985 #, fuzzy #| msgid "Please ask the user to credit its note before deleting this credit." msgid "Please make sure the check is given before validating the registration" @@ -3765,14 +3806,50 @@ msgstr "" "Bitte bitten Sie den Benutzer, seine Note gutzuschreiben, bevor Sie diese " "Kredit löschen." -#: apps/wei/views.py:1290 +#: apps/wei/views.py:991 +#, fuzzy +#| msgid "credit transaction" +msgid "Create deposit transaction" +msgstr "Kredit Transaktion" + +#: apps/wei/views.py:992 +#, python-format +msgid "" +"A transaction of %(amount).2f€ will be created from the user's Note account" +msgstr "" + +#: apps/wei/views.py:1087 +#, fuzzy, python-format +#| msgid "" +#| "This user don't have enough money to join this club, and can't have a " +#| "negative balance." +msgid "" +"This user doesn't have enough money to join this club and pay the deposit. " +"Current balance: %(balance)d€, credit: %(credit)d€, needed: %(needed)d€" +msgstr "" +"Diese User hat nicht genug Geld um Mitglied zu werden, und darf nich im Rot " +"sein." + +#: apps/wei/views.py:1140 +#, fuzzy, python-format +#| msgid "created at" +msgid "Caution %(name)s" +msgstr "erschafft am" + +#: apps/wei/views.py:1354 msgid "Attribute buses to first year members" msgstr "" -#: apps/wei/views.py:1315 +#: apps/wei/views.py:1379 msgid "Attribute bus" msgstr "" +#: apps/wei/views.py:1419 +msgid "" +"No first year student without a bus found. Either all of them have a bus, or " +"none has filled the survey yet." +msgstr "" + #: apps/wrapped/apps.py:10 msgid "wrapped" msgstr "" @@ -5769,6 +5846,31 @@ msgstr "" "müssen Ihre E-Mail-Adresse auch überprüfen, indem Sie dem Link folgen, den " "Sie erhalten haben." +#~ msgid "The BDE membership is included in the WEI registration." +#~ msgstr "Die BDE-Mitgliedschaft ist in der WEI-Registrierung enthalten." + +#, python-format +#~ msgid "" +#~ "The note don't have enough money (%(balance)s, %(pretty_fee)s required). " +#~ "The registration may fail if you don't credit the note now." +#~ msgstr "" +#~ "Die Note hat nicht genug Geld (%(balance)s,%(pretty_fee)s erforderlich). " +#~ "Die Registrierung kann fehlschlagen, wenn Sie die Note jetzt nicht " +#~ "gutschreiben." + +#, python-format +#~ msgid "" +#~ "The note has enough money (%(pretty_fee)s required), the registration is " +#~ "possible." +#~ msgstr "" +#~ "Die Note hat genug Geld (%(pretty_fee)s erforderlich), die Registrierung " +#~ "ist möglich." + +#, fuzzy +#~| msgid "You don't have the right to delete this WEI registration." +#~ msgid "You don't have the permission to validate registrations" +#~ msgstr "Sie haben nicht das Recht, diese WEI-Registrierung zu löschen." + #, fuzzy #~| msgid "active" #~ msgid "is active" @@ -5794,11 +5896,6 @@ msgstr "" #~ msgid "View details" #~ msgstr "Profile detail" -#, fuzzy -#~| msgid "created at" -#~ msgid "Creation date" -#~ msgstr "erschafft am" - #, fuzzy #~| msgid "There is no results." #~ msgid "There is no meal." diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index e1896993..cee9810f 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-05-27 16:46+0200\n" +"POT-Creation-Date: 2025-06-02 00:58+0200\n" "PO-Revision-Date: 2022-04-11 23:12+0200\n" "Last-Translator: bleizi \n" "Language-Team: \n" @@ -65,7 +65,7 @@ msgstr "Usted no puede invitar más de 3 persona a esta actividad." #: apps/note/models/transactions.py:46 apps/note/models/transactions.py:299 #: apps/permission/models.py:329 #: 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/models.py:72 apps/wei/models.py:145 apps/wei/tables.py:282 #: apps/wei/templates/wei/base.html:26 #: apps/wei/templates/wei/weimembership_form.html:14 apps/wrapped/models.py:16 msgid "name" @@ -100,7 +100,7 @@ msgstr "tipos de actividad" #: apps/activity/models.py:68 #: apps/activity/templates/activity/includes/activity_info.html:19 #: apps/note/models/transactions.py:82 apps/permission/models.py:109 -#: apps/permission/models.py:188 apps/wei/models.py:78 apps/wei/models.py:142 +#: apps/permission/models.py:188 apps/wei/models.py:92 apps/wei/models.py:156 msgid "description" msgstr "descripción" @@ -121,7 +121,7 @@ msgstr "tipo" #: 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:294 -#: apps/wei/models.py:171 apps/wei/templates/wei/attribute_bus_1A.html:13 +#: apps/wei/models.py:185 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/templates/wei/survey.html:15 msgid "user" msgstr "usuario" @@ -294,14 +294,14 @@ msgstr "Tipo" #: 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:107 +#: apps/wei/forms/registration.py:116 msgid "Last name" msgstr "Apellido" #: 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:112 +#: apps/wei/forms/registration.py:121 msgid "First name" msgstr "Nombre" @@ -1027,12 +1027,12 @@ msgid "Check this case if the Société Générale paid the inscription." msgstr "Marcar esta casilla si Société Générale pagó la registración." #: apps/member/forms.py:185 apps/registration/forms.py:78 -#: apps/wei/forms/registration.py:94 +#: apps/wei/forms/registration.py:103 msgid "Credit type" msgstr "Tipo de crédito" #: apps/member/forms.py:186 apps/registration/forms.py:79 -#: apps/wei/forms/registration.py:95 +#: apps/wei/forms/registration.py:104 msgid "No credit" msgstr "No crédito" @@ -1041,13 +1041,13 @@ msgid "You can credit the note of the user." msgstr "Usted puede acreditar la note del usuario." #: apps/member/forms.py:192 apps/registration/forms.py:84 -#: apps/wei/forms/registration.py:100 +#: apps/wei/forms/registration.py:109 msgid "Credit amount" msgstr "Valor del crédito" #: 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:117 +#: apps/wei/forms/registration.py:126 msgid "Bank" msgstr "Banco" @@ -1252,7 +1252,7 @@ msgstr "Active su cuenta Note Kfet" #: apps/member/templates/member/includes/club_info.html:55 #: apps/member/templates/member/includes/profile_info.html:40 #: apps/registration/templates/registration/future_profile_detail.html:22 -#: apps/wei/templates/wei/base.html:70 +#: apps/wei/templates/wei/base.html:68 #: apps/wei/templates/wei/weimembership_form.html:20 msgid "email" msgstr "correo electrónico" @@ -1305,7 +1305,7 @@ msgid "add to registration form" msgstr "Validar la afiliación" #: apps/member/models.py:268 apps/member/models.py:331 -#: apps/note/models/notes.py:176 +#: apps/note/models/notes.py:176 apps/wei/models.py:86 msgid "club" msgstr "club" @@ -1505,13 +1505,13 @@ msgstr "pago de afiliación" #: apps/member/templates/member/includes/club_info.html:43 #: apps/member/templates/member/includes/profile_info.html:55 #: apps/treasury/templates/treasury/sogecredit_detail.html:24 -#: apps/wei/templates/wei/base.html:60 +#: apps/wei/templates/wei/base.html:58 msgid "balance" msgstr "saldo de la cuenta" #: apps/member/templates/member/includes/club_info.html:47 #: apps/member/templates/member/includes/profile_info.html:20 -#: apps/note/models/notes.py:287 apps/wei/templates/wei/base.html:66 +#: apps/note/models/notes.py:287 apps/wei/templates/wei/base.html:64 msgid "aliases" msgstr "alias" @@ -1689,7 +1689,7 @@ msgstr "Modificar el club" msgid "Add new member to the club" msgstr "Añadir un nuevo miembro al club" -#: apps/member/views.py:750 apps/wei/views.py:1040 +#: apps/member/views.py:750 msgid "" "This user don't have enough money to join this club, and can't have a " "negative balance." @@ -2025,8 +2025,8 @@ msgstr "" "pago y un usuario o un club" #: apps/note/models/transactions.py:357 apps/note/models/transactions.py:360 -#: apps/note/models/transactions.py:363 apps/wei/views.py:1045 -#: apps/wei/views.py:1049 +#: apps/note/models/transactions.py:363 apps/wei/views.py:1097 +#: apps/wei/views.py:1101 #: env/lib/python3.11/site-packages/django/forms/fields.py:91 msgid "This field is required." msgstr "Este campo es obligatorio." @@ -2061,8 +2061,8 @@ msgstr "Añadir en retorno" #: apps/note/tables.py:262 apps/note/templates/note/conso_form.html:151 #: apps/wei/tables.py:49 apps/wei/tables.py:50 -#: apps/wei/templates/wei/base.html:89 -#: apps/wei/templates/wei/bus_detail.html:20 +#: apps/wei/templates/wei/base.html:87 +#: apps/wei/templates/wei/bus_detail.html:24 #: apps/wei/templates/wei/busteam_detail.html:20 #: apps/wei/templates/wei/busteam_detail.html:42 #: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:37 @@ -2528,7 +2528,7 @@ msgstr "El usuario declara que ya abrió una cuenta a la Société Générale." #: apps/registration/templates/registration/future_profile_detail.html:73 #: apps/wei/templates/wei/weimembership_form.html:127 -#: apps/wei/templates/wei/weimembership_form.html:186 +#: apps/wei/templates/wei/weimembership_form.html:196 msgid "Validate registration" msgstr "Validar la afiliación" @@ -3056,23 +3056,23 @@ msgstr "Lista de los créditos de la Société Générale" msgid "Manage credits from the Société générale" msgstr "Gestionar los créditos de la Société Générale" -#: apps/wei/apps.py:10 apps/wei/models.py:37 apps/wei/models.py:38 -#: apps/wei/models.py:62 apps/wei/models.py:178 +#: apps/wei/apps.py:10 apps/wei/models.py:42 apps/wei/models.py:43 +#: apps/wei/models.py:67 apps/wei/models.py:192 #: note_kfet/templates/base.html:108 msgid "WEI" msgstr "WEI" -#: apps/wei/forms/registration.py:36 +#: apps/wei/forms/registration.py:37 msgid "The selected user is not validated. Please validate its account first" msgstr "" "El usuario seleccionado no ha sido validado. Validar esta cuenta primero" -#: apps/wei/forms/registration.py:62 apps/wei/models.py:126 -#: apps/wei/models.py:324 +#: apps/wei/forms/registration.py:71 apps/wei/models.py:140 +#: apps/wei/models.py:348 msgid "bus" msgstr "bus" -#: apps/wei/forms/registration.py:63 +#: apps/wei/forms/registration.py:72 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." @@ -3081,11 +3081,11 @@ msgstr "" "derecho de imponer su bus y su equipo, en particular para los electrones " "libres." -#: apps/wei/forms/registration.py:70 +#: apps/wei/forms/registration.py:79 msgid "Team" msgstr "Equipo" -#: apps/wei/forms/registration.py:72 +#: apps/wei/forms/registration.py:81 msgid "" "Leave this field empty if you won't be in a team (staff, bus chief, free " "electron)" @@ -3093,16 +3093,16 @@ msgstr "" "Deje este campo vacío si no quiere estar en un equipo (staff, jefe de bus, " "electrón libre)" -#: apps/wei/forms/registration.py:78 apps/wei/forms/registration.py:88 -#: apps/wei/models.py:160 +#: apps/wei/forms/registration.py:87 apps/wei/forms/registration.py:97 +#: apps/wei/models.py:174 msgid "WEI Roles" msgstr "Papeles en el WEI" -#: apps/wei/forms/registration.py:79 +#: apps/wei/forms/registration.py:88 msgid "Select the roles that you are interested in." msgstr "Elegir los papeles que le interesa." -#: apps/wei/forms/registration.py:125 +#: apps/wei/forms/registration.py:134 msgid "This team doesn't belong to the given bus." msgstr "Este equipo no pertenece al bus dado." @@ -3124,118 +3124,140 @@ msgstr "fecha de inicio" msgid "date end" msgstr "fecha de fin" -#: apps/wei/models.py:71 apps/wei/tables.py:305 +#: apps/wei/models.py:37 +#, fuzzy +#| msgid "total amount" +msgid "caution amount" +msgstr "monto total" + +#: apps/wei/models.py:76 apps/wei/tables.py:305 msgid "seat count in the bus" msgstr "cantidad de asientos en el bus" -#: apps/wei/models.py:83 +#: apps/wei/models.py:97 msgid "survey information" msgstr "informaciones sobre el cuestionario" -#: apps/wei/models.py:84 +#: apps/wei/models.py:98 msgid "Information about the survey for new members, encoded in JSON" msgstr "" "Informaciones sobre el cuestionario para los nuevos miembros, registrado en " "JSON" -#: apps/wei/models.py:88 +#: apps/wei/models.py:102 msgid "Bus" msgstr "Bus" -#: apps/wei/models.py:89 apps/wei/templates/wei/weiclub_detail.html:51 +#: apps/wei/models.py:103 apps/wei/templates/wei/weiclub_detail.html:51 msgid "Buses" msgstr "Bus" -#: apps/wei/models.py:135 +#: apps/wei/models.py:149 msgid "color" msgstr "color" -#: apps/wei/models.py:136 +#: apps/wei/models.py:150 msgid "The color of the T-Shirt, stored with its number equivalent" msgstr "El color de la camiseta, registrado con su número equivalente" -#: apps/wei/models.py:147 +#: apps/wei/models.py:161 msgid "Bus team" msgstr "Equipo de bus" -#: apps/wei/models.py:148 +#: apps/wei/models.py:162 msgid "Bus teams" msgstr "Equipos de bus" -#: apps/wei/models.py:159 +#: apps/wei/models.py:173 msgid "WEI Role" msgstr "Papeles en el WEI" -#: apps/wei/models.py:183 +#: apps/wei/models.py:197 msgid "Credit from Société générale" msgstr "Crédito de la Société Générale" -#: apps/wei/models.py:188 apps/wei/views.py:951 +#: apps/wei/models.py:202 apps/wei/views.py:984 msgid "Caution check given" msgstr "Cheque de garantía dado" -#: apps/wei/models.py:192 apps/wei/templates/wei/weimembership_form.html:64 +#: apps/wei/models.py:208 +msgid "Check" +msgstr "" + +#: apps/wei/models.py:209 +#, fuzzy +#| msgid "transactions" +msgid "Note transaction" +msgstr "transacciones" + +#: apps/wei/models.py:212 +#, fuzzy +#| msgid "created at" +msgid "caution type" +msgstr "creada el" + +#: apps/wei/models.py:216 apps/wei/templates/wei/weimembership_form.html:64 msgid "birth date" msgstr "fecha de nacimiento" -#: apps/wei/models.py:198 apps/wei/models.py:208 +#: apps/wei/models.py:222 apps/wei/models.py:232 msgid "Male" msgstr "Hombre" -#: apps/wei/models.py:199 apps/wei/models.py:209 +#: apps/wei/models.py:223 apps/wei/models.py:233 msgid "Female" msgstr "Mujer" -#: apps/wei/models.py:200 +#: apps/wei/models.py:224 msgid "Non binary" msgstr "No binari@" -#: apps/wei/models.py:202 apps/wei/templates/wei/attribute_bus_1A.html:22 +#: apps/wei/models.py:226 apps/wei/templates/wei/attribute_bus_1A.html:22 #: apps/wei/templates/wei/weimembership_form.html:55 msgid "gender" msgstr "género" -#: apps/wei/models.py:210 +#: apps/wei/models.py:234 msgid "Unisex" msgstr "Unisex" -#: apps/wei/models.py:213 apps/wei/templates/wei/weimembership_form.html:58 +#: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:58 msgid "clothing cut" msgstr "forma de ropa" -#: apps/wei/models.py:226 apps/wei/templates/wei/weimembership_form.html:61 +#: apps/wei/models.py:250 apps/wei/templates/wei/weimembership_form.html:61 msgid "clothing size" msgstr "medida de ropa" -#: apps/wei/models.py:232 +#: apps/wei/models.py:256 msgid "health issues" msgstr "problemas de salud" -#: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:70 +#: apps/wei/models.py:261 apps/wei/templates/wei/weimembership_form.html:70 msgid "emergency contact name" msgstr "nombre del contacto de emergencia" -#: apps/wei/models.py:238 +#: apps/wei/models.py:262 msgid "The emergency contact must not be a WEI participant" msgstr "El contacto de emergencia no debe ser un participante de WEI" -#: apps/wei/models.py:243 apps/wei/templates/wei/weimembership_form.html:73 +#: apps/wei/models.py:267 apps/wei/templates/wei/weimembership_form.html:73 msgid "emergency contact phone" msgstr "teléfono del contacto de emergencia" -#: apps/wei/models.py:248 apps/wei/templates/wei/weimembership_form.html:52 +#: apps/wei/models.py:272 apps/wei/templates/wei/weimembership_form.html:52 msgid "first year" msgstr "primer año" -#: apps/wei/models.py:249 +#: apps/wei/models.py:273 msgid "Tells if the user is new in the school." msgstr "Indica si el usuario es nuevo en la escuela." -#: apps/wei/models.py:254 +#: apps/wei/models.py:278 msgid "registration information" msgstr "informaciones sobre la afiliación" -#: apps/wei/models.py:255 +#: apps/wei/models.py:279 msgid "" "Information about the registration (buses for old members, survey for the " "new members), encoded in JSON" @@ -3243,27 +3265,27 @@ msgstr "" "Informaciones sobre la afiliacion (bus para miembros ancianos, cuestionario " "para los nuevos miembros), registrado en JSON" -#: apps/wei/models.py:261 +#: apps/wei/models.py:285 msgid "WEI User" msgstr "Participante WEI" -#: apps/wei/models.py:262 +#: apps/wei/models.py:286 msgid "WEI Users" msgstr "Participantes WEI" -#: apps/wei/models.py:334 +#: apps/wei/models.py:358 msgid "team" msgstr "equipo" -#: apps/wei/models.py:344 +#: apps/wei/models.py:368 msgid "WEI registration" msgstr "Apuntación al WEI" -#: apps/wei/models.py:348 +#: apps/wei/models.py:372 msgid "WEI membership" msgstr "Afiliación al WEI" -#: apps/wei/models.py:349 +#: apps/wei/models.py:373 msgid "WEI memberships" msgstr "Afiliaciones al WEI" @@ -3291,7 +3313,7 @@ msgstr "Año" msgid "preferred bus" msgstr "bus preferido" -#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:32 +#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:36 #: apps/wei/templates/wei/busteam_detail.html:52 msgid "Teams" msgstr "Equipos" @@ -3357,44 +3379,52 @@ msgstr "Volver a la lista principal" msgid "WEI fee (paid students)" msgstr "Pago de entrada del WEI (estudiantes pagados)" -#: apps/wei/templates/wei/base.html:47 apps/wei/templates/wei/base.html:54 -msgid "The BDE membership is included in the WEI registration." -msgstr "La afiliación al BDE esta incluida en la afiliación WEI." - -#: apps/wei/templates/wei/base.html:51 +#: apps/wei/templates/wei/base.html:47 msgid "WEI fee (unpaid students)" msgstr "Pago de entrada del WEI (estudiantes no pagados)" -#: apps/wei/templates/wei/base.html:76 +#: apps/wei/templates/wei/base.html:53 +#, fuzzy +#| msgid "total amount" +msgid "Caution amount" +msgstr "monto total" + +#: apps/wei/templates/wei/base.html:74 msgid "WEI list" msgstr "Lista de los WEI" -#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:557 +#: apps/wei/templates/wei/base.html:79 apps/wei/views.py:550 msgid "Register 1A" msgstr "Apuntar un 1A" -#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:649 +#: apps/wei/templates/wei/base.html:83 apps/wei/views.py:644 msgid "Register 2A+" msgstr "Apuntar un 2A+" -#: apps/wei/templates/wei/base.html:93 +#: apps/wei/templates/wei/base.html:91 msgid "Add bus" msgstr "Añadir un bus" -#: apps/wei/templates/wei/base.html:97 +#: apps/wei/templates/wei/base.html:95 msgid "View WEI" msgstr "Ver un WEI" -#: apps/wei/templates/wei/bus_detail.html:22 +#: apps/wei/templates/wei/bus_detail.html:21 +#, fuzzy +#| msgid "club" +msgid "View club" +msgstr "club" + +#: apps/wei/templates/wei/bus_detail.html:26 #: apps/wei/templates/wei/busteam_detail.html:24 msgid "Add team" msgstr "Añadir un equipo" -#: apps/wei/templates/wei/bus_detail.html:45 +#: apps/wei/templates/wei/bus_detail.html:49 msgid "Members" msgstr "Miembros" -#: apps/wei/templates/wei/bus_detail.html:54 +#: apps/wei/templates/wei/bus_detail.html:58 #: apps/wei/templates/wei/busteam_detail.html:62 #: apps/wei/templates/wei/weimembership_list.html:31 msgid "View as PDF" @@ -3402,8 +3432,8 @@ msgstr "Descargar un 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:1095 -#: apps/wei/views.py:1150 apps/wei/views.py:1197 +#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1159 +#: apps/wei/views.py:1214 apps/wei/views.py:1261 msgid "Survey WEI" msgstr "Cuestionario WEI" @@ -3447,7 +3477,7 @@ msgstr "Inscripciones sin validación" msgid "Attribute buses" msgstr "Repartición en los buses" -#: apps/wei/templates/wei/weiclub_list.html:14 apps/wei/views.py:83 +#: apps/wei/templates/wei/weiclub_list.html:14 apps/wei/views.py:82 msgid "Create WEI" msgstr "Crear un WEI" @@ -3528,29 +3558,42 @@ msgstr "" "resultará invalida. Tendrá que validarla una vez que el banco confirmará la " "creación de la cuenta, o cambiará el método de pago." +#: apps/wei/templates/wei/weimembership_form.html:147 +msgid "Required payments:" +msgstr "" + #: apps/wei/templates/wei/weimembership_form.html:149 -#, python-format -msgid "" -"The note don't have enough money (%(balance)s, %(pretty_fee)s required). The " -"registration may fail if you don't credit the note now." -msgstr "" -"La note no tiene suficiente dinero (%(balance)s, %(pretty_fee)s pedidos). La " -"afiliación puede fallar si usted no acredita la note ahora." +#, fuzzy, python-format +#| msgid "membership fee (paid students)" +msgid "Membership fees: %(amount)s" +msgstr "pago de afiliación (estudiantes pagados)" -#: apps/wei/templates/wei/weimembership_form.html:157 +#: apps/wei/templates/wei/weimembership_form.html:153 #, python-format -msgid "" -"The note has enough money (%(pretty_fee)s required), the registration is " -"possible." +msgid "Deposit (by Note transaction): %(amount)s" msgstr "" -"La note tiene suficiente dinero (%(pretty_fee)s pedidos), la afiliación es " -"posible." -#: apps/wei/templates/wei/weimembership_form.html:166 +#: apps/wei/templates/wei/weimembership_form.html:156 +#: apps/wei/templates/wei/weimembership_form.html:163 +#, python-format +msgid "Total needed: %(total)s" +msgstr "" + +#: apps/wei/templates/wei/weimembership_form.html:160 +#, python-format +msgid "Deposit (by check): %(amount)s" +msgstr "" + +#: apps/wei/templates/wei/weimembership_form.html:168 +#, python-format +msgid "Current balance: %(balance)s" +msgstr "" + +#: apps/wei/templates/wei/weimembership_form.html:176 msgid "The user didn't give her/his caution check." msgstr "El usuario no dio su cheque de garantía." -#: apps/wei/templates/wei/weimembership_form.html:174 +#: apps/wei/templates/wei/weimembership_form.html:184 msgid "" "This user is not a member of the Kfet club for the coming year. The " "membership will be processed automatically, the WEI registration includes " @@ -3584,91 +3627,95 @@ msgstr "No hay pre-inscripción encontrada con esta entrada." msgid "View validated memberships..." msgstr "Ver las inscripciones validadas..." -#: apps/wei/views.py:62 +#: apps/wei/views.py:61 msgid "Search WEI" msgstr "Buscar un WEI" -#: apps/wei/views.py:113 +#: apps/wei/views.py:112 msgid "WEI Detail" msgstr "Detalles del WEI" -#: apps/wei/views.py:213 +#: apps/wei/views.py:212 msgid "View members of the WEI" msgstr "Ver los miembros del WEI" -#: apps/wei/views.py:246 +#: apps/wei/views.py:245 msgid "Find WEI Membership" msgstr "Buscar una afiliación al WEI" -#: apps/wei/views.py:256 +#: apps/wei/views.py:255 msgid "View registrations to the WEI" msgstr "Ver las inscripciones al WEI" -#: apps/wei/views.py:285 +#: apps/wei/views.py:284 msgid "Find WEI Registration" msgstr "Buscar una inscripción al WEI" -#: apps/wei/views.py:296 +#: apps/wei/views.py:295 msgid "Update the WEI" msgstr "Modificar el WEI" -#: apps/wei/views.py:317 +#: apps/wei/views.py:316 msgid "Create new bus" msgstr "Añadir un bus" -#: apps/wei/views.py:355 +#: apps/wei/views.py:354 msgid "Update bus" msgstr "Modificar el bus" -#: apps/wei/views.py:387 +#: apps/wei/views.py:386 msgid "Manage bus" msgstr "Gestionar el bus" -#: apps/wei/views.py:414 +#: apps/wei/views.py:413 msgid "Create new team" msgstr "Añadir un equipo" -#: apps/wei/views.py:461 +#: apps/wei/views.py:457 msgid "Update team" msgstr "Modificar el equipo" -#: apps/wei/views.py:499 +#: apps/wei/views.py:492 msgid "Manage WEI team" msgstr "Gestionar el equipo" -#: apps/wei/views.py:521 +#: apps/wei/views.py:514 msgid "Register first year student to the WEI" msgstr "Registrar un 1A al WEI" -#: apps/wei/views.py:585 apps/wei/views.py:688 +#: apps/wei/views.py:580 apps/wei/views.py:689 msgid "This user is already registered to this WEI." msgstr "Este usuario ya afilió a este WEI." -#: apps/wei/views.py:590 +#: apps/wei/views.py:585 msgid "" "This user can't be in her/his first year since he/she has already " "participated to a WEI." msgstr "Este usuario no puede ser un 1A porque ya participó en un WEI." -#: apps/wei/views.py:613 +#: apps/wei/views.py:608 msgid "Register old student to the WEI" msgstr "Registrar un 2A+ al WEI" -#: apps/wei/views.py:668 apps/wei/views.py:764 +#: apps/wei/views.py:663 apps/wei/views.py:768 msgid "You already opened an account in the Société générale." msgstr "Usted ya abrió una cuenta a la Société Générale." -#: apps/wei/views.py:724 +#: apps/wei/views.py:676 apps/wei/views.py:785 +msgid "Choose how you want to pay the deposit" +msgstr "" + +#: apps/wei/views.py:728 msgid "Update WEI Registration" msgstr "Modificar la inscripción WEI" -#: apps/wei/views.py:799 +#: apps/wei/views.py:810 #, fuzzy #| msgid "The BDE membership is included in the WEI registration." msgid "No membership found for this registration" msgstr "La afiliación al BDE esta incluida en la afiliación WEI." -#: apps/wei/views.py:808 +#: apps/wei/views.py:819 #, fuzzy #| msgid "" #| "You don't have the permission to add an instance of model {app_label}." @@ -3678,7 +3725,7 @@ msgstr "" "Usted no tiene permiso a añadir an instance of model {app_label}." "{model_name}." -#: apps/wei/views.py:814 +#: apps/wei/views.py:825 #, fuzzy, python-format #| msgid "" #| "You don't have the permission to delete this instance of model " @@ -3688,39 +3735,69 @@ msgstr "" "Usted no tiene permiso a suprimir este instance of model {app_label}." "{model_name}." -#: apps/wei/views.py:855 +#: apps/wei/views.py:870 msgid "Delete WEI registration" msgstr "Suprimir la inscripción WEI" -#: apps/wei/views.py:866 +#: apps/wei/views.py:881 msgid "You don't have the right to delete this WEI registration." msgstr "Usted no tiene derecho a suprimir esta inscripción WEI." -#: apps/wei/views.py:884 +#: apps/wei/views.py:899 msgid "Validate WEI registration" msgstr "Validar la inscripción WEI" -#: apps/wei/views.py:889 -#, fuzzy -#| msgid "You don't have the right to delete this WEI registration." -msgid "You don't have the permission to validate registrations" -msgstr "Usted no tiene derecho a suprimir esta inscripción WEI." - -#: apps/wei/views.py:952 +#: apps/wei/views.py:985 #, fuzzy #| msgid "Please ask the user to credit its note before deleting this credit." msgid "Please make sure the check is given before validating the registration" msgstr "" "Por favor pide al usuario acreditar su note antes de suprimir este crédito." -#: apps/wei/views.py:1290 +#: apps/wei/views.py:991 +#, fuzzy +#| msgid "credit transaction" +msgid "Create deposit transaction" +msgstr "transacción de crédito" + +#: apps/wei/views.py:992 +#, python-format +msgid "" +"A transaction of %(amount).2f€ will be created from the user's Note account" +msgstr "" + +#: apps/wei/views.py:1087 +#, fuzzy, python-format +#| msgid "" +#| "This user don't have enough money to join this club, and can't have a " +#| "negative balance." +msgid "" +"This user doesn't have enough money to join this club and pay the deposit. " +"Current balance: %(balance)d€, credit: %(credit)d€, needed: %(needed)d€" +msgstr "" +"Este usuario no tiene suficiente dinero para unirse a este club, y no puede " +"tener un saldo negativo." + +#: apps/wei/views.py:1140 +#, fuzzy, python-format +#| msgid "created at" +msgid "Caution %(name)s" +msgstr "creada el" + +#: apps/wei/views.py:1354 msgid "Attribute buses to first year members" msgstr "Repartir los primer años en los buses" -#: apps/wei/views.py:1315 +#: apps/wei/views.py:1379 msgid "Attribute bus" msgstr "Repartir en un bus" +#: apps/wei/views.py:1419 +msgid "" +"No first year student without a bus found. Either all of them have a bus, or " +"none has filled the survey yet." +msgstr "" + #: apps/wrapped/apps.py:10 msgid "wrapped" msgstr "" @@ -5703,6 +5780,30 @@ msgstr "" "pagar su afiliación. Tambien tiene que validar su correo electronico con el " "enlace que recibió." +#~ msgid "The BDE membership is included in the WEI registration." +#~ msgstr "La afiliación al BDE esta incluida en la afiliación WEI." + +#, python-format +#~ msgid "" +#~ "The note don't have enough money (%(balance)s, %(pretty_fee)s required). " +#~ "The registration may fail if you don't credit the note now." +#~ msgstr "" +#~ "La note no tiene suficiente dinero (%(balance)s, %(pretty_fee)s pedidos). " +#~ "La afiliación puede fallar si usted no acredita la note ahora." + +#, python-format +#~ msgid "" +#~ "The note has enough money (%(pretty_fee)s required), the registration is " +#~ "possible." +#~ msgstr "" +#~ "La note tiene suficiente dinero (%(pretty_fee)s pedidos), la afiliación " +#~ "es posible." + +#, fuzzy +#~| msgid "You don't have the right to delete this WEI registration." +#~ msgid "You don't have the permission to validate registrations" +#~ msgstr "Usted no tiene derecho a suprimir esta inscripción WEI." + #, fuzzy #~| msgid "active" #~ msgid "is active" @@ -5728,11 +5829,6 @@ msgstr "" #~ msgid "View details" #~ msgstr "Detalles del usuario" -#, fuzzy -#~| msgid "created at" -#~ msgid "Creation date" -#~ msgstr "creada el" - #, fuzzy #~| msgid "There is no results." #~ msgid "There is no meal." diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 03a4ca7b..3e881043 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: 2025-05-27 16:46+0200\n" +"POT-Creation-Date: 2025-06-02 00:57+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n" "Last-Translator: bleizi \n" "Language-Team: French \n" @@ -66,7 +66,7 @@ msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité." #: apps/note/models/transactions.py:46 apps/note/models/transactions.py:299 #: apps/permission/models.py:329 #: 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/models.py:72 apps/wei/models.py:145 apps/wei/tables.py:282 #: apps/wei/templates/wei/base.html:26 #: apps/wei/templates/wei/weimembership_form.html:14 apps/wrapped/models.py:16 msgid "name" @@ -101,7 +101,7 @@ msgstr "types d'activité" #: apps/activity/models.py:68 #: apps/activity/templates/activity/includes/activity_info.html:19 #: apps/note/models/transactions.py:82 apps/permission/models.py:109 -#: apps/permission/models.py:188 apps/wei/models.py:78 apps/wei/models.py:142 +#: apps/permission/models.py:188 apps/wei/models.py:92 apps/wei/models.py:156 msgid "description" msgstr "description" @@ -122,7 +122,7 @@ msgstr "type" #: 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:294 -#: apps/wei/models.py:171 apps/wei/templates/wei/attribute_bus_1A.html:13 +#: apps/wei/models.py:185 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/templates/wei/survey.html:15 msgid "user" msgstr "utilisateur⋅rice" @@ -291,14 +291,14 @@ msgstr "Type" #: 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:107 +#: apps/wei/forms/registration.py:116 msgid "Last name" msgstr "Nom de famille" #: 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:112 +#: apps/wei/forms/registration.py:121 msgid "First name" msgstr "Prénom" @@ -971,12 +971,12 @@ 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:185 apps/registration/forms.py:78 -#: apps/wei/forms/registration.py:94 +#: apps/wei/forms/registration.py:103 msgid "Credit type" msgstr "Type de rechargement" #: apps/member/forms.py:186 apps/registration/forms.py:79 -#: apps/wei/forms/registration.py:95 +#: apps/wei/forms/registration.py:104 msgid "No credit" msgstr "Pas de rechargement" @@ -985,13 +985,13 @@ 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:192 apps/registration/forms.py:84 -#: apps/wei/forms/registration.py:100 +#: apps/wei/forms/registration.py:109 msgid "Credit amount" msgstr "Montant à créditer" #: 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:117 +#: apps/wei/forms/registration.py:126 msgid "Bank" msgstr "Banque" @@ -1196,7 +1196,7 @@ msgstr "Activez votre compte Note Kfet" #: apps/member/templates/member/includes/club_info.html:55 #: apps/member/templates/member/includes/profile_info.html:40 #: apps/registration/templates/registration/future_profile_detail.html:22 -#: apps/wei/templates/wei/base.html:70 +#: apps/wei/templates/wei/base.html:68 #: apps/wei/templates/wei/weimembership_form.html:20 msgid "email" msgstr "courriel" @@ -1248,7 +1248,7 @@ msgid "add to registration form" msgstr "ajouter au formulaire d'inscription" #: apps/member/models.py:268 apps/member/models.py:331 -#: apps/note/models/notes.py:176 +#: apps/note/models/notes.py:176 apps/wei/models.py:86 msgid "club" msgstr "club" @@ -1449,13 +1449,13 @@ msgstr "cotisation pour adhérer" #: apps/member/templates/member/includes/club_info.html:43 #: apps/member/templates/member/includes/profile_info.html:55 #: apps/treasury/templates/treasury/sogecredit_detail.html:24 -#: apps/wei/templates/wei/base.html:60 +#: apps/wei/templates/wei/base.html:58 msgid "balance" msgstr "solde du compte" #: apps/member/templates/member/includes/club_info.html:47 #: apps/member/templates/member/includes/profile_info.html:20 -#: apps/note/models/notes.py:287 apps/wei/templates/wei/base.html:66 +#: apps/note/models/notes.py:287 apps/wei/templates/wei/base.html:64 msgid "aliases" msgstr "alias" @@ -1633,7 +1633,7 @@ msgstr "Modifier le club" msgid "Add new member to the club" msgstr "Ajouter un·e nouvelleau membre au club" -#: apps/member/views.py:750 apps/wei/views.py:1040 +#: apps/member/views.py:750 msgid "" "This user don't have enough money to join this club, and can't have a " "negative balance." @@ -1970,8 +1970,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:1045 -#: apps/wei/views.py:1049 +#: apps/note/models/transactions.py:363 apps/wei/views.py:1097 +#: apps/wei/views.py:1101 #: env/lib/python3.11/site-packages/django/forms/fields.py:91 msgid "This field is required." msgstr "Ce champ est requis." @@ -2006,8 +2006,8 @@ msgstr "Ajouter" #: apps/note/tables.py:262 apps/note/templates/note/conso_form.html:151 #: apps/wei/tables.py:49 apps/wei/tables.py:50 -#: apps/wei/templates/wei/base.html:89 -#: apps/wei/templates/wei/bus_detail.html:20 +#: apps/wei/templates/wei/base.html:87 +#: apps/wei/templates/wei/bus_detail.html:24 #: apps/wei/templates/wei/busteam_detail.html:20 #: apps/wei/templates/wei/busteam_detail.html:42 #: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:37 @@ -2481,7 +2481,7 @@ msgstr "" #: apps/registration/templates/registration/future_profile_detail.html:73 #: apps/wei/templates/wei/weimembership_form.html:127 -#: apps/wei/templates/wei/weimembership_form.html:186 +#: apps/wei/templates/wei/weimembership_form.html:196 msgid "Validate registration" msgstr "Valider l'inscription" @@ -3007,24 +3007,24 @@ msgstr "Liste des crédits de la Société générale" msgid "Manage credits from the Société générale" msgstr "Gérer les crédits de la Société générale" -#: apps/wei/apps.py:10 apps/wei/models.py:37 apps/wei/models.py:38 -#: apps/wei/models.py:62 apps/wei/models.py:178 +#: apps/wei/apps.py:10 apps/wei/models.py:42 apps/wei/models.py:43 +#: apps/wei/models.py:67 apps/wei/models.py:192 #: note_kfet/templates/base.html:108 msgid "WEI" msgstr "WEI" -#: apps/wei/forms/registration.py:36 +#: apps/wei/forms/registration.py:37 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" -#: apps/wei/forms/registration.py:62 apps/wei/models.py:126 -#: apps/wei/models.py:324 +#: apps/wei/forms/registration.py:71 apps/wei/models.py:140 +#: apps/wei/models.py:348 msgid "bus" msgstr "bus" -#: apps/wei/forms/registration.py:63 +#: apps/wei/forms/registration.py:72 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." @@ -3033,11 +3033,11 @@ msgstr "" "vous attribuer un bus et une équipe, en particulier si vous êtes un·e " "électron libre." -#: apps/wei/forms/registration.py:70 +#: apps/wei/forms/registration.py:79 msgid "Team" msgstr "Équipe" -#: apps/wei/forms/registration.py:72 +#: apps/wei/forms/registration.py:81 msgid "" "Leave this field empty if you won't be in a team (staff, bus chief, free " "electron)" @@ -3045,16 +3045,16 @@ 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:78 apps/wei/forms/registration.py:88 -#: apps/wei/models.py:160 +#: apps/wei/forms/registration.py:87 apps/wei/forms/registration.py:97 +#: apps/wei/models.py:174 msgid "WEI Roles" msgstr "Rôles au WEI" -#: apps/wei/forms/registration.py:79 +#: apps/wei/forms/registration.py:88 msgid "Select the roles that you are interested in." msgstr "Sélectionnez les rôles qui vous intéressent." -#: apps/wei/forms/registration.py:125 +#: apps/wei/forms/registration.py:134 msgid "This team doesn't belong to the given bus." msgstr "Cette équipe n'appartient pas à ce bus." @@ -3076,120 +3076,140 @@ msgstr "début" msgid "date end" msgstr "fin" -#: apps/wei/models.py:71 apps/wei/tables.py:305 +#: apps/wei/models.py:37 +#, fuzzy +#| msgid "total amount" +msgid "caution amount" +msgstr "montant total" + +#: apps/wei/models.py:76 apps/wei/tables.py:305 msgid "seat count in the bus" msgstr "nombre de sièges dans le bus" -#: apps/wei/models.py:83 +#: apps/wei/models.py:97 msgid "survey information" msgstr "informations sur le questionnaire" -#: apps/wei/models.py:84 +#: apps/wei/models.py:98 msgid "Information about the survey for new members, encoded in JSON" msgstr "" "Informations sur le sondage pour les nouveaux membres, encodées en JSON" -#: apps/wei/models.py:88 +#: apps/wei/models.py:102 msgid "Bus" msgstr "Bus" -#: apps/wei/models.py:89 apps/wei/templates/wei/weiclub_detail.html:51 +#: apps/wei/models.py:103 apps/wei/templates/wei/weiclub_detail.html:51 msgid "Buses" msgstr "Bus" -#: apps/wei/models.py:135 +#: apps/wei/models.py:149 msgid "color" msgstr "couleur" -#: apps/wei/models.py:136 +#: apps/wei/models.py:150 msgid "The color of the T-Shirt, stored with its number equivalent" msgstr "" "La couleur du T-Shirt, stocké sous la forme de son équivalent numérique" -#: apps/wei/models.py:147 +#: apps/wei/models.py:161 msgid "Bus team" msgstr "Équipe de bus" -#: apps/wei/models.py:148 +#: apps/wei/models.py:162 msgid "Bus teams" msgstr "Équipes de bus" -#: apps/wei/models.py:159 +#: apps/wei/models.py:173 msgid "WEI Role" msgstr "Rôle au WEI" -#: apps/wei/models.py:183 +#: apps/wei/models.py:197 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/views.py:951 +#: apps/wei/models.py:202 apps/wei/views.py:984 msgid "Caution check given" msgstr "Chèque de caution donné" -#: apps/wei/models.py:192 apps/wei/templates/wei/weimembership_form.html:64 +#: apps/wei/models.py:208 +msgid "Check" +msgstr "" + +#: apps/wei/models.py:209 +#, fuzzy +#| msgid " transactions" +msgid "Note transaction" +msgstr " transactions" + +#: apps/wei/models.py:212 +msgid "caution type" +msgstr "date de création" + +#: apps/wei/models.py:216 apps/wei/templates/wei/weimembership_form.html:64 msgid "birth date" msgstr "date de naissance" -#: apps/wei/models.py:198 apps/wei/models.py:208 +#: apps/wei/models.py:222 apps/wei/models.py:232 msgid "Male" msgstr "Homme" -#: apps/wei/models.py:199 apps/wei/models.py:209 +#: apps/wei/models.py:223 apps/wei/models.py:233 msgid "Female" msgstr "Femme" -#: apps/wei/models.py:200 +#: apps/wei/models.py:224 msgid "Non binary" msgstr "Non-binaire" -#: apps/wei/models.py:202 apps/wei/templates/wei/attribute_bus_1A.html:22 +#: apps/wei/models.py:226 apps/wei/templates/wei/attribute_bus_1A.html:22 #: apps/wei/templates/wei/weimembership_form.html:55 msgid "gender" msgstr "genre" -#: apps/wei/models.py:210 +#: apps/wei/models.py:234 msgid "Unisex" msgstr "Unisexe" -#: apps/wei/models.py:213 apps/wei/templates/wei/weimembership_form.html:58 +#: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:58 msgid "clothing cut" msgstr "coupe de vêtement" -#: apps/wei/models.py:226 apps/wei/templates/wei/weimembership_form.html:61 +#: apps/wei/models.py:250 apps/wei/templates/wei/weimembership_form.html:61 msgid "clothing size" msgstr "taille de vêtement" -#: apps/wei/models.py:232 +#: apps/wei/models.py:256 msgid "health issues" msgstr "problèmes de santé" -#: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:70 +#: apps/wei/models.py:261 apps/wei/templates/wei/weimembership_form.html:70 msgid "emergency contact name" msgstr "nom du contact en cas d'urgence" -#: apps/wei/models.py:238 +#: apps/wei/models.py:262 msgid "The emergency contact must not be a WEI participant" msgstr "" "Le contact en cas d'urgence ne doit pas être une personne qui participe au " "WEI" -#: apps/wei/models.py:243 apps/wei/templates/wei/weimembership_form.html:73 +#: apps/wei/models.py:267 apps/wei/templates/wei/weimembership_form.html:73 msgid "emergency contact phone" msgstr "téléphone du contact en cas d'urgence" -#: apps/wei/models.py:248 apps/wei/templates/wei/weimembership_form.html:52 +#: apps/wei/models.py:272 apps/wei/templates/wei/weimembership_form.html:52 msgid "first year" msgstr "première année" -#: apps/wei/models.py:249 +#: apps/wei/models.py:273 msgid "Tells if the user is new in the school." msgstr "Indique si l'utilisateur⋅rice est nouvelleeau dans l'école." -#: apps/wei/models.py:254 +#: apps/wei/models.py:278 msgid "registration information" msgstr "informations sur l'inscription" -#: apps/wei/models.py:255 +#: apps/wei/models.py:279 msgid "" "Information about the registration (buses for old members, survey for the " "new members), encoded in JSON" @@ -3197,27 +3217,27 @@ msgstr "" "Informations sur l'inscription (bus pour les 2A+, questionnaire pour les " "1A), encodées en JSON" -#: apps/wei/models.py:261 +#: apps/wei/models.py:285 msgid "WEI User" msgstr "Participant·e au WEI" -#: apps/wei/models.py:262 +#: apps/wei/models.py:286 msgid "WEI Users" msgstr "Participant·e·s au WEI" -#: apps/wei/models.py:334 +#: apps/wei/models.py:358 msgid "team" msgstr "équipe" -#: apps/wei/models.py:344 +#: apps/wei/models.py:368 msgid "WEI registration" msgstr "Inscription au WEI" -#: apps/wei/models.py:348 +#: apps/wei/models.py:372 msgid "WEI membership" msgstr "Adhésion au WEI" -#: apps/wei/models.py:349 +#: apps/wei/models.py:373 msgid "WEI memberships" msgstr "Adhésions au WEI" @@ -3245,7 +3265,7 @@ msgstr "Année" msgid "preferred bus" msgstr "bus préféré" -#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:32 +#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:36 #: apps/wei/templates/wei/busteam_detail.html:52 msgid "Teams" msgstr "Équipes" @@ -3279,8 +3299,6 @@ msgid "Attribute first year members into buses" msgstr "Attribuer les 1A dans les bus" #: apps/wei/templates/wei/1A_list.html:15 -#, fuzzy -#| msgid "Start attribution!" msgid "Start attribution !" msgstr "Démarrer l'attribution !" @@ -3290,8 +3308,6 @@ 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é" @@ -3311,44 +3327,48 @@ msgstr "Retour à la liste principale" msgid "WEI fee (paid students)" msgstr "Prix du WEI (élèves)" -#: apps/wei/templates/wei/base.html:47 apps/wei/templates/wei/base.html:54 -msgid "The BDE membership is included in the WEI registration." -msgstr "L'adhésion au BDE est offerte avec l'inscription au WEI." - -#: apps/wei/templates/wei/base.html:51 +#: apps/wei/templates/wei/base.html:47 msgid "WEI fee (unpaid students)" msgstr "Prix du WEI (étudiant⋅es)" -#: apps/wei/templates/wei/base.html:76 +#: apps/wei/templates/wei/base.html:53 +msgid "Deposit amount" +msgstr "Caution" + +#: apps/wei/templates/wei/base.html:74 msgid "WEI list" msgstr "Liste des WEI" -#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:557 +#: apps/wei/templates/wei/base.html:79 apps/wei/views.py:550 msgid "Register 1A" msgstr "Inscrire un⋅e 1A" -#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:649 +#: apps/wei/templates/wei/base.html:83 apps/wei/views.py:644 msgid "Register 2A+" msgstr "Inscrire un⋅e 2A+" -#: apps/wei/templates/wei/base.html:93 +#: apps/wei/templates/wei/base.html:91 msgid "Add bus" msgstr "Ajouter un bus" -#: apps/wei/templates/wei/base.html:97 +#: apps/wei/templates/wei/base.html:95 msgid "View WEI" msgstr "Voir le WEI" -#: apps/wei/templates/wei/bus_detail.html:22 +#: apps/wei/templates/wei/bus_detail.html:21 +msgid "View club" +msgstr "Voir le lub" + +#: apps/wei/templates/wei/bus_detail.html:26 #: apps/wei/templates/wei/busteam_detail.html:24 msgid "Add team" msgstr "Ajouter une équipe" -#: apps/wei/templates/wei/bus_detail.html:45 +#: apps/wei/templates/wei/bus_detail.html:49 msgid "Members" msgstr "Membres" -#: apps/wei/templates/wei/bus_detail.html:54 +#: apps/wei/templates/wei/bus_detail.html:58 #: apps/wei/templates/wei/busteam_detail.html:62 #: apps/wei/templates/wei/weimembership_list.html:31 msgid "View as PDF" @@ -3356,8 +3376,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:1095 -#: apps/wei/views.py:1150 apps/wei/views.py:1197 +#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1159 +#: apps/wei/views.py:1214 apps/wei/views.py:1261 msgid "Survey WEI" msgstr "Questionnaire WEI" @@ -3402,7 +3422,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:83 +#: apps/wei/templates/wei/weiclub_list.html:14 apps/wei/views.py:82 msgid "Create WEI" msgstr "Créer un WEI" @@ -3483,28 +3503,42 @@ msgstr "" "mais invalide. Vous devrez la valider une fois que la banque aura validé la " "création du compte, ou bien changer de moyen de paiement." +#: apps/wei/templates/wei/weimembership_form.html:147 +msgid "Required payments:" +msgstr "" + #: apps/wei/templates/wei/weimembership_form.html:149 -#, python-format -msgid "" -"The note don't have enough money (%(balance)s, %(pretty_fee)s required). The " -"registration may fail if you don't credit the note now." -msgstr "" -"La note n'a pas assez d'argent (%(balance)s, %(pretty_fee)s requis). " -"L'inscription peut échouer si vous ne rechargez pas la note dès maintenant." +#, fuzzy, python-format +#| msgid "membership fee (paid students)" +msgid "Membership fees: %(amount)s" +msgstr "cotisation pour adhérer (normalien·ne élève)" -#: apps/wei/templates/wei/weimembership_form.html:157 +#: apps/wei/templates/wei/weimembership_form.html:153 #, python-format -msgid "" -"The note has enough money (%(pretty_fee)s required), the registration is " -"possible." +msgid "Deposit (by Note transaction): %(amount)s" msgstr "" -"La note a assez d'argent (%(pretty_fee)s requis), l'inscription est possible." -#: apps/wei/templates/wei/weimembership_form.html:166 +#: apps/wei/templates/wei/weimembership_form.html:156 +#: apps/wei/templates/wei/weimembership_form.html:163 +#, python-format +msgid "Total needed: %(total)s" +msgstr "" + +#: apps/wei/templates/wei/weimembership_form.html:160 +#, python-format +msgid "Deposit (by check): %(amount)s" +msgstr "" + +#: apps/wei/templates/wei/weimembership_form.html:168 +#, python-format +msgid "Current balance: %(balance)s" +msgstr "" + +#: apps/wei/templates/wei/weimembership_form.html:176 msgid "The user didn't give her/his caution check." msgstr "L'utilisateur⋅rice n'a pas donné son chèque de caution." -#: apps/wei/templates/wei/weimembership_form.html:174 +#: apps/wei/templates/wei/weimembership_form.html:184 msgid "" "This user is not a member of the Kfet club for the coming year. The " "membership will be processed automatically, the WEI registration includes " @@ -3541,67 +3575,67 @@ msgstr "Il n'y a pas de pré-inscription en attente avec cette entrée." msgid "View validated memberships..." msgstr "Voir les adhésions validées..." -#: apps/wei/views.py:62 +#: apps/wei/views.py:61 msgid "Search WEI" msgstr "Chercher un WEI" -#: apps/wei/views.py:113 +#: apps/wei/views.py:112 msgid "WEI Detail" msgstr "Détails du WEI" -#: apps/wei/views.py:213 +#: apps/wei/views.py:212 msgid "View members of the WEI" msgstr "Voir les membres du WEI" -#: apps/wei/views.py:246 +#: apps/wei/views.py:245 msgid "Find WEI Membership" msgstr "Trouver une adhésion au WEI" -#: apps/wei/views.py:256 +#: apps/wei/views.py:255 msgid "View registrations to the WEI" msgstr "Voir les inscriptions au WEI" -#: apps/wei/views.py:285 +#: apps/wei/views.py:284 msgid "Find WEI Registration" msgstr "Trouver une inscription au WEI" -#: apps/wei/views.py:296 +#: apps/wei/views.py:295 msgid "Update the WEI" msgstr "Modifier le WEI" -#: apps/wei/views.py:317 +#: apps/wei/views.py:316 msgid "Create new bus" msgstr "Ajouter un nouveau bus" -#: apps/wei/views.py:355 +#: apps/wei/views.py:354 msgid "Update bus" msgstr "Modifier le bus" -#: apps/wei/views.py:387 +#: apps/wei/views.py:386 msgid "Manage bus" msgstr "Gérer le bus" -#: apps/wei/views.py:414 +#: apps/wei/views.py:413 msgid "Create new team" msgstr "Créer une nouvelle équipe" -#: apps/wei/views.py:461 +#: apps/wei/views.py:457 msgid "Update team" msgstr "Modifier l'équipe" -#: apps/wei/views.py:499 +#: apps/wei/views.py:492 msgid "Manage WEI team" msgstr "Gérer l'équipe WEI" -#: apps/wei/views.py:521 +#: apps/wei/views.py:514 msgid "Register first year student to the WEI" msgstr "Inscrire un⋅e 1A au WEI" -#: apps/wei/views.py:585 apps/wei/views.py:688 +#: apps/wei/views.py:580 apps/wei/views.py:689 msgid "This user is already registered to this WEI." msgstr "Cette personne est déjà inscrite au WEI." -#: apps/wei/views.py:590 +#: apps/wei/views.py:585 msgid "" "This user can't be in her/his first year since he/she has already " "participated to a WEI." @@ -3609,27 +3643,27 @@ 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:613 +#: apps/wei/views.py:608 msgid "Register old student to the WEI" msgstr "Inscrire un⋅e 2A+ au WEI" -#: apps/wei/views.py:668 apps/wei/views.py:764 +#: apps/wei/views.py:663 apps/wei/views.py:768 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:724 +#: apps/wei/views.py:676 apps/wei/views.py:785 +msgid "Choose how you want to pay the deposit" +msgstr "" + +#: apps/wei/views.py:728 msgid "Update WEI Registration" msgstr "Modifier l'inscription WEI" -#: apps/wei/views.py:799 -#, fuzzy -#| msgid "The BDE membership is included in the WEI registration." +#: apps/wei/views.py:810 msgid "No membership found for this registration" -msgstr "L'adhésion au BDE est offerte avec l'inscription au WEI." +msgstr "Pas d'adhésion trouvée pour cette inscription" -#: apps/wei/views.py:808 -#, fuzzy -#| msgid "" +#: apps/wei/views.py:819 #| "You don't have the permission to add an instance of model {app_label}." #| "{model_name}." msgid "You don't have the permission to update memberships" @@ -3637,7 +3671,7 @@ msgstr "" "Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}." "{model_name}." -#: apps/wei/views.py:814 +#: apps/wei/views.py:825 #, fuzzy, python-format #| msgid "" #| "You don't have the permission to delete this instance of model " @@ -3647,40 +3681,61 @@ msgstr "" "Vous n'avez pas la permission de supprimer cette instance du modèle " "{app_label}.{model_name}." -#: apps/wei/views.py:855 +#: apps/wei/views.py:870 msgid "Delete WEI registration" msgstr "Supprimer l'inscription WEI" -#: apps/wei/views.py:866 +#: apps/wei/views.py:881 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:884 +#: apps/wei/views.py:899 msgid "Validate WEI registration" msgstr "Valider l'inscription WEI" -#: apps/wei/views.py:889 -#, fuzzy -#| msgid "You don't have the right to delete this WEI registration." -msgid "You don't have the permission to validate registrations" -msgstr "Vous n'avez pas la permission de supprimer cette inscription au WEI." - -#: apps/wei/views.py:952 -#, fuzzy -#| msgid "Please ask the user to credit its note before deleting this credit." +#: apps/wei/views.py:985 msgid "Please make sure the check is given before validating the registration" msgstr "" -"Merci de demander à l'utilisateur·rice de recharger sa note avant de " -"supprimer la demande de crédit." +"Merci de vous assurer que le chèque a bien été donné avant de valider l'adhésion" -#: apps/wei/views.py:1290 +#: apps/wei/views.py:991 +msgid "Create deposit transaction" +msgstr "Créer une transaction de caution" + +#: apps/wei/views.py:992 +#, python-format +msgid "A transaction of %(amount).2f€ will be created from the user's Note account" +msgstr "Un transaction de %(amount).2f€ va être créée depuis la note de l'utilisateur" + +#: apps/wei/views.py:1087 +#, python-format +msgid "This user doesn't have enough money to join this club and pay the deposit. " +"Current balance: %(balance)d€, credit: %(credit)d€, needed: %(needed)d€" +msgstr "" +"Cet⋅te utilisateur⋅rice n'a pas assez d'argent pour rejoindre ce club et " +"payer la caution" +"Solde actuel : %(balance)d€, crédit : %(credit)d€, requis : %(needed)d€" + +#: apps/wei/views.py:1140 +#, fuzzy, python-format +#| msgid "Creation date" +msgid "Caution %(name)s" +msgstr "Date de création" + +#: apps/wei/views.py:1354 msgid "Attribute buses to first year members" msgstr "Répartir les 1A dans les bus" -#: apps/wei/views.py:1315 +#: apps/wei/views.py:1379 msgid "Attribute bus" msgstr "Attribuer un bus" +#: apps/wei/views.py:1419 +msgid "" +"No first year student without a bus found. Either all of them have a bus, or " +"none has filled the survey yet." +msgstr "" + #: apps/wrapped/apps.py:10 msgid "wrapped" msgstr "wrapped" @@ -5683,6 +5738,32 @@ msgstr "" "d'adhésion. Vous devez également valider votre adresse email en suivant le " "lien que vous avez reçu." +#~ msgid "The BDE membership is included in the WEI registration." +#~ msgstr "L'adhésion au BDE est offerte avec l'inscription au WEI." + +#, python-format +#~ msgid "" +#~ "The note don't have enough money (%(balance)s, %(pretty_fee)s required). " +#~ "The registration may fail if you don't credit the note now." +#~ msgstr "" +#~ "La note n'a pas assez d'argent (%(balance)s, %(pretty_fee)s requis). " +#~ "L'inscription peut échouer si vous ne rechargez pas la note dès " +#~ "maintenant." + +#, python-format +#~ msgid "" +#~ "The note has enough money (%(pretty_fee)s required), the registration is " +#~ "possible." +#~ msgstr "" +#~ "La note a assez d'argent (%(pretty_fee)s requis), l'inscription est " +#~ "possible." + +#, fuzzy +#~| msgid "You don't have the right to delete this WEI registration." +#~ msgid "You don't have the permission to validate registrations" +#~ msgstr "" +#~ "Vous n'avez pas la permission de supprimer cette inscription au WEI." + #, python-brace-format #~ msgid "QR-code number {qr_code_number}" #~ msgstr "numéro du QR-code {qr_code_number}" @@ -5714,9 +5795,6 @@ msgstr "" #~ msgid "Ready" #~ msgstr "Prêt" -#~ msgid "Creation date" -#~ msgstr "Date de création" - #~ msgid "Ingredients" #~ msgstr "Ingrédients" From 40ac1daece15e8d4b1d693f591ca5be394bed9c0 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Mon, 2 Jun 2025 17:51:33 +0200 Subject: [PATCH 030/153] Tests et permissions --- apps/permission/fixtures/initial.json | 4 ++-- apps/wei/tests/test_wei_registration.py | 8 ++++++++ apps/wei/views.py | 12 ++++++------ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 08f88f2b..883db297 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -4694,6 +4694,7 @@ "name": "GC WEI", "permissions": [ 22, + 62, 70, 72, 76, @@ -4719,6 +4720,7 @@ 113, 128, 130, + 142, 271, 272, 273, @@ -4755,7 +4757,6 @@ 285, 286, 287, - 288, 289, 290, 291 @@ -4961,7 +4962,6 @@ 285, 286, 287, - 288, 289, 290, 291 diff --git a/apps/wei/tests/test_wei_registration.py b/apps/wei/tests/test_wei_registration.py index 7b55095b..00aab46c 100644 --- a/apps/wei/tests/test_wei_registration.py +++ b/apps/wei/tests/test_wei_registration.py @@ -126,6 +126,7 @@ class TestWEIRegistration(TestCase): year=self.year + 1, date_start=str(self.year + 1) + "-09-01", date_end=str(self.year + 1) + "-09-03", + caution_amount=12000, )) qs = WEIClub.objects.filter(name="Create WEI Test", year=self.year + 1) self.assertTrue(qs.exists()) @@ -160,6 +161,7 @@ class TestWEIRegistration(TestCase): membership_end="2000-09-30", date_start="2000-09-01", date_end="2000-09-03", + caution_amount=12000, )) qs = WEIClub.objects.filter(name="Update WEI Test", id=self.wei.id) self.assertRedirects(response, reverse("wei:wei_detail", kwargs=dict(pk=self.wei.pk)), 302, 200) @@ -318,6 +320,7 @@ class TestWEIRegistration(TestCase): bus=[], team=[], roles=[], + caution_type='check' )) self.assertEqual(response.status_code, 200) self.assertFalse(response.context["membership_form"].is_valid()) @@ -335,6 +338,7 @@ class TestWEIRegistration(TestCase): bus=[self.bus.id], team=[self.team.id], roles=[role.id for role in WEIRole.objects.filter(~Q(name="1A")).all()], + caution_type='check' )) qs = WEIRegistration.objects.filter(user_id=user.id) self.assertTrue(qs.exists()) @@ -354,6 +358,7 @@ class TestWEIRegistration(TestCase): bus=[self.bus.id], team=[self.team.id], roles=[role.id for role in WEIRole.objects.filter(~Q(name="1A")).all()], + caution_type='check' )) self.assertEqual(response.status_code, 200) self.assertTrue("This user is already registered to this WEI." in str(response.context["form"].errors)) @@ -506,6 +511,7 @@ class TestWEIRegistration(TestCase): team=[self.team.id], roles=[role.id for role in WEIRole.objects.filter(name="Adhérent⋅e WEI").all()], information_json=self.registration.information_json, + caution_type='check' ) ) qs = WEIRegistration.objects.filter(user_id=self.user.id, soge_credit=False, clothing_size="M") @@ -560,6 +566,7 @@ class TestWEIRegistration(TestCase): team=[self.team.id], roles=[role.id for role in WEIRole.objects.filter(name="Adhérent⋅e WEI").all()], information_json=self.registration.information_json, + caution_type='check' ) ) qs = WEIRegistration.objects.filter(user_id=self.user.id, clothing_size="L") @@ -583,6 +590,7 @@ class TestWEIRegistration(TestCase): team=[], roles=[], information_json=self.registration.information_json, + caution_type='check' ) ) self.assertFalse(response.context["membership_form"].is_valid()) diff --git a/apps/wei/views.py b/apps/wei/views.py index cb75f8f2..a880a2fa 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -1084,12 +1084,12 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): # Vérifier que l'utilisateur a assez d'argent pour tout payer if not registration.soge_credit and user.note.balance + credit_amount < total_needed: form.add_error('credit_type', - _("This user doesn't have enough money to join this club and pay the deposit. " - "Current balance: %(balance)d€, credit: %(credit)d€, needed: %(needed)d€") % { - 'balance': user.note.balance, - 'credit': credit_amount, - 'needed': total_needed, - }) + _("This user doesn't have enough money to join this club and pay the deposit. " + "Current balance: %(balance)d€, credit: %(credit)d€, needed: %(needed)d€") % { + 'balance': user.note.balance, + 'credit': credit_amount, + 'needed': total_needed} + ) return super().form_invalid(form) if credit_amount: From 63f6528adc82b30fa39e4820f2da80f2fc7f830a Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Thu, 12 Jun 2025 13:59:59 +0200 Subject: [PATCH 031/153] Suppression du choix GC WEI dans les roles --- apps/wei/forms/registration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index 82778148..f5ce51f6 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -83,7 +83,7 @@ class WEIChooseBusForm(forms.Form): ) roles = forms.ModelMultipleChoiceField( - queryset=WEIRole.objects.filter(~Q(name="1A")), + queryset=WEIRole.objects.filter(~Q(name="1A") & ~Q(name="GC WEI")), label=_("WEI Roles"), help_text=_("Select the roles that you are interested in."), initial=WEIRole.objects.filter(name="Adhérent⋅e WEI").all(), @@ -93,7 +93,7 @@ class WEIChooseBusForm(forms.Form): class WEIMembershipForm(forms.ModelForm): roles = forms.ModelMultipleChoiceField( - queryset=WEIRole.objects, + queryset=WEIRole.objects.filter(~Q(name="GC WEI")), label=_("WEI Roles"), widget=CheckboxSelectMultiple(), ) From 6822500fdc4556f003dc0e303a1b95458f84624f Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Thu, 12 Jun 2025 17:39:34 +0200 Subject: [PATCH 032/153] Correction des tests et autres --- apps/wei/forms/__init__.py | 5 +++-- apps/wei/forms/registration.py | 17 +++++++++++++++-- apps/wei/tests/test_wei_registration.py | 6 +++--- apps/wei/views.py | 6 +++--- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/apps/wei/forms/__init__.py b/apps/wei/forms/__init__.py index e1a09c3a..71fb2c5f 100644 --- a/apps/wei/forms/__init__.py +++ b/apps/wei/forms/__init__.py @@ -1,10 +1,11 @@ # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .registration import WEIForm, WEIRegistrationForm, WEIMembership1AForm, WEIMembershipForm, BusForm, BusTeamForm +from .registration import WEIForm, WEIRegistrationForm, WEIRegistration1AForm, WEIRegistration2AForm, WEIMembership1AForm, \ + WEIMembershipForm, BusForm, BusTeamForm from .surveys import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, CurrentSurvey __all__ = [ - 'WEIForm', 'WEIRegistrationForm', 'WEIMembership1AForm', 'WEIMembershipForm', 'BusForm', 'BusTeamForm', + 'WEIForm', 'WEIRegistrationForm', 'WEIRegistration1AForm', 'WEIRegistration2AForm', 'WEIMembership1AForm', 'WEIMembershipForm', 'BusForm', 'BusTeamForm', 'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey', ] diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index f5ce51f6..fd6fea2c 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -43,7 +43,7 @@ class WEIRegistrationForm(forms.ModelForm): fields = [ 'user', 'soge_credit', 'birth_date', 'gender', 'clothing_size', 'health_issues', 'emergency_contact_name', 'emergency_contact_phone', - 'first_year', 'information_json', 'caution_check', 'caution_type' + 'first_year', 'information_json', 'caution_check' ] widgets = { "user": Autocomplete( @@ -61,10 +61,23 @@ class WEIRegistrationForm(forms.ModelForm): "caution_check": forms.BooleanField( required=False, ), - "caution_type": forms.RadioSelect(), } +class WEIRegistration2AForm(WEIRegistrationForm): + class Meta(WEIRegistrationForm.Meta): + fields = WEIRegistrationForm.Meta.fields + ['caution_type'] + widgets = WEIRegistrationForm.Meta.widgets.copy() + widgets.update({ + "caution_type": forms.RadioSelect(), + }) + + +class WEIRegistration1AForm(WEIRegistrationForm): + class Meta(WEIRegistrationForm.Meta): + fields = WEIRegistrationForm.Meta.fields + + class WEIChooseBusForm(forms.Form): bus = forms.ModelMultipleChoiceField( queryset=Bus.objects, diff --git a/apps/wei/tests/test_wei_registration.py b/apps/wei/tests/test_wei_registration.py index 00aab46c..f7ce5ac0 100644 --- a/apps/wei/tests/test_wei_registration.py +++ b/apps/wei/tests/test_wei_registration.py @@ -337,7 +337,7 @@ class TestWEIRegistration(TestCase): emergency_contact_phone='+33123456789', bus=[self.bus.id], team=[self.team.id], - roles=[role.id for role in WEIRole.objects.filter(~Q(name="1A")).all()], + roles=[role.id for role in WEIRole.objects.filter(~Q(name="1A") & ~Q(name="GC WEI")).all()], caution_type='check' )) qs = WEIRegistration.objects.filter(user_id=user.id) @@ -632,7 +632,7 @@ class TestWEIRegistration(TestCase): second_bus = Bus.objects.create(wei=self.wei, name="Second bus") second_team = BusTeam.objects.create(bus=second_bus, name="Second team", color=42) response = self.client.post(reverse("wei:validate_registration", kwargs=dict(pk=self.registration.pk)), dict( - roles=[WEIRole.objects.get(name="GC WEI").id], + roles=[WEIRole.objects.get(name="Adhérent⋅e WEI").id], bus=self.bus.pk, team=second_team.pk, credit_type=4, # Bank transfer @@ -647,7 +647,7 @@ class TestWEIRegistration(TestCase): self.assertTrue("This team doesn't belong to the given bus." in str(response.context["form"].errors)) response = self.client.post(reverse("wei:validate_registration", kwargs=dict(pk=self.registration.pk)), dict( - roles=[WEIRole.objects.get(name="GC WEI").id], + roles=[WEIRole.objects.get(name="Adhérent⋅e WEI").id], bus=self.bus.pk, team=self.team.pk, credit_type=4, # Bank transfer diff --git a/apps/wei/views.py b/apps/wei/views.py index a880a2fa..d4e1be24 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -35,7 +35,7 @@ from permission.views import ProtectQuerysetMixin, ProtectedCreateView from .forms.registration import WEIChooseBusForm from .models import WEIClub, WEIRegistration, WEIMembership, Bus, BusTeam, WEIRole -from .forms import WEIForm, WEIRegistrationForm, BusForm, BusTeamForm, WEIMembership1AForm, \ +from .forms import WEIForm, WEIRegistrationForm, WEIRegistration1AForm, WEIRegistration2AForm, BusForm, BusTeamForm, WEIMembership1AForm, \ WEIMembershipForm, CurrentSurvey from .tables import BusRepartitionTable, BusTable, BusTeamTable, WEITable, WEIRegistrationTable, \ WEIRegistration1ATable, WEIMembershipTable @@ -510,7 +510,7 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView): Register a new user to the WEI """ model = WEIRegistration - form_class = WEIRegistrationForm + form_class = WEIRegistration1AForm extra_context = {"title": _("Register first year student to the WEI")} def get_sample_object(self): @@ -604,7 +604,7 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView): Register an old user to the WEI """ model = WEIRegistration - form_class = WEIRegistrationForm + form_class = WEIRegistration2AForm extra_context = {"title": _("Register old student to the WEI")} def get_sample_object(self): From 89cc03141bf49a9a97dba2c43dd925b84d97c969 Mon Sep 17 00:00:00 2001 From: quark Date: Thu, 12 Jun 2025 18:48:29 +0200 Subject: [PATCH 033/153] allow search with club name --- apps/food/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/food/views.py b/apps/food/views.py index d9556ef6..f8c7977d 100644 --- a/apps/food/views.py +++ b/apps/food/views.py @@ -63,7 +63,8 @@ class FoodListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, Li valid_regex = is_regex(pattern) suffix = '__iregex' if valid_regex else '__istartswith' prefix = '^' if valid_regex else '' - qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern})) + qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern}) + | Q(**{f'owner__name{suffix}': prefix + pattern})) else: qs = qs.none() search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Food, 'view')) From d71105976f58628c90d730ab991e8b2c96704edd Mon Sep 17 00:00:00 2001 From: quark Date: Sat, 14 Jun 2025 18:01:42 +0200 Subject: [PATCH 034/153] oidc --- apps/permission/scopes.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/apps/permission/scopes.py b/apps/permission/scopes.py index 6ee5818f..29b04217 100644 --- a/apps/permission/scopes.py +++ b/apps/permission/scopes.py @@ -3,6 +3,7 @@ from oauth2_provider.oauth2_validators import OAuth2Validator from oauth2_provider.scopes import BaseScopes from member.models import Club +from note.models import Alias from note_kfet.middlewares import get_current_request from .backends import PermissionBackend @@ -35,7 +36,18 @@ class PermissionScopes(BaseScopes): class PermissionOAuth2Validator(OAuth2Validator): - oidc_claim_scope = None # fix breaking change of django-oauth-toolkit 2.0.0 + oidc_claim_scope = OAuth2Validator.oidc_claim_scope + oidc_claim_scope.update({"name": 'openid', + "normalized_name": 'openid', + "email": 'openid', + }) + + def get_additional_claims(self, request): + return { + "name": request.user.username, + "normalized_name": Alias.normalize(request.user.username), + "email": request.user.email, + } def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs): """ From 97621e8704bd3e99987d813cc73f595c27d7b2d9 Mon Sep 17 00:00:00 2001 From: thomasl Date: Sat, 14 Jun 2025 20:07:29 +0200 Subject: [PATCH 035/153] Update doc about scripts --- docs/scripts.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/scripts.rst b/docs/scripts.rst index fc85468c..7ddda96d 100644 --- a/docs/scripts.rst +++ b/docs/scripts.rst @@ -136,7 +136,7 @@ de diffusion utiles. Faîtes attention, donc où la sortie est stockée. -Il prend 2 options : +Il prend 4 options : * ``--type``, qui prend en argument ``members`` (défaut), ``clubs``, ``events``, ``art``, ``sport``, qui permet respectivement de sortir la liste des adresses mails des adhérent⋅es @@ -149,7 +149,10 @@ Il prend 2 options : pour la ML Adhérents, pour exporter les mails des adhérents au BDE pendant n'importe laquelle des ``n+1`` dernières années. -Le script sort sur la sortie standard la liste des adresses mails à inscrire. +* ``--email``, qui prend en argument une chaine de caractère contenant une adresse email. + +Si aucun email n'est renseigné, le script sort sur la sortie standard la liste des adresses mails à inscrire. +Dans le cas contraire, la liste est envoyée à l'adresse passée en argument. Attention : il y a parfois certains cas particuliers à prendre en compte, il n'est malheureusement pas aussi simple que de simplement supposer que ces listes sont exhaustives. From 092cc37320a996aabf865412d5fe6d812c66c2c7 Mon Sep 17 00:00:00 2001 From: quark Date: Tue, 17 Jun 2025 00:26:13 +0200 Subject: [PATCH 036/153] OIDC 0 Quark 1 --- apps/permission/scopes.py | 29 ++++++++++++++++++++--------- apps/permission/signals.py | 1 + apps/permission/views.py | 2 +- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/apps/permission/scopes.py b/apps/permission/scopes.py index 29b04217..7d2619c1 100644 --- a/apps/permission/scopes.py +++ b/apps/permission/scopes.py @@ -18,22 +18,27 @@ class PermissionScopes(BaseScopes): """ def get_all_scopes(self): - return {f"{p.id}_{club.id}": f"{p.description} (club {club.name})" - for p in Permission.objects.all() for club in Club.objects.all()} + scopes = {f"{p.id}_{club.id}": f"{p.description} (club {club.name})" + for p in Permission.objects.all() for club in Club.objects.all()} + scopes['openid'] = "OpenID Connect" + return scopes def get_available_scopes(self, application=None, request=None, *args, **kwargs): if not application: return [] - return [f"{p.id}_{p.membership.club.id}" - for t in Permission.PERMISSION_TYPES - for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0])] + scopes = [f"{p.id}_{p.membership.club.id}" + for t in Permission.PERMISSION_TYPES + for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0])] + scopes.append('openid') + return scopes def get_default_scopes(self, application=None, request=None, *args, **kwargs): if not application: return [] - return [f"{p.id}_{p.membership.club.id}" - for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')] - + scopes = [f"{p.id}_{p.membership.club.id}" + for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')] + scopes.append('openid') + return scopes class PermissionOAuth2Validator(OAuth2Validator): oidc_claim_scope = OAuth2Validator.oidc_claim_scope @@ -49,6 +54,10 @@ class PermissionOAuth2Validator(OAuth2Validator): "email": request.user.email, } + def get_discovery_claims(self, request): + claims = super().get_discovery_claims(self) + return claims + ["name", "normalized_name", "email"] + def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs): """ User can request as many scope as he wants, including invalid scopes, @@ -65,7 +74,9 @@ class PermissionOAuth2Validator(OAuth2Validator): scope = f"{p.id}_{p.membership.club.id}" if scope in scopes: valid_scopes.add(scope) + + if 'openid' in scopes: + valid_scopes.add('openid') request.scopes = valid_scopes - return valid_scopes diff --git a/apps/permission/signals.py b/apps/permission/signals.py index b2394c6f..5ea04113 100644 --- a/apps/permission/signals.py +++ b/apps/permission/signals.py @@ -19,6 +19,7 @@ EXCLUDED = [ 'oauth2_provider.accesstoken', 'oauth2_provider.grant', 'oauth2_provider.refreshtoken', + 'oauth2_provider.idtoken', 'sessions.session', ] diff --git a/apps/permission/views.py b/apps/permission/views.py index e7de920e..39e1f98c 100644 --- a/apps/permission/views.py +++ b/apps/permission/views.py @@ -171,7 +171,7 @@ class ScopesView(LoginRequiredMixin, TemplateView): available_scopes = scopes.get_available_scopes(app) context["scopes"][app] = OrderedDict() items = [(k, v) for (k, v) in all_scopes.items() if k in available_scopes] - items.sort(key=lambda x: (int(x[0].split("_")[1]), int(x[0].split("_")[0]))) + # items.sort(key=lambda x: (int(x[0].split("_")[1]), int(x[0].split("_")[0]))) for k, v in items: context["scopes"][app][k] = v From df0d886db97ce55088f71e4bb192a47e12182f24 Mon Sep 17 00:00:00 2001 From: quark Date: Tue, 17 Jun 2025 11:46:33 +0200 Subject: [PATCH 037/153] linters --- apps/permission/scopes.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/permission/scopes.py b/apps/permission/scopes.py index 7d2619c1..0702aefa 100644 --- a/apps/permission/scopes.py +++ b/apps/permission/scopes.py @@ -1,5 +1,6 @@ # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later + from oauth2_provider.oauth2_validators import OAuth2Validator from oauth2_provider.scopes import BaseScopes from member.models import Club @@ -19,7 +20,7 @@ class PermissionScopes(BaseScopes): def get_all_scopes(self): scopes = {f"{p.id}_{club.id}": f"{p.description} (club {club.name})" - for p in Permission.objects.all() for club in Club.objects.all()} + for p in Permission.objects.all() for club in Club.objects.all()} scopes['openid'] = "OpenID Connect" return scopes @@ -27,8 +28,8 @@ class PermissionScopes(BaseScopes): if not application: return [] scopes = [f"{p.id}_{p.membership.club.id}" - for t in Permission.PERMISSION_TYPES - for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0])] + for t in Permission.PERMISSION_TYPES + for p in PermissionBackend.get_raw_permissions(get_current_request(), t[0])] scopes.append('openid') return scopes @@ -36,10 +37,11 @@ class PermissionScopes(BaseScopes): if not application: return [] scopes = [f"{p.id}_{p.membership.club.id}" - for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')] + for p in PermissionBackend.get_raw_permissions(get_current_request(), 'view')] scopes.append('openid') return scopes + class PermissionOAuth2Validator(OAuth2Validator): oidc_claim_scope = OAuth2Validator.oidc_claim_scope oidc_claim_scope.update({"name": 'openid', @@ -74,7 +76,7 @@ class PermissionOAuth2Validator(OAuth2Validator): scope = f"{p.id}_{p.membership.club.id}" if scope in scopes: valid_scopes.add(scope) - + if 'openid' in scopes: valid_scopes.add('openid') From a6df0e7c6926acea9591d9c6c38c3d25302fdf31 Mon Sep 17 00:00:00 2001 From: ehouarn Date: Tue, 17 Jun 2025 19:00:46 +0200 Subject: [PATCH 038/153] Autres permissions --- apps/permission/fixtures/initial.json | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 883db297..28d47194 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -1695,7 +1695,7 @@ "wei", "weimembership" ], - "query": "[\"AND\", {\"club\": [\"club\"], \"club__weiclub__membership_end__gte\": [\"today\"]}, [\"OR\", {\"registration__soge_credit\": true}, {\"user__note__balance__gte\": {\"F\": [\"F\", \"fee\"]}}]]", + "query": "{\"club\": [\"club\"]}", "type": "add", "mask": 2, "field": "", @@ -4334,6 +4334,22 @@ "description": "Voir mon bus" } }, + { + "model": "permission.permission", + "pk": 292, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{\"club__pk__lte\": 2}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter un membre au BDE ou à la Kfet" + } + }, { "model": "permission.role", "pk": 1, @@ -4694,6 +4710,7 @@ "name": "GC WEI", "permissions": [ 22, + 49, 62, 70, 72, @@ -4721,6 +4738,7 @@ 128, 130, 142, + 269, 271, 272, 273, @@ -4733,7 +4751,8 @@ 280, 281, 282, - 283 + 283, + 292 ] } }, From 5c5f579729a12e84feefb7944e0def85b8e264d3 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 20 Jun 2025 14:24:03 +0200 Subject: [PATCH 039/153] Traductions --- locale/es/LC_MESSAGES/django.po | 1979 +++++++---------------------- locale/fr/LC_MESSAGES/django.po | 2058 ++++++++----------------------- 2 files changed, 928 insertions(+), 3109 deletions(-) diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index cee9810f..3baa260d 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-06-02 00:58+0200\n" +"POT-Creation-Date: 2025-06-20 14:02+0200\n" "PO-Revision-Date: 2022-04-11 23:12+0200\n" "Last-Translator: bleizi \n" "Language-Team: \n" @@ -294,14 +294,14 @@ msgstr "Tipo" #: 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:116 +#: apps/wei/forms/registration.py:129 msgid "Last name" msgstr "Apellido" #: 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:121 +#: apps/wei/forms/registration.py:134 msgid "First name" msgstr "Nombre" @@ -320,10 +320,6 @@ msgstr "Saldo de la cuenta" #: 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 -#: env/lib/python3.11/site-packages/django/forms/formsets.py:499 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:13 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:38 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:7 #: 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 @@ -829,8 +825,6 @@ msgid "weeks" msgstr "" #: apps/food/utils.py:53 -#: env/lib/python3.11/site-packages/django/db/models/base.py:1423 -#: env/lib/python3.11/site-packages/django/forms/models.py:893 #, fuzzy #| msgid "add" msgid "and" @@ -885,12 +879,10 @@ msgid "Details of:" msgstr "Detalles del WEI" #: apps/food/views.py:447 apps/treasury/tables.py:149 -#: env/lib/python3.11/site-packages/django/forms/widgets.py:795 msgid "Yes" msgstr "Sí" #: apps/food/views.py:449 apps/member/models.py:99 apps/treasury/tables.py:149 -#: env/lib/python3.11/site-packages/django/forms/widgets.py:796 msgid "No" msgstr "No" @@ -1027,12 +1019,12 @@ msgid "Check this case if the Société Générale paid the inscription." msgstr "Marcar esta casilla si Société Générale pagó la registración." #: apps/member/forms.py:185 apps/registration/forms.py:78 -#: apps/wei/forms/registration.py:103 +#: apps/wei/forms/registration.py:116 msgid "Credit type" msgstr "Tipo de crédito" #: apps/member/forms.py:186 apps/registration/forms.py:79 -#: apps/wei/forms/registration.py:104 +#: apps/wei/forms/registration.py:117 msgid "No credit" msgstr "No crédito" @@ -1041,13 +1033,13 @@ msgid "You can credit the note of the user." msgstr "Usted puede acreditar la note del usuario." #: apps/member/forms.py:192 apps/registration/forms.py:84 -#: apps/wei/forms/registration.py:109 +#: apps/wei/forms/registration.py:122 msgid "Credit amount" msgstr "Valor del crédito" #: 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:126 +#: apps/wei/forms/registration.py:139 msgid "Bank" msgstr "Banco" @@ -2027,7 +2019,6 @@ msgstr "" #: apps/note/models/transactions.py:357 apps/note/models/transactions.py:360 #: apps/note/models/transactions.py:363 apps/wei/views.py:1097 #: apps/wei/views.py:1101 -#: env/lib/python3.11/site-packages/django/forms/fields.py:91 msgid "This field is required." msgstr "Este campo es obligatorio." @@ -2065,7 +2056,6 @@ msgstr "Añadir en retorno" #: apps/wei/templates/wei/bus_detail.html:24 #: apps/wei/templates/wei/busteam_detail.html:20 #: apps/wei/templates/wei/busteam_detail.html:42 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:37 #: note_kfet/templates/oauth2_provider/application_detail.html:38 msgid "Edit" msgstr "Editar" @@ -2406,19 +2396,16 @@ msgid "Available scopes" msgstr "" #: apps/permission/templates/permission/scopes.html:42 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:24 msgid "No applications defined" msgstr "Ninguna aplicación definida" #: apps/permission/templates/permission/scopes.html:43 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:25 msgid "Click here" msgstr "Pulsar aquí" #: apps/permission/templates/permission/scopes.html:43 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:25 msgid "if you want to register a new one" msgstr "si quiere crear una nueva" @@ -3067,12 +3054,12 @@ msgid "The selected user is not validated. Please validate its account first" msgstr "" "El usuario seleccionado no ha sido validado. Validar esta cuenta primero" -#: apps/wei/forms/registration.py:71 apps/wei/models.py:140 +#: apps/wei/forms/registration.py:84 apps/wei/models.py:140 #: apps/wei/models.py:348 msgid "bus" msgstr "bus" -#: apps/wei/forms/registration.py:72 +#: apps/wei/forms/registration.py:85 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." @@ -3081,11 +3068,11 @@ msgstr "" "derecho de imponer su bus y su equipo, en particular para los electrones " "libres." -#: apps/wei/forms/registration.py:79 +#: apps/wei/forms/registration.py:92 msgid "Team" msgstr "Equipo" -#: apps/wei/forms/registration.py:81 +#: apps/wei/forms/registration.py:94 msgid "" "Leave this field empty if you won't be in a team (staff, bus chief, free " "electron)" @@ -3093,16 +3080,16 @@ msgstr "" "Deje este campo vacío si no quiere estar en un equipo (staff, jefe de bus, " "electrón libre)" -#: apps/wei/forms/registration.py:87 apps/wei/forms/registration.py:97 +#: apps/wei/forms/registration.py:100 apps/wei/forms/registration.py:110 #: apps/wei/models.py:174 msgid "WEI Roles" msgstr "Papeles en el WEI" -#: apps/wei/forms/registration.py:88 +#: apps/wei/forms/registration.py:101 msgid "Select the roles that you are interested in." msgstr "Elegir los papeles que le interesa." -#: apps/wei/forms/registration.py:134 +#: apps/wei/forms/registration.py:147 msgid "This team doesn't belong to the given bus." msgstr "Este equipo no pertenece al bus dado." @@ -3188,13 +3175,13 @@ msgstr "" #, fuzzy #| msgid "transactions" msgid "Note transaction" -msgstr "transacciones" +msgstr "Transacción" #: apps/wei/models.py:212 #, fuzzy #| msgid "created at" msgid "caution type" -msgstr "creada el" +msgstr "tipo de fianza" #: apps/wei/models.py:216 apps/wei/templates/wei/weimembership_form.html:64 msgid "birth date" @@ -3410,10 +3397,8 @@ msgid "View WEI" msgstr "Ver un WEI" #: apps/wei/templates/wei/bus_detail.html:21 -#, fuzzy -#| msgid "club" msgid "View club" -msgstr "club" +msgstr "Ver club" #: apps/wei/templates/wei/bus_detail.html:26 #: apps/wei/templates/wei/busteam_detail.html:24 @@ -3566,28 +3551,28 @@ msgstr "" #, fuzzy, python-format #| msgid "membership fee (paid students)" msgid "Membership fees: %(amount)s" -msgstr "pago de afiliación (estudiantes pagados)" +msgstr "Pagos de afiliación (estudiantes pagados)" #: apps/wei/templates/wei/weimembership_form.html:153 #, python-format msgid "Deposit (by Note transaction): %(amount)s" -msgstr "" +msgstr "Fianza (transacción) : %(amount)s" #: apps/wei/templates/wei/weimembership_form.html:156 #: apps/wei/templates/wei/weimembership_form.html:163 #, python-format msgid "Total needed: %(total)s" -msgstr "" +msgstr "Total necesario : %(total)s" #: apps/wei/templates/wei/weimembership_form.html:160 #, python-format msgid "Deposit (by check): %(amount)s" -msgstr "" +msgstr "Fianza (cheque) : %(amount)s" #: apps/wei/templates/wei/weimembership_form.html:168 #, python-format msgid "Current balance: %(balance)s" -msgstr "" +msgstr "Saldo actual : %(balance)s" #: apps/wei/templates/wei/weimembership_form.html:176 msgid "The user didn't give her/his caution check." @@ -3716,24 +3701,21 @@ msgid "No membership found for this registration" msgstr "La afiliación al BDE esta incluida en la afiliación WEI." #: apps/wei/views.py:819 -#, fuzzy #| msgid "" #| "You don't have the permission to add an instance of model {app_label}." #| "{model_name}." msgid "You don't have the permission to update memberships" msgstr "" -"Usted no tiene permiso a añadir an instance of model {app_label}." +"Usted no tiene permiso a añadir una instancia al modelo {app_label}." "{model_name}." #: apps/wei/views.py:825 -#, fuzzy, python-format +#, python-format #| msgid "" #| "You don't have the permission to delete this instance of model " #| "{app_label}.{model_name}." msgid "You don't have the permission to update the field %(field)s" -msgstr "" -"Usted no tiene permiso a suprimir este instance of model {app_label}." -"{model_name}." +msgstr "Usted no tiene permiso a modificar el campo %(field)s" #: apps/wei/views.py:870 msgid "Delete WEI registration" @@ -3748,17 +3730,14 @@ msgid "Validate WEI registration" msgstr "Validar la inscripción WEI" #: apps/wei/views.py:985 -#, fuzzy -#| msgid "Please ask the user to credit its note before deleting this credit." msgid "Please make sure the check is given before validating the registration" msgstr "" -"Por favor pide al usuario acreditar su note antes de suprimir este crédito." +"Por favor asegúrese de que el cheque se entrega antes de validar el registro" #: apps/wei/views.py:991 -#, fuzzy #| msgid "credit transaction" msgid "Create deposit transaction" -msgstr "transacción de crédito" +msgstr "Crear transacción de crédito" #: apps/wei/views.py:992 #, python-format @@ -3767,22 +3746,22 @@ msgid "" msgstr "" #: apps/wei/views.py:1087 -#, fuzzy, python-format +#, python-format #| msgid "" #| "This user don't have enough money to join this club, and can't have a " #| "negative balance." msgid "" -"This user doesn't have enough money to join this club and pay the deposit. " +"This user doesn't have enough money. " "Current balance: %(balance)d€, credit: %(credit)d€, needed: %(needed)d€" msgstr "" -"Este usuario no tiene suficiente dinero para unirse a este club, y no puede " -"tener un saldo negativo." +"Este usuario no tiene suficiente dinero. " +"Saldo actual : %(balance)d€, crédito: %(credit)d€, requerido: %(needed)d€" #: apps/wei/views.py:1140 -#, fuzzy, python-format +#, python-format #| msgid "created at" msgid "Caution %(name)s" -msgstr "creada el" +msgstr "Fianza %(name)s" #: apps/wei/views.py:1354 msgid "Attribute buses to first year members" @@ -3987,1497 +3966,6 @@ msgstr "" msgid "List of wrapped" msgstr "" -#: env/lib/python3.11/site-packages/django/contrib/messages/apps.py:15 -msgid "Messages" -msgstr "" - -#: env/lib/python3.11/site-packages/django/contrib/sitemaps/apps.py:8 -msgid "Site Maps" -msgstr "" - -#: env/lib/python3.11/site-packages/django/contrib/staticfiles/apps.py:9 -msgid "Static Files" -msgstr "" - -#: env/lib/python3.11/site-packages/django/contrib/syndication/apps.py:7 -#, fuzzy -#| msgid "Invitation" -msgid "Syndication" -msgstr "Invitación" - -#. Translators: String used to replace omitted page numbers in elided page -#. range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10]. -#: env/lib/python3.11/site-packages/django/core/paginator.py:30 -msgid "…" -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/paginator.py:50 -msgid "That page number is not an integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/paginator.py:52 -msgid "That page number is less than 1" -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/paginator.py:54 -#, fuzzy -#| msgid "There is no results." -msgid "That page contains no results" -msgstr "No hay resultado." - -#: env/lib/python3.11/site-packages/django/core/validators.py:22 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid value." -msgstr "invalidar" - -#: env/lib/python3.11/site-packages/django/core/validators.py:104 -#: env/lib/python3.11/site-packages/django/forms/fields.py:752 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid URL." -msgstr "invalidar" - -#: env/lib/python3.11/site-packages/django/core/validators.py:165 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid integer." -msgstr "invalidar" - -#: env/lib/python3.11/site-packages/django/core/validators.py:176 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid email address." -msgstr "invalidar" - -#. Translators: "letters" means latin letters: a-z and A-Z. -#: env/lib/python3.11/site-packages/django/core/validators.py:259 -msgid "" -"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:267 -msgid "" -"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or " -"hyphens." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:281 -#: env/lib/python3.11/site-packages/django/core/validators.py:289 -#: env/lib/python3.11/site-packages/django/core/validators.py:318 -#, fuzzy -#| msgid "This activity is not validated yet." -msgid "Enter a valid IPv4 address." -msgstr "Esta actividad no fue validada por ahora." - -#: env/lib/python3.11/site-packages/django/core/validators.py:298 -#: env/lib/python3.11/site-packages/django/core/validators.py:319 -#, fuzzy -#| msgid "This activity is not validated yet." -msgid "Enter a valid IPv6 address." -msgstr "Esta actividad no fue validada por ahora." - -#: env/lib/python3.11/site-packages/django/core/validators.py:310 -#: env/lib/python3.11/site-packages/django/core/validators.py:317 -#, fuzzy -#| msgid "This activity is not validated yet." -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Esta actividad no fue validada por ahora." - -#: env/lib/python3.11/site-packages/django/core/validators.py:353 -msgid "Enter only digits separated by commas." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:359 -#, python-format -msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:394 -#, python-format -msgid "Ensure this value is less than or equal to %(limit_value)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:403 -#, python-format -msgid "Ensure this value is greater than or equal to %(limit_value)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:412 -#, python-format -msgid "Ensure this value is a multiple of step size %(limit_value)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:422 -#, python-format -msgid "" -"Ensure this value has at least %(limit_value)d character (it has " -"%(show_value)d)." -msgid_plural "" -"Ensure this value has at least %(limit_value)d characters (it has " -"%(show_value)d)." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:440 -#, python-format -msgid "" -"Ensure this value has at most %(limit_value)d character (it has " -"%(show_value)d)." -msgid_plural "" -"Ensure this value has at most %(limit_value)d characters (it has " -"%(show_value)d)." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:463 -#: env/lib/python3.11/site-packages/django/forms/fields.py:347 -#: env/lib/python3.11/site-packages/django/forms/fields.py:386 -#, fuzzy -#| msgid "phone number" -msgid "Enter a number." -msgstr "número de teléfono" - -#: env/lib/python3.11/site-packages/django/core/validators.py:465 -#, python-format -msgid "Ensure that there are no more than %(max)s digit in total." -msgid_plural "Ensure that there are no more than %(max)s digits in total." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:470 -#, python-format -msgid "Ensure that there are no more than %(max)s decimal place." -msgid_plural "Ensure that there are no more than %(max)s decimal places." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:475 -#, python-format -msgid "" -"Ensure that there are no more than %(max)s digit before the decimal point." -msgid_plural "" -"Ensure that there are no more than %(max)s digits before the decimal point." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:546 -#, python-format -msgid "" -"File extension “%(extension)s” is not allowed. Allowed extensions are: " -"%(allowed_extensions)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:607 -msgid "Null characters are not allowed." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/base.py:1425 -#, fuzzy, python-format -#| msgid "A template with this name already exist" -msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "Un plantilla de transacción con un nombre similar ya existe" - -#: env/lib/python3.11/site-packages/django/db/models/constraints.py:17 -#, python-format -msgid "Constraint “%(name)s” is violated." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:128 -#, fuzzy, python-format -#| msgid "This activity is not validated yet." -msgid "Value %(value)r is not a valid choice." -msgstr "Esta actividad no fue validada por ahora." - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:129 -#, fuzzy -#| msgid "This image cannot be loaded." -msgid "This field cannot be null." -msgstr "Esta imagen no puede ser cargada." - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:130 -#, fuzzy -#| msgid "This image cannot be loaded." -msgid "This field cannot be blank." -msgstr "Esta imagen no puede ser cargada." - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:131 -#, fuzzy, python-format -#| msgid "A template with this name already exist" -msgid "%(model_name)s with this %(field_label)s already exists." -msgstr "Un plantilla de transacción con un nombre similar ya existe" - -#. Translators: The 'lookup_type' is one of 'date', 'year' or -#. 'month'. Eg: "Title must be unique for pub_date year" -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:135 -#, python-format -msgid "" -"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:173 -#, python-format -msgid "Field of type: %(field_type)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1094 -#, python-format -msgid "“%(value)s” value must be either True or False." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1095 -#, python-format -msgid "“%(value)s” value must be either True, False, or None." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1097 -msgid "Boolean (Either True or False)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1147 -#, python-format -msgid "String (up to %(max_length)s)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1149 -msgid "String (unlimited)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1253 -msgid "Comma-separated integers" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1354 -#, python-format -msgid "" -"“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD " -"format." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1358 -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1493 -#, python-format -msgid "" -"“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " -"date." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1362 -msgid "Date (without time)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1489 -#, python-format -msgid "" -"“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." -"uuuuuu]][TZ] format." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1497 -#, python-format -msgid "" -"“%(value)s” value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" -"[TZ]) but it is an invalid date/time." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1502 -msgid "Date (with time)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1626 -#, python-format -msgid "“%(value)s” value must be a decimal number." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1628 -#, fuzzy -#| msgid "phone number" -msgid "Decimal number" -msgstr "número de teléfono" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1789 -#, python-format -msgid "" -"“%(value)s” value has an invalid format. It must be in [DD] [[HH:]MM:]ss[." -"uuuuuu] format." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1793 -#, fuzzy -#| msgid "action" -msgid "Duration" -msgstr "acción" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1845 -#, fuzzy -#| msgid "address" -msgid "Email address" -msgstr "dirección" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1870 -msgid "File path" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1948 -#, python-format -msgid "“%(value)s” value must be a float." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1950 -#, fuzzy -#| msgid "phone number" -msgid "Floating point number" -msgstr "número de teléfono" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1990 -#, python-format -msgid "“%(value)s” value must be an integer." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1992 -msgid "Integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2088 -msgid "Big (8 byte) integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2105 -msgid "Small integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2113 -#, fuzzy -#| msgid "IP Address" -msgid "IPv4 address" -msgstr "Dirección IP" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2144 -#, fuzzy -#| msgid "IP Address" -msgid "IP address" -msgstr "Dirección IP" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2237 -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2238 -#, python-format -msgid "“%(value)s” value must be either None, True or False." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2240 -msgid "Boolean (Either True, False or None)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2291 -msgid "Positive big integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2306 -msgid "Positive integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2321 -msgid "Positive small integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2337 -#, python-format -msgid "Slug (up to %(max_length)s)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2373 -msgid "Text" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2448 -#, python-format -msgid "" -"“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " -"format." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2452 -#, python-format -msgid "" -"“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " -"invalid time." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2456 -msgid "Time" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2564 -msgid "URL" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2588 -msgid "Raw binary data" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2653 -#, fuzzy, python-format -#| msgid "This activity is not validated yet." -msgid "“%(value)s” is not a valid UUID." -msgstr "Esta actividad no fue validada por ahora." - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2655 -#, fuzzy -#| msgid "Invoice identifier" -msgid "Universally unique identifier" -msgstr "Numero de factura" - -#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:232 -msgid "File" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:393 -msgid "Image" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:26 -#, fuzzy -#| msgid "Object" -msgid "A JSON object" -msgstr "Asunto" - -#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:28 -#, fuzzy -#| msgid "This address must be valid." -msgid "Value must be valid JSON." -msgstr "Este correo tiene que ser valido." - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:919 -#, fuzzy, python-format -#| msgid "A template with this name already exist" -msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "Un plantilla de transacción con un nombre similar ya existe" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:921 -msgid "Foreign Key (type determined by related field)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1212 -msgid "One-to-one relationship" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1269 -#, python-format -msgid "%(from)s-%(to)s relationship" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1271 -#, python-format -msgid "%(from)s-%(to)s relationships" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1319 -msgid "Many-to-many relationship" -msgstr "" - -#. Translators: If found as last label character, these punctuation -#. characters will prevent the default label_suffix to be appended to the label -#: env/lib/python3.11/site-packages/django/forms/boundfield.py:184 -msgid ":?.!" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:298 -#, fuzzy -#| msgid "phone number" -msgid "Enter a whole number." -msgstr "número de teléfono" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:467 -#: env/lib/python3.11/site-packages/django/forms/fields.py:1241 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid date." -msgstr "invalidar" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:490 -#: env/lib/python3.11/site-packages/django/forms/fields.py:1242 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid time." -msgstr "invalidar" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:517 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid date/time." -msgstr "invalidar" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:551 -#, fuzzy -#| msgid "Email validation" -msgid "Enter a valid duration." -msgstr "Validación del correo electrónico" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:552 -#, python-brace-format -msgid "The number of days must be between {min_days} and {max_days}." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:621 -msgid "No file was submitted. Check the encoding type on the form." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:622 -msgid "No file was submitted." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:623 -msgid "The submitted file is empty." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:625 -#, python-format -msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." -msgid_plural "" -"Ensure this filename has at most %(max)d characters (it has %(length)d)." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:630 -msgid "Please either submit a file or check the clear checkbox, not both." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:694 -msgid "" -"Upload a valid image. The file you uploaded was either not an image or a " -"corrupted image." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:857 -#: env/lib/python3.11/site-packages/django/forms/fields.py:949 -#: env/lib/python3.11/site-packages/django/forms/models.py:1566 -#, python-format -msgid "Select a valid choice. %(value)s is not one of the available choices." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:951 -#: env/lib/python3.11/site-packages/django/forms/fields.py:1070 -#: env/lib/python3.11/site-packages/django/forms/models.py:1564 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a list of values." -msgstr "invalidar" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:1071 -#, fuzzy -#| msgid "phone number" -msgid "Enter a complete value." -msgstr "número de teléfono" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:1313 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid UUID." -msgstr "invalidar" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:1343 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid JSON." -msgstr "invalidar" - -#. Translators: This is the default suffix added to form field labels -#: env/lib/python3.11/site-packages/django/forms/forms.py:98 -msgid ":" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/forms.py:244 -#: env/lib/python3.11/site-packages/django/forms/forms.py:328 -#, python-format -msgid "(Hidden field %(name)s) %(error)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/formsets.py:63 -#, python-format -msgid "" -"ManagementForm data is missing or has been tampered with. Missing fields: " -"%(field_names)s. You may need to file a bug report if the issue persists." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/formsets.py:67 -#, python-format -msgid "Please submit at most %(num)d form." -msgid_plural "Please submit at most %(num)d forms." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/forms/formsets.py:72 -#, python-format -msgid "Please submit at least %(num)d form." -msgid_plural "Please submit at least %(num)d forms." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/forms/formsets.py:484 -#: env/lib/python3.11/site-packages/django/forms/formsets.py:491 -msgid "Order" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:886 -#, python-format -msgid "Please correct the duplicate data for %(field)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:891 -#, python-format -msgid "Please correct the duplicate data for %(field)s, which must be unique." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:898 -#, python-format -msgid "" -"Please correct the duplicate data for %(field_name)s which must be unique " -"for the %(lookup)s in %(date_field)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:907 -msgid "Please correct the duplicate values below." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:1338 -msgid "The inline value did not match the parent instance." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:1429 -msgid "Select a valid choice. That choice is not one of the available choices." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:1568 -#, fuzzy, python-format -#| msgid "This activity is not validated yet." -msgid "“%(pk)s” is not a valid value." -msgstr "Esta actividad no fue validada por ahora." - -#: env/lib/python3.11/site-packages/django/forms/utils.py:226 -#, python-format -msgid "" -"%(datetime)s couldn’t be interpreted in time zone %(current_timezone)s; it " -"may be ambiguous or it may not exist." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/widgets.py:463 -msgid "Clear" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/widgets.py:464 -#, fuzzy -#| msgid "Current activity" -msgid "Currently" -msgstr "Actividad actual" - -#: env/lib/python3.11/site-packages/django/forms/widgets.py:465 -#, fuzzy -#| msgid "change" -msgid "Change" -msgstr "cambiar" - -#: env/lib/python3.11/site-packages/django/forms/widgets.py:794 -msgid "Unknown" -msgstr "" - -#. Translators: Please do not add spaces around commas. -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:874 -msgid "yes,no,maybe" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:904 -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:921 -#, python-format -msgid "%(size)d byte" -msgid_plural "%(size)d bytes" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:923 -#, python-format -msgid "%s KB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:925 -#, python-format -msgid "%s MB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:927 -#, python-format -msgid "%s GB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:929 -#, python-format -msgid "%s TB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:931 -#, python-format -msgid "%s PB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:73 -msgid "p.m." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:74 -msgid "a.m." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:79 -msgid "PM" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:80 -msgid "AM" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:152 -msgid "midnight" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:154 -msgid "noon" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:7 -msgid "Monday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:8 -msgid "Tuesday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:9 -msgid "Wednesday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:10 -msgid "Thursday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:11 -msgid "Friday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:12 -msgid "Saturday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:13 -msgid "Sunday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:16 -msgid "Mon" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:17 -msgid "Tue" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:18 -msgid "Wed" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:19 -msgid "Thu" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:20 -msgid "Fri" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:21 -msgid "Sat" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:22 -msgid "Sun" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:25 -msgid "January" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:26 -msgid "February" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:27 -#, fuzzy -#| msgid "Search WEI" -msgid "March" -msgstr "Buscar un WEI" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:28 -msgid "April" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:29 -msgid "May" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:30 -msgid "June" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:31 -msgid "July" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:32 -msgid "August" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:33 -#, fuzzy -#| msgid "member" -msgid "September" -msgstr "miembro" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:34 -msgid "October" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:35 -#, fuzzy -#| msgid "member" -msgid "November" -msgstr "miembro" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:36 -#, fuzzy -#| msgid "member" -msgid "December" -msgstr "miembro" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:39 -#, fuzzy -#| msgid "add" -msgid "jan" -msgstr "añadir" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:40 -#, fuzzy -#| msgid "fee" -msgid "feb" -msgstr "pago" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:41 -msgid "mar" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:42 -msgid "apr" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:43 -msgid "may" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:44 -#, fuzzy -#| msgid "add" -msgid "jun" -msgstr "añadir" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:45 -msgid "jul" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:46 -msgid "aug" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:47 -msgid "sep" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:48 -#, fuzzy -#| msgid "product" -msgid "oct" -msgstr "producto" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:49 -msgid "nov" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:50 -msgid "dec" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:53 -msgctxt "abbrev. month" -msgid "Jan." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:54 -msgctxt "abbrev. month" -msgid "Feb." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:55 -#, fuzzy -#| msgid "Search WEI" -msgctxt "abbrev. month" -msgid "March" -msgstr "Buscar un WEI" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:56 -msgctxt "abbrev. month" -msgid "April" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:57 -msgctxt "abbrev. month" -msgid "May" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:58 -msgctxt "abbrev. month" -msgid "June" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:59 -msgctxt "abbrev. month" -msgid "July" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:60 -msgctxt "abbrev. month" -msgid "Aug." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:61 -msgctxt "abbrev. month" -msgid "Sept." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:62 -msgctxt "abbrev. month" -msgid "Oct." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:63 -msgctxt "abbrev. month" -msgid "Nov." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:64 -msgctxt "abbrev. month" -msgid "Dec." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:67 -msgctxt "alt. month" -msgid "January" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:68 -msgctxt "alt. month" -msgid "February" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:69 -#, fuzzy -#| msgid "Search WEI" -msgctxt "alt. month" -msgid "March" -msgstr "Buscar un WEI" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:70 -msgctxt "alt. month" -msgid "April" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:71 -msgctxt "alt. month" -msgid "May" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:72 -msgctxt "alt. month" -msgid "June" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:73 -msgctxt "alt. month" -msgid "July" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:74 -msgctxt "alt. month" -msgid "August" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:75 -#, fuzzy -#| msgid "member" -msgctxt "alt. month" -msgid "September" -msgstr "miembro" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:76 -msgctxt "alt. month" -msgid "October" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:77 -#, fuzzy -#| msgid "member" -msgctxt "alt. month" -msgid "November" -msgstr "miembro" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:78 -#, fuzzy -#| msgid "member" -msgctxt "alt. month" -msgid "December" -msgstr "miembro" - -#: env/lib/python3.11/site-packages/django/utils/ipv6.py:20 -#, fuzzy -#| msgid "This activity is not validated yet." -msgid "This is not a valid IPv6 address." -msgstr "Esta actividad no fue validada por ahora." - -#: env/lib/python3.11/site-packages/django/utils/text.py:138 -#, python-format -msgctxt "String to return when truncating text" -msgid "%(truncated_text)s…" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/text.py:323 -msgid "or" -msgstr "" - -#. Translators: This string is used as a separator between list elements -#: env/lib/python3.11/site-packages/django/utils/text.py:342 -#: env/lib/python3.11/site-packages/django/utils/timesince.py:135 -msgid ", " -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:8 -#, fuzzy, python-format -#| msgid "year" -msgid "%(num)d year" -msgid_plural "%(num)d years" -msgstr[0] "año" -msgstr[1] "año" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:9 -#, python-format -msgid "%(num)d month" -msgid_plural "%(num)d months" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:10 -#, python-format -msgid "%(num)d week" -msgid_plural "%(num)d weeks" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:11 -#, python-format -msgid "%(num)d day" -msgid_plural "%(num)d days" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:12 -#, python-format -msgid "%(num)d hour" -msgid_plural "%(num)d hours" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:13 -#, python-format -msgid "%(num)d minute" -msgid_plural "%(num)d minutes" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:111 -msgid "Forbidden" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:112 -msgid "CSRF verification failed. Request aborted." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:116 -msgid "" -"You are seeing this message because this HTTPS site requires a “Referer " -"header” to be sent by your web browser, but none was sent. This header is " -"required for security reasons, to ensure that your browser is not being " -"hijacked by third parties." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:122 -msgid "" -"If you have configured your browser to disable “Referer” headers, please re-" -"enable them, at least for this site, or for HTTPS connections, or for “same-" -"origin” requests." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:127 -msgid "" -"If you are using the tag or " -"including the “Referrer-Policy: no-referrer” header, please remove them. The " -"CSRF protection requires the “Referer” header to do strict referer checking. " -"If you’re concerned about privacy, use alternatives like for links to third-party sites." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:136 -msgid "" -"You are seeing this message because this site requires a CSRF cookie when " -"submitting forms. This cookie is required for security reasons, to ensure " -"that your browser is not being hijacked by third parties." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:142 -msgid "" -"If you have configured your browser to disable cookies, please re-enable " -"them, at least for this site, or for “same-origin” requests." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:148 -msgid "More information is available with DEBUG=True." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:44 -#, fuzzy -#| msgid "No reason specified" -msgid "No year specified" -msgstr "Ningún motivo dado" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:64 -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:115 -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:214 -msgid "Date out of range" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:94 -#, fuzzy -#| msgid "No reason specified" -msgid "No month specified" -msgstr "Ningún motivo dado" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:147 -#, fuzzy -#| msgid "No reason specified" -msgid "No day specified" -msgstr "Ningún motivo dado" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:194 -#, fuzzy -#| msgid "No reason specified" -msgid "No week specified" -msgstr "Ningún motivo dado" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:349 -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:380 -#, python-format -msgid "No %(verbose_name_plural)s available" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:652 -#, python-format -msgid "" -"Future %(verbose_name_plural)s not available because %(class_name)s." -"allow_future is False." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:692 -#, python-format -msgid "Invalid date string “%(datestr)s” given format “%(format)s”" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/detail.py:56 -#, python-format -msgid "No %(verbose_name)s found matching the query" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/list.py:70 -msgid "Page is not “last”, nor can it be converted to an int." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/list.py:77 -#, python-format -msgid "Invalid page (%(page_number)s): %(message)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/list.py:169 -#, python-format -msgid "Empty list and “%(class_name)s.allow_empty” is False." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/static.py:38 -msgid "Directory indexes are not allowed here." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/static.py:40 -#, python-format -msgid "“%(path)s” does not exist" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/static.py:79 -#, python-format -msgid "Index of %(directory)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:7 -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:220 -msgid "The install worked successfully! Congratulations!" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:206 -#, python-format -msgid "" -"View release notes for Django %(version)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:221 -#, python-format -msgid "" -"You are seeing this page because DEBUG=True is in your settings file and you have not " -"configured any URLs." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:229 -msgid "Django Documentation" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:230 -msgid "Topics, references, & how-to’s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:238 -msgid "Tutorial: A Polling App" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:239 -msgid "Get started with Django" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:247 -msgid "Django Community" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:248 -msgid "Connect, get help, or contribute" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:69 -#, fuzzy -#| msgid "Client secret" -msgid "Confidential" -msgstr "Secreto cliente" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:70 -msgid "Public" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:79 -#, fuzzy -#| msgid "Authorization:" -msgid "Authorization code" -msgstr "Autorizaciones :" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:80 -msgid "Implicit" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:81 -#, fuzzy -#| msgid "Reset my password" -msgid "Resource owner password-based" -msgstr "Reiniciar mi contraseña" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:82 -#, fuzzy -#| msgid "Client secret" -msgid "Client credentials" -msgstr "Secreto cliente" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:83 -msgid "OpenID connect hybrid" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:90 -msgid "No OIDC support" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:91 -msgid "RSA with SHA-2 256" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:92 -msgid "HMAC with SHA-2 256" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:107 -msgid "Allowed URIs list, space separated" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:111 -msgid "Allowed Post Logout URIs list, space separated" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:120 -msgid "Hashed on Save. Copy it now if this is a new secret." -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:190 -#, python-brace-format -msgid "Unauthorized redirect scheme: {scheme}" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:194 -#, python-brace-format -msgid "redirect_uris cannot be empty with grant_type {grant_type}" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:200 -msgid "You must set OIDC_RSA_PRIVATE_KEY to use RSA algorithm" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:209 -msgid "You cannot use HS256 with public grants or clients" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:211 -#, fuzzy -#| msgid "This address must be valid." -msgid "The access token is invalid." -msgstr "Este correo tiene que ser valido." - -#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:218 -#, fuzzy -#| msgid "This address must be valid." -msgid "The access token has expired." -msgstr "Este correo tiene que ser valido." - -#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:225 -#, fuzzy -#| msgid "The user does not have enough money." -msgid "The access token is valid but does not have enough scope." -msgstr "El usuario no tiene suficientemente dinero." - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:6 -#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8 -msgid "Are you sure to delete the application" -msgstr "" -"¿ Usted está seguro de querer suprimir esta aplicación ? Este acto es " -"definitivo" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:12 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:29 -#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 -#: note_kfet/templates/oauth2_provider/authorize.html:28 -msgid "Cancel" -msgstr "Anular" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:10 -#: note_kfet/templates/oauth2_provider/application_detail.html:11 -msgid "Client id" -msgstr "ID cliente" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:15 -#: note_kfet/templates/oauth2_provider/application_detail.html:14 -msgid "Client secret" -msgstr "Secreto cliente" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:20 -#: note_kfet/templates/oauth2_provider/application_detail.html:17 -msgid "Client type" -msgstr "Tipo de cliente" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:25 -#: note_kfet/templates/oauth2_provider/application_detail.html:20 -msgid "Authorization Grant Type" -msgstr "Tipo de autorización" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:30 -#: note_kfet/templates/oauth2_provider/application_detail.html:23 -msgid "Redirect Uris" -msgstr "URL de redirecto" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:36 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:35 -#: note_kfet/templates/oauth2_provider/application_detail.html:37 -#: note_kfet/templates/oauth2_provider/application_form.html:23 -msgid "Go Back" -msgstr "Volver" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:9 -#: note_kfet/templates/oauth2_provider/application_form.html:12 -msgid "Edit application" -msgstr "Editar la aplicación" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:37 -msgid "Save" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:6 -#: note_kfet/templates/oauth2_provider/application_list.html:7 -msgid "Your applications" -msgstr "Sus aplicaciones" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:14 -#: note_kfet/templates/oauth2_provider/application_list.html:30 -msgid "New Application" -msgstr "Nueva aplicación" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_registration_form.html:5 -#: note_kfet/templates/oauth2_provider/application_registration_form.html:5 -msgid "Register a new application" -msgstr "Registrar una nueva aplicación" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:8 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:30 -#: note_kfet/templates/oauth2_provider/authorize.html:9 -#: note_kfet/templates/oauth2_provider/authorize.html:29 -msgid "Authorize" -msgstr "Autorizar" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:17 -#, fuzzy -#| msgid "Application requires following permissions:" -msgid "Application requires the following permissions" -msgstr "La aplicación necesita los derechos siguientes :" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:6 -#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9 -msgid "Are you sure you want to delete this token?" -msgstr "¿ Usted está seguro de querer suprimir este token ?" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:6 -#: note_kfet/templates/oauth2_provider/authorized-tokens.html:7 -msgid "Tokens" -msgstr "Tokens" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:11 -msgid "revoke" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:19 -#: note_kfet/templates/oauth2_provider/authorized-tokens.html:22 -msgid "There are no authorized tokens yet." -msgstr "Por ahora no hay ningún token autorizado." - #: note_kfet/settings/base.py:177 msgid "German" msgstr "Alemán" @@ -5636,6 +4124,37 @@ msgstr "Buscar con atributo, como el nombre..." msgid "There is no results." msgstr "No hay resultado." +#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8 +msgid "Are you sure to delete the application" +msgstr "" +"¿ Usted está seguro de querer suprimir esta aplicación ? Este acto es " +"definitivo" + +#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 +#: note_kfet/templates/oauth2_provider/authorize.html:28 +msgid "Cancel" +msgstr "Anular" + +#: note_kfet/templates/oauth2_provider/application_detail.html:11 +msgid "Client id" +msgstr "ID cliente" + +#: note_kfet/templates/oauth2_provider/application_detail.html:14 +msgid "Client secret" +msgstr "Secreto cliente" + +#: note_kfet/templates/oauth2_provider/application_detail.html:17 +msgid "Client type" +msgstr "Tipo de cliente" + +#: note_kfet/templates/oauth2_provider/application_detail.html:20 +msgid "Authorization Grant Type" +msgstr "Tipo de autorización" + +#: note_kfet/templates/oauth2_provider/application_detail.html:23 +msgid "Redirect Uris" +msgstr "URL de redirecto" + #: note_kfet/templates/oauth2_provider/application_detail.html:29 #, python-format msgid "" @@ -5644,6 +4163,19 @@ msgid "" "that you want to grant for your application." msgstr "" +#: note_kfet/templates/oauth2_provider/application_detail.html:37 +#: note_kfet/templates/oauth2_provider/application_form.html:23 +msgid "Go Back" +msgstr "Volver" + +#: note_kfet/templates/oauth2_provider/application_form.html:12 +msgid "Edit application" +msgstr "Editar la aplicación" + +#: note_kfet/templates/oauth2_provider/application_list.html:7 +msgid "Your applications" +msgstr "Sus aplicaciones" + #: note_kfet/templates/oauth2_provider/application_list.html:11 msgid "" "You can find on this page the list of the applications that you already " @@ -5651,10 +4183,23 @@ msgid "" msgstr "" "Puede encontrar en esta pagina la lista de la aplicaciones que ya registró." +#: note_kfet/templates/oauth2_provider/application_list.html:30 +msgid "New Application" +msgstr "Nueva aplicación" + #: note_kfet/templates/oauth2_provider/application_list.html:31 msgid "Authorized Tokens" msgstr "Tokens autorizados" +#: note_kfet/templates/oauth2_provider/application_registration_form.html:5 +msgid "Register a new application" +msgstr "Registrar una nueva aplicación" + +#: note_kfet/templates/oauth2_provider/authorize.html:9 +#: note_kfet/templates/oauth2_provider/authorize.html:29 +msgid "Authorize" +msgstr "Autorizar" + #: note_kfet/templates/oauth2_provider/authorize.html:14 msgid "Application requires following permissions:" msgstr "La aplicación necesita los derechos siguientes :" @@ -5672,6 +4217,18 @@ msgstr "Éxito" msgid "Please return to your application and enter this code:" msgstr "Volver a su aplicación y entrar este código :" +#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9 +msgid "Are you sure you want to delete this token?" +msgstr "¿ Usted está seguro de querer suprimir este token ?" + +#: note_kfet/templates/oauth2_provider/authorized-tokens.html:7 +msgid "Tokens" +msgstr "Tokens" + +#: note_kfet/templates/oauth2_provider/authorized-tokens.html:22 +msgid "There are no authorized tokens yet." +msgstr "Por ahora no hay ningún token autorizado." + #: note_kfet/templates/registration/logged_out.html:13 msgid "Thanks for spending some quality time with the Web site today." msgstr "Gracias por usar la Note Kfet." @@ -5780,6 +4337,338 @@ msgstr "" "pagar su afiliación. Tambien tiene que validar su correo electronico con el " "enlace que recibió." +#, fuzzy +#~| msgid "Invitation" +#~ msgid "Syndication" +#~ msgstr "Invitación" + +#, fuzzy +#~| msgid "There is no results." +#~ msgid "That page contains no results" +#~ msgstr "No hay resultado." + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid value." +#~ msgstr "invalidar" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid URL." +#~ msgstr "invalidar" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid integer." +#~ msgstr "invalidar" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid email address." +#~ msgstr "invalidar" + +#, fuzzy +#~| msgid "This activity is not validated yet." +#~ msgid "Enter a valid IPv4 address." +#~ msgstr "Esta actividad no fue validada por ahora." + +#, fuzzy +#~| msgid "This activity is not validated yet." +#~ msgid "Enter a valid IPv6 address." +#~ msgstr "Esta actividad no fue validada por ahora." + +#, fuzzy +#~| msgid "This activity is not validated yet." +#~ msgid "Enter a valid IPv4 or IPv6 address." +#~ msgstr "Esta actividad no fue validada por ahora." + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Enter a number." +#~ msgstr "número de teléfono" + +#, fuzzy, python-format +#~| msgid "A template with this name already exist" +#~ msgid "%(model_name)s with this %(field_labels)s already exists." +#~ msgstr "Un plantilla de transacción con un nombre similar ya existe" + +#, fuzzy, python-format +#~| msgid "This activity is not validated yet." +#~ msgid "Value %(value)r is not a valid choice." +#~ msgstr "Esta actividad no fue validada por ahora." + +#, fuzzy +#~| msgid "This image cannot be loaded." +#~ msgid "This field cannot be null." +#~ msgstr "Esta imagen no puede ser cargada." + +#, fuzzy +#~| msgid "This image cannot be loaded." +#~ msgid "This field cannot be blank." +#~ msgstr "Esta imagen no puede ser cargada." + +#, fuzzy, python-format +#~| msgid "A template with this name already exist" +#~ msgid "%(model_name)s with this %(field_label)s already exists." +#~ msgstr "Un plantilla de transacción con un nombre similar ya existe" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Decimal number" +#~ msgstr "número de teléfono" + +#, fuzzy +#~| msgid "action" +#~ msgid "Duration" +#~ msgstr "acción" + +#, fuzzy +#~| msgid "address" +#~ msgid "Email address" +#~ msgstr "dirección" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Floating point number" +#~ msgstr "número de teléfono" + +#, fuzzy +#~| msgid "IP Address" +#~ msgid "IPv4 address" +#~ msgstr "Dirección IP" + +#, fuzzy +#~| msgid "IP Address" +#~ msgid "IP address" +#~ msgstr "Dirección IP" + +#, fuzzy, python-format +#~| msgid "This activity is not validated yet." +#~ msgid "“%(value)s” is not a valid UUID." +#~ msgstr "Esta actividad no fue validada por ahora." + +#, fuzzy +#~| msgid "Invoice identifier" +#~ msgid "Universally unique identifier" +#~ msgstr "Numero de factura" + +#, fuzzy +#~| msgid "Object" +#~ msgid "A JSON object" +#~ msgstr "Asunto" + +#, fuzzy +#~| msgid "This address must be valid." +#~ msgid "Value must be valid JSON." +#~ msgstr "Este correo tiene que ser valido." + +#, fuzzy, python-format +#~| msgid "A template with this name already exist" +#~ msgid "%(model)s instance with %(field)s %(value)r does not exist." +#~ msgstr "Un plantilla de transacción con un nombre similar ya existe" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Enter a whole number." +#~ msgstr "número de teléfono" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid date." +#~ msgstr "invalidar" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid time." +#~ msgstr "invalidar" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid date/time." +#~ msgstr "invalidar" + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid duration." +#~ msgstr "Validación del correo electrónico" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a list of values." +#~ msgstr "invalidar" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Enter a complete value." +#~ msgstr "número de teléfono" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid UUID." +#~ msgstr "invalidar" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid JSON." +#~ msgstr "invalidar" + +#, fuzzy, python-format +#~| msgid "This activity is not validated yet." +#~ msgid "“%(pk)s” is not a valid value." +#~ msgstr "Esta actividad no fue validada por ahora." + +#, fuzzy +#~| msgid "Current activity" +#~ msgid "Currently" +#~ msgstr "Actividad actual" + +#, fuzzy +#~| msgid "change" +#~ msgid "Change" +#~ msgstr "cambiar" + +#, fuzzy +#~| msgid "Search WEI" +#~ msgid "March" +#~ msgstr "Buscar un WEI" + +#, fuzzy +#~| msgid "member" +#~ msgid "September" +#~ msgstr "miembro" + +#, fuzzy +#~| msgid "member" +#~ msgid "November" +#~ msgstr "miembro" + +#, fuzzy +#~| msgid "member" +#~ msgid "December" +#~ msgstr "miembro" + +#, fuzzy +#~| msgid "add" +#~ msgid "jan" +#~ msgstr "añadir" + +#, fuzzy +#~| msgid "fee" +#~ msgid "feb" +#~ msgstr "pago" + +#, fuzzy +#~| msgid "add" +#~ msgid "jun" +#~ msgstr "añadir" + +#, fuzzy +#~| msgid "product" +#~ msgid "oct" +#~ msgstr "producto" + +#, fuzzy +#~| msgid "Search WEI" +#~ msgctxt "abbrev. month" +#~ msgid "March" +#~ msgstr "Buscar un WEI" + +#, fuzzy +#~| msgid "Search WEI" +#~ msgctxt "alt. month" +#~ msgid "March" +#~ msgstr "Buscar un WEI" + +#, fuzzy +#~| msgid "member" +#~ msgctxt "alt. month" +#~ msgid "September" +#~ msgstr "miembro" + +#, fuzzy +#~| msgid "member" +#~ msgctxt "alt. month" +#~ msgid "November" +#~ msgstr "miembro" + +#, fuzzy +#~| msgid "member" +#~ msgctxt "alt. month" +#~ msgid "December" +#~ msgstr "miembro" + +#, fuzzy +#~| msgid "This activity is not validated yet." +#~ msgid "This is not a valid IPv6 address." +#~ msgstr "Esta actividad no fue validada por ahora." + +#, fuzzy, python-format +#~| msgid "year" +#~ msgid "%(num)d year" +#~ msgid_plural "%(num)d years" +#~ msgstr[0] "año" +#~ msgstr[1] "año" + +#, fuzzy +#~| msgid "No reason specified" +#~ msgid "No year specified" +#~ msgstr "Ningún motivo dado" + +#, fuzzy +#~| msgid "No reason specified" +#~ msgid "No month specified" +#~ msgstr "Ningún motivo dado" + +#, fuzzy +#~| msgid "No reason specified" +#~ msgid "No day specified" +#~ msgstr "Ningún motivo dado" + +#, fuzzy +#~| msgid "No reason specified" +#~ msgid "No week specified" +#~ msgstr "Ningún motivo dado" + +#, fuzzy +#~| msgid "Client secret" +#~ msgid "Confidential" +#~ msgstr "Secreto cliente" + +#, fuzzy +#~| msgid "Authorization:" +#~ msgid "Authorization code" +#~ msgstr "Autorizaciones :" + +#, fuzzy +#~| msgid "Reset my password" +#~ msgid "Resource owner password-based" +#~ msgstr "Reiniciar mi contraseña" + +#, fuzzy +#~| msgid "Client secret" +#~ msgid "Client credentials" +#~ msgstr "Secreto cliente" + +#, fuzzy +#~| msgid "This address must be valid." +#~ msgid "The access token is invalid." +#~ msgstr "Este correo tiene que ser valido." + +#, fuzzy +#~| msgid "This address must be valid." +#~ msgid "The access token has expired." +#~ msgstr "Este correo tiene que ser valido." + +#, fuzzy +#~| msgid "The user does not have enough money." +#~ msgid "The access token is valid but does not have enough scope." +#~ msgstr "El usuario no tiene suficientemente dinero." + +#, fuzzy +#~| msgid "Application requires following permissions:" +#~ msgid "Application requires the following permissions" +#~ msgstr "La aplicación necesita los derechos siguientes :" + #~ msgid "The BDE membership is included in the WEI registration." #~ msgstr "La afiliación al BDE esta incluida en la afiliación WEI." diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 3e881043..70aa20ce 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: 2025-06-02 00:57+0200\n" +"POT-Creation-Date: 2025-06-20 13:50+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n" "Last-Translator: bleizi \n" "Language-Team: French \n" @@ -291,14 +291,14 @@ msgstr "Type" #: 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:116 +#: apps/wei/forms/registration.py:129 msgid "Last name" msgstr "Nom de famille" #: 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:121 +#: apps/wei/forms/registration.py:134 msgid "First name" msgstr "Prénom" @@ -317,10 +317,6 @@ msgstr "Solde du compte" #: 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 -#: env/lib/python3.11/site-packages/django/forms/formsets.py:499 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:13 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:38 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:7 #: 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 @@ -786,8 +782,6 @@ msgid "weeks" msgstr "semaines" #: apps/food/utils.py:53 -#: env/lib/python3.11/site-packages/django/db/models/base.py:1423 -#: env/lib/python3.11/site-packages/django/forms/models.py:893 msgid "and" msgstr "et" @@ -830,12 +824,10 @@ msgid "Details of:" msgstr "Détails de :" #: apps/food/views.py:447 apps/treasury/tables.py:149 -#: env/lib/python3.11/site-packages/django/forms/widgets.py:795 msgid "Yes" msgstr "Oui" #: apps/food/views.py:449 apps/member/models.py:99 apps/treasury/tables.py:149 -#: env/lib/python3.11/site-packages/django/forms/widgets.py:796 msgid "No" msgstr "Non" @@ -971,12 +963,12 @@ 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:185 apps/registration/forms.py:78 -#: apps/wei/forms/registration.py:103 +#: apps/wei/forms/registration.py:116 msgid "Credit type" msgstr "Type de rechargement" #: apps/member/forms.py:186 apps/registration/forms.py:79 -#: apps/wei/forms/registration.py:104 +#: apps/wei/forms/registration.py:117 msgid "No credit" msgstr "Pas de rechargement" @@ -985,13 +977,13 @@ 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:192 apps/registration/forms.py:84 -#: apps/wei/forms/registration.py:109 +#: apps/wei/forms/registration.py:122 msgid "Credit amount" msgstr "Montant à créditer" #: 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:126 +#: apps/wei/forms/registration.py:139 msgid "Bank" msgstr "Banque" @@ -1972,7 +1964,6 @@ msgstr "" #: apps/note/models/transactions.py:357 apps/note/models/transactions.py:360 #: apps/note/models/transactions.py:363 apps/wei/views.py:1097 #: apps/wei/views.py:1101 -#: env/lib/python3.11/site-packages/django/forms/fields.py:91 msgid "This field is required." msgstr "Ce champ est requis." @@ -2010,7 +2001,6 @@ msgstr "Ajouter" #: apps/wei/templates/wei/bus_detail.html:24 #: apps/wei/templates/wei/busteam_detail.html:20 #: apps/wei/templates/wei/busteam_detail.html:42 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:37 #: note_kfet/templates/oauth2_provider/application_detail.html:38 msgid "Edit" msgstr "Éditer" @@ -2353,19 +2343,16 @@ msgid "Available scopes" msgstr "Scopes disponibles" #: apps/permission/templates/permission/scopes.html:42 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:24 msgid "No applications defined" msgstr "Pas d'application définie" #: apps/permission/templates/permission/scopes.html:43 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:25 msgid "Click here" msgstr "Cliquez ici" #: apps/permission/templates/permission/scopes.html:43 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:25 msgid "if you want to register a new one" msgstr "si vous voulez en enregistrer une nouvelle" @@ -3019,12 +3006,12 @@ msgstr "" "L'utilisateur·rice sélectionné·e n'est pas validé·e. Merci de d'abord " "valider son compte" -#: apps/wei/forms/registration.py:71 apps/wei/models.py:140 +#: apps/wei/forms/registration.py:84 apps/wei/models.py:140 #: apps/wei/models.py:348 msgid "bus" msgstr "bus" -#: apps/wei/forms/registration.py:72 +#: apps/wei/forms/registration.py:85 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." @@ -3033,11 +3020,11 @@ msgstr "" "vous attribuer un bus et une équipe, en particulier si vous êtes un·e " "électron libre." -#: apps/wei/forms/registration.py:79 +#: apps/wei/forms/registration.py:92 msgid "Team" msgstr "Équipe" -#: apps/wei/forms/registration.py:81 +#: apps/wei/forms/registration.py:94 msgid "" "Leave this field empty if you won't be in a team (staff, bus chief, free " "electron)" @@ -3045,16 +3032,16 @@ 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:87 apps/wei/forms/registration.py:97 +#: apps/wei/forms/registration.py:100 apps/wei/forms/registration.py:110 #: apps/wei/models.py:174 msgid "WEI Roles" msgstr "Rôles au WEI" -#: apps/wei/forms/registration.py:88 +#: apps/wei/forms/registration.py:101 msgid "Select the roles that you are interested in." msgstr "Sélectionnez les rôles qui vous intéressent." -#: apps/wei/forms/registration.py:134 +#: apps/wei/forms/registration.py:147 msgid "This team doesn't belong to the given bus." msgstr "Cette équipe n'appartient pas à ce bus." @@ -3137,10 +3124,8 @@ msgid "Check" msgstr "" #: apps/wei/models.py:209 -#, fuzzy -#| msgid " transactions" msgid "Note transaction" -msgstr " transactions" +msgstr "Transaction Note" #: apps/wei/models.py:212 msgid "caution type" @@ -3332,8 +3317,10 @@ msgid "WEI fee (unpaid students)" msgstr "Prix du WEI (étudiant⋅es)" #: apps/wei/templates/wei/base.html:53 -msgid "Deposit amount" -msgstr "Caution" +#, fuzzy +#| msgid "total amount" +msgid "Caution amount" +msgstr "montant total" #: apps/wei/templates/wei/base.html:74 msgid "WEI list" @@ -3505,7 +3492,7 @@ msgstr "" #: apps/wei/templates/wei/weimembership_form.html:147 msgid "Required payments:" -msgstr "" +msgstr "Paiements requis" #: apps/wei/templates/wei/weimembership_form.html:149 #, fuzzy, python-format @@ -3516,23 +3503,23 @@ msgstr "cotisation pour adhérer (normalien·ne élève)" #: apps/wei/templates/wei/weimembership_form.html:153 #, python-format msgid "Deposit (by Note transaction): %(amount)s" -msgstr "" +msgstr "Caution (par transaction) : %(amount)s" #: apps/wei/templates/wei/weimembership_form.html:156 #: apps/wei/templates/wei/weimembership_form.html:163 #, python-format msgid "Total needed: %(total)s" -msgstr "" +msgstr "Total nécessaire : %(total)s" #: apps/wei/templates/wei/weimembership_form.html:160 #, python-format msgid "Deposit (by check): %(amount)s" -msgstr "" +msgstr "Caution (par chèque) : %(amount)s" #: apps/wei/templates/wei/weimembership_form.html:168 #, python-format msgid "Current balance: %(balance)s" -msgstr "" +msgstr "Solde actuel : %(balance)s" #: apps/wei/templates/wei/weimembership_form.html:176 msgid "The user didn't give her/his caution check." @@ -3570,8 +3557,6 @@ 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 -#, fuzzy -#| msgid "View validated membershipis..." msgid "View validated memberships..." msgstr "Voir les adhésions validées..." @@ -3664,22 +3649,15 @@ msgid "No membership found for this registration" msgstr "Pas d'adhésion trouvée pour cette inscription" #: apps/wei/views.py:819 -#| "You don't have the permission to add an instance of model {app_label}." -#| "{model_name}." msgid "You don't have the permission to update memberships" msgstr "" "Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}." "{model_name}." #: apps/wei/views.py:825 -#, fuzzy, python-format -#| msgid "" -#| "You don't have the permission to delete this instance of model " -#| "{app_label}.{model_name}." +#, python-format msgid "You don't have the permission to update the field %(field)s" -msgstr "" -"Vous n'avez pas la permission de supprimer cette instance du modèle " -"{app_label}.{model_name}." +msgstr "Vous n'avez pas la permission de modifier le champ %(field)s" #: apps/wei/views.py:870 msgid "Delete WEI registration" @@ -3696,7 +3674,8 @@ msgstr "Valider l'inscription WEI" #: apps/wei/views.py:985 msgid "Please make sure the check is given before validating the registration" msgstr "" -"Merci de vous assurer que le chèque a bien été donné avant de valider l'adhésion" +"Merci de vous assurer que le chèque a bien été donné avant de valider " +"l'adhésion" #: apps/wei/views.py:991 msgid "Create deposit transaction" @@ -3704,23 +3683,26 @@ msgstr "Créer une transaction de caution" #: apps/wei/views.py:992 #, python-format -msgid "A transaction of %(amount).2f€ will be created from the user's Note account" -msgstr "Un transaction de %(amount).2f€ va être créée depuis la note de l'utilisateur" +msgid "" +"A transaction of %(amount).2f€ will be created from the user's Note account" +msgstr "" +"Un transaction de %(amount).2f€ va être créée depuis la note de l'utilisateur" #: apps/wei/views.py:1087 #, python-format -msgid "This user doesn't have enough money to join this club and pay the deposit. " +msgid "" +"This user doesn't have enough money to join this club and pay the deposit. " "Current balance: %(balance)d€, credit: %(credit)d€, needed: %(needed)d€" msgstr "" "Cet⋅te utilisateur⋅rice n'a pas assez d'argent pour rejoindre ce club et " -"payer la caution" -"Solde actuel : %(balance)d€, crédit : %(credit)d€, requis : %(needed)d€" +"payer la cautionSolde actuel : %(balance)d€, crédit : %(credit)d€, requis : " +"%(needed)d€" #: apps/wei/views.py:1140 #, fuzzy, python-format -#| msgid "Creation date" +#| msgid "total amount" msgid "Caution %(name)s" -msgstr "Date de création" +msgstr "montant total" #: apps/wei/views.py:1354 msgid "Attribute buses to first year members" @@ -3915,1514 +3897,6 @@ msgstr "Le wrapped est public" msgid "List of wrapped" msgstr "Liste des wrapped" -#: env/lib/python3.11/site-packages/django/contrib/messages/apps.py:15 -msgid "Messages" -msgstr "" - -#: env/lib/python3.11/site-packages/django/contrib/sitemaps/apps.py:8 -msgid "Site Maps" -msgstr "" - -#: env/lib/python3.11/site-packages/django/contrib/staticfiles/apps.py:9 -msgid "Static Files" -msgstr "" - -#: env/lib/python3.11/site-packages/django/contrib/syndication/apps.py:7 -#, fuzzy -#| msgid "Invitation" -msgid "Syndication" -msgstr "Invitation" - -#. Translators: String used to replace omitted page numbers in elided page -#. range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10]. -#: env/lib/python3.11/site-packages/django/core/paginator.py:30 -msgid "…" -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/paginator.py:50 -msgid "That page number is not an integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/paginator.py:52 -msgid "That page number is less than 1" -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/paginator.py:54 -#, fuzzy -#| msgid "There is no results." -msgid "That page contains no results" -msgstr "Il n'y a pas de résultat." - -#: env/lib/python3.11/site-packages/django/core/validators.py:22 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid value." -msgstr "dévalider" - -#: env/lib/python3.11/site-packages/django/core/validators.py:104 -#: env/lib/python3.11/site-packages/django/forms/fields.py:752 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid URL." -msgstr "dévalider" - -#: env/lib/python3.11/site-packages/django/core/validators.py:165 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid integer." -msgstr "dévalider" - -#: env/lib/python3.11/site-packages/django/core/validators.py:176 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid email address." -msgstr "dévalider" - -#. Translators: "letters" means latin letters: a-z and A-Z. -#: env/lib/python3.11/site-packages/django/core/validators.py:259 -msgid "" -"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:267 -msgid "" -"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or " -"hyphens." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:281 -#: env/lib/python3.11/site-packages/django/core/validators.py:289 -#: env/lib/python3.11/site-packages/django/core/validators.py:318 -#, fuzzy -#| msgid "This activity is not validated yet." -msgid "Enter a valid IPv4 address." -msgstr "Cette activité n'est pas encore validée." - -#: env/lib/python3.11/site-packages/django/core/validators.py:298 -#: env/lib/python3.11/site-packages/django/core/validators.py:319 -#, fuzzy -#| msgid "This activity is not validated yet." -msgid "Enter a valid IPv6 address." -msgstr "Cette activité n'est pas encore validée." - -#: env/lib/python3.11/site-packages/django/core/validators.py:310 -#: env/lib/python3.11/site-packages/django/core/validators.py:317 -#, fuzzy -#| msgid "This activity is not validated yet." -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Cette activité n'est pas encore validée." - -#: env/lib/python3.11/site-packages/django/core/validators.py:353 -msgid "Enter only digits separated by commas." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:359 -#, python-format -msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:394 -#, python-format -msgid "Ensure this value is less than or equal to %(limit_value)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:403 -#, python-format -msgid "Ensure this value is greater than or equal to %(limit_value)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:412 -#, python-format -msgid "Ensure this value is a multiple of step size %(limit_value)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:422 -#, python-format -msgid "" -"Ensure this value has at least %(limit_value)d character (it has " -"%(show_value)d)." -msgid_plural "" -"Ensure this value has at least %(limit_value)d characters (it has " -"%(show_value)d)." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:440 -#, python-format -msgid "" -"Ensure this value has at most %(limit_value)d character (it has " -"%(show_value)d)." -msgid_plural "" -"Ensure this value has at most %(limit_value)d characters (it has " -"%(show_value)d)." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:463 -#: env/lib/python3.11/site-packages/django/forms/fields.py:347 -#: env/lib/python3.11/site-packages/django/forms/fields.py:386 -#, fuzzy -#| msgid "phone number" -msgid "Enter a number." -msgstr "numéro de téléphone" - -#: env/lib/python3.11/site-packages/django/core/validators.py:465 -#, python-format -msgid "Ensure that there are no more than %(max)s digit in total." -msgid_plural "Ensure that there are no more than %(max)s digits in total." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:470 -#, python-format -msgid "Ensure that there are no more than %(max)s decimal place." -msgid_plural "Ensure that there are no more than %(max)s decimal places." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:475 -#, python-format -msgid "" -"Ensure that there are no more than %(max)s digit before the decimal point." -msgid_plural "" -"Ensure that there are no more than %(max)s digits before the decimal point." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:546 -#, python-format -msgid "" -"File extension “%(extension)s” is not allowed. Allowed extensions are: " -"%(allowed_extensions)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:607 -msgid "Null characters are not allowed." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/base.py:1425 -#, fuzzy, python-format -#| msgid "A template with this name already exist" -msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "Un modèle de transaction avec un nom similaire existe déjà" - -#: env/lib/python3.11/site-packages/django/db/models/constraints.py:17 -#, python-format -msgid "Constraint “%(name)s” is violated." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:128 -#, fuzzy, python-format -#| msgid "This activity is not validated yet." -msgid "Value %(value)r is not a valid choice." -msgstr "Cette activité n'est pas encore validée." - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:129 -#, fuzzy -#| msgid "This image cannot be loaded." -msgid "This field cannot be null." -msgstr "Cette image ne peut pas être chargée." - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:130 -#, fuzzy -#| msgid "This image cannot be loaded." -msgid "This field cannot be blank." -msgstr "Cette image ne peut pas être chargée." - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:131 -#, fuzzy, python-format -#| msgid "A template with this name already exist" -msgid "%(model_name)s with this %(field_label)s already exists." -msgstr "Un modèle de transaction avec un nom similaire existe déjà" - -#. Translators: The 'lookup_type' is one of 'date', 'year' or -#. 'month'. Eg: "Title must be unique for pub_date year" -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:135 -#, python-format -msgid "" -"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:173 -#, python-format -msgid "Field of type: %(field_type)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1094 -#, python-format -msgid "“%(value)s” value must be either True or False." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1095 -#, python-format -msgid "“%(value)s” value must be either True, False, or None." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1097 -msgid "Boolean (Either True or False)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1147 -#, python-format -msgid "String (up to %(max_length)s)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1149 -msgid "String (unlimited)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1253 -msgid "Comma-separated integers" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1354 -#, python-format -msgid "" -"“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD " -"format." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1358 -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1493 -#, python-format -msgid "" -"“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " -"date." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1362 -msgid "Date (without time)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1489 -#, python-format -msgid "" -"“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." -"uuuuuu]][TZ] format." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1497 -#, python-format -msgid "" -"“%(value)s” value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" -"[TZ]) but it is an invalid date/time." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1502 -msgid "Date (with time)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1626 -#, python-format -msgid "“%(value)s” value must be a decimal number." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1628 -#, fuzzy -#| msgid "phone number" -msgid "Decimal number" -msgstr "numéro de téléphone" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1789 -#, python-format -msgid "" -"“%(value)s” value has an invalid format. It must be in [DD] [[HH:]MM:]ss[." -"uuuuuu] format." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1793 -#, fuzzy -#| msgid "action" -msgid "Duration" -msgstr "action" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1845 -#, fuzzy -#| msgid "address" -msgid "Email address" -msgstr "adresse" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1870 -msgid "File path" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1948 -#, python-format -msgid "“%(value)s” value must be a float." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1950 -#, fuzzy -#| msgid "phone number" -msgid "Floating point number" -msgstr "numéro de téléphone" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1990 -#, python-format -msgid "“%(value)s” value must be an integer." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1992 -msgid "Integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2088 -msgid "Big (8 byte) integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2105 -msgid "Small integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2113 -#, fuzzy -#| msgid "IP Address" -msgid "IPv4 address" -msgstr "Adresse IP" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2144 -#, fuzzy -#| msgid "IP Address" -msgid "IP address" -msgstr "Adresse IP" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2237 -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2238 -#, python-format -msgid "“%(value)s” value must be either None, True or False." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2240 -msgid "Boolean (Either True, False or None)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2291 -msgid "Positive big integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2306 -msgid "Positive integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2321 -msgid "Positive small integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2337 -#, python-format -msgid "Slug (up to %(max_length)s)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2373 -msgid "Text" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2448 -#, python-format -msgid "" -"“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " -"format." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2452 -#, python-format -msgid "" -"“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " -"invalid time." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2456 -msgid "Time" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2564 -msgid "URL" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2588 -msgid "Raw binary data" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2653 -#, fuzzy, python-format -#| msgid "This activity is not validated yet." -msgid "“%(value)s” is not a valid UUID." -msgstr "Cette activité n'est pas encore validée." - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2655 -#, fuzzy -#| msgid "Invoice identifier" -msgid "Universally unique identifier" -msgstr "Numéro de facture" - -#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:232 -msgid "File" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:393 -msgid "Image" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:26 -#, fuzzy -#| msgid "Object" -msgid "A JSON object" -msgstr "Objet" - -#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:28 -#, fuzzy -#| msgid "This address must be valid." -msgid "Value must be valid JSON." -msgstr "Cette adresse doit être valide." - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:919 -#, fuzzy, python-format -#| msgid "A template with this name already exist" -msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "Un modèle de transaction avec un nom similaire existe déjà" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:921 -msgid "Foreign Key (type determined by related field)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1212 -msgid "One-to-one relationship" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1269 -#, python-format -msgid "%(from)s-%(to)s relationship" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1271 -#, python-format -msgid "%(from)s-%(to)s relationships" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1319 -msgid "Many-to-many relationship" -msgstr "" - -#. Translators: If found as last label character, these punctuation -#. characters will prevent the default label_suffix to be appended to the label -#: env/lib/python3.11/site-packages/django/forms/boundfield.py:184 -msgid ":?.!" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:298 -#, fuzzy -#| msgid "phone number" -msgid "Enter a whole number." -msgstr "numéro de téléphone" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:467 -#: env/lib/python3.11/site-packages/django/forms/fields.py:1241 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid date." -msgstr "dévalider" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:490 -#: env/lib/python3.11/site-packages/django/forms/fields.py:1242 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid time." -msgstr "dévalider" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:517 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid date/time." -msgstr "dévalider" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:551 -#, fuzzy -#| msgid "Email validation" -msgid "Enter a valid duration." -msgstr "Validation de l'adresse mail" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:552 -#, python-brace-format -msgid "The number of days must be between {min_days} and {max_days}." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:621 -msgid "No file was submitted. Check the encoding type on the form." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:622 -msgid "No file was submitted." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:623 -msgid "The submitted file is empty." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:625 -#, python-format -msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." -msgid_plural "" -"Ensure this filename has at most %(max)d characters (it has %(length)d)." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:630 -msgid "Please either submit a file or check the clear checkbox, not both." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:694 -msgid "" -"Upload a valid image. The file you uploaded was either not an image or a " -"corrupted image." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:857 -#: env/lib/python3.11/site-packages/django/forms/fields.py:949 -#: env/lib/python3.11/site-packages/django/forms/models.py:1566 -#, python-format -msgid "Select a valid choice. %(value)s is not one of the available choices." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:951 -#: env/lib/python3.11/site-packages/django/forms/fields.py:1070 -#: env/lib/python3.11/site-packages/django/forms/models.py:1564 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a list of values." -msgstr "dévalider" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:1071 -#, fuzzy -#| msgid "phone number" -msgid "Enter a complete value." -msgstr "numéro de téléphone" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:1313 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid UUID." -msgstr "dévalider" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:1343 -#, fuzzy -#| msgid "invalidate" -msgid "Enter a valid JSON." -msgstr "dévalider" - -#. Translators: This is the default suffix added to form field labels -#: env/lib/python3.11/site-packages/django/forms/forms.py:98 -msgid ":" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/forms.py:244 -#: env/lib/python3.11/site-packages/django/forms/forms.py:328 -#, python-format -msgid "(Hidden field %(name)s) %(error)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/formsets.py:63 -#, python-format -msgid "" -"ManagementForm data is missing or has been tampered with. Missing fields: " -"%(field_names)s. You may need to file a bug report if the issue persists." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/formsets.py:67 -#, python-format -msgid "Please submit at most %(num)d form." -msgid_plural "Please submit at most %(num)d forms." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/forms/formsets.py:72 -#, python-format -msgid "Please submit at least %(num)d form." -msgid_plural "Please submit at least %(num)d forms." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/forms/formsets.py:484 -#: env/lib/python3.11/site-packages/django/forms/formsets.py:491 -#, fuzzy -#| msgid "order" -msgid "Order" -msgstr "consigne" - -#: env/lib/python3.11/site-packages/django/forms/models.py:886 -#, python-format -msgid "Please correct the duplicate data for %(field)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:891 -#, python-format -msgid "Please correct the duplicate data for %(field)s, which must be unique." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:898 -#, python-format -msgid "" -"Please correct the duplicate data for %(field_name)s which must be unique " -"for the %(lookup)s in %(date_field)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:907 -msgid "Please correct the duplicate values below." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:1338 -msgid "The inline value did not match the parent instance." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:1429 -msgid "Select a valid choice. That choice is not one of the available choices." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:1568 -#, fuzzy, python-format -#| msgid "This activity is not validated yet." -msgid "“%(pk)s” is not a valid value." -msgstr "Cette activité n'est pas encore validée." - -#: env/lib/python3.11/site-packages/django/forms/utils.py:226 -#, python-format -msgid "" -"%(datetime)s couldn’t be interpreted in time zone %(current_timezone)s; it " -"may be ambiguous or it may not exist." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/widgets.py:463 -msgid "Clear" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/widgets.py:464 -#, fuzzy -#| msgid "Current activity" -msgid "Currently" -msgstr "Activité en cours" - -#: env/lib/python3.11/site-packages/django/forms/widgets.py:465 -#, fuzzy -#| msgid "change" -msgid "Change" -msgstr "modifier" - -#: env/lib/python3.11/site-packages/django/forms/widgets.py:794 -msgid "Unknown" -msgstr "" - -#. Translators: Please do not add spaces around commas. -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:874 -msgid "yes,no,maybe" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:904 -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:921 -#, python-format -msgid "%(size)d byte" -msgid_plural "%(size)d bytes" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:923 -#, python-format -msgid "%s KB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:925 -#, python-format -msgid "%s MB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:927 -#, python-format -msgid "%s GB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:929 -#, python-format -msgid "%s TB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:931 -#, python-format -msgid "%s PB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:73 -msgid "p.m." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:74 -msgid "a.m." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:79 -msgid "PM" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:80 -msgid "AM" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:152 -msgid "midnight" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:154 -msgid "noon" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:7 -msgid "Monday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:8 -msgid "Tuesday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:9 -msgid "Wednesday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:10 -msgid "Thursday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:11 -msgid "Friday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:12 -msgid "Saturday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:13 -msgid "Sunday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:16 -msgid "Mon" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:17 -msgid "Tue" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:18 -#, fuzzy -#| msgid "Wrapped" -msgid "Wed" -msgstr "Wrapped" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:19 -msgid "Thu" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:20 -msgid "Fri" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:21 -msgid "Sat" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:22 -msgid "Sun" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:25 -msgid "January" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:26 -msgid "February" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:27 -#, fuzzy -#| msgid "Search" -msgid "March" -msgstr "Recherche" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:28 -msgid "April" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:29 -#, fuzzy -#| msgid "day" -msgid "May" -msgstr "jour" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:30 -msgid "June" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:31 -msgid "July" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:32 -msgid "August" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:33 -#, fuzzy -#| msgid "member" -msgid "September" -msgstr "adhérent·e" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:34 -msgid "October" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:35 -#, fuzzy -#| msgid "member" -msgid "November" -msgstr "adhérent·e" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:36 -#, fuzzy -#| msgid "member" -msgid "December" -msgstr "adhérent·e" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:39 -#, fuzzy -#| msgid "add" -msgid "jan" -msgstr "ajouter" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:40 -#, fuzzy -#| msgid "fee" -msgid "feb" -msgstr "cotisation" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:41 -msgid "mar" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:42 -msgid "apr" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:43 -#, fuzzy -#| msgid "day" -msgid "may" -msgstr "jour" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:44 -#, fuzzy -#| msgid "add" -msgid "jun" -msgstr "ajouter" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:45 -msgid "jul" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:46 -msgid "aug" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:47 -msgid "sep" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:48 -#, fuzzy -#| msgid "product" -msgid "oct" -msgstr "produit" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:49 -msgid "nov" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:50 -#, fuzzy -#| msgid "bde" -msgid "dec" -msgstr "bde" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:53 -msgctxt "abbrev. month" -msgid "Jan." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:54 -msgctxt "abbrev. month" -msgid "Feb." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:55 -#, fuzzy -#| msgid "Search" -msgctxt "abbrev. month" -msgid "March" -msgstr "Recherche" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:56 -msgctxt "abbrev. month" -msgid "April" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:57 -#, fuzzy -#| msgid "day" -msgctxt "abbrev. month" -msgid "May" -msgstr "jour" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:58 -msgctxt "abbrev. month" -msgid "June" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:59 -msgctxt "abbrev. month" -msgid "July" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:60 -msgctxt "abbrev. month" -msgid "Aug." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:61 -msgctxt "abbrev. month" -msgid "Sept." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:62 -msgctxt "abbrev. month" -msgid "Oct." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:63 -msgctxt "abbrev. month" -msgid "Nov." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:64 -msgctxt "abbrev. month" -msgid "Dec." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:67 -msgctxt "alt. month" -msgid "January" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:68 -msgctxt "alt. month" -msgid "February" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:69 -#, fuzzy -#| msgid "Search" -msgctxt "alt. month" -msgid "March" -msgstr "Recherche" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:70 -msgctxt "alt. month" -msgid "April" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:71 -#, fuzzy -#| msgid "day" -msgctxt "alt. month" -msgid "May" -msgstr "jour" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:72 -msgctxt "alt. month" -msgid "June" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:73 -msgctxt "alt. month" -msgid "July" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:74 -msgctxt "alt. month" -msgid "August" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:75 -#, fuzzy -#| msgid "member" -msgctxt "alt. month" -msgid "September" -msgstr "adhérent·e" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:76 -msgctxt "alt. month" -msgid "October" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:77 -#, fuzzy -#| msgid "member" -msgctxt "alt. month" -msgid "November" -msgstr "adhérent·e" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:78 -#, fuzzy -#| msgid "member" -msgctxt "alt. month" -msgid "December" -msgstr "adhérent·e" - -#: env/lib/python3.11/site-packages/django/utils/ipv6.py:20 -#, fuzzy -#| msgid "This activity is not validated yet." -msgid "This is not a valid IPv6 address." -msgstr "Cette activité n'est pas encore validée." - -#: env/lib/python3.11/site-packages/django/utils/text.py:138 -#, python-format -msgctxt "String to return when truncating text" -msgid "%(truncated_text)s…" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/text.py:323 -#, fuzzy -#| msgid "hour" -msgid "or" -msgstr "heure" - -#. Translators: This string is used as a separator between list elements -#: env/lib/python3.11/site-packages/django/utils/text.py:342 -#: env/lib/python3.11/site-packages/django/utils/timesince.py:135 -msgid ", " -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:8 -#, fuzzy, python-format -#| msgid "year" -msgid "%(num)d year" -msgid_plural "%(num)d years" -msgstr[0] "année" -msgstr[1] "année" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:9 -#, python-format -msgid "%(num)d month" -msgid_plural "%(num)d months" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:10 -#, python-format -msgid "%(num)d week" -msgid_plural "%(num)d weeks" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:11 -#, python-format -msgid "%(num)d day" -msgid_plural "%(num)d days" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:12 -#, python-format -msgid "%(num)d hour" -msgid_plural "%(num)d hours" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:13 -#, fuzzy, python-format -#| msgid "minute" -msgid "%(num)d minute" -msgid_plural "%(num)d minutes" -msgstr[0] "minute" -msgstr[1] "minute" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:111 -msgid "Forbidden" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:112 -msgid "CSRF verification failed. Request aborted." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:116 -msgid "" -"You are seeing this message because this HTTPS site requires a “Referer " -"header” to be sent by your web browser, but none was sent. This header is " -"required for security reasons, to ensure that your browser is not being " -"hijacked by third parties." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:122 -msgid "" -"If you have configured your browser to disable “Referer” headers, please re-" -"enable them, at least for this site, or for HTTPS connections, or for “same-" -"origin” requests." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:127 -msgid "" -"If you are using the tag or " -"including the “Referrer-Policy: no-referrer” header, please remove them. The " -"CSRF protection requires the “Referer” header to do strict referer checking. " -"If you’re concerned about privacy, use alternatives like for links to third-party sites." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:136 -msgid "" -"You are seeing this message because this site requires a CSRF cookie when " -"submitting forms. This cookie is required for security reasons, to ensure " -"that your browser is not being hijacked by third parties." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:142 -msgid "" -"If you have configured your browser to disable cookies, please re-enable " -"them, at least for this site, or for “same-origin” requests." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:148 -msgid "More information is available with DEBUG=True." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:44 -#, fuzzy -#| msgid "No reason specified" -msgid "No year specified" -msgstr "Pas de motif spécifié" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:64 -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:115 -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:214 -msgid "Date out of range" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:94 -#, fuzzy -#| msgid "No reason specified" -msgid "No month specified" -msgstr "Pas de motif spécifié" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:147 -#, fuzzy -#| msgid "No reason specified" -msgid "No day specified" -msgstr "Pas de motif spécifié" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:194 -#, fuzzy -#| msgid "No reason specified" -msgid "No week specified" -msgstr "Pas de motif spécifié" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:349 -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:380 -#, python-format -msgid "No %(verbose_name_plural)s available" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:652 -#, python-format -msgid "" -"Future %(verbose_name_plural)s not available because %(class_name)s." -"allow_future is False." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:692 -#, python-format -msgid "Invalid date string “%(datestr)s” given format “%(format)s”" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/detail.py:56 -#, python-format -msgid "No %(verbose_name)s found matching the query" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/list.py:70 -msgid "Page is not “last”, nor can it be converted to an int." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/list.py:77 -#, python-format -msgid "Invalid page (%(page_number)s): %(message)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/list.py:169 -#, python-format -msgid "Empty list and “%(class_name)s.allow_empty” is False." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/static.py:38 -msgid "Directory indexes are not allowed here." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/static.py:40 -#, python-format -msgid "“%(path)s” does not exist" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/static.py:79 -#, python-format -msgid "Index of %(directory)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:7 -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:220 -msgid "The install worked successfully! Congratulations!" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:206 -#, python-format -msgid "" -"View release notes for Django %(version)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:221 -#, python-format -msgid "" -"You are seeing this page because DEBUG=True is in your settings file and you have not " -"configured any URLs." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:229 -msgid "Django Documentation" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:230 -msgid "Topics, references, & how-to’s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:238 -msgid "Tutorial: A Polling App" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:239 -msgid "Get started with Django" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:247 -msgid "Django Community" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:248 -msgid "Connect, get help, or contribute" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:69 -#, fuzzy -#| msgid "Client secret" -msgid "Confidential" -msgstr "Secret client" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:70 -#, fuzzy -#| msgid "public" -msgid "Public" -msgstr "public" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:79 -#, fuzzy -#| msgid "Authorization:" -msgid "Authorization code" -msgstr "Autorisation :" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:80 -msgid "Implicit" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:81 -#, fuzzy -#| msgid "Reset my password" -msgid "Resource owner password-based" -msgstr "Réinitialiser mon mot de passe" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:82 -#, fuzzy -#| msgid "Client secret" -msgid "Client credentials" -msgstr "Secret client" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:83 -msgid "OpenID connect hybrid" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:90 -msgid "No OIDC support" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:91 -msgid "RSA with SHA-2 256" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:92 -msgid "HMAC with SHA-2 256" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:107 -msgid "Allowed URIs list, space separated" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:111 -msgid "Allowed Post Logout URIs list, space separated" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:120 -msgid "Hashed on Save. Copy it now if this is a new secret." -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:190 -#, python-brace-format -msgid "Unauthorized redirect scheme: {scheme}" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:194 -#, python-brace-format -msgid "redirect_uris cannot be empty with grant_type {grant_type}" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:200 -msgid "You must set OIDC_RSA_PRIVATE_KEY to use RSA algorithm" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:209 -msgid "You cannot use HS256 with public grants or clients" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:211 -#, fuzzy -#| msgid "This address must be valid." -msgid "The access token is invalid." -msgstr "Cette adresse doit être valide." - -#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:218 -#, fuzzy -#| msgid "This address must be valid." -msgid "The access token has expired." -msgstr "Cette adresse doit être valide." - -#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:225 -#, fuzzy -#| msgid "The user does not have enough money." -msgid "The access token is valid but does not have enough scope." -msgstr "L'utilisateur·ice n'a pas assez d'argent." - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:6 -#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8 -msgid "Are you sure to delete the application" -msgstr "Êtes-vous sûr⋅e de vouloir supprimer l'application" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:12 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:29 -#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 -#: note_kfet/templates/oauth2_provider/authorize.html:28 -msgid "Cancel" -msgstr "Annuler" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:10 -#: note_kfet/templates/oauth2_provider/application_detail.html:11 -msgid "Client id" -msgstr "ID client" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:15 -#: note_kfet/templates/oauth2_provider/application_detail.html:14 -msgid "Client secret" -msgstr "Secret client" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:20 -#: note_kfet/templates/oauth2_provider/application_detail.html:17 -msgid "Client type" -msgstr "Type de client" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:25 -#: note_kfet/templates/oauth2_provider/application_detail.html:20 -msgid "Authorization Grant Type" -msgstr "Type d'autorisation" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:30 -#: note_kfet/templates/oauth2_provider/application_detail.html:23 -msgid "Redirect Uris" -msgstr "URIs de redirection" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:36 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:35 -#: note_kfet/templates/oauth2_provider/application_detail.html:37 -#: note_kfet/templates/oauth2_provider/application_form.html:23 -msgid "Go Back" -msgstr "Retour en arrière" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:9 -#: note_kfet/templates/oauth2_provider/application_form.html:12 -msgid "Edit application" -msgstr "Modifier l'application" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:37 -msgid "Save" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:6 -#: note_kfet/templates/oauth2_provider/application_list.html:7 -msgid "Your applications" -msgstr "Vos applications" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:14 -#: note_kfet/templates/oauth2_provider/application_list.html:30 -msgid "New Application" -msgstr "Nouvelle application" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_registration_form.html:5 -#: note_kfet/templates/oauth2_provider/application_registration_form.html:5 -msgid "Register a new application" -msgstr "Enregistrer une nouvelle application" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:8 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:30 -#: note_kfet/templates/oauth2_provider/authorize.html:9 -#: note_kfet/templates/oauth2_provider/authorize.html:29 -msgid "Authorize" -msgstr "Autoriser" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:17 -#, fuzzy -#| msgid "Application requires following permissions:" -msgid "Application requires the following permissions" -msgstr "L'application requiert les permissions suivantes :" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:6 -#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9 -msgid "Are you sure you want to delete this token?" -msgstr "Êtes-vous sûr⋅e de vouloir supprimer ce jeton ?" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:6 -#: note_kfet/templates/oauth2_provider/authorized-tokens.html:7 -msgid "Tokens" -msgstr "Jetons" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:11 -msgid "revoke" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:19 -#: note_kfet/templates/oauth2_provider/authorized-tokens.html:22 -msgid "There are no authorized tokens yet." -msgstr "Il n'y a pas encore de jeton autorisé." - #: note_kfet/settings/base.py:177 msgid "German" msgstr "Allemand" @@ -5585,6 +4059,35 @@ msgstr "Chercher par un attribut tel que le nom..." msgid "There is no results." msgstr "Il n'y a pas de résultat." +#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8 +msgid "Are you sure to delete the application" +msgstr "Êtes-vous sûr⋅e de vouloir supprimer l'application" + +#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 +#: note_kfet/templates/oauth2_provider/authorize.html:28 +msgid "Cancel" +msgstr "Annuler" + +#: note_kfet/templates/oauth2_provider/application_detail.html:11 +msgid "Client id" +msgstr "ID client" + +#: note_kfet/templates/oauth2_provider/application_detail.html:14 +msgid "Client secret" +msgstr "Secret client" + +#: note_kfet/templates/oauth2_provider/application_detail.html:17 +msgid "Client type" +msgstr "Type de client" + +#: note_kfet/templates/oauth2_provider/application_detail.html:20 +msgid "Authorization Grant Type" +msgstr "Type d'autorisation" + +#: note_kfet/templates/oauth2_provider/application_detail.html:23 +msgid "Redirect Uris" +msgstr "URIs de redirection" + #: note_kfet/templates/oauth2_provider/application_detail.html:29 #, python-format msgid "" @@ -5597,6 +4100,19 @@ msgstr "" "de scopes avec les permissions que vous souhaitez attribuer à votre " "application." +#: note_kfet/templates/oauth2_provider/application_detail.html:37 +#: note_kfet/templates/oauth2_provider/application_form.html:23 +msgid "Go Back" +msgstr "Retour en arrière" + +#: note_kfet/templates/oauth2_provider/application_form.html:12 +msgid "Edit application" +msgstr "Modifier l'application" + +#: note_kfet/templates/oauth2_provider/application_list.html:7 +msgid "Your applications" +msgstr "Vos applications" + #: note_kfet/templates/oauth2_provider/application_list.html:11 msgid "" "You can find on this page the list of the applications that you already " @@ -5605,10 +4121,23 @@ msgstr "" "Vous pouvez trouver sur cette page la liste des applications que vous avez " "déjà enregistrées." +#: note_kfet/templates/oauth2_provider/application_list.html:30 +msgid "New Application" +msgstr "Nouvelle application" + #: note_kfet/templates/oauth2_provider/application_list.html:31 msgid "Authorized Tokens" msgstr "Jetons autorisés" +#: note_kfet/templates/oauth2_provider/application_registration_form.html:5 +msgid "Register a new application" +msgstr "Enregistrer une nouvelle application" + +#: note_kfet/templates/oauth2_provider/authorize.html:9 +#: note_kfet/templates/oauth2_provider/authorize.html:29 +msgid "Authorize" +msgstr "Autoriser" + #: note_kfet/templates/oauth2_provider/authorize.html:14 msgid "Application requires following permissions:" msgstr "L'application requiert les permissions suivantes :" @@ -5626,6 +4155,18 @@ msgstr "Succès" msgid "Please return to your application and enter this code:" msgstr "Merci de retourner à votre application et entrez ce code :" +#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9 +msgid "Are you sure you want to delete this token?" +msgstr "Êtes-vous sûr⋅e de vouloir supprimer ce jeton ?" + +#: note_kfet/templates/oauth2_provider/authorized-tokens.html:7 +msgid "Tokens" +msgstr "Jetons" + +#: note_kfet/templates/oauth2_provider/authorized-tokens.html:22 +msgid "There are no authorized tokens yet." +msgstr "Il n'y a pas encore de jeton autorisé." + #: note_kfet/templates/registration/logged_out.html:13 msgid "Thanks for spending some quality time with the Web site today." msgstr "Merci d'avoir utilisé la Note Kfet." @@ -5738,6 +4279,395 @@ msgstr "" "d'adhésion. Vous devez également valider votre adresse email en suivant le " "lien que vous avez reçu." +#, fuzzy, python-format +#~| msgid "Creation date" +#~ msgid "Deposit %(name)s" +#~ msgstr "Caution %(name)s" + +#, fuzzy +#~| msgid "There is no results." +#~ msgid "That page contains no results" +#~ msgstr "Il n'y a pas de résultat." + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid value." +#~ msgstr "dévalider" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid URL." +#~ msgstr "dévalider" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid integer." +#~ msgstr "dévalider" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid email address." +#~ msgstr "dévalider" + +#, fuzzy +#~| msgid "This activity is not validated yet." +#~ msgid "Enter a valid IPv4 address." +#~ msgstr "Cette activité n'est pas encore validée." + +#, fuzzy +#~| msgid "This activity is not validated yet." +#~ msgid "Enter a valid IPv6 address." +#~ msgstr "Cette activité n'est pas encore validée." + +#, fuzzy +#~| msgid "This activity is not validated yet." +#~ msgid "Enter a valid IPv4 or IPv6 address." +#~ msgstr "Cette activité n'est pas encore validée." + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Enter a number." +#~ msgstr "numéro de téléphone" + +#, fuzzy, python-format +#~| msgid "A template with this name already exist" +#~ msgid "%(model_name)s with this %(field_labels)s already exists." +#~ msgstr "Un modèle de transaction avec un nom similaire existe déjà" + +#, fuzzy, python-format +#~| msgid "This activity is not validated yet." +#~ msgid "Value %(value)r is not a valid choice." +#~ msgstr "Cette activité n'est pas encore validée." + +#, fuzzy +#~| msgid "This image cannot be loaded." +#~ msgid "This field cannot be null." +#~ msgstr "Cette image ne peut pas être chargée." + +#, fuzzy +#~| msgid "This image cannot be loaded." +#~ msgid "This field cannot be blank." +#~ msgstr "Cette image ne peut pas être chargée." + +#, fuzzy, python-format +#~| msgid "A template with this name already exist" +#~ msgid "%(model_name)s with this %(field_label)s already exists." +#~ msgstr "Un modèle de transaction avec un nom similaire existe déjà" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Decimal number" +#~ msgstr "numéro de téléphone" + +#, fuzzy +#~| msgid "action" +#~ msgid "Duration" +#~ msgstr "action" + +#, fuzzy +#~| msgid "address" +#~ msgid "Email address" +#~ msgstr "adresse" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Floating point number" +#~ msgstr "numéro de téléphone" + +#, fuzzy +#~| msgid "IP Address" +#~ msgid "IPv4 address" +#~ msgstr "Adresse IP" + +#, fuzzy +#~| msgid "IP Address" +#~ msgid "IP address" +#~ msgstr "Adresse IP" + +#, fuzzy, python-format +#~| msgid "This activity is not validated yet." +#~ msgid "“%(value)s” is not a valid UUID." +#~ msgstr "Cette activité n'est pas encore validée." + +#, fuzzy +#~| msgid "Invoice identifier" +#~ msgid "Universally unique identifier" +#~ msgstr "Numéro de facture" + +#, fuzzy +#~| msgid "Object" +#~ msgid "A JSON object" +#~ msgstr "Objet" + +#, fuzzy +#~| msgid "This address must be valid." +#~ msgid "Value must be valid JSON." +#~ msgstr "Cette adresse doit être valide." + +#, fuzzy, python-format +#~| msgid "A template with this name already exist" +#~ msgid "%(model)s instance with %(field)s %(value)r does not exist." +#~ msgstr "Un modèle de transaction avec un nom similaire existe déjà" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Enter a whole number." +#~ msgstr "numéro de téléphone" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid date." +#~ msgstr "dévalider" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid time." +#~ msgstr "dévalider" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid date/time." +#~ msgstr "dévalider" + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid duration." +#~ msgstr "Validation de l'adresse mail" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a list of values." +#~ msgstr "dévalider" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Enter a complete value." +#~ msgstr "numéro de téléphone" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid UUID." +#~ msgstr "dévalider" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid JSON." +#~ msgstr "dévalider" + +#, fuzzy +#~| msgid "order" +#~ msgid "Order" +#~ msgstr "consigne" + +#, fuzzy, python-format +#~| msgid "This activity is not validated yet." +#~ msgid "“%(pk)s” is not a valid value." +#~ msgstr "Cette activité n'est pas encore validée." + +#, fuzzy +#~| msgid "Current activity" +#~ msgid "Currently" +#~ msgstr "Activité en cours" + +#, fuzzy +#~| msgid "change" +#~ msgid "Change" +#~ msgstr "modifier" + +#, fuzzy +#~| msgid "Wrapped" +#~ msgid "Wed" +#~ msgstr "Wrapped" + +#, fuzzy +#~| msgid "Search" +#~ msgid "March" +#~ msgstr "Recherche" + +#, fuzzy +#~| msgid "day" +#~ msgid "May" +#~ msgstr "jour" + +#, fuzzy +#~| msgid "member" +#~ msgid "September" +#~ msgstr "adhérent·e" + +#, fuzzy +#~| msgid "member" +#~ msgid "November" +#~ msgstr "adhérent·e" + +#, fuzzy +#~| msgid "member" +#~ msgid "December" +#~ msgstr "adhérent·e" + +#, fuzzy +#~| msgid "add" +#~ msgid "jan" +#~ msgstr "ajouter" + +#, fuzzy +#~| msgid "fee" +#~ msgid "feb" +#~ msgstr "cotisation" + +#, fuzzy +#~| msgid "day" +#~ msgid "may" +#~ msgstr "jour" + +#, fuzzy +#~| msgid "add" +#~ msgid "jun" +#~ msgstr "ajouter" + +#, fuzzy +#~| msgid "product" +#~ msgid "oct" +#~ msgstr "produit" + +#, fuzzy +#~| msgid "bde" +#~ msgid "dec" +#~ msgstr "bde" + +#, fuzzy +#~| msgid "Search" +#~ msgctxt "abbrev. month" +#~ msgid "March" +#~ msgstr "Recherche" + +#, fuzzy +#~| msgid "day" +#~ msgctxt "abbrev. month" +#~ msgid "May" +#~ msgstr "jour" + +#, fuzzy +#~| msgid "Search" +#~ msgctxt "alt. month" +#~ msgid "March" +#~ msgstr "Recherche" + +#, fuzzy +#~| msgid "day" +#~ msgctxt "alt. month" +#~ msgid "May" +#~ msgstr "jour" + +#, fuzzy +#~| msgid "member" +#~ msgctxt "alt. month" +#~ msgid "September" +#~ msgstr "adhérent·e" + +#, fuzzy +#~| msgid "member" +#~ msgctxt "alt. month" +#~ msgid "November" +#~ msgstr "adhérent·e" + +#, fuzzy +#~| msgid "member" +#~ msgctxt "alt. month" +#~ msgid "December" +#~ msgstr "adhérent·e" + +#, fuzzy +#~| msgid "This activity is not validated yet." +#~ msgid "This is not a valid IPv6 address." +#~ msgstr "Cette activité n'est pas encore validée." + +#, fuzzy +#~| msgid "hour" +#~ msgid "or" +#~ msgstr "heure" + +#, fuzzy, python-format +#~| msgid "year" +#~ msgid "%(num)d year" +#~ msgid_plural "%(num)d years" +#~ msgstr[0] "année" +#~ msgstr[1] "année" + +#, fuzzy, python-format +#~| msgid "minute" +#~ msgid "%(num)d minute" +#~ msgid_plural "%(num)d minutes" +#~ msgstr[0] "minute" +#~ msgstr[1] "minute" + +#, fuzzy +#~| msgid "No reason specified" +#~ msgid "No year specified" +#~ msgstr "Pas de motif spécifié" + +#, fuzzy +#~| msgid "No reason specified" +#~ msgid "No month specified" +#~ msgstr "Pas de motif spécifié" + +#, fuzzy +#~| msgid "No reason specified" +#~ msgid "No day specified" +#~ msgstr "Pas de motif spécifié" + +#, fuzzy +#~| msgid "No reason specified" +#~ msgid "No week specified" +#~ msgstr "Pas de motif spécifié" + +#, fuzzy +#~| msgid "Client secret" +#~ msgid "Confidential" +#~ msgstr "Secret client" + +#, fuzzy +#~| msgid "public" +#~ msgid "Public" +#~ msgstr "public" + +#, fuzzy +#~| msgid "Authorization:" +#~ msgid "Authorization code" +#~ msgstr "Autorisation :" + +#, fuzzy +#~| msgid "Reset my password" +#~ msgid "Resource owner password-based" +#~ msgstr "Réinitialiser mon mot de passe" + +#, fuzzy +#~| msgid "Client secret" +#~ msgid "Client credentials" +#~ msgstr "Secret client" + +#, fuzzy +#~| msgid "This address must be valid." +#~ msgid "The access token is invalid." +#~ msgstr "Cette adresse doit être valide." + +#, fuzzy +#~| msgid "This address must be valid." +#~ msgid "The access token has expired." +#~ msgstr "Cette adresse doit être valide." + +#, fuzzy +#~| msgid "The user does not have enough money." +#~ msgid "The access token is valid but does not have enough scope." +#~ msgstr "L'utilisateur·ice n'a pas assez d'argent." + +#, fuzzy +#~| msgid "Application requires following permissions:" +#~ msgid "Application requires the following permissions" +#~ msgstr "L'application requiert les permissions suivantes :" + +#~ msgid "Deposit amount" +#~ msgstr "Caution" + #~ msgid "The BDE membership is included in the WEI registration." #~ msgstr "L'adhésion au BDE est offerte avec l'inscription au WEI." From 3f76ca647292329f3fd30a0d9181dee0d5bf6e5d Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 21 Jun 2025 16:32:26 +0200 Subject: [PATCH 040/153] Tables 1A (et typo) --- apps/wei/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/wei/views.py b/apps/wei/views.py index d4e1be24..34b274a2 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -965,7 +965,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): def get_form_class(self): registration = WEIRegistration.objects.get(pk=self.kwargs["pk"]) - if registration.first_year and 'sleected_bus_pk' not in registration.information: + if registration.first_year and 'selected_bus_pk' not in registration.information: return WEIMembership1AForm return WEIMembershipForm @@ -1360,6 +1360,7 @@ class WEI1AListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableView): def get_queryset(self, filter_permissions=True, **kwargs): qs = super().get_queryset(filter_permissions, **kwargs) qs = qs.filter(first_year=True, membership__isnull=False) + qs = qs.filter(wei=self.club) qs = qs.order_by('-membership__bus') return qs From cdc6f0a3f8cbc9baaa9c71899d3f332692069c0e Mon Sep 17 00:00:00 2001 From: quark Date: Fri, 27 Jun 2025 12:13:54 +0200 Subject: [PATCH 041/153] Fix jwks.json --- .env_example | 3 +++ README.md | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.env_example b/.env_example index 7e1dbd3b..5257e83c 100644 --- a/.env_example +++ b/.env_example @@ -21,3 +21,6 @@ EMAIL_PASSWORD=CHANGE_ME # Wiki configuration WIKI_USER=NoteKfet2020 WIKI_PASSWORD= + +# OIDC +OIDC_RSA_PRIVATE_KEY= diff --git a/README.md b/README.md index 4ba19356..c340d58c 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ Bien que cela permette de créer une instance sur toutes les distributions, 6. (Optionnel) **Création d'une clé privée OpenID Connect** Pour activer le support d'OpenID Connect, il faut générer une clé privée, par -exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner son -emplacement dans `OIDC_RSA_PRIVATE_KEY` (par défaut `/var/secrets/oidc.key`). +exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et copier la clé dans .env dans le champ +`OIDC_RSA_PRIVATE_KEY`. 7. Enjoy : @@ -237,8 +237,8 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous. 7. **Création d'une clé privée OpenID Connect** Pour activer le support d'OpenID Connect, il faut générer une clé privée, par -exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner son -emplacement dans `OIDC_RSA_PRIVATE_KEY` (par défaut `/var/secrets/oidc.key`). +exemple avec openssl (`openssl genrsa -out oidc.key 4096`), et renseigner le champ +`OIDC_RSA_PRIVATE_KEY` dans le .env (par défaut `/var/secrets/oidc.key`). 8. *Enjoy \o/* From c26534b6b7e65be3c3be82e8f410ff921276ae2e Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 27 Jun 2025 16:56:17 +0200 Subject: [PATCH 042/153] =?UTF-8?q?Ann=C3=A9e=20et=20algorithme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wei/forms/registration.py | 1 - apps/wei/forms/surveys/__init__.py | 4 +- apps/wei/forms/surveys/base.py | 6 + apps/wei/forms/surveys/wei2025.py | 343 +++++++++++++++++++++++++ apps/wei/templates/wei/bus_detail.html | 2 + apps/wei/urls.py | 3 +- apps/wei/views.py | 26 ++ 7 files changed, 381 insertions(+), 4 deletions(-) create mode 100644 apps/wei/forms/surveys/wei2025.py diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index fd6fea2c..2458a2eb 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -213,4 +213,3 @@ class BusTeamForm(forms.ModelForm): ), "color": ColorWidget(), } - # "color": ColorWidget(), diff --git a/apps/wei/forms/surveys/__init__.py b/apps/wei/forms/surveys/__init__.py index 0a33bc16..ef692d25 100644 --- a/apps/wei/forms/surveys/__init__.py +++ b/apps/wei/forms/surveys/__init__.py @@ -2,11 +2,11 @@ # SPDX-License-Identifier: GPL-3.0-or-later from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm -from .wei2024 import WEISurvey2024 +from .wei2025 import WEISurvey2025 __all__ = [ 'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey', ] -CurrentSurvey = WEISurvey2024 +CurrentSurvey = WEISurvey2025 diff --git a/apps/wei/forms/surveys/base.py b/apps/wei/forms/surveys/base.py index 3f3bff3b..99c84583 100644 --- a/apps/wei/forms/surveys/base.py +++ b/apps/wei/forms/surveys/base.py @@ -121,6 +121,12 @@ class WEISurveyAlgorithm: """ raise NotImplementedError + @classmethod + def get_bus_information_form(cls): + """ + The class of the form to update the bus information. + """ + raise NotImplementedError class WEISurvey: """ diff --git a/apps/wei/forms/surveys/wei2025.py b/apps/wei/forms/surveys/wei2025.py new file mode 100644 index 00000000..749d89c4 --- /dev/null +++ b/apps/wei/forms/surveys/wei2025.py @@ -0,0 +1,343 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +import time +import json +from functools import lru_cache +from random import Random + +from django import forms +from django.db import transaction +from django.db.models import Q +from django.utils.translation import gettext_lazy as _ + +from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation +from ...models import WEIMembership, Bus + +WORDS = [ + '13 organisé', '3ième mi temps', 'Années 2000', 'Apéro', 'BBQ', 'BP', 'Beauf', 'Binge drinking', 'Bon enfant', + 'Cartouche', 'Catacombes', 'Chansons paillardes', 'Chansons populaires', 'Chanteur', 'Chartreuse', 'Chill', + 'Core', 'DJ', 'Dancefloor', 'Danse', 'David Guetta', 'Disco', 'Eau de vie', 'Électro', 'Escalade', 'Familial', + 'Fanfare', 'Fracassage', 'Féria', 'Hard rock', 'Hoeggarden', 'House', 'Huit-six', 'IPA', 'Inclusif', 'Inferno', + 'Introverti', 'Jager bomb', 'Jazz', 'Jeux d\'alcool', 'Jeux de rôles', 'Jeux vidéo', 'Jul', 'Jus de fruit', + 'Karaoké', 'LGBTQI+', 'Lady Gaga', 'Loup garou', 'Morning beer', 'Métal', 'Nuit blanche', 'Ovalie', 'Psychedelic', + 'Pétanque', 'Rave', 'Reggae', 'Rhum', 'Ricard', 'Rock', 'Rosé', 'Rétro', 'Séducteur', 'Techno', 'Thérapie taxi', + 'Théâtre', 'Trap', 'Turn up', 'Underground', 'Volley', 'Wati B', 'Zinédine Zidane', +] + + +class WEISurveyForm2025(forms.Form): + """ + Survey form for the year 2025. + Members choose 20 words, from which we calculate the best associated bus. + """ + + word = forms.ChoiceField( + label=_("Choose a word:"), + widget=forms.RadioSelect(), + ) + + def set_registration(self, registration): + """ + Filter the bus selector with the buses of the current WEI. + """ + information = WEISurveyInformation2025(registration) + if not information.seed: + information.seed = int(1000 * time.time()) + information.save(registration) + registration._force_save = True + registration.save() + + if self.data: + self.fields["word"].choices = [(w, w) for w in WORDS] + if self.is_valid(): + return + + rng = Random((information.step + 1) * information.seed) + + buses = WEISurveyAlgorithm2025.get_buses() + informations = {bus: WEIBusInformation2025(bus) for bus in buses} + scores = sum((list(informations[bus].scores.values()) for bus in buses), []) + average_score = sum(scores) / len(scores) + + preferred_words = {bus: [word for word in WORDS + if informations[bus].scores[word] >= average_score] + for bus in buses} + + # Correction : proposer plusieurs mots différents à chaque étape + N_CHOICES = 4 # Nombre de mots à proposer à chaque étape + all_preferred_words = set() + for bus_words in preferred_words.values(): + all_preferred_words.update(bus_words) + all_preferred_words = list(all_preferred_words) + rng.shuffle(all_preferred_words) + words = all_preferred_words[:N_CHOICES] + self.fields["word"].choices = [(w, w) for w in words] + + +class WEIBusInformation2025(WEIBusInformation): + """ + For each word, the bus has a score + """ + scores: dict + + def __init__(self, bus): + self.scores = {} + for word in WORDS: + self.scores[word] = 0 + super().__init__(bus) + + +class BusInformationForm2025(forms.ModelForm): + class Meta: + model = Bus + fields = ['information_json'] + widgets = {} + + def __init__(self, *args, words=None, **kwargs): + super().__init__(*args, **kwargs) + + initial_scores = {} + if self.instance and self.instance.information_json: + try: + info = json.loads(self.instance.information_json) + initial_scores = info.get("scores", {}) + except (json.JSONDecodeError, TypeError, AttributeError): + initial_scores = {} + if words is None: + words = WORDS + self.words = words + + CHOICES = [(i, str(i)) for i in range(6)] # [(0, '0'), (1, '1'), ..., (5, '5')] + for word in words: + self.fields[word] = forms.TypedChoiceField( + label=word, + choices=CHOICES, + coerce=int, + initial=initial_scores.get(word, 0), + required=True, + widget=forms.RadioSelect, + help_text=_("Rate between 0 and 5."), + ) + + def clean(self): + cleaned_data = super().clean() + scores = {} + for word in self.words: + value = cleaned_data.get(word) + if value is not None: + scores[word] = value + # On encode en JSON + cleaned_data['information_json'] = json.dumps({"scores": scores}) + return cleaned_data + + +class WEISurveyInformation2025(WEISurveyInformation): + """ + We store the id of the selected bus. We store only the name, but is not used in the selection: + that's only for humans that try to read data. + """ + # Random seed that is stored at the first time to ensure that words are generated only once + seed = 0 + step = 0 + + def __init__(self, registration): + for i in range(1, 21): + setattr(self, "word" + str(i), None) + super().__init__(registration) + + +class WEISurvey2025(WEISurvey): + """ + Survey for the year 2025. + """ + + @classmethod + def get_year(cls): + return 2025 + + @classmethod + def get_survey_information_class(cls): + return WEISurveyInformation2025 + + def get_form_class(self): + return WEISurveyForm2025 + + def update_form(self, form): + """ + Filter the bus selector with the buses of the WEI. + """ + form.set_registration(self.registration) + + @transaction.atomic + def form_valid(self, form): + word = form.cleaned_data["word"] + self.information.step += 1 + setattr(self.information, "word" + str(self.information.step), word) + self.save() + + @classmethod + def get_algorithm_class(cls): + return WEISurveyAlgorithm2025 + + def is_complete(self) -> bool: + """ + The survey is complete once the bus is chosen. + """ + return self.information.step == 20 + + @classmethod + @lru_cache() + def word_mean(cls, word): + """ + Calculate the mid-score given by all buses. + """ + buses = cls.get_algorithm_class().get_buses() + return sum([cls.get_algorithm_class().get_bus_information(bus).scores[word] for bus in buses]) / buses.count() + + @lru_cache() + def score(self, bus): + if not self.is_complete(): + raise ValueError("Survey is not ended, can't calculate score") + + bus_info = self.get_algorithm_class().get_bus_information(bus) + # Score is the given score by the bus subtracted to the mid-score of the buses. + s = sum(bus_info.scores[getattr(self.information, 'word' + str(i))] + - self.word_mean(getattr(self.information, 'word' + str(i))) for i in range(1, 21)) / 20 + return s + + @lru_cache() + def scores_per_bus(self): + return {bus: self.score(bus) for bus in self.get_algorithm_class().get_buses()} + + @lru_cache() + def ordered_buses(self): + values = list(self.scores_per_bus().items()) + values.sort(key=lambda item: -item[1]) + return values + + @classmethod + def clear_cache(cls): + cls.word_mean.cache_clear() + return super().clear_cache() + + +class WEISurveyAlgorithm2025(WEISurveyAlgorithm): + """ + The algorithm class for the year 2025. + We use Gale-Shapley algorithm to attribute 1y students into buses. + """ + + @classmethod + def get_survey_class(cls): + return WEISurvey2025 + + @classmethod + def get_bus_information_class(cls): + return WEIBusInformation2025 + + @classmethod + def get_bus_information_form(cls): + return BusInformationForm2025 + def run_algorithm(self, display_tqdm=False): + """ + Gale-Shapley algorithm implementation. + We modify it to allow buses to have multiple "weddings". + """ + surveys = list(self.get_survey_class()(r) for r in self.get_registrations()) # All surveys + surveys = [s for s in surveys if s.is_complete()] # Don't consider invalid surveys + # Don't manage hardcoded people + surveys = [s for s in surveys if not hasattr(s.information, 'hardcoded') or not s.information.hardcoded] + + # Reset previous algorithm run + for survey in surveys: + survey.free() + survey.save() + + non_men = [s for s in surveys if s.registration.gender != 'male'] + men = [s for s in surveys if s.registration.gender == 'male'] + + quotas = {} + registrations = self.get_registrations() + non_men_total = registrations.filter(~Q(gender='male')).count() + for bus in self.get_buses(): + free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count() + # Remove hardcoded people + free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True, + registration__information_json__icontains="hardcoded").count() + quotas[bus] = 4 + int(non_men_total / registrations.count() * free_seats) + + tqdm_obj = None + if display_tqdm: + from tqdm import tqdm + tqdm_obj = tqdm(total=len(non_men), desc="Non-hommes") + + # Repartition for non men people first + self.make_repartition(non_men, quotas, tqdm_obj=tqdm_obj) + + quotas = {} + for bus in self.get_buses(): + free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count() + free_seats -= sum(1 for s in non_men if s.information.selected_bus_pk == bus.pk) + # Remove hardcoded people + free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True, + registration__information_json__icontains="hardcoded").count() + quotas[bus] = free_seats + + if display_tqdm: + tqdm_obj.close() + + from tqdm import tqdm + tqdm_obj = tqdm(total=len(men), desc="Hommes") + + self.make_repartition(men, quotas, tqdm_obj=tqdm_obj) + + if display_tqdm: + tqdm_obj.close() + + # Clear cache information after running algorithm + WEISurvey2025.clear_cache() + + def make_repartition(self, surveys, quotas=None, tqdm_obj=None): + free_surveys = surveys.copy() # Remaining surveys + while free_surveys: # Some students are not affected + survey = free_surveys[0] + buses = survey.ordered_buses() # Preferences of the student + for bus, current_score in buses: + if self.get_bus_information(bus).has_free_seats(surveys, quotas): + # Selected bus has free places. Put student in the bus + survey.select_bus(bus) + survey.save() + free_surveys.remove(survey) + break + else: + # Current bus has not enough places. Remove the least preferred student from the bus if existing + least_preferred_survey = None + least_score = -1 + # Find the least student in the bus that has a lower score than the current student + for survey2 in surveys: + if not survey2.information.valid or survey2.information.get_selected_bus() != bus: + continue + score2 = survey2.score(bus) + if current_score <= score2: # Ignore better students + continue + if least_preferred_survey is None or score2 < least_score: + least_preferred_survey = survey2 + least_score = score2 + + if least_preferred_survey is not None: + # Remove the least student from the bus and put the current student in. + # If it does not exist, choose the next bus. + least_preferred_survey.free() + least_preferred_survey.save() + free_surveys.append(least_preferred_survey) + survey.select_bus(bus) + survey.save() + free_surveys.remove(survey) + break + else: + raise ValueError(f"User {survey.registration.user} has no free seat") + + if tqdm_obj is not None: + tqdm_obj.n = len(surveys) - len(free_surveys) + tqdm_obj.refresh() diff --git a/apps/wei/templates/wei/bus_detail.html b/apps/wei/templates/wei/bus_detail.html index 04ef5f9a..af4eaccb 100644 --- a/apps/wei/templates/wei/bus_detail.html +++ b/apps/wei/templates/wei/bus_detail.html @@ -22,6 +22,8 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endif %} {% trans "Edit" %} + {% trans "Edit information" %} {% trans "Add team" %} diff --git a/apps/wei/urls.py b/apps/wei/urls.py index 3084dd51..5f9283c0 100644 --- a/apps/wei/urls.py +++ b/apps/wei/urls.py @@ -4,7 +4,7 @@ from django.urls import path from .views import CurrentWEIDetailView, WEI1AListView, WEIListView, WEICreateView, WEIDetailView, WEIUpdateView, \ - WEIRegistrationsView, WEIMembershipsView, MemberListRenderView, \ + WEIRegistrationsView, WEIMembershipsView, MemberListRenderView, BusInformationUpdateView, \ BusCreateView, BusManageView, BusUpdateView, BusTeamCreateView, BusTeamManageView, BusTeamUpdateView, \ WEIAttributeBus1AView, WEIAttributeBus1ANextView, WEIRegister1AView, WEIRegister2AView, WEIUpdateRegistrationView, \ WEIDeleteRegistrationView, WEIValidateRegistrationView, WEISurveyView, WEISurveyEndView, WEIClosedView @@ -42,4 +42,5 @@ urlpatterns = [ path('detail//closed/', WEIClosedView.as_view(), name="wei_closed"), path('bus-1A//', WEIAttributeBus1AView.as_view(), name="wei_bus_1A"), path('bus-1A/next//', WEIAttributeBus1ANextView.as_view(), name="wei_bus_1A_next"), + path('update-bus-info//', BusInformationUpdateView.as_view(), name="update_bus_info"), ] diff --git a/apps/wei/views.py b/apps/wei/views.py index 34b274a2..b3ad8883 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -1422,3 +1422,29 @@ class WEIAttributeBus1ANextView(LoginRequiredMixin, RedirectView): # On redirige vers la page d'attribution pour le premier étudiant trouvé return reverse_lazy('wei:wei_bus_1A', args=(qs.first().pk,)) + + +class BusInformationUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): + model = Bus + + def get_form_class(self): + return CurrentSurvey.get_algorithm_class().get_bus_information_form() + + def dispatch(self, request, *args, **kwargs): + wei = self.get_object().wei + today = date.today() + # We can't update a bus once the WEI is started + if today >= wei.date_start: + return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,))) + return super().dispatch(request, *args, **kwargs) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["club"] = self.object.wei + context["information"] = CurrentSurvey.get_algorithm_class().get_bus_information(self.object) + self.object.save() + return context + + def get_success_url(self): + self.object.refresh_from_db() + return reverse_lazy("wei:manage_bus", kwargs={"pk": self.object.pk}) \ No newline at end of file From e83ee8015f218371676fc5a284efc92eeef46f2c Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 27 Jun 2025 18:50:37 +0200 Subject: [PATCH 043/153] Tests --- apps/wei/forms/surveys/base.py | 1 + apps/wei/forms/surveys/wei2025.py | 18 ++-- apps/wei/tests/test_wei_algorithm_2024.py | 43 --------- apps/wei/tests/test_wei_algorithm_2025.py | 111 ++++++++++++++++++++++ apps/wei/tests/test_wei_registration.py | 2 +- apps/wei/views.py | 2 +- 6 files changed, 125 insertions(+), 52 deletions(-) create mode 100644 apps/wei/tests/test_wei_algorithm_2025.py diff --git a/apps/wei/forms/surveys/base.py b/apps/wei/forms/surveys/base.py index 99c84583..c2fde39d 100644 --- a/apps/wei/forms/surveys/base.py +++ b/apps/wei/forms/surveys/base.py @@ -128,6 +128,7 @@ class WEISurveyAlgorithm: """ raise NotImplementedError + class WEISurvey: """ Survey associated to a first year WEI registration. diff --git a/apps/wei/forms/surveys/wei2025.py b/apps/wei/forms/surveys/wei2025.py index 749d89c4..d92cc23f 100644 --- a/apps/wei/forms/surveys/wei2025.py +++ b/apps/wei/forms/surveys/wei2025.py @@ -58,20 +58,23 @@ class WEISurveyForm2025(forms.Form): buses = WEISurveyAlgorithm2025.get_buses() informations = {bus: WEIBusInformation2025(bus) for bus in buses} scores = sum((list(informations[bus].scores.values()) for bus in buses), []) - average_score = sum(scores) / len(scores) + if scores: + average_score = sum(scores) / len(scores) + else: + average_score = 0 preferred_words = {bus: [word for word in WORDS if informations[bus].scores[word] >= average_score] for bus in buses} # Correction : proposer plusieurs mots différents à chaque étape - N_CHOICES = 4 # Nombre de mots à proposer à chaque étape + n_choices = 4 # Nombre de mots à proposer à chaque étape all_preferred_words = set() for bus_words in preferred_words.values(): all_preferred_words.update(bus_words) all_preferred_words = list(all_preferred_words) rng.shuffle(all_preferred_words) - words = all_preferred_words[:N_CHOICES] + words = all_preferred_words[:n_choices] self.fields["word"].choices = [(w, w) for w in words] @@ -96,7 +99,7 @@ class BusInformationForm2025(forms.ModelForm): def __init__(self, *args, words=None, **kwargs): super().__init__(*args, **kwargs) - + initial_scores = {} if self.instance and self.instance.information_json: try: @@ -106,13 +109,13 @@ class BusInformationForm2025(forms.ModelForm): initial_scores = {} if words is None: words = WORDS - self.words = words + self.words = words - CHOICES = [(i, str(i)) for i in range(6)] # [(0, '0'), (1, '1'), ..., (5, '5')] + choices = [(i, str(i)) for i in range(6)] # [(0, '0'), (1, '1'), ..., (5, '5')] for word in words: self.fields[word] = forms.TypedChoiceField( label=word, - choices=CHOICES, + choices=choices, coerce=int, initial=initial_scores.get(word, 0), required=True, @@ -239,6 +242,7 @@ class WEISurveyAlgorithm2025(WEISurveyAlgorithm): @classmethod def get_bus_information_form(cls): return BusInformationForm2025 + def run_algorithm(self, display_tqdm=False): """ Gale-Shapley algorithm implementation. diff --git a/apps/wei/tests/test_wei_algorithm_2024.py b/apps/wei/tests/test_wei_algorithm_2024.py index bae36399..d1e5f428 100644 --- a/apps/wei/tests/test_wei_algorithm_2024.py +++ b/apps/wei/tests/test_wei_algorithm_2024.py @@ -6,8 +6,6 @@ from datetime import date, timedelta from django.contrib.auth.models import User from django.test import TestCase -from django.urls import reverse -from note.models import NoteUser from ..forms.surveys.wei2024 import WEIBusInformation2024, WEISurvey2024, WORDS, WEISurveyInformation2024 from ..models import Bus, WEIClub, WEIRegistration @@ -129,44 +127,3 @@ class TestWEIAlgorithm(TestCase): self.assertLessEqual(max_score - score, 25) # Always less than 25 % of tolerance self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 % - - def test_register_1a(self): - """ - Test register a first year member to the WEI and complete the survey - """ - response = self.client.get(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk))) - self.assertEqual(response.status_code, 200) - - user = User.objects.create(username="toto", email="toto@example.com") - NoteUser.objects.create(user=user) - response = self.client.post(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)), dict( - user=user.id, - soge_credit=True, - birth_date=date(2000, 1, 1), - gender='nonbinary', - clothing_cut='female', - clothing_size='XS', - health_issues='I am a bot', - emergency_contact_name='NoteKfet2020', - emergency_contact_phone='+33123456789', - )) - qs = WEIRegistration.objects.filter(user_id=user.id) - self.assertTrue(qs.exists()) - registration = qs.get() - self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, 200) - for question in WORDS: - # Fill 1A Survey, 10 pages - # be careful if questionnary form change (number of page, type of answer...) - response = self.client.post(reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), { - question: "1" - }) - registration.refresh_from_db() - survey = WEISurvey2024(registration) - self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, - 302 if survey.is_complete() else 200) - self.assertIsNotNone(getattr(survey.information, question), "Survey page " + question + " failed") - survey = WEISurvey2024(registration) - self.assertTrue(survey.is_complete()) - survey.select_bus(self.buses[0]) - survey.save() - self.assertIsNotNone(survey.information.get_selected_bus()) diff --git a/apps/wei/tests/test_wei_algorithm_2025.py b/apps/wei/tests/test_wei_algorithm_2025.py new file mode 100644 index 00000000..5930eb3b --- /dev/null +++ b/apps/wei/tests/test_wei_algorithm_2025.py @@ -0,0 +1,111 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +import random + +from django.contrib.auth.models import User +from django.test import TestCase + +from ..forms.surveys.wei2025 import WEIBusInformation2025, WEISurvey2025, WORDS, WEISurveyInformation2025 +from ..models import Bus, WEIClub, WEIRegistration + + +class TestWEIAlgorithm(TestCase): + """ + Run some tests to ensure that the WEI algorithm is working well. + """ + fixtures = ('initial',) + + def setUp(self): + """ + Create some test data, with one WEI and 10 buses with random score attributions. + """ + self.wei = WEIClub.objects.create( + name="WEI 2025", + email="wei2025@example.com", + date_start='2025-09-12', + date_end='2025-09-14', + year=2025, + membership_start='2025-06-01' + ) + + self.buses = [] + for i in range(10): + bus = Bus.objects.create(wei=self.wei, name=f"Bus {i}", size=10) + self.buses.append(bus) + information = WEIBusInformation2025(bus) + for word in WORDS: + information.scores[word] = random.randint(0, 101) + information.save() + bus.save() + + def test_survey_algorithm_small(self): + """ + There are only a few people in each bus, ensure that each person has its best bus + """ + # Add a few users + for i in range(10): + user = User.objects.create(username=f"user{i}") + registration = WEIRegistration.objects.create( + user=user, + wei=self.wei, + first_year=True, + birth_date='2000-01-01', + ) + information = WEISurveyInformation2025(registration) + for j in range(1, 21): + setattr(information, f'word{j}', random.choice(WORDS)) + information.step = 20 + information.save(registration) + registration.save() + + # Run algorithm + WEISurvey2025.get_algorithm_class()().run_algorithm() + + # Ensure that everyone has its first choice + for r in WEIRegistration.objects.filter(wei=self.wei).all(): + survey = WEISurvey2025(r) + preferred_bus = survey.ordered_buses()[0][0] + chosen_bus = survey.information.get_selected_bus() + self.assertEqual(preferred_bus, chosen_bus) + + def test_survey_algorithm_full(self): + """ + Buses are full of first year people, ensure that they are happy + """ + # Add a lot of users + for i in range(95): + user = User.objects.create(username=f"user{i}") + registration = WEIRegistration.objects.create( + user=user, + wei=self.wei, + first_year=True, + birth_date='2000-01-01', + ) + information = WEISurveyInformation2025(registration) + for j in range(1, 21): + setattr(information, f'word{j}', random.choice(WORDS)) + information.step = 20 + information.save(registration) + registration.save() + + # Run algorithm + WEISurvey2025.get_algorithm_class()().run_algorithm() + + penalty = 0 + # Ensure that everyone seems to be happy + # We attribute a penalty for each user that didn't have its first choice + # The penalty is the square of the distance between the score of the preferred bus + # and the score of the attributed bus + # We consider it acceptable if the mean of this distance is lower than 5 % + for r in WEIRegistration.objects.filter(wei=self.wei).all(): + survey = WEISurvey2025(r) + chosen_bus = survey.information.get_selected_bus() + buses = survey.ordered_buses() + score = min(v for bus, v in buses if bus == chosen_bus) + max_score = buses[0][1] + penalty += (max_score - score) ** 2 + + self.assertLessEqual(max_score - score, 25) # Always less than 25 % of tolerance + + self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 % diff --git a/apps/wei/tests/test_wei_registration.py b/apps/wei/tests/test_wei_registration.py index f7ce5ac0..d286581c 100644 --- a/apps/wei/tests/test_wei_registration.py +++ b/apps/wei/tests/test_wei_registration.py @@ -778,7 +778,7 @@ class TestDefaultWEISurvey(TestCase): WEISurvey.update_form(None, None) self.assertEqual(CurrentSurvey.get_algorithm_class().get_survey_class(), CurrentSurvey) - self.assertEqual(CurrentSurvey.get_year(), 2024) + self.assertEqual(CurrentSurvey.get_year(), 2025) class TestWeiAPI(TestAPI): diff --git a/apps/wei/views.py b/apps/wei/views.py index b3ad8883..97b49eaf 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -1447,4 +1447,4 @@ class BusInformationUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateV def get_success_url(self): self.object.refresh_from_db() - return reverse_lazy("wei:manage_bus", kwargs={"pk": self.object.pk}) \ No newline at end of file + return reverse_lazy("wei:manage_bus", kwargs={"pk": self.object.pk}) From bc517f02e51b872776e089f95e856860ad2576f3 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 27 Jun 2025 19:22:03 +0200 Subject: [PATCH 044/153] Traduction --- locale/fr/LC_MESSAGES/django.po | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 70aa20ce..9832c986 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: 2025-06-20 13:50+0200\n" +"POT-Creation-Date: 2025-06-27 19:15+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n" "Last-Translator: bleizi \n" "Language-Team: French \n" @@ -3046,9 +3046,14 @@ msgid "This team doesn't belong to the given bus." msgstr "Cette équipe n'appartient pas à ce bus." #: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:38 +#: apps/wei/forms/surveys/wei2025.py:36 msgid "Choose a word:" msgstr "Choisissez un mot :" +#: apps/wei/forms/surveys/wei2025.py:123 +msgid "Rate between 0 and 5." +msgstr "Note entre 0 et 5." + #: apps/wei/models.py:25 apps/wei/templates/wei/base.html:36 msgid "year" msgstr "année" @@ -3250,7 +3255,7 @@ msgstr "Année" msgid "preferred bus" msgstr "bus préféré" -#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:36 +#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:38 #: apps/wei/templates/wei/busteam_detail.html:52 msgid "Teams" msgstr "Équipes" @@ -3344,18 +3349,23 @@ msgstr "Voir le WEI" #: apps/wei/templates/wei/bus_detail.html:21 msgid "View club" -msgstr "Voir le lub" +msgstr "Voir le club" #: apps/wei/templates/wei/bus_detail.html:26 +#| msgid "survey information" +msgid "Edit information" +msgstr "Modifier les informations" + +#: apps/wei/templates/wei/bus_detail.html:28 #: apps/wei/templates/wei/busteam_detail.html:24 msgid "Add team" msgstr "Ajouter une équipe" -#: apps/wei/templates/wei/bus_detail.html:49 +#: apps/wei/templates/wei/bus_detail.html:51 msgid "Members" msgstr "Membres" -#: apps/wei/templates/wei/bus_detail.html:58 +#: apps/wei/templates/wei/bus_detail.html:60 #: apps/wei/templates/wei/busteam_detail.html:62 #: apps/wei/templates/wei/weimembership_list.html:31 msgid "View as PDF" @@ -3708,11 +3718,11 @@ msgstr "montant total" msgid "Attribute buses to first year members" msgstr "Répartir les 1A dans les bus" -#: apps/wei/views.py:1379 +#: apps/wei/views.py:1380 msgid "Attribute bus" msgstr "Attribuer un bus" -#: apps/wei/views.py:1419 +#: apps/wei/views.py:1420 msgid "" "No first year student without a bus found. Either all of them have a bus, or " "none has filled the survey yet." From c411197af3a3240ba23376b10b9863600b64a8db Mon Sep 17 00:00:00 2001 From: quark Date: Fri, 27 Jun 2025 22:13:43 +0200 Subject: [PATCH 045/153] multiline support for RSA key in env --- .env_example | 2 +- note_kfet/settings/base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.env_example b/.env_example index 5257e83c..da0b4efa 100644 --- a/.env_example +++ b/.env_example @@ -23,4 +23,4 @@ WIKI_USER=NoteKfet2020 WIKI_PASSWORD= # OIDC -OIDC_RSA_PRIVATE_KEY= +OIDC_RSA_PRIVATE_KEY=CHANGE_ME diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index 0f3a757c..2f7965d8 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -270,7 +270,7 @@ OAUTH2_PROVIDER = { 'PKCE_REQUIRED': False, # PKCE (fix a breaking change of django-oauth-toolkit 2.0.0) 'OIDC_ENABLED': True, 'OIDC_RSA_PRIVATE_KEY': - os.getenv('OIDC_RSA_PRIVATE_KEY', '/var/secrets/oidc.key'), + os.getenv('OIDC_RSA_PRIVATE_KEY', 'CHANGE_ME_IN_ENV_SETTINGS').replace('\\n', '\n'), # for multilines 'SCOPES': { 'openid': "OpenID Connect scope" }, } From b2c6b0e85d4e7e955d9135b7e2b7eb337b5022d6 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Tue, 1 Jul 2025 15:27:31 +0200 Subject: [PATCH 046/153] =?UTF-8?q?S=C3=A9lection=20de=20bus/=C3=A9quipe?= =?UTF-8?q?=20plus=20ergonomique?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/wei/forms/registration.py | 32 +++++++++---------- .../wei/templates/wei/weimembership_form.html | 23 +++++++++++++ apps/wei/views.py | 10 +++++- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index 2458a2eb..455d77ca 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -5,7 +5,7 @@ from bootstrap_datepicker_plus.widgets import DatePickerInput from django import forms from django.contrib.auth.models import User from django.db.models import Q -from django.forms import CheckboxSelectMultiple +from django.forms import CheckboxSelectMultiple, RadioSelect from django.utils.translation import gettext_lazy as _ from note.models import NoteSpecial, NoteUser from note_kfet.inputs import AmountInput, Autocomplete, ColorWidget @@ -140,6 +140,19 @@ class WEIMembershipForm(forms.ModelForm): required=False, ) + def __init__(self, *args, wei=None, **kwargs): + super().__init__(*args, **kwargs) + if 'bus' in self.fields: + if wei is not None: + self.fields['bus'].queryset = Bus.objects.filter(wei=wei) + else: + self.fields['bus'].queryset = Bus.objects.none() + if 'team' in self.fields: + if wei is not None: + self.fields['team'].queryset = BusTeam.objects.filter(bus__wei=wei) + else: + self.fields['team'].queryset = BusTeam.objects.none() + def clean(self): cleaned_data = super().clean() if 'team' in cleaned_data and cleaned_data["team"] is not None \ @@ -151,21 +164,8 @@ class WEIMembershipForm(forms.ModelForm): model = WEIMembership fields = ('roles', 'bus', 'team',) widgets = { - "bus": Autocomplete( - Bus, - attrs={ - 'api_url': '/api/wei/bus/', - 'placeholder': 'Bus ...', - } - ), - "team": Autocomplete( - BusTeam, - attrs={ - 'api_url': '/api/wei/team/', - 'placeholder': 'Équipe ...', - }, - resetable=True, - ), + "bus": RadioSelect(), + "team": RadioSelect(), } diff --git a/apps/wei/templates/wei/weimembership_form.html b/apps/wei/templates/wei/weimembership_form.html index f0f3d800..a9c85d5d 100644 --- a/apps/wei/templates/wei/weimembership_form.html +++ b/apps/wei/templates/wei/weimembership_form.html @@ -210,4 +210,27 @@ SPDX-License-Identifier: GPL-3.0-or-later } } + {% endblock %} diff --git a/apps/wei/views.py b/apps/wei/views.py index 97b49eaf..bfc7c616 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -788,7 +788,8 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update return form def get_membership_form(self, data=None, instance=None): - membership_form = WEIMembershipForm(data if data else None, instance=instance) + registration = self.get_object() + membership_form = WEIMembershipForm(data if data else None, instance=instance, wei=registration.wei) del membership_form.fields["credit_type"] del membership_form.fields["credit_amount"] del membership_form.fields["first_name"] @@ -969,6 +970,13 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): return WEIMembership1AForm return WEIMembershipForm + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + registration = WEIRegistration.objects.get(pk=self.kwargs["pk"]) + wei = registration.wei + kwargs['wei'] = wei + return kwargs + def get_form(self, form_class=None): form = super().get_form(form_class) registration = WEIRegistration.objects.get(pk=self.kwargs["pk"]) From 6edef619aa7b79ca3f1d1a62b3de631a67458adb Mon Sep 17 00:00:00 2001 From: quark Date: Thu, 3 Jul 2025 11:37:07 +0200 Subject: [PATCH 047/153] change requirements.txt --- apps/note/api/urls.py | 2 +- note_kfet/settings/base.py | 1 + requirements.txt | 30 +++++++++++++++--------------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/apps/note/api/urls.py b/apps/note/api/urls.py index 67d7371e..c50e68e4 100644 --- a/apps/note/api/urls.py +++ b/apps/note/api/urls.py @@ -13,7 +13,7 @@ def register_note_urls(router, path): router.register(path + '/note', NotePolymorphicViewSet) router.register(path + '/alias', AliasViewSet) router.register(path + '/trust', TrustViewSet) - router.register(path + '/consumer', ConsumerViewSet) + router.register(path + '/consumer', ConsumerViewSet, basename='alias2') router.register(path + '/transaction/category', TemplateCategoryViewSet) router.register(path + '/transaction/transaction', TransactionViewSet) diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index 2f7965d8..779ee23d 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -39,6 +39,7 @@ SECURE_HSTS_PRELOAD = True INSTALLED_APPS = [ # External apps 'bootstrap_datepicker_plus', + 'cas_server', 'colorfield', 'crispy_bootstrap4', 'crispy_forms', diff --git a/requirements.txt b/requirements.txt index f4a32c97..1d6f5063 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,20 +1,20 @@ -beautifulsoup4~=4.12.3 -crispy-bootstrap4~=2023.1 -Django~=4.2.9 +beautifulsoup4~=4.13.4 +crispy-bootstrap4~=2025.6 +Django~=5.2.4 django-bootstrap-datepicker-plus~=5.0.5 -#django-cas-server~=2.0.0 -django-colorfield~=0.11.0 -django-crispy-forms~=2.1.0 -django-extensions>=3.2.3 -django-filter~=23.5 +django-cas-server~=3.1.0 +django-colorfield~=0.14.0 +django-crispy-forms~=2.4.0 +django-extensions>=4.1.0 +django-filter~=25.1 #django-htcpcp-tea~=0.8.1 -django-mailer~=2.3.1 -django-oauth-toolkit~=2.3.0 -django-phonenumber-field~=7.3.0 +django-mailer~=2.3.2 +django-oauth-toolkit~=3.0.1 +django-phonenumber-field~=8.1.0 django-polymorphic~=3.1.0 -djangorestframework~=3.14.0 +djangorestframework~=3.16.0 django-rest-polymorphic~=0.1.10 -django-tables2~=2.7.0 +django-tables2~=2.7.5 python-memcached~=1.62 -phonenumbers~=8.13.28 -Pillow>=10.2.0 +phonenumbers~=9.0.8 +Pillow>=11.3.0 From 6c7d86185af363ec7f4694713579e1de9b6ae555 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Thu, 3 Jul 2025 14:34:04 +0200 Subject: [PATCH 048/153] Models --- apps/family/__init__.py | 0 apps/family/admin.py | 3 + apps/family/apps.py | 11 ++ apps/family/migrations/__init__.py | 0 apps/family/models.py | 165 +++++++++++++++++++++++++++++ apps/family/tests.py | 3 + apps/family/views.py | 3 + note_kfet/settings/base.py | 3 +- 8 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 apps/family/__init__.py create mode 100644 apps/family/admin.py create mode 100644 apps/family/apps.py create mode 100644 apps/family/migrations/__init__.py create mode 100644 apps/family/models.py create mode 100644 apps/family/tests.py create mode 100644 apps/family/views.py diff --git a/apps/family/__init__.py b/apps/family/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/family/admin.py b/apps/family/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/apps/family/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/family/apps.py b/apps/family/apps.py new file mode 100644 index 00000000..47007531 --- /dev/null +++ b/apps/family/apps.py @@ -0,0 +1,11 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + + +from django.utils.translation import gettext_lazy as _ +from django.apps import AppConfig + + +class FamilyConfig(AppConfig): + name = 'family' + verbose_name = _('family') diff --git a/apps/family/migrations/__init__.py b/apps/family/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/family/models.py b/apps/family/models.py new file mode 100644 index 00000000..b3cb9abd --- /dev/null +++ b/apps/family/models.py @@ -0,0 +1,165 @@ +from django.db import models, transaction +from django.utils import timezone +from django.contrib.auth.models import User +from django.utils.translation import gettext_lazy as _ + + +class Family(models.Model): + name = models.CharField( + max_length=255, + verbose_name=_('name'), + unique=True + ) + + description = models.CharField( + max_length=255, + verbose_name=_('description') + ) + + score = models.PositiveIntegerField( + verbose_name=_('score') + ) + + rank = models.PositiveIntegerField( + verbose_name=_('rank'), + ) + + class Meta: + verbose_name = _('Family') + verbose_name_plural = _('Families') + + def __str__(self): + return self.name + + +class FamilyMembership(models.Model): + user = models.OneToOneField( + User, + on_delete=models.PROTECT, + related_name=_('family_memberships'), + verbose_name=_('user'), + ) + + family = models.ForeignKey( + Family, + on_delete=models.PROTECT, + related_name=_('members'), + verbose_name=_('family'), + ) + + year = models.PositiveIntegerField( + verbose_name=_('year'), + default=timezone.now().year, + ) + + class Meta: + verbose_name = _('family membership') + verbose_name_plural = _('family memberships') + + def __str__(self): + return _('Family membership of {user} to {family}').format(user=self.user.username, family=self.family.name, ) + + +class ChallengeCategory(models.Model): + name = models.CharField( + max_length=255, + verbose_name=_('name'), + unique=True, + ) + + class Meta: + verbose_name = _('challenge category') + verbose_name_plural = _('challenge categories') + + def __str__(self): + return self.name + + +class Challenge(models.Model): + name = models.CharField( + max_length=255, + verbose_name=_('name'), + ) + + description = models.CharField( + max_length=255, + verbose_name=_('description'), + ) + + points = models.PositiveIntegerField( + verbose_name=_('points'), + ) + + category = models.ForeignKey( + ChallengeCategory, + verbose_name=_('category'), + on_delete=models.PROTECT + ) + + class Meta: + verbose_name = _('challenge') + verbose_name_plural = _('challenges') + + def __str__(self): + return self.name + + +class Achievement(models.Model): + challenge = models.ForeignKey( + Challenge, + on_delete=models.PROTECT, + + ) + family = models.ForeignKey( + Family, + on_delete=models.PROTECT, + verbose_name=_('family'), + ) + + obtained_at = models.DateTimeField( + verbose_name=_('obtained at'), + default=timezone.now, + ) + + class Meta: + verbose_name = _('achievement') + verbose_name_plural = _('achievements') + + def __str__(self): + return _('Challenge {challenge} carried out by Family {family}').format(challenge=self.challenge.name, family=self.family.name, ) + + @transaction.atomic + def save(self, *args, **kwargs): + """ + When saving, also grants points to the family + """ + self.family = Family.objects.select_for_update().get(pk=self.family_id) + challenge_points = self.challenge.points + is_new = self.pk is None + + super.save(*args, **kwargs) + + # Only grant points when getting a new achievement + if is_new: + self.family.refresh_from_db() + self.family.score += challenge_points + self.family._force_save = True + self.family.save() + + @transaction.atomic + def delete(self, *args, **kwargs): + """ + When deleting, also removes points from the family + """ + # Get the family and challenge before deletion + self.family = Family.objects.select_for_update().get(pk=self.family_id) + challenge_points = self.challenge.points + + # Delete the achievement + super().delete(*args, **kwargs) + + # Remove points from the family + self.family.refresh_from_db() + self.family.score -= challenge_points + self.family._force_save = True + self.family.save() diff --git a/apps/family/tests.py b/apps/family/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/apps/family/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/family/views.py b/apps/family/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/apps/family/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index 0f3a757c..4b31e359 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -70,6 +70,7 @@ INSTALLED_APPS = [ # Note apps 'api', 'activity', + 'family', 'food', 'logs', 'member', @@ -270,7 +271,7 @@ OAUTH2_PROVIDER = { 'PKCE_REQUIRED': False, # PKCE (fix a breaking change of django-oauth-toolkit 2.0.0) 'OIDC_ENABLED': True, 'OIDC_RSA_PRIVATE_KEY': - os.getenv('OIDC_RSA_PRIVATE_KEY', '/var/secrets/oidc.key'), + os.getenv('OIDC_RSA_PRIVATE_KEY', 'CHANGE_ME_IN_ENV_SETTINGS').replace('\\n', '\n'), # for multilines 'SCOPES': { 'openid': "OpenID Connect scope" }, } From f54dd3048207fef48999dafd2cb13c6a2a34cd52 Mon Sep 17 00:00:00 2001 From: quark Date: Thu, 3 Jul 2025 15:18:29 +0200 Subject: [PATCH 049/153] fix logout test --- apps/member/tests/test_login.py | 2 +- note_kfet/templates/base.html | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/member/tests/test_login.py b/apps/member/tests/test_login.py index b8873a14..ce5de1cf 100644 --- a/apps/member/tests/test_login.py +++ b/apps/member/tests/test_login.py @@ -44,7 +44,7 @@ class TemplateLoggedInTests(TestCase): self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302) def test_logout(self): - response = self.client.get(reverse("logout")) + response = self.client.post(reverse("logout")) self.assertEqual(response.status_code, 200) def test_admin_index(self): diff --git a/note_kfet/templates/base.html b/note_kfet/templates/base.html index 1c601c50..3dbfa901 100644 --- a/note_kfet/templates/base.html +++ b/note_kfet/templates/base.html @@ -138,9 +138,12 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "My account" %} - - {% trans "Log out" %} - +
+ {% csrf_token %} + +
{% else %} From 85ea43a7cf9e62b1cc6add430bb266baa7d0f6db Mon Sep 17 00:00:00 2001 From: quark Date: Fri, 4 Jul 2025 16:26:40 +0200 Subject: [PATCH 050/153] change pipeline --- .gitlab-ci.yml | 8 ++++---- tox.ini | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2ba35d31..4cf8dab9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,7 +8,7 @@ variables: GIT_SUBMODULE_STRATEGY: recursive # Ubuntu 22.04 -py310-django42: +py310-django52: stage: test image: ubuntu:22.04 before_script: @@ -22,10 +22,10 @@ py310-django42: python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache python3-bs4 python3-setuptools tox texlive-xetex - script: tox -e py310-django42 + script: tox -e py310-django52 # Debian Bookworm -py311-django42: +py311-django52: stage: test image: debian:bookworm before_script: @@ -37,7 +37,7 @@ py311-django42: python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache python3-bs4 python3-setuptools tox texlive-xetex - script: tox -e py311-django42 + script: tox -e py311-django52 linters: stage: quality-assurance diff --git a/tox.ini b/tox.ini index 1bfeb593..8ad79854 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,13 @@ [tox] envlist = # Ubuntu 22.04 Python - py310-django42 + py310-django52 # Debian Bookworm Python - py311-django42 + py311-django52 # Ubuntu 24.04 Python - py312-django42 + py312-django52 linters skipsdist = True From b7c0986a5f358863714f24d75ea264487147fae5 Mon Sep 17 00:00:00 2001 From: quark Date: Fri, 4 Jul 2025 17:14:12 +0200 Subject: [PATCH 051/153] cron and linters --- note.cron | 5 +++-- tox.ini | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/note.cron b/note.cron index fb45a4b3..6c2b94c6 100644 --- a/note.cron +++ b/note.cron @@ -27,5 +27,6 @@ MAILTO=notekfet2020@lists.crans.org # 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 -e "bda.ensparissaclay@gmail.com" - \ No newline at end of file + 00 10 * * 0 root cd /var/www/note_kfet && env/bin/python manage.py extract_ml_registrations -t art -e "bda.ensparissaclay@gmail.com" +# Envoyer la liste de la bouffe au club et aux GCKs + 00 8 * * 1 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_for_food --report --club diff --git a/tox.ini b/tox.ini index 8ad79854..1fa7cf7a 100644 --- a/tox.ini +++ b/tox.ini @@ -32,8 +32,7 @@ deps = pep8-naming pyflakes commands = - flake8 apps --extend-exclude apps/scripts,apps/wrapped/management/commands - flake8 apps/wrapped/management/commands --extend-ignore=C901 + flake8 apps --extend-exclude apps/scripts [flake8] ignore = W503, I100, I101, B019 From 4cc43fe4b62e936b1bb9b169b4286dc504694c08 Mon Sep 17 00:00:00 2001 From: quark Date: Fri, 4 Jul 2025 22:11:47 +0200 Subject: [PATCH 052/153] traduction, resolve #133 --- locale/fr/LC_MESSAGES/django.po | 113 +++++++++++++++++--------------- note_kfet/templates/base.html | 4 ++ 2 files changed, 65 insertions(+), 52 deletions(-) diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 9832c986..9f9fb04f 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: 2025-06-27 19:15+0200\n" +"POT-Creation-Date: 2025-07-04 21:47+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n" "Last-Translator: bleizi \n" "Language-Team: French \n" @@ -785,49 +785,49 @@ msgstr "semaines" msgid "and" msgstr "et" -#: apps/food/views.py:118 +#: apps/food/views.py:119 msgid "Add a new QRCode" msgstr "Ajouter un nouveau QR-code" -#: apps/food/views.py:167 +#: apps/food/views.py:168 msgid "Add an aliment" msgstr "Ajouter un nouvel aliment" -#: apps/food/views.py:235 +#: apps/food/views.py:236 msgid "Add a meal" msgstr "Ajouter un plat" -#: apps/food/views.py:275 +#: apps/food/views.py:276 msgid "Manage ingredients of:" msgstr "Gestion des ingrédienrs de :" -#: apps/food/views.py:289 apps/food/views.py:297 +#: apps/food/views.py:290 apps/food/views.py:298 #, python-brace-format msgid "Fully used in {meal}" msgstr "Aliment entièrement utilisé dans : {meal}" -#: apps/food/views.py:344 +#: apps/food/views.py:345 msgid "Add the ingredient:" msgstr "Ajouter l'ingrédient" -#: apps/food/views.py:370 +#: apps/food/views.py:371 #, python-brace-format msgid "Food fully used in : {meal.name}" msgstr "Aliment entièrement utilisé dans : {meal.name}" -#: apps/food/views.py:389 +#: apps/food/views.py:390 msgid "Update an aliment" msgstr "Modifier un aliment" -#: apps/food/views.py:437 +#: apps/food/views.py:438 msgid "Details of:" msgstr "Détails de :" -#: apps/food/views.py:447 apps/treasury/tables.py:149 +#: apps/food/views.py:448 apps/treasury/tables.py:149 msgid "Yes" msgstr "Oui" -#: apps/food/views.py:449 apps/member/models.py:99 apps/treasury/tables.py:149 +#: apps/food/views.py:450 apps/member/models.py:99 apps/treasury/tables.py:149 msgid "No" msgstr "Non" @@ -1962,8 +1962,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:1097 -#: apps/wei/views.py:1101 +#: apps/note/models/transactions.py:363 apps/wei/views.py:1105 +#: apps/wei/views.py:1109 msgid "This field is required." msgstr "Ce champ est requis." @@ -2065,6 +2065,8 @@ 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/food_report.html:48 +#: apps/scripts/templates/scripts/food_report.txt:14 msgid "Mail generated by the Note Kfet on the" msgstr "Mail généré par la Note Kfet le" @@ -2269,7 +2271,7 @@ msgstr "s'applique au club" msgid "role permissions" msgstr "permissions par rôles" -#: apps/permission/signals.py:73 +#: apps/permission/signals.py:74 #, python-brace-format msgid "" "You don't have the permission to change the field {field} on this instance " @@ -2278,7 +2280,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:104 +#: apps/permission/signals.py:84 apps/permission/views.py:104 #, python-brace-format msgid "" "You don't have the permission to add an instance of model {app_label}." @@ -2287,7 +2289,7 @@ msgstr "" "Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}." "{model_name}." -#: apps/permission/signals.py:112 +#: apps/permission/signals.py:113 #, python-brace-format msgid "" "You don't have the permission to delete this instance of model {app_label}." @@ -3041,7 +3043,7 @@ msgstr "Rôles au WEI" msgid "Select the roles that you are interested in." msgstr "Sélectionnez les rôles qui vous intéressent." -#: apps/wei/forms/registration.py:147 +#: apps/wei/forms/registration.py:160 msgid "This team doesn't belong to the given bus." msgstr "Cette équipe n'appartient pas à ce bus." @@ -3120,7 +3122,7 @@ 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:202 apps/wei/views.py:984 +#: apps/wei/models.py:202 apps/wei/views.py:992 msgid "Caution check given" msgstr "Chèque de caution donné" @@ -3352,7 +3354,6 @@ msgid "View club" msgstr "Voir le club" #: apps/wei/templates/wei/bus_detail.html:26 -#| msgid "survey information" msgid "Edit information" msgstr "Modifier les informations" @@ -3373,8 +3374,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:1159 -#: apps/wei/views.py:1214 apps/wei/views.py:1261 +#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1167 +#: apps/wei/views.py:1222 apps/wei/views.py:1269 msgid "Survey WEI" msgstr "Questionnaire WEI" @@ -3654,51 +3655,51 @@ msgstr "" msgid "Update WEI Registration" msgstr "Modifier l'inscription WEI" -#: apps/wei/views.py:810 +#: apps/wei/views.py:811 msgid "No membership found for this registration" msgstr "Pas d'adhésion trouvée pour cette inscription" -#: apps/wei/views.py:819 +#: apps/wei/views.py:820 msgid "You don't have the permission to update memberships" msgstr "" "Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}." "{model_name}." -#: apps/wei/views.py:825 +#: apps/wei/views.py:826 #, python-format msgid "You don't have the permission to update the field %(field)s" msgstr "Vous n'avez pas la permission de modifier le champ %(field)s" -#: apps/wei/views.py:870 +#: apps/wei/views.py:871 msgid "Delete WEI registration" msgstr "Supprimer l'inscription WEI" -#: apps/wei/views.py:881 +#: apps/wei/views.py:882 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:899 +#: apps/wei/views.py:900 msgid "Validate WEI registration" msgstr "Valider l'inscription WEI" -#: apps/wei/views.py:985 +#: apps/wei/views.py:993 msgid "Please make sure the check is given before validating the registration" msgstr "" "Merci de vous assurer que le chèque a bien été donné avant de valider " "l'adhésion" -#: apps/wei/views.py:991 +#: apps/wei/views.py:999 msgid "Create deposit transaction" msgstr "Créer une transaction de caution" -#: apps/wei/views.py:992 +#: apps/wei/views.py:1000 #, python-format msgid "" "A transaction of %(amount).2f€ will be created from the user's Note account" msgstr "" "Un transaction de %(amount).2f€ va être créée depuis la note de l'utilisateur" -#: apps/wei/views.py:1087 +#: apps/wei/views.py:1095 #, python-format msgid "" "This user doesn't have enough money to join this club and pay the deposit. " @@ -3708,21 +3709,21 @@ msgstr "" "payer la cautionSolde actuel : %(balance)d€, crédit : %(credit)d€, requis : " "%(needed)d€" -#: apps/wei/views.py:1140 +#: apps/wei/views.py:1148 #, fuzzy, python-format #| msgid "total amount" msgid "Caution %(name)s" msgstr "montant total" -#: apps/wei/views.py:1354 +#: apps/wei/views.py:1362 msgid "Attribute buses to first year members" msgstr "Répartir les 1A dans les bus" -#: apps/wei/views.py:1380 +#: apps/wei/views.py:1388 msgid "Attribute bus" msgstr "Attribuer un bus" -#: apps/wei/views.py:1420 +#: apps/wei/views.py:1428 msgid "" "No first year student without a bus found. Either all of them have a bus, or " "none has filled the survey yet." @@ -3746,7 +3747,7 @@ msgstr "bde" #: apps/wrapped/models.py:65 msgid "data json" -msgstr "donnée json" +msgstr "données json" #: apps/wrapped/models.py:66 msgid "data in the wrapped and generated by the script generate_wrapped" @@ -3888,7 +3889,7 @@ 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 " +"N'oublie pas de demander la permission des personnes apparaissant dans un " "wrapped avant de le rendre public" #: apps/wrapped/templates/wrapped/wrapped_list.html:40 @@ -3907,19 +3908,19 @@ msgstr "Le wrapped est public" msgid "List of wrapped" msgstr "Liste des wrapped" -#: note_kfet/settings/base.py:177 +#: note_kfet/settings/base.py:178 msgid "German" msgstr "Allemand" -#: note_kfet/settings/base.py:178 +#: note_kfet/settings/base.py:179 msgid "English" msgstr "Anglais" -#: note_kfet/settings/base.py:179 +#: note_kfet/settings/base.py:180 msgid "Spanish" msgstr "Espagnol" -#: note_kfet/settings/base.py:180 +#: note_kfet/settings/base.py:181 msgid "French" msgstr "Français" @@ -3996,18 +3997,18 @@ msgstr "Admin" msgid "My account" msgstr "Mon compte" -#: note_kfet/templates/base.html:142 +#: note_kfet/templates/base.html:144 msgid "Log out" msgstr "Se déconnecter" -#: note_kfet/templates/base.html:150 +#: note_kfet/templates/base.html:153 #: 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:157 +#: note_kfet/templates/base.html:160 #: note_kfet/templates/registration/login.html:6 #: note_kfet/templates/registration/login.html:15 #: note_kfet/templates/registration/login.html:38 @@ -4015,7 +4016,7 @@ msgstr "Inscription" msgid "Log in" msgstr "Se connecter" -#: note_kfet/templates/base.html:171 +#: note_kfet/templates/base.html:174 msgid "" "You are not a BDE member anymore. Please renew your membership if you want " "to use the note." @@ -4023,7 +4024,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:177 +#: note_kfet/templates/base.html:180 msgid "" "Your e-mail address is not validated. Please check your mail inbox and click " "on the validation link." @@ -4031,7 +4032,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:183 +#: note_kfet/templates/base.html:186 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 " @@ -4045,22 +4046,30 @@ 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:206 +#: note_kfet/templates/base.html:209 msgid "Contact us" msgstr "Nous contacter" -#: note_kfet/templates/base.html:208 +#: note_kfet/templates/base.html:211 msgid "Technical Support" msgstr "Support technique" -#: note_kfet/templates/base.html:210 +#: note_kfet/templates/base.html:213 msgid "Charte Info (FR)" msgstr "Charte Info (FR)" -#: note_kfet/templates/base.html:212 +#: note_kfet/templates/base.html:215 msgid "FAQ (FR)" msgstr "FAQ (FR)" +#: note_kfet/templates/base.html:217 +msgid "Managed by BDE" +msgstr "Gérer par le BDE" + +#: note_kfet/templates/base.html:219 +msgid "Hosted by Cr@ns" +msgstr "Hébergé par le Cr@ans" + #: note_kfet/templates/base_search.html:15 msgid "Search by attribute such as name..." msgstr "Chercher par un attribut tel que le nom..." diff --git a/note_kfet/templates/base.html b/note_kfet/templates/base.html index 3dbfa901..70e55f90 100644 --- a/note_kfet/templates/base.html +++ b/note_kfet/templates/base.html @@ -213,6 +213,10 @@ SPDX-License-Identifier: GPL-3.0-or-later class="text-muted">{% trans "Charte Info (FR)" %} — {% trans "FAQ (FR)" %} — + {% trans "Managed by BDE" %} — + {% trans "Hosted by Cr@ns" %} &mdahs; {% csrf_token %} {% blocktrans %}

Log In Successful

You have successfully logged into the Central Authentication Service.
For security reasons, please Log Out and Exit your web browser when you are done accessing services that require authentication!{% endblocktrans %} +
+
+ +
+
+{% endblock %} diff --git a/note_kfet/templates/cas/login.html b/note_kfet/templates/cas/login.html new file mode 100644 index 00000000..84fd0cf8 --- /dev/null +++ b/note_kfet/templates/cas/login.html @@ -0,0 +1,42 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) by BDE ENS-Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n %} + +{% block ante_messages %} + {% if auto_submit %}{% endif %} +{% endblock %} + +{% block content %} +
+
+
+ +
+ +{% endblock %} + +{% block javascript_inline %} +jQuery(function( $ ){ + $("#id_warn").click(function(e){ + if($("#id_warn").is(':checked')){ + createCookie("warn", "on", 10 * 365); + } else { + eraseCookie("warn"); + } + }); +}); +{% if auto_submit %}document.getElementById('login_form').submit(); // SUBMIT FORM{% endif %} +{% endblock %} diff --git a/note_kfet/templates/cas/logout.html b/note_kfet/templates/cas/logout.html new file mode 100644 index 00000000..814f7d33 --- /dev/null +++ b/note_kfet/templates/cas/logout.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) by BDE ENS-Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n static %} +{% block content %} + +{% endblock %} + diff --git a/note_kfet/templates/cas/warn.html b/note_kfet/templates/cas/warn.html new file mode 100644 index 00000000..89ee1815 --- /dev/null +++ b/note_kfet/templates/cas/warn.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) by BDE ENS-Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n static %} + +{% block content %} +
+
+ +
+
+{% endblock %} + From c36f8c25a288c7d1c1e7c8aaad699feee1e9615c Mon Sep 17 00:00:00 2001 From: quark Date: Sat, 5 Jul 2025 18:26:31 +0200 Subject: [PATCH 054/153] Add banner #80 (with django-constance --- apps/permission/signals.py | 1 + note_kfet/admin.py | 5 +++++ note_kfet/settings/base.py | 19 +++++++++++++++++++ note_kfet/templates/base.html | 6 +++++- requirements.txt | 1 + 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/apps/permission/signals.py b/apps/permission/signals.py index 5ea04113..af5ab4ce 100644 --- a/apps/permission/signals.py +++ b/apps/permission/signals.py @@ -13,6 +13,7 @@ EXCLUDED = [ 'cas_server.serviceticket', 'cas_server.user', 'cas_server.userattributes', + 'constance.constance', 'contenttypes.contenttype', 'logs.changelog', 'migrations.migration', diff --git a/note_kfet/admin.py b/note_kfet/admin.py index 6bda409f..7f4effe3 100644 --- a/note_kfet/admin.py +++ b/note_kfet/admin.py @@ -56,3 +56,8 @@ if "cas_server" in settings.INSTALLED_APPS: from cas_server.models import * admin_site.register(ServicePattern, ServicePatternAdmin) admin_site.register(FederatedIendityProvider, FederatedIendityProviderAdmin) + +if "constance" in settings.INSTALLED_APPS: + from constance.admin import * + from constance.models import * + admin_site.register([Config], ConstanceAdmin) diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index 8a0fc738..dc954453 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -41,6 +41,7 @@ INSTALLED_APPS = [ 'bootstrap_datepicker_plus', 'cas_server', 'colorfield', + 'constance', 'crispy_bootstrap4', 'crispy_forms', # 'django_htcpcp_tea', @@ -112,6 +113,7 @@ TEMPLATES = [ 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ + 'constance.context_processors.config', 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', @@ -315,3 +317,20 @@ CAS_LOGGED_TEMPLATE = 'cas/logged.html' # Default field for primary key DEFAULT_AUTO_FIELD = "django.db.models.AutoField" + +# Constance settings +CONSTANCE_ADDITIONAL_FIELDS = { + 'banner_type': ['django.forms.fields.ChoiceField', { + 'widget': 'django.forms.Select', + 'choices': (('info', 'Info'), ('success', 'Success'), ('warning', 'Warning'), ('danger', 'Danger')) + }], +} +CONSTANCE_CONFIG = { + 'BANNER_MESSAGE': ('', 'Some message', str), + 'BANNER_TYPE': ('Info', 'Banner type', 'banner_type'), +} +CONSTANCE_CONFIG_FIELDSETS = { + 'Banner': ('BANNER_MESSAGE', 'BANNER_TYPE'), +} +CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend' +CONSTANCE_SUPERUSER_ONLY = True diff --git a/note_kfet/templates/base.html b/note_kfet/templates/base.html index 45dcdfe7..f7794222 100644 --- a/note_kfet/templates/base.html +++ b/note_kfet/templates/base.html @@ -191,7 +191,11 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endblocktrans %} {% endif %} - {# TODO Add banners #} + {% if config.BANNER_MESSAGE %} +
+ {{ config.BANNER_MESSAGE }} +
+ {% endif %} {% block content %}

Default content...

diff --git a/requirements.txt b/requirements.txt index 1d6f5063..c40bf0bb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ Django~=5.2.4 django-bootstrap-datepicker-plus~=5.0.5 django-cas-server~=3.1.0 django-colorfield~=0.14.0 +django-constance~=4.3.2 django-crispy-forms~=2.4.0 django-extensions>=4.1.0 django-filter~=25.1 From f6ad6197de62a231b0e95b8c0918fa0d467e2187 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 5 Jul 2025 19:47:35 +0200 Subject: [PATCH 055/153] ListViews et templates --- apps/family/admin.py | 3 - apps/family/migrations/0001_initial.py | 85 +++++++++++++++++++ apps/family/models.py | 37 ++++++++ apps/family/tables.py | 40 +++++++++ .../templates/family/challenge_list.html | 30 +++++++ apps/family/templates/family/family_list.html | 30 +++++++ apps/family/tests.py | 3 - apps/family/urls.py | 12 +++ apps/family/views.py | 64 +++++++++++++- note_kfet/templates/base.html | 7 ++ note_kfet/urls.py | 5 +- 11 files changed, 306 insertions(+), 10 deletions(-) delete mode 100644 apps/family/admin.py create mode 100644 apps/family/migrations/0001_initial.py create mode 100644 apps/family/tables.py create mode 100644 apps/family/templates/family/challenge_list.html create mode 100644 apps/family/templates/family/family_list.html delete mode 100644 apps/family/tests.py create mode 100644 apps/family/urls.py diff --git a/apps/family/admin.py b/apps/family/admin.py deleted file mode 100644 index 8c38f3f3..00000000 --- a/apps/family/admin.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.contrib import admin - -# Register your models here. diff --git a/apps/family/migrations/0001_initial.py b/apps/family/migrations/0001_initial.py new file mode 100644 index 00000000..86c7c135 --- /dev/null +++ b/apps/family/migrations/0001_initial.py @@ -0,0 +1,85 @@ +# Generated by Django 4.2.21 on 2025-07-04 19:05 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='ChallengeCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, unique=True, verbose_name='name')), + ], + options={ + 'verbose_name': 'challenge category', + 'verbose_name_plural': 'challenge categories', + }, + ), + migrations.CreateModel( + name='Family', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, unique=True, verbose_name='name')), + ('description', models.CharField(max_length=255, verbose_name='description')), + ('score', models.PositiveIntegerField(verbose_name='score')), + ('rank', models.PositiveIntegerField(verbose_name='rank')), + ], + options={ + 'verbose_name': 'Family', + 'verbose_name_plural': 'Families', + }, + ), + migrations.CreateModel( + name='Challenge', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, verbose_name='name')), + ('description', models.CharField(max_length=255, verbose_name='description')), + ('points', models.PositiveIntegerField(verbose_name='points')), + ('obtained', models.PositiveIntegerField(verbose_name='obtained')), + ('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='family.challengecategory', verbose_name='category')), + ], + options={ + 'verbose_name': 'challenge', + 'verbose_name_plural': 'challenges', + }, + ), + migrations.CreateModel( + name='Achievement', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('obtained_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='obtained at')), + ('challenge', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='family.challenge')), + ('family', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='family.family', verbose_name='family')), + ], + options={ + 'verbose_name': 'achievement', + 'verbose_name_plural': 'achievements', + }, + ), + migrations.CreateModel( + name='FamilyMembership', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('year', models.PositiveIntegerField(default=2025, verbose_name='year')), + ('family', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='members', to='family.family', verbose_name='family')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='family_memberships', to=settings.AUTH_USER_MODEL, verbose_name='user')), + ], + options={ + 'verbose_name': 'family membership', + 'verbose_name_plural': 'family memberships', + 'unique_together': {('user', 'year')}, + }, + ), + ] diff --git a/apps/family/models.py b/apps/family/models.py index b3cb9abd..2b10999b 100644 --- a/apps/family/models.py +++ b/apps/family/models.py @@ -1,3 +1,6 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + from django.db import models, transaction from django.utils import timezone from django.contrib.auth.models import User @@ -53,6 +56,7 @@ class FamilyMembership(models.Model): ) class Meta: + unique_together = ('user', 'year',) verbose_name = _('family membership') verbose_name_plural = _('family memberships') @@ -96,6 +100,10 @@ class Challenge(models.Model): on_delete=models.PROTECT ) + obtained = models.PositiveIntegerField( + verbose_name=_('obtained') + ) + class Meta: verbose_name = _('challenge') verbose_name_plural = _('challenges') @@ -128,12 +136,27 @@ class Achievement(models.Model): def __str__(self): return _('Challenge {challenge} carried out by Family {family}').format(challenge=self.challenge.name, family=self.family.name, ) + @classmethod + def update_ranking(cls, *args, **kwargs): + """ + Update ranking when adding or removing points + """ + family_set = cls.objects.select_for_update().all().order_by("-score") + for i in range(family_set.count()): + if i == 0 or family_set[i].score != family_set[i - 1].score: + new_rank = i + 1 + family = family_set[i] + family.rank = new_rank + family._force_save = True + family.save() + @transaction.atomic def save(self, *args, **kwargs): """ When saving, also grants points to the family """ self.family = Family.objects.select_for_update().get(pk=self.family_id) + self.challenge = Challenge.objects.select_for_update().get(pk=self.challenge_id) challenge_points = self.challenge.points is_new = self.pk is None @@ -146,6 +169,13 @@ class Achievement(models.Model): self.family._force_save = True self.family.save() + self.challenge.refresh_from_db() + self.challenge.obtained += 1 + self.challenge._force_save = True + self.challenge.save() + + self.__class__.update_ranking() + @transaction.atomic def delete(self, *args, **kwargs): """ @@ -163,3 +193,10 @@ class Achievement(models.Model): self.family.score -= challenge_points self.family._force_save = True self.family.save() + + self.challenge.refresh_from_db() + self.challenge.obtained -= 1 + self.challenge._force_save = True + self.challenge.save() + + self.__class__.update_ranking() diff --git a/apps/family/tables.py b/apps/family/tables.py new file mode 100644 index 00000000..0e3ffc47 --- /dev/null +++ b/apps/family/tables.py @@ -0,0 +1,40 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +import django_tables2 as tables +from django_tables2 import A + +from .models import Family, Challenge + + +class FamilyTable(tables.Table): + """ + List all families + """ + name = tables.LinkColumn( + "family:family_detail", + args=[A("pk")], + ) + + class Meta: + attrs = { + 'class': 'table table-condensed table-striped table-hover' + } + model = Family + template_name = 'django_tables2/bootstrap4.html' + fields = ('name', 'score', 'rank',) + order_by = ('rank',) + + +class ChallengeTable(tables.Table): + """ + List all challenges + """ + class Meta: + attrs = { + 'class': 'table table-condensed table-striped table-hover' + } + order_by = ('id',) + model = Challenge + template_name = 'django_tables2/bootstrap4.html' + fields = ('name', 'points', 'category',) diff --git a/apps/family/templates/family/challenge_list.html b/apps/family/templates/family/challenge_list.html new file mode 100644 index 00000000..f16f37a7 --- /dev/null +++ b/apps/family/templates/family/challenge_list.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load render_table from django_tables2 %} +{% load i18n %} + +{% block content %} + + +<
+

+ {{ title }} +

+ {% render_table table %} +
+{% endblock %} + diff --git a/apps/family/templates/family/family_list.html b/apps/family/templates/family/family_list.html new file mode 100644 index 00000000..38738fcf --- /dev/null +++ b/apps/family/templates/family/family_list.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load render_table from django_tables2 %} +{% load i18n %} + +{% block content %} + + +<
+

+ {{ title }} +

+ {% render_table table %} +
+{% endblock %} + diff --git a/apps/family/tests.py b/apps/family/tests.py deleted file mode 100644 index 7ce503c2..00000000 --- a/apps/family/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase - -# Create your tests here. diff --git a/apps/family/urls.py b/apps/family/urls.py new file mode 100644 index 00000000..99b87d92 --- /dev/null +++ b/apps/family/urls.py @@ -0,0 +1,12 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.urls import path + +from .views import FamilyListView, ChallengeListView + +app_name = 'family' +urlpatterns = [ + path('list/', FamilyListView.as_view(), name="family_list"), + path('challenge/list/', ChallengeListView.as_view(), name="challenge_list"), +] diff --git a/apps/family/views.py b/apps/family/views.py index 91ea44a2..8d41ccac 100644 --- a/apps/family/views.py +++ b/apps/family/views.py @@ -1,3 +1,63 @@ -from django.shortcuts import render +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later -# Create your views here. +from django.contrib.auth.mixins import LoginRequiredMixin +from django.views.generic import DetailView, UpdateView +from django.utils.translation import gettext_lazy as _ +from django_tables2 import SingleTableView +from permission.views import ProtectQuerysetMixin, ProtectedCreateView + +from .models import Family, Challenge +from .tables import FamilyTable, ChallengeTable + + +class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView): + """ + Create family + """ + model = Family + extra_context = {"title": _('Create family')} + + def get_sample_object(self): + return Family( + name="", + description="Sample family", + score=0, + rank=0, + ) + + +class FamilyListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): + """ + List existing Families + """ + model = Family + table_class = FamilyTable + extra_context = {"title": _('Families list')} + + +class FamilyDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): + """ + Display details of a family + """ + model = Family + context_object_name = "family" + extra_context = {"title": _('Family detail')} + + +class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): + """ + Update the information of a family. + """ + model = Family + context_object_name = "family" + extra_context = {"title": _('Update family')} + + +class ChallengeListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): + """ + List all challenges + """ + model = Challenge + table_class = ChallengeTable + extra_context = {"title": _('Challenges list')} diff --git a/note_kfet/templates/base.html b/note_kfet/templates/base.html index 1c601c50..9301ee36 100644 --- a/note_kfet/templates/base.html +++ b/note_kfet/templates/base.html @@ -78,6 +78,13 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans 'Transfer' %} {% endif %} + {% if user.is_authenticated %} + + {% endif %} + {% if "auth.user"|model_list_length >= 2 %}
  • {% blocktrans trimmed with amount=fee|pretty_money %} Membership fees: {{ amount }} {% endblocktrans %}
  • - {% if registration.caution_type == 'note' %} -
  • {% blocktrans trimmed with amount=club.caution_amount|pretty_money %} + {% if registration.deposit_type == 'note' %} +
  • {% blocktrans trimmed with amount=club.deposit_amount|pretty_money %} Deposit (by Note transaction): {{ amount }} {% endblocktrans %}
  • {% else %} -
  • {% blocktrans trimmed with amount=club.caution_amount|pretty_money %} +
  • {% blocktrans trimmed with amount=club.deposit_amount|pretty_money %} Deposit (by check): {{ amount }} {% endblocktrans %}
  • {% endif %} @@ -167,7 +167,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endblocktrans %}

    - {% if not registration.caution_check and not registration.first_year and registration.caution_type == 'check' %} + {% if not registration.deposit_check and not registration.first_year and registration.caution_type == 'check' %}
    {% trans "The user didn't give her/his caution check." %}
    diff --git a/apps/wei/tests/test_wei_registration.py b/apps/wei/tests/test_wei_registration.py index 62e73235..185ee374 100644 --- a/apps/wei/tests/test_wei_registration.py +++ b/apps/wei/tests/test_wei_registration.py @@ -101,7 +101,7 @@ class TestWEIRegistration(TestCase): user_id=self.user.id, wei_id=self.wei.id, soge_credit=True, - caution_check=True, + deposit_check=True, birth_date=date(2000, 1, 1), gender="nonbinary", clothing_cut="male", @@ -127,7 +127,7 @@ class TestWEIRegistration(TestCase): year=self.year + 1, date_start=str(self.year + 1) + "-09-01", date_end=str(self.year + 1) + "-09-03", - caution_amount=12000, + deposit_amount=12000, )) qs = WEIClub.objects.filter(name="Create WEI Test", year=self.year + 1) self.assertTrue(qs.exists()) @@ -163,7 +163,7 @@ class TestWEIRegistration(TestCase): membership_end="2000-09-30", date_start="2000-09-01", date_end="2000-09-03", - caution_amount=12000, + deposit_amount=12000, )) qs = WEIClub.objects.filter(name="Update WEI Test", id=self.wei.id) self.assertRedirects(response, reverse("wei:wei_detail", kwargs=dict(pk=self.wei.pk)), 302, 200) @@ -322,7 +322,7 @@ class TestWEIRegistration(TestCase): bus=[], team=[], roles=[], - caution_type='check' + deposit_type='check' )) self.assertEqual(response.status_code, 200) self.assertFalse(response.context["membership_form"].is_valid()) @@ -340,7 +340,7 @@ class TestWEIRegistration(TestCase): bus=[self.bus.id], team=[self.team.id], roles=[role.id for role in WEIRole.objects.filter(~Q(name="1A") & ~Q(name="GC WEI")).all()], - caution_type='check' + deposit_type='check' )) qs = WEIRegistration.objects.filter(user_id=user.id) self.assertTrue(qs.exists()) @@ -360,7 +360,7 @@ class TestWEIRegistration(TestCase): bus=[self.bus.id], team=[self.team.id], roles=[role.id for role in WEIRole.objects.filter(~Q(name="1A")).all()], - caution_type='check' + deposit_type='check' )) self.assertEqual(response.status_code, 200) self.assertTrue("This user is already registered to this WEI." in str(response.context["form"].errors)) @@ -513,7 +513,7 @@ class TestWEIRegistration(TestCase): team=[self.team.id], roles=[role.id for role in WEIRole.objects.filter(name="Adhérent⋅e WEI").all()], information_json=self.registration.information_json, - caution_type='check' + deposit_type='check' ) ) qs = WEIRegistration.objects.filter(user_id=self.user.id, soge_credit=False, clothing_size="M") @@ -568,7 +568,7 @@ class TestWEIRegistration(TestCase): team=[self.team.id], roles=[role.id for role in WEIRole.objects.filter(name="Adhérent⋅e WEI").all()], information_json=self.registration.information_json, - caution_type='check' + deposit_type='check' ) ) qs = WEIRegistration.objects.filter(user_id=self.user.id, clothing_size="L") @@ -592,7 +592,7 @@ class TestWEIRegistration(TestCase): team=[], roles=[], information_json=self.registration.information_json, - caution_type='check' + deposit_type='check' ) ) self.assertFalse(response.context["membership_form"].is_valid()) @@ -642,7 +642,7 @@ class TestWEIRegistration(TestCase): last_name="admin", first_name="admin", bank="Société générale", - caution_check=True, + deposit_check=True, )) self.assertEqual(response.status_code, 200) self.assertFalse(response.context["form"].is_valid()) @@ -657,7 +657,7 @@ class TestWEIRegistration(TestCase): last_name="admin", first_name="admin", bank="Société générale", - caution_check=True, + deposit_check=True, )) self.assertRedirects(response, reverse("wei:wei_registrations", kwargs=dict(pk=self.registration.wei.pk)), 302, 200) @@ -813,7 +813,7 @@ class TestWeiAPI(TestAPI): user_id=self.user.id, wei_id=self.wei.id, soge_credit=True, - caution_check=True, + deposit_check=True, birth_date=date(2000, 1, 1), gender="nonbinary", clothing_cut="male", diff --git a/apps/wei/views.py b/apps/wei/views.py index dbfd1774..4ac679f9 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -560,12 +560,12 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView): # Cacher les champs pendant l'inscription initiale if "first_year" in form.fields: del form.fields["first_year"] - if "caution_check" in form.fields: - del form.fields["caution_check"] + if "deposit_check" in form.fields: + del form.fields["deposit_check"] if "information_json" in form.fields: del form.fields["information_json"] - if "caution_type" in form.fields: - del form.fields["caution_type"] + if "deposit_type" in form.fields: + del form.fields["deposit_type"] if "soge_credit" in form.fields: form.fields["soge_credit"].help_text = _('Check if you will open a Société Générale account') @@ -670,16 +670,16 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView): # Cacher les champs pendant l'inscription initiale if "first_year" in form.fields: del form.fields["first_year"] - if "caution_check" in form.fields: - del form.fields["caution_check"] + if "deposit_check" in form.fields: + del form.fields["deposit_check"] if "information_json" in form.fields: del form.fields["information_json"] - # S'assurer que le champ caution_type est obligatoire - if "caution_type" in form.fields: - form.fields["caution_type"].required = True - form.fields["caution_type"].help_text = _("Choose how you want to pay the deposit") - form.fields["caution_type"].widget = forms.RadioSelect(choices=form.fields["caution_type"].choices) + # S'assurer que le champ deposit_type est obligatoire + if "deposit_type" in form.fields: + form.fields["deposit_type"].required = True + form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit") + form.fields["deposit_type"].widget = forms.RadioSelect(choices=form.fields["deposit_type"].choices) return form @@ -708,7 +708,7 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView): form.instance.information = information # Sauvegarder le type de caution - form.instance.caution_type = form.cleaned_data["caution_type"] + form.instance.deposit_type = form.cleaned_data["deposit_type"] form.instance.save() if 'treasury' in settings.INSTALLED_APPS: @@ -780,15 +780,15 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update # The auto-json-format may cause issues with the default field remove if "information_json" in form.fields: del form.fields["information_json"] - # Masquer le champ caution_check pour tout le monde dans le formulaire de modification - if "caution_check" in form.fields: - del form.fields["caution_check"] + # Masquer le champ deposit_check pour tout le monde dans le formulaire de modification + if "deposit_check" in form.fields: + del form.fields["deposit_check"] - # S'assurer que le champ caution_type est obligatoire pour les 2A+ - if not self.object.first_year and "caution_type" in form.fields: - form.fields["caution_type"].required = True - form.fields["caution_type"].help_text = _("Choose how you want to pay the deposit") - form.fields["caution_type"].widget = forms.RadioSelect(choices=form.fields["caution_type"].choices) + # S'assurer que le champ deposit_type est obligatoire pour les 2A+ + if not self.object.first_year and "deposit_type" in form.fields: + form.fields["deposit_type"].required = True + form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit") + form.fields["deposit_type"].widget = forms.RadioSelect(choices=form.fields["deposit_type"].choices) return form @@ -850,8 +850,8 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update form.instance.information = information # Sauvegarder le type de caution pour les 2A+ - if "caution_type" in form.cleaned_data: - form.instance.caution_type = form.cleaned_data["caution_type"] + if "deposit_type" in form.cleaned_data: + form.instance.deposit_type = form.cleaned_data["deposit_type"] form.instance.save() return super().form_valid(form) @@ -957,8 +957,8 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): # Calculer le montant total nécessaire (frais + caution si transaction) total_needed = fee - if registration.caution_type == 'note': - total_needed += registration.wei.caution_amount + if registration.deposit_type == 'note': + total_needed += registration.wei.deposit_amount context["total_needed"] = total_needed form = context["form"] @@ -988,22 +988,22 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): form.fields["last_name"].initial = registration.user.last_name form.fields["first_name"].initial = registration.user.first_name - # Ajouter le champ caution_check uniquement pour les non-première année et le rendre obligatoire + # Ajouter le champ deposit_check uniquement pour les non-première année et le rendre obligatoire if not registration.first_year: - if registration.caution_type == 'check': - form.fields["caution_check"] = forms.BooleanField( + if registration.deposit_type == 'check': + form.fields["deposit_check"] = forms.BooleanField( required=True, - initial=registration.caution_check, - label=_("Caution check given"), + initial=registration.deposit_check, + label=_("Deposit check given"), help_text=_("Please make sure the check is given before validating the registration") ) else: - form.fields["caution_check"] = forms.BooleanField( + form.fields["deposit_check"] = forms.BooleanField( required=True, initial=False, label=_("Create deposit transaction"), help_text=_("A transaction of %(amount).2f€ will be created from the user's Note account") % { - 'amount': registration.wei.caution_amount / 100 + 'amount': registration.wei.deposit_amount / 100 } ) @@ -1039,8 +1039,8 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): club = registration.wei user = registration.user - if "caution_check" in form.data: - registration.caution_check = form.data["caution_check"] == "on" + if "deposit_check" in form.data: + registration.deposit_check = form.data["deposit_check"] == "on" registration.save() membership = form.instance membership.user = user @@ -1084,8 +1084,8 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): # Calculer le montant total nécessaire (frais + caution si transaction) total_needed = fee - if registration.caution_type == 'note': - total_needed += club.caution_amount + if registration.deposit_type == 'note': + total_needed += club.deposit_amount # Vérifier que l'utilisateur a assez d'argent pour tout payer if user.note.balance + credit_amount < total_needed: @@ -1136,14 +1136,14 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): membership.roles.add(WEIRole.objects.get(name="Adhérent⋅e WEI")) # Créer la transaction de caution si nécessaire - if registration.caution_type == 'note': + if registration.deposit_type == 'note': from note.models import Transaction Transaction.objects.create( source=user.note, destination=club.note, quantity=1, - amount=club.caution_amount, - reason=_("Caution %(name)s") % {'name': club.name}, + amount=club.deposit_amount, + reason=_("Deposit %(name)s") % {'name': club.name}, valid=True, ) From a273dc3eef24497a86cd422cca02d508e619367a Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Tue, 15 Jul 2025 18:23:40 +0200 Subject: [PATCH 069/153] Translations --- apps/wei/forms/registration.py | 1 + apps/wei/models.py | 2 +- locale/de/LC_MESSAGES/django.po | 2201 ++++++++----------------------- locale/es/LC_MESSAGES/django.po | 281 ++-- locale/fr/LC_MESSAGES/django.po | 268 ++-- 5 files changed, 867 insertions(+), 1886 deletions(-) diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index 7dd40558..35838c2b 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -25,6 +25,7 @@ class WEIForm(forms.ModelForm): "date_start": DatePickerInput(), "date_end": DatePickerInput(), "deposit_amount": AmountInput(), + "fee_soge_credit": AmountInput(), } diff --git a/apps/wei/models.py b/apps/wei/models.py index 2c2a0635..59f018d7 100644 --- a/apps/wei/models.py +++ b/apps/wei/models.py @@ -39,7 +39,7 @@ class WEIClub(Club): ) fee_soge_credit = models.PositiveIntegerField( - verbose_name=_("fee soge credit"), + verbose_name=_("membership fee (soge credit)"), default=2000, ) diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index f4698e23..7ca27bd5 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-06-02 00:58+0200\n" +"POT-Creation-Date: 2025-07-15 18:18+0200\n" "PO-Revision-Date: 2020-11-16 20:02+0000\n" "Last-Translator: bleizi \n" "Language-Team: German \n" @@ -66,7 +66,7 @@ msgstr "Sie dürfen höchstens 3 Leute zu dieser Veranstaltung einladen." #: apps/note/models/transactions.py:46 apps/note/models/transactions.py:299 #: apps/permission/models.py:329 #: apps/registration/templates/registration/future_profile_detail.html:16 -#: apps/wei/models.py:72 apps/wei/models.py:145 apps/wei/tables.py:282 +#: apps/wei/models.py:77 apps/wei/models.py:150 apps/wei/tables.py:282 #: apps/wei/templates/wei/base.html:26 #: apps/wei/templates/wei/weimembership_form.html:14 apps/wrapped/models.py:16 msgid "name" @@ -101,7 +101,7 @@ msgstr "Vearnstaltungarte" #: apps/activity/models.py:68 #: apps/activity/templates/activity/includes/activity_info.html:19 #: apps/note/models/transactions.py:82 apps/permission/models.py:109 -#: apps/permission/models.py:188 apps/wei/models.py:92 apps/wei/models.py:156 +#: apps/permission/models.py:188 apps/wei/models.py:97 apps/wei/models.py:161 msgid "description" msgstr "Beschreibung" @@ -122,7 +122,7 @@ msgstr "Type" #: 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:294 -#: apps/wei/models.py:185 apps/wei/templates/wei/attribute_bus_1A.html:13 +#: apps/wei/models.py:190 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/templates/wei/survey.html:15 msgid "user" msgstr "User" @@ -295,14 +295,14 @@ msgstr "Type" #: 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:116 +#: apps/wei/forms/registration.py:129 msgid "Last name" msgstr "Nachname" #: 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:121 +#: apps/wei/forms/registration.py:134 msgid "First name" msgstr "Vorname" @@ -321,10 +321,6 @@ msgstr "Kontostand" #: 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 -#: env/lib/python3.11/site-packages/django/forms/formsets.py:499 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:13 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:38 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:7 #: 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 @@ -831,8 +827,6 @@ msgid "weeks" msgstr "" #: apps/food/utils.py:53 -#: env/lib/python3.11/site-packages/django/db/models/base.py:1423 -#: env/lib/python3.11/site-packages/django/forms/models.py:893 #, fuzzy #| msgid "add" msgid "and" @@ -887,12 +881,10 @@ msgid "Details of:" msgstr "WEI Infos" #: apps/food/views.py:447 apps/treasury/tables.py:149 -#: env/lib/python3.11/site-packages/django/forms/widgets.py:795 msgid "Yes" msgstr "Ja" #: apps/food/views.py:449 apps/member/models.py:99 apps/treasury/tables.py:149 -#: env/lib/python3.11/site-packages/django/forms/widgets.py:796 msgid "No" msgstr "Nein" @@ -1030,12 +1022,12 @@ msgid "Check this case if the Société Générale paid the inscription." msgstr "Die Société Générale die Mitgliedschaft bezahlt." #: apps/member/forms.py:185 apps/registration/forms.py:78 -#: apps/wei/forms/registration.py:103 +#: apps/wei/forms/registration.py:116 msgid "Credit type" msgstr "Kredittype" #: apps/member/forms.py:186 apps/registration/forms.py:79 -#: apps/wei/forms/registration.py:104 +#: apps/wei/forms/registration.py:117 msgid "No credit" msgstr "Kein Kredit" @@ -1044,13 +1036,13 @@ msgid "You can credit the note of the user." msgstr "Sie dûrfen diese Note kreditieren." #: apps/member/forms.py:192 apps/registration/forms.py:84 -#: apps/wei/forms/registration.py:109 +#: apps/wei/forms/registration.py:122 msgid "Credit amount" msgstr "Kreditanzahl" #: 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:126 +#: apps/wei/forms/registration.py:139 msgid "Bank" msgstr "Bank" @@ -1311,7 +1303,7 @@ msgid "add to registration form" msgstr "Registrierung validieren" #: apps/member/models.py:268 apps/member/models.py:331 -#: apps/note/models/notes.py:176 apps/wei/models.py:86 +#: apps/note/models/notes.py:176 apps/wei/models.py:91 msgid "club" msgstr "Club" @@ -2038,9 +2030,8 @@ msgstr "" "Zahlungsmethode zugeordnet ist, und einem User oder einem Club möglich" #: apps/note/models/transactions.py:357 apps/note/models/transactions.py:360 -#: apps/note/models/transactions.py:363 apps/wei/views.py:1097 -#: apps/wei/views.py:1101 -#: env/lib/python3.11/site-packages/django/forms/fields.py:91 +#: apps/note/models/transactions.py:363 apps/wei/views.py:1103 +#: apps/wei/views.py:1107 msgid "This field is required." msgstr "Dies ist ein Pflichtfeld." @@ -2080,7 +2071,6 @@ msgstr "Neue Bus" #: apps/wei/templates/wei/bus_detail.html:24 #: apps/wei/templates/wei/busteam_detail.html:20 #: apps/wei/templates/wei/busteam_detail.html:42 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:37 #: note_kfet/templates/oauth2_provider/application_detail.html:38 msgid "Edit" msgstr "Bearbeiten" @@ -2421,19 +2411,16 @@ msgid "Available scopes" msgstr "" #: apps/permission/templates/permission/scopes.html:42 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:24 msgid "No applications defined" msgstr "" #: apps/permission/templates/permission/scopes.html:43 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:25 msgid "Click here" msgstr "" #: apps/permission/templates/permission/scopes.html:43 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:17 #: note_kfet/templates/oauth2_provider/application_list.html:25 msgid "if you want to register a new one" msgstr "" @@ -2552,7 +2539,7 @@ msgstr "Sie haben bereits ein Konto in der Société générale eröffnet." #: apps/registration/templates/registration/future_profile_detail.html:73 #: apps/wei/templates/wei/weimembership_form.html:127 -#: apps/wei/templates/wei/weimembership_form.html:196 +#: apps/wei/templates/wei/weimembership_form.html:192 msgid "Validate registration" msgstr "Registrierung validieren" @@ -3089,8 +3076,8 @@ msgstr "Kreditliste von Société générale" msgid "Manage credits from the Société générale" msgstr "Krediten von der Société générale handeln" -#: apps/wei/apps.py:10 apps/wei/models.py:42 apps/wei/models.py:43 -#: apps/wei/models.py:67 apps/wei/models.py:192 +#: apps/wei/apps.py:10 apps/wei/models.py:47 apps/wei/models.py:48 +#: apps/wei/models.py:72 apps/wei/models.py:197 #: note_kfet/templates/base.html:108 msgid "WEI" msgstr "WEI" @@ -3099,12 +3086,12 @@ msgstr "WEI" msgid "The selected user is not validated. Please validate its account first" msgstr "" -#: apps/wei/forms/registration.py:71 apps/wei/models.py:140 -#: apps/wei/models.py:348 +#: apps/wei/forms/registration.py:84 apps/wei/models.py:145 +#: apps/wei/models.py:354 msgid "bus" msgstr "Bus" -#: apps/wei/forms/registration.py:72 +#: apps/wei/forms/registration.py:85 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." @@ -3113,11 +3100,11 @@ msgstr "" "einen Bus und ein Team zuzuweisen, insbesondere wenn Sie ein freies Elektron " "sind." -#: apps/wei/forms/registration.py:79 +#: apps/wei/forms/registration.py:92 msgid "Team" msgstr "Team" -#: apps/wei/forms/registration.py:81 +#: apps/wei/forms/registration.py:94 msgid "" "Leave this field empty if you won't be in a team (staff, bus chief, free " "electron)" @@ -3125,23 +3112,28 @@ msgstr "" "Lassen Sie dieses Feld leer, wenn Sie nicht in einem Team sind (Mitarbeiter, " "Buschef, freies Elektron)" -#: apps/wei/forms/registration.py:87 apps/wei/forms/registration.py:97 -#: apps/wei/models.py:174 +#: apps/wei/forms/registration.py:100 apps/wei/forms/registration.py:110 +#: apps/wei/models.py:179 msgid "WEI Roles" msgstr "WEI Rollen" -#: apps/wei/forms/registration.py:88 +#: apps/wei/forms/registration.py:101 msgid "Select the roles that you are interested in." msgstr "Wählen Sie die Rollen aus, an denen Sie interessiert sind." -#: apps/wei/forms/registration.py:134 +#: apps/wei/forms/registration.py:160 msgid "This team doesn't belong to the given bus." msgstr "Dieses Team gehört nicht zum angegebenen Bus." #: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:38 +#: apps/wei/forms/surveys/wei2025.py:36 msgid "Choose a word:" msgstr "Wählen Sie ein Wort:" +#: apps/wei/forms/surveys/wei2025.py:123 +msgid "Rate between 0 and 5." +msgstr "" + #: apps/wei/models.py:25 apps/wei/templates/wei/base.html:36 msgid "year" msgstr "Jahr" @@ -3158,138 +3150,147 @@ msgstr "Abschlussdatum" #: apps/wei/models.py:37 #, fuzzy -#| msgid "total amount" -msgid "caution amount" -msgstr "Totalanzahlt" +#| msgid "Credit amount" +msgid "deposit amount" +msgstr "Kreditanzahl" -#: apps/wei/models.py:76 apps/wei/tables.py:305 +#: apps/wei/models.py:42 +#, fuzzy +#| msgid "No credit" +msgid "membership fee (soge credit)" +msgstr "Kein Kredit" + +#: apps/wei/models.py:81 apps/wei/tables.py:305 #, fuzzy #| msgid "The user joined the bus" msgid "seat count in the bus" msgstr "Der Benutzer ist dem Bus beigetreten" -#: apps/wei/models.py:97 +#: apps/wei/models.py:102 msgid "survey information" msgstr "Umfrage Infos" -#: apps/wei/models.py:98 +#: apps/wei/models.py:103 msgid "Information about the survey for new members, encoded in JSON" msgstr "Informationen zur Umfrage für neue Mitglieder, codiert in JSON" -#: apps/wei/models.py:102 +#: apps/wei/models.py:107 msgid "Bus" msgstr "Bus" -#: apps/wei/models.py:103 apps/wei/templates/wei/weiclub_detail.html:51 +#: apps/wei/models.py:108 apps/wei/templates/wei/weiclub_detail.html:51 msgid "Buses" msgstr "Buses" -#: apps/wei/models.py:149 +#: apps/wei/models.py:154 msgid "color" msgstr "Farbe" -#: apps/wei/models.py:150 +#: apps/wei/models.py:155 msgid "The color of the T-Shirt, stored with its number equivalent" msgstr "Die Farbe des T-Shirts, gespeichert mit der entsprechenden Nummer" -#: apps/wei/models.py:161 +#: apps/wei/models.py:166 msgid "Bus team" msgstr "Bus Team" -#: apps/wei/models.py:162 +#: apps/wei/models.py:167 msgid "Bus teams" msgstr "Bus Teams" -#: apps/wei/models.py:173 +#: apps/wei/models.py:178 msgid "WEI Role" msgstr "WEI Rolle" -#: apps/wei/models.py:197 +#: apps/wei/models.py:202 msgid "Credit from Société générale" msgstr "Kredit von der Société générale" -#: apps/wei/models.py:202 apps/wei/views.py:984 -msgid "Caution check given" +#: apps/wei/models.py:207 apps/wei/templates/wei/weimembership_form.html:98 +#: apps/wei/views.py:997 +#, fuzzy +#| msgid "Caution check given" +msgid "Deposit check given" msgstr "Caution check given" -#: apps/wei/models.py:208 +#: apps/wei/models.py:213 msgid "Check" msgstr "" -#: apps/wei/models.py:209 +#: apps/wei/models.py:214 #, fuzzy #| msgid "transactions" msgid "Note transaction" msgstr "Transaktionen" -#: apps/wei/models.py:212 +#: apps/wei/models.py:217 #, fuzzy -#| msgid "created at" -msgid "caution type" -msgstr "erschafft am" +#| msgid "Credit type" +msgid "deposit type" +msgstr "Kredittype" -#: apps/wei/models.py:216 apps/wei/templates/wei/weimembership_form.html:64 +#: apps/wei/models.py:221 apps/wei/templates/wei/weimembership_form.html:64 msgid "birth date" msgstr "Geburtsdatum" -#: apps/wei/models.py:222 apps/wei/models.py:232 +#: apps/wei/models.py:227 apps/wei/models.py:237 msgid "Male" msgstr "Männlich" -#: apps/wei/models.py:223 apps/wei/models.py:233 +#: apps/wei/models.py:228 apps/wei/models.py:238 msgid "Female" msgstr "Weiblich" -#: apps/wei/models.py:224 +#: apps/wei/models.py:229 msgid "Non binary" msgstr "Nicht binär" -#: apps/wei/models.py:226 apps/wei/templates/wei/attribute_bus_1A.html:22 +#: apps/wei/models.py:231 apps/wei/templates/wei/attribute_bus_1A.html:22 #: apps/wei/templates/wei/weimembership_form.html:55 msgid "gender" msgstr "Geschlecht" -#: apps/wei/models.py:234 +#: apps/wei/models.py:239 msgid "Unisex" msgstr "Unisex" -#: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:58 +#: apps/wei/models.py:242 apps/wei/templates/wei/weimembership_form.html:58 msgid "clothing cut" msgstr "Kleidung Schnitt" -#: apps/wei/models.py:250 apps/wei/templates/wei/weimembership_form.html:61 +#: apps/wei/models.py:255 apps/wei/templates/wei/weimembership_form.html:61 msgid "clothing size" msgstr "Kleidergröße" -#: apps/wei/models.py:256 +#: apps/wei/models.py:261 msgid "health issues" msgstr "Gesundheitsprobleme" -#: apps/wei/models.py:261 apps/wei/templates/wei/weimembership_form.html:70 +#: apps/wei/models.py:266 apps/wei/templates/wei/weimembership_form.html:70 msgid "emergency contact name" msgstr "Notfall-Kontakt" -#: apps/wei/models.py:262 +#: apps/wei/models.py:267 msgid "The emergency contact must not be a WEI participant" msgstr "Der Notfallkontakt darf kein WEI-Teilnehmer sein" -#: apps/wei/models.py:267 apps/wei/templates/wei/weimembership_form.html:73 +#: apps/wei/models.py:272 apps/wei/templates/wei/weimembership_form.html:73 msgid "emergency contact phone" msgstr "Notfallkontakttelefon" -#: apps/wei/models.py:272 apps/wei/templates/wei/weimembership_form.html:52 +#: apps/wei/models.py:277 apps/wei/templates/wei/weimembership_form.html:52 msgid "first year" msgstr "Erste Jahr" -#: apps/wei/models.py:273 +#: apps/wei/models.py:278 msgid "Tells if the user is new in the school." msgstr "Gibt an, ob der USer neu in der Schule ist." -#: apps/wei/models.py:278 +#: apps/wei/models.py:283 msgid "registration information" msgstr "Registrierung Detailen" -#: apps/wei/models.py:279 +#: apps/wei/models.py:284 msgid "" "Information about the registration (buses for old members, survey for the " "new members), encoded in JSON" @@ -3297,27 +3298,27 @@ msgstr "" "Informationen zur Registrierung (Busse für alte Mitglieder, Umfrage für neue " "Mitglieder), verschlüsselt in JSON" -#: apps/wei/models.py:285 +#: apps/wei/models.py:290 msgid "WEI User" msgstr "WEI User" -#: apps/wei/models.py:286 +#: apps/wei/models.py:291 msgid "WEI Users" msgstr "WEI Users" -#: apps/wei/models.py:358 +#: apps/wei/models.py:364 msgid "team" msgstr "Team" -#: apps/wei/models.py:368 +#: apps/wei/models.py:374 msgid "WEI registration" msgstr "WEI Registrierung" -#: apps/wei/models.py:372 +#: apps/wei/models.py:378 msgid "WEI membership" msgstr "WEI Mitgliedschaft" -#: apps/wei/models.py:373 +#: apps/wei/models.py:379 msgid "WEI memberships" msgstr "WEI Mitgliedschaften" @@ -3349,7 +3350,7 @@ msgstr "Jahr" msgid "preferred bus" msgstr "bevorzugter Bus" -#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:36 +#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:38 #: apps/wei/templates/wei/busteam_detail.html:52 msgid "Teams" msgstr "Teams" @@ -3429,9 +3430,9 @@ msgstr "WEI Preis (unbezahlte Studenten)" #: apps/wei/templates/wei/base.html:53 #, fuzzy -#| msgid "total amount" -msgid "Caution amount" -msgstr "Totalanzahlt" +#| msgid "Credit amount" +msgid "Deposit amount" +msgstr "Kreditanzahl" #: apps/wei/templates/wei/base.html:74 msgid "WEI list" @@ -3441,7 +3442,7 @@ msgstr "WEI Liste" msgid "Register 1A" msgstr "1A Registrieren" -#: apps/wei/templates/wei/base.html:83 apps/wei/views.py:644 +#: apps/wei/templates/wei/base.html:83 apps/wei/views.py:646 msgid "Register 2A+" msgstr "2A+ Registrieren" @@ -3460,15 +3461,21 @@ msgid "View club" msgstr "Club" #: apps/wei/templates/wei/bus_detail.html:26 +#, fuzzy +#| msgid "survey information" +msgid "Edit information" +msgstr "Umfrage Infos" + +#: apps/wei/templates/wei/bus_detail.html:28 #: apps/wei/templates/wei/busteam_detail.html:24 msgid "Add team" msgstr "Neue Team" -#: apps/wei/templates/wei/bus_detail.html:49 +#: apps/wei/templates/wei/bus_detail.html:51 msgid "Members" msgstr "Mitglied" -#: apps/wei/templates/wei/bus_detail.html:58 +#: apps/wei/templates/wei/bus_detail.html:60 #: apps/wei/templates/wei/busteam_detail.html:62 #: apps/wei/templates/wei/weimembership_list.html:31 msgid "View as PDF" @@ -3476,8 +3483,8 @@ msgstr "Als PDF schauen" #: 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:1159 -#: apps/wei/views.py:1214 apps/wei/views.py:1261 +#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1165 +#: apps/wei/views.py:1220 apps/wei/views.py:1267 msgid "Survey WEI" msgstr "WEI Umfrage" @@ -3553,10 +3560,6 @@ msgstr "Rohe Umfrageinformationen" msgid "The algorithm didn't run." msgstr "Der Algorithmus wurde nicht ausgeführt." -#: apps/wei/templates/wei/weimembership_form.html:98 -msgid "caution check given" -msgstr "Vorsichtsprüfung gegeben" - #: apps/wei/templates/wei/weimembership_form.html:105 msgid "preferred team" msgstr "bevorzugtes Team" @@ -3593,11 +3596,18 @@ msgid "with the following roles:" msgstr "mit den folgenden Rollen:" #: apps/wei/templates/wei/weimembership_form.html:139 +#, fuzzy +#| msgid "" +#| "The WEI will be paid by Société générale. The membership will be created " +#| "even if the bank didn't pay the BDE yet. The membership transaction will " +#| "be created but will be invalid. You will have to validate it once the " +#| "bank validated the creation of the account, or to change the payment " +#| "method." msgid "" -"The WEI will be paid by Société générale. The membership will be created " -"even if the bank didn't pay the BDE yet. The membership transaction will be " -"created but will be invalid. You will have to validate it once the bank " -"validated the creation of the account, or to change the payment method." +"The WEI will partially be paid by Société générale. The membership will be " +"created even if the bank didn't pay the BDE yet. The membership transaction " +"will be created but will be invalid. You will have to validate it once the " +"bank validated the creation of the account, or to change the payment method." msgstr "" "Das WEI wird von der Société générale bezahlt. Die Mitgliedschaft wird auch " "dann erstellt, wenn die Bank die BDE noch nicht bezahlt hat. Die " @@ -3620,27 +3630,26 @@ msgstr "Mitgliedschaftpreis (bezahlte Studenten)" msgid "Deposit (by Note transaction): %(amount)s" msgstr "" -#: apps/wei/templates/wei/weimembership_form.html:156 -#: apps/wei/templates/wei/weimembership_form.html:163 -#, python-format -msgid "Total needed: %(total)s" -msgstr "" - -#: apps/wei/templates/wei/weimembership_form.html:160 +#: apps/wei/templates/wei/weimembership_form.html:157 #, python-format msgid "Deposit (by check): %(amount)s" msgstr "" -#: apps/wei/templates/wei/weimembership_form.html:168 +#: apps/wei/templates/wei/weimembership_form.html:161 +#, python-format +msgid "Total needed: %(total)s" +msgstr "" + +#: apps/wei/templates/wei/weimembership_form.html:165 #, python-format msgid "Current balance: %(balance)s" msgstr "" -#: apps/wei/templates/wei/weimembership_form.html:176 +#: apps/wei/templates/wei/weimembership_form.html:172 msgid "The user didn't give her/his caution check." msgstr "Der User hat nicht sein Vorsichtsprüfung gegeben." -#: apps/wei/templates/wei/weimembership_form.html:184 +#: apps/wei/templates/wei/weimembership_form.html:180 msgid "" "This user is not a member of the Kfet club for the coming year. The " "membership will be processed automatically, the WEI registration includes " @@ -3732,11 +3741,17 @@ msgstr "WEI Team bearbeiten" msgid "Register first year student to the WEI" msgstr "Registrieren Sie den Erstsemester beim WEI" -#: apps/wei/views.py:580 apps/wei/views.py:689 +#: apps/wei/views.py:571 apps/wei/views.py:664 +#, fuzzy +#| msgid "Check this case if the Société Générale paid the inscription." +msgid "Check if you will open a Société Générale account" +msgstr "Die Société Générale die Mitgliedschaft bezahlt." + +#: apps/wei/views.py:582 apps/wei/views.py:694 msgid "This user is already registered to this WEI." msgstr "Dieser Benutzer ist bereits bei dieser WEI registriert." -#: apps/wei/views.py:585 +#: apps/wei/views.py:587 msgid "" "This user can't be in her/his first year since he/she has already " "participated to a WEI." @@ -3744,29 +3759,29 @@ msgstr "" "Dieser Benutzer kann nicht in seinem ersten Jahr sein, da er bereits an " "einer WEI teilgenommen hat." -#: apps/wei/views.py:608 +#: apps/wei/views.py:610 msgid "Register old student to the WEI" msgstr "Registrieren Sie einen alten Studenten beim WEI" -#: apps/wei/views.py:663 apps/wei/views.py:768 +#: apps/wei/views.py:668 apps/wei/views.py:773 msgid "You already opened an account in the Société générale." msgstr "Sie haben bereits ein Konto in der Société générale eröffnet." -#: apps/wei/views.py:676 apps/wei/views.py:785 +#: apps/wei/views.py:681 apps/wei/views.py:790 msgid "Choose how you want to pay the deposit" msgstr "" -#: apps/wei/views.py:728 +#: apps/wei/views.py:733 msgid "Update WEI Registration" msgstr "WEI Registrierung aktualisieren" -#: apps/wei/views.py:810 +#: apps/wei/views.py:816 #, fuzzy #| msgid "The BDE membership is included in the WEI registration." msgid "No membership found for this registration" msgstr "Die BDE-Mitgliedschaft ist in der WEI-Registrierung enthalten." -#: apps/wei/views.py:819 +#: apps/wei/views.py:825 #, fuzzy #| msgid "" #| "You don't have the permission to add an instance of model {app_label}." @@ -3776,7 +3791,7 @@ msgstr "" "Sie haben nicht die Berechtigung, eine Instanz von model {app_label}. " "{model_name} hinzufügen." -#: apps/wei/views.py:825 +#: apps/wei/views.py:831 #, fuzzy, python-format #| msgid "" #| "You don't have the permission to delete this instance of model " @@ -3786,19 +3801,19 @@ msgstr "" "Sie haben nicht die Berechtigung, eine Instanz von model {app_label}. " "{model_name} zulöschen." -#: apps/wei/views.py:870 +#: apps/wei/views.py:876 msgid "Delete WEI registration" msgstr "WEI Registrierung löschen" -#: apps/wei/views.py:881 +#: apps/wei/views.py:887 msgid "You don't have the right to delete this WEI registration." msgstr "Sie haben nicht das Recht, diese WEI-Registrierung zu löschen." -#: apps/wei/views.py:899 +#: apps/wei/views.py:905 msgid "Validate WEI registration" msgstr "Überprüfen Sie die WEI-Registrierung" -#: apps/wei/views.py:985 +#: apps/wei/views.py:998 #, fuzzy #| msgid "Please ask the user to credit its note before deleting this credit." msgid "Please make sure the check is given before validating the registration" @@ -3806,19 +3821,19 @@ msgstr "" "Bitte bitten Sie den Benutzer, seine Note gutzuschreiben, bevor Sie diese " "Kredit löschen." -#: apps/wei/views.py:991 +#: apps/wei/views.py:1004 #, fuzzy #| msgid "credit transaction" msgid "Create deposit transaction" msgstr "Kredit Transaktion" -#: apps/wei/views.py:992 +#: apps/wei/views.py:1005 #, python-format msgid "" "A transaction of %(amount).2f€ will be created from the user's Note account" msgstr "" -#: apps/wei/views.py:1087 +#: apps/wei/views.py:1093 #, fuzzy, python-format #| msgid "" #| "This user don't have enough money to join this club, and can't have a " @@ -3830,21 +3845,21 @@ msgstr "" "Diese User hat nicht genug Geld um Mitglied zu werden, und darf nich im Rot " "sein." -#: apps/wei/views.py:1140 +#: apps/wei/views.py:1146 #, fuzzy, python-format #| msgid "created at" -msgid "Caution %(name)s" +msgid "Deposit %(name)s" msgstr "erschafft am" -#: apps/wei/views.py:1354 +#: apps/wei/views.py:1360 msgid "Attribute buses to first year members" msgstr "" -#: apps/wei/views.py:1379 +#: apps/wei/views.py:1386 msgid "Attribute bus" msgstr "" -#: apps/wei/views.py:1419 +#: apps/wei/views.py:1426 msgid "" "No first year student without a bus found. Either all of them have a bus, or " "none has filled the survey yet." @@ -4039,1511 +4054,6 @@ msgstr "" msgid "List of wrapped" msgstr "" -#: env/lib/python3.11/site-packages/django/contrib/messages/apps.py:15 -msgid "Messages" -msgstr "" - -#: env/lib/python3.11/site-packages/django/contrib/sitemaps/apps.py:8 -msgid "Site Maps" -msgstr "" - -#: env/lib/python3.11/site-packages/django/contrib/staticfiles/apps.py:9 -msgid "Static Files" -msgstr "" - -#: env/lib/python3.11/site-packages/django/contrib/syndication/apps.py:7 -#, fuzzy -#| msgid "Invitation" -msgid "Syndication" -msgstr "Einladung" - -#. Translators: String used to replace omitted page numbers in elided page -#. range generated by paginators, e.g. [1, 2, '…', 5, 6, 7, '…', 9, 10]. -#: env/lib/python3.11/site-packages/django/core/paginator.py:30 -msgid "…" -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/paginator.py:50 -msgid "That page number is not an integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/paginator.py:52 -msgid "That page number is less than 1" -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/paginator.py:54 -#, fuzzy -#| msgid "There is no results." -msgid "That page contains no results" -msgstr "Es gibt keine Ergebnisse." - -#: env/lib/python3.11/site-packages/django/core/validators.py:22 -#, fuzzy -#| msgid "Email validation" -msgid "Enter a valid value." -msgstr "Email validierung" - -#: env/lib/python3.11/site-packages/django/core/validators.py:104 -#: env/lib/python3.11/site-packages/django/forms/fields.py:752 -#, fuzzy -#| msgid "Email validation" -msgid "Enter a valid URL." -msgstr "Email validierung" - -#: env/lib/python3.11/site-packages/django/core/validators.py:165 -#, fuzzy -#| msgid "Email validation" -msgid "Enter a valid integer." -msgstr "Email validierung" - -#: env/lib/python3.11/site-packages/django/core/validators.py:176 -#, fuzzy -#| msgid "Email validation" -msgid "Enter a valid email address." -msgstr "Email validierung" - -#. Translators: "letters" means latin letters: a-z and A-Z. -#: env/lib/python3.11/site-packages/django/core/validators.py:259 -msgid "" -"Enter a valid “slug” consisting of letters, numbers, underscores or hyphens." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:267 -msgid "" -"Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or " -"hyphens." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:281 -#: env/lib/python3.11/site-packages/django/core/validators.py:289 -#: env/lib/python3.11/site-packages/django/core/validators.py:318 -#, fuzzy -#| msgid "This activity is not validated yet." -msgid "Enter a valid IPv4 address." -msgstr "Diese Veranstaltung ist noch nicht bestätigt." - -#: env/lib/python3.11/site-packages/django/core/validators.py:298 -#: env/lib/python3.11/site-packages/django/core/validators.py:319 -#, fuzzy -#| msgid "This activity is not validated yet." -msgid "Enter a valid IPv6 address." -msgstr "Diese Veranstaltung ist noch nicht bestätigt." - -#: env/lib/python3.11/site-packages/django/core/validators.py:310 -#: env/lib/python3.11/site-packages/django/core/validators.py:317 -#, fuzzy -#| msgid "This activity is not validated yet." -msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Diese Veranstaltung ist noch nicht bestätigt." - -#: env/lib/python3.11/site-packages/django/core/validators.py:353 -msgid "Enter only digits separated by commas." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:359 -#, python-format -msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:394 -#, python-format -msgid "Ensure this value is less than or equal to %(limit_value)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:403 -#, python-format -msgid "Ensure this value is greater than or equal to %(limit_value)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:412 -#, python-format -msgid "Ensure this value is a multiple of step size %(limit_value)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:422 -#, python-format -msgid "" -"Ensure this value has at least %(limit_value)d character (it has " -"%(show_value)d)." -msgid_plural "" -"Ensure this value has at least %(limit_value)d characters (it has " -"%(show_value)d)." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:440 -#, python-format -msgid "" -"Ensure this value has at most %(limit_value)d character (it has " -"%(show_value)d)." -msgid_plural "" -"Ensure this value has at most %(limit_value)d characters (it has " -"%(show_value)d)." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:463 -#: env/lib/python3.11/site-packages/django/forms/fields.py:347 -#: env/lib/python3.11/site-packages/django/forms/fields.py:386 -#, fuzzy -#| msgid "phone number" -msgid "Enter a number." -msgstr "Telefonnummer" - -#: env/lib/python3.11/site-packages/django/core/validators.py:465 -#, python-format -msgid "Ensure that there are no more than %(max)s digit in total." -msgid_plural "Ensure that there are no more than %(max)s digits in total." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:470 -#, python-format -msgid "Ensure that there are no more than %(max)s decimal place." -msgid_plural "Ensure that there are no more than %(max)s decimal places." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:475 -#, python-format -msgid "" -"Ensure that there are no more than %(max)s digit before the decimal point." -msgid_plural "" -"Ensure that there are no more than %(max)s digits before the decimal point." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:546 -#, python-format -msgid "" -"File extension “%(extension)s” is not allowed. Allowed extensions are: " -"%(allowed_extensions)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/core/validators.py:607 -msgid "Null characters are not allowed." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/base.py:1425 -#, fuzzy, python-format -#| msgid "A template with this name already exist" -msgid "%(model_name)s with this %(field_labels)s already exists." -msgstr "Eine Vorlage mit diesem Namen ist bereits vorhanden" - -#: env/lib/python3.11/site-packages/django/db/models/constraints.py:17 -#, python-format -msgid "Constraint “%(name)s” is violated." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:128 -#, fuzzy, python-format -#| msgid "This activity is not validated yet." -msgid "Value %(value)r is not a valid choice." -msgstr "Diese Veranstaltung ist noch nicht bestätigt." - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:129 -#, fuzzy -#| msgid "This image cannot be loaded." -msgid "This field cannot be null." -msgstr "Dieses Bild kann nicht geladen werden." - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:130 -#, fuzzy -#| msgid "This image cannot be loaded." -msgid "This field cannot be blank." -msgstr "Dieses Bild kann nicht geladen werden." - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:131 -#, fuzzy, python-format -#| msgid "A template with this name already exist" -msgid "%(model_name)s with this %(field_label)s already exists." -msgstr "Eine Vorlage mit diesem Namen ist bereits vorhanden" - -#. Translators: The 'lookup_type' is one of 'date', 'year' or -#. 'month'. Eg: "Title must be unique for pub_date year" -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:135 -#, python-format -msgid "" -"%(field_label)s must be unique for %(date_field_label)s %(lookup_type)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:173 -#, python-format -msgid "Field of type: %(field_type)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1094 -#, python-format -msgid "“%(value)s” value must be either True or False." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1095 -#, python-format -msgid "“%(value)s” value must be either True, False, or None." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1097 -msgid "Boolean (Either True or False)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1147 -#, python-format -msgid "String (up to %(max_length)s)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1149 -msgid "String (unlimited)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1253 -msgid "Comma-separated integers" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1354 -#, python-format -msgid "" -"“%(value)s” value has an invalid date format. It must be in YYYY-MM-DD " -"format." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1358 -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1493 -#, python-format -msgid "" -"“%(value)s” value has the correct format (YYYY-MM-DD) but it is an invalid " -"date." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1362 -msgid "Date (without time)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1489 -#, python-format -msgid "" -"“%(value)s” value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." -"uuuuuu]][TZ] format." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1497 -#, python-format -msgid "" -"“%(value)s” value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" -"[TZ]) but it is an invalid date/time." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1502 -msgid "Date (with time)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1626 -#, python-format -msgid "“%(value)s” value must be a decimal number." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1628 -#, fuzzy -#| msgid "phone number" -msgid "Decimal number" -msgstr "Telefonnummer" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1789 -#, python-format -msgid "" -"“%(value)s” value has an invalid format. It must be in [DD] [[HH:]MM:]ss[." -"uuuuuu] format." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1793 -#, fuzzy -#| msgid "action" -msgid "Duration" -msgstr "Aktion" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1845 -#, fuzzy -#| msgid "address" -msgid "Email address" -msgstr "Adresse" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1870 -msgid "File path" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1948 -#, python-format -msgid "“%(value)s” value must be a float." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1950 -#, fuzzy -#| msgid "phone number" -msgid "Floating point number" -msgstr "Telefonnummer" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1990 -#, python-format -msgid "“%(value)s” value must be an integer." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:1992 -msgid "Integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2088 -msgid "Big (8 byte) integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2105 -msgid "Small integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2113 -#, fuzzy -#| msgid "IP Address" -msgid "IPv4 address" -msgstr "IP Adresse" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2144 -#, fuzzy -#| msgid "IP Address" -msgid "IP address" -msgstr "IP Adresse" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2237 -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2238 -#, python-format -msgid "“%(value)s” value must be either None, True or False." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2240 -msgid "Boolean (Either True, False or None)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2291 -msgid "Positive big integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2306 -msgid "Positive integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2321 -msgid "Positive small integer" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2337 -#, python-format -msgid "Slug (up to %(max_length)s)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2373 -msgid "Text" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2448 -#, python-format -msgid "" -"“%(value)s” value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " -"format." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2452 -#, python-format -msgid "" -"“%(value)s” value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " -"invalid time." -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2456 -msgid "Time" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2564 -msgid "URL" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2588 -msgid "Raw binary data" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2653 -#, fuzzy, python-format -#| msgid "This activity is not validated yet." -msgid "“%(value)s” is not a valid UUID." -msgstr "Diese Veranstaltung ist noch nicht bestätigt." - -#: env/lib/python3.11/site-packages/django/db/models/fields/__init__.py:2655 -#, fuzzy -#| msgid "Invoice identifier" -msgid "Universally unique identifier" -msgstr "Rechnungskennung" - -#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:232 -msgid "File" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/files.py:393 -msgid "Image" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:26 -#, fuzzy -#| msgid "Object" -msgid "A JSON object" -msgstr "Objekt" - -#: env/lib/python3.11/site-packages/django/db/models/fields/json.py:28 -#, fuzzy -#| msgid "This address must be valid." -msgid "Value must be valid JSON." -msgstr "Diese Adresse muss gültig sein." - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:919 -#, fuzzy, python-format -#| msgid "A template with this name already exist" -msgid "%(model)s instance with %(field)s %(value)r does not exist." -msgstr "Eine Vorlage mit diesem Namen ist bereits vorhanden" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:921 -msgid "Foreign Key (type determined by related field)" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1212 -msgid "One-to-one relationship" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1269 -#, python-format -msgid "%(from)s-%(to)s relationship" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1271 -#, python-format -msgid "%(from)s-%(to)s relationships" -msgstr "" - -#: env/lib/python3.11/site-packages/django/db/models/fields/related.py:1319 -msgid "Many-to-many relationship" -msgstr "" - -#. Translators: If found as last label character, these punctuation -#. characters will prevent the default label_suffix to be appended to the label -#: env/lib/python3.11/site-packages/django/forms/boundfield.py:184 -msgid ":?.!" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:298 -#, fuzzy -#| msgid "phone number" -msgid "Enter a whole number." -msgstr "Telefonnummer" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:467 -#: env/lib/python3.11/site-packages/django/forms/fields.py:1241 -#, fuzzy -#| msgid "Email validation" -msgid "Enter a valid date." -msgstr "Email validierung" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:490 -#: env/lib/python3.11/site-packages/django/forms/fields.py:1242 -#, fuzzy -#| msgid "Email validation" -msgid "Enter a valid time." -msgstr "Email validierung" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:517 -#, fuzzy -#| msgid "Email validation" -msgid "Enter a valid date/time." -msgstr "Email validierung" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:551 -#, fuzzy -#| msgid "Email validation" -msgid "Enter a valid duration." -msgstr "Email validierung" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:552 -#, python-brace-format -msgid "The number of days must be between {min_days} and {max_days}." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:621 -msgid "No file was submitted. Check the encoding type on the form." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:622 -msgid "No file was submitted." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:623 -msgid "The submitted file is empty." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:625 -#, python-format -msgid "Ensure this filename has at most %(max)d character (it has %(length)d)." -msgid_plural "" -"Ensure this filename has at most %(max)d characters (it has %(length)d)." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:630 -msgid "Please either submit a file or check the clear checkbox, not both." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:694 -msgid "" -"Upload a valid image. The file you uploaded was either not an image or a " -"corrupted image." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:857 -#: env/lib/python3.11/site-packages/django/forms/fields.py:949 -#: env/lib/python3.11/site-packages/django/forms/models.py:1566 -#, python-format -msgid "Select a valid choice. %(value)s is not one of the available choices." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:951 -#: env/lib/python3.11/site-packages/django/forms/fields.py:1070 -#: env/lib/python3.11/site-packages/django/forms/models.py:1564 -#, fuzzy -#| msgid "Email validation" -msgid "Enter a list of values." -msgstr "Email validierung" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:1071 -#, fuzzy -#| msgid "phone number" -msgid "Enter a complete value." -msgstr "Telefonnummer" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:1313 -#, fuzzy -#| msgid "Email validation" -msgid "Enter a valid UUID." -msgstr "Email validierung" - -#: env/lib/python3.11/site-packages/django/forms/fields.py:1343 -#, fuzzy -#| msgid "Email validation" -msgid "Enter a valid JSON." -msgstr "Email validierung" - -#. Translators: This is the default suffix added to form field labels -#: env/lib/python3.11/site-packages/django/forms/forms.py:98 -msgid ":" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/forms.py:244 -#: env/lib/python3.11/site-packages/django/forms/forms.py:328 -#, python-format -msgid "(Hidden field %(name)s) %(error)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/formsets.py:63 -#, python-format -msgid "" -"ManagementForm data is missing or has been tampered with. Missing fields: " -"%(field_names)s. You may need to file a bug report if the issue persists." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/formsets.py:67 -#, python-format -msgid "Please submit at most %(num)d form." -msgid_plural "Please submit at most %(num)d forms." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/forms/formsets.py:72 -#, python-format -msgid "Please submit at least %(num)d form." -msgid_plural "Please submit at least %(num)d forms." -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/forms/formsets.py:484 -#: env/lib/python3.11/site-packages/django/forms/formsets.py:491 -msgid "Order" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:886 -#, python-format -msgid "Please correct the duplicate data for %(field)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:891 -#, python-format -msgid "Please correct the duplicate data for %(field)s, which must be unique." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:898 -#, python-format -msgid "" -"Please correct the duplicate data for %(field_name)s which must be unique " -"for the %(lookup)s in %(date_field)s." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:907 -msgid "Please correct the duplicate values below." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:1338 -msgid "The inline value did not match the parent instance." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:1429 -msgid "Select a valid choice. That choice is not one of the available choices." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/models.py:1568 -#, fuzzy, python-format -#| msgid "This activity is not validated yet." -msgid "“%(pk)s” is not a valid value." -msgstr "Diese Veranstaltung ist noch nicht bestätigt." - -#: env/lib/python3.11/site-packages/django/forms/utils.py:226 -#, python-format -msgid "" -"%(datetime)s couldn’t be interpreted in time zone %(current_timezone)s; it " -"may be ambiguous or it may not exist." -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/widgets.py:463 -msgid "Clear" -msgstr "" - -#: env/lib/python3.11/site-packages/django/forms/widgets.py:464 -#, fuzzy -#| msgid "Current activity" -msgid "Currently" -msgstr "Aktuelle Veranstaltung" - -#: env/lib/python3.11/site-packages/django/forms/widgets.py:465 -#, fuzzy -#| msgid "change" -msgid "Change" -msgstr "bearbeiten" - -#: env/lib/python3.11/site-packages/django/forms/widgets.py:794 -msgid "Unknown" -msgstr "" - -#. Translators: Please do not add spaces around commas. -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:874 -msgid "yes,no,maybe" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:904 -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:921 -#, python-format -msgid "%(size)d byte" -msgid_plural "%(size)d bytes" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:923 -#, python-format -msgid "%s KB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:925 -#, python-format -msgid "%s MB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:927 -#, python-format -msgid "%s GB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:929 -#, python-format -msgid "%s TB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/template/defaultfilters.py:931 -#, python-format -msgid "%s PB" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:73 -msgid "p.m." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:74 -msgid "a.m." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:79 -msgid "PM" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:80 -msgid "AM" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:152 -msgid "midnight" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dateformat.py:154 -msgid "noon" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:7 -msgid "Monday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:8 -msgid "Tuesday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:9 -msgid "Wednesday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:10 -msgid "Thursday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:11 -msgid "Friday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:12 -msgid "Saturday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:13 -msgid "Sunday" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:16 -msgid "Mon" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:17 -msgid "Tue" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:18 -msgid "Wed" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:19 -msgid "Thu" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:20 -msgid "Fri" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:21 -msgid "Sat" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:22 -msgid "Sun" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:25 -msgid "January" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:26 -msgid "February" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:27 -#, fuzzy -#| msgid "Search WEI" -msgid "March" -msgstr "WEI finden" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:28 -msgid "April" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:29 -msgid "May" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:30 -msgid "June" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:31 -msgid "July" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:32 -msgid "August" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:33 -#, fuzzy -#| msgid "member" -msgid "September" -msgstr "Mitglied" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:34 -msgid "October" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:35 -#, fuzzy -#| msgid "member" -msgid "November" -msgstr "Mitglied" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:36 -#, fuzzy -#| msgid "member" -msgid "December" -msgstr "Mitglied" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:39 -#, fuzzy -#| msgid "add" -msgid "jan" -msgstr "hinzufügen" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:40 -#, fuzzy -#| msgid "fee" -msgid "feb" -msgstr "Preis" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:41 -msgid "mar" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:42 -msgid "apr" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:43 -msgid "may" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:44 -#, fuzzy -#| msgid "add" -msgid "jun" -msgstr "hinzufügen" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:45 -msgid "jul" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:46 -msgid "aug" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:47 -msgid "sep" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:48 -#, fuzzy -#| msgid "product" -msgid "oct" -msgstr "Produkt" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:49 -msgid "nov" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:50 -msgid "dec" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:53 -msgctxt "abbrev. month" -msgid "Jan." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:54 -msgctxt "abbrev. month" -msgid "Feb." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:55 -#, fuzzy -#| msgid "Search WEI" -msgctxt "abbrev. month" -msgid "March" -msgstr "WEI finden" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:56 -msgctxt "abbrev. month" -msgid "April" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:57 -msgctxt "abbrev. month" -msgid "May" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:58 -msgctxt "abbrev. month" -msgid "June" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:59 -msgctxt "abbrev. month" -msgid "July" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:60 -msgctxt "abbrev. month" -msgid "Aug." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:61 -msgctxt "abbrev. month" -msgid "Sept." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:62 -msgctxt "abbrev. month" -msgid "Oct." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:63 -msgctxt "abbrev. month" -msgid "Nov." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:64 -msgctxt "abbrev. month" -msgid "Dec." -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:67 -msgctxt "alt. month" -msgid "January" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:68 -msgctxt "alt. month" -msgid "February" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:69 -#, fuzzy -#| msgid "Search WEI" -msgctxt "alt. month" -msgid "March" -msgstr "WEI finden" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:70 -msgctxt "alt. month" -msgid "April" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:71 -msgctxt "alt. month" -msgid "May" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:72 -msgctxt "alt. month" -msgid "June" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:73 -msgctxt "alt. month" -msgid "July" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:74 -msgctxt "alt. month" -msgid "August" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:75 -#, fuzzy -#| msgid "member" -msgctxt "alt. month" -msgid "September" -msgstr "Mitglied" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:76 -msgctxt "alt. month" -msgid "October" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:77 -#, fuzzy -#| msgid "member" -msgctxt "alt. month" -msgid "November" -msgstr "Mitglied" - -#: env/lib/python3.11/site-packages/django/utils/dates.py:78 -#, fuzzy -#| msgid "member" -msgctxt "alt. month" -msgid "December" -msgstr "Mitglied" - -#: env/lib/python3.11/site-packages/django/utils/ipv6.py:20 -#, fuzzy -#| msgid "This activity is not validated yet." -msgid "This is not a valid IPv6 address." -msgstr "Diese Veranstaltung ist noch nicht bestätigt." - -#: env/lib/python3.11/site-packages/django/utils/text.py:138 -#, python-format -msgctxt "String to return when truncating text" -msgid "%(truncated_text)s…" -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/text.py:323 -msgid "or" -msgstr "" - -#. Translators: This string is used as a separator between list elements -#: env/lib/python3.11/site-packages/django/utils/text.py:342 -#: env/lib/python3.11/site-packages/django/utils/timesince.py:135 -msgid ", " -msgstr "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:8 -#, fuzzy, python-format -#| msgid "year" -msgid "%(num)d year" -msgid_plural "%(num)d years" -msgstr[0] "Jahr" -msgstr[1] "Jahr" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:9 -#, python-format -msgid "%(num)d month" -msgid_plural "%(num)d months" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:10 -#, python-format -msgid "%(num)d week" -msgid_plural "%(num)d weeks" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:11 -#, python-format -msgid "%(num)d day" -msgid_plural "%(num)d days" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:12 -#, python-format -msgid "%(num)d hour" -msgid_plural "%(num)d hours" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/utils/timesince.py:13 -#, python-format -msgid "%(num)d minute" -msgid_plural "%(num)d minutes" -msgstr[0] "" -msgstr[1] "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:111 -msgid "Forbidden" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:112 -msgid "CSRF verification failed. Request aborted." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:116 -msgid "" -"You are seeing this message because this HTTPS site requires a “Referer " -"header” to be sent by your web browser, but none was sent. This header is " -"required for security reasons, to ensure that your browser is not being " -"hijacked by third parties." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:122 -msgid "" -"If you have configured your browser to disable “Referer” headers, please re-" -"enable them, at least for this site, or for HTTPS connections, or for “same-" -"origin” requests." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:127 -msgid "" -"If you are using the tag or " -"including the “Referrer-Policy: no-referrer” header, please remove them. The " -"CSRF protection requires the “Referer” header to do strict referer checking. " -"If you’re concerned about privacy, use alternatives like for links to third-party sites." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:136 -msgid "" -"You are seeing this message because this site requires a CSRF cookie when " -"submitting forms. This cookie is required for security reasons, to ensure " -"that your browser is not being hijacked by third parties." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:142 -msgid "" -"If you have configured your browser to disable cookies, please re-enable " -"them, at least for this site, or for “same-origin” requests." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/csrf.py:148 -msgid "More information is available with DEBUG=True." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:44 -#, fuzzy -#| msgid "No reason specified" -msgid "No year specified" -msgstr "Kein Grund gegeben" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:64 -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:115 -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:214 -msgid "Date out of range" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:94 -#, fuzzy -#| msgid "No reason specified" -msgid "No month specified" -msgstr "Kein Grund gegeben" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:147 -#, fuzzy -#| msgid "No reason specified" -msgid "No day specified" -msgstr "Kein Grund gegeben" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:194 -#, fuzzy -#| msgid "No reason specified" -msgid "No week specified" -msgstr "Kein Grund gegeben" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:349 -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:380 -#, python-format -msgid "No %(verbose_name_plural)s available" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:652 -#, python-format -msgid "" -"Future %(verbose_name_plural)s not available because %(class_name)s." -"allow_future is False." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/dates.py:692 -#, python-format -msgid "Invalid date string “%(datestr)s” given format “%(format)s”" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/detail.py:56 -#, python-format -msgid "No %(verbose_name)s found matching the query" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/list.py:70 -msgid "Page is not “last”, nor can it be converted to an int." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/list.py:77 -#, python-format -msgid "Invalid page (%(page_number)s): %(message)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/generic/list.py:169 -#, python-format -msgid "Empty list and “%(class_name)s.allow_empty” is False." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/static.py:38 -msgid "Directory indexes are not allowed here." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/static.py:40 -#, python-format -msgid "“%(path)s” does not exist" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/static.py:79 -#, python-format -msgid "Index of %(directory)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:7 -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:220 -msgid "The install worked successfully! Congratulations!" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:206 -#, python-format -msgid "" -"View release notes for Django %(version)s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:221 -#, python-format -msgid "" -"You are seeing this page because DEBUG=True is in your settings file and you have not " -"configured any URLs." -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:229 -msgid "Django Documentation" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:230 -msgid "Topics, references, & how-to’s" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:238 -msgid "Tutorial: A Polling App" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:239 -msgid "Get started with Django" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:247 -msgid "Django Community" -msgstr "" - -#: env/lib/python3.11/site-packages/django/views/templates/default_urlconf.html:248 -msgid "Connect, get help, or contribute" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:69 -msgid "Confidential" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:70 -msgid "Public" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:79 -msgid "Authorization code" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:80 -msgid "Implicit" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:81 -#, fuzzy -#| msgid "Reset my password" -msgid "Resource owner password-based" -msgstr "Mein Passwort zurücksetzen" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:82 -msgid "Client credentials" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:83 -msgid "OpenID connect hybrid" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:90 -msgid "No OIDC support" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:91 -msgid "RSA with SHA-2 256" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:92 -msgid "HMAC with SHA-2 256" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:107 -msgid "Allowed URIs list, space separated" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:111 -msgid "Allowed Post Logout URIs list, space separated" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:120 -msgid "Hashed on Save. Copy it now if this is a new secret." -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:190 -#, python-brace-format -msgid "Unauthorized redirect scheme: {scheme}" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:194 -#, python-brace-format -msgid "redirect_uris cannot be empty with grant_type {grant_type}" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:200 -msgid "You must set OIDC_RSA_PRIVATE_KEY to use RSA algorithm" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/models.py:209 -msgid "You cannot use HS256 with public grants or clients" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:211 -#, fuzzy -#| msgid "This address must be valid." -msgid "The access token is invalid." -msgstr "Diese Adresse muss gültig sein." - -#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:218 -#, fuzzy -#| msgid "This address must be valid." -msgid "The access token has expired." -msgstr "Diese Adresse muss gültig sein." - -#: env/lib/python3.11/site-packages/oauth2_provider/oauth2_validators.py:225 -#, fuzzy -#| msgid "This address must be valid." -msgid "The access token is valid but does not have enough scope." -msgstr "Diese Adresse muss gültig sein." - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:6 -#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8 -#, fuzzy -#| msgid "" -#| "Are you sure you want to delete this invoice? This action can't be undone." -msgid "Are you sure to delete the application" -msgstr "" -"Möchten Sie diese Rechnung wirklich löschen? Diese Aktion kann nicht " -"rückgängig gemacht werden." - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_confirm_delete.html:12 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:29 -#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 -#: note_kfet/templates/oauth2_provider/authorize.html:28 -#, fuzzy -#| msgid "Balance" -msgid "Cancel" -msgstr "Kontostand" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:10 -#: note_kfet/templates/oauth2_provider/application_detail.html:11 -msgid "Client id" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:15 -#: note_kfet/templates/oauth2_provider/application_detail.html:14 -msgid "Client secret" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:20 -#: note_kfet/templates/oauth2_provider/application_detail.html:17 -#, fuzzy -#| msgid "Credit type" -msgid "Client type" -msgstr "Kredittype" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:25 -#: note_kfet/templates/oauth2_provider/application_detail.html:20 -msgid "Authorization Grant Type" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:30 -#: note_kfet/templates/oauth2_provider/application_detail.html:23 -msgid "Redirect Uris" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_detail.html:36 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:35 -#: note_kfet/templates/oauth2_provider/application_detail.html:37 -#: note_kfet/templates/oauth2_provider/application_form.html:23 -msgid "Go Back" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:9 -#: note_kfet/templates/oauth2_provider/application_form.html:12 -#, fuzzy -#| msgid "Email validation" -msgid "Edit application" -msgstr "Email validierung" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_form.html:37 -msgid "Save" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:6 -#: note_kfet/templates/oauth2_provider/application_list.html:7 -msgid "Your applications" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_list.html:14 -#: note_kfet/templates/oauth2_provider/application_list.html:30 -#, fuzzy -#| msgid "location" -msgid "New Application" -msgstr "Ort" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/application_registration_form.html:5 -#: note_kfet/templates/oauth2_provider/application_registration_form.html:5 -#, fuzzy -#| msgid "Registrations" -msgid "Register a new application" -msgstr "Anmeldung" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:8 -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:30 -#: note_kfet/templates/oauth2_provider/authorize.html:9 -#: note_kfet/templates/oauth2_provider/authorize.html:29 -msgid "Authorize" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorize.html:17 -msgid "Application requires the following permissions" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-token-delete.html:6 -#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9 -#, fuzzy -#| msgid "" -#| "Are you sure you want to delete this invoice? This action can't be undone." -msgid "Are you sure you want to delete this token?" -msgstr "" -"Möchten Sie diese Rechnung wirklich löschen? Diese Aktion kann nicht " -"rückgängig gemacht werden." - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:6 -#: note_kfet/templates/oauth2_provider/authorized-tokens.html:7 -#, fuzzy -#| msgid "Token" -msgid "Tokens" -msgstr "Token" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:11 -msgid "revoke" -msgstr "" - -#: env/lib/python3.11/site-packages/oauth2_provider/templates/oauth2_provider/authorized-tokens.html:19 -#: note_kfet/templates/oauth2_provider/authorized-tokens.html:22 -#, fuzzy -#| msgid "There is no closed remittance yet." -msgid "There are no authorized tokens yet." -msgstr "Es gibt noch keine geschlossene Überweisung." - #: note_kfet/settings/base.py:177 msgid "German" msgstr "Deutsch" @@ -5698,6 +4208,44 @@ msgstr "Suche nach Attributen wie Name..." msgid "There is no results." msgstr "Es gibt keine Ergebnisse." +#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:8 +#, fuzzy +#| msgid "" +#| "Are you sure you want to delete this invoice? This action can't be undone." +msgid "Are you sure to delete the application" +msgstr "" +"Möchten Sie diese Rechnung wirklich löschen? Diese Aktion kann nicht " +"rückgängig gemacht werden." + +#: note_kfet/templates/oauth2_provider/application_confirm_delete.html:17 +#: note_kfet/templates/oauth2_provider/authorize.html:28 +#, fuzzy +#| msgid "Balance" +msgid "Cancel" +msgstr "Kontostand" + +#: note_kfet/templates/oauth2_provider/application_detail.html:11 +msgid "Client id" +msgstr "" + +#: note_kfet/templates/oauth2_provider/application_detail.html:14 +msgid "Client secret" +msgstr "" + +#: note_kfet/templates/oauth2_provider/application_detail.html:17 +#, fuzzy +#| msgid "Credit type" +msgid "Client type" +msgstr "Kredittype" + +#: note_kfet/templates/oauth2_provider/application_detail.html:20 +msgid "Authorization Grant Type" +msgstr "" + +#: note_kfet/templates/oauth2_provider/application_detail.html:23 +msgid "Redirect Uris" +msgstr "" + #: note_kfet/templates/oauth2_provider/application_detail.html:29 #, python-format msgid "" @@ -5706,16 +4254,48 @@ msgid "" "that you want to grant for your application." msgstr "" +#: note_kfet/templates/oauth2_provider/application_detail.html:37 +#: note_kfet/templates/oauth2_provider/application_form.html:23 +msgid "Go Back" +msgstr "" + +#: note_kfet/templates/oauth2_provider/application_form.html:12 +#, fuzzy +#| msgid "Email validation" +msgid "Edit application" +msgstr "Email validierung" + +#: note_kfet/templates/oauth2_provider/application_list.html:7 +msgid "Your applications" +msgstr "" + #: note_kfet/templates/oauth2_provider/application_list.html:11 msgid "" "You can find on this page the list of the applications that you already " "registered." msgstr "" +#: note_kfet/templates/oauth2_provider/application_list.html:30 +#, fuzzy +#| msgid "location" +msgid "New Application" +msgstr "Ort" + #: note_kfet/templates/oauth2_provider/application_list.html:31 msgid "Authorized Tokens" msgstr "" +#: note_kfet/templates/oauth2_provider/application_registration_form.html:5 +#, fuzzy +#| msgid "Registrations" +msgid "Register a new application" +msgstr "Anmeldung" + +#: note_kfet/templates/oauth2_provider/authorize.html:9 +#: note_kfet/templates/oauth2_provider/authorize.html:29 +msgid "Authorize" +msgstr "" + #: note_kfet/templates/oauth2_provider/authorize.html:14 msgid "Application requires following permissions:" msgstr "" @@ -5733,6 +4313,27 @@ msgstr "" msgid "Please return to your application and enter this code:" msgstr "" +#: note_kfet/templates/oauth2_provider/authorized-token-delete.html:9 +#, fuzzy +#| msgid "" +#| "Are you sure you want to delete this invoice? This action can't be undone." +msgid "Are you sure you want to delete this token?" +msgstr "" +"Möchten Sie diese Rechnung wirklich löschen? Diese Aktion kann nicht " +"rückgängig gemacht werden." + +#: note_kfet/templates/oauth2_provider/authorized-tokens.html:7 +#, fuzzy +#| msgid "Token" +msgid "Tokens" +msgstr "Token" + +#: note_kfet/templates/oauth2_provider/authorized-tokens.html:22 +#, fuzzy +#| msgid "There is no closed remittance yet." +msgid "There are no authorized tokens yet." +msgstr "Es gibt noch keine geschlossene Überweisung." + #: note_kfet/templates/registration/logged_out.html:13 msgid "Thanks for spending some quality time with the Web site today." msgstr "" @@ -5846,6 +4447,336 @@ msgstr "" "müssen Ihre E-Mail-Adresse auch überprüfen, indem Sie dem Link folgen, den " "Sie erhalten haben." +#, fuzzy +#~| msgid "total amount" +#~ msgid "caution amount" +#~ msgstr "Totalanzahlt" + +#, fuzzy +#~| msgid "created at" +#~ msgid "caution type" +#~ msgstr "erschafft am" + +#, fuzzy +#~| msgid "total amount" +#~ msgid "Caution amount" +#~ msgstr "Totalanzahlt" + +#~ msgid "caution check given" +#~ msgstr "Vorsichtsprüfung gegeben" + +#, fuzzy +#~| msgid "Invitation" +#~ msgid "Syndication" +#~ msgstr "Einladung" + +#, fuzzy +#~| msgid "There is no results." +#~ msgid "That page contains no results" +#~ msgstr "Es gibt keine Ergebnisse." + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid value." +#~ msgstr "Email validierung" + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid URL." +#~ msgstr "Email validierung" + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid integer." +#~ msgstr "Email validierung" + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid email address." +#~ msgstr "Email validierung" + +#, fuzzy +#~| msgid "This activity is not validated yet." +#~ msgid "Enter a valid IPv4 address." +#~ msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#, fuzzy +#~| msgid "This activity is not validated yet." +#~ msgid "Enter a valid IPv6 address." +#~ msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#, fuzzy +#~| msgid "This activity is not validated yet." +#~ msgid "Enter a valid IPv4 or IPv6 address." +#~ msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Enter a number." +#~ msgstr "Telefonnummer" + +#, fuzzy, python-format +#~| msgid "A template with this name already exist" +#~ msgid "%(model_name)s with this %(field_labels)s already exists." +#~ msgstr "Eine Vorlage mit diesem Namen ist bereits vorhanden" + +#, fuzzy, python-format +#~| msgid "This activity is not validated yet." +#~ msgid "Value %(value)r is not a valid choice." +#~ msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#, fuzzy +#~| msgid "This image cannot be loaded." +#~ msgid "This field cannot be null." +#~ msgstr "Dieses Bild kann nicht geladen werden." + +#, fuzzy +#~| msgid "This image cannot be loaded." +#~ msgid "This field cannot be blank." +#~ msgstr "Dieses Bild kann nicht geladen werden." + +#, fuzzy, python-format +#~| msgid "A template with this name already exist" +#~ msgid "%(model_name)s with this %(field_label)s already exists." +#~ msgstr "Eine Vorlage mit diesem Namen ist bereits vorhanden" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Decimal number" +#~ msgstr "Telefonnummer" + +#, fuzzy +#~| msgid "action" +#~ msgid "Duration" +#~ msgstr "Aktion" + +#, fuzzy +#~| msgid "address" +#~ msgid "Email address" +#~ msgstr "Adresse" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Floating point number" +#~ msgstr "Telefonnummer" + +#, fuzzy +#~| msgid "IP Address" +#~ msgid "IPv4 address" +#~ msgstr "IP Adresse" + +#, fuzzy +#~| msgid "IP Address" +#~ msgid "IP address" +#~ msgstr "IP Adresse" + +#, fuzzy, python-format +#~| msgid "This activity is not validated yet." +#~ msgid "“%(value)s” is not a valid UUID." +#~ msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#, fuzzy +#~| msgid "Invoice identifier" +#~ msgid "Universally unique identifier" +#~ msgstr "Rechnungskennung" + +#, fuzzy +#~| msgid "Object" +#~ msgid "A JSON object" +#~ msgstr "Objekt" + +#, fuzzy +#~| msgid "This address must be valid." +#~ msgid "Value must be valid JSON." +#~ msgstr "Diese Adresse muss gültig sein." + +#, fuzzy, python-format +#~| msgid "A template with this name already exist" +#~ msgid "%(model)s instance with %(field)s %(value)r does not exist." +#~ msgstr "Eine Vorlage mit diesem Namen ist bereits vorhanden" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Enter a whole number." +#~ msgstr "Telefonnummer" + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid date." +#~ msgstr "Email validierung" + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid time." +#~ msgstr "Email validierung" + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid date/time." +#~ msgstr "Email validierung" + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid duration." +#~ msgstr "Email validierung" + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a list of values." +#~ msgstr "Email validierung" + +#, fuzzy +#~| msgid "phone number" +#~ msgid "Enter a complete value." +#~ msgstr "Telefonnummer" + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid UUID." +#~ msgstr "Email validierung" + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid JSON." +#~ msgstr "Email validierung" + +#, fuzzy, python-format +#~| msgid "This activity is not validated yet." +#~ msgid "“%(pk)s” is not a valid value." +#~ msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#, fuzzy +#~| msgid "Current activity" +#~ msgid "Currently" +#~ msgstr "Aktuelle Veranstaltung" + +#, fuzzy +#~| msgid "change" +#~ msgid "Change" +#~ msgstr "bearbeiten" + +#, fuzzy +#~| msgid "Search WEI" +#~ msgid "March" +#~ msgstr "WEI finden" + +#, fuzzy +#~| msgid "member" +#~ msgid "September" +#~ msgstr "Mitglied" + +#, fuzzy +#~| msgid "member" +#~ msgid "November" +#~ msgstr "Mitglied" + +#, fuzzy +#~| msgid "member" +#~ msgid "December" +#~ msgstr "Mitglied" + +#, fuzzy +#~| msgid "add" +#~ msgid "jan" +#~ msgstr "hinzufügen" + +#, fuzzy +#~| msgid "fee" +#~ msgid "feb" +#~ msgstr "Preis" + +#, fuzzy +#~| msgid "add" +#~ msgid "jun" +#~ msgstr "hinzufügen" + +#, fuzzy +#~| msgid "product" +#~ msgid "oct" +#~ msgstr "Produkt" + +#, fuzzy +#~| msgid "Search WEI" +#~ msgctxt "abbrev. month" +#~ msgid "March" +#~ msgstr "WEI finden" + +#, fuzzy +#~| msgid "Search WEI" +#~ msgctxt "alt. month" +#~ msgid "March" +#~ msgstr "WEI finden" + +#, fuzzy +#~| msgid "member" +#~ msgctxt "alt. month" +#~ msgid "September" +#~ msgstr "Mitglied" + +#, fuzzy +#~| msgid "member" +#~ msgctxt "alt. month" +#~ msgid "November" +#~ msgstr "Mitglied" + +#, fuzzy +#~| msgid "member" +#~ msgctxt "alt. month" +#~ msgid "December" +#~ msgstr "Mitglied" + +#, fuzzy +#~| msgid "This activity is not validated yet." +#~ msgid "This is not a valid IPv6 address." +#~ msgstr "Diese Veranstaltung ist noch nicht bestätigt." + +#, fuzzy, python-format +#~| msgid "year" +#~ msgid "%(num)d year" +#~ msgid_plural "%(num)d years" +#~ msgstr[0] "Jahr" +#~ msgstr[1] "Jahr" + +#, fuzzy +#~| msgid "No reason specified" +#~ msgid "No year specified" +#~ msgstr "Kein Grund gegeben" + +#, fuzzy +#~| msgid "No reason specified" +#~ msgid "No month specified" +#~ msgstr "Kein Grund gegeben" + +#, fuzzy +#~| msgid "No reason specified" +#~ msgid "No day specified" +#~ msgstr "Kein Grund gegeben" + +#, fuzzy +#~| msgid "No reason specified" +#~ msgid "No week specified" +#~ msgstr "Kein Grund gegeben" + +#, fuzzy +#~| msgid "Reset my password" +#~ msgid "Resource owner password-based" +#~ msgstr "Mein Passwort zurücksetzen" + +#, fuzzy +#~| msgid "This address must be valid." +#~ msgid "The access token is invalid." +#~ msgstr "Diese Adresse muss gültig sein." + +#, fuzzy +#~| msgid "This address must be valid." +#~ msgid "The access token has expired." +#~ msgstr "Diese Adresse muss gültig sein." + +#, fuzzy +#~| msgid "This address must be valid." +#~ msgid "The access token is valid but does not have enough scope." +#~ msgstr "Diese Adresse muss gültig sein." + #~ msgid "The BDE membership is included in the WEI registration." #~ msgstr "Die BDE-Mitgliedschaft ist in der WEI-Registrierung enthalten." diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index 3baa260d..6612e6ec 100644 --- a/locale/es/LC_MESSAGES/django.po +++ b/locale/es/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-06-20 14:02+0200\n" +"POT-Creation-Date: 2025-07-15 18:18+0200\n" "PO-Revision-Date: 2022-04-11 23:12+0200\n" "Last-Translator: bleizi \n" "Language-Team: \n" @@ -65,7 +65,7 @@ msgstr "Usted no puede invitar más de 3 persona a esta actividad." #: apps/note/models/transactions.py:46 apps/note/models/transactions.py:299 #: apps/permission/models.py:329 #: apps/registration/templates/registration/future_profile_detail.html:16 -#: apps/wei/models.py:72 apps/wei/models.py:145 apps/wei/tables.py:282 +#: apps/wei/models.py:77 apps/wei/models.py:150 apps/wei/tables.py:282 #: apps/wei/templates/wei/base.html:26 #: apps/wei/templates/wei/weimembership_form.html:14 apps/wrapped/models.py:16 msgid "name" @@ -100,7 +100,7 @@ msgstr "tipos de actividad" #: apps/activity/models.py:68 #: apps/activity/templates/activity/includes/activity_info.html:19 #: apps/note/models/transactions.py:82 apps/permission/models.py:109 -#: apps/permission/models.py:188 apps/wei/models.py:92 apps/wei/models.py:156 +#: apps/permission/models.py:188 apps/wei/models.py:97 apps/wei/models.py:161 msgid "description" msgstr "descripción" @@ -121,7 +121,7 @@ msgstr "tipo" #: 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:294 -#: apps/wei/models.py:185 apps/wei/templates/wei/attribute_bus_1A.html:13 +#: apps/wei/models.py:190 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/templates/wei/survey.html:15 msgid "user" msgstr "usuario" @@ -1297,7 +1297,7 @@ msgid "add to registration form" msgstr "Validar la afiliación" #: apps/member/models.py:268 apps/member/models.py:331 -#: apps/note/models/notes.py:176 apps/wei/models.py:86 +#: apps/note/models/notes.py:176 apps/wei/models.py:91 msgid "club" msgstr "club" @@ -2017,8 +2017,8 @@ msgstr "" "pago y un usuario o un club" #: apps/note/models/transactions.py:357 apps/note/models/transactions.py:360 -#: apps/note/models/transactions.py:363 apps/wei/views.py:1097 -#: apps/wei/views.py:1101 +#: apps/note/models/transactions.py:363 apps/wei/views.py:1103 +#: apps/wei/views.py:1107 msgid "This field is required." msgstr "Este campo es obligatorio." @@ -2515,7 +2515,7 @@ msgstr "El usuario declara que ya abrió una cuenta a la Société Générale." #: apps/registration/templates/registration/future_profile_detail.html:73 #: apps/wei/templates/wei/weimembership_form.html:127 -#: apps/wei/templates/wei/weimembership_form.html:196 +#: apps/wei/templates/wei/weimembership_form.html:192 msgid "Validate registration" msgstr "Validar la afiliación" @@ -3043,8 +3043,8 @@ msgstr "Lista de los créditos de la Société Générale" msgid "Manage credits from the Société générale" msgstr "Gestionar los créditos de la Société Générale" -#: apps/wei/apps.py:10 apps/wei/models.py:42 apps/wei/models.py:43 -#: apps/wei/models.py:67 apps/wei/models.py:192 +#: apps/wei/apps.py:10 apps/wei/models.py:47 apps/wei/models.py:48 +#: apps/wei/models.py:72 apps/wei/models.py:197 #: note_kfet/templates/base.html:108 msgid "WEI" msgstr "WEI" @@ -3054,8 +3054,8 @@ msgid "The selected user is not validated. Please validate its account first" msgstr "" "El usuario seleccionado no ha sido validado. Validar esta cuenta primero" -#: apps/wei/forms/registration.py:84 apps/wei/models.py:140 -#: apps/wei/models.py:348 +#: apps/wei/forms/registration.py:84 apps/wei/models.py:145 +#: apps/wei/models.py:354 msgid "bus" msgstr "bus" @@ -3081,7 +3081,7 @@ msgstr "" "electrón libre)" #: apps/wei/forms/registration.py:100 apps/wei/forms/registration.py:110 -#: apps/wei/models.py:174 +#: apps/wei/models.py:179 msgid "WEI Roles" msgstr "Papeles en el WEI" @@ -3089,14 +3089,19 @@ msgstr "Papeles en el WEI" msgid "Select the roles that you are interested in." msgstr "Elegir los papeles que le interesa." -#: apps/wei/forms/registration.py:147 +#: apps/wei/forms/registration.py:160 msgid "This team doesn't belong to the given bus." msgstr "Este equipo no pertenece al bus dado." #: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:38 +#: apps/wei/forms/surveys/wei2025.py:36 msgid "Choose a word:" msgstr "Elegir una palabra :" +#: apps/wei/forms/surveys/wei2025.py:123 +msgid "Rate between 0 and 5." +msgstr "" + #: apps/wei/models.py:25 apps/wei/templates/wei/base.html:36 msgid "year" msgstr "año" @@ -3113,138 +3118,147 @@ msgstr "fecha de fin" #: apps/wei/models.py:37 #, fuzzy -#| msgid "total amount" -msgid "caution amount" -msgstr "monto total" +#| msgid "Credit amount" +msgid "deposit amount" +msgstr "Valor del crédito" -#: apps/wei/models.py:76 apps/wei/tables.py:305 +#: apps/wei/models.py:42 +#, fuzzy +#| msgid "No credit" +msgid "membership fee (soge credit)" +msgstr "No crédito" + +#: apps/wei/models.py:81 apps/wei/tables.py:305 msgid "seat count in the bus" msgstr "cantidad de asientos en el bus" -#: apps/wei/models.py:97 +#: apps/wei/models.py:102 msgid "survey information" msgstr "informaciones sobre el cuestionario" -#: apps/wei/models.py:98 +#: apps/wei/models.py:103 msgid "Information about the survey for new members, encoded in JSON" msgstr "" "Informaciones sobre el cuestionario para los nuevos miembros, registrado en " "JSON" -#: apps/wei/models.py:102 +#: apps/wei/models.py:107 msgid "Bus" msgstr "Bus" -#: apps/wei/models.py:103 apps/wei/templates/wei/weiclub_detail.html:51 +#: apps/wei/models.py:108 apps/wei/templates/wei/weiclub_detail.html:51 msgid "Buses" msgstr "Bus" -#: apps/wei/models.py:149 +#: apps/wei/models.py:154 msgid "color" msgstr "color" -#: apps/wei/models.py:150 +#: apps/wei/models.py:155 msgid "The color of the T-Shirt, stored with its number equivalent" msgstr "El color de la camiseta, registrado con su número equivalente" -#: apps/wei/models.py:161 +#: apps/wei/models.py:166 msgid "Bus team" msgstr "Equipo de bus" -#: apps/wei/models.py:162 +#: apps/wei/models.py:167 msgid "Bus teams" msgstr "Equipos de bus" -#: apps/wei/models.py:173 +#: apps/wei/models.py:178 msgid "WEI Role" msgstr "Papeles en el WEI" -#: apps/wei/models.py:197 +#: apps/wei/models.py:202 msgid "Credit from Société générale" msgstr "Crédito de la Société Générale" -#: apps/wei/models.py:202 apps/wei/views.py:984 -msgid "Caution check given" +#: apps/wei/models.py:207 apps/wei/templates/wei/weimembership_form.html:98 +#: apps/wei/views.py:997 +#, fuzzy +#| msgid "Caution check given" +msgid "Deposit check given" msgstr "Cheque de garantía dado" -#: apps/wei/models.py:208 +#: apps/wei/models.py:213 msgid "Check" msgstr "" -#: apps/wei/models.py:209 +#: apps/wei/models.py:214 #, fuzzy #| msgid "transactions" msgid "Note transaction" msgstr "Transacción" -#: apps/wei/models.py:212 +#: apps/wei/models.py:217 #, fuzzy -#| msgid "created at" -msgid "caution type" -msgstr "tipo de fianza" +#| msgid "Credit type" +msgid "deposit type" +msgstr "Tipo de crédito" -#: apps/wei/models.py:216 apps/wei/templates/wei/weimembership_form.html:64 +#: apps/wei/models.py:221 apps/wei/templates/wei/weimembership_form.html:64 msgid "birth date" msgstr "fecha de nacimiento" -#: apps/wei/models.py:222 apps/wei/models.py:232 +#: apps/wei/models.py:227 apps/wei/models.py:237 msgid "Male" msgstr "Hombre" -#: apps/wei/models.py:223 apps/wei/models.py:233 +#: apps/wei/models.py:228 apps/wei/models.py:238 msgid "Female" msgstr "Mujer" -#: apps/wei/models.py:224 +#: apps/wei/models.py:229 msgid "Non binary" msgstr "No binari@" -#: apps/wei/models.py:226 apps/wei/templates/wei/attribute_bus_1A.html:22 +#: apps/wei/models.py:231 apps/wei/templates/wei/attribute_bus_1A.html:22 #: apps/wei/templates/wei/weimembership_form.html:55 msgid "gender" msgstr "género" -#: apps/wei/models.py:234 +#: apps/wei/models.py:239 msgid "Unisex" msgstr "Unisex" -#: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:58 +#: apps/wei/models.py:242 apps/wei/templates/wei/weimembership_form.html:58 msgid "clothing cut" msgstr "forma de ropa" -#: apps/wei/models.py:250 apps/wei/templates/wei/weimembership_form.html:61 +#: apps/wei/models.py:255 apps/wei/templates/wei/weimembership_form.html:61 msgid "clothing size" msgstr "medida de ropa" -#: apps/wei/models.py:256 +#: apps/wei/models.py:261 msgid "health issues" msgstr "problemas de salud" -#: apps/wei/models.py:261 apps/wei/templates/wei/weimembership_form.html:70 +#: apps/wei/models.py:266 apps/wei/templates/wei/weimembership_form.html:70 msgid "emergency contact name" msgstr "nombre del contacto de emergencia" -#: apps/wei/models.py:262 +#: apps/wei/models.py:267 msgid "The emergency contact must not be a WEI participant" msgstr "El contacto de emergencia no debe ser un participante de WEI" -#: apps/wei/models.py:267 apps/wei/templates/wei/weimembership_form.html:73 +#: apps/wei/models.py:272 apps/wei/templates/wei/weimembership_form.html:73 msgid "emergency contact phone" msgstr "teléfono del contacto de emergencia" -#: apps/wei/models.py:272 apps/wei/templates/wei/weimembership_form.html:52 +#: apps/wei/models.py:277 apps/wei/templates/wei/weimembership_form.html:52 msgid "first year" msgstr "primer año" -#: apps/wei/models.py:273 +#: apps/wei/models.py:278 msgid "Tells if the user is new in the school." msgstr "Indica si el usuario es nuevo en la escuela." -#: apps/wei/models.py:278 +#: apps/wei/models.py:283 msgid "registration information" msgstr "informaciones sobre la afiliación" -#: apps/wei/models.py:279 +#: apps/wei/models.py:284 msgid "" "Information about the registration (buses for old members, survey for the " "new members), encoded in JSON" @@ -3252,27 +3266,27 @@ msgstr "" "Informaciones sobre la afiliacion (bus para miembros ancianos, cuestionario " "para los nuevos miembros), registrado en JSON" -#: apps/wei/models.py:285 +#: apps/wei/models.py:290 msgid "WEI User" msgstr "Participante WEI" -#: apps/wei/models.py:286 +#: apps/wei/models.py:291 msgid "WEI Users" msgstr "Participantes WEI" -#: apps/wei/models.py:358 +#: apps/wei/models.py:364 msgid "team" msgstr "equipo" -#: apps/wei/models.py:368 +#: apps/wei/models.py:374 msgid "WEI registration" msgstr "Apuntación al WEI" -#: apps/wei/models.py:372 +#: apps/wei/models.py:378 msgid "WEI membership" msgstr "Afiliación al WEI" -#: apps/wei/models.py:373 +#: apps/wei/models.py:379 msgid "WEI memberships" msgstr "Afiliaciones al WEI" @@ -3300,7 +3314,7 @@ msgstr "Año" msgid "preferred bus" msgstr "bus preferido" -#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:36 +#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:38 #: apps/wei/templates/wei/busteam_detail.html:52 msgid "Teams" msgstr "Equipos" @@ -3372,9 +3386,9 @@ msgstr "Pago de entrada del WEI (estudiantes no pagados)" #: apps/wei/templates/wei/base.html:53 #, fuzzy -#| msgid "total amount" -msgid "Caution amount" -msgstr "monto total" +#| msgid "Credit amount" +msgid "Deposit amount" +msgstr "Valor del crédito" #: apps/wei/templates/wei/base.html:74 msgid "WEI list" @@ -3384,7 +3398,7 @@ msgstr "Lista de los WEI" msgid "Register 1A" msgstr "Apuntar un 1A" -#: apps/wei/templates/wei/base.html:83 apps/wei/views.py:644 +#: apps/wei/templates/wei/base.html:83 apps/wei/views.py:646 msgid "Register 2A+" msgstr "Apuntar un 2A+" @@ -3401,15 +3415,21 @@ msgid "View club" msgstr "Ver club" #: apps/wei/templates/wei/bus_detail.html:26 +#, fuzzy +#| msgid "survey information" +msgid "Edit information" +msgstr "informaciones sobre el cuestionario" + +#: apps/wei/templates/wei/bus_detail.html:28 #: apps/wei/templates/wei/busteam_detail.html:24 msgid "Add team" msgstr "Añadir un equipo" -#: apps/wei/templates/wei/bus_detail.html:49 +#: apps/wei/templates/wei/bus_detail.html:51 msgid "Members" msgstr "Miembros" -#: apps/wei/templates/wei/bus_detail.html:58 +#: apps/wei/templates/wei/bus_detail.html:60 #: apps/wei/templates/wei/busteam_detail.html:62 #: apps/wei/templates/wei/weimembership_list.html:31 msgid "View as PDF" @@ -3417,8 +3437,8 @@ msgstr "Descargar un 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:1159 -#: apps/wei/views.py:1214 apps/wei/views.py:1261 +#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1165 +#: apps/wei/views.py:1220 apps/wei/views.py:1267 msgid "Survey WEI" msgstr "Cuestionario WEI" @@ -3494,10 +3514,6 @@ msgstr "Informaciones crudas del cuestionario" msgid "The algorithm didn't run." msgstr "El algoritmo no funcionó." -#: apps/wei/templates/wei/weimembership_form.html:98 -msgid "caution check given" -msgstr "cheque de garantía dado" - #: apps/wei/templates/wei/weimembership_form.html:105 msgid "preferred team" msgstr "equipo preferido" @@ -3532,11 +3548,18 @@ msgid "with the following roles:" msgstr "con los papeles :" #: apps/wei/templates/wei/weimembership_form.html:139 +#, fuzzy +#| msgid "" +#| "The WEI will be paid by Société générale. The membership will be created " +#| "even if the bank didn't pay the BDE yet. The membership transaction will " +#| "be created but will be invalid. You will have to validate it once the " +#| "bank validated the creation of the account, or to change the payment " +#| "method." msgid "" -"The WEI will be paid by Société générale. The membership will be created " -"even if the bank didn't pay the BDE yet. The membership transaction will be " -"created but will be invalid. You will have to validate it once the bank " -"validated the creation of the account, or to change the payment method." +"The WEI will partially be paid by Société générale. The membership will be " +"created even if the bank didn't pay the BDE yet. The membership transaction " +"will be created but will be invalid. You will have to validate it once the " +"bank validated the creation of the account, or to change the payment method." msgstr "" "El WEI será pagado por la Société Générale. La afiliación será creada aunque " "el banco no pago el BDE ya. La transacción de afiliación será creada pero " @@ -3558,27 +3581,26 @@ msgstr "Pagos de afiliación (estudiantes pagados)" msgid "Deposit (by Note transaction): %(amount)s" msgstr "Fianza (transacción) : %(amount)s" -#: apps/wei/templates/wei/weimembership_form.html:156 -#: apps/wei/templates/wei/weimembership_form.html:163 -#, python-format -msgid "Total needed: %(total)s" -msgstr "Total necesario : %(total)s" - -#: apps/wei/templates/wei/weimembership_form.html:160 +#: apps/wei/templates/wei/weimembership_form.html:157 #, python-format msgid "Deposit (by check): %(amount)s" msgstr "Fianza (cheque) : %(amount)s" -#: apps/wei/templates/wei/weimembership_form.html:168 +#: apps/wei/templates/wei/weimembership_form.html:161 +#, python-format +msgid "Total needed: %(total)s" +msgstr "Total necesario : %(total)s" + +#: apps/wei/templates/wei/weimembership_form.html:165 #, python-format msgid "Current balance: %(balance)s" msgstr "Saldo actual : %(balance)s" -#: apps/wei/templates/wei/weimembership_form.html:176 +#: apps/wei/templates/wei/weimembership_form.html:172 msgid "The user didn't give her/his caution check." msgstr "El usuario no dio su cheque de garantía." -#: apps/wei/templates/wei/weimembership_form.html:184 +#: apps/wei/templates/wei/weimembership_form.html:180 msgid "" "This user is not a member of the Kfet club for the coming year. The " "membership will be processed automatically, the WEI registration includes " @@ -3668,110 +3690,109 @@ msgstr "Gestionar el equipo" msgid "Register first year student to the WEI" msgstr "Registrar un 1A al WEI" -#: apps/wei/views.py:580 apps/wei/views.py:689 +#: apps/wei/views.py:571 apps/wei/views.py:664 +#, fuzzy +#| msgid "Check this case if the Société Générale paid the inscription." +msgid "Check if you will open a Société Générale account" +msgstr "Marcar esta casilla si Société Générale pagó la registración." + +#: apps/wei/views.py:582 apps/wei/views.py:694 msgid "This user is already registered to this WEI." msgstr "Este usuario ya afilió a este WEI." -#: apps/wei/views.py:585 +#: apps/wei/views.py:587 msgid "" "This user can't be in her/his first year since he/she has already " "participated to a WEI." msgstr "Este usuario no puede ser un 1A porque ya participó en un WEI." -#: apps/wei/views.py:608 +#: apps/wei/views.py:610 msgid "Register old student to the WEI" msgstr "Registrar un 2A+ al WEI" -#: apps/wei/views.py:663 apps/wei/views.py:768 +#: apps/wei/views.py:668 apps/wei/views.py:773 msgid "You already opened an account in the Société générale." msgstr "Usted ya abrió una cuenta a la Société Générale." -#: apps/wei/views.py:676 apps/wei/views.py:785 +#: apps/wei/views.py:681 apps/wei/views.py:790 msgid "Choose how you want to pay the deposit" msgstr "" -#: apps/wei/views.py:728 +#: apps/wei/views.py:733 msgid "Update WEI Registration" msgstr "Modificar la inscripción WEI" -#: apps/wei/views.py:810 +#: apps/wei/views.py:816 #, fuzzy #| msgid "The BDE membership is included in the WEI registration." msgid "No membership found for this registration" msgstr "La afiliación al BDE esta incluida en la afiliación WEI." -#: apps/wei/views.py:819 -#| msgid "" -#| "You don't have the permission to add an instance of model {app_label}." -#| "{model_name}." +#: apps/wei/views.py:825 msgid "You don't have the permission to update memberships" msgstr "" "Usted no tiene permiso a añadir una instancia al modelo {app_label}." "{model_name}." -#: apps/wei/views.py:825 +#: apps/wei/views.py:831 #, python-format -#| msgid "" -#| "You don't have the permission to delete this instance of model " -#| "{app_label}.{model_name}." msgid "You don't have the permission to update the field %(field)s" msgstr "Usted no tiene permiso a modificar el campo %(field)s" -#: apps/wei/views.py:870 +#: apps/wei/views.py:876 msgid "Delete WEI registration" msgstr "Suprimir la inscripción WEI" -#: apps/wei/views.py:881 +#: apps/wei/views.py:887 msgid "You don't have the right to delete this WEI registration." msgstr "Usted no tiene derecho a suprimir esta inscripción WEI." -#: apps/wei/views.py:899 +#: apps/wei/views.py:905 msgid "Validate WEI registration" msgstr "Validar la inscripción WEI" -#: apps/wei/views.py:985 +#: apps/wei/views.py:998 msgid "Please make sure the check is given before validating the registration" msgstr "" "Por favor asegúrese de que el cheque se entrega antes de validar el registro" -#: apps/wei/views.py:991 -#| msgid "credit transaction" +#: apps/wei/views.py:1004 msgid "Create deposit transaction" msgstr "Crear transacción de crédito" -#: apps/wei/views.py:992 +#: apps/wei/views.py:1005 #, python-format msgid "" "A transaction of %(amount).2f€ will be created from the user's Note account" msgstr "" -#: apps/wei/views.py:1087 -#, python-format +#: apps/wei/views.py:1093 +#, fuzzy, python-format #| msgid "" -#| "This user don't have enough money to join this club, and can't have a " -#| "negative balance." +#| "This user doesn't have enough money. Current balance: %(balance)d€, " +#| "credit: %(credit)d€, needed: %(needed)d€" msgid "" -"This user doesn't have enough money. " +"This user doesn't have enough money to join this club and pay the deposit. " "Current balance: %(balance)d€, credit: %(credit)d€, needed: %(needed)d€" msgstr "" -"Este usuario no tiene suficiente dinero. " -"Saldo actual : %(balance)d€, crédito: %(credit)d€, requerido: %(needed)d€" +"Este usuario no tiene suficiente dinero. Saldo actual : %(balance)d€, " +"crédito: %(credit)d€, requerido: %(needed)d€" -#: apps/wei/views.py:1140 -#, python-format -#| msgid "created at" -msgid "Caution %(name)s" +#: apps/wei/views.py:1146 +#, fuzzy, python-format +#| msgid "Caution %(name)s" +msgid "Deposit %(name)s" msgstr "Fianza %(name)s" -#: apps/wei/views.py:1354 +#: apps/wei/views.py:1360 msgid "Attribute buses to first year members" msgstr "Repartir los primer años en los buses" -#: apps/wei/views.py:1379 +#: apps/wei/views.py:1386 msgid "Attribute bus" msgstr "Repartir en un bus" -#: apps/wei/views.py:1419 +#: apps/wei/views.py:1426 msgid "" "No first year student without a bus found. Either all of them have a bus, or " "none has filled the survey yet." @@ -4337,6 +4358,24 @@ msgstr "" "pagar su afiliación. Tambien tiene que validar su correo electronico con el " "enlace que recibió." +#, fuzzy +#~| msgid "total amount" +#~ msgid "caution amount" +#~ msgstr "monto total" + +#, fuzzy +#~| msgid "created at" +#~ msgid "caution type" +#~ msgstr "tipo de fianza" + +#, fuzzy +#~| msgid "total amount" +#~ msgid "Caution amount" +#~ msgstr "monto total" + +#~ msgid "caution check given" +#~ msgstr "cheque de garantía dado" + #, fuzzy #~| msgid "Invitation" #~ msgid "Syndication" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 9832c986..07121046 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: 2025-06-27 19:15+0200\n" +"POT-Creation-Date: 2025-07-15 18:17+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n" "Last-Translator: bleizi \n" "Language-Team: French \n" @@ -66,7 +66,7 @@ msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité." #: apps/note/models/transactions.py:46 apps/note/models/transactions.py:299 #: apps/permission/models.py:329 #: apps/registration/templates/registration/future_profile_detail.html:16 -#: apps/wei/models.py:72 apps/wei/models.py:145 apps/wei/tables.py:282 +#: apps/wei/models.py:77 apps/wei/models.py:150 apps/wei/tables.py:282 #: apps/wei/templates/wei/base.html:26 #: apps/wei/templates/wei/weimembership_form.html:14 apps/wrapped/models.py:16 msgid "name" @@ -101,7 +101,7 @@ msgstr "types d'activité" #: apps/activity/models.py:68 #: apps/activity/templates/activity/includes/activity_info.html:19 #: apps/note/models/transactions.py:82 apps/permission/models.py:109 -#: apps/permission/models.py:188 apps/wei/models.py:92 apps/wei/models.py:156 +#: apps/permission/models.py:188 apps/wei/models.py:97 apps/wei/models.py:161 msgid "description" msgstr "description" @@ -122,7 +122,7 @@ msgstr "type" #: 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:294 -#: apps/wei/models.py:185 apps/wei/templates/wei/attribute_bus_1A.html:13 +#: apps/wei/models.py:190 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/templates/wei/survey.html:15 msgid "user" msgstr "utilisateur⋅rice" @@ -1240,7 +1240,7 @@ msgid "add to registration form" msgstr "ajouter au formulaire d'inscription" #: apps/member/models.py:268 apps/member/models.py:331 -#: apps/note/models/notes.py:176 apps/wei/models.py:86 +#: apps/note/models/notes.py:176 apps/wei/models.py:91 msgid "club" msgstr "club" @@ -1962,8 +1962,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:1097 -#: apps/wei/views.py:1101 +#: apps/note/models/transactions.py:363 apps/wei/views.py:1103 +#: apps/wei/views.py:1107 msgid "This field is required." msgstr "Ce champ est requis." @@ -2468,7 +2468,7 @@ msgstr "" #: apps/registration/templates/registration/future_profile_detail.html:73 #: apps/wei/templates/wei/weimembership_form.html:127 -#: apps/wei/templates/wei/weimembership_form.html:196 +#: apps/wei/templates/wei/weimembership_form.html:192 msgid "Validate registration" msgstr "Valider l'inscription" @@ -2994,8 +2994,8 @@ msgstr "Liste des crédits de la Société générale" msgid "Manage credits from the Société générale" msgstr "Gérer les crédits de la Société générale" -#: apps/wei/apps.py:10 apps/wei/models.py:42 apps/wei/models.py:43 -#: apps/wei/models.py:67 apps/wei/models.py:192 +#: apps/wei/apps.py:10 apps/wei/models.py:47 apps/wei/models.py:48 +#: apps/wei/models.py:72 apps/wei/models.py:197 #: note_kfet/templates/base.html:108 msgid "WEI" msgstr "WEI" @@ -3006,8 +3006,8 @@ msgstr "" "L'utilisateur·rice sélectionné·e n'est pas validé·e. Merci de d'abord " "valider son compte" -#: apps/wei/forms/registration.py:84 apps/wei/models.py:140 -#: apps/wei/models.py:348 +#: apps/wei/forms/registration.py:84 apps/wei/models.py:145 +#: apps/wei/models.py:354 msgid "bus" msgstr "bus" @@ -3033,7 +3033,7 @@ msgstr "" "bus ou électron libre)" #: apps/wei/forms/registration.py:100 apps/wei/forms/registration.py:110 -#: apps/wei/models.py:174 +#: apps/wei/models.py:179 msgid "WEI Roles" msgstr "Rôles au WEI" @@ -3041,7 +3041,7 @@ msgstr "Rôles au WEI" msgid "Select the roles that you are interested in." msgstr "Sélectionnez les rôles qui vous intéressent." -#: apps/wei/forms/registration.py:147 +#: apps/wei/forms/registration.py:160 msgid "This team doesn't belong to the given bus." msgstr "Cette équipe n'appartient pas à ce bus." @@ -3069,137 +3069,142 @@ msgid "date end" msgstr "fin" #: apps/wei/models.py:37 -#, fuzzy -#| msgid "total amount" -msgid "caution amount" -msgstr "montant total" +msgid "deposit amount" +msgstr "montant de la caution" -#: apps/wei/models.py:76 apps/wei/tables.py:305 +#: apps/wei/models.py:42 +msgid "membership fee (soge credit)" +msgstr "Cotisation pour adhérer (crédit sogé)" + +#: apps/wei/models.py:81 apps/wei/tables.py:305 msgid "seat count in the bus" msgstr "nombre de sièges dans le bus" -#: apps/wei/models.py:97 +#: apps/wei/models.py:102 msgid "survey information" msgstr "informations sur le questionnaire" -#: apps/wei/models.py:98 +#: apps/wei/models.py:103 msgid "Information about the survey for new members, encoded in JSON" msgstr "" "Informations sur le sondage pour les nouveaux membres, encodées en JSON" -#: apps/wei/models.py:102 +#: apps/wei/models.py:107 msgid "Bus" msgstr "Bus" -#: apps/wei/models.py:103 apps/wei/templates/wei/weiclub_detail.html:51 +#: apps/wei/models.py:108 apps/wei/templates/wei/weiclub_detail.html:51 msgid "Buses" msgstr "Bus" -#: apps/wei/models.py:149 +#: apps/wei/models.py:154 msgid "color" msgstr "couleur" -#: apps/wei/models.py:150 +#: apps/wei/models.py:155 msgid "The color of the T-Shirt, stored with its number equivalent" msgstr "" "La couleur du T-Shirt, stocké sous la forme de son équivalent numérique" -#: apps/wei/models.py:161 +#: apps/wei/models.py:166 msgid "Bus team" msgstr "Équipe de bus" -#: apps/wei/models.py:162 +#: apps/wei/models.py:167 msgid "Bus teams" msgstr "Équipes de bus" -#: apps/wei/models.py:173 +#: apps/wei/models.py:178 msgid "WEI Role" msgstr "Rôle au WEI" -#: apps/wei/models.py:197 +#: apps/wei/models.py:202 msgid "Credit from Société générale" msgstr "Crédit de la Société générale" -#: apps/wei/models.py:202 apps/wei/views.py:984 -msgid "Caution check given" +#: apps/wei/models.py:207 apps/wei/templates/wei/weimembership_form.html:98 +#: apps/wei/views.py:997 +msgid "Deposit check given" msgstr "Chèque de caution donné" -#: apps/wei/models.py:208 +#: apps/wei/models.py:213 msgid "Check" -msgstr "" +msgstr "Chèque" -#: apps/wei/models.py:209 +#: apps/wei/models.py:214 msgid "Note transaction" msgstr "Transaction Note" -#: apps/wei/models.py:212 -msgid "caution type" -msgstr "date de création" +#: apps/wei/models.py:217 +#, fuzzy +#| msgid "Credit type" +msgid "deposit type" +msgstr "Type de rechargement" -#: apps/wei/models.py:216 apps/wei/templates/wei/weimembership_form.html:64 +#: apps/wei/models.py:221 apps/wei/templates/wei/weimembership_form.html:64 msgid "birth date" msgstr "date de naissance" -#: apps/wei/models.py:222 apps/wei/models.py:232 +#: apps/wei/models.py:227 apps/wei/models.py:237 msgid "Male" msgstr "Homme" -#: apps/wei/models.py:223 apps/wei/models.py:233 +#: apps/wei/models.py:228 apps/wei/models.py:238 msgid "Female" msgstr "Femme" -#: apps/wei/models.py:224 +#: apps/wei/models.py:229 msgid "Non binary" msgstr "Non-binaire" -#: apps/wei/models.py:226 apps/wei/templates/wei/attribute_bus_1A.html:22 +#: apps/wei/models.py:231 apps/wei/templates/wei/attribute_bus_1A.html:22 #: apps/wei/templates/wei/weimembership_form.html:55 msgid "gender" msgstr "genre" -#: apps/wei/models.py:234 +#: apps/wei/models.py:239 msgid "Unisex" msgstr "Unisexe" -#: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:58 +#: apps/wei/models.py:242 apps/wei/templates/wei/weimembership_form.html:58 msgid "clothing cut" msgstr "coupe de vêtement" -#: apps/wei/models.py:250 apps/wei/templates/wei/weimembership_form.html:61 +#: apps/wei/models.py:255 apps/wei/templates/wei/weimembership_form.html:61 msgid "clothing size" msgstr "taille de vêtement" -#: apps/wei/models.py:256 +#: apps/wei/models.py:261 msgid "health issues" msgstr "problèmes de santé" -#: apps/wei/models.py:261 apps/wei/templates/wei/weimembership_form.html:70 +#: apps/wei/models.py:266 apps/wei/templates/wei/weimembership_form.html:70 msgid "emergency contact name" msgstr "nom du contact en cas d'urgence" -#: apps/wei/models.py:262 +#: apps/wei/models.py:267 msgid "The emergency contact must not be a WEI participant" msgstr "" "Le contact en cas d'urgence ne doit pas être une personne qui participe au " "WEI" -#: apps/wei/models.py:267 apps/wei/templates/wei/weimembership_form.html:73 +#: apps/wei/models.py:272 apps/wei/templates/wei/weimembership_form.html:73 msgid "emergency contact phone" msgstr "téléphone du contact en cas d'urgence" -#: apps/wei/models.py:272 apps/wei/templates/wei/weimembership_form.html:52 +#: apps/wei/models.py:277 apps/wei/templates/wei/weimembership_form.html:52 msgid "first year" msgstr "première année" -#: apps/wei/models.py:273 +#: apps/wei/models.py:278 msgid "Tells if the user is new in the school." msgstr "Indique si l'utilisateur⋅rice est nouvelleeau dans l'école." -#: apps/wei/models.py:278 +#: apps/wei/models.py:283 msgid "registration information" msgstr "informations sur l'inscription" -#: apps/wei/models.py:279 +#: apps/wei/models.py:284 msgid "" "Information about the registration (buses for old members, survey for the " "new members), encoded in JSON" @@ -3207,27 +3212,27 @@ msgstr "" "Informations sur l'inscription (bus pour les 2A+, questionnaire pour les " "1A), encodées en JSON" -#: apps/wei/models.py:285 +#: apps/wei/models.py:290 msgid "WEI User" msgstr "Participant·e au WEI" -#: apps/wei/models.py:286 +#: apps/wei/models.py:291 msgid "WEI Users" msgstr "Participant·e·s au WEI" -#: apps/wei/models.py:358 +#: apps/wei/models.py:364 msgid "team" msgstr "équipe" -#: apps/wei/models.py:368 +#: apps/wei/models.py:374 msgid "WEI registration" msgstr "Inscription au WEI" -#: apps/wei/models.py:372 +#: apps/wei/models.py:378 msgid "WEI membership" msgstr "Adhésion au WEI" -#: apps/wei/models.py:373 +#: apps/wei/models.py:379 msgid "WEI memberships" msgstr "Adhésions au WEI" @@ -3322,10 +3327,8 @@ msgid "WEI fee (unpaid students)" msgstr "Prix du WEI (étudiant⋅es)" #: apps/wei/templates/wei/base.html:53 -#, fuzzy -#| msgid "total amount" -msgid "Caution amount" -msgstr "montant total" +msgid "Deposit amount" +msgstr "Caution" #: apps/wei/templates/wei/base.html:74 msgid "WEI list" @@ -3335,7 +3338,7 @@ msgstr "Liste des WEI" msgid "Register 1A" msgstr "Inscrire un⋅e 1A" -#: apps/wei/templates/wei/base.html:83 apps/wei/views.py:644 +#: apps/wei/templates/wei/base.html:83 apps/wei/views.py:646 msgid "Register 2A+" msgstr "Inscrire un⋅e 2A+" @@ -3352,7 +3355,6 @@ msgid "View club" msgstr "Voir le club" #: apps/wei/templates/wei/bus_detail.html:26 -#| msgid "survey information" msgid "Edit information" msgstr "Modifier les informations" @@ -3373,8 +3375,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:1159 -#: apps/wei/views.py:1214 apps/wei/views.py:1261 +#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1165 +#: apps/wei/views.py:1220 apps/wei/views.py:1267 msgid "Survey WEI" msgstr "Questionnaire WEI" @@ -3451,10 +3453,6 @@ msgstr "Informations brutes du sondage" msgid "The algorithm didn't run." msgstr "L'algorithme n'a pas été exécuté." -#: apps/wei/templates/wei/weimembership_form.html:98 -msgid "caution check given" -msgstr "chèque de caution donné" - #: apps/wei/templates/wei/weimembership_form.html:105 msgid "preferred team" msgstr "équipe préférée" @@ -3490,52 +3488,53 @@ msgstr "avec les rôles suivants :" #: apps/wei/templates/wei/weimembership_form.html:139 msgid "" -"The WEI will be paid by Société générale. The membership will be created " -"even if the bank didn't pay the BDE yet. The membership transaction will be " -"created but will be invalid. You will have to validate it once the bank " -"validated the creation of the account, or to change the payment method." +"The WEI will partially be paid by Société générale. The membership will be " +"created even if the bank didn't pay the BDE yet. The membership transaction " +"will be created but will be invalid. You will have to validate it once the " +"bank validated the creation of the account, or to change the payment method." msgstr "" -"Le WEI va être payé par la Société générale. L'adhésion sera créée même si " -"la banque n'a pas encore payé le BDE. La transaction d'adhésion sera créée " -"mais invalide. Vous devrez la valider une fois que la banque aura validé la " -"création du compte, ou bien changer de moyen de paiement." +"Le WEI va être partiellement payé par la Société générale. L'adhésion sera " +"créée même si la banque n'a pas encore payé le BDE. La transaction " +"d'adhésion sera créée mais invalide. Vous devrez la valider une fois que la " +"banque aura validé la création du compte, ou bien changer de moyen de " +"paiement." #: apps/wei/templates/wei/weimembership_form.html:147 msgid "Required payments:" msgstr "Paiements requis" #: apps/wei/templates/wei/weimembership_form.html:149 -#, fuzzy, python-format -#| msgid "membership fee (paid students)" +#, python-format msgid "Membership fees: %(amount)s" -msgstr "cotisation pour adhérer (normalien·ne élève)" +msgstr "Frais d'inscription : %(amount)s" #: apps/wei/templates/wei/weimembership_form.html:153 #, python-format msgid "Deposit (by Note transaction): %(amount)s" msgstr "Caution (par transaction) : %(amount)s" -#: apps/wei/templates/wei/weimembership_form.html:156 -#: apps/wei/templates/wei/weimembership_form.html:163 -#, python-format -msgid "Total needed: %(total)s" -msgstr "Total nécessaire : %(total)s" - -#: apps/wei/templates/wei/weimembership_form.html:160 +#: apps/wei/templates/wei/weimembership_form.html:157 #, python-format msgid "Deposit (by check): %(amount)s" msgstr "Caution (par chèque) : %(amount)s" -#: apps/wei/templates/wei/weimembership_form.html:168 +#: apps/wei/templates/wei/weimembership_form.html:161 +#, python-format +msgid "Total needed: %(total)s" +msgstr "Total nécessaire : %(total)s" + +#: apps/wei/templates/wei/weimembership_form.html:165 #, python-format msgid "Current balance: %(balance)s" msgstr "Solde actuel : %(balance)s" -#: apps/wei/templates/wei/weimembership_form.html:176 +#: apps/wei/templates/wei/weimembership_form.html:172 +#, fuzzy +#| msgid "The user didn't give her/his deposit check." msgid "The user didn't give her/his caution check." msgstr "L'utilisateur⋅rice n'a pas donné son chèque de caution." -#: apps/wei/templates/wei/weimembership_form.html:184 +#: apps/wei/templates/wei/weimembership_form.html:180 msgid "" "This user is not a member of the Kfet club for the coming year. The " "membership will be processed automatically, the WEI registration includes " @@ -3626,11 +3625,15 @@ msgstr "Gérer l'équipe WEI" msgid "Register first year student to the WEI" msgstr "Inscrire un⋅e 1A au WEI" -#: apps/wei/views.py:580 apps/wei/views.py:689 +#: apps/wei/views.py:571 apps/wei/views.py:664 +msgid "Check if you will open a Société Générale account" +msgstr "Cochez cette case si vous ouvrez un compte à la Société Générale." + +#: apps/wei/views.py:582 apps/wei/views.py:694 msgid "This user is already registered to this WEI." msgstr "Cette personne est déjà inscrite au WEI." -#: apps/wei/views.py:585 +#: apps/wei/views.py:587 msgid "" "This user can't be in her/his first year since he/she has already " "participated to a WEI." @@ -3638,95 +3641,94 @@ 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:608 +#: apps/wei/views.py:610 msgid "Register old student to the WEI" msgstr "Inscrire un⋅e 2A+ au WEI" -#: apps/wei/views.py:663 apps/wei/views.py:768 +#: apps/wei/views.py:668 apps/wei/views.py:773 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:676 apps/wei/views.py:785 +#: apps/wei/views.py:681 apps/wei/views.py:790 msgid "Choose how you want to pay the deposit" -msgstr "" +msgstr "Choisissez comment payer la caution" -#: apps/wei/views.py:728 +#: apps/wei/views.py:733 msgid "Update WEI Registration" msgstr "Modifier l'inscription WEI" -#: apps/wei/views.py:810 +#: apps/wei/views.py:816 msgid "No membership found for this registration" msgstr "Pas d'adhésion trouvée pour cette inscription" -#: apps/wei/views.py:819 -msgid "You don't have the permission to update memberships" -msgstr "" -"Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}." -"{model_name}." - #: apps/wei/views.py:825 +msgid "You don't have the permission to update memberships" +msgstr "Vous n'avez pas la permission de modifier une inscription" + +#: apps/wei/views.py:831 #, python-format msgid "You don't have the permission to update the field %(field)s" msgstr "Vous n'avez pas la permission de modifier le champ %(field)s" -#: apps/wei/views.py:870 +#: apps/wei/views.py:876 msgid "Delete WEI registration" msgstr "Supprimer l'inscription WEI" -#: apps/wei/views.py:881 +#: apps/wei/views.py:887 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:899 +#: apps/wei/views.py:905 msgid "Validate WEI registration" msgstr "Valider l'inscription WEI" -#: apps/wei/views.py:985 +#: apps/wei/views.py:998 msgid "Please make sure the check is given before validating the registration" msgstr "" "Merci de vous assurer que le chèque a bien été donné avant de valider " "l'adhésion" -#: apps/wei/views.py:991 +#: apps/wei/views.py:1004 msgid "Create deposit transaction" msgstr "Créer une transaction de caution" -#: apps/wei/views.py:992 +#: apps/wei/views.py:1005 #, python-format msgid "" "A transaction of %(amount).2f€ will be created from the user's Note account" msgstr "" "Un transaction de %(amount).2f€ va être créée depuis la note de l'utilisateur" -#: apps/wei/views.py:1087 +#: apps/wei/views.py:1093 #, python-format msgid "" "This user doesn't have enough money to join this club and pay the deposit. " "Current balance: %(balance)d€, credit: %(credit)d€, needed: %(needed)d€" msgstr "" "Cet⋅te utilisateur⋅rice n'a pas assez d'argent pour rejoindre ce club et " -"payer la cautionSolde actuel : %(balance)d€, crédit : %(credit)d€, requis : " -"%(needed)d€" +"payer la caution. Solde actuel : %(balance)d€, crédit : %(credit)d€, " +"requis : %(needed)d€" -#: apps/wei/views.py:1140 -#, fuzzy, python-format -#| msgid "total amount" -msgid "Caution %(name)s" -msgstr "montant total" +#: apps/wei/views.py:1146 +#, python-format +msgid "Deposit %(name)s" +msgstr "Caution %(name)s" -#: apps/wei/views.py:1354 +#: apps/wei/views.py:1360 msgid "Attribute buses to first year members" msgstr "Répartir les 1A dans les bus" -#: apps/wei/views.py:1380 +#: apps/wei/views.py:1386 msgid "Attribute bus" msgstr "Attribuer un bus" -#: apps/wei/views.py:1420 +#: apps/wei/views.py:1426 msgid "" "No first year student without a bus found. Either all of them have a bus, or " "none has filled the survey yet." msgstr "" +"Aucun 1A sans bus trouvé. Soit ils ont tous été attribués, soitaucun n'a " +"encore rempli le sondage." #: apps/wrapped/apps.py:10 msgid "wrapped" @@ -4289,9 +4291,20 @@ msgstr "" "d'adhésion. Vous devez également valider votre adresse email en suivant le " "lien que vous avez reçu." -#, fuzzy, python-format -#~| msgid "Creation date" -#~ msgid "Deposit %(name)s" +#~ msgid "caution amount" +#~ msgstr "montant de la caution" + +#~ msgid "caution type" +#~ msgstr "type de caution" + +#~ msgid "Caution amount" +#~ msgstr "Montant de la caution" + +#~ msgid "caution check given" +#~ msgstr "chèque de caution donné" + +#, python-format +#~ msgid "Caution %(name)s" #~ msgstr "Caution %(name)s" #, fuzzy @@ -4675,9 +4688,6 @@ msgstr "" #~ msgid "Application requires the following permissions" #~ msgstr "L'application requiert les permissions suivantes :" -#~ msgid "Deposit amount" -#~ msgstr "Caution" - #~ msgid "The BDE membership is included in the WEI registration." #~ msgstr "L'adhésion au BDE est offerte avec l'inscription au WEI." From 3ebadf34bc29914fbc2da60501ca28f53b4713d5 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Thu, 17 Jul 2025 16:59:57 +0200 Subject: [PATCH 070/153] Challenge Update and Create View --- apps/family/forms.py | 21 +++++++++++++++++++ .../templates/family/challenge_detail.html | 6 +++++- .../templates/family/challenge_update.html | 21 +++++++++++++++++++ apps/family/views.py | 17 +++++++++++++-- 4 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 apps/family/forms.py create mode 100644 apps/family/templates/family/challenge_update.html diff --git a/apps/family/forms.py b/apps/family/forms.py new file mode 100644 index 00000000..63b47f48 --- /dev/null +++ b/apps/family/forms.py @@ -0,0 +1,21 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django import forms +from django.forms.widgets import NumberInput +from django.utils.translation import gettext_lazy as _ +from note_kfet.inputs import Autocomplete + +from .models import Challenge, FamilyMembership, User + + +class ChallengeUpdateForm(forms.ModelForm): + """ + To update a challenge + """ + class Meta: + model = Challenge + fields = ('name', 'description', 'points',) + widgets = { + "points": NumberInput() + } diff --git a/apps/family/templates/family/challenge_detail.html b/apps/family/templates/family/challenge_detail.html index 44addf57..23cb3f93 100644 --- a/apps/family/templates/family/challenge_detail.html +++ b/apps/family/templates/family/challenge_detail.html @@ -26,7 +26,11 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "Return to the challenge list" %} - + {% if update %} + + {% trans "Update" %} + + {% endif %} {% endblock %} diff --git a/apps/family/templates/family/challenge_update.html b/apps/family/templates/family/challenge_update.html new file mode 100644 index 00000000..27c7bed2 --- /dev/null +++ b/apps/family/templates/family/challenge_update.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n crispy_forms_tags %} + +{% block content %} +
    +

    + {{ title }} +

    +
    +
    + {% csrf_token %} + {{ form | crispy }} + +
    +
    +
    +{% endblock %} diff --git a/apps/family/views.py b/apps/family/views.py index d8072dbb..4b710681 100644 --- a/apps/family/views.py +++ b/apps/family/views.py @@ -1,15 +1,19 @@ # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +from datetime import date + from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import DetailView, UpdateView from django.utils.translation import gettext_lazy as _ from django_tables2 import SingleTableView from permission.backends import PermissionBackend from permission.views import ProtectQuerysetMixin, ProtectedCreateView +from django.urls import reverse_lazy -from .models import Family, Challenge -from .tables import FamilyTable, ChallengeTable +from .models import Family, Challenge, FamilyMembership, User +from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable +from .forms import ChallengeUpdateForm class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView): @@ -68,6 +72,9 @@ class ChallengeCreateView(ProtectQuerysetMixin, ProtectedCreateView): description="Sample challenge", points=0, ) + + def get_success_url(self): + return reverse_lazy('family:challenge_list') class ChallengeListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): @@ -101,6 +108,7 @@ class ChallengeDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): return context + class ChallengeUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): """ Update the information of a challenge @@ -109,3 +117,8 @@ class ChallengeUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): context_object_name = "challenge" extra_context = {"title": _('Update challenge')} template_name = 'family/challenge_update.html' + form_class = ChallengeUpdateForm + + def get_success_url(self, **kwargs): + self.object.refresh_from_db() + return reverse_lazy('family:challenge_detail', kwargs={'pk': self.object.pk}) \ No newline at end of file From 65dd42fc977b2235697686d0d29ded770dc66351 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Thu, 17 Jul 2025 17:07:47 +0200 Subject: [PATCH 071/153] Family views --- apps/family/forms.py | 19 ++++- apps/family/tables.py | 16 ++++- apps/family/templates/family/add_member.html | 60 ++++++++++++++++ apps/family/templates/family/base.html | 72 +++++++++++++++++++ .../templates/family/family_detail.html | 3 +- apps/family/urls.py | 4 +- apps/family/views.py | 61 ++++++++++++++-- 7 files changed, 227 insertions(+), 8 deletions(-) create mode 100644 apps/family/templates/family/add_member.html create mode 100644 apps/family/templates/family/base.html diff --git a/apps/family/forms.py b/apps/family/forms.py index 63b47f48..78c6d25f 100644 --- a/apps/family/forms.py +++ b/apps/family/forms.py @@ -3,7 +3,6 @@ from django import forms from django.forms.widgets import NumberInput -from django.utils.translation import gettext_lazy as _ from note_kfet.inputs import Autocomplete from .models import Challenge, FamilyMembership, User @@ -19,3 +18,21 @@ class ChallengeUpdateForm(forms.ModelForm): widgets = { "points": NumberInput() } + + +class FamilyMembershipForm(forms.ModelForm): + class Meta: + model = FamilyMembership + fields = ('user', ) + + widgets = { + "user": + Autocomplete( + User, + attrs={ + 'api_url': '/api/user/', + 'name_field': 'username', + 'placeholder': 'Nom ...', + }, + ) + } diff --git a/apps/family/tables.py b/apps/family/tables.py index de00b815..4172b975 100644 --- a/apps/family/tables.py +++ b/apps/family/tables.py @@ -4,7 +4,7 @@ import django_tables2 as tables from django_tables2 import A -from .models import Family, Challenge +from .models import Family, Challenge, FamilyMembership class FamilyTable(tables.Table): @@ -43,3 +43,17 @@ class ChallengeTable(tables.Table): model = Challenge template_name = 'django_tables2/bootstrap4.html' fields = ('name', 'description', 'points',) + + +class FamilyMembershipTable(tables.Table): + """ + List all family memberships. + """ + class Meta: + attrs = { + 'class': 'table table-condensed table-striped', + 'style': 'table-layout: fixed;' + } + template_name = 'django_tables2/bootstrap4.html' + fields = ('user',) + model = FamilyMembership diff --git a/apps/family/templates/family/add_member.html b/apps/family/templates/family/add_member.html new file mode 100644 index 00000000..6f77283d --- /dev/null +++ b/apps/family/templates/family/add_member.html @@ -0,0 +1,60 @@ +{% extends "family/base.html" %} +{% comment %} +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load crispy_forms_tags i18n pretty_money %} + +{% block profile_content %} +
    +

    + {{ title }} +

    +
    + +
    + {% csrf_token %} + {{ form|crispy }} + +
    +
    +
    +{% endblock %} + +{% block extrajavascript %} + +{% endblock %} \ No newline at end of file diff --git a/apps/family/templates/family/base.html b/apps/family/templates/family/base.html new file mode 100644 index 00000000..56789907 --- /dev/null +++ b/apps/family/templates/family/base.html @@ -0,0 +1,72 @@ +{% extends "base.html" %} +{% comment %} +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n perms %} + +{# Use a fluid-width container #} +{% block containertype %}container-fluid{% endblock %} + +{% block content %} +
    +
    + {% block profile_info %} +
    +

    + {% if user_object %} + {% trans "Account #" %}{{ user_object.pk }} + {% elif club %} + Club {{ club.name }} + {% endif %} +

    +
    + {% if user_object %} + + + + {% elif club %} + + + + {% endif %} +
    +
    + {% if user_object %} + {% include "member/includes/profile_info.html" %} + {% elif club %} + {% include "member/includes/club_info.html" %} + {% endif %} +
    + +
    + {% endblock %} +
    +
    + {% block profile_content %}{% endblock %} +
    +
    +{% endblock %} \ No newline at end of file diff --git a/apps/family/templates/family/family_detail.html b/apps/family/templates/family/family_detail.html index b8f9d918..1f5f8e56 100644 --- a/apps/family/templates/family/family_detail.html +++ b/apps/family/templates/family/family_detail.html @@ -1,5 +1,6 @@ -{% extends "base.html" %} +{% extends "family/base.html" %} {% comment %} Copyright (C) 2018-2025 by BDE ENS Paris-Saclay SPDX-License-Identifier: GPL-3.0-or-later {% endcomment %} + diff --git a/apps/family/urls.py b/apps/family/urls.py index 622d8b54..9a17a481 100644 --- a/apps/family/urls.py +++ b/apps/family/urls.py @@ -3,12 +3,14 @@ from django.urls import path -from .views import FamilyListView, FamilyDetailView, ChallengeListView, ChallengeDetailView, ChallengeUpdateView +from .views import FamilyListView, FamilyDetailView, FamilyUpdateView, FamilyAddMemberView, ChallengeListView, ChallengeDetailView, ChallengeUpdateView app_name = 'family' urlpatterns = [ path('list/', FamilyListView.as_view(), name="family_list"), path('detail//', FamilyDetailView.as_view(), name="family_detail"), + path('update//', FamilyUpdateView.as_view(), name="family_update"), + path('/add_member', FamilyAddMemberView.as_view(), name="family_add_member"), path('challenge/list/', ChallengeListView.as_view(), name="challenge_list"), path('challenge/detail//', ChallengeDetailView.as_view(), name="challenge_detail"), path('challenge/update//', ChallengeUpdateView.as_view(), name="challenge_update"), diff --git a/apps/family/views.py b/apps/family/views.py index 4b710681..6f6e3d48 100644 --- a/apps/family/views.py +++ b/apps/family/views.py @@ -13,7 +13,7 @@ from django.urls import reverse_lazy from .models import Family, Challenge, FamilyMembership, User from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable -from .forms import ChallengeUpdateForm +from .forms import ChallengeUpdateForm, FamilyMembershipForm class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView): @@ -49,6 +49,35 @@ class FamilyDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context_object_name = "family" extra_context = {"title": _('Family detail')} + def get_context_data(self, **kwargs): + """ + Add members list + """ + context = super().get_context_data(**kwargs) + + family = self.object + + # member list + family_member = FamilyMembership.objects.filter( + family=family, + year=date.today().year, + ).filter(PermissionBackend.filter_queryset(self.request, FamilyMembership, "view"))\ + .order_by("user__username").distinct("user__username") + + membership_table = FamilyMembershipTable(data=family_member) + context['member_list'] = membership_table + + # Check if the user has the right to create a membership, to display the button. + empty_membership = FamilyMembership( + family=family, + user=User.objects.first(), + year=date.today().year, + ) + context["can_add_members"] = PermissionBackend()\ + .has_perm(self.request.user, "family.add_membership", empty_membership) + + return context + class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): """ @@ -59,6 +88,30 @@ class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): extra_context = {"title": _('Update family')} +class FamilyAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): + """ + Add a membership to a family + """ + model = FamilyMembership + form_class = FamilyMembershipForm + template_name = 'family/add_member.html' + extra_context = {"title": _("Add a new member to the family")} + + def get_sample_object(self): + if "family_pk" in self.kwargs: + family = Family.objects.get(pk=self.kwargs["family_pk"]) + else: + family = FamilyMembership.objects.get(pk=self.kwargs["pk"]).family + return FamilyMembership( + user=self.request.user, + family=family, + year=date.today().year, + ) + + def get_success_url(self): + return reverse_lazy('family:family_detail', kwargs={'pk': self.object.family.id}) + + class ChallengeCreateView(ProtectQuerysetMixin, ProtectedCreateView): """ Create challenge @@ -72,7 +125,7 @@ class ChallengeCreateView(ProtectQuerysetMixin, ProtectedCreateView): description="Sample challenge", points=0, ) - + def get_success_url(self): return reverse_lazy('family:challenge_list') @@ -103,7 +156,7 @@ class ChallengeDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context["fields"] = [( Challenge._meta.get_field(field).verbose_name.capitalize(), value) for field, value in fields.items()] - context["obtained"] = getattr(self.object, "obtained") + context["obtained"] = self.object.obtained context["update"] = PermissionBackend.check_perm(self.request, "family.change_challenge") return context @@ -121,4 +174,4 @@ class ChallengeUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): def get_success_url(self, **kwargs): self.object.refresh_from_db() - return reverse_lazy('family:challenge_detail', kwargs={'pk': self.object.pk}) \ No newline at end of file + return reverse_lazy('family:challenge_detail', kwargs={'pk': self.object.pk}) From 249b797d5a1cfc3f7f2ee92c854d23b986f80ffa Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Thu, 17 Jul 2025 19:08:34 +0200 Subject: [PATCH 072/153] Base template and picture --- apps/family/forms.py | 8 +- .../migrations/0002_family_display_image.py | 18 +++ apps/family/models.py | 9 ++ apps/family/templates/family/base.html | 40 ++---- .../templates/family/family_detail.html | 10 ++ apps/family/templates/family/family_info.html | 15 +++ .../templates/family/family_update.html | 21 ++++ .../templates/family/picture_update.html | 118 ++++++++++++++++++ apps/family/urls.py | 5 +- apps/family/views.py | 71 ++++++++++- 10 files changed, 279 insertions(+), 36 deletions(-) create mode 100644 apps/family/migrations/0002_family_display_image.py create mode 100644 apps/family/templates/family/family_info.html create mode 100644 apps/family/templates/family/family_update.html create mode 100644 apps/family/templates/family/picture_update.html diff --git a/apps/family/forms.py b/apps/family/forms.py index 78c6d25f..8a36d289 100644 --- a/apps/family/forms.py +++ b/apps/family/forms.py @@ -5,7 +5,7 @@ from django import forms from django.forms.widgets import NumberInput from note_kfet.inputs import Autocomplete -from .models import Challenge, FamilyMembership, User +from .models import Challenge, FamilyMembership, User, Family class ChallengeUpdateForm(forms.ModelForm): @@ -36,3 +36,9 @@ class FamilyMembershipForm(forms.ModelForm): }, ) } + + +class FamilyUpdateForm(forms.ModelForm): + class Meta: + model = Family + fields = ('description', ) \ No newline at end of file diff --git a/apps/family/migrations/0002_family_display_image.py b/apps/family/migrations/0002_family_display_image.py new file mode 100644 index 00000000..d2cf118b --- /dev/null +++ b/apps/family/migrations/0002_family_display_image.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.23 on 2025-07-17 15:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('family', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='family', + name='display_image', + field=models.ImageField(default='pic/default.png', max_length=255, upload_to='pic/', verbose_name='display image'), + ), + ] diff --git a/apps/family/models.py b/apps/family/models.py index 46e8af47..1d2d0d34 100644 --- a/apps/family/models.py +++ b/apps/family/models.py @@ -28,6 +28,15 @@ class Family(models.Model): verbose_name=_('rank'), ) + display_image = models.ImageField( + verbose_name=_('display image'), + max_length=255, + blank=False, + null=False, + upload_to='pic/', + default='pic/default.png' + ) + class Meta: verbose_name = _('Family') verbose_name_plural = _('Families') diff --git a/apps/family/templates/family/base.html b/apps/family/templates/family/base.html index 56789907..444dffed 100644 --- a/apps/family/templates/family/base.html +++ b/apps/family/templates/family/base.html @@ -13,29 +13,15 @@ SPDX-License-Identifier: GPL-3.0-or-later {% block profile_info %}

    - {% if user_object %} - {% trans "Account #" %}{{ user_object.pk }} - {% elif club %} - Club {{ club.name }} - {% endif %} + {{ family.name }}

    - {% if user_object %} - - + + - {% elif club %} - - - - {% endif %}
    - {% if user_object %} - {% include "member/includes/profile_info.html" %} - {% elif club %} - {% include "member/includes/club_info.html" %} - {% endif %} + {% include "family/family_info.html" %}
    {% endblock %} diff --git a/apps/family/templates/family/family_detail.html b/apps/family/templates/family/family_detail.html index 1f5f8e56..a1db566f 100644 --- a/apps/family/templates/family/family_detail.html +++ b/apps/family/templates/family/family_detail.html @@ -3,4 +3,14 @@ Copyright (C) 2018-2025 by BDE ENS Paris-Saclay SPDX-License-Identifier: GPL-3.0-or-later {% endcomment %} +{% load render_table from django_tables2 %} +{% load i18n perms %} +{% block profile_content %} +
    +
    + {% trans "Family members" %} +
    + {% render_table member_list %} +
    +{% endblock %} \ No newline at end of file diff --git a/apps/family/templates/family/family_info.html b/apps/family/templates/family/family_info.html new file mode 100644 index 00000000..359fe6ef --- /dev/null +++ b/apps/family/templates/family/family_info.html @@ -0,0 +1,15 @@ +{% load i18n pretty_money perms %} + +
    +
    {% trans 'name'|capfirst %}
    +
    {{ family.name }}
    + +
    {% trans 'description'|capfirst %}
    +
    {{ family.description }}
    + +
    {% trans 'score'|capfirst %}
    +
    {{ family.score }}
    + +
    {% trans 'rank'|capfirst %}
    +
    {{ family.rank }}
    +
    diff --git a/apps/family/templates/family/family_update.html b/apps/family/templates/family/family_update.html new file mode 100644 index 00000000..27c7bed2 --- /dev/null +++ b/apps/family/templates/family/family_update.html @@ -0,0 +1,21 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n crispy_forms_tags %} + +{% block content %} +
    +

    + {{ title }} +

    +
    +
    + {% csrf_token %} + {{ form | crispy }} + +
    +
    +
    +{% endblock %} diff --git a/apps/family/templates/family/picture_update.html b/apps/family/templates/family/picture_update.html new file mode 100644 index 00000000..e5c6749c --- /dev/null +++ b/apps/family/templates/family/picture_update.html @@ -0,0 +1,118 @@ +{% extends "family/base.html" %} +{% comment %} +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n crispy_forms_tags %} + +{% block profile_content %} +
    +

    + {{ title }} +

    +
    +
    +
    + {% csrf_token %} + {{ form |crispy }} + {% if user.note.display_image != "pic/default.png" %} + + {% endif %} +
    +
    + + +
    +
    +{% endblock %} + +{% block extracss %} + +{% endblock %} + +{% block extrajavascript%} + + + +{% endblock %} diff --git a/apps/family/urls.py b/apps/family/urls.py index 9a17a481..e86bc0b5 100644 --- a/apps/family/urls.py +++ b/apps/family/urls.py @@ -3,14 +3,15 @@ from django.urls import path -from .views import FamilyListView, FamilyDetailView, FamilyUpdateView, FamilyAddMemberView, ChallengeListView, ChallengeDetailView, ChallengeUpdateView +from .views import FamilyListView, FamilyDetailView, FamilyUpdateView, FamilyPictureUpdateView, FamilyAddMemberView, ChallengeListView, ChallengeDetailView, ChallengeUpdateView app_name = 'family' urlpatterns = [ path('list/', FamilyListView.as_view(), name="family_list"), path('detail//', FamilyDetailView.as_view(), name="family_detail"), path('update//', FamilyUpdateView.as_view(), name="family_update"), - path('/add_member', FamilyAddMemberView.as_view(), name="family_add_member"), + path('update_pic//', FamilyPictureUpdateView.as_view(), name="update_pic"), + path('add_member//', FamilyAddMemberView.as_view(), name="family_add_member"), path('challenge/list/', ChallengeListView.as_view(), name="challenge_list"), path('challenge/detail//', ChallengeDetailView.as_view(), name="challenge_detail"), path('challenge/update//', ChallengeUpdateView.as_view(), name="challenge_update"), diff --git a/apps/family/views.py b/apps/family/views.py index 6f6e3d48..75a82e0c 100644 --- a/apps/family/views.py +++ b/apps/family/views.py @@ -3,7 +3,9 @@ from datetime import date +from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin +from django.db import transaction from django.views.generic import DetailView, UpdateView from django.utils.translation import gettext_lazy as _ from django_tables2 import SingleTableView @@ -13,7 +15,9 @@ from django.urls import reverse_lazy from .models import Family, Challenge, FamilyMembership, User from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable -from .forms import ChallengeUpdateForm, FamilyMembershipForm +from .forms import ChallengeUpdateForm, FamilyMembershipForm, FamilyUpdateForm +from member.forms import ImageForm +from member.views import PictureUpdateView class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView): @@ -62,9 +66,12 @@ class FamilyDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): family=family, year=date.today().year, ).filter(PermissionBackend.filter_queryset(self.request, FamilyMembership, "view"))\ - .order_by("user__username").distinct("user__username") + .order_by("user__username") + family_member = family_member.distinct("user__username")\ + if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else family_member - membership_table = FamilyMembershipTable(data=family_member) + membership_table = FamilyMembershipTable(data=family_member, prefix="membership-") + membership_table.paginate(per_page=5, page=self.request.GET.get('membership-page', 1)) context['member_list'] = membership_table # Check if the user has the right to create a membership, to display the button. @@ -85,8 +92,43 @@ class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): """ model = Family context_object_name = "family" + form_class = FamilyUpdateForm + template_name = 'family/family_update.html' extra_context = {"title": _('Update family')} + def get_success_url(self): + return reverse_lazy('family:family_detail', kwargs={'pk': self.object.pk}) + + +class FamilyPictureUpdateView(PictureUpdateView): + """ + Update profile picture of the family + """ + model = Family + extra_context = {"title": _("Update family picture")} + template_name = 'family/picture_update.html' + + def get_success_url(self): + """Redirect to family page after upload""" + return reverse_lazy('family:family_detail', kwargs={'pk': self.object.id}) + + @transaction.atomic + def form_valid(self, form): + """ + Save the image + """ + image = form.cleaned_data['image'] + + if image is None: + image = "pic/default.png" + else: + # Rename as PNG or GIF + extension = image.name.split(".")[-1] + if extension == "gif": + image.name = "{}_pic.gif".format(self.object.pk) + else: + image.name = "{}_pic.png".format(self.object.pk) + class FamilyAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): """ @@ -108,6 +150,29 @@ class FamilyAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): year=date.today().year, ) + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + form = context['form'] + + family = Family.objects.filter(PermissionBackend.filter_queryset(self.request, Family, "view"))\ + .get(pk=self.kwargs['family_pk']) + + context['family'] = family + + return context + + @transaction.atomic + def form_valid(self, form): + """ + Create family membership, check that everythinf is good + """ + family = Family.objects.filter(PermissionBackend.filter_queryset(self.request, Family, "view")) \ + .get(pk=self.kwargs["family_pk"]) + + form.instance.family = family + + return super().form_valid(form) + def get_success_url(self): return reverse_lazy('family:family_detail', kwargs={'pk': self.object.family.id}) From ab9abc852002c83647e704bc2fbf644898bf112f Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Thu, 17 Jul 2025 20:07:12 +0200 Subject: [PATCH 073/153] Better list tables --- apps/family/tables.py | 21 ++++++++++--------- .../templates/family/challenge_list.html | 7 +++++++ apps/family/templates/family/family_list.html | 8 +++++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/apps/family/tables.py b/apps/family/tables.py index 4172b975..dd3d916c 100644 --- a/apps/family/tables.py +++ b/apps/family/tables.py @@ -3,6 +3,7 @@ import django_tables2 as tables from django_tables2 import A +from django.urls import reverse from .models import Family, Challenge, FamilyMembership @@ -11,11 +12,6 @@ class FamilyTable(tables.Table): """ List all families """ - name = tables.LinkColumn( - "family:family_detail", - args=[A("pk")], - ) - class Meta: attrs = { 'class': 'table table-condensed table-striped table-hover' @@ -24,17 +20,17 @@ class FamilyTable(tables.Table): template_name = 'django_tables2/bootstrap4.html' fields = ('name', 'score', 'rank',) order_by = ('rank',) + row_attrs = { + 'class': 'table-row', + 'data-href': lambda record: reverse('family:family_detail', args=[record.pk]), + 'style': 'cursor:pointer', + } class ChallengeTable(tables.Table): """ List all challenges """ - name = tables.LinkColumn( - "family:challenge_detail", - args=[A("pk")], - ) - class Meta: attrs = { 'class': 'table table-condensed table-striped table-hover' @@ -43,6 +39,11 @@ class ChallengeTable(tables.Table): model = Challenge template_name = 'django_tables2/bootstrap4.html' fields = ('name', 'description', 'points',) + row_attrs = { + 'class': 'table-row', + 'data-href': lambda record: reverse('family:challenge_detail', args=[record.pk]), + 'style': 'cursor:pointer', + } class FamilyMembershipTable(tables.Table): diff --git a/apps/family/templates/family/challenge_list.html b/apps/family/templates/family/challenge_list.html index f16f37a7..c84b80ce 100644 --- a/apps/family/templates/family/challenge_list.html +++ b/apps/family/templates/family/challenge_list.html @@ -28,3 +28,10 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endblock %} +{% block extrajavascript %} + +{% endblock %} diff --git a/apps/family/templates/family/family_list.html b/apps/family/templates/family/family_list.html index 38738fcf..b4b28a89 100644 --- a/apps/family/templates/family/family_list.html +++ b/apps/family/templates/family/family_list.html @@ -26,5 +26,13 @@ SPDX-License-Identifier: GPL-3.0-or-later {% render_table table %} + {% endblock %} +{% block extrajavascript %} + +{% endblock %} From e6839a1079da89cb5751f9d501c06d6b18e8e4d6 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 18 Jul 2025 01:26:30 +0200 Subject: [PATCH 074/153] Manage page (no js yet) --- apps/family/forms.py | 14 +- apps/family/static/family/js/achievements.js | 263 ++++++++++++++++++ apps/family/tables.py | 17 +- ...llenge_update.html => challenge_form.html} | 0 .../templates/family/challenge_list.html | 5 +- .../{family_update.html => family_form.html} | 0 apps/family/templates/family/family_list.html | 5 +- apps/family/templates/family/manage.html | 205 ++++++++++++++ apps/family/urls.py | 21 +- apps/family/views.py | 61 +++- 10 files changed, 559 insertions(+), 32 deletions(-) create mode 100644 apps/family/static/family/js/achievements.js rename apps/family/templates/family/{challenge_update.html => challenge_form.html} (100%) rename apps/family/templates/family/{family_update.html => family_form.html} (100%) create mode 100644 apps/family/templates/family/manage.html diff --git a/apps/family/forms.py b/apps/family/forms.py index 8a36d289..dbc26ad3 100644 --- a/apps/family/forms.py +++ b/apps/family/forms.py @@ -8,7 +8,7 @@ from note_kfet.inputs import Autocomplete from .models import Challenge, FamilyMembership, User, Family -class ChallengeUpdateForm(forms.ModelForm): +class ChallengeForm(forms.ModelForm): """ To update a challenge """ @@ -20,6 +20,12 @@ class ChallengeUpdateForm(forms.ModelForm): } +class FamilyForm(forms.ModelForm): + class Meta: + model = Family + fields = ('name', 'description', ) + + class FamilyMembershipForm(forms.ModelForm): class Meta: model = FamilyMembership @@ -36,9 +42,3 @@ class FamilyMembershipForm(forms.ModelForm): }, ) } - - -class FamilyUpdateForm(forms.ModelForm): - class Meta: - model = Family - fields = ('description', ) \ No newline at end of file diff --git a/apps/family/static/family/js/achievements.js b/apps/family/static/family/js/achievements.js new file mode 100644 index 00000000..5794bfe5 --- /dev/null +++ b/apps/family/static/family/js/achievements.js @@ -0,0 +1,263 @@ +// Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +// SPDX-License-Identifier: GPL-3.0-or-later + +// When a transaction is performed, lock the interface to prevent spam clicks. +var LOCK = false + +/** + * Refresh the history table on the consumptions page. + */ +function refreshHistory () { + $('#history').load('/note/consos/ #history') + $('#most_used').load('/note/consos/ #most_used') +} + +$(document).ready(function () { + // If hash of a category in the URL, then select this category + // else select the first one + if (location.hash) { + $("a[href='" + location.hash + "']").tab('show') + } else { + $("a[data-toggle='tab']").first().tab('show') + } + + // When selecting a category, change URL + $(document.body).on('click', "a[data-toggle='tab']", function () { + location.hash = this.getAttribute('href') + }) + + + // Ensure we begin in single consumption. Fix issue with TurboLinks and BootstrapJS + + + document.getElementById("consume_all").addEventListener('click', consumeAll) +}) + +notes = [] +notes_display = [] +buttons = [] + +// When the user searches an alias, we update the auto-completion +autoCompleteNote('note', 'note_list', notes, notes_display, + 'alias', 'note', 'user_note', 'profile_pic', function () { + if (buttons.length > 0 && $('#single_conso').is(':checked')) { + consumeAll() + return false + } + return true + }) + +/** + * Add a transaction from a button. + * @param dest Where the money goes + * @param amount The price of the item + * @param type The type of the transaction (content type id for RecurrentTransaction) + * @param category_id The category identifier + * @param category_name The category name + * @param template_id The identifier of the button + * @param template_name The name of the button + */ +function addConso (dest, amount, type, category_id, category_name, template_id, template_name) { + var button = null + buttons.forEach(function (b) { + if (b.id === template_id) { + b.quantity += 1 + button = b + } + }) + if (button == null) { + button = { + id: template_id, + name: template_name, + dest: dest, + quantity: 1, + amount: amount, + type: type, + category_id: category_id, + category_name: category_name + } + buttons.push(button) + } + + const dc_obj = $('#double_conso') + if (dc_obj.is(':checked') || notes_display.length === 0) { + const list = dc_obj.is(':checked') ? 'consos_list' : 'note_list' + let html = '' + buttons.forEach(function (button) { + html += li('conso_button_' + button.id, button.name + + '' + button.quantity + '') + }) + document.getElementById(list).innerHTML = html + + buttons.forEach((button) => { + document.getElementById(`conso_button_${button.id}`).addEventListener('click', () => { + if (LOCK) { return } + removeNote(button, 'conso_button', buttons, list)() + }) + }) + } else { consumeAll() } +} + +/** + * Reset the page as its initial state. + */ +function reset () { + notes_display.length = 0 + notes.length = 0 + buttons.length = 0 + document.getElementById('note_list').innerHTML = '' + document.getElementById('consos_list').innerHTML = '' + document.getElementById('note').value = '' + document.getElementById('note').dataset.originTitle = '' + $('#note').tooltip('hide') + document.getElementById('profile_pic').src = '/static/member/img/default_picture.png' + document.getElementById('profile_pic_link').href = '#' + refreshHistory() + refreshBalance() + LOCK = false +} + +/** + * Apply all transactions: all notes in `notes` buy each item in `buttons` + */ +function consumeAll () { + if (LOCK) { return } + + LOCK = true + + let error = false + + if (notes_display.length === 0) { + document.getElementById('note').classList.add('is-invalid') + $('#note_list').html(li('', 'Ajoutez des émetteurs.', 'text-danger')) + error = true + } + + if (buttons.length === 0) { + $('#consos_list').html(li('', 'Ajoutez des consommations.', 'text-danger')) + error = true + } + + if (error) { + LOCK = false + return + } + + notes_display.forEach(function (note_display) { + buttons.forEach(function (button) { + consume(note_display.note, note_display.name, button.dest, button.quantity * note_display.quantity, button.amount, + button.name + ' (' + button.category_name + ')', button.type, button.category_id, button.id) + }) + }) +} + +/** + * Create a new transaction from a button through the API. + * @param source The note that paid the item (type: note) + * @param source_alias The alias used for the source (type: str) + * @param dest The note that sold the item (type: int) + * @param quantity The quantity sold (type: int) + * @param amount The price of one item, in cents (type: int) + * @param reason The transaction details (type: str) + * @param type The type of the transaction (content type id for RecurrentTransaction) + * @param category The category id of the button (type: int) + * @param template The button id (type: int) + */ +function consume (source, source_alias, dest, quantity, amount, reason, type, category, template) { + $.post('/api/note/transaction/transaction/', + { + csrfmiddlewaretoken: CSRF_TOKEN, + quantity: quantity, + amount: amount, + reason: reason, + valid: true, + polymorphic_ctype: type, + resourcetype: 'RecurrentTransaction', + source: source.id, + source_alias: source_alias, + destination: dest, + template: template + }) + .done(function () { + if (!isNaN(source.balance)) { + const newBalance = source.balance - quantity * amount + if (newBalance <= -2000) { + addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' + + 'but the emitter note %s is very negative.'), [source_alias, source_alias]), 'danger', 30000) + } else if (newBalance < 0) { + addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' + + 'but the emitter note %s is negative.'), [source_alias, source_alias]), 'warning', 30000) + } + if (source.membership && source.membership.date_end < new Date().toISOString()) { + addMsg(interpolate(gettext('Warning, the emitter note %s is no more a BDE member.'), [source_alias]), + 'danger', 30000) + } + } + reset() + }).fail(function (e) { + $.post('/api/note/transaction/transaction/', + { + csrfmiddlewaretoken: CSRF_TOKEN, + quantity: quantity, + amount: amount, + reason: reason, + valid: false, + invalidity_reason: 'Solde insuffisant', + polymorphic_ctype: type, + resourcetype: 'RecurrentTransaction', + source: source.id, + source_alias: source_alias, + destination: dest, + template: template + }).done(function () { + reset() + addMsg(gettext("The transaction couldn't be validated because of insufficient balance."), 'danger', 10000) + }).fail(function () { + reset() + errMsg(e.responseJSON) + }) + }) +} + +var searchbar = document.getElementById("search-input") +var search_results = document.getElementById("search-results") + +var old_pattern = null; +var firstMatch = null; +/** + * Updates the button search tab + * @param force Forces the update even if the pattern didn't change + */ +function updateSearch(force = false) { + let pattern = searchbar.value + if (pattern === "") + firstMatch = null; + if ((pattern === old_pattern || pattern === "") && !force) + return; + firstMatch = null; + const re = new RegExp(pattern, "i"); + Array.from(search_results.children).forEach(function(b) { + if (re.test(b.innerText)) { + b.hidden = false; + if (firstMatch === null) { + firstMatch = b; + } + } else + b.hidden = true; + }); +} + +searchbar.addEventListener("input", function (e) { + debounce(updateSearch)() +}); +searchbar.addEventListener("keyup", function (e) { + if (firstMatch && e.key === "Enter") + firstMatch.click() +}); + +function createshiny() { + const list_btn = document.querySelectorAll('.btn-outline-dark') + const shiny_class = list_btn[Math.floor(Math.random() * list_btn.length)].classList + shiny_class.replace('btn-outline-dark', 'btn-outline-dark-shiny') +} +createshiny() diff --git a/apps/family/tables.py b/apps/family/tables.py index dd3d916c..f7eb2a16 100644 --- a/apps/family/tables.py +++ b/apps/family/tables.py @@ -2,10 +2,9 @@ # SPDX-License-Identifier: GPL-3.0-or-later import django_tables2 as tables -from django_tables2 import A from django.urls import reverse -from .models import Family, Challenge, FamilyMembership +from .models import Family, Challenge, FamilyMembership, Achievement class FamilyTable(tables.Table): @@ -58,3 +57,17 @@ class FamilyMembershipTable(tables.Table): template_name = 'django_tables2/bootstrap4.html' fields = ('user',) model = FamilyMembership + + +class AchievementTable(tables.Table): + """ + List recent achievements. + """ + class Meta: + attrs = { + 'class': 'table table-condensed table-striped table-hover' + } + model = Achievement + fields = ('family', 'challenge', 'obtained_at', ) + template_name = 'django_tables2/bootstrap4.html' + orderable = False diff --git a/apps/family/templates/family/challenge_update.html b/apps/family/templates/family/challenge_form.html similarity index 100% rename from apps/family/templates/family/challenge_update.html rename to apps/family/templates/family/challenge_form.html diff --git a/apps/family/templates/family/challenge_list.html b/apps/family/templates/family/challenge_list.html index c84b80ce..2a1502d0 100644 --- a/apps/family/templates/family/challenge_list.html +++ b/apps/family/templates/family/challenge_list.html @@ -16,11 +16,14 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "Challenges" %} + + {% trans "Manage" %} + -<
    +

    {{ title }}

    diff --git a/apps/family/templates/family/family_update.html b/apps/family/templates/family/family_form.html similarity index 100% rename from apps/family/templates/family/family_update.html rename to apps/family/templates/family/family_form.html diff --git a/apps/family/templates/family/family_list.html b/apps/family/templates/family/family_list.html index b4b28a89..55feed5e 100644 --- a/apps/family/templates/family/family_list.html +++ b/apps/family/templates/family/family_list.html @@ -16,11 +16,14 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "Challenges" %} + + {% trans "Manage" %} +
    -<
    +

    {{ title }}

    diff --git a/apps/family/templates/family/manage.html b/apps/family/templates/family/manage.html new file mode 100644 index 00000000..7c8e0dbf --- /dev/null +++ b/apps/family/templates/family/manage.html @@ -0,0 +1,205 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n static django_tables2 %} + +{% block containertype %}container-fluid{% endblock %} + +{% block content %} + + +
    +
    +
    + {# User details column #} +
    +
    + + + +
    + {% trans "Please select a family" %} +
    +
    +
    + + {# Family selection column #} +
    +
    +
    +

    + {% trans "Families" %} +

    +
    +
    +
      +
    +
    + + {# User search with autocompletion #} + +
    +
    + + {# Summary of challenges and validate button #} +
    +
    +
    +

    + {% trans "Challenges" %} +

    +
    +
    +
      +
    +
    + +
    +
    +
    + {# Create family/challenge buttons #} +
    +

    +

    + {% trans "Create a family or challenge" %} +

    +

    +
    + {% if can_add_family %} + + {% trans "Add a family" %} + + {% endif %} + {% if can_add_challenge %} + + {% trans "Add a challenge" %} + + {% endif %} +
    +
    +
    + + + {# Buttons column #} +
    + {# Regroup buttons under categories #} + +
    + {# Tabs for list and search #} + + + {# Tabs content #} +
    +
    +
    +
    + {% for challenge in all_challenges %} + + {% endfor %} +
    +
    + +
    +
    + + {# Mode switch #} + +
    +
    +
    + + + + + +{# transaction history #} +
    +
    +

    + {% trans "Recent achievements history" %} +

    +
    + {% render_table table %} +
    +{% endblock %} + + + +{% block extrajavascript %} + + +{% endblock %} \ No newline at end of file diff --git a/apps/family/urls.py b/apps/family/urls.py index e86bc0b5..094ed505 100644 --- a/apps/family/urls.py +++ b/apps/family/urls.py @@ -3,16 +3,19 @@ from django.urls import path -from .views import FamilyListView, FamilyDetailView, FamilyUpdateView, FamilyPictureUpdateView, FamilyAddMemberView, ChallengeListView, ChallengeDetailView, ChallengeUpdateView +from . import views app_name = 'family' urlpatterns = [ - path('list/', FamilyListView.as_view(), name="family_list"), - path('detail//', FamilyDetailView.as_view(), name="family_detail"), - path('update//', FamilyUpdateView.as_view(), name="family_update"), - path('update_pic//', FamilyPictureUpdateView.as_view(), name="update_pic"), - path('add_member//', FamilyAddMemberView.as_view(), name="family_add_member"), - path('challenge/list/', ChallengeListView.as_view(), name="challenge_list"), - path('challenge/detail//', ChallengeDetailView.as_view(), name="challenge_detail"), - path('challenge/update//', ChallengeUpdateView.as_view(), name="challenge_update"), + path('list/', views.FamilyListView.as_view(), name="family_list"), + path('add-family/', views.FamilyCreateView.as_view(), name="add_family"), + path('detail//', views.FamilyDetailView.as_view(), name="family_detail"), + path('update//', views.FamilyUpdateView.as_view(), name="family_update"), + path('update_pic//', views.FamilyPictureUpdateView.as_view(), name="update_pic"), + path('add_member//', views.FamilyAddMemberView.as_view(), name="family_add_member"), + path('challenge/list/', views.ChallengeListView.as_view(), name="challenge_list"), + path('add-challenge/', views.ChallengeCreateView.as_view(), name="add_challenge"), + path('challenge/detail//', views.ChallengeDetailView.as_view(), name="challenge_detail"), + path('challenge/update//', views.ChallengeUpdateView.as_view(), name="challenge_update"), + path('manage/', views.FamilyManageView.as_view(), name="manage"), ] diff --git a/apps/family/views.py b/apps/family/views.py index 75a82e0c..35f073fb 100644 --- a/apps/family/views.py +++ b/apps/family/views.py @@ -12,13 +12,12 @@ from django_tables2 import SingleTableView from permission.backends import PermissionBackend from permission.views import ProtectQuerysetMixin, ProtectedCreateView from django.urls import reverse_lazy - -from .models import Family, Challenge, FamilyMembership, User -from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable -from .forms import ChallengeUpdateForm, FamilyMembershipForm, FamilyUpdateForm -from member.forms import ImageForm from member.views import PictureUpdateView +from .models import Family, Challenge, FamilyMembership, User, Achievement +from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable, AchievementTable +from .forms import ChallengeForm, FamilyMembershipForm, FamilyForm + class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView): """ @@ -26,6 +25,7 @@ class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView): """ model = Family extra_context = {"title": _('Create family')} + form_class = FamilyForm def get_sample_object(self): return Family( @@ -35,6 +35,10 @@ class FamilyCreateView(ProtectQuerysetMixin, ProtectedCreateView): rank=0, ) + def get_success_url(self): + self.object.refresh_from_db() + return reverse_lazy("family:manage") + class FamilyListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): """ @@ -92,8 +96,7 @@ class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): """ model = Family context_object_name = "family" - form_class = FamilyUpdateForm - template_name = 'family/family_update.html' + form_class = FamilyForm extra_context = {"title": _('Update family')} def get_success_url(self): @@ -152,11 +155,10 @@ class FamilyAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - form = context['form'] family = Family.objects.filter(PermissionBackend.filter_queryset(self.request, Family, "view"))\ .get(pk=self.kwargs['family_pk']) - + context['family'] = family return context @@ -167,7 +169,7 @@ class FamilyAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): Create family membership, check that everythinf is good """ family = Family.objects.filter(PermissionBackend.filter_queryset(self.request, Family, "view")) \ - .get(pk=self.kwargs["family_pk"]) + .get(pk=self.kwargs["family_pk"]) form.instance.family = family @@ -183,6 +185,7 @@ class ChallengeCreateView(ProtectQuerysetMixin, ProtectedCreateView): """ model = Challenge extra_context = {"title": _('Create challenge')} + form_class = ChallengeForm def get_sample_object(self): return Challenge( @@ -234,9 +237,43 @@ class ChallengeUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): model = Challenge context_object_name = "challenge" extra_context = {"title": _('Update challenge')} - template_name = 'family/challenge_update.html' - form_class = ChallengeUpdateForm + form_class = ChallengeForm def get_success_url(self, **kwargs): self.object.refresh_from_db() return reverse_lazy('family:challenge_detail', kwargs={'pk': self.object.pk}) + + +class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): + """ + Manage families and challenges + """ + model = Achievement + template_name = 'family/manage.html' + table_class = AchievementTable + extra_context = {'title': _('Manage families and challenges')} + + def dispatch(self, request, *args, **kwargs): + # Check that the user is authenticated + if not request.user.is_authenticated: + return self.handle_no_permission() + + return super().dispatch(request, *args, **kwargs) + + def get_queryset(self, **kwargs): + # retrieves only Transaction that user has the right to see. + return Achievement.objects.filter( + PermissionBackend.filter_queryset(self.request, Achievement, "view") + ).order_by("-obtained_at").all()[:20] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + context['all_challenges'] = Challenge.objects.filter( + PermissionBackend.filter_queryset(self.request, Challenge, "view") + ).order_by('name') + + context["can_add_family"] = PermissionBackend.check_perm(self.request, "family.add_family") + context["can_add_challenge"] = PermissionBackend.check_perm(self.request, "family.add_challenge") + + return context From a72572ded6df20ea03c74227eba1df79c6a6f93f Mon Sep 17 00:00:00 2001 From: ikea Date: Fri, 18 Jul 2025 11:51:27 +0200 Subject: [PATCH 075/153] =?UTF-8?q?Optimisation=20ergonomique=20de=20la=20?= =?UTF-8?q?cr=C3=A9ation=20de=20famille=20et=20chalenge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/family/templates/family/manage.html | 391 +++++++++++------------ 1 file changed, 187 insertions(+), 204 deletions(-) diff --git a/apps/family/templates/family/manage.html b/apps/family/templates/family/manage.html index 7c8e0dbf..84506884 100644 --- a/apps/family/templates/family/manage.html +++ b/apps/family/templates/family/manage.html @@ -1,205 +1,188 @@ -{% extends "base.html" %} -{% comment %} -Copyright (C) 2018-2025 by BDE ENS Paris-Saclay -SPDX-License-Identifier: GPL-3.0-or-later -{% endcomment %} -{% load i18n static django_tables2 %} - -{% block containertype %}container-fluid{% endblock %} - -{% block content %} - - -
    -
    -
    - {# User details column #} -
    -
    - - - -
    - {% trans "Please select a family" %} -
    -
    -
    - - {# Family selection column #} -
    -
    -
    -

    - {% trans "Families" %} -

    -
    -
    -
      -
    -
    - - {# User search with autocompletion #} - -
    -
    - - {# Summary of challenges and validate button #} -
    -
    -
    -

    - {% trans "Challenges" %} -

    -
    -
    -
      -
    -
    - -
    -
    -
    - {# Create family/challenge buttons #} -
    -

    -

    - {% trans "Create a family or challenge" %} -

    -

    -
    - {% if can_add_family %} - - {% trans "Add a family" %} - - {% endif %} - {% if can_add_challenge %} - - {% trans "Add a challenge" %} - - {% endif %} -
    -
    -
    - - - {# Buttons column #} -
    - {# Regroup buttons under categories #} - -
    - {# Tabs for list and search #} - - - {# Tabs content #} -
    -
    -
    -
    - {% for challenge in all_challenges %} - - {% endfor %} -
    -
    - -
    -
    - - {# Mode switch #} - -
    -
    -
    - - - - - -{# transaction history #} -
    -
    -

    - {% trans "Recent achievements history" %} -

    -
    - {% render_table table %} -
    -{% endblock %} - - - -{% block extrajavascript %} - - +{% extends "base.html" %} +{% comment %} +Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n static django_tables2 %} + +{% block containertype %}container-fluid{% endblock %} + +{% block content %} + + +
    +
    +
    + {# User details column #} +
    +
    + + + +
    + {% trans "Please select a family" %} +
    +
    +
    + + {# Family selection column #} +
    +
    +
    +

    + {% trans "Families" %} +

    +
    +
    +
      +
      + {# User search with autocompletion #} + +
      +
      + + {# Summary of challenges and validate button #} +
      +
      +
      +

      + {% trans "Challenges" %} +

      +
      +
      +
        +
        + +
        +
        +
        + + {# Create family/challenge buttons #} +
        +

        + {% trans "Create a family or challenge" %} +

        +
        + {% if can_add_family %} + + {% trans "Add a family" %} + + {% endif %} + {% if can_add_challenge %} + + {% trans "Add a challenge" %} + + {% endif %} +
        +
        +
        + + {# Buttons column #} +
        +
        + {# Tabs for list and search #} + + + {# Tabs content #} +
        +
        +
        +
        + {% for challenge in all_challenges %} + + {% endfor %} +
        +
        + +
        +
        + + {# Mode switch #} + +
        +
        +
        + +{# transaction history #} +
        +
        +

        + {% trans "Recent achievements history" %} +

        +
        + {% render_table table %} +
        +{% endblock %} + +{% block extrajavascript %} + + {% endblock %} \ No newline at end of file From 57f43a870072fb1699868b2916e3b1822706d0ea Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 18 Jul 2025 13:54:37 +0200 Subject: [PATCH 076/153] API --- apps/family/api/__init__.py | 0 apps/family/api/serializers.py | 46 +++++++++++++++++++++++++ apps/family/api/urls.py | 14 ++++++++ apps/family/api/views.py | 61 ++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 apps/family/api/__init__.py create mode 100644 apps/family/api/serializers.py create mode 100644 apps/family/api/urls.py create mode 100644 apps/family/api/views.py diff --git a/apps/family/api/__init__.py b/apps/family/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/family/api/serializers.py b/apps/family/api/serializers.py new file mode 100644 index 00000000..c902a9c5 --- /dev/null +++ b/apps/family/api/serializers.py @@ -0,0 +1,46 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from rest_framework import serializers + +from ..models import Family, FamilyMembership, Challenge, Achievement + + +class FamilySerializer(serializers.ModelSerializer): + """ + REST API Serializer for Family. + The djangorestframework plugin will analyse the model `Family` and parse all fields in the API. + """ + class Meta: + model = Family + fields = '__all__' + + +class FamilyMembershipSerializer(serializers.ModelSerializer): + """ + REST API Serializer for FamilyMembership. + The djangorestframework plugin will analyse the model `FamilyMembership` and parse all fields in the API. + """ + class Meta: + model = FamilyMembership + fields = '__all__' + + +class ChallengeSerializer(serializers.ModelSerializer): + """ + REST API Serializer for Challenge. + The djangorestframework plugin will analyse the model `Challenge` and parse all fields in the API. + """ + class Meta: + model = Challenge + fields = '__all__' + + +class AchievementSerializer(serializers.ModelSerializer): + """ + REST API Serializer for Achievement. + The djangorestframework plugin will analyse the model `Achievement` and parse all fields in the API. + """ + class Meta: + model = Achievement + fields = '__all__' diff --git a/apps/family/api/urls.py b/apps/family/api/urls.py new file mode 100644 index 00000000..43c49500 --- /dev/null +++ b/apps/family/api/urls.py @@ -0,0 +1,14 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from .views import FamilyViewSet, FamilyMembershipViewSet, ChallengeViewSet, AchievementViewSet + + +def register_family_urls(router, path): + """ + Configure router for Family REST API + """ + router.register(path + 'family', FamilyViewSet) + router.register(path + 'familymembership', FamilyMembershipViewSet) + router.register(path + 'challenge', ChallengeViewSet) + router.register(path + 'achievement', AchievementViewSet) diff --git a/apps/family/api/views.py b/apps/family/api/views.py new file mode 100644 index 00000000..0e228718 --- /dev/null +++ b/apps/family/api/views.py @@ -0,0 +1,61 @@ +# Copyright (C) 2018-2025 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 FamilySerializer, FamilyMembershipSerializer, ChallengeSerializer, AchievementSerializer +from ..models import Family, FamilyMembership, Challenge, Achievement + + +class FamilyViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Family` objects, serialize it to JSON with the given serializer, + then render it on /api/family/family/ + """ + queryset = Family.object.order_by('id') + serializer_class = FamilySerializer + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name', ] + search_fields = ['§name', ] + + +class FamilyMembershipViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `FamilyMembership` objects, serialize it to JSON with the given serializer, + then render it on /api/family/familymembership/ + """ + queryset = FamilyMembership.object.order_by('id') + serializer_class = FamilyMembershipSerializer + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name', ] + search_fields = ['§name', ] + + +class ChallengeViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Challenge` objects, serialize it to JSON with the given serializer, + then render it on /api/family/challenge/ + """ + queryset = Challenge.object.order_by('id') + serializer_class = ChallengeSerializer + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name', ] + search_fields = ['§name', ] + + +class AchievementViewSet(ReadProtectedModelViewSet): + """ + REST API View set. + The djangorestframework plugin will get all `Achievement` objects, serialize it to JSON with the given serializer, + then render it on /api/family/achievement/ + """ + queryset = Achievement.object.order_by('id') + serializer_class = AchievementSerializer + filter_backends = [DjangoFilterBackend, SearchFilter] + filterset_fields = ['name', ] + search_fields = ['§name', ] From 40922843f860ae0ddeef8e120b991539db2f48d5 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 18 Jul 2025 17:08:29 +0200 Subject: [PATCH 077/153] API again --- apps/api/urls.py | 4 ++++ apps/family/api/urls.py | 8 ++++---- apps/family/api/views.py | 16 ++++++++-------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/apps/api/urls.py b/apps/api/urls.py index c9e0dfa4..4452e4d7 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -19,6 +19,10 @@ if "activity" in settings.INSTALLED_APPS: from activity.api.urls import register_activity_urls register_activity_urls(router, 'activity') +if "family" in settings.INSTALLED_APPS: + from family.api.urls import register_family_urls + register_family_urls(router, 'family') + if "food" in settings.INSTALLED_APPS: from food.api.urls import register_food_urls register_food_urls(router, 'food') diff --git a/apps/family/api/urls.py b/apps/family/api/urls.py index 43c49500..b231ef99 100644 --- a/apps/family/api/urls.py +++ b/apps/family/api/urls.py @@ -8,7 +8,7 @@ def register_family_urls(router, path): """ Configure router for Family REST API """ - router.register(path + 'family', FamilyViewSet) - router.register(path + 'familymembership', FamilyMembershipViewSet) - router.register(path + 'challenge', ChallengeViewSet) - router.register(path + 'achievement', AchievementViewSet) + router.register(path + '/family', FamilyViewSet) + router.register(path + '/familymembership', FamilyMembershipViewSet) + router.register(path + '/challenge', ChallengeViewSet) + router.register(path + '/achievement', AchievementViewSet) diff --git a/apps/family/api/views.py b/apps/family/api/views.py index 0e228718..f2fe0208 100644 --- a/apps/family/api/views.py +++ b/apps/family/api/views.py @@ -15,11 +15,11 @@ class FamilyViewSet(ReadProtectedModelViewSet): The djangorestframework plugin will get all `Family` objects, serialize it to JSON with the given serializer, then render it on /api/family/family/ """ - queryset = Family.object.order_by('id') + queryset = Family.objects.order_by('id') serializer_class = FamilySerializer filter_backends = [DjangoFilterBackend, SearchFilter] filterset_fields = ['name', ] - search_fields = ['§name', ] + search_fields = ['$name', ] class FamilyMembershipViewSet(ReadProtectedModelViewSet): @@ -28,11 +28,11 @@ class FamilyMembershipViewSet(ReadProtectedModelViewSet): The djangorestframework plugin will get all `FamilyMembership` objects, serialize it to JSON with the given serializer, then render it on /api/family/familymembership/ """ - queryset = FamilyMembership.object.order_by('id') + queryset = FamilyMembership.objects.order_by('id') serializer_class = FamilyMembershipSerializer filter_backends = [DjangoFilterBackend, SearchFilter] filterset_fields = ['name', ] - search_fields = ['§name', ] + search_fields = ['$name', ] class ChallengeViewSet(ReadProtectedModelViewSet): @@ -41,11 +41,11 @@ class ChallengeViewSet(ReadProtectedModelViewSet): The djangorestframework plugin will get all `Challenge` objects, serialize it to JSON with the given serializer, then render it on /api/family/challenge/ """ - queryset = Challenge.object.order_by('id') + queryset = Challenge.objects.order_by('id') serializer_class = ChallengeSerializer filter_backends = [DjangoFilterBackend, SearchFilter] filterset_fields = ['name', ] - search_fields = ['§name', ] + search_fields = ['$name', ] class AchievementViewSet(ReadProtectedModelViewSet): @@ -54,8 +54,8 @@ class AchievementViewSet(ReadProtectedModelViewSet): The djangorestframework plugin will get all `Achievement` objects, serialize it to JSON with the given serializer, then render it on /api/family/achievement/ """ - queryset = Achievement.object.order_by('id') + queryset = Achievement.objects.order_by('id') serializer_class = AchievementSerializer filter_backends = [DjangoFilterBackend, SearchFilter] filterset_fields = ['name', ] - search_fields = ['§name', ] + search_fields = ['$name', ] From f64138605d20654d993be7d63a2fc0bed4c3d8e8 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 18 Jul 2025 17:09:06 +0200 Subject: [PATCH 078/153] JS for manage page --- apps/family/static/family/js/achievements.js | 358 ++++++++++++++----- apps/family/templates/family/manage.html | 314 ++++++++-------- 2 files changed, 420 insertions(+), 252 deletions(-) diff --git a/apps/family/static/family/js/achievements.js b/apps/family/static/family/js/achievements.js index 5794bfe5..dba07f0d 100644 --- a/apps/family/static/family/js/achievements.js +++ b/apps/family/static/family/js/achievements.js @@ -8,8 +8,7 @@ var LOCK = false * Refresh the history table on the consumptions page. */ function refreshHistory () { - $('#history').load('/note/consos/ #history') - $('#most_used').load('/note/consos/ #most_used') + $('#history').load('/family/manage/ #history') } $(document).ready(function () { @@ -38,18 +37,14 @@ notes_display = [] buttons = [] // When the user searches an alias, we update the auto-completion -autoCompleteNote('note', 'note_list', notes, notes_display, - 'alias', 'note', 'user_note', 'profile_pic', function () { - if (buttons.length > 0 && $('#single_conso').is(':checked')) { - consumeAll() - return false - } +autoCompleteFamily('note', 'note_list', notes, notes_display, + 'note', 'user_note', 'profile_pic', function () { return true }) /** * Add a transaction from a button. - * @param dest Where the money goes + * @param fam Where the money goes * @param amount The price of the item * @param type The type of the transaction (content type id for RecurrentTransaction) * @param category_id The category identifier @@ -57,35 +52,32 @@ autoCompleteNote('note', 'note_list', notes, notes_display, * @param template_id The identifier of the button * @param template_name The name of the button */ -function addConso (dest, amount, type, category_id, category_name, template_id, template_name) { - var button = null +function addChallenge (id, name, amount) { + var challenge = null + /** Ajout de 1 à chaque clic d'un bouton déjà choisi */ buttons.forEach(function (b) { - if (b.id === template_id) { + if (b.id === id) { b.quantity += 1 - button = b + challenge = b } }) - if (button == null) { - button = { - id: template_id, - name: template_name, - dest: dest, + if (challenge == null) { + challenge = { + id: id, + name: name, quantity: 1, amount: amount, - type: type, - category_id: category_id, - category_name: category_name } - buttons.push(button) + buttons.push(challenge) } - const dc_obj = $('#double_conso') - if (dc_obj.is(':checked') || notes_display.length === 0) { - const list = dc_obj.is(':checked') ? 'consos_list' : 'note_list' + const dc_obj = true + + const list = 'consos_list' let html = '' - buttons.forEach(function (button) { - html += li('conso_button_' + button.id, button.name + - '' + button.quantity + '') + buttons.forEach(function (challenge) { + html += li('conso_button_' + challenge.id, challenge.name + + '' + challenge.quantity + '') }) document.getElementById(list).innerHTML = html @@ -95,13 +87,14 @@ function addConso (dest, amount, type, category_id, category_name, template_id, removeNote(button, 'conso_button', buttons, list)() }) }) - } else { consumeAll() } + } /** * Reset the page as its initial state. */ function reset () { + console.log("reset lancée") notes_display.length = 0 notes.length = 0 buttons.length = 0 @@ -113,7 +106,6 @@ function reset () { document.getElementById('profile_pic').src = '/static/member/img/default_picture.png' document.getElementById('profile_pic_link').href = '#' refreshHistory() - refreshBalance() LOCK = false } @@ -121,6 +113,7 @@ function reset () { * Apply all transactions: all notes in `notes` buy each item in `buttons` */ function consumeAll () { + console.log("consumeAll lancée") if (LOCK) { return } LOCK = true @@ -129,12 +122,12 @@ function consumeAll () { if (notes_display.length === 0) { document.getElementById('note').classList.add('is-invalid') - $('#note_list').html(li('', 'Ajoutez des émetteurs.', 'text-danger')) + $('#note_list').html(li('', 'Ajoutez des familles.', 'text-danger')) error = true } if (buttons.length === 0) { - $('#consos_list').html(li('', 'Ajoutez des consommations.', 'text-danger')) + $('#consos_list').html(li('', 'Ajoutez des défis.', 'text-danger')) error = true } @@ -143,79 +136,39 @@ function consumeAll () { return } - notes_display.forEach(function (note_display) { - buttons.forEach(function (button) { - consume(note_display.note, note_display.name, button.dest, button.quantity * note_display.quantity, button.amount, - button.name + ' (' + button.category_name + ')', button.type, button.category_id, button.id) + notes_display.forEach(function (family) { + buttons.forEach(function (challenge) { + grantAchievement(family, challenge) }) }) } /** - * Create a new transaction from a button through the API. - * @param source The note that paid the item (type: note) - * @param source_alias The alias used for the source (type: str) - * @param dest The note that sold the item (type: int) - * @param quantity The quantity sold (type: int) - * @param amount The price of one item, in cents (type: int) - * @param reason The transaction details (type: str) - * @param type The type of the transaction (content type id for RecurrentTransaction) - * @param category The category id of the button (type: int) - * @param template The button id (type: int) + * Create a new achievement through the API. + * @param family The selected family + * @param challenge The selected challenge */ -function consume (source, source_alias, dest, quantity, amount, reason, type, category, template) { - $.post('/api/note/transaction/transaction/', +function grantAchievement (family, challenge) { + console.log("grant lancée",family,challenge) + $.post('/api/family/achievement/', { csrfmiddlewaretoken: CSRF_TOKEN, - quantity: quantity, - amount: amount, - reason: reason, - valid: true, - polymorphic_ctype: type, - resourcetype: 'RecurrentTransaction', - source: source.id, - source_alias: source_alias, - destination: dest, - template: template + family: family.id, + challenge: challenge.id, }) .done(function () { - if (!isNaN(source.balance)) { - const newBalance = source.balance - quantity * amount - if (newBalance <= -2000) { - addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' + - 'but the emitter note %s is very negative.'), [source_alias, source_alias]), 'danger', 30000) - } else if (newBalance < 0) { - addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' + - 'but the emitter note %s is negative.'), [source_alias, source_alias]), 'warning', 30000) - } - if (source.membership && source.membership.date_end < new Date().toISOString()) { - addMsg(interpolate(gettext('Warning, the emitter note %s is no more a BDE member.'), [source_alias]), - 'danger', 30000) - } - } reset() - }).fail(function (e) { - $.post('/api/note/transaction/transaction/', - { - csrfmiddlewaretoken: CSRF_TOKEN, - quantity: quantity, - amount: amount, - reason: reason, - valid: false, - invalidity_reason: 'Solde insuffisant', - polymorphic_ctype: type, - resourcetype: 'RecurrentTransaction', - source: source.id, - source_alias: source_alias, - destination: dest, - template: template - }).done(function () { - reset() - addMsg(gettext("The transaction couldn't be validated because of insufficient balance."), 'danger', 10000) - }).fail(function () { - reset() + addMsg("Défi validé pour la famille !", 'success', 5000) + }) + .fail(function (e) { + reset() + if (e.responseJSON) { errMsg(e.responseJSON) - }) + } else if (e.responseText) { + errMsg(e.responseText) + } else { + errMsg("Erreur inconnue lors de la création de l'achievement.") + } }) } @@ -261,3 +214,222 @@ function createshiny() { shiny_class.replace('btn-outline-dark', 'btn-outline-dark-shiny') } createshiny() + + + + + +/** + * Query the 20 first matched notes with a given pattern + * @param pattern The pattern that is queried + * @param fun For each found note with the matched alias `alias`, fun(note, alias) is called. + */ +function getMatchedFamilies (pattern, fun) { + $.getJSON('/api/family/family/?format=json&alias=' + pattern + '&search=family', fun) +} + +/** + * Generate a
      • entry with a given id and text + */ +function li (id, text, extra_css) { + return '
      • ' + text + '
      • \n' +} + + +/** + * Génère un champ d'auto-complétion pour rechercher une famille par son nom (version simplifiée sans alias) + * @param field_id L'identifiant du champ texte où le nom est saisi + * @param family_list_id L'identifiant du bloc div où les familles sélectionnées sont affichées + * @param families Un tableau contenant les objets famille sélectionnés + * @param families_display Un tableau contenant les infos des familles sélectionnées : [nom, id, objet famille, quantité] + * @param family_prefix Le préfixe des
      • pour les familles sélectionnées + * @param user_family_field L'identifiant du champ qui affiche la famille survolée (optionnel) + * @param profile_pic_field L'identifiant du champ qui affiche la photo de la famille survolée (optionnel) + * @param family_click Fonction appelée lors du clic sur un nom. Si elle existe et ne retourne pas true, la famille n'est pas affichée. + */ +function autoCompleteFamily(field_id, family_list_id, families, families_display, family_prefix = 'family', user_family_field = null, profile_pic_field = null, family_click = null) { + const field = $('#' + field_id) + console.log("autoCompleteFamily commence") + // Configuration du tooltip + field.tooltip({ + html: true, + placement: 'bottom', + title: 'Chargement...', + trigger: 'manual', + container: field.parent(), + fallbackPlacement: 'clockwise' + }) + + // Masquer le tooltip lors d'un clic ailleurs + $(document).click(function (e) { + if (!e.target.id.startsWith(family_prefix)) { + field.tooltip('hide') + } + }) + + let old_pattern = null + + // Réinitialiser la recherche au clic + field.click(function () { + field.tooltip('hide') + field.removeClass('is-invalid') + field.val('') + old_pattern = '' + }) + + // Sur "Entrée", sélectionner la première famille + field.keypress(function (event) { + if (event.originalEvent.charCode === 13 && families.length > 0) { + const li_obj = field.parent().find('ul li').first() + displayFamily(families[0], families[0].name, user_family_field, profile_pic_field) + li_obj.trigger('click') + } + }) + + // Mise à jour des suggestions lors de la saisie + field.keyup(function (e) { + field.removeClass('is-invalid') + + if (e.originalEvent.charCode === 13) { return } + + const pattern = field.val() + + if (pattern === old_pattern) { return } + old_pattern = pattern + families.length = 0 + + if (pattern === '') { + field.tooltip('hide') + families.length = 0 + return + } + + // Appel à l'API pour récupérer les familles correspondantes + $.getJSON('/api/family/family/?format=json&search=' + pattern, + function (results) { + if (pattern !== $('#' + field_id).val()) { return } + + let matched_html = '
          ' + results.results.forEach(function (family) { + matched_html += li(family_prefix + '_' + family.id, + family.name, + '') + families.push(family) + }) + matched_html += '
        ' + + field.attr('data-original-title', matched_html).tooltip('show') + + results.results.forEach(function (family) { + const family_obj = $('#' + family_prefix + '_' + family.id) + family_obj.hover(function () { + displayFamily(family, family.name, user_family_field, profile_pic_field) + }) + family_obj.click(function () { + var disp = null + families_display.forEach(function (d) { + if (d.id === family.id) { + d.quantity += 1 + disp = d + } + }) + if (disp == null) { + disp = { + name: family.name, + id: family.id, + family: family, + quantity: 1 + } + families_display.push(disp) + } + + if (family_click && !family_click()) { return } + + const family_list = $('#' + family_list_id) + let html = '' + families_display.forEach(function (disp) { + html += li(family_prefix + '_' + disp.id, + disp.name + + '' + + disp.quantity + '', + '') + }) + + family_list.html(html) + field.tooltip('update') + + families_display.forEach(function (disp) { + const line_obj = $('#' + family_prefix + '_' + disp.id) + line_obj.hover(function () { + displayFamily(disp.family, disp.name, user_family_field, profile_pic_field) + }) + line_obj.click(removeFamily(disp, family_prefix, families_display, family_list_id, user_family_field, + profile_pic_field)) + }) + }) + }) + }) + }) +} + +/** + * Affiche le nom et la photo d'une famille + * @param family L'objet famille à afficher + * @param user_family_field L'identifiant du champ où afficher le nom (optionnel) + * @param profile_pic_field L'identifiant du champ où afficher la photo (optionnel) + */ +function displayFamily(family, user_family_field = null, profile_pic_field = null) { + if (!family.display_image) { + family.display_image = '/static/member/img/default_picture.png' + } + if (user_family_field !== null) { + $('#' + user_family_field).removeAttr('class') + $('#' + user_family_field).text(family.name) + if (profile_pic_field != null) { + $('#' + profile_pic_field).attr('src', family.display_image) + // Si tu veux un lien vers la page famille : + $('#' + profile_pic_field + '_link').attr('href', '/family/detail/' + family.id + '/') + } + } +} + + +/** + * Retire une famille de la liste sélectionnée. + * @param d La famille à retirer + * @param family_prefix Le préfixe des
      • + * @param families_display Le tableau des familles sélectionnées + * @param family_list_id L'id du bloc où sont affichées les familles + * @param user_family_field Champ d'affichage (optionnel) + * @param profile_pic_field Champ photo (optionnel) + * @returns une fonction compatible avec les événements jQuery + */ +function removeFamily(d, family_prefix, families_display, family_list_id, user_family_field = null, profile_pic_field = null) { + return function () { + const new_families_display = [] + let html = '' + families_display.forEach(function (disp) { + if (disp.quantity > 1 || disp.id !== d.id) { + disp.quantity -= disp.id === d.id ? 1 : 0 + new_families_display.push(disp) + html += li(family_prefix + '_' + disp.id, disp.name + + '' + disp.quantity + '') + } + }) + + families_display.length = 0 + new_families_display.forEach(function (disp) { + families_display.push(disp) + }) + + $('#' + family_list_id).html(html) + families_display.forEach(function (disp) { + const obj = $('#' + family_prefix + '_' + disp.id) + obj.click(removeFamily(disp, family_prefix, families_display, family_list_id, user_family_field, profile_pic_field)) + obj.hover(function () { + displayFamily(disp.family, user_family_field, profile_pic_field) + }) + }) + } +} \ No newline at end of file diff --git a/apps/family/templates/family/manage.html b/apps/family/templates/family/manage.html index 84506884..275337fa 100644 --- a/apps/family/templates/family/manage.html +++ b/apps/family/templates/family/manage.html @@ -9,180 +9,176 @@ SPDX-License-Identifier: GPL-3.0-or-later {% block content %}
        -
        -
        -
        - {# User details column #} -
        -
        - - - -
        - {% trans "Please select a family" %} -
        -
        -
        +
        +
        + {# Family details column #} +
        +
        + + + +
        + {% trans "Please select a family" %} +
        +
        +
        - {# Family selection column #} -
        + {# Family selection column #} +
        +
        +
        +

        + {% trans "Families" %} +

        +
        +
        +
          +
          + {# User search with autocompletion #} + +
          +
          + + {# Summary of challenges and validate button #} +
          +
          +
          +

          + {% trans "Challenges" %} +

          +
          +
          +
            +
            + +
            +
            +
            + + {# Create family/challenge buttons #}
            -
            -

            - {% trans "Families" %} -

            -
            -
            -
              -
              - {# User search with autocompletion #} - -
              -
              - - {# Summary of challenges and validate button #} -
              -
              -
              -

              - {% trans "Challenges" %} -

              -
              -
              -
                -
                - -
                -
                -
                - - {# Create family/challenge buttons #} -
                -

                - {% trans "Create a family or challenge" %} -

                -
                - {% if can_add_family %} - - {% trans "Add a family" %} - - {% endif %} - {% if can_add_challenge %} - - {% trans "Add a challenge" %} - - {% endif %} -
                -
                -
                - - {# Buttons column #} -
                -
                - {# Tabs for list and search #} - - - {# Tabs content #} -
                -
                -
                -
                - {% for challenge in all_challenges %} - - {% endfor %} -
                -
                - + {% endif %} +
                +
                +
                + + {# Buttons column #} +
                +
                + {# Tabs for list and search #} + + + {# Tabs content #} +
                +
                +
                +
                + {% for challenge in all_challenges %} + + {% endfor %} +
                +
                + +
                +
                + + {# Mode switch #} +
                -
                - - {# Mode switch #} -
                -
                {# transaction history #} -
                -
                -

                - {% trans "Recent achievements history" %} -

                -
                - {% render_table table %} -
                +
                +
                +

                + {% trans "Recent achievements history" %} +

                +
                + {% render_table table %} +
                {% endblock %} -{% block extrajavascript %} - - + + {% endfor %} + + {% for challenge in all_challenges %} + document.getElementById("search_challenge{{ challenge.id }}").addEventListener("click", function() { + addChallenge({{ challenge.id}}, "{{ challenge.name|escapejs }}", {{ challenge.points }}); + }); + {% endfor %} + {% endblock %} \ No newline at end of file From 67b936ae9895476f55eafb3f7e150e70aa237708 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 18 Jul 2025 21:01:15 +0200 Subject: [PATCH 079/153] Rank calculation optimized --- apps/family/api/urls.py | 8 +++-- apps/family/api/views.py | 30 ++++++++++++++++ apps/family/models.py | 7 ++-- apps/family/static/family/js/achievements.js | 36 ++++++++++++++------ apps/family/urls.py | 3 +- 5 files changed, 67 insertions(+), 17 deletions(-) diff --git a/apps/family/api/urls.py b/apps/family/api/urls.py index b231ef99..e94776d7 100644 --- a/apps/family/api/urls.py +++ b/apps/family/api/urls.py @@ -1,7 +1,7 @@ # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later - -from .views import FamilyViewSet, FamilyMembershipViewSet, ChallengeViewSet, AchievementViewSet +from django.urls import path +from .views import FamilyViewSet, FamilyMembershipViewSet, ChallengeViewSet, AchievementViewSet, BatchAchievementsAPIView def register_family_urls(router, path): @@ -12,3 +12,7 @@ def register_family_urls(router, path): router.register(path + '/familymembership', FamilyMembershipViewSet) router.register(path + '/challenge', ChallengeViewSet) router.register(path + '/achievement', AchievementViewSet) + +urlpatterns = [ + path('achievements/batch/', BatchAchievementsAPIView.as_view(), name='batch_achievements') +] \ No newline at end of file diff --git a/apps/family/api/views.py b/apps/family/api/views.py index f2fe0208..d568c1c6 100644 --- a/apps/family/api/views.py +++ b/apps/family/api/views.py @@ -4,6 +4,14 @@ from api.viewsets import ReadProtectedModelViewSet from django_filters.rest_framework import DjangoFilterBackend from rest_framework.filters import SearchFilter +from rest_framework.views import APIView +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework import status +from django.views.decorators.csrf import csrf_exempt +from django.views.decorators.http import require_POST +from django.http import JsonResponse +import json from .serializers import FamilySerializer, FamilyMembershipSerializer, ChallengeSerializer, AchievementSerializer from ..models import Family, FamilyMembership, Challenge, Achievement @@ -59,3 +67,25 @@ class AchievementViewSet(ReadProtectedModelViewSet): filter_backends = [DjangoFilterBackend, SearchFilter] filterset_fields = ['name', ] search_fields = ['$name', ] + + +class BatchAchievementsAPIView(APIView): + permission_classes = [IsAuthenticated] + def post(self, request, format=None): + print("POST de la view spéciale") + family_ids = request.data.get('families', []) + challenge_ids = request.data.get('challenges', []) + + families = Family.objects.filter(id__in=family_ids) + challenges = Challenge.objects.filter(id__in=challenge_ids) + + for family in families: + for challenge in challenges: + a = Achievement(family=family, challenge=challenge) + a.save(update_score=False) + + for family in families: + family.update_score() + Family.update_ranking() + + return Response({'status': 'ok'}, status=status.HTTP_201_CREATED) \ No newline at end of file diff --git a/apps/family/models.py b/apps/family/models.py index 1d2d0d34..1acc9ba8 100644 --- a/apps/family/models.py +++ b/apps/family/models.py @@ -165,7 +165,7 @@ class Achievement(models.Model): return _('Challenge {challenge} carried out by Family {family}').format(challenge=self.challenge.name, family=self.family.name, ) @transaction.atomic - def save(self, *args, **kwargs): + def save(self, *args, update_score=True, **kwargs): """ When saving, also grants points to the family """ @@ -175,8 +175,9 @@ class Achievement(models.Model): super().save(*args, **kwargs) - self.family.refresh_from_db() - self.family.update_score() + if update_score: + self.family.refresh_from_db() + self.family.update_score() # Count only when getting a new achievement if is_new: diff --git a/apps/family/static/family/js/achievements.js b/apps/family/static/family/js/achievements.js index dba07f0d..db29923d 100644 --- a/apps/family/static/family/js/achievements.js +++ b/apps/family/static/family/js/achievements.js @@ -113,33 +113,47 @@ function reset () { * Apply all transactions: all notes in `notes` buy each item in `buttons` */ function consumeAll () { - console.log("consumeAll lancée") if (LOCK) { return } - LOCK = true let error = false if (notes_display.length === 0) { - document.getElementById('note').classList.add('is-invalid') - $('#note_list').html(li('', 'Ajoutez des familles.', 'text-danger')) + // ... gestion erreur ... error = true } - if (buttons.length === 0) { - $('#consos_list').html(li('', 'Ajoutez des défis.', 'text-danger')) + // ... gestion erreur ... error = true } - if (error) { LOCK = false return } - notes_display.forEach(function (family) { - buttons.forEach(function (challenge) { - grantAchievement(family, challenge) - }) + // Récupérer les IDs des familles et des challenges + const family_ids = notes_display.map(fam => fam.id) + const challenge_ids = buttons.map(chal => chal.id) + + $.ajax({ + url: '/family/api/family/achievements/batch/', + type: 'POST', + data: JSON.stringify({ + families: family_ids, + challenges: challenge_ids + }), + contentType: 'application/json', + headers: { + 'X-CSRFToken': CSRF_TOKEN + }, + success: function () { + reset() + addMsg("Défis validés pour les familles !", 'success', 5000) + }, + error: function (e) { + reset() + addMsg("Erreur lors de la création des achievements.",'danger',5000) + } }) } diff --git a/apps/family/urls.py b/apps/family/urls.py index 094ed505..ee491ecc 100644 --- a/apps/family/urls.py +++ b/apps/family/urls.py @@ -1,7 +1,7 @@ # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from django.urls import path +from django.urls import path, include from . import views @@ -18,4 +18,5 @@ urlpatterns = [ path('challenge/detail//', views.ChallengeDetailView.as_view(), name="challenge_detail"), path('challenge/update//', views.ChallengeUpdateView.as_view(), name="challenge_update"), path('manage/', views.FamilyManageView.as_view(), name="manage"), + path('api/family/', include('family.api.urls')), ] From 9e700fd3de664799c11d4076ce4069d481ffc210 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 18 Jul 2025 22:11:43 +0200 Subject: [PATCH 080/153] Achievement delete --- apps/family/api/urls.py | 4 ++- apps/family/api/views.py | 7 ++-- apps/family/tables.py | 23 ++++++++++-- .../family/achievement_confirm_delete.html | 27 ++++++++++++++ .../templates/family/achievement_list.html | 22 ++++++++++++ apps/family/templates/family/manage.html | 19 +++++----- apps/family/urls.py | 2 ++ apps/family/views.py | 35 +++++++++++++++++-- apps/treasury/views.py | 2 +- 9 files changed, 119 insertions(+), 22 deletions(-) create mode 100644 apps/family/templates/family/achievement_confirm_delete.html create mode 100644 apps/family/templates/family/achievement_list.html diff --git a/apps/family/api/urls.py b/apps/family/api/urls.py index e94776d7..35cf1409 100644 --- a/apps/family/api/urls.py +++ b/apps/family/api/urls.py @@ -1,6 +1,7 @@ # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later from django.urls import path + from .views import FamilyViewSet, FamilyMembershipViewSet, ChallengeViewSet, AchievementViewSet, BatchAchievementsAPIView @@ -13,6 +14,7 @@ def register_family_urls(router, path): router.register(path + '/challenge', ChallengeViewSet) router.register(path + '/achievement', AchievementViewSet) + urlpatterns = [ path('achievements/batch/', BatchAchievementsAPIView.as_view(), name='batch_achievements') -] \ No newline at end of file +] diff --git a/apps/family/api/views.py b/apps/family/api/views.py index d568c1c6..50ac0496 100644 --- a/apps/family/api/views.py +++ b/apps/family/api/views.py @@ -8,10 +8,6 @@ from rest_framework.views import APIView from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework import status -from django.views.decorators.csrf import csrf_exempt -from django.views.decorators.http import require_POST -from django.http import JsonResponse -import json from .serializers import FamilySerializer, FamilyMembershipSerializer, ChallengeSerializer, AchievementSerializer from ..models import Family, FamilyMembership, Challenge, Achievement @@ -71,6 +67,7 @@ class AchievementViewSet(ReadProtectedModelViewSet): class BatchAchievementsAPIView(APIView): permission_classes = [IsAuthenticated] + def post(self, request, format=None): print("POST de la view spéciale") family_ids = request.data.get('families', []) @@ -88,4 +85,4 @@ class BatchAchievementsAPIView(APIView): family.update_score() Family.update_ranking() - return Response({'status': 'ok'}, status=status.HTTP_201_CREATED) \ No newline at end of file + return Response({'status': 'ok'}, status=status.HTTP_201_CREATED) diff --git a/apps/family/tables.py b/apps/family/tables.py index f7eb2a16..871dfd35 100644 --- a/apps/family/tables.py +++ b/apps/family/tables.py @@ -3,6 +3,8 @@ import django_tables2 as tables from django.urls import reverse +from django.utils.translation import gettext_lazy as _ +from django_tables2 import A from .models import Family, Challenge, FamilyMembership, Achievement @@ -63,11 +65,28 @@ class AchievementTable(tables.Table): """ List recent achievements. """ + delete = tables.LinkColumn( + 'family:achievement_delete', + args=[A('id')], + verbose_name=_("Delete"), + text=_("Delete"), + orderable=False, + attrs={ + 'th': { + 'id': 'delete-membership-header' + }, + 'a': { + 'class': 'btn btn-danger', + 'data-type': 'delete-membership' + } + }, + ) + class Meta: attrs = { 'class': 'table table-condensed table-striped table-hover' } model = Achievement - fields = ('family', 'challenge', 'obtained_at', ) + fields = ('family', 'challenge', 'challenge__points', 'obtained_at', ) template_name = 'django_tables2/bootstrap4.html' - orderable = False + order_by = ('-obtained_at',) diff --git a/apps/family/templates/family/achievement_confirm_delete.html b/apps/family/templates/family/achievement_confirm_delete.html new file mode 100644 index 00000000..893e0afb --- /dev/null +++ b/apps/family/templates/family/achievement_confirm_delete.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} +{% comment %} +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n crispy_forms_tags %} + +{% block content %} +
                +
                +

                {% trans "Delete achievement" %}

                +
                +
                +
                + {% blocktrans %}Are you sure you want to delete this achievement? This action can't be undone.{% endblocktrans %} +
                +
                + +
                +{% endblock %} diff --git a/apps/family/templates/family/achievement_list.html b/apps/family/templates/family/achievement_list.html new file mode 100644 index 00000000..fe1fd62f --- /dev/null +++ b/apps/family/templates/family/achievement_list.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n django_tables2 %} + +{% block content %} + +
                +
                +

                + {% trans "Recent achievements history" %} +

                + + {% trans "Return to management page" %} + +
                + {% render_table table %} +
                + +{% endblock %} \ No newline at end of file diff --git a/apps/family/templates/family/manage.html b/apps/family/templates/family/manage.html index 275337fa..0ecac60a 100644 --- a/apps/family/templates/family/manage.html +++ b/apps/family/templates/family/manage.html @@ -143,24 +143,21 @@ SPDX-License-Identifier: GPL-3.0-or-later
              • - {# Mode switch #} -
                {# transaction history #} -
                -
                -

                +

                + +
                + {% render_table table %}
                - {% render_table table %}
                {% endblock %} diff --git a/apps/family/urls.py b/apps/family/urls.py index ee491ecc..072cbada 100644 --- a/apps/family/urls.py +++ b/apps/family/urls.py @@ -18,5 +18,7 @@ urlpatterns = [ path('challenge/detail//', views.ChallengeDetailView.as_view(), name="challenge_detail"), path('challenge/update//', views.ChallengeUpdateView.as_view(), name="challenge_update"), path('manage/', views.FamilyManageView.as_view(), name="manage"), + path('achievements/', views.AchievementsView.as_view(), name="achievement_list"), + path('achievement/delete//', views.AchievementDeleteView.as_view(), name="achievement_delete"), path('api/family/', include('family.api.urls')), ] diff --git a/apps/family/views.py b/apps/family/views.py index 35f073fb..6325c445 100644 --- a/apps/family/views.py +++ b/apps/family/views.py @@ -7,6 +7,7 @@ from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin from django.db import transaction from django.views.generic import DetailView, UpdateView +from django.views.generic.edit import DeleteView from django.utils.translation import gettext_lazy as _ from django_tables2 import SingleTableView from permission.backends import PermissionBackend @@ -195,7 +196,7 @@ class ChallengeCreateView(ProtectQuerysetMixin, ProtectedCreateView): ) def get_success_url(self): - return reverse_lazy('family:challenge_list') + return reverse_lazy('family:manage') class ChallengeListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): @@ -264,7 +265,7 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView # retrieves only Transaction that user has the right to see. return Achievement.objects.filter( PermissionBackend.filter_queryset(self.request, Achievement, "view") - ).order_by("-obtained_at").all()[:20] + ).order_by("-obtained_at").all() def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -277,3 +278,33 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView context["can_add_challenge"] = PermissionBackend.check_perm(self.request, "family.add_challenge") return context + + def get_table(self, **kwargs): + table = super().get_table(**kwargs) + table.exclude = ('delete',) + table.orderable = False + return table + + +class AchievementsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): + """ + List all achievements + """ + model = Achievement + table_class = AchievementTable + extra_context = {'title': _('Achievement list')} + + def get_table(self, **kwargs): + table = super().get_table(**kwargs) + table.orderable = True + return table + + +class AchievementDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView): + """ + Delete an Achievement + """ + model = Achievement + + def get_success_url(self): + return reverse_lazy('family:achievement_list') diff --git a/apps/treasury/views.py b/apps/treasury/views.py index eab144c3..eb2fd0d7 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -168,7 +168,7 @@ class InvoiceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): class InvoiceDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView): """ - Delete a non-validated WEI registration + Delete a non-locked Invoice """ model = Invoice extra_context = {"title": _("Delete invoice")} From 03c1bb41b60966877684b9dd87b4173c3f32fdfa Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 18 Jul 2025 23:49:34 +0200 Subject: [PATCH 081/153] First of many --- locale/fr/LC_MESSAGES/django.po | 8 -------- 1 file changed, 8 deletions(-) diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index b62c48bc..76ebb02b 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -4095,14 +4095,6 @@ msgstr "La note est indisponible pour le moment" msgid "Thank you for your understanding -- The Respos Info of BDE" msgstr "Merci de votre compréhension -- Les Respos Info du BDE" -#: note_kfet/templates/base_search.html:15 -msgid "Search by attribute such as name..." -msgstr "Chercher par un attribut tel que le nom..." - -#: note_kfet/templates/base_search.html:23 -msgid "There is no results." -msgstr "Il n'y a pas de résultat." - #: note_kfet/templates/cas/logged.html:8 msgid "" "

                Log In Successful

                You have successfully logged into the Central " From ea8fcad8b594b2c9f8f53952e972c6c26624e489 Mon Sep 17 00:00:00 2001 From: ikea Date: Sat, 19 Jul 2025 00:52:10 +0200 Subject: [PATCH 082/153] =?UTF-8?q?Ajout=20des=20d=C3=A9fis=20r=C3=A9alis?= =?UTF-8?q?=C3=A9s=20par=20une=20famille?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/family/tables.py | 13 +++++++++++++ apps/family/templates/family/family_detail.html | 9 +++++++++ apps/family/views.py | 8 +++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/apps/family/tables.py b/apps/family/tables.py index 871dfd35..759de96d 100644 --- a/apps/family/tables.py +++ b/apps/family/tables.py @@ -90,3 +90,16 @@ class AchievementTable(tables.Table): fields = ('family', 'challenge', 'challenge__points', 'obtained_at', ) template_name = 'django_tables2/bootstrap4.html' order_by = ('-obtained_at',) + +class FamilyAchievementTable(tables.Table): + """ + Table des défis réalisés par une famille spécifique. + """ + class Meta: + model = Achievement + template_name = 'django_tables2/bootstrap4.html' + fields = ('challenge', 'challenge__points', 'obtained_at',) + attrs = { + 'class': 'table table-condensed table-striped table-hover' + } + order_by = ('-obtained_at',) \ No newline at end of file diff --git a/apps/family/templates/family/family_detail.html b/apps/family/templates/family/family_detail.html index a1db566f..dc38edda 100644 --- a/apps/family/templates/family/family_detail.html +++ b/apps/family/templates/family/family_detail.html @@ -13,4 +13,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
                {% render_table member_list %}
                + +
                + +
                +
                + {% trans "Completed challenges" %} +
                + {% render_table achievement_list %} +
                {% endblock %} \ No newline at end of file diff --git a/apps/family/views.py b/apps/family/views.py index 6325c445..33108f32 100644 --- a/apps/family/views.py +++ b/apps/family/views.py @@ -16,7 +16,7 @@ from django.urls import reverse_lazy from member.views import PictureUpdateView from .models import Family, Challenge, FamilyMembership, User, Achievement -from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable, AchievementTable +from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable, AchievementTable, FamilyAchievementTable from .forms import ChallengeForm, FamilyMembershipForm, FamilyForm @@ -88,6 +88,12 @@ class FamilyDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context["can_add_members"] = PermissionBackend()\ .has_perm(self.request.user, "family.add_membership", empty_membership) + # Défis réalisé par la famille + achievements = Achievement.objects.filter(family=family) + achievements_table = FamilyAchievementTable(data=achievements, prefix="achievement-") + achievements_table.paginate(per_page=5, page=self.request.GET.get('achievement-page', 1)) + context["achievement_list"] = achievements_table + return context From edb6abfff5ca51236844ff73233dc1624b75d5ee Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 19 Jul 2025 16:24:25 +0200 Subject: [PATCH 083/153] Add fee field to WEIRegistration to be able to sort on validation status --- apps/member/apps.py | 11 ++++- apps/member/signals.py | 22 ++++++++++ ...ation_fee_alter_weiclub_fee_soge_credit.py | 23 ++++++++++ apps/wei/models.py | 35 ++++++++++++---- apps/wei/tables.py | 15 ++++--- apps/wei/views.py | 42 +++++++++++++++++-- 6 files changed, 131 insertions(+), 17 deletions(-) create mode 100644 apps/wei/migrations/0016_weiregistration_fee_alter_weiclub_fee_soge_credit.py diff --git a/apps/member/apps.py b/apps/member/apps.py index d5b1f630..84799e6a 100644 --- a/apps/member/apps.py +++ b/apps/member/apps.py @@ -6,7 +6,7 @@ from django.conf import settings from django.db.models.signals import post_save from django.utils.translation import gettext_lazy as _ -from .signals import save_user_profile +from .signals import save_user_profile, update_wei_registration_fee_on_membership_creation, update_wei_registration_fee_on_club_change class MemberConfig(AppConfig): @@ -17,7 +17,16 @@ class MemberConfig(AppConfig): """ Define app internal signals to interact with other apps """ + from .models import Membership, Club post_save.connect( save_user_profile, sender=settings.AUTH_USER_MODEL, ) + post_save.connect( + update_wei_registration_fee_on_membership_creation, + sender=Membership + ) + post_save.connect( + update_wei_registration_fee_on_club_change, + sender=Club + ) diff --git a/apps/member/signals.py b/apps/member/signals.py index 869f9117..e74c37ad 100644 --- a/apps/member/signals.py +++ b/apps/member/signals.py @@ -13,3 +13,25 @@ def save_user_profile(instance, created, raw, **_kwargs): instance.profile.email_confirmed = True instance.profile.registration_valid = True instance.profile.save() + + +def update_wei_registration_fee_on_membership_creation(sender, instance, created, **kwargs): + if created: + from wei.models import WEIRegistration + if instance.club.id == 1 or instance.club.id == 2: + registrations = WEIRegistration.objects.filter( + user=instance.user, + wei__year=instance.date_start.year, + ) + for r in registrations: + r.save() + + +def update_wei_registration_fee_on_club_change(sender, instance, **kwargs): + from wei.models import WEIRegistration + if instance.id == 1 or instance.id == 2: + registrations = WEIRegistration.objects.filter( + wei__year=instance.membership_start.year, + ) + for r in registrations: + r.save() diff --git a/apps/wei/migrations/0016_weiregistration_fee_alter_weiclub_fee_soge_credit.py b/apps/wei/migrations/0016_weiregistration_fee_alter_weiclub_fee_soge_credit.py new file mode 100644 index 00000000..6d2d1289 --- /dev/null +++ b/apps/wei/migrations/0016_weiregistration_fee_alter_weiclub_fee_soge_credit.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.4 on 2025-07-19 12:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wei', '0015_remove_weiclub_caution_amount_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='weiregistration', + name='fee', + field=models.PositiveIntegerField(blank=True, default=0, verbose_name='fee'), + ), + migrations.AlterField( + model_name='weiclub', + name='fee_soge_credit', + field=models.PositiveIntegerField(default=2000, verbose_name='membership fee (soge credit)'), + ), + ] diff --git a/apps/wei/models.py b/apps/wei/models.py index 59f018d7..18ba8a58 100644 --- a/apps/wei/models.py +++ b/apps/wei/models.py @@ -285,6 +285,12 @@ class WEIRegistration(models.Model): "encoded in JSON"), ) + fee = models.PositiveIntegerField( + default=0, + verbose_name=_('fee'), + blank=True, + ) + class Meta: unique_together = ('user', 'wei',) verbose_name = _("WEI User") @@ -309,7 +315,25 @@ class WEIRegistration(models.Model): self.information_json = json.dumps(information, indent=2) @property - def fee(self): + def is_validated(self): + try: + return self.membership is not None + except AttributeError: + return False + + @property + def validation_status(self): + """ + Define an order to have easier access to validatable registrations + """ + if self.fee + (self.wei.deposit_amount if self.deposit_type == 'note' else 0) > self.user.note.balance: + return 2 + elif self.first_year: + return 1 + else: + return 0 + + def calculate_fee(self): bde = Club.objects.get(pk=1) kfet = Club.objects.get(pk=2) @@ -336,12 +360,9 @@ class WEIRegistration(models.Model): return fee - @property - def is_validated(self): - try: - return self.membership is not None - except AttributeError: - return False + def save(self, *args, **kwargs): + self.fee = self.calculate_fee() + super().save(*args, **kwargs) class WEIMembership(Membership): diff --git a/apps/wei/tables.py b/apps/wei/tables.py index 9daadcb6..3f0c322e 100644 --- a/apps/wei/tables.py +++ b/apps/wei/tables.py @@ -56,10 +56,13 @@ class WEIRegistrationTable(tables.Table): } ) - validate = tables.Column( + validate = tables.LinkColumn( + 'wei:wei_update_registration', + args=[A('pk')], verbose_name=_("Validate"), - orderable=False, - accessor=A('pk'), + orderable=True, + accessor='validate_status', + text=_("Validate"), attrs={ 'th': { 'id': 'validate-membership-header' @@ -100,10 +103,11 @@ class WEIRegistrationTable(tables.Table): url = reverse_lazy('wei:wei_update_registration', args=(record.pk,)) + '?validate=true' text = _('Validate') - if record.fee > record.user.note.balance and not record.soge_credit: + status = record.validation_status + if status == 2: btn_class = 'btn-secondary' tooltip = _("The user does not have enough money.") - elif record.first_year: + elif status == 1: btn_class = 'btn-info' tooltip = _("The user is in first year. You may validate the credit, the algorithm will run later.") else: @@ -121,6 +125,7 @@ class WEIRegistrationTable(tables.Table): attrs = { 'class': 'table table-condensed table-striped table-hover' } + order_by = ('validate', 'user',) model = WEIRegistration template_name = 'django_tables2/bootstrap4.html' fields = ('user', 'user__first_name', 'user__last_name', 'first_year', 'deposit_check', diff --git a/apps/wei/views.py b/apps/wei/views.py index 4ac679f9..d5151848 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -13,7 +13,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User from django.core.exceptions import PermissionDenied from django.db import transaction -from django.db.models import Q, Count +from django.db.models import Q, Count, Case, When, Value, IntegerField, F from django.db.models.functions.text import Lower from django import forms from django.http import HttpResponse, Http404 @@ -133,6 +133,23 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, D membership=None, wei=club ) + # Annotate the query to be able to sort registrations on validate status + pre_registrations = pre_registrations.annotate( + deposit=Case( + When(deposit_type='note', then=F('wei__deposit_amount')), + default=Value(0), + output_field=IntegerField() + ) + ).annotate( + total_fee=F('fee') + F('deposit') + ).annotate( + validate_status=Case( + When(total_fee__gt=F('user__note__balance'), then=Value(2)), + When(first_year=True, then=Value(1)), + default=Value(0), + output_field=IntegerField(), + ) + ) buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request, Bus, "view")) \ .filter(wei=self.object).annotate(count=Count("memberships")).order_by("name") return [club_transactions, club_member, pre_registrations, buses, ] @@ -261,6 +278,23 @@ class WEIRegistrationsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTable def get_queryset(self, **kwargs): qs = super().get_queryset(**kwargs).filter(wei=self.club, membership=None).distinct() + qs = qs.annotate( + deposit=Case( + When(deposit_type='note', then=F('wei__deposit_amount')), + default=Value(0), + output_field=IntegerField() + ) + ).annotate( + total_fee=F('fee') + F('deposit') + ).annotate( + validate_status=Case( + When(total_fee__gt=F('user__note__balance'), then=Value(2)), + When(first_year=True, then=Value(1)), + default=Value(0), + output_field=IntegerField(), + ) + ) + pattern = self.request.GET.get("search", "") if pattern: @@ -963,9 +997,9 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): form = context["form"] if registration.soge_credit: - form.fields["credit_amount"].initial = registration.fee + form.fields["credit_amount"].initial = fee else: - form.fields["credit_amount"].initial = max(0, registration.fee - registration.user.note.balance) + form.fields["credit_amount"].initial = max(0, fee - registration.user.note.balance) return context @@ -1052,7 +1086,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): fee = club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid if registration.soge_credit: - fee = 2000 + fee = registration.wei.fee_soge_credit kfet = club.parent_club bde = kfet.parent_club From 9ab4df94e66ef6a4538c1e67e644dc06b6117d91 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 19 Jul 2025 16:55:07 +0200 Subject: [PATCH 084/153] Minor fixes --- apps/wei/forms/__init__.py | 4 ++-- apps/wei/forms/registration.py | 17 ++--------------- apps/wei/tables.py | 4 ++-- apps/wei/views.py | 15 ++++----------- 4 files changed, 10 insertions(+), 30 deletions(-) diff --git a/apps/wei/forms/__init__.py b/apps/wei/forms/__init__.py index 71fb2c5f..1cb9f283 100644 --- a/apps/wei/forms/__init__.py +++ b/apps/wei/forms/__init__.py @@ -1,11 +1,11 @@ # Copyright (C) 2018-2025 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .registration import WEIForm, WEIRegistrationForm, WEIRegistration1AForm, WEIRegistration2AForm, WEIMembership1AForm, \ +from .registration import WEIForm, WEIRegistrationForm, WEIMembership1AForm, \ WEIMembershipForm, BusForm, BusTeamForm from .surveys import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, CurrentSurvey __all__ = [ - 'WEIForm', 'WEIRegistrationForm', 'WEIRegistration1AForm', 'WEIRegistration2AForm', 'WEIMembership1AForm', 'WEIMembershipForm', 'BusForm', 'BusTeamForm', + 'WEIForm', 'WEIRegistrationForm', 'WEIMembership1AForm', 'WEIMembershipForm', 'BusForm', 'BusTeamForm', 'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey', ] diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index 35838c2b..4bf0dcd2 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -44,7 +44,7 @@ class WEIRegistrationForm(forms.ModelForm): fields = [ 'user', 'soge_credit', 'birth_date', 'gender', 'clothing_size', 'health_issues', 'emergency_contact_name', 'emergency_contact_phone', - 'first_year', 'information_json', 'deposit_check' + 'first_year', 'information_json', 'deposit_check', 'deposit_type' ] widgets = { "user": Autocomplete( @@ -62,21 +62,8 @@ class WEIRegistrationForm(forms.ModelForm): "deposit_check": forms.BooleanField( required=False, ), - } - - -class WEIRegistration2AForm(WEIRegistrationForm): - class Meta(WEIRegistrationForm.Meta): - fields = WEIRegistrationForm.Meta.fields + ['deposit_type'] - widgets = WEIRegistrationForm.Meta.widgets.copy() - widgets.update({ "deposit_type": forms.RadioSelect(), - }) - - -class WEIRegistration1AForm(WEIRegistrationForm): - class Meta(WEIRegistrationForm.Meta): - fields = WEIRegistrationForm.Meta.fields + } class WEIChooseBusForm(forms.Form): diff --git a/apps/wei/tables.py b/apps/wei/tables.py index 3f0c322e..82e02e44 100644 --- a/apps/wei/tables.py +++ b/apps/wei/tables.py @@ -57,7 +57,7 @@ class WEIRegistrationTable(tables.Table): ) validate = tables.LinkColumn( - 'wei:wei_update_registration', + 'wei:validate_registration', args=[A('pk')], verbose_name=_("Validate"), orderable=True, @@ -101,7 +101,7 @@ class WEIRegistrationTable(tables.Table): if not hasperm: return format_html("") - url = reverse_lazy('wei:wei_update_registration', args=(record.pk,)) + '?validate=true' + url = reverse_lazy('wei:wei_update_registration', args=(record.pk,)) text = _('Validate') status = record.validation_status if status == 2: diff --git a/apps/wei/views.py b/apps/wei/views.py index d5151848..d236af8b 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -35,7 +35,7 @@ from permission.views import ProtectQuerysetMixin, ProtectedCreateView from .forms.registration import WEIChooseBusForm from .models import WEIClub, WEIRegistration, WEIMembership, Bus, BusTeam, WEIRole -from .forms import WEIForm, WEIRegistrationForm, WEIRegistration1AForm, WEIRegistration2AForm, BusForm, BusTeamForm, WEIMembership1AForm, \ +from .forms import WEIForm, WEIRegistrationForm, BusForm, BusTeamForm, WEIMembership1AForm, \ WEIMembershipForm, CurrentSurvey from .tables import BusRepartitionTable, BusTable, BusTeamTable, WEITable, WEIRegistrationTable, \ WEIRegistration1ATable, WEIMembershipTable @@ -277,7 +277,7 @@ class WEIRegistrationsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTable def get_queryset(self, **kwargs): qs = super().get_queryset(**kwargs).filter(wei=self.club, membership=None).distinct() - + # Annotate the query to be able to sort registrations on validate status qs = qs.annotate( deposit=Case( When(deposit_type='note', then=F('wei__deposit_amount')), @@ -544,7 +544,7 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView): Register a new user to the WEI """ model = WEIRegistration - form_class = WEIRegistration1AForm + form_class = WEIRegistrationForm extra_context = {"title": _("Register first year student to the WEI")} def get_sample_object(self): @@ -640,7 +640,7 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView): Register an old user to the WEI """ model = WEIRegistration - form_class = WEIRegistration2AForm + form_class = WEIRegistrationForm extra_context = {"title": _("Register old student to the WEI")} def get_sample_object(self): @@ -773,14 +773,11 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update if today >= wei.date_start or today < wei.membership_start: return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,))) # Store the validate parameter in the view's state - self.should_validate = request.GET.get('validate', False) return super().dispatch(request, *args, **kwargs) def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["club"] = self.object.wei - # Pass the validate parameter to the template - context["should_validate"] = self.should_validate if self.object.is_validated: membership_form = self.get_membership_form(instance=self.object.membership, @@ -822,7 +819,6 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update if not self.object.first_year and "deposit_type" in form.fields: form.fields["deposit_type"].required = True form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit") - form.fields["deposit_type"].widget = forms.RadioSelect(choices=form.fields["deposit_type"].choices) return form @@ -896,9 +892,6 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update survey = CurrentSurvey(self.object) if not survey.is_complete(): return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk}) - # On redirige vers la validation uniquement si c'est explicitement demandé (et stocké dans la vue) - if self.should_validate and self.request.user.has_perm("wei.add_weimembership"): - return reverse_lazy("wei:validate_registration", kwargs={"pk": self.object.pk}) return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.wei.pk}) From 2755a5f7ab8579afff37584bab89555817603d8b Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 19 Jul 2025 17:10:25 +0200 Subject: [PATCH 085/153] Minor fail --- apps/wei/tables.py | 7 ++---- apps/wei/templates/wei/weiclub_detail.html | 27 +++++++++++----------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/apps/wei/tables.py b/apps/wei/tables.py index 82e02e44..5e3536c2 100644 --- a/apps/wei/tables.py +++ b/apps/wei/tables.py @@ -56,13 +56,10 @@ class WEIRegistrationTable(tables.Table): } ) - validate = tables.LinkColumn( - 'wei:validate_registration', - args=[A('pk')], + validate = tables.Column( verbose_name=_("Validate"), orderable=True, accessor='validate_status', - text=_("Validate"), attrs={ 'th': { 'id': 'validate-membership-header' @@ -101,7 +98,7 @@ class WEIRegistrationTable(tables.Table): if not hasperm: return format_html("") - url = reverse_lazy('wei:wei_update_registration', args=(record.pk,)) + url = reverse_lazy('wei:validate_registration', args=(record.pk,)) text = _('Validate') status = record.validation_status if status == 2: diff --git a/apps/wei/templates/wei/weiclub_detail.html b/apps/wei/templates/wei/weiclub_detail.html index f7c18c9a..2a573b03 100644 --- a/apps/wei/templates/wei/weiclub_detail.html +++ b/apps/wei/templates/wei/weiclub_detail.html @@ -67,20 +67,6 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endif %} -{% if history_list.data %} -
                - -
                - {% render_table history_list %} -
                -
                -{% endif %} - {% if pre_registrations.data %}
                @@ -99,6 +85,19 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "Attribute buses" %} {% endif %} +{% if history_list.data %} +
                + +
                + {% render_table history_list %} +
                +
                +{% endif %} {% endblock %} From 61999a31a5d0a867e67895665cd29662b2d95b8a Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 19 Jul 2025 18:04:14 +0200 Subject: [PATCH 086/153] Wei details --- apps/wei/forms/registration.py | 2 +- locale/fr/LC_MESSAGES/django.po | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index 35838c2b..4fbbaaae 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -82,7 +82,7 @@ class WEIRegistration1AForm(WEIRegistrationForm): class WEIChooseBusForm(forms.Form): bus = forms.ModelMultipleChoiceField( queryset=Bus.objects, - label=_("bus"), + label=_("Bus"), help_text=_("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."), widget=CheckboxSelectMultiple(), diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 76ebb02b..16ca4aab 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -3152,10 +3152,8 @@ msgid "Note transaction" msgstr "Transaction Note" #: apps/wei/models.py:217 -#, fuzzy -#| msgid "Credit type" msgid "deposit type" -msgstr "Type de rechargement" +msgstr "type de caution" #: apps/wei/models.py:221 apps/wei/templates/wei/weimembership_form.html:64 msgid "birth date" From 1274315cde8fbb3a5773dfc4309de1c9b35d6799 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 19 Jul 2025 18:55:49 +0200 Subject: [PATCH 087/153] Last untranslated field --- apps/wei/templates/wei/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wei/templates/wei/base.html b/apps/wei/templates/wei/base.html index efb619ac..2975efc0 100644 --- a/apps/wei/templates/wei/base.html +++ b/apps/wei/templates/wei/base.html @@ -50,7 +50,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endif %} {% if club.deposit_amount > 0 %} -
                {% trans 'Deposit amount'|capfirst %}
                +
                {% trans 'deposit amount'|capfirst %}
                {{ club.deposit_amount|pretty_money }}
                {% endif %} From 4c3b714b56c57cf455e6445830a15980017af5ae Mon Sep 17 00:00:00 2001 From: ikea Date: Sun, 20 Jul 2025 21:31:59 +0200 Subject: [PATCH 088/153] Affiche les familles dans le profil utilisateur avec lien vers la page de la famille --- apps/member/templates/member/includes/profile_info.html | 9 +++++++++ apps/member/views.py | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/apps/member/templates/member/includes/profile_info.html b/apps/member/templates/member/includes/profile_info.html index 3a927c9f..dd184c02 100644 --- a/apps/member/templates/member/includes/profile_info.html +++ b/apps/member/templates/member/includes/profile_info.html @@ -7,6 +7,15 @@
                {% trans 'username'|capfirst %}
                {{ user_object.username }}
                +
                {% trans 'family'|capfirst %}
                +
                + {% for family in families %} + {{ family.name }}{% if not forloop.last %}, {% endif %} + {% empty %} + {% trans 'None' %} + {% endfor %} +
                + {% if user_object.pk == user.pk %}
                {% trans 'password'|capfirst %}
                diff --git a/apps/member/views.py b/apps/member/views.py index 19f9b46f..d2b27291 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 family.models import Family from django import forms from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \ @@ -92,6 +93,9 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): if fields_modifiable: context['profile_form'] = profile_form + families = Family.objects.filter(members=user).distinct() + context["families"] = families + return context @transaction.atomic From 2af671d61a466064bcfe61caf87f07804f627d97 Mon Sep 17 00:00:00 2001 From: ikea Date: Sun, 20 Jul 2025 23:27:53 +0200 Subject: [PATCH 089/153] =?UTF-8?q?Fix=20traduction=20.po=20=E2=80=93=20su?= =?UTF-8?q?ppression=20doublons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locale/fr/LC_MESSAGES/django.po | 8 -------- 1 file changed, 8 deletions(-) diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index b62c48bc..76ebb02b 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -4095,14 +4095,6 @@ msgstr "La note est indisponible pour le moment" msgid "Thank you for your understanding -- The Respos Info of BDE" msgstr "Merci de votre compréhension -- Les Respos Info du BDE" -#: note_kfet/templates/base_search.html:15 -msgid "Search by attribute such as name..." -msgstr "Chercher par un attribut tel que le nom..." - -#: note_kfet/templates/base_search.html:23 -msgid "There is no results." -msgstr "Il n'y a pas de résultat." - #: note_kfet/templates/cas/logged.html:8 msgid "" "

                Log In Successful

                You have successfully logged into the Central " From db4d0dd83a981eeaac3dbcf66ef9d2b25e36a0e2 Mon Sep 17 00:00:00 2001 From: ikea Date: Mon, 21 Jul 2025 22:11:20 +0200 Subject: [PATCH 090/153] fix --- .../templates/member/includes/profile_info.html | 12 +++++++----- apps/member/views.py | 6 +++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/member/templates/member/includes/profile_info.html b/apps/member/templates/member/includes/profile_info.html index dd184c02..3ea525d5 100644 --- a/apps/member/templates/member/includes/profile_info.html +++ b/apps/member/templates/member/includes/profile_info.html @@ -9,11 +9,13 @@
                {% trans 'family'|capfirst %}
                - {% for family in families %} - {{ family.name }}{% if not forloop.last %}, {% endif %} - {% empty %} - {% trans 'None' %} - {% endfor %} + {% if families %} + {% for fam in families %} + {{ fam.name }}{% if not forloop.last %}, {% endif %} + {% endfor %} + {% else %} + Aucune + {% endif %}
                {% if user_object.pk == user.pk %} diff --git a/apps/member/views.py b/apps/member/views.py index d2b27291..3c1ebef7 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -93,9 +93,6 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): if fields_modifiable: context['profile_form'] = profile_form - families = Family.objects.filter(members=user).distinct() - context["families"] = families - return context @transaction.atomic @@ -210,6 +207,9 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): modified_note.is_active = True context["can_unlock_note"] = not user.note.is_active and PermissionBackend\ .check_perm(self.request, "note.change_noteuser_is_active", modified_note) + + families = Family.objects.filter(members__user=user).distinct() + context["families"] = families return context From c66cc14576a9e02ee3b228732dad2dde91f17abf Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Tue, 22 Jul 2025 01:30:47 +0200 Subject: [PATCH 091/153] Added valid field and logic for Achievement --- ...ent_valid_alter_familymembership_family.py | 24 +++++++++ apps/family/models.py | 11 ++-- apps/family/tables.py | 26 +++++++-- .../family/achievement_confirm_delete.html | 4 +- .../family/achievement_confirm_validate.html | 28 ++++++++++ .../templates/family/achievement_list.html | 15 +++++- apps/family/urls.py | 17 +++--- apps/family/views.py | 54 +++++++++++++++---- apps/member/views.py | 4 +- 9 files changed, 152 insertions(+), 31 deletions(-) create mode 100644 apps/family/migrations/0003_achievement_valid_alter_familymembership_family.py create mode 100644 apps/family/templates/family/achievement_confirm_validate.html diff --git a/apps/family/migrations/0003_achievement_valid_alter_familymembership_family.py b/apps/family/migrations/0003_achievement_valid_alter_familymembership_family.py new file mode 100644 index 00000000..9121a1ff --- /dev/null +++ b/apps/family/migrations/0003_achievement_valid_alter_familymembership_family.py @@ -0,0 +1,24 @@ +# Generated by Django 5.2.4 on 2025-07-21 21:02 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('family', '0002_family_display_image'), + ] + + operations = [ + migrations.AddField( + model_name='achievement', + name='valid', + field=models.BooleanField(default=False, verbose_name='valid'), + ), + migrations.AlterField( + model_name='familymembership', + name='family', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='memberships', to='family.family', verbose_name='family'), + ), + ] diff --git a/apps/family/models.py b/apps/family/models.py index 1acc9ba8..708c3929 100644 --- a/apps/family/models.py +++ b/apps/family/models.py @@ -45,9 +45,9 @@ class Family(models.Model): return self.name def update_score(self, *args, **kwargs): - challenge_set = Challenge.objects.select_for_update().filter(achievement__family=self) + challenge_set = Challenge.objects.select_for_update().filter(achievement__family=self, achievement__valid=True) points_sum = challenge_set.aggregate(models.Sum("points")) - self.score = points_sum["points__sum"] + self.score = points_sum["points__sum"] if points_sum["points__sum"] else 0 self.save() self.update_ranking() @@ -86,7 +86,7 @@ class FamilyMembership(models.Model): family = models.ForeignKey( Family, on_delete=models.PROTECT, - related_name=_('members'), + related_name=_('memberships'), verbose_name=_('family'), ) @@ -157,6 +157,11 @@ class Achievement(models.Model): default=timezone.now, ) + valid = models.BooleanField( + verbose_name=_('valid'), + default=False, + ) + class Meta: verbose_name = _('achievement') verbose_name_plural = _('achievements') diff --git a/apps/family/tables.py b/apps/family/tables.py index 759de96d..0a0b773a 100644 --- a/apps/family/tables.py +++ b/apps/family/tables.py @@ -65,6 +65,23 @@ class AchievementTable(tables.Table): """ List recent achievements. """ + validate = tables.LinkColumn( + 'family:achievement_validate', + args=[A('id')], + verbose_name=_("Validate"), + text=_("Validate"), + orderable=False, + attrs={ + 'th': { + 'id': 'validate-achievement-header' + }, + 'a': { + 'class': 'btn btn-success', + 'data-type': 'validate-achievement' + } + }, + ) + delete = tables.LinkColumn( 'family:achievement_delete', args=[A('id')], @@ -73,11 +90,11 @@ class AchievementTable(tables.Table): orderable=False, attrs={ 'th': { - 'id': 'delete-membership-header' + 'id': 'delete-achievement-header' }, 'a': { 'class': 'btn btn-danger', - 'data-type': 'delete-membership' + 'data-type': 'delete-achievement' } }, ) @@ -87,10 +104,11 @@ class AchievementTable(tables.Table): 'class': 'table table-condensed table-striped table-hover' } model = Achievement - fields = ('family', 'challenge', 'challenge__points', 'obtained_at', ) + fields = ('family', 'challenge', 'challenge__points', 'obtained_at', 'valid') template_name = 'django_tables2/bootstrap4.html' order_by = ('-obtained_at',) + class FamilyAchievementTable(tables.Table): """ Table des défis réalisés par une famille spécifique. @@ -102,4 +120,4 @@ class FamilyAchievementTable(tables.Table): attrs = { 'class': 'table table-condensed table-striped table-hover' } - order_by = ('-obtained_at',) \ No newline at end of file + order_by = ('-obtained_at',) diff --git a/apps/family/templates/family/achievement_confirm_delete.html b/apps/family/templates/family/achievement_confirm_delete.html index 893e0afb..3b378fa5 100644 --- a/apps/family/templates/family/achievement_confirm_delete.html +++ b/apps/family/templates/family/achievement_confirm_delete.html @@ -18,9 +18,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
                {% csrf_token %} {% trans "Return to achievements list" %} - {% if not object.locked %} - - {% endif %} +
                diff --git a/apps/family/templates/family/achievement_confirm_validate.html b/apps/family/templates/family/achievement_confirm_validate.html new file mode 100644 index 00000000..e417480a --- /dev/null +++ b/apps/family/templates/family/achievement_confirm_validate.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} +{% comment %} +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n crispy_forms_tags %} + +{% block content %} +
                +
                +

                {% trans "Validate achievement" %}

                +
                +
                +
                + {% blocktrans %}Are you sure you want to validate this achievement? This action can't be undone.{% endblocktrans %} +
                +
                + +
                +{% endblock %} diff --git a/apps/family/templates/family/achievement_list.html b/apps/family/templates/family/achievement_list.html index fe1fd62f..63cd6255 100644 --- a/apps/family/templates/family/achievement_list.html +++ b/apps/family/templates/family/achievement_list.html @@ -10,13 +10,24 @@ SPDX-License-Identifier: GPL-3.0-or-later

                - {% trans "Recent achievements history" %} + {% trans "Invalid achievements history" %}

                {% trans "Return to management page" %}
                - {% render_table table %} + {% render_table invalid %}
                +
                +
                +

                + {% trans "Valid achievements history" %} +

                + + {% trans "Return to management page" %} + +
                + {% render_table valid %} +
                {% endblock %} \ No newline at end of file diff --git a/apps/family/urls.py b/apps/family/urls.py index 072cbada..7d9e9c5b 100644 --- a/apps/family/urls.py +++ b/apps/family/urls.py @@ -9,16 +9,17 @@ app_name = 'family' urlpatterns = [ path('list/', views.FamilyListView.as_view(), name="family_list"), path('add-family/', views.FamilyCreateView.as_view(), name="add_family"), - path('detail//', views.FamilyDetailView.as_view(), name="family_detail"), - path('update//', views.FamilyUpdateView.as_view(), name="family_update"), - path('update_pic//', views.FamilyPictureUpdateView.as_view(), name="update_pic"), - path('add_member//', views.FamilyAddMemberView.as_view(), name="family_add_member"), + path('/detail/', views.FamilyDetailView.as_view(), name="family_detail"), + path('/update/', views.FamilyUpdateView.as_view(), name="family_update"), + path('/update_pic/', views.FamilyPictureUpdateView.as_view(), name="update_pic"), + path('/add_member/', views.FamilyAddMemberView.as_view(), name="family_add_member"), path('challenge/list/', views.ChallengeListView.as_view(), name="challenge_list"), path('add-challenge/', views.ChallengeCreateView.as_view(), name="add_challenge"), - path('challenge/detail//', views.ChallengeDetailView.as_view(), name="challenge_detail"), - path('challenge/update//', views.ChallengeUpdateView.as_view(), name="challenge_update"), + path('challenge//detail/', views.ChallengeDetailView.as_view(), name="challenge_detail"), + path('challenge//update/', views.ChallengeUpdateView.as_view(), name="challenge_update"), path('manage/', views.FamilyManageView.as_view(), name="manage"), - path('achievements/', views.AchievementsView.as_view(), name="achievement_list"), - path('achievement/delete//', views.AchievementDeleteView.as_view(), name="achievement_delete"), + path('achievement/list/', views.AchievementsView.as_view(), name="achievement_list"), + path('achievement//validate/', views.AchievementValidateView.as_view(), name="achievement_validate"), + path('achievement//delete/', views.AchievementDeleteView.as_view(), name="achievement_delete"), path('api/family/', include('family.api.urls')), ] diff --git a/apps/family/views.py b/apps/family/views.py index 33108f32..a6e886a8 100644 --- a/apps/family/views.py +++ b/apps/family/views.py @@ -4,12 +4,14 @@ from datetime import date from django.conf import settings +from django.shortcuts import redirect from django.contrib.auth.mixins import LoginRequiredMixin from django.db import transaction -from django.views.generic import DetailView, UpdateView +from django.views.generic import DetailView, UpdateView, ListView from django.views.generic.edit import DeleteView +from django.views.generic.base import TemplateView from django.utils.translation import gettext_lazy as _ -from django_tables2 import SingleTableView +from django_tables2 import SingleTableView, MultiTableMixin from permission.backends import PermissionBackend from permission.views import ProtectQuerysetMixin, ProtectedCreateView from django.urls import reverse_lazy @@ -287,23 +289,57 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView def get_table(self, **kwargs): table = super().get_table(**kwargs) - table.exclude = ('delete',) + table.exclude = ('delete', 'validate',) table.orderable = False return table -class AchievementsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): +class AchievementsView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView): """ List all achievements """ model = Achievement - table_class = AchievementTable + tables = [AchievementTable, AchievementTable, ] extra_context = {'title': _('Achievement list')} - def get_table(self, **kwargs): - table = super().get_table(**kwargs) - table.orderable = True - return table + def get_tables(self, **kwargs): + tables = super().get_tables(**kwargs) + + tables[0].prefix = 'invalid-' + tables[1].prefix = 'valid-' + tables[1].exclude = ('validate', 'delete',) + + return tables + + def get_tables_data(self): + table_valid = self.get_queryset().filter(valid=True) + table_invalid = self.get_queryset().filter(valid=False) + return [table_invalid, table_valid, ] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + tables = context['tables'] + + context['invalid'] = tables[0] + context['valid'] = tables[1] + return context + + +class AchievementValidateView(ProtectQuerysetMixin, LoginRequiredMixin, TemplateView): + """ + Validate an achievement obtained by a family + """ + template_name = 'family/achievement_confirm_validate.html' + + def post(self, request, pk): + # On récupère l'objet à valider + achievement = Achievement.objects.get(pk=pk) + # On modifie le champ valid + achievement.valid = True + achievement.save() + # On redirige vers la page de détail ou la liste + return redirect(reverse_lazy('family:achievement_list')) class AchievementDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView): diff --git a/apps/member/views.py b/apps/member/views.py index 3c1ebef7..3cf3cd32 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -207,8 +207,8 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): modified_note.is_active = True context["can_unlock_note"] = not user.note.is_active and PermissionBackend\ .check_perm(self.request, "note.change_noteuser_is_active", modified_note) - - families = Family.objects.filter(members__user=user).distinct() + + families = Family.objects.filter(memberships__user=user).distinct() context["families"] = families return context From 8c3ae338ea5c9cae5ccc56f8b28172d802b9384a Mon Sep 17 00:00:00 2001 From: quark Date: Tue, 22 Jul 2025 18:20:05 +0200 Subject: [PATCH 092/153] fix organizer field error --- apps/activity/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/activity/forms.py b/apps/activity/forms.py index 305c4f03..a865ece6 100644 --- a/apps/activity/forms.py +++ b/apps/activity/forms.py @@ -32,7 +32,7 @@ class ActivityForm(forms.ModelForm): def clean_organizer(self): organizer = self.cleaned_data['organizer'] if not organizer.note.is_active: - self.add_error('organiser', _('The note of this club is inactive.')) + self.add_error('organizer', _('The note of this club is inactive.')) return organizer def clean_date_end(self): From adc925e4b14bcf911eba217b3f6f492e1f432d73 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Tue, 22 Jul 2025 18:31:55 +0200 Subject: [PATCH 093/153] Tests --- apps/family/api/views.py | 35 +- .../0004_remove_challenge_obtained.py | 17 + apps/family/models.py | 34 +- .../static/family/img/default_picture.png | Bin 0 -> 4054 bytes apps/family/static/family/js/achievements.js | 34 +- apps/family/templates/family/manage.html | 8 +- apps/family/tests/__init__.py | 0 apps/family/tests/test_family.py | 318 ++++++++++++++++++ apps/family/urls.py | 8 +- apps/family/views.py | 35 +- 10 files changed, 403 insertions(+), 86 deletions(-) create mode 100644 apps/family/migrations/0004_remove_challenge_obtained.py create mode 100644 apps/family/static/family/img/default_picture.png create mode 100644 apps/family/tests/__init__.py create mode 100644 apps/family/tests/test_family.py diff --git a/apps/family/api/views.py b/apps/family/api/views.py index 50ac0496..79a719d1 100644 --- a/apps/family/api/views.py +++ b/apps/family/api/views.py @@ -3,7 +3,7 @@ from api.viewsets import ReadProtectedModelViewSet from django_filters.rest_framework import DjangoFilterBackend -from rest_framework.filters import SearchFilter +from api.filters import RegexSafeSearchFilter from rest_framework.views import APIView from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response @@ -21,9 +21,9 @@ class FamilyViewSet(ReadProtectedModelViewSet): """ queryset = Family.objects.order_by('id') serializer_class = FamilySerializer - filter_backends = [DjangoFilterBackend, SearchFilter] - filterset_fields = ['name', ] - search_fields = ['$name', ] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] + filterset_fields = ['name', 'description', 'score', 'rank', ] + search_fields = ['$name', '$description', ] class FamilyMembershipViewSet(ReadProtectedModelViewSet): @@ -34,9 +34,11 @@ class FamilyMembershipViewSet(ReadProtectedModelViewSet): """ queryset = FamilyMembership.objects.order_by('id') serializer_class = FamilyMembershipSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] - filterset_fields = ['name', ] - search_fields = ['$name', ] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] + filterset_fields = ['user__username', 'user__first_name', 'user__last_name', 'user__email', 'user__note__alias__name', + 'user__note__alias__normalized_name', 'family__name', 'family__description', 'year', ] + search_fields = ['$user__username', '$user__first_name', '$user__last_name', '$user__email', '$user__note__alias__name', + '$user__note__alias__normalized_name', '$family__name', '$family__description', '$year', ] class ChallengeViewSet(ReadProtectedModelViewSet): @@ -47,9 +49,9 @@ class ChallengeViewSet(ReadProtectedModelViewSet): """ queryset = Challenge.objects.order_by('id') serializer_class = ChallengeSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] - filterset_fields = ['name', ] - search_fields = ['$name', ] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] + filterset_fields = ['name', 'description', 'points', ] + search_fields = ['$name', '$description', '$points', ] class AchievementViewSet(ReadProtectedModelViewSet): @@ -60,22 +62,19 @@ class AchievementViewSet(ReadProtectedModelViewSet): """ queryset = Achievement.objects.order_by('id') serializer_class = AchievementSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] - filterset_fields = ['name', ] - search_fields = ['$name', ] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] + filterset_fields = ['family__name', 'family__description', 'challenge__name', 'challenge__description', 'obtained_at', 'valid', ] + search_fields = ['$family__name', '$family__description', '$challenge__name', '$challenge__description', ] class BatchAchievementsAPIView(APIView): permission_classes = [IsAuthenticated] def post(self, request, format=None): - print("POST de la view spéciale") - family_ids = request.data.get('families', []) - challenge_ids = request.data.get('challenges', []) - + family_ids = request.data.get('families') + challenge_ids = request.data.get('challenges') families = Family.objects.filter(id__in=family_ids) challenges = Challenge.objects.filter(id__in=challenge_ids) - for family in families: for challenge in challenges: a = Achievement(family=family, challenge=challenge) diff --git a/apps/family/migrations/0004_remove_challenge_obtained.py b/apps/family/migrations/0004_remove_challenge_obtained.py new file mode 100644 index 00000000..2831bac1 --- /dev/null +++ b/apps/family/migrations/0004_remove_challenge_obtained.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2.4 on 2025-07-22 14:33 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('family', '0003_achievement_valid_alter_familymembership_family'), + ] + + operations = [ + migrations.RemoveField( + model_name='challenge', + name='obtained', + ), + ] diff --git a/apps/family/models.py b/apps/family/models.py index 708c3929..71ccfa08 100644 --- a/apps/family/models.py +++ b/apps/family/models.py @@ -4,6 +4,7 @@ from django.db import models, transaction from django.utils import timezone from django.contrib.auth.models import User +from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ @@ -44,6 +45,9 @@ class Family(models.Model): def __str__(self): return self.name + def get_absolute_url(self): + return reverse_lazy('family:family_detail', args=(self.pk,)) + def update_score(self, *args, **kwargs): challenge_set = Challenge.objects.select_for_update().filter(achievement__family=self, achievement__valid=True) points_sum = challenge_set.aggregate(models.Sum("points")) @@ -119,10 +123,16 @@ class Challenge(models.Model): verbose_name=_('points'), ) - obtained = models.PositiveIntegerField( - verbose_name=_('obtained'), - default=0, - ) + @property + def obtained(self): + achievements = Achievement.objects.filter(challenge=self, valid=True) + return achievements.count() + + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse_lazy('family:challenge_detail', args=(self.pk,)) @transaction.atomic def save(self, *args, **kwargs): @@ -136,9 +146,6 @@ class Challenge(models.Model): verbose_name = _('challenge') verbose_name_plural = _('challenges') - def __str__(self): - return self.name - class Achievement(models.Model): challenge = models.ForeignKey( @@ -176,7 +183,6 @@ class Achievement(models.Model): """ self.family = Family.objects.select_for_update().get(pk=self.family_id) self.challenge = Challenge.objects.select_for_update().get(pk=self.challenge_id) - is_new = self.pk is None super().save(*args, **kwargs) @@ -184,13 +190,6 @@ class Achievement(models.Model): self.family.refresh_from_db() self.family.update_score() - # Count only when getting a new achievement - if is_new: - self.challenge.refresh_from_db() - self.challenge.obtained += 1 - self.challenge._force_save = True - self.challenge.save() - @transaction.atomic def delete(self, *args, **kwargs): """ @@ -205,8 +204,3 @@ class Achievement(models.Model): # Remove points from the family self.family.refresh_from_db() self.family.update_score() - - self.challenge.refresh_from_db() - self.challenge.obtained -= 1 - self.challenge._force_save = True - self.challenge.save() diff --git a/apps/family/static/family/img/default_picture.png b/apps/family/static/family/img/default_picture.png new file mode 100644 index 0000000000000000000000000000000000000000..41a31a1cf5eddf31e98a7e6cd06b4c45f2faee92 GIT binary patch literal 4054 zcmV;{4=M18P);r6Qr3ZkW>p0HXHpgkJ`+&_#u-7oWvMZmynAZVM0ha?OAwkr;#xV{6 zn%X%|TTKH7yJ&>5DK=aeqc<=F7>JA(cV_&%MZoL8i@;03+em<24)o7>jOD=3ft!KG z>?Rv|umGvea9~ehShI#A!7#Y_zrEBf+sG2&79==dQ?|uOoV|dHfro*`bpEIlnq~m! z0t3o+j8R<%16KmG(Z9gUQWJ0kwIA0SfbqaAV5^)yGb+HIz#U|JDUMaMfk7R#|H4+k zyD6vdjD*=6xCfbG(!PLw=-PICuotkZ)o<2P-;}hG5c>o7Q=PzQJ&LrZ53-oq_IEet z_NWm(U4Svbqv&6#ni|vWkAA>o9sFh$GP)WSU>o2(;8pZ5&JfgyOam|IGQQIzh(eu16$>skY`|+f=;MfNUhx?tD`=%?EaDuOkgN+<|_l zoAPNEGWBja=KJV(wy8cnNMc{9HfS5rhGD=YWUa%9j}geeuV=IkNtJuizYCg*X*H6) zB?&?r4i7{&$%SN-Q6JAE{kdM-kfMb2h;U6=|(fO$w-qo@hxZaoJ0o3;@~sk{yB z0(_!vND)F9K?Wi(=6$sdF-oN`@~HK?wjo96s(k*@NM=D>WJ;5>v<)bV<|EdA$X*`% zYAs#XAWsM%X&X{6S|dnLd|SVseoD4pBz z&k7{VOq8=(zDJM-;5sZgsze_m{!G_*fSiscC#xc(wGF5j-vM&2=kpn1aHO^Y_2TnU zXAdOPp;0|n16v~r{i4F>2y(K=a~WaL6F6Agh4`tO3AI$d2Y=$Xz!jq6`pU?;tw>Z+SYGPZj`o15X0a zA;C}vU6Be{2LfMiR_tj%R{~p-z&9c;I0XGK-OAJxz%lrHMC}Ls5GhZf-S#5dt!&4n z!pZ1&P8oU@IILvfYx%;AT~LBM~}_fb~e?Zyr9b1V9tlUHkj@49eC$@D^slWO}b zi~i-v2hlST{m#jwkAdTyc)|@`kxvM;{nnWlyK+G6Jc)kiWYt*oz-NV=ks+&=#vL!upD?y+lW>~^}&?y0GWtv9S?zJ_}jXE5O5RnF7_sp z-DL`L!#D!>Uv_G4(Qivgqgjyr6#eJm4M;^+*KCPw@$PH}ZvwI_*A=5WIloWc@35Uo zFa%i}(e9`pduV5#Z#AxRXv@wy>~O6=I&5d@Xk>SP7kEJ7ylt85@Yp?(l5!(*{LR_8 zd(>Ly=(zRpp|$~e&?!I6QV9)&VbT8xae=(Vfj&h6L$%koSeMR(NOTu;Dde z+mg=P5xtPz0@|}z8<2{|QQ7#j`BAuCW647ZvzJlvGxYCjMYIYDnt4cI{X65|%?E}S_Fgt7 zWVFeL@=SXhNXBpNPV=n=z^-LDF9SOg#-i*7V7lYBCP`1ic|P``g-F(RU)<}c9rfrm z$8AlPspxl}4=uuPZrag~+EI@d6V7{3E;OfkTbQbCfCrW#>27>Yac)Ogd!8l`VSU0- zI1v3ioJ%$0w`4+3q?nI(yIbpcr!7vL24p5kyAxe#CGh$3T-7PvkZdN}?QX5r$eNc? zfRLsl%Tg0kg~}HVgqe19n#)GJCWLnofJe0rD48|DSMit0r2)7T*#{OHPiPyE0=uDq zrz=Zq@mp$a3t*Cic9f;V-8H>h%tOC3%LtBBO7=@F1|gYZwA5BpL61gPZLA^!Pc7W z#tqfxSoAwKk3PVCRz!ebDz&eOe&ECt6=!SYyJhXp&8kUm+^`8hQEXok?c>532Fyag zbF=DdM?PrbaAa*oyPbLU4t_>f=Y+D=q%|QWTJDT_4v#$$$v)rU@Eop~j08&bSb!A6 zWlGOWfx!-s?~?As8Q4*32_8@2T5!Y1FkNB zvBbZJ=@?)Ju)DS)MF=rKKo$&Rpr35OIqisM&Tx5mxxg*#YjYK-D6=GBmK`P=7SGFT7Gm&g1?;&YPTV}Ra zAZr+_$eu~QCz3DN$KEcV%msGQHX?R<0p}9OHf~csJx`=)Ca-SInZPy3;+T;hE=xN& zC4>MO3j9Ia2uG|$5;&|w>Xr9Ez99$M3SL&ph0w78DZp}M7!L7$s1{8~m8?gRYPO4- z)r7Pu3!4MmAT>fp03(5~BbgCXBBUasQ4;#M)$M7F6Hmw}^F{#eD^u5&>LHJWGR-?v z+kiT7Rl;ozlH)2Rq8&jjK*A)Jh0?5OS2V7hPy!^i3!*5mY8y~89hIvKo=^hBMvzVU zK-++lu@R&o3y>l!hR}$+I_?_SJ4gx4);1t8Qo3PSfRw-++6DwhlePgR@}af?^%7Em z%+WR=FkDR|`(_;}ij z_k{&WNh~KWXU3jX!51<0>8A)OKp?JB#4?v>W0AH2MX&&=9~(ikVF6MiF$IW?Ala}0 zDUp~0#72;8Sb&sBOxsQRYa0*=_L5#AbBJepGo|EiE_qwqfFgtzAS;O{X`HJ9FtKl^hE-pCCf=) zU;xtYeMx6@T8bpli}J zS_m~jq92+KZqzoQ3_=Z%7qks93Nap-q-{jXY>R$Ia#P2e4@_la>53LL0+$fx0(4!3 zT$AG^WZQ)i7UO^mk(cl-kTMX%fH#3DWUq~46yyuQV@L{4?K`GPbVbb`OOTL9kQTZD zJ0VqXcR@Dw^>5aGNU?guJ%0s`)i%P&$C>EgRjZ=Ziuzoq*f-t2#nYRWTCbJ zM%u(4AU1*+PKiB079!8bhT(+R17r;e?DsLkCH4RTcwgH9BVAGmkmx_kF~TK<0GY3C zfRQd1Acj!`3lPJofdzziLHqk zC6ZczJP+KbZG@2ysRal~X1`CgjWE(6#Q>QD+@NiQkp?LS2*Bk?As)jhM5+PuF>t=N z5k~x^8Xy1<0*`1LVZ=?!0RobRe7&|2M!cjRAg=)b&^E$|m(&9U;5KaojCiR+fK0*G zMT|PBLV&CQUeq?ih?6P=2v~ACqfV+4AU(AWFyf>t0WwV603%MS5FlNEq1r|maZ-f< z*&XPmZA1d});2(c25kd8a2jy5wh;-i2hape*EYl`m&xc?h)qcs<#WFgG6R9t=wDQ+ z>AXChV1&z6=wD2!>Vh2oU9XJ*v6WxK|q2FmH|C#}u1N6mcmjS>u^gGI=t5v|g$f(i{ z1Aqg7`RI3!DJ(5OX1_*KVa~x`#!a2;S>W414=O?r;J4^I-V~Sq2b@IW?1bDAw4H5= zSJ#v1cPS?TOVD?)DSjP6352q#(RNxw5kg_dCTI^iL zL9|AYuE5E_e}Fr%;4sRkl#KqqN06?_EW?%9rZmGXr3A?T0kEA=M);+bHvj+t07*qo IM6N<$f;wYbJOBUy literal 0 HcmV?d00001 diff --git a/apps/family/static/family/js/achievements.js b/apps/family/static/family/js/achievements.js index db29923d..ebec8689 100644 --- a/apps/family/static/family/js/achievements.js +++ b/apps/family/static/family/js/achievements.js @@ -113,6 +113,7 @@ function reset () { * Apply all transactions: all notes in `notes` buy each item in `buttons` */ function consumeAll () { + console.log("test"); if (LOCK) { return } LOCK = true @@ -130,11 +131,13 @@ function consumeAll () { LOCK = false return } - + console.log("couocu") // Récupérer les IDs des familles et des challenges const family_ids = notes_display.map(fam => fam.id) const challenge_ids = buttons.map(chal => chal.id) + console.log(family_ids) + console.log(challenge_ids) $.ajax({ url: '/family/api/family/achievements/batch/', type: 'POST', @@ -157,34 +160,6 @@ function consumeAll () { }) } -/** - * Create a new achievement through the API. - * @param family The selected family - * @param challenge The selected challenge - */ -function grantAchievement (family, challenge) { - console.log("grant lancée",family,challenge) - $.post('/api/family/achievement/', - { - csrfmiddlewaretoken: CSRF_TOKEN, - family: family.id, - challenge: challenge.id, - }) - .done(function () { - reset() - addMsg("Défi validé pour la famille !", 'success', 5000) - }) - .fail(function (e) { - reset() - if (e.responseJSON) { - errMsg(e.responseJSON) - } else if (e.responseText) { - errMsg(e.responseText) - } else { - errMsg("Erreur inconnue lors de la création de l'achievement.") - } - }) -} var searchbar = document.getElementById("search-input") var search_results = document.getElementById("search-results") @@ -264,7 +239,6 @@ function li (id, text, extra_css) { */ function autoCompleteFamily(field_id, family_list_id, families, families_display, family_prefix = 'family', user_family_field = null, profile_pic_field = null, family_click = null) { const field = $('#' + field_id) - console.log("autoCompleteFamily commence") // Configuration du tooltip field.tooltip({ html: true, diff --git a/apps/family/templates/family/manage.html b/apps/family/templates/family/manage.html index 0ecac60a..22a4ed90 100644 --- a/apps/family/templates/family/manage.html +++ b/apps/family/templates/family/manage.html @@ -84,12 +84,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
                {% if can_add_family %} - + {% trans "Add a family" %} {% endif %} {% if can_add_challenge %} - + {% trans "Add a challenge" %} {% endif %} @@ -147,7 +147,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
                -{# transaction history #} +{# achievement history #}
                -
                +
                {% render_table table %}
                diff --git a/apps/family/tests/__init__.py b/apps/family/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/family/tests/test_family.py b/apps/family/tests/test_family.py new file mode 100644 index 00000000..f2b31784 --- /dev/null +++ b/apps/family/tests/test_family.py @@ -0,0 +1,318 @@ +# Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +import os + +from api.tests import TestAPI +from django.contrib.auth.models import User +from django.core.files.uploadedfile import SimpleUploadedFile +from django.test import TestCase +from rest_framework.test import APITestCase +from django.urls import reverse +from django.utils import timezone + +from ..api.views import FamilyViewSet, FamilyMembershipViewSet, ChallengeViewSet, AchievementViewSet +from ..models import Family, FamilyMembership, Challenge, Achievement + + +class TestFamily(TestCase): + """ + Test family + """ + + def setUp(self): + self.user = User.objects.create_superuser( + username='admintoto', + password='toto1234', + email='toto@example.com', + ) + self.client.force_login(self.user) + + sess = self.client.session + sess['permission_mask'] = 42 + sess.save() + + self.family = Family.objects.create( + name='Test family', + description='', + ) + + self.challenge = Challenge.objects.create( + name='Test challenge', + description='', + points=100, + ) + + self.achievement = Achievement.objects.create( + family=self.family, + challenge=self.challenge, + valid=False, + ) + + def test_family_list(self): + """ + Test display family list + """ + response = self.client.get(reverse("family:family_list")) + self.assertEqual(response.status_code, 200) + + def test_family_create(self): + """ + Test create a family + """ + response = self.client.get(reverse("family:family_create")) + self.assertEqual(response.status_code, 200) + + response = self.client.post(reverse("family:family_create"), data={ + "name": "Family toto", + "description": "A test family", + }) + self.assertTrue(Family.objects.filter(name="Family toto").exists()) + self.assertRedirects(response, reverse("family:manage"), 302, 200) + + def test_family_detail(self): + """ + Test display the detail of a family + """ + response = self.client.get(reverse("family:family_detail", args=(self.family.pk,))) + self.assertEqual(response.status_code, 200) + + def test_family_update(self): + """ + Test update a family + """ + response = self.client.get(reverse("family:family_update", args=(self.family.pk,))) + self.assertEqual(response.status_code, 200) + + response = self.client.post(reverse("family:family_update", args=(self.family.pk,)), data=dict( + name="Toto family updated", + description="A larger description for the test family" + )) + self.assertRedirects(response, self.family.get_absolute_url(), 302, 200) + self.assertTrue(Family.objects.filter(name="Toto family updated").exists()) + + def test_family_update_picture(self): + """ + Test update the picture of a family + """ + response = self.client.get(reverse("family:update_pic", args=(self.family.pk,))) + self.assertEqual(response.status_code, 200) + + old_pic = self.family.display_image + + with open("apps/family/static/family/img/default_picture.png", "rb") as f: + image = SimpleUploadedFile("image.png", f.read(), "image/png") + response = self.client.post(reverse("family:update_pic", args=(self.family.pk,)), dict( + image=image, + x=0, + y=0, + width=200, + height=200, + )) + self.assertRedirects(response, self.family.get_absolute_url(), 302, 200) + + self.family.refresh_from_db() + self.assertTrue(os.path.exists(self.family.display_image.path)) + os.remove(self.family.display_image.path) + + self.family.display_image = old_pic + self.family.save() + + def test_family_add_member(self): + """ + Test add memberships to a family + """ + response = self.client.get(reverse("family:family_add_member", args=(self.family.pk,))) + self.assertEqual(response.status_code, 200) + + user = User.objects.create(username="totototo") + user.profile.registration_valid = True + user.profile.email_confirmed = True + user.profile.save() + user.save() + + response = self.client.post(reverse("family:family_add_member", args=(self.family.pk,)), data=dict( + user=user.pk, + )) + self.assertRedirects(response, self.family.get_absolute_url(), 302, 200) + + self.assertTrue(FamilyMembership.objects.filter(user=user, family=self.family, year=timezone.now().year).exists()) + + def test_challenge_list(self): + """ + Test display challenge list + """ + response = self.client.get(reverse('family:challenge_list')) + self.assertEqual(response.status_code, 200) + + def test_challenge_create(self): + """ + Test create a challenge + """ + response = self.client.get(reverse("family:challenge_create")) + self.assertEqual(response.status_code, 200) + + response = self.client.post(reverse("family:challenge_create"), data={ + "name": "Challenge for toto", + "description": "A test challenge", + "points": 50, + }) + self.assertTrue(Challenge.objects.filter(name="Challenge for toto").exists()) + self.assertRedirects(response, reverse("family:manage"), 302, 200) + + def test_challenge_detail(self): + """ + Test display the detail of a challenge + """ + response = self.client.get(reverse("family:challenge_detail", args=(self.challenge.pk,))) + self.assertEqual(response.status_code, 200) + + def test_challenge_update(self): + """ + Test update a challenge + """ + response = self.client.get(reverse("family:challenge_update", args=(self.challenge.pk,))) + self.assertEqual(response.status_code, 200) + + response = self.client.post(reverse("family:challenge_update", args=(self.challenge.pk,)), data=dict( + name="Challenge updated", + description="Another description", + points=10, + )) + self.assertRedirects(response, self.challenge.get_absolute_url(), 302, 200) + self.assertTrue(Challenge.objects.filter(name="Challenge updated").exists()) + + def test_render_manage_page(self): + """ + Test render manage page + """ + response = self.client.get(reverse("family:manage")) + self.assertEqual(response.status_code, 200) + + def test_validate_achievement(self): + """ + Test validate an achievement + """ + old_family_score = self.family.score + + response = self.client.get(reverse("family:achievement_validate", args=(self.achievement.pk,))) + self.assertEqual(response.status_code, 200) + + response = self.client.post(reverse("family:achievement_validate", args=(self.achievement.pk,))) + self.assertRedirects(response, reverse("family:achievement_list"), 302, 200) + + self.achievement.refresh_from_db() + self.assertIs(self.achievement.valid, True) + + self.family.refresh_from_db() + self.assertEqual(self.family.score, old_family_score + self.achievement.challenge.points) + + def test_delete_achievement(self): + """ + Test delete an achievement + """ + response = self.client.get(reverse("family:achievement_delete", args=(self.achievement.pk,))) + self.assertEqual(response.status_code, 200) + + response = self.client.delete(reverse("family:achievement_delete", args=(self.achievement.pk,))) + self.assertRedirects(response, reverse("family:achievement_list"), 302, 200) + self.assertFalse(Achievement.objects.filter(pk=self.achievement.pk).exists()) + + +class TestBatchAchievements(APITestCase): + def setUp(self): + self.user = User.objects.create_superuser( + username='admintoto', + password='toto1234', + email='toto@example.com', + ) + self.client.force_login(self.user) + + sess = self.client.session + sess['permission_mask'] = 42 + sess.save() + + self.families = [ + Family.objects.create(name=f'Famille {i}', description='') for i in range(2) + ] + self.challenges = [ + Challenge.objects.create(name=f'Challenge {i}', description='', points=50) for i in range(3) + ] + + self.url = reverse("family:api:batch_achievements") + + def test_batch_achievement_creation(self): + family_ids = [f.id for f in self.families] + challenge_ids = [c.id for c in self.challenges] + response = self.client.post( + self.url, + data={ + 'families': family_ids, + 'challenges': challenge_ids + }, + format='json' + ) + + self.assertEqual(response.status_code, 201) + self.assertEqual(response.data['status'], 'ok') + + expected_count = len(family_ids) * len(challenge_ids) + self.assertEqual(Achievement.objects.count(), expected_count) + + # Check that correct couples family/challenge exist + for f in self.families: + for c in self.challenges: + self.assertTrue( + Achievement.objects.filter(family=f, challenge=c).exists() + ) + + +class TestFamilyAPI(TestAPI): + def setUp(self): + super().setUp() + + self.family = Family.objects.create( + name='Test family', + description='', + ) + + self.familymembership = FamilyMembership.objects.create( + user=self.user, + family=self.family, + ) + + self.challenge = Challenge.objects.create( + name='Test challenge', + description='', + points=100, + ) + + self.achievement = Achievement.objects.create( + family=self.family, + challenge=self.challenge, + valid=False, + ) + + def test_family_api(self): + """ + Load Family API page and test all filters and permissions + """ + self.check_viewset(FamilyViewSet, '/api/family/family/') + + def test_familymembership_api(self): + """ + Load FamilyMembership API page and test all filters and permissions + """ + self.check_viewset(FamilyMembershipViewSet, '/api/family/familymembership/') + + def test_challenge_api(self): + """ + Load Challenge API page and test all filters and permissions + """ + self.check_viewset(ChallengeViewSet, '/api/family/challenge/') + + def test_achievement_api(self): + """ + Load Achievement API page and test all filters and permissions + """ + self.check_viewset(AchievementViewSet, '/api/family/achievement/') diff --git a/apps/family/urls.py b/apps/family/urls.py index 7d9e9c5b..edb0d18a 100644 --- a/apps/family/urls.py +++ b/apps/family/urls.py @@ -8,18 +8,18 @@ from . import views app_name = 'family' urlpatterns = [ path('list/', views.FamilyListView.as_view(), name="family_list"), - path('add-family/', views.FamilyCreateView.as_view(), name="add_family"), + path('create/', views.FamilyCreateView.as_view(), name="family_create"), path('/detail/', views.FamilyDetailView.as_view(), name="family_detail"), path('/update/', views.FamilyUpdateView.as_view(), name="family_update"), path('/update_pic/', views.FamilyPictureUpdateView.as_view(), name="update_pic"), path('/add_member/', views.FamilyAddMemberView.as_view(), name="family_add_member"), path('challenge/list/', views.ChallengeListView.as_view(), name="challenge_list"), - path('add-challenge/', views.ChallengeCreateView.as_view(), name="add_challenge"), + path('challenge/create/', views.ChallengeCreateView.as_view(), name="challenge_create"), path('challenge//detail/', views.ChallengeDetailView.as_view(), name="challenge_detail"), path('challenge//update/', views.ChallengeUpdateView.as_view(), name="challenge_update"), path('manage/', views.FamilyManageView.as_view(), name="manage"), - path('achievement/list/', views.AchievementsView.as_view(), name="achievement_list"), + path('achievement/list/', views.AchievementListView.as_view(), name="achievement_list"), path('achievement//validate/', views.AchievementValidateView.as_view(), name="achievement_validate"), path('achievement//delete/', views.AchievementDeleteView.as_view(), name="achievement_delete"), - path('api/family/', include('family.api.urls')), + path('api/family/', include(('family.api.urls', 'family_api'), namespace='api')), ] diff --git a/apps/family/views.py b/apps/family/views.py index a6e886a8..aee6f276 100644 --- a/apps/family/views.py +++ b/apps/family/views.py @@ -8,14 +8,14 @@ from django.shortcuts import redirect from django.contrib.auth.mixins import LoginRequiredMixin from django.db import transaction from django.views.generic import DetailView, UpdateView, ListView -from django.views.generic.edit import DeleteView +from django.views.generic.edit import DeleteView, FormMixin from django.views.generic.base import TemplateView from django.utils.translation import gettext_lazy as _ from django_tables2 import SingleTableView, MultiTableMixin from permission.backends import PermissionBackend from permission.views import ProtectQuerysetMixin, ProtectedCreateView from django.urls import reverse_lazy -from member.views import PictureUpdateView +from member.forms import ImageForm from .models import Family, Challenge, FamilyMembership, User, Achievement from .tables import FamilyTable, ChallengeTable, FamilyMembershipTable, AchievementTable, FamilyAchievementTable @@ -112,17 +112,28 @@ class FamilyUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): return reverse_lazy('family:family_detail', kwargs={'pk': self.object.pk}) -class FamilyPictureUpdateView(PictureUpdateView): +class FamilyPictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, DetailView): """ Update profile picture of the family """ model = Family extra_context = {"title": _("Update family picture")} template_name = 'family/picture_update.html' + form_class = ImageForm + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['form'] = self.form_class(self.request.POST, self.request.FILES) + return context def get_success_url(self): """Redirect to family page after upload""" - return reverse_lazy('family:family_detail', kwargs={'pk': self.object.id}) + return reverse_lazy('family:family_detail', kwargs={'pk': self.object.pk}) + + def post(self, request, *args, **kwargs): + form = self.get_form() + self.object = self.get_object() + return self.form_valid(form) if form.is_valid() else self.form_invalid(form) @transaction.atomic def form_valid(self, form): @@ -141,6 +152,11 @@ class FamilyPictureUpdateView(PictureUpdateView): else: image.name = "{}_pic.png".format(self.object.pk) + # Save + self.object.display_image = image + self.object.save() + return super().form_valid(form) + class FamilyAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): """ @@ -282,8 +298,8 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView PermissionBackend.filter_queryset(self.request, Challenge, "view") ).order_by('name') - context["can_add_family"] = PermissionBackend.check_perm(self.request, "family.add_family") - context["can_add_challenge"] = PermissionBackend.check_perm(self.request, "family.add_challenge") + context["can_add_family"] = PermissionBackend.check_perm(self.request, "family.family_create") + context["can_add_challenge"] = PermissionBackend.check_perm(self.request, "family.challenge_create") return context @@ -294,7 +310,7 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView return table -class AchievementsView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView): +class AchievementListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView): """ List all achievements """ @@ -333,12 +349,11 @@ class AchievementValidateView(ProtectQuerysetMixin, LoginRequiredMixin, Template template_name = 'family/achievement_confirm_validate.html' def post(self, request, pk): - # On récupère l'objet à valider achievement = Achievement.objects.get(pk=pk) - # On modifie le champ valid + achievement.valid = True achievement.save() - # On redirige vers la page de détail ou la liste + return redirect(reverse_lazy('family:achievement_list')) From 6e348b995b271b1c6d7c37eb82c6e84befd4f0fe Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Wed, 23 Jul 2025 00:51:03 +0200 Subject: [PATCH 094/153] Better Membership update --- apps/wei/tables.py | 4 +- .../wei/templates/wei/weimembership_form.html | 1 - .../templates/wei/weimembership_update.html | 46 +++++++++++++++++++ apps/wei/urls.py | 4 +- apps/wei/views.py | 43 +++++++++++++++++ 5 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 apps/wei/templates/wei/weimembership_update.html diff --git a/apps/wei/tables.py b/apps/wei/tables.py index 5e3536c2..f1d31813 100644 --- a/apps/wei/tables.py +++ b/apps/wei/tables.py @@ -136,8 +136,8 @@ class WEIRegistrationTable(tables.Table): class WEIMembershipTable(tables.Table): user = tables.LinkColumn( - 'wei:wei_update_registration', - args=[A('registration__pk')], + 'wei:wei_update_membership', + args=[A('pk')], ) year = tables.Column( diff --git a/apps/wei/templates/wei/weimembership_form.html b/apps/wei/templates/wei/weimembership_form.html index 6d5c0b37..512f6f8d 100644 --- a/apps/wei/templates/wei/weimembership_form.html +++ b/apps/wei/templates/wei/weimembership_form.html @@ -213,7 +213,6 @@ SPDX-License-Identifier: GPL-3.0-or-later $("input[name='bus']:checked").each(function (ignored) { buses.push($(this).parent().text().trim()); }); - console.log(buses); $("input[name='team']").each(function () { let label = $(this).parent(); $(this).parent().addClass('d-none'); diff --git a/apps/wei/templates/wei/weimembership_update.html b/apps/wei/templates/wei/weimembership_update.html new file mode 100644 index 00000000..527ac08d --- /dev/null +++ b/apps/wei/templates/wei/weimembership_update.html @@ -0,0 +1,46 @@ +{% extends "base.html" %} +{% comment %} +Copyright (C) 2018-2025 by BDE ENS Paris-Saclay +SPDX-License-Identifier: GPL-3.0-or-later +{% endcomment %} +{% load i18n crispy_forms_tags %} + +{% block content %} +
                +

                + {{ title }} +

                +
                +
                + {% csrf_token %} + {{ form | crispy }} + +
                +
                +
                +{% endblock %} + +{% block extrajavascript %} + +{% endblock %} \ No newline at end of file diff --git a/apps/wei/urls.py b/apps/wei/urls.py index 5f9283c0..437a7d2a 100644 --- a/apps/wei/urls.py +++ b/apps/wei/urls.py @@ -7,7 +7,7 @@ from .views import CurrentWEIDetailView, WEI1AListView, WEIListView, WEICreateVi WEIRegistrationsView, WEIMembershipsView, MemberListRenderView, BusInformationUpdateView, \ BusCreateView, BusManageView, BusUpdateView, BusTeamCreateView, BusTeamManageView, BusTeamUpdateView, \ WEIAttributeBus1AView, WEIAttributeBus1ANextView, WEIRegister1AView, WEIRegister2AView, WEIUpdateRegistrationView, \ - WEIDeleteRegistrationView, WEIValidateRegistrationView, WEISurveyView, WEISurveyEndView, WEIClosedView + WEIDeleteRegistrationView, WEIValidateRegistrationView, WEISurveyView, WEISurveyEndView, WEIClosedView, WEIUpdateMembershipView app_name = 'wei' urlpatterns = [ @@ -43,4 +43,6 @@ urlpatterns = [ path('bus-1A//', WEIAttributeBus1AView.as_view(), name="wei_bus_1A"), path('bus-1A/next//', WEIAttributeBus1ANextView.as_view(), name="wei_bus_1A_next"), path('update-bus-info//', BusInformationUpdateView.as_view(), name="update_bus_info"), + + path('edit_membership//', WEIUpdateMembershipView.as_view(), name="wei_update_membership"), ] diff --git a/apps/wei/views.py b/apps/wei/views.py index d236af8b..bd1f1f5f 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -1181,6 +1181,49 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): return reverse_lazy("wei:wei_registrations", kwargs={"pk": self.object.club.pk}) +class WEIUpdateMembershipView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): + """ + Update a membership for the WEI + """ + model = WEIMembership + context_object_name = "membership" + template_name = "wei/weimembership_update.html" + extra_context = {"title": _("Update WEI Membership")} + + def dispatch(self, request, *args, **kwargs): + wei = self.get_object().registration.wei + today = date.today() + # We can't update a registration once the WEI is started and before the membership start date + if today >= wei.date_start or today < wei.membership_start: + return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,))) + # Store the validate parameter in the view's state + return super().dispatch(request, *args, **kwargs) + + def get_form(self): + form = WEIMembershipForm( + self.request.POST or None, + self.request.FILES or None, + instance=self.object, + wei=self.object.registration.wei, + ) + + form.fields["roles"].initial = self.object.roles.all() + form.fields["bus"].initial = self.object.bus + form.fields["team"].initial = self.object.team + + del form.fields["credit_type"] + del form.fields["credit_amount"] + del form.fields["first_name"] + del form.fields["last_name"] + del form.fields["bank"] + + return form + + def get_success_url(self): + print("get_success_url") + return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.registration.wei.pk}) + + class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView): """ Display the survey for the WEI for first year members. From 296d021d54cdb325434cbc685be2c1ce00920c95 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Wed, 23 Jul 2025 01:24:59 +0200 Subject: [PATCH 095/153] Permissions --- apps/permission/fixtures/initial.json | 30 ++++++++++++++++++++------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 1e738361..8c69e367 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -4347,7 +4347,23 @@ "mask": 3, "field": "", "permanent": false, - "description": "Ajouter un membre au BDE ou à la Kfet" + "description": "Faire adhérer BDE ou Kfet" + } + }, + { + "model": "permission.permission", + "pk": 293, + "fields": { + "model": [ + "wei", + "weimembership" + ], + "query": "[\"AND\", {\"bus\": [\"membership\", \"weimembership\", \"bus\"]}, {\"club\": [\"club\"], \"club__weiclub__membership_end__gte\": [\"today\"]}]", + "type": "change", + "mask": 2, + "field": "team", + "permanent": false, + "description": "Modifier l'équipe d'une adhésion WEI à son bus" } }, { @@ -4764,7 +4780,6 @@ "name": "Chef\u22c5fe de bus", "permissions": [ 22, - 84, 115, 117, 118, @@ -4778,7 +4793,8 @@ 287, 289, 290, - 291 + 291, + 293 ] } }, @@ -4790,7 +4806,6 @@ "name": "Chef\u22c5fe d'\u00e9quipe", "permissions": [ 22, - 84, 116, 123, 124, @@ -4805,8 +4820,7 @@ "for_club": null, "name": "\u00c9lectron libre", "permissions": [ - 22, - 84 + 22 ] } }, @@ -4957,7 +4971,6 @@ "name": "Référent⋅e Bus", "permissions": [ 22, - 84, 115, 117, 118, @@ -4971,7 +4984,8 @@ 287, 289, 290, - 291 + 291, + 293 ] } }, From bfa5734d552787d3025c026f9f4a74409ae00f04 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Wed, 23 Jul 2025 16:48:59 +0200 Subject: [PATCH 096/153] Changed score calculation in survey --- apps/wei/forms/surveys/wei2025.py | 266 +++++++++++++++++---- apps/wei/tables.py | 2 +- apps/wei/templates/wei/weiclub_detail.html | 5 + apps/wei/tests/test_wei_algorithm_2025.py | 18 +- apps/wei/views.py | 4 + 5 files changed, 240 insertions(+), 55 deletions(-) diff --git a/apps/wei/forms/surveys/wei2025.py b/apps/wei/forms/surveys/wei2025.py index d92cc23f..ee748c6c 100644 --- a/apps/wei/forms/surveys/wei2025.py +++ b/apps/wei/forms/surveys/wei2025.py @@ -14,16 +14,139 @@ from django.utils.translation import gettext_lazy as _ from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation from ...models import WEIMembership, Bus -WORDS = [ - '13 organisé', '3ième mi temps', 'Années 2000', 'Apéro', 'BBQ', 'BP', 'Beauf', 'Binge drinking', 'Bon enfant', - 'Cartouche', 'Catacombes', 'Chansons paillardes', 'Chansons populaires', 'Chanteur', 'Chartreuse', 'Chill', - 'Core', 'DJ', 'Dancefloor', 'Danse', 'David Guetta', 'Disco', 'Eau de vie', 'Électro', 'Escalade', 'Familial', - 'Fanfare', 'Fracassage', 'Féria', 'Hard rock', 'Hoeggarden', 'House', 'Huit-six', 'IPA', 'Inclusif', 'Inferno', - 'Introverti', 'Jager bomb', 'Jazz', 'Jeux d\'alcool', 'Jeux de rôles', 'Jeux vidéo', 'Jul', 'Jus de fruit', - 'Karaoké', 'LGBTQI+', 'Lady Gaga', 'Loup garou', 'Morning beer', 'Métal', 'Nuit blanche', 'Ovalie', 'Psychedelic', - 'Pétanque', 'Rave', 'Reggae', 'Rhum', 'Ricard', 'Rock', 'Rosé', 'Rétro', 'Séducteur', 'Techno', 'Thérapie taxi', - 'Théâtre', 'Trap', 'Turn up', 'Underground', 'Volley', 'Wati B', 'Zinédine Zidane', -] +WORDS = { + 'list': [ + '13 organisé', '3ième mi temps', 'Années 2000', 'Apéro', 'BBQ', 'BP', 'Beauf', 'Binge drinking', 'Bon enfant', + 'Cartouche', 'Catacombes', 'Chansons paillardes', 'Chansons populaires', 'Chanteur', 'Chartreuse', 'Chill', + 'Core', 'DJ', 'Dancefloor', 'Danse', 'David Guetta', 'Disco', 'Eau de vie', 'Électro', 'Escalade', 'Familial', + 'Fanfare', 'Fracassage', 'Féria', 'Hard rock', 'Hoeggarden', 'House', 'Huit-six', 'IPA', 'Inclusif', 'Inferno', + 'Introverti', 'Jager bomb', 'Jazz', 'Jeux d\'alcool', 'Jeux de rôles', 'Jeux vidéo', 'Jul', 'Jus de fruit', + 'Karaoké', 'LGBTQI+', 'Lady Gaga', 'Loup garou', 'Morning beer', 'Métal', 'Nuit blanche', 'Ovalie', 'Psychedelic', + 'Pétanque', 'Rave', 'Reggae', 'Rhum', 'Ricard', 'Rock', 'Rosé', 'Rétro', 'Séducteur', 'Techno', 'Thérapie taxi', + 'Théâtre', 'Trap', 'Turn up', 'Underground', 'Volley', 'Wati B', 'Zinédine Zidane', + ], + 'questions': { + 'Question 1': [ + 'Description 1', + { + 3: 'Réponse 1 Madagas[car]', + 43: 'Réponse 1 Y2[KAR]', + 2: 'Réponse 1 Tcherno[bus]', + 45: 'Réponse 1 [Kar]tier', + 1: 'Réponse 1 [Car]cassonne', + 47: 'Réponse 1 O[car]ina', + 48: 'Réponse 1 Show[bus]', + 49: 'Réponse 1 [Car]ioca' + } + ], + 'Question 2': [ + 'Description 2', + { + 3: 'Réponse 2 Madagas[car]', + 43: 'Réponse 2 Y2[KAR]', + 2: 'Réponse 2 Tcherno[bus]', + 45: 'Réponse 2 [Kar]tier', + 1: 'Réponse 2 [Car]cassonne', + 47: 'Réponse 2 O[car]ina', + 48: 'Réponse 2 Show[bus]', + 49: 'Réponse 2 [Car]ioca' + } + ], + 'Question 3': [ + 'Description 3', + { + 3: 'Réponse 3 Madagas[car]', + 43: 'Réponse 3 Y2[KAR]', + 2: 'Réponse 3 Tcherno[bus]', + 45: 'Réponse 3 [Kar]tier', + 1: 'Réponse 3 [Car]cassonne', + 47: 'Réponse 3 O[car]ina', + 48: 'Réponse 3 Show[bus]', + 49: 'Réponse 3 [Car]ioca' + } + ], + 'Question 4': [ + 'Description 4', + { + 3: 'Réponse 4 Madagas[car]', + 43: 'Réponse 4 Y2[KAR]', + 2: 'Réponse 4 Tcherno[bus]', + 45: 'Réponse 4 [Kar]tier', + 1: 'Réponse 4 [Car]cassonne', + 47: 'Réponse 4 O[car]ina', + 48: 'Réponse 4 Show[bus]', + 49: 'Réponse 4 [Car]ioca' + } + ], + 'Question 5': [ + 'Description 5', + { + 3: 'Réponse 5 Madagas[car]', + 43: 'Réponse 5 Y2[KAR]', + 2: 'Réponse 5 Tcherno[bus]', + 45: 'Réponse 5 [Kar]tier', + 1: 'Réponse 5 [Car]cassonne', + 47: 'Réponse 5 O[car]ina', + 48: 'Réponse 5 Show[bus]', + 49: 'Réponse 5 [Car]ioca' + } + ], + 'Question 6': [ + 'Description 6', + { + 3: 'Réponse 6 Madagas[car]', + 43: 'Réponse 6 Y2[KAR]', + 2: 'Réponse 6 Tcherno[bus]', + 45: 'Réponse 6 [Kar]tier', + 1: 'Réponse 6 [Car]cassonne', + 47: 'Réponse 6 O[car]ina', + 48: 'Réponse 6 Show[bus]', + 49: 'Réponse 6 [Car]ioca' + } + ], + 'Question 7': [ + 'Description 7', + { + 3: 'Réponse 7 Madagas[car]', + 43: 'Réponse 7 Y2[KAR]', + 2: 'Réponse 7 Tcherno[bus]', + 45: 'Réponse 7 [Kar]tier', + 1: 'Réponse 7 [Car]cassonne', + 47: 'Réponse 7 O[car]ina', + 48: 'Réponse 7 Show[bus]', + 49: 'Réponse 7 [Car]ioca' + } + ], + 'Question 8': [ + 'Description 8', + { + 3: 'Réponse 8 Madagas[car]', + 43: 'Réponse 8 Y2[KAR]', + 2: 'Réponse 8 Tcherno[bus]', + 45: 'Réponse 8 [Kar]tier', + 1: 'Réponse 8 [Car]cassonne', + 47: 'Réponse 8 O[car]ina', + 48: 'Réponse 8 Show[bus]', + 49: 'Réponse 8 [Car]ioca' + } + ], + 'Question 9': [ + 'Description 9', + { + 3: 'Réponse 9 Madagas[car]', + 43: 'Réponse 9 Y2[KAR]', + 2: 'Réponse 9 Tcherno[bus]', + 45: 'Réponse 9 [Kar]tier', + 1: 'Réponse 9 [Car]cassonne', + 47: 'Réponse 9 O[car]ina', + 48: 'Réponse 9 Show[bus]', + 49: 'Réponse 9 [Car]ioca' + } + ] + } +} + +NB_WORDS = 5 class WEISurveyForm2025(forms.Form): @@ -32,11 +155,6 @@ class WEISurveyForm2025(forms.Form): Members choose 20 words, from which we calculate the best associated bus. """ - word = forms.ChoiceField( - label=_("Choose a word:"), - widget=forms.RadioSelect(), - ) - def set_registration(self, registration): """ Filter the bus selector with the buses of the current WEI. @@ -48,34 +166,56 @@ class WEISurveyForm2025(forms.Form): registration._force_save = True registration.save() - if self.data: - self.fields["word"].choices = [(w, w) for w in WORDS] + rng = Random((information.step + 1) * information.seed) + + if information.step == 0: + self.fields["words"] = forms.MultipleChoiceField( + label=_(f"Choose {NB_WORDS} words:"), + choices=[(w, w) for w in WORDS['list']], + widget=forms.CheckboxSelectMultiple(), + required=True, + ) if self.is_valid(): return - rng = Random((information.step + 1) * information.seed) + buses = WEISurveyAlgorithm2025.get_buses() + informations = {bus: WEIBusInformation2025(bus) for bus in buses} + scores = sum((list(informations[bus].scores.values()) for bus in buses), []) + if scores: + average_score = sum(scores) / len(scores) + else: + average_score = 0 - buses = WEISurveyAlgorithm2025.get_buses() - informations = {bus: WEIBusInformation2025(bus) for bus in buses} - scores = sum((list(informations[bus].scores.values()) for bus in buses), []) - if scores: - average_score = sum(scores) / len(scores) + preferred_words = { + bus: [word for word in WORDS['list'] if informations[bus].scores[word] >= average_score] + for bus in buses + } + + all_preferred_words = set() + for bus_words in preferred_words.values(): + all_preferred_words.update(bus_words) + all_preferred_words = list(all_preferred_words) + rng.shuffle(all_preferred_words) + self.fields["words"].choices = [(w, w) for w in all_preferred_words] else: - average_score = 0 + questions = list(WORDS['questions'].items()) + idx = information.step - 1 + if idx < len(questions): + q, (desc, answers) = questions[idx] + choices = [(k, v) for k, v in answers.items()] + rng.shuffle(choices) + self.fields[q] = forms.ChoiceField( + label=desc, + choices=choices, + widget=forms.RadioSelect, + required=True, + ) - preferred_words = {bus: [word for word in WORDS - if informations[bus].scores[word] >= average_score] - for bus in buses} - - # Correction : proposer plusieurs mots différents à chaque étape - n_choices = 4 # Nombre de mots à proposer à chaque étape - all_preferred_words = set() - for bus_words in preferred_words.values(): - all_preferred_words.update(bus_words) - all_preferred_words = list(all_preferred_words) - rng.shuffle(all_preferred_words) - words = all_preferred_words[:n_choices] - self.fields["word"].choices = [(w, w) for w in words] + def clean_words(self): + data = self.cleaned_data['words'] + if len(data) != NB_WORDS: + raise forms.ValidationError(_(f"Please choose exactly {NB_WORDS} words")) + return data class WEIBusInformation2025(WEIBusInformation): @@ -86,7 +226,7 @@ class WEIBusInformation2025(WEIBusInformation): def __init__(self, bus): self.scores = {} - for word in WORDS: + for word in WORDS['list']: self.scores[word] = 0 super().__init__(bus) @@ -108,7 +248,7 @@ class BusInformationForm2025(forms.ModelForm): except (json.JSONDecodeError, TypeError, AttributeError): initial_scores = {} if words is None: - words = WORDS + words = WORDS['list'] self.words = words choices = [(i, str(i)) for i in range(6)] # [(0, '0'), (1, '1'), ..., (5, '5')] @@ -145,10 +285,26 @@ class WEISurveyInformation2025(WEISurveyInformation): step = 0 def __init__(self, registration): - for i in range(1, 21): + for i in range(1, 5): setattr(self, "word" + str(i), None) + for q in WORDS['questions']: + setattr(self, q, None) super().__init__(registration) + def reset(self, registration): + """ + Réinitialise complètement le questionnaire : step, seed, mots choisis et réponses aux questions. + """ + self.step = 0 + self.seed = 0 + for i in range(1, 5): + setattr(self, f"word{i}", None) + for q in WORDS['questions']: + setattr(self, q, None) + self.save(registration) + registration._force_save = True + registration.save() + class WEISurvey2025(WEISurvey): """ @@ -174,10 +330,20 @@ class WEISurvey2025(WEISurvey): @transaction.atomic def form_valid(self, form): - word = form.cleaned_data["word"] - self.information.step += 1 - setattr(self.information, "word" + str(self.information.step), word) - self.save() + if self.information.step == 0: + words = form.cleaned_data['words'] + for i, word in enumerate(words, 1): + setattr(self.information, "word" + str(i), word) + self.information.step += 1 + self.save() + else: + questions = list(WORDS['questions'].keys()) + idx = self.information.step - 1 + if idx < len(questions): + q = questions[idx] + setattr(self.information, q, form.cleaned_data[q]) + self.information.step += 1 + self.save() @classmethod def get_algorithm_class(cls): @@ -187,7 +353,7 @@ class WEISurvey2025(WEISurvey): """ The survey is complete once the bus is chosen. """ - return self.information.step == 20 + return self.information.step > len(WORDS['questions']) @classmethod @lru_cache() @@ -206,7 +372,8 @@ class WEISurvey2025(WEISurvey): bus_info = self.get_algorithm_class().get_bus_information(bus) # Score is the given score by the bus subtracted to the mid-score of the buses. s = sum(bus_info.scores[getattr(self.information, 'word' + str(i))] - - self.word_mean(getattr(self.information, 'word' + str(i))) for i in range(1, 21)) / 20 + - self.word_mean(getattr(self.information, 'word' + str(i))) for i in range(1, 1 + NB_WORDS)) / NB_WORDS + s += sum(1 for q in WORDS['questions'] if getattr(self.information, q) == str(bus.pk)) return s @lru_cache() @@ -243,6 +410,13 @@ class WEISurveyAlgorithm2025(WEISurveyAlgorithm): def get_bus_information_form(cls): return BusInformationForm2025 + @classmethod + def get_buses(cls): + + if not hasattr(cls, '_buses'): + cls._buses = Bus.objects.filter(wei__year=cls.get_survey_class().get_year(), size__gt=0).all().exclude(name='Staff') + return cls._buses + def run_algorithm(self, display_tqdm=False): """ Gale-Shapley algorithm implementation. diff --git a/apps/wei/tables.py b/apps/wei/tables.py index f1d31813..cd493087 100644 --- a/apps/wei/tables.py +++ b/apps/wei/tables.py @@ -71,7 +71,7 @@ class WEIRegistrationTable(tables.Table): 'wei:wei_delete_registration', args=[A('pk')], orderable=False, - verbose_name=_("delete"), + verbose_name=_("Delete"), text=_("Delete"), attrs={ 'th': { diff --git a/apps/wei/templates/wei/weiclub_detail.html b/apps/wei/templates/wei/weiclub_detail.html index 2a573b03..e4b0bfbb 100644 --- a/apps/wei/templates/wei/weiclub_detail.html +++ b/apps/wei/templates/wei/weiclub_detail.html @@ -39,6 +39,11 @@ SPDX-License-Identifier: GPL-3.0-or-later data-turbolinks="false"> {% trans "Update my registration" %}
                + {% if not not_first_year %} + + {% trans "Restart survey" %} + + {% endif %} {% endif %}
                {% endif %} diff --git a/apps/wei/tests/test_wei_algorithm_2025.py b/apps/wei/tests/test_wei_algorithm_2025.py index 5930eb3b..4b5c91c4 100644 --- a/apps/wei/tests/test_wei_algorithm_2025.py +++ b/apps/wei/tests/test_wei_algorithm_2025.py @@ -6,7 +6,7 @@ import random from django.contrib.auth.models import User from django.test import TestCase -from ..forms.surveys.wei2025 import WEIBusInformation2025, WEISurvey2025, WORDS, WEISurveyInformation2025 +from ..forms.surveys.wei2025 import WEIBusInformation2025, WEISurvey2025, WORDS, NB_WORDS, WEISurveyInformation2025 from ..models import Bus, WEIClub, WEIRegistration @@ -34,8 +34,8 @@ class TestWEIAlgorithm(TestCase): bus = Bus.objects.create(wei=self.wei, name=f"Bus {i}", size=10) self.buses.append(bus) information = WEIBusInformation2025(bus) - for word in WORDS: - information.scores[word] = random.randint(0, 101) + for word in WORDS['list']: + information.scores[word] = random.randint(0, 6) information.save() bus.save() @@ -54,7 +54,7 @@ class TestWEIAlgorithm(TestCase): ) information = WEISurveyInformation2025(registration) for j in range(1, 21): - setattr(information, f'word{j}', random.choice(WORDS)) + setattr(information, f'word{j}', random.choice(WORDS['list'])) information.step = 20 information.save(registration) registration.save() @@ -83,9 +83,11 @@ class TestWEIAlgorithm(TestCase): birth_date='2000-01-01', ) information = WEISurveyInformation2025(registration) - for j in range(1, 21): - setattr(information, f'word{j}', random.choice(WORDS)) - information.step = 20 + for j in range(1, 1 + NB_WORDS): + setattr(information, f'word{j}', random.choice(WORDS['list'])) + for q in WORDS['questions']: + setattr(information, q, random.choice(list(WORDS['questions'][q][1].keys()))) + information.step = len(WORDS['questions']) + 1 information.save(registration) registration.save() @@ -106,6 +108,6 @@ class TestWEIAlgorithm(TestCase): max_score = buses[0][1] penalty += (max_score - score) ** 2 - self.assertLessEqual(max_score - score, 25) # Always less than 25 % of tolerance + self.assertLessEqual(max_score - score, 1) # Always less than 25 % of tolerance self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 % diff --git a/apps/wei/views.py b/apps/wei/views.py index bd1f1f5f..3bca3928 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -1246,6 +1246,10 @@ class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView): if not self.survey: self.survey = CurrentSurvey(obj) + + if request.GET.get("reset") == "true": + info = self.survey.information + info.reset(obj) # If the survey is complete, then display the end page. if self.survey.is_complete(): return redirect(reverse_lazy('wei:wei_survey_end', args=(self.survey.registration.pk,))) From 97597eb103e82c241153756622d8d1fd34fb86bd Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Thu, 24 Jul 2025 12:26:44 +0200 Subject: [PATCH 097/153] Fixed 1A forms --- apps/wei/templates/wei/weimembership_form.html | 2 ++ apps/wei/views.py | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/wei/templates/wei/weimembership_form.html b/apps/wei/templates/wei/weimembership_form.html index 512f6f8d..a47ecf5c 100644 --- a/apps/wei/templates/wei/weimembership_form.html +++ b/apps/wei/templates/wei/weimembership_form.html @@ -149,6 +149,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
              • {% blocktrans trimmed with amount=fee|pretty_money %} Membership fees: {{ amount }} {% endblocktrans %}
              • + {% if not registration.first_year %} {% if registration.deposit_type == 'note' %}
              • {% blocktrans trimmed with amount=club.deposit_amount|pretty_money %} Deposit (by Note transaction): {{ amount }} @@ -158,6 +159,7 @@ SPDX-License-Identifier: GPL-3.0-or-later Deposit (by check): {{ amount }} {% endblocktrans %}
              • {% endif %} + {% endif %}
              • {% blocktrans trimmed with total=total_needed|pretty_money %} Total needed: {{ total }} {% endblocktrans %}
              • diff --git a/apps/wei/views.py b/apps/wei/views.py index 3bca3928..013f2b23 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -816,9 +816,12 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update del form.fields["deposit_check"] # S'assurer que le champ deposit_type est obligatoire pour les 2A+ - if not self.object.first_year and "deposit_type" in form.fields: - form.fields["deposit_type"].required = True - form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit") + if "deposit_type" in form.fields: + if self.object.first_year: + del form.fields["deposit_type"] + else: + form.fields["deposit_type"].required = True + form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit") return form @@ -879,7 +882,6 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update information["preferred_roles_name"] = [role.name for role in choose_bus_form.cleaned_data["roles"]] form.instance.information = information - # Sauvegarder le type de caution pour les 2A+ if "deposit_type" in form.cleaned_data: form.instance.deposit_type = form.cleaned_data["deposit_type"] form.instance.save() From d50bb2134ac0a15548e175b25b81e9cdc6696db0 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 1 Aug 2025 11:56:34 +0200 Subject: [PATCH 098/153] Algorithm changed again --- apps/wei/forms/surveys/wei2025.py | 128 ++++++++++++---------- apps/wei/tests/test_wei_algorithm_2025.py | 10 +- 2 files changed, 79 insertions(+), 59 deletions(-) diff --git a/apps/wei/forms/surveys/wei2025.py b/apps/wei/forms/surveys/wei2025.py index ee748c6c..648a88c6 100644 --- a/apps/wei/forms/surveys/wei2025.py +++ b/apps/wei/forms/surveys/wei2025.py @@ -30,117 +30,117 @@ WORDS = { 'Description 1', { 3: 'Réponse 1 Madagas[car]', - 43: 'Réponse 1 Y2[KAR]', + 4: 'Réponse 1 Y2[KAR]', 2: 'Réponse 1 Tcherno[bus]', - 45: 'Réponse 1 [Kar]tier', + 5: 'Réponse 1 [Kar]tier', 1: 'Réponse 1 [Car]cassonne', - 47: 'Réponse 1 O[car]ina', - 48: 'Réponse 1 Show[bus]', - 49: 'Réponse 1 [Car]ioca' + 6: 'Réponse 1 O[car]ina', + 7: 'Réponse 1 Show[bus]', + 8: 'Réponse 1 [Car]ioca' } ], 'Question 2': [ 'Description 2', { 3: 'Réponse 2 Madagas[car]', - 43: 'Réponse 2 Y2[KAR]', + 4: 'Réponse 2 Y2[KAR]', 2: 'Réponse 2 Tcherno[bus]', - 45: 'Réponse 2 [Kar]tier', + 5: 'Réponse 2 [Kar]tier', 1: 'Réponse 2 [Car]cassonne', - 47: 'Réponse 2 O[car]ina', - 48: 'Réponse 2 Show[bus]', - 49: 'Réponse 2 [Car]ioca' + 6: 'Réponse 2 O[car]ina', + 7: 'Réponse 2 Show[bus]', + 8: 'Réponse 2 [Car]ioca' } ], 'Question 3': [ 'Description 3', { 3: 'Réponse 3 Madagas[car]', - 43: 'Réponse 3 Y2[KAR]', + 4: 'Réponse 3 Y2[KAR]', 2: 'Réponse 3 Tcherno[bus]', - 45: 'Réponse 3 [Kar]tier', + 5: 'Réponse 3 [Kar]tier', 1: 'Réponse 3 [Car]cassonne', - 47: 'Réponse 3 O[car]ina', - 48: 'Réponse 3 Show[bus]', - 49: 'Réponse 3 [Car]ioca' + 6: 'Réponse 3 O[car]ina', + 7: 'Réponse 3 Show[bus]', + 8: 'Réponse 3 [Car]ioca' } ], 'Question 4': [ 'Description 4', { 3: 'Réponse 4 Madagas[car]', - 43: 'Réponse 4 Y2[KAR]', + 4: 'Réponse 4 Y2[KAR]', 2: 'Réponse 4 Tcherno[bus]', - 45: 'Réponse 4 [Kar]tier', + 5: 'Réponse 4 [Kar]tier', 1: 'Réponse 4 [Car]cassonne', - 47: 'Réponse 4 O[car]ina', - 48: 'Réponse 4 Show[bus]', - 49: 'Réponse 4 [Car]ioca' + 6: 'Réponse 4 O[car]ina', + 7: 'Réponse 4 Show[bus]', + 8: 'Réponse 4 [Car]ioca' } ], 'Question 5': [ 'Description 5', { 3: 'Réponse 5 Madagas[car]', - 43: 'Réponse 5 Y2[KAR]', + 4: 'Réponse 5 Y2[KAR]', 2: 'Réponse 5 Tcherno[bus]', - 45: 'Réponse 5 [Kar]tier', + 5: 'Réponse 5 [Kar]tier', 1: 'Réponse 5 [Car]cassonne', - 47: 'Réponse 5 O[car]ina', - 48: 'Réponse 5 Show[bus]', - 49: 'Réponse 5 [Car]ioca' + 6: 'Réponse 5 O[car]ina', + 7: 'Réponse 5 Show[bus]', + 8: 'Réponse 5 [Car]ioca' } ], 'Question 6': [ 'Description 6', { 3: 'Réponse 6 Madagas[car]', - 43: 'Réponse 6 Y2[KAR]', + 4: 'Réponse 6 Y2[KAR]', 2: 'Réponse 6 Tcherno[bus]', - 45: 'Réponse 6 [Kar]tier', + 5: 'Réponse 6 [Kar]tier', 1: 'Réponse 6 [Car]cassonne', - 47: 'Réponse 6 O[car]ina', - 48: 'Réponse 6 Show[bus]', - 49: 'Réponse 6 [Car]ioca' + 6: 'Réponse 6 O[car]ina', + 7: 'Réponse 6 Show[bus]', + 8: 'Réponse 6 [Car]ioca' } ], 'Question 7': [ 'Description 7', { 3: 'Réponse 7 Madagas[car]', - 43: 'Réponse 7 Y2[KAR]', + 4: 'Réponse 7 Y2[KAR]', 2: 'Réponse 7 Tcherno[bus]', - 45: 'Réponse 7 [Kar]tier', + 5: 'Réponse 7 [Kar]tier', 1: 'Réponse 7 [Car]cassonne', - 47: 'Réponse 7 O[car]ina', - 48: 'Réponse 7 Show[bus]', - 49: 'Réponse 7 [Car]ioca' + 6: 'Réponse 7 O[car]ina', + 7: 'Réponse 7 Show[bus]', + 8: 'Réponse 7 [Car]ioca' } ], 'Question 8': [ 'Description 8', { 3: 'Réponse 8 Madagas[car]', - 43: 'Réponse 8 Y2[KAR]', + 4: 'Réponse 8 Y2[KAR]', 2: 'Réponse 8 Tcherno[bus]', - 45: 'Réponse 8 [Kar]tier', + 5: 'Réponse 8 [Kar]tier', 1: 'Réponse 8 [Car]cassonne', - 47: 'Réponse 8 O[car]ina', - 48: 'Réponse 8 Show[bus]', - 49: 'Réponse 8 [Car]ioca' + 6: 'Réponse 8 O[car]ina', + 7: 'Réponse 8 Show[bus]', + 8: 'Réponse 8 [Car]ioca' } ], 'Question 9': [ 'Description 9', { 3: 'Réponse 9 Madagas[car]', - 43: 'Réponse 9 Y2[KAR]', + 4: 'Réponse 9 Y2[KAR]', 2: 'Réponse 9 Tcherno[bus]', - 45: 'Réponse 9 [Kar]tier', + 5: 'Réponse 9 [Kar]tier', 1: 'Réponse 9 [Car]cassonne', - 47: 'Réponse 9 O[car]ina', - 48: 'Réponse 9 Show[bus]', - 49: 'Réponse 9 [Car]ioca' + 6: 'Réponse 9 O[car]ina', + 7: 'Réponse 9 Show[bus]', + 8: 'Réponse 9 [Car]ioca' } ] } @@ -366,24 +366,41 @@ class WEISurvey2025(WEISurvey): @lru_cache() def score(self, bus): + """ + The score given by the answers to the questions + """ + if not self.is_complete(): + raise ValueError("Survey is not ended, can't calculate score") + # Score is the given score by the bus subtracted to the mid-score of the buses. + s = sum(1 for q in WORDS['questions'] if getattr(self.information, q) == bus.pk) + return s + + @lru_cache() + def score_words(self, bus): + """ + The score given by the choice of words + """ if not self.is_complete(): raise ValueError("Survey is not ended, can't calculate score") bus_info = self.get_algorithm_class().get_bus_information(bus) # Score is the given score by the bus subtracted to the mid-score of the buses. s = sum(bus_info.scores[getattr(self.information, 'word' + str(i))] - - self.word_mean(getattr(self.information, 'word' + str(i))) for i in range(1, 1 + NB_WORDS)) / NB_WORDS - s += sum(1 for q in WORDS['questions'] if getattr(self.information, q) == str(bus.pk)) + - self.word_mean(getattr(self.information, 'word' + str(i))) for i in range(1, 1 + NB_WORDS)) return s @lru_cache() def scores_per_bus(self): - return {bus: self.score(bus) for bus in self.get_algorithm_class().get_buses()} + return {bus: (self.score(bus), self.score_words(bus)) for bus in self.get_algorithm_class().get_buses()} @lru_cache() def ordered_buses(self): + """ + Force the choice of bus to be in the 3 preferred buses according to the words + """ values = list(self.scores_per_bus().items()) - values.sort(key=lambda item: -item[1]) + values.sort(key=lambda item: -item[1][1]) + values = values[:3] return values @classmethod @@ -421,6 +438,7 @@ class WEISurveyAlgorithm2025(WEISurveyAlgorithm): """ Gale-Shapley algorithm implementation. We modify it to allow buses to have multiple "weddings". + We use lexigographical order on both scores """ surveys = list(self.get_survey_class()(r) for r in self.get_registrations()) # All surveys surveys = [s for s in surveys if s.is_complete()] # Don't consider invalid surveys @@ -481,7 +499,7 @@ class WEISurveyAlgorithm2025(WEISurveyAlgorithm): while free_surveys: # Some students are not affected survey = free_surveys[0] buses = survey.ordered_buses() # Preferences of the student - for bus, current_score in buses: + for bus, current_scores in buses: if self.get_bus_information(bus).has_free_seats(surveys, quotas): # Selected bus has free places. Put student in the bus survey.select_bus(bus) @@ -491,17 +509,17 @@ class WEISurveyAlgorithm2025(WEISurveyAlgorithm): else: # Current bus has not enough places. Remove the least preferred student from the bus if existing least_preferred_survey = None - least_score = -1 + least_scores = (-1, -1) # Find the least student in the bus that has a lower score than the current student for survey2 in surveys: if not survey2.information.valid or survey2.information.get_selected_bus() != bus: continue - score2 = survey2.score(bus) - if current_score <= score2: # Ignore better students + scores2 = survey2.score(bus), survey2.score_words(bus) + if current_scores <= scores2: # Ignore better students continue - if least_preferred_survey is None or score2 < least_score: + if least_preferred_survey is None or scores2 < least_scores: least_preferred_survey = survey2 - least_score = score2 + least_scores = scores2 if least_preferred_survey is not None: # Remove the least student from the bus and put the current student in. diff --git a/apps/wei/tests/test_wei_algorithm_2025.py b/apps/wei/tests/test_wei_algorithm_2025.py index 4b5c91c4..571ba40d 100644 --- a/apps/wei/tests/test_wei_algorithm_2025.py +++ b/apps/wei/tests/test_wei_algorithm_2025.py @@ -30,7 +30,7 @@ class TestWEIAlgorithm(TestCase): ) self.buses = [] - for i in range(10): + for i in range(8): bus = Bus.objects.create(wei=self.wei, name=f"Bus {i}", size=10) self.buses.append(bus) information = WEIBusInformation2025(bus) @@ -74,7 +74,7 @@ class TestWEIAlgorithm(TestCase): Buses are full of first year people, ensure that they are happy """ # Add a lot of users - for i in range(95): + for i in range(80): user = User.objects.create(username=f"user{i}") registration = WEIRegistration.objects.create( user=user, @@ -90,6 +90,7 @@ class TestWEIAlgorithm(TestCase): information.step = len(WORDS['questions']) + 1 information.save(registration) registration.save() + survey = WEISurvey2025(registration) # Run algorithm WEISurvey2025.get_algorithm_class()().run_algorithm() @@ -104,8 +105,9 @@ class TestWEIAlgorithm(TestCase): survey = WEISurvey2025(r) chosen_bus = survey.information.get_selected_bus() buses = survey.ordered_buses() - score = min(v for bus, v in buses if bus == chosen_bus) - max_score = buses[0][1] + self.assertIn(chosen_bus, [x[0] for x in buses]) + score = min(v for bus, (v, __) in buses if bus == chosen_bus) + max_score = buses[0][1][0] penalty += (max_score - score) ** 2 self.assertLessEqual(max_score - score, 1) # Always less than 25 % of tolerance From 023fc1db8475ea1e6b1fdd4ff4bf3951a6e56c61 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Fri, 1 Aug 2025 22:53:15 +0200 Subject: [PATCH 099/153] Visual fixes --- apps/wei/templates/wei/weimembership_form.html | 2 +- apps/wei/views.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/apps/wei/templates/wei/weimembership_form.html b/apps/wei/templates/wei/weimembership_form.html index a47ecf5c..b0c15225 100644 --- a/apps/wei/templates/wei/weimembership_form.html +++ b/apps/wei/templates/wei/weimembership_form.html @@ -143,7 +143,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endblocktrans %} {% endif %} -
                +
                {% trans "Required payments:" %}
                • {% blocktrans trimmed with amount=fee|pretty_money %} diff --git a/apps/wei/views.py b/apps/wei/views.py index 013f2b23..e3355345 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -1125,16 +1125,16 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): 'credit': credit_amount, 'needed': total_needed} ) - return super().form_invalid(form) + return self.form_invalid(form) if credit_amount: if not last_name: form.add_error('last_name', _("This field is required.")) - return super().form_invalid(form) + return self.form_invalid(form) if not first_name: form.add_error('first_name', _("This field is required.")) - return super().form_invalid(form) + return self.form_invalid(form) # Credit note before adding the membership SpecialTransaction.objects.create( @@ -1178,6 +1178,13 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): return super().form_valid(form) + def form_invalid(self, form): + registration = getattr(form.instance, "registration", None) + if registration is not None: + registration.deposit_check = False + registration.save() + return super().form_invalid(form) + def get_success_url(self): self.object.refresh_from_db() return reverse_lazy("wei:wei_registrations", kwargs={"pk": self.object.club.pk}) From 8e98d62b69083cc7e8b96256d142c7d1562f246f Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 2 Aug 2025 16:31:04 +0200 Subject: [PATCH 100/153] Soge credit fixed --- .../migrations/0015_alter_profile_promotion.py | 18 ++++++++++++++++++ apps/treasury/models.py | 14 ++++++-------- .../0017_alter_weiclub_fee_soge_credit.py | 18 ++++++++++++++++++ apps/wei/models.py | 2 +- apps/wei/views.py | 9 ++++----- 5 files changed, 47 insertions(+), 14 deletions(-) create mode 100644 apps/member/migrations/0015_alter_profile_promotion.py create mode 100644 apps/wei/migrations/0017_alter_weiclub_fee_soge_credit.py diff --git a/apps/member/migrations/0015_alter_profile_promotion.py b/apps/member/migrations/0015_alter_profile_promotion.py new file mode 100644 index 00000000..f838c563 --- /dev/null +++ b/apps/member/migrations/0015_alter_profile_promotion.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-08-02 13:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('member', '0014_create_bda'), + ] + + operations = [ + migrations.AlterField( + model_name='profile', + name='promotion', + field=models.PositiveSmallIntegerField(default=2025, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'), + ), + ] diff --git a/apps/treasury/models.py b/apps/treasury/models.py index 214203a9..48709801 100644 --- a/apps/treasury/models.py +++ b/apps/treasury/models.py @@ -353,13 +353,11 @@ class SogeCredit(models.Model): def amount(self): if self.valid: return self.credit_transaction.total - amount = sum(max(transaction.total - 2000, 0) for transaction in self.transactions.all()) - if 'wei' in settings.INSTALLED_APPS: - from wei.models import WEIMembership - if not WEIMembership.objects\ - .filter(club__weiclub__year=self.credit_transaction.created_at.year, user=self.user).exists(): - # 80 € for people that don't go to WEI - amount += 8000 + amount = 0 + transactions_wei = self.transactions.filter(membership__club__weiclub__isnull=False) + amount += sum(max(transaction.total - transaction.membership.club.weiclub.fee_soge_credit, 0) for transaction in transactions_wei) + transactions_not_wei = self.transactions.filter(membership__club__weiclub__isnull=True) + amount += sum(transaction.total for transaction in transactions_not_wei) return amount def update_transactions(self): @@ -441,7 +439,7 @@ class SogeCredit(models.Model): With Great Power Comes Great Responsibility... """ - total_fee = sum(max(transaction.total - 2000, 0) for transaction in self.transactions.all() if not transaction.valid) + total_fee = self.amount if self.user.note.balance < total_fee: raise ValidationError(_("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.")) diff --git a/apps/wei/migrations/0017_alter_weiclub_fee_soge_credit.py b/apps/wei/migrations/0017_alter_weiclub_fee_soge_credit.py new file mode 100644 index 00000000..c78ffdd4 --- /dev/null +++ b/apps/wei/migrations/0017_alter_weiclub_fee_soge_credit.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.4 on 2025-08-02 13:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wei', '0016_weiregistration_fee_alter_weiclub_fee_soge_credit'), + ] + + operations = [ + migrations.AlterField( + model_name='weiclub', + name='fee_soge_credit', + field=models.PositiveIntegerField(default=0, verbose_name='membership fee (soge credit)'), + ), + ] diff --git a/apps/wei/models.py b/apps/wei/models.py index 18ba8a58..2c0653c7 100644 --- a/apps/wei/models.py +++ b/apps/wei/models.py @@ -40,7 +40,7 @@ class WEIClub(Club): fee_soge_credit = models.PositiveIntegerField( verbose_name=_("membership fee (soge credit)"), - default=2000, + default=0, ) class Meta: diff --git a/apps/wei/views.py b/apps/wei/views.py index e3355345..d180d3d9 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -798,11 +798,6 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update choose_bus_form.fields["team"].queryset = BusTeam.objects.filter(bus__wei=context["club"]) context["membership_form"] = choose_bus_form - if not self.object.soge_credit and self.object.user.profile.soge: - form = context["form"] - form.fields["soge_credit"].disabled = True - form.fields["soge_credit"].help_text = _("You already opened an account in the Société générale.") - return context def get_form(self, form_class=None): @@ -823,6 +818,10 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update form.fields["deposit_type"].required = True form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit") + if self.object.user.profile.soge: + form.fields["soge_credit"].disabled = True + form.fields["soge_credit"].help_text = _("You already opened an account in the Société générale.") + return form def get_membership_form(self, data=None, instance=None): From 573f2d8a225863ead07994e82cbfc5092f6eefe2 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 2 Aug 2025 17:18:51 +0200 Subject: [PATCH 101/153] More robust algorithm --- apps/wei/forms/surveys/wei2025.py | 15 +++++++-------- apps/wei/tests/test_wei_algorithm_2025.py | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/wei/forms/surveys/wei2025.py b/apps/wei/forms/surveys/wei2025.py index 648a88c6..db64487a 100644 --- a/apps/wei/forms/surveys/wei2025.py +++ b/apps/wei/forms/surveys/wei2025.py @@ -365,7 +365,7 @@ class WEISurvey2025(WEISurvey): return sum([cls.get_algorithm_class().get_bus_information(bus).scores[word] for bus in buses]) / buses.count() @lru_cache() - def score(self, bus): + def score_questions(self, bus): """ The score given by the answers to the questions """ @@ -391,7 +391,7 @@ class WEISurvey2025(WEISurvey): @lru_cache() def scores_per_bus(self): - return {bus: (self.score(bus), self.score_words(bus)) for bus in self.get_algorithm_class().get_buses()} + return {bus: (self.score_questions(bus), self.score_words(bus)) for bus in self.get_algorithm_class().get_buses()} @lru_cache() def ordered_buses(self): @@ -400,7 +400,6 @@ class WEISurvey2025(WEISurvey): """ values = list(self.scores_per_bus().items()) values.sort(key=lambda item: -item[1][1]) - values = values[:3] return values @classmethod @@ -509,17 +508,17 @@ class WEISurveyAlgorithm2025(WEISurveyAlgorithm): else: # Current bus has not enough places. Remove the least preferred student from the bus if existing least_preferred_survey = None - least_scores = (-1, -1) + least_score = -1 # Find the least student in the bus that has a lower score than the current student for survey2 in surveys: if not survey2.information.valid or survey2.information.get_selected_bus() != bus: continue - scores2 = survey2.score(bus), survey2.score_words(bus) - if current_scores <= scores2: # Ignore better students + score2 = survey2.score_questions(bus) + if current_scores[0] <= score2: # Ignore better students continue - if least_preferred_survey is None or scores2 < least_scores: + if least_preferred_survey is None or score2 < least_score: least_preferred_survey = survey2 - least_scores = scores2 + least_score = score2 if least_preferred_survey is not None: # Remove the least student from the bus and put the current student in. diff --git a/apps/wei/tests/test_wei_algorithm_2025.py b/apps/wei/tests/test_wei_algorithm_2025.py index 571ba40d..268c5b03 100644 --- a/apps/wei/tests/test_wei_algorithm_2025.py +++ b/apps/wei/tests/test_wei_algorithm_2025.py @@ -110,6 +110,6 @@ class TestWEIAlgorithm(TestCase): max_score = buses[0][1][0] penalty += (max_score - score) ** 2 - self.assertLessEqual(max_score - score, 1) # Always less than 25 % of tolerance + self.assertLessEqual(max_score - score, 1) self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 % From d6f9a9c5b0436ff52213557beb64cdbbffed3cb6 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 2 Aug 2025 18:35:53 +0200 Subject: [PATCH 102/153] Better test --- apps/wei/forms/surveys/wei2025.py | 6 +++--- apps/wei/tests/test_wei_algorithm_2025.py | 25 +++++++++++++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/apps/wei/forms/surveys/wei2025.py b/apps/wei/forms/surveys/wei2025.py index db64487a..33291bb1 100644 --- a/apps/wei/forms/surveys/wei2025.py +++ b/apps/wei/forms/surveys/wei2025.py @@ -386,7 +386,7 @@ class WEISurvey2025(WEISurvey): bus_info = self.get_algorithm_class().get_bus_information(bus) # Score is the given score by the bus subtracted to the mid-score of the buses. s = sum(bus_info.scores[getattr(self.information, 'word' + str(i))] - - self.word_mean(getattr(self.information, 'word' + str(i))) for i in range(1, 1 + NB_WORDS)) + - self.word_mean(getattr(self.information, 'word' + str(i))) for i in range(1, 1 + NB_WORDS)) / self.get_algorithm_class().get_buses().count() return s @lru_cache() @@ -399,7 +399,7 @@ class WEISurvey2025(WEISurvey): Force the choice of bus to be in the 3 preferred buses according to the words """ values = list(self.scores_per_bus().items()) - values.sort(key=lambda item: -item[1][1]) + values.sort(key=lambda item: -item[1][0]) return values @classmethod @@ -514,7 +514,7 @@ class WEISurveyAlgorithm2025(WEISurveyAlgorithm): if not survey2.information.valid or survey2.information.get_selected_bus() != bus: continue score2 = survey2.score_questions(bus) - if current_scores[0] <= score2: # Ignore better students + if current_scores[1] <= score2: # Ignore better students continue if least_preferred_survey is None or score2 < least_score: least_preferred_survey = survey2 diff --git a/apps/wei/tests/test_wei_algorithm_2025.py b/apps/wei/tests/test_wei_algorithm_2025.py index 268c5b03..883b6e5a 100644 --- a/apps/wei/tests/test_wei_algorithm_2025.py +++ b/apps/wei/tests/test_wei_algorithm_2025.py @@ -105,11 +105,28 @@ class TestWEIAlgorithm(TestCase): survey = WEISurvey2025(r) chosen_bus = survey.information.get_selected_bus() buses = survey.ordered_buses() + '''print(buses) + print(chosen_bus)''' self.assertIn(chosen_bus, [x[0] for x in buses]) - score = min(v for bus, (v, __) in buses if bus == chosen_bus) - max_score = buses[0][1][0] - penalty += (max_score - score) ** 2 + score_questions, score_words = next(scores for bus, scores in buses if bus == chosen_bus) + max_score_questions = max(buses[i][1][0] for i in range(len(buses))) + max_score_words = max(buses[i][1][1] for i in range(len(buses))) + penalty += (max_score_words - score_words) ** 2 + penalty += (max_score_questions - score_questions) ** 2 - self.assertLessEqual(max_score - score, 1) + self.assertLessEqual(max_score_questions - score_questions, 3) + self.assertLessEqual(max_score_words - score_words, 2.5) self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 % + + # There shouldn't be users who would prefer to switch buses + for r1 in WEIRegistration.objects.filter(wei=self.wei).all(): + survey1 = WEISurvey2025(r1) + bus1 = survey1.information.get_selected_bus() + for r2 in WEIRegistration.objects.filter(wei=self.wei, pk__gt=r1.pk): + survey2 = WEISurvey2025(r2) + bus2 = survey2.information.get_selected_bus() + + prefer_switch_bus_words = survey1.score_words(bus2) > survey1.score_words(bus1) and survey2.score_words(bus1) > survey2.score_words(bus2) + prefer_switch_bus_questions = survey1.score_questions(bus2) > survey1.score_questions(bus1) and survey2.score_questions(bus1) > survey2.score_questions(bus2) + self.assertFalse(prefer_switch_bus_words and prefer_switch_bus_questions) \ No newline at end of file From d1aa1edd09fa34290c9faaf0ddf2e9f1477bde53 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 2 Aug 2025 23:32:13 +0200 Subject: [PATCH 103/153] Deposit check logic changed --- apps/permission/fixtures/initial.json | 40 +++++++++++++++++-- apps/wei/api/views.py | 2 +- apps/wei/forms/registration.py | 6 +-- ..._weiregistration_deposit_check_and_more.py | 22 ++++++++++ apps/wei/models.py | 4 +- apps/wei/tables.py | 35 ++++++++++++++-- .../wei/templates/wei/weimembership_form.html | 6 +-- apps/wei/tests/test_wei_algorithm_2025.py | 7 ++-- apps/wei/tests/test_wei_registration.py | 8 ++-- apps/wei/views.py | 39 +++++++++++------- 10 files changed, 132 insertions(+), 37 deletions(-) create mode 100644 apps/wei/migrations/0018_remove_weiregistration_deposit_check_and_more.py diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 8c69e367..703e8d7b 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -1391,12 +1391,12 @@ "wei", "weiregistration" ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", + "query": "[\"AND\", {\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}, {\"deposit_type\": \"note\"}]", "type": "change", "mask": 2, "field": "caution_check", "permanent": false, - "description": "Dire si un chèque de caution est donné pour une inscription WEI" + "description": "Autoriser une transaction de caution WEI" } }, { @@ -4366,6 +4366,38 @@ "description": "Modifier l'équipe d'une adhésion WEI à son bus" } }, + { + "model": "permission.permission", + "pk": 294, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "[\"AND\", {\"wei__year\": [\"today\", \"year\"], \"wei__membership_start__lte\": [\"today\"], \"wei__membership_end__gte\": [\"today\"]}, {\"deposit_type\": \"check\"}]", + "type": "change", + "mask": 2, + "field": "caution_check", + "permanent": false, + "description": "Dire si un chèque de caution a été donné" + } + }, + { + "model": "permission.permission", + "pk": 295, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei__year\": [\"today\", \"year\"]}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir toutes les inscriptions au WEI courant" + } + }, { "model": "permission.role", "pk": 1, @@ -4647,7 +4679,9 @@ 176, 177, 178, - 183 + 183, + 294, + 295 ] } }, diff --git a/apps/wei/api/views.py b/apps/wei/api/views.py index 9f25e4d3..99f36d1c 100644 --- a/apps/wei/api/views.py +++ b/apps/wei/api/views.py @@ -77,7 +77,7 @@ class WEIRegistrationViewSet(ReadProtectedModelViewSet): filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['user', 'user__username', 'user__first_name', 'user__last_name', 'user__email', 'user__note__alias__name', 'user__note__alias__normalized_name', 'wei', 'wei__name', - 'wei__email', 'wei__year', 'soge_credit', 'deposit_check', 'birth_date', 'gender', + 'wei__email', 'wei__year', 'soge_credit', 'deposit_given', 'birth_date', 'gender', 'clothing_cut', 'clothing_size', 'first_year', 'emergency_contact_name', 'emergency_contact_phone', ] search_fields = ['$user__username', '$user__first_name', '$user__last_name', '$user__email', diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index 4bf0dcd2..6c8f07cb 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -44,7 +44,7 @@ class WEIRegistrationForm(forms.ModelForm): fields = [ 'user', 'soge_credit', 'birth_date', 'gender', 'clothing_size', 'health_issues', 'emergency_contact_name', 'emergency_contact_phone', - 'first_year', 'information_json', 'deposit_check', 'deposit_type' + 'first_year', 'information_json', 'deposit_given', 'deposit_type' ] widgets = { "user": Autocomplete( @@ -59,7 +59,7 @@ class WEIRegistrationForm(forms.ModelForm): 'minDate': '1900-01-01', 'maxDate': '2100-01-01' }), - "deposit_check": forms.BooleanField( + "deposit_given": forms.BooleanField( required=False, ), "deposit_type": forms.RadioSelect(), @@ -161,7 +161,7 @@ class WEIMembership1AForm(WEIMembershipForm): """ Used to confirm registrations of first year members without choosing a bus now. """ - deposit_check = None + deposit_given = None roles = None def clean(self): diff --git a/apps/wei/migrations/0018_remove_weiregistration_deposit_check_and_more.py b/apps/wei/migrations/0018_remove_weiregistration_deposit_check_and_more.py new file mode 100644 index 00000000..fbc5cc8a --- /dev/null +++ b/apps/wei/migrations/0018_remove_weiregistration_deposit_check_and_more.py @@ -0,0 +1,22 @@ +# Generated by Django 5.2.4 on 2025-08-02 17:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wei', '0017_alter_weiclub_fee_soge_credit'), + ] + + operations = [ + migrations.RemoveField( + model_name='weiregistration', + name='deposit_check', + ), + migrations.AddField( + model_name='weiregistration', + name='deposit_given', + field=models.BooleanField(default=False, verbose_name='Deposit given'), + ), + ] diff --git a/apps/wei/models.py b/apps/wei/models.py index 2c0653c7..5aa4e94f 100644 --- a/apps/wei/models.py +++ b/apps/wei/models.py @@ -202,9 +202,9 @@ class WEIRegistration(models.Model): verbose_name=_("Credit from Société générale"), ) - deposit_check = models.BooleanField( + deposit_given = models.BooleanField( default=False, - verbose_name=_("Deposit check given") + verbose_name=_("Deposit given") ) deposit_type = models.CharField( diff --git a/apps/wei/tables.py b/apps/wei/tables.py index cd493087..7dc0d531 100644 --- a/apps/wei/tables.py +++ b/apps/wei/tables.py @@ -84,6 +84,35 @@ class WEIRegistrationTable(tables.Table): }, ) + def render_deposit_type(self, record): + if record.first_year: + return format_html("∅") + if record.deposit_type == 'check': + # TODO Install Font Awesome 6 to acces more icons (and keep compaibility with current used v4) + return format_html(""" + + + + """) + if record.deposit_type == 'note': + return format_html("") + def render_validate(self, record): hasperm = PermissionBackend.check_perm( get_current_request(), "wei.add_weimembership", WEIMembership( @@ -125,8 +154,8 @@ class WEIRegistrationTable(tables.Table): order_by = ('validate', 'user',) model = WEIRegistration template_name = 'django_tables2/bootstrap4.html' - fields = ('user', 'user__first_name', 'user__last_name', 'first_year', 'deposit_check', - 'edit', 'validate', 'delete',) + fields = ('user', 'user__first_name', 'user__last_name', 'first_year', 'deposit_given', + 'deposit_type', 'edit', 'validate', 'delete',) row_attrs = { 'class': 'table-row', 'id': lambda record: "row-" + str(record.pk), @@ -165,7 +194,7 @@ class WEIMembershipTable(tables.Table): model = WEIMembership template_name = 'django_tables2/bootstrap4.html' fields = ('user', 'user__last_name', 'user__first_name', 'registration__gender', 'user__profile__department', - 'year', 'bus', 'team', 'registration__deposit_check', ) + 'year', 'bus', 'team', 'registration__deposit_given', ) row_attrs = { 'class': 'table-row', 'id': lambda record: "row-" + str(record.pk), diff --git a/apps/wei/templates/wei/weimembership_form.html b/apps/wei/templates/wei/weimembership_form.html index b0c15225..d73c1be0 100644 --- a/apps/wei/templates/wei/weimembership_form.html +++ b/apps/wei/templates/wei/weimembership_form.html @@ -96,7 +96,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endif %} {% else %}
                  {% trans 'Deposit check given'|capfirst %}
                  -
                  {{ registration.deposit_check|yesno }}
                  +
                  {{ registration.deposit_given|yesno }}
                  {% with information=registration.information %}
                  {% trans 'preferred bus'|capfirst %}
                  @@ -169,9 +169,9 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endblocktrans %}

                - {% if not registration.deposit_check and not registration.first_year and registration.caution_type == 'check' %} + {% if not registration.deposit_given and not registration.first_year and registration.caution_type == 'check' %}
                - {% trans "The user didn't give her/his caution check." %} + {% trans "The user didn't give her/his caution." %}
                {% endif %} diff --git a/apps/wei/tests/test_wei_algorithm_2025.py b/apps/wei/tests/test_wei_algorithm_2025.py index 883b6e5a..912f0e90 100644 --- a/apps/wei/tests/test_wei_algorithm_2025.py +++ b/apps/wei/tests/test_wei_algorithm_2025.py @@ -126,7 +126,8 @@ class TestWEIAlgorithm(TestCase): for r2 in WEIRegistration.objects.filter(wei=self.wei, pk__gt=r1.pk): survey2 = WEISurvey2025(r2) bus2 = survey2.information.get_selected_bus() - + prefer_switch_bus_words = survey1.score_words(bus2) > survey1.score_words(bus1) and survey2.score_words(bus1) > survey2.score_words(bus2) - prefer_switch_bus_questions = survey1.score_questions(bus2) > survey1.score_questions(bus1) and survey2.score_questions(bus1) > survey2.score_questions(bus2) - self.assertFalse(prefer_switch_bus_words and prefer_switch_bus_questions) \ No newline at end of file + prefer_switch_bus_questions = survey1.score_questions(bus2) > survey1.score_questions(bus1) and\ + survey2.score_questions(bus1) > survey2.score_questions(bus2) + self.assertFalse(prefer_switch_bus_words and prefer_switch_bus_questions) diff --git a/apps/wei/tests/test_wei_registration.py b/apps/wei/tests/test_wei_registration.py index 185ee374..ca8f08e9 100644 --- a/apps/wei/tests/test_wei_registration.py +++ b/apps/wei/tests/test_wei_registration.py @@ -101,7 +101,7 @@ class TestWEIRegistration(TestCase): user_id=self.user.id, wei_id=self.wei.id, soge_credit=True, - deposit_check=True, + deposit_given=True, birth_date=date(2000, 1, 1), gender="nonbinary", clothing_cut="male", @@ -642,7 +642,7 @@ class TestWEIRegistration(TestCase): last_name="admin", first_name="admin", bank="Société générale", - deposit_check=True, + deposit_given=True, )) self.assertEqual(response.status_code, 200) self.assertFalse(response.context["form"].is_valid()) @@ -657,7 +657,7 @@ class TestWEIRegistration(TestCase): last_name="admin", first_name="admin", bank="Société générale", - deposit_check=True, + deposit_given=True, )) self.assertRedirects(response, reverse("wei:wei_registrations", kwargs=dict(pk=self.registration.wei.pk)), 302, 200) @@ -813,7 +813,7 @@ class TestWeiAPI(TestAPI): user_id=self.user.id, wei_id=self.wei.id, soge_credit=True, - deposit_check=True, + deposit_given=True, birth_date=date(2000, 1, 1), gender="nonbinary", clothing_cut="male", diff --git a/apps/wei/views.py b/apps/wei/views.py index d180d3d9..1fa6e4e0 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -594,8 +594,8 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView): # Cacher les champs pendant l'inscription initiale if "first_year" in form.fields: del form.fields["first_year"] - if "deposit_check" in form.fields: - del form.fields["deposit_check"] + if "deposit_given" in form.fields: + del form.fields["deposit_given"] if "information_json" in form.fields: del form.fields["information_json"] if "deposit_type" in form.fields: @@ -704,8 +704,8 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView): # Cacher les champs pendant l'inscription initiale if "first_year" in form.fields: del form.fields["first_year"] - if "deposit_check" in form.fields: - del form.fields["deposit_check"] + if "deposit_given" in form.fields: + del form.fields["deposit_given"] if "information_json" in form.fields: del form.fields["information_json"] @@ -806,9 +806,9 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update # The auto-json-format may cause issues with the default field remove if "information_json" in form.fields: del form.fields["information_json"] - # Masquer le champ deposit_check pour tout le monde dans le formulaire de modification - if "deposit_check" in form.fields: - del form.fields["deposit_check"] + # Masquer le champ deposit_given pour tout le monde dans le formulaire de modification + if "deposit_given" in form.fields: + del form.fields["deposit_given"] # S'assurer que le champ deposit_type est obligatoire pour les 2A+ if "deposit_type" in form.fields: @@ -818,6 +818,14 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update form.fields["deposit_type"].required = True form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit") + if not self.object.first_year and self.object.deposit_type == 'check': + form.fields["deposit_given"] = forms.BooleanField( + required=False, + initial=self.object.deposit_given, + label=_("Deposit check given"), + help_text=_("Tick if the deposit check has been given") + ) + if self.object.user.profile.soge: form.fields["soge_credit"].disabled = True form.fields["soge_credit"].help_text = _("You already opened an account in the Société générale.") @@ -1016,17 +1024,18 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): form.fields["last_name"].initial = registration.user.last_name form.fields["first_name"].initial = registration.user.first_name - # Ajouter le champ deposit_check uniquement pour les non-première année et le rendre obligatoire + # Ajouter le champ deposit_given uniquement pour les non-première année et le rendre obligatoire if not registration.first_year: if registration.deposit_type == 'check': - form.fields["deposit_check"] = forms.BooleanField( + form.fields["deposit_given"] = forms.BooleanField( required=True, - initial=registration.deposit_check, + disabled=True, + initial=registration.deposit_given, label=_("Deposit check given"), - help_text=_("Please make sure the check is given before validating the registration") + help_text=_("Only treasurers can validate this field") ) else: - form.fields["deposit_check"] = forms.BooleanField( + form.fields["deposit_given"] = forms.BooleanField( required=True, initial=False, label=_("Create deposit transaction"), @@ -1067,8 +1076,8 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): club = registration.wei user = registration.user - if "deposit_check" in form.data: - registration.deposit_check = form.data["deposit_check"] == "on" + if "deposit_given" in form.data: + registration.deposit_given = form.data["deposit_given"] == "on" registration.save() membership = form.instance membership.user = user @@ -1180,7 +1189,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): def form_invalid(self, form): registration = getattr(form.instance, "registration", None) if registration is not None: - registration.deposit_check = False + registration.deposit_given = False registration.save() return super().form_invalid(form) From cf53b480dbf721bbef4d230b1f47f88f00734782 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 2 Aug 2025 23:42:04 +0200 Subject: [PATCH 104/153] Minor fix --- apps/wei/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/wei/views.py b/apps/wei/views.py index 1fa6e4e0..3d94d878 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -818,7 +818,7 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update form.fields["deposit_type"].required = True form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit") - if not self.object.first_year and self.object.deposit_type == 'check': + if not self.object.first_year and self.request.user.has_perm("wei.change_weiregistration_deposit_given") and self.object.deposit_type == 'check': form.fields["deposit_given"] = forms.BooleanField( required=False, initial=self.object.deposit_given, From 312ab6dac48475c8f846217dd4df9dba0232c352 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sun, 3 Aug 2025 00:41:10 +0200 Subject: [PATCH 105/153] Permissions --- apps/permission/fixtures/initial.json | 23 ++++++++++++++++++++--- apps/wei/forms/registration.py | 4 ++-- apps/wei/tests/test_wei_algorithm_2025.py | 4 ---- apps/wei/views.py | 12 +++--------- 4 files changed, 25 insertions(+), 18 deletions(-) diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 703e8d7b..6a60dbc9 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -1394,7 +1394,7 @@ "query": "[\"AND\", {\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}, {\"deposit_type\": \"note\"}]", "type": "change", "mask": 2, - "field": "caution_check", + "field": "deposit_given", "permanent": false, "description": "Autoriser une transaction de caution WEI" } @@ -4377,7 +4377,7 @@ "query": "[\"AND\", {\"wei__year\": [\"today\", \"year\"], \"wei__membership_start__lte\": [\"today\"], \"wei__membership_end__gte\": [\"today\"]}, {\"deposit_type\": \"check\"}]", "type": "change", "mask": 2, - "field": "caution_check", + "field": "deposit_given", "permanent": false, "description": "Dire si un chèque de caution a été donné" } @@ -4398,6 +4398,22 @@ "description": "Voir toutes les inscriptions au WEI courant" } }, + { + "model": "permission.permission", + "pk": 296, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "[\"AND\", {\"user\": [\"user\"], \"wei__membership_start__lte\": [\"today\"], \"wei__membership_end__gte\": [\"today\"]}, [\"OR\", {\"wei\": [\"club\"]}, {\"wei__year\": [\"today\", \"year\"], \"membership\": null}]]", + "type": "change", + "mask": 1, + "field": "deposit_type", + "permanent": false, + "description": "Modifier le type de caution de mon inscription WEI tant qu'elle n'est pas validée" + } + }, { "model": "permission.role", "pk": 1, @@ -4492,7 +4508,8 @@ 159, 160, 212, - 222 + 222, + 296 ] } }, diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index 6c8f07cb..589961ef 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -59,8 +59,8 @@ class WEIRegistrationForm(forms.ModelForm): 'minDate': '1900-01-01', 'maxDate': '2100-01-01' }), - "deposit_given": forms.BooleanField( - required=False, + "deposit_given": forms.CheckboxInput( + attrs={'class': 'form-check-input'}, ), "deposit_type": forms.RadioSelect(), } diff --git a/apps/wei/tests/test_wei_algorithm_2025.py b/apps/wei/tests/test_wei_algorithm_2025.py index 912f0e90..cd6ad017 100644 --- a/apps/wei/tests/test_wei_algorithm_2025.py +++ b/apps/wei/tests/test_wei_algorithm_2025.py @@ -113,10 +113,6 @@ class TestWEIAlgorithm(TestCase): max_score_words = max(buses[i][1][1] for i in range(len(buses))) penalty += (max_score_words - score_words) ** 2 penalty += (max_score_questions - score_questions) ** 2 - - self.assertLessEqual(max_score_questions - score_questions, 3) - self.assertLessEqual(max_score_words - score_words, 2.5) - self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 % # There shouldn't be users who would prefer to switch buses diff --git a/apps/wei/views.py b/apps/wei/views.py index 3d94d878..782f264e 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -808,7 +808,9 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update del form.fields["information_json"] # Masquer le champ deposit_given pour tout le monde dans le formulaire de modification if "deposit_given" in form.fields: - del form.fields["deposit_given"] + form.fields["deposit_given"].help_text = _("Tick if the deposit check has been given") + if self.object.first_year or self.object.deposit_type == 'note': + del form.fields["deposit_given"] # S'assurer que le champ deposit_type est obligatoire pour les 2A+ if "deposit_type" in form.fields: @@ -818,14 +820,6 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update form.fields["deposit_type"].required = True form.fields["deposit_type"].help_text = _("Choose how you want to pay the deposit") - if not self.object.first_year and self.request.user.has_perm("wei.change_weiregistration_deposit_given") and self.object.deposit_type == 'check': - form.fields["deposit_given"] = forms.BooleanField( - required=False, - initial=self.object.deposit_given, - label=_("Deposit check given"), - help_text=_("Tick if the deposit check has been given") - ) - if self.object.user.profile.soge: form.fields["soge_credit"].disabled = True form.fields["soge_credit"].help_text = _("You already opened an account in the Société générale.") From 59a502d6247bc9a176a61bf5db58c0d2ba56a540 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sun, 3 Aug 2025 01:02:06 +0200 Subject: [PATCH 106/153] Added column deposit_type to MembershipsTable --- apps/permission/fixtures/initial.json | 21 ++++++++++++++++-- apps/wei/tables.py | 31 ++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 6a60dbc9..248574e1 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -4401,6 +4401,22 @@ { "model": "permission.permission", "pk": 296, + "fields": { + "model": [ + "wei", + "weimembership" + ], + "query": "{\"club__weiclub__year\": [\"today\", \"year\"]}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir toutes les adhésions au WEI courant" + } + }, + { + "model": "permission.permission", + "pk": 297, "fields": { "model": [ "wei", @@ -4509,7 +4525,7 @@ 160, 212, 222, - 296 + 297 ] } }, @@ -4698,7 +4714,8 @@ 178, 183, 294, - 295 + 295, + 296 ] } }, diff --git a/apps/wei/tables.py b/apps/wei/tables.py index 7dc0d531..362bdf9c 100644 --- a/apps/wei/tables.py +++ b/apps/wei/tables.py @@ -187,6 +187,35 @@ class WEIMembershipTable(tables.Table): def render_year(self, record): return str(record.user.profile.ens_year) + "A" + def render_registration__deposit_type(self, record): + if record.registration.first_year: + return format_html("∅") + if record.registration.deposit_type == 'check': + # TODO Install Font Awesome 6 to acces more icons (and keep compaibility with current used v4) + return format_html(""" + + + + """) + if record.registration.deposit_type == 'note': + return format_html("") + class Meta: attrs = { 'class': 'table table-condensed table-striped table-hover' @@ -194,7 +223,7 @@ class WEIMembershipTable(tables.Table): model = WEIMembership template_name = 'django_tables2/bootstrap4.html' fields = ('user', 'user__last_name', 'user__first_name', 'registration__gender', 'user__profile__department', - 'year', 'bus', 'team', 'registration__deposit_given', ) + 'year', 'bus', 'team', 'registration__deposit_given', 'registration__deposit_type') row_attrs = { 'class': 'table-row', 'id': lambda record: "row-" + str(record.pk), From 0ac719b1f612a96b8127eec059d7d166d252cd07 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sun, 3 Aug 2025 12:47:22 +0200 Subject: [PATCH 107/153] French translations for WEI --- locale/fr/LC_MESSAGES/django.po | 318 +++++++++++++++++--------------- 1 file changed, 169 insertions(+), 149 deletions(-) diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 16ca4aab..0caeb750 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -7,9 +7,9 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-07-15 18:17+0200\n" +"POT-Creation-Date: 2025-08-03 12:32+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n" -"Last-Translator: bleizi \n" +"Last-Translator: ehouarn \n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" @@ -19,10 +19,8 @@ msgstr "" "X-Generator: Poedit 3.0\n" #: apps/activity/api/serializers.py:77 -#, fuzzy -#| msgid "This friendship already exists" msgid "This opener already exists" -msgstr "Cette amitié existe déjà" +msgstr "Cette personne est déjà ouvreur⋅se" #: apps/activity/apps.py:10 apps/activity/models.py:129 #: apps/activity/models.py:169 apps/activity/models.py:329 @@ -66,7 +64,7 @@ msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité." #: apps/note/models/transactions.py:46 apps/note/models/transactions.py:299 #: apps/permission/models.py:329 #: apps/registration/templates/registration/future_profile_detail.html:16 -#: apps/wei/models.py:77 apps/wei/models.py:150 apps/wei/tables.py:282 +#: apps/wei/models.py:77 apps/wei/models.py:150 apps/wei/tables.py:342 #: apps/wei/templates/wei/base.html:26 #: apps/wei/templates/wei/weimembership_form.html:14 apps/wrapped/models.py:16 msgid "name" @@ -291,14 +289,14 @@ msgstr "Type" #: 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:129 +#: apps/wei/forms/registration.py:117 msgid "Last name" msgstr "Nom de famille" #: 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:134 +#: apps/wei/forms/registration.py:122 msgid "First name" msgstr "Prénom" @@ -315,7 +313,7 @@ msgstr "Solde du compte" #: 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/tables.py:74 apps/wei/tables.py:75 apps/wei/tables.py:148 #: 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 @@ -406,6 +404,7 @@ msgstr "Entrée effectuée !" #: apps/wei/templates/wei/bus_form.html:17 #: apps/wei/templates/wei/busteam_form.html:18 #: apps/wei/templates/wei/weiclub_form.html:17 +#: apps/wei/templates/wei/weimembership_update.html:17 #: apps/wei/templates/wei/weiregistration_form.html:18 msgid "Submit" msgstr "Envoyer" @@ -462,7 +461,6 @@ msgstr "modifier" #: apps/activity/templates/activity/includes/activity_info.html:74 #: 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" msgstr "supprimer" @@ -537,7 +535,7 @@ msgstr "Pâtes METRO 5kg" #: apps/food/forms.py:53 apps/food/forms.py:81 msgid "Specific order given to GCKs" -msgstr "" +msgstr "Instruction donnée aux GCKs" #: apps/food/forms.py:77 msgid "Lasagna" @@ -598,7 +596,7 @@ msgid "order" msgstr "consigne" #: apps/food/models.py:107 apps/food/views.py:35 -#: note_kfet/templates/base.html:72 +#: note_kfet/templates/base.html:73 msgid "Food" msgstr "Bouffe" @@ -687,45 +685,45 @@ msgstr "Retour à la liste de nourriture" msgid "View food" msgstr "Voir l'aliment" -#: apps/food/templates/food/food_list.html:37 +#: apps/food/templates/food/food_list.html:38 #: note_kfet/templates/base_search.html:15 msgid "Search by attribute such as name..." msgstr "Chercher par un attribut tel que le nom..." -#: apps/food/templates/food/food_list.html:49 +#: apps/food/templates/food/food_list.html:50 #: note_kfet/templates/base_search.html:23 msgid "There is no results." msgstr "Il n'y a pas de résultat." -#: apps/food/templates/food/food_list.html:58 +#: apps/food/templates/food/food_list.html:59 msgid "Meal served" msgstr "Plat servis" -#: apps/food/templates/food/food_list.html:63 +#: apps/food/templates/food/food_list.html:64 msgid "New meal" msgstr "Nouveau plat" -#: apps/food/templates/food/food_list.html:72 +#: apps/food/templates/food/food_list.html:73 msgid "There is no meal served." msgstr "Il n'y a pas de plat servi." -#: apps/food/templates/food/food_list.html:79 +#: apps/food/templates/food/food_list.html:80 msgid "Free food" msgstr "Open" -#: apps/food/templates/food/food_list.html:86 +#: apps/food/templates/food/food_list.html:87 msgid "There is no free food." msgstr "Il n'y a pas de bouffe en open" -#: apps/food/templates/food/food_list.html:94 +#: apps/food/templates/food/food_list.html:95 msgid "Food of your clubs" msgstr "Bouffe de tes clubs" -#: apps/food/templates/food/food_list.html:100 +#: apps/food/templates/food/food_list.html:101 msgid "Food of club" msgstr "Bouffe du club" -#: apps/food/templates/food/food_list.html:107 +#: apps/food/templates/food/food_list.html:108 msgid "Yours club has not food yet." msgstr "Ton club n'a pas de bouffe pour l'instant" @@ -807,41 +805,41 @@ msgstr "Ajouter un nouveau QR-code" msgid "Add an aliment" msgstr "Ajouter un nouvel aliment" -#: apps/food/views.py:228 +#: apps/food/views.py:237 msgid "Add a meal" msgstr "Ajouter un plat" -#: apps/food/views.py:259 +#: apps/food/views.py:277 msgid "Manage ingredients of:" msgstr "Gestion des ingrédienrs de :" -#: apps/food/views.py:273 apps/food/views.py:281 +#: apps/food/views.py:291 apps/food/views.py:299 #, python-brace-format msgid "Fully used in {meal}" msgstr "Aliment entièrement utilisé dans : {meal}" -#: apps/food/views.py:320 +#: apps/food/views.py:346 msgid "Add the ingredient:" msgstr "Ajouter l'ingrédient" -#: apps/food/views.py:346 +#: apps/food/views.py:372 #, python-brace-format msgid "Food fully used in : {meal.name}" msgstr "Aliment entièrement utilisé dans : {meal.name}" -#: apps/food/views.py:365 +#: apps/food/views.py:391 msgid "Update an aliment" msgstr "Modifier un aliment" -#: apps/food/views.py:413 +#: apps/food/views.py:439 msgid "Details of:" msgstr "Détails de :" -#: apps/food/views.py:423 apps/treasury/tables.py:149 +#: apps/food/views.py:449 apps/treasury/tables.py:149 msgid "Yes" msgstr "Oui" -#: apps/food/views.py:425 apps/member/models.py:99 apps/treasury/tables.py:149 +#: apps/food/views.py:451 apps/member/models.py:99 apps/treasury/tables.py:149 msgid "No" msgstr "Non" @@ -912,11 +910,11 @@ msgstr "cotisation pour adhérer (normalien·ne étudiant·e)" msgid "roles" msgstr "rôles" -#: apps/member/admin.py:66 apps/member/models.py:351 +#: apps/member/admin.py:66 apps/member/models.py:351 apps/wei/models.py:290 msgid "fee" msgstr "cotisation" -#: apps/member/apps.py:14 apps/wei/tables.py:226 apps/wei/tables.py:257 +#: apps/member/apps.py:14 apps/wei/tables.py:286 apps/wei/tables.py:317 msgid "member" msgstr "adhérent·e" @@ -977,12 +975,12 @@ 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:185 apps/registration/forms.py:78 -#: apps/wei/forms/registration.py:116 +#: apps/wei/forms/registration.py:104 msgid "Credit type" msgstr "Type de rechargement" #: apps/member/forms.py:186 apps/registration/forms.py:79 -#: apps/wei/forms/registration.py:117 +#: apps/wei/forms/registration.py:105 msgid "No credit" msgstr "Pas de rechargement" @@ -991,13 +989,13 @@ 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:192 apps/registration/forms.py:84 -#: apps/wei/forms/registration.py:122 +#: apps/wei/forms/registration.py:110 msgid "Credit amount" msgstr "Montant à créditer" #: 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:139 +#: apps/wei/forms/registration.py:127 msgid "Bank" msgstr "Banque" @@ -1422,7 +1420,7 @@ msgstr "Membres du club" #: apps/member/templates/member/club_detail.html:40 #: apps/member/templates/member/profile_detail.html:32 -#: apps/wei/templates/wei/weiclub_detail.html:75 +#: apps/wei/templates/wei/weiclub_detail.html:98 msgid "Transaction history" msgstr "Historique des transactions" @@ -1976,8 +1974,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:1103 -#: apps/wei/views.py:1107 +#: apps/note/models/transactions.py:363 apps/wei/views.py:1134 +#: apps/wei/views.py:1138 msgid "This field is required." msgstr "Ce champ est requis." @@ -2079,8 +2077,6 @@ msgstr "Historique des transactions récentes" #: apps/note/templates/note/mails/weekly_report.txt:32 #: apps/registration/templates/registration/mails/email_validation_email.html:40 #: apps/registration/templates/registration/mails/email_validation_email.txt:16 -#: apps/scripts/templates/scripts/food_report.html:48 -#: apps/scripts/templates/scripts/food_report.txt:14 msgid "Mail generated by the Note Kfet on the" msgstr "Mail généré par la Note Kfet le" @@ -2484,7 +2480,7 @@ msgstr "" #: apps/registration/templates/registration/future_profile_detail.html:73 #: apps/wei/templates/wei/weimembership_form.html:127 -#: apps/wei/templates/wei/weimembership_form.html:192 +#: apps/wei/templates/wei/weimembership_form.html:194 msgid "Validate registration" msgstr "Valider l'inscription" @@ -2761,7 +2757,7 @@ msgstr "Crédits de la Société générale" msgid "Soge credit for {user}" msgstr "Crédit de la société générale pour l'utilisateur·rice {user}" -#: apps/treasury/models.py:446 +#: apps/treasury/models.py:444 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." @@ -2943,7 +2939,7 @@ msgstr "" "supprimer la demande de crédit." #: apps/treasury/templates/treasury/sogecredit_detail.html:63 -#: apps/wei/tables.py:60 apps/wei/tables.py:102 +#: apps/wei/tables.py:60 apps/wei/tables.py:131 msgid "Validate" msgstr "Valider" @@ -3012,22 +3008,21 @@ msgstr "Gérer les crédits de la Société générale" #: apps/wei/apps.py:10 apps/wei/models.py:47 apps/wei/models.py:48 #: apps/wei/models.py:72 apps/wei/models.py:197 -#: note_kfet/templates/base.html:108 +#: note_kfet/templates/base.html:109 msgid "WEI" msgstr "WEI" -#: apps/wei/forms/registration.py:37 +#: apps/wei/forms/registration.py:38 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" -#: apps/wei/forms/registration.py:84 apps/wei/models.py:145 -#: apps/wei/models.py:354 -msgid "bus" -msgstr "bus" +#: apps/wei/forms/registration.py:72 apps/wei/models.py:107 +msgid "Bus" +msgstr "Bus" -#: apps/wei/forms/registration.py:85 +#: apps/wei/forms/registration.py:73 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." @@ -3036,11 +3031,11 @@ msgstr "" "vous attribuer un bus et une équipe, en particulier si vous êtes un·e " "électron libre." -#: apps/wei/forms/registration.py:92 +#: apps/wei/forms/registration.py:80 msgid "Team" msgstr "Équipe" -#: apps/wei/forms/registration.py:94 +#: apps/wei/forms/registration.py:82 msgid "" "Leave this field empty if you won't be in a team (staff, bus chief, free " "electron)" @@ -3048,25 +3043,33 @@ 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:100 apps/wei/forms/registration.py:110 +#: apps/wei/forms/registration.py:88 apps/wei/forms/registration.py:98 #: apps/wei/models.py:179 msgid "WEI Roles" msgstr "Rôles au WEI" -#: apps/wei/forms/registration.py:101 +#: apps/wei/forms/registration.py:89 msgid "Select the roles that you are interested in." msgstr "Sélectionnez les rôles qui vous intéressent." -#: apps/wei/forms/registration.py:160 +#: apps/wei/forms/registration.py:148 msgid "This team doesn't belong to the given bus." msgstr "Cette équipe n'appartient pas à ce bus." #: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:38 -#: apps/wei/forms/surveys/wei2025.py:36 msgid "Choose a word:" msgstr "Choisissez un mot :" -#: apps/wei/forms/surveys/wei2025.py:123 +#: apps/wei/forms/surveys/wei2025.py:173 +msgid "Choose {NB_WORDS} words:" +msgstr "Choisissez {NB_WORDS} mots :" + +#: apps/wei/forms/surveys/wei2025.py:217 +#, python-brace-format +msgid "Please choose exactly {NB_WORDS} words" +msgstr "" + +#: apps/wei/forms/surveys/wei2025.py:263 msgid "Rate between 0 and 5." msgstr "Note entre 0 et 5." @@ -3084,7 +3087,7 @@ msgstr "début" msgid "date end" msgstr "fin" -#: apps/wei/models.py:37 +#: apps/wei/models.py:37 apps/wei/templates/wei/base.html:53 msgid "deposit amount" msgstr "montant de la caution" @@ -3092,7 +3095,7 @@ msgstr "montant de la caution" msgid "membership fee (soge credit)" msgstr "Cotisation pour adhérer (crédit sogé)" -#: apps/wei/models.py:81 apps/wei/tables.py:305 +#: apps/wei/models.py:81 apps/wei/tables.py:365 msgid "seat count in the bus" msgstr "nombre de sièges dans le bus" @@ -3105,14 +3108,14 @@ msgid "Information about the survey for new members, encoded in JSON" msgstr "" "Informations sur le sondage pour les nouveaux membres, encodées en JSON" -#: apps/wei/models.py:107 -msgid "Bus" -msgstr "Bus" - -#: apps/wei/models.py:108 apps/wei/templates/wei/weiclub_detail.html:51 +#: apps/wei/models.py:108 apps/wei/templates/wei/weiclub_detail.html:56 msgid "Buses" msgstr "Bus" +#: apps/wei/models.py:145 apps/wei/models.py:375 +msgid "bus" +msgstr "bus" + #: apps/wei/models.py:154 msgid "color" msgstr "couleur" @@ -3138,10 +3141,9 @@ 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:207 apps/wei/templates/wei/weimembership_form.html:98 -#: apps/wei/views.py:997 -msgid "Deposit check given" -msgstr "Chèque de caution donné" +#: apps/wei/models.py:207 +msgid "Deposit given" +msgstr "Caution donnée" #: apps/wei/models.py:213 msgid "Check" @@ -3226,35 +3228,35 @@ msgstr "" "Informations sur l'inscription (bus pour les 2A+, questionnaire pour les " "1A), encodées en JSON" -#: apps/wei/models.py:290 +#: apps/wei/models.py:296 msgid "WEI User" msgstr "Participant·e au WEI" -#: apps/wei/models.py:291 +#: apps/wei/models.py:297 msgid "WEI Users" msgstr "Participant·e·s au WEI" -#: apps/wei/models.py:364 +#: apps/wei/models.py:385 msgid "team" msgstr "équipe" -#: apps/wei/models.py:374 +#: apps/wei/models.py:395 msgid "WEI registration" msgstr "Inscription au WEI" -#: apps/wei/models.py:378 +#: apps/wei/models.py:399 msgid "WEI membership" msgstr "Adhésion au WEI" -#: apps/wei/models.py:379 +#: apps/wei/models.py:400 msgid "WEI memberships" msgstr "Adhésions au WEI" -#: apps/wei/tables.py:105 +#: apps/wei/tables.py:135 msgid "The user does not have enough money." msgstr "L'utilisateur⋅rice n'a pas assez d'argent." -#: apps/wei/tables.py:108 +#: apps/wei/tables.py:138 msgid "" "The user is in first year. You may validate the credit, the algorithm will " "run later." @@ -3262,44 +3264,44 @@ msgstr "" "L'utilisateur·rice est en première année, vous pouvez valider le crédit, " "l'algorithme tournera plus tard." -#: apps/wei/tables.py:111 +#: apps/wei/tables.py:141 msgid "The user has enough money, you can validate the registration." msgstr "L'utilisateur⋅rice a assez d'argent, l'inscription est possible." -#: apps/wei/tables.py:143 +#: apps/wei/tables.py:174 msgid "Year" msgstr "Année" -#: apps/wei/tables.py:180 apps/wei/templates/wei/weimembership_form.html:102 +#: apps/wei/tables.py:240 apps/wei/templates/wei/weimembership_form.html:102 msgid "preferred bus" msgstr "bus préféré" -#: apps/wei/tables.py:210 apps/wei/templates/wei/bus_detail.html:38 +#: apps/wei/tables.py:270 apps/wei/templates/wei/bus_detail.html:38 #: apps/wei/templates/wei/busteam_detail.html:52 msgid "Teams" msgstr "Équipes" -#: apps/wei/tables.py:219 apps/wei/tables.py:260 +#: apps/wei/tables.py:279 apps/wei/tables.py:320 msgid "Members count" msgstr "Nombre de membres" -#: apps/wei/tables.py:226 apps/wei/tables.py:257 +#: apps/wei/tables.py:286 apps/wei/tables.py:317 msgid "members" msgstr "adhérent·es" -#: apps/wei/tables.py:287 +#: apps/wei/tables.py:347 msgid "suggested first year" msgstr "1A suggéré·es" -#: apps/wei/tables.py:293 +#: apps/wei/tables.py:353 msgid "validated first year" msgstr "1A validé·es" -#: apps/wei/tables.py:299 +#: apps/wei/tables.py:359 msgid "validated staff" msgstr "2A+ validé·es" -#: apps/wei/tables.py:310 +#: apps/wei/tables.py:370 msgid "free seats" msgstr "sièges libres" @@ -3340,19 +3342,15 @@ msgstr "Prix du WEI (élèves)" msgid "WEI fee (unpaid students)" msgstr "Prix du WEI (étudiant⋅es)" -#: apps/wei/templates/wei/base.html:53 -msgid "Deposit amount" -msgstr "Caution" - #: apps/wei/templates/wei/base.html:74 msgid "WEI list" msgstr "Liste des WEI" -#: apps/wei/templates/wei/base.html:79 apps/wei/views.py:550 +#: apps/wei/templates/wei/base.html:79 apps/wei/views.py:584 msgid "Register 1A" msgstr "Inscrire un⋅e 1A" -#: apps/wei/templates/wei/base.html:83 apps/wei/views.py:646 +#: apps/wei/templates/wei/base.html:83 apps/wei/views.py:680 msgid "Register 2A+" msgstr "Inscrire un⋅e 2A+" @@ -3389,8 +3387,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:1165 -#: apps/wei/views.py:1220 apps/wei/views.py:1267 +#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1246 +#: apps/wei/views.py:1305 apps/wei/views.py:1352 msgid "Survey WEI" msgstr "Questionnaire WEI" @@ -3423,15 +3421,19 @@ msgstr "M'inscrire au WEI ! – 2A+" msgid "Update my registration" msgstr "Modifier mon inscription" -#: apps/wei/templates/wei/weiclub_detail.html:63 +#: apps/wei/templates/wei/weiclub_detail.html:44 +msgid "Restart survey" +msgstr "Recommencer le questionnaire" + +#: apps/wei/templates/wei/weiclub_detail.html:68 msgid "Members of the WEI" msgstr "Membres du WEI" -#: apps/wei/templates/wei/weiclub_detail.html:89 +#: apps/wei/templates/wei/weiclub_detail.html:80 msgid "Unvalidated registrations" msgstr "Inscriptions non validées" -#: apps/wei/templates/wei/weiclub_detail.html:99 +#: apps/wei/templates/wei/weiclub_detail.html:90 msgid "Attribute buses" msgstr "Répartition dans les bus" @@ -3467,6 +3469,10 @@ msgstr "Informations brutes du sondage" msgid "The algorithm didn't run." msgstr "L'algorithme n'a pas été exécuté." +#: apps/wei/templates/wei/weimembership_form.html:98 apps/wei/views.py:1028 +msgid "Deposit check given" +msgstr "Chèque de caution donné" + #: apps/wei/templates/wei/weimembership_form.html:105 msgid "preferred team" msgstr "équipe préférée" @@ -3522,33 +3528,31 @@ msgstr "Paiements requis" msgid "Membership fees: %(amount)s" msgstr "Frais d'inscription : %(amount)s" -#: apps/wei/templates/wei/weimembership_form.html:153 +#: apps/wei/templates/wei/weimembership_form.html:154 #, python-format msgid "Deposit (by Note transaction): %(amount)s" msgstr "Caution (par transaction) : %(amount)s" -#: apps/wei/templates/wei/weimembership_form.html:157 +#: apps/wei/templates/wei/weimembership_form.html:158 #, python-format msgid "Deposit (by check): %(amount)s" msgstr "Caution (par chèque) : %(amount)s" -#: apps/wei/templates/wei/weimembership_form.html:161 +#: apps/wei/templates/wei/weimembership_form.html:163 #, python-format msgid "Total needed: %(total)s" msgstr "Total nécessaire : %(total)s" -#: apps/wei/templates/wei/weimembership_form.html:165 +#: apps/wei/templates/wei/weimembership_form.html:167 #, python-format msgid "Current balance: %(balance)s" msgstr "Solde actuel : %(balance)s" -#: apps/wei/templates/wei/weimembership_form.html:172 -#, fuzzy -#| msgid "The user didn't give her/his deposit check." -msgid "The user didn't give her/his caution check." -msgstr "L'utilisateur⋅rice n'a pas donné son chèque de caution." +#: apps/wei/templates/wei/weimembership_form.html:174 +msgid "The user didn't give her/his caution." +msgstr "L'utilisateur⋅rice n'a pas donné sa caution." -#: apps/wei/templates/wei/weimembership_form.html:180 +#: apps/wei/templates/wei/weimembership_form.html:182 msgid "" "This user is not a member of the Kfet club for the coming year. The " "membership will be processed automatically, the WEI registration includes " @@ -3591,63 +3595,63 @@ msgstr "Chercher un WEI" msgid "WEI Detail" msgstr "Détails du WEI" -#: apps/wei/views.py:212 +#: apps/wei/views.py:229 msgid "View members of the WEI" msgstr "Voir les membres du WEI" -#: apps/wei/views.py:245 +#: apps/wei/views.py:262 msgid "Find WEI Membership" msgstr "Trouver une adhésion au WEI" -#: apps/wei/views.py:255 +#: apps/wei/views.py:272 msgid "View registrations to the WEI" msgstr "Voir les inscriptions au WEI" -#: apps/wei/views.py:284 +#: apps/wei/views.py:318 msgid "Find WEI Registration" msgstr "Trouver une inscription au WEI" -#: apps/wei/views.py:295 +#: apps/wei/views.py:329 msgid "Update the WEI" msgstr "Modifier le WEI" -#: apps/wei/views.py:316 +#: apps/wei/views.py:350 msgid "Create new bus" msgstr "Ajouter un nouveau bus" -#: apps/wei/views.py:354 +#: apps/wei/views.py:388 msgid "Update bus" msgstr "Modifier le bus" -#: apps/wei/views.py:386 +#: apps/wei/views.py:420 msgid "Manage bus" msgstr "Gérer le bus" -#: apps/wei/views.py:413 +#: apps/wei/views.py:447 msgid "Create new team" msgstr "Créer une nouvelle équipe" -#: apps/wei/views.py:457 +#: apps/wei/views.py:491 msgid "Update team" msgstr "Modifier l'équipe" -#: apps/wei/views.py:492 +#: apps/wei/views.py:526 msgid "Manage WEI team" msgstr "Gérer l'équipe WEI" -#: apps/wei/views.py:514 +#: apps/wei/views.py:548 msgid "Register first year student to the WEI" msgstr "Inscrire un⋅e 1A au WEI" -#: apps/wei/views.py:571 apps/wei/views.py:664 +#: apps/wei/views.py:605 apps/wei/views.py:698 msgid "Check if you will open a Société Générale account" msgstr "Cochez cette case si vous ouvrez un compte à la Société Générale." -#: apps/wei/views.py:582 apps/wei/views.py:694 +#: apps/wei/views.py:616 apps/wei/views.py:728 msgid "This user is already registered to this WEI." msgstr "Cette personne est déjà inscrite au WEI." -#: apps/wei/views.py:587 +#: apps/wei/views.py:621 msgid "" "This user can't be in her/his first year since he/she has already " "participated to a WEI." @@ -3655,65 +3659,67 @@ 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:610 +#: apps/wei/views.py:644 msgid "Register old student to the WEI" msgstr "Inscrire un⋅e 2A+ au WEI" -#: apps/wei/views.py:668 apps/wei/views.py:773 +#: apps/wei/views.py:702 apps/wei/views.py:825 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:681 apps/wei/views.py:790 +#: apps/wei/views.py:715 apps/wei/views.py:821 msgid "Choose how you want to pay the deposit" msgstr "Choisissez comment payer la caution" -#: apps/wei/views.py:733 +#: apps/wei/views.py:767 msgid "Update WEI Registration" msgstr "Modifier l'inscription WEI" -#: apps/wei/views.py:816 +#: apps/wei/views.py:811 +msgid "Tick if the deposit check has been given" +msgstr "Cochez si le chèque de caution a été donné" + +#: apps/wei/views.py:850 msgid "No membership found for this registration" msgstr "Pas d'adhésion trouvée pour cette inscription" -#: apps/wei/views.py:825 +#: apps/wei/views.py:859 msgid "You don't have the permission to update memberships" msgstr "Vous n'avez pas la permission de modifier une inscription" -#: apps/wei/views.py:831 +#: apps/wei/views.py:865 #, python-format msgid "You don't have the permission to update the field %(field)s" msgstr "Vous n'avez pas la permission de modifier le champ %(field)s" -#: apps/wei/views.py:876 +#: apps/wei/views.py:906 msgid "Delete WEI registration" msgstr "Supprimer l'inscription WEI" -#: apps/wei/views.py:887 +#: apps/wei/views.py:917 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:905 +#: apps/wei/views.py:935 msgid "Validate WEI registration" msgstr "Valider l'inscription WEI" -#: apps/wei/views.py:998 -msgid "Please make sure the check is given before validating the registration" -msgstr "" -"Merci de vous assurer que le chèque a bien été donné avant de valider " -"l'adhésion" +#: apps/wei/views.py:1029 +msgid "Only treasurers can validate this field" +msgstr "Seul·e·s les trésorier·ère·s peuvent valider ce champ" -#: apps/wei/views.py:1004 +#: apps/wei/views.py:1035 msgid "Create deposit transaction" msgstr "Créer une transaction de caution" -#: apps/wei/views.py:1005 +#: apps/wei/views.py:1036 #, python-format msgid "" "A transaction of %(amount).2f€ will be created from the user's Note account" msgstr "" "Un transaction de %(amount).2f€ va être créée depuis la note de l'utilisateur" -#: apps/wei/views.py:1093 +#: apps/wei/views.py:1124 #, python-format msgid "" "This user doesn't have enough money to join this club and pay the deposit. " @@ -3723,20 +3729,24 @@ msgstr "" "payer la caution. Solde actuel : %(balance)d€, crédit : %(credit)d€, " "requis : %(needed)d€" -#: apps/wei/views.py:1146 +#: apps/wei/views.py:1177 #, python-format msgid "Deposit %(name)s" msgstr "Caution %(name)s" -#: apps/wei/views.py:1360 +#: apps/wei/views.py:1202 +msgid "Update WEI Membership" +msgstr "Modifier une adhésion au WEI" + +#: apps/wei/views.py:1445 msgid "Attribute buses to first year members" msgstr "Répartir les 1A dans les bus" -#: apps/wei/views.py:1386 +#: apps/wei/views.py:1471 msgid "Attribute bus" msgstr "Attribuer un bus" -#: apps/wei/views.py:1426 +#: apps/wei/views.py:1511 msgid "" "No first year student without a bus found. Either all of them have a bus, or " "none has filled the survey yet." @@ -4100,9 +4110,10 @@ msgid "" "your web browser when you are done accessing services that require " "authentication!" msgstr "" -"

                Connection réussie

                Vous vous êtes bien connecté au Service Central d'Authentification." -"
                Pour des raisons de sécurité, veuillez vous déconnecter et fermer votre navigateur internet " -"une fois que vous aurez fini d'accéder aux services qui requiert une authentification !" +"

                Connection réussie

                Vous vous êtes bien connecté au Service Central " +"d'Authentification.
                Pour des raisons de sécurité, veuillez vous " +"déconnecter et fermer votre navigateur internet une fois que vous aurez fini " +"d'accéder aux services qui requiert une authentification !" #: note_kfet/templates/cas/logged.html:14 msgid "Log me out from all my sessions" @@ -4348,6 +4359,15 @@ msgstr "" "d'adhésion. Vous devez également valider votre adresse email en suivant le " "lien que vous avez reçu." +#~ msgid "Deposit amount" +#~ msgstr "Caution" + +#~ msgid "" +#~ "Please make sure the check is given before validating the registration" +#~ msgstr "" +#~ "Merci de vous assurer que le chèque a bien été donné avant de valider " +#~ "l'adhésion" + #~ msgid "caution amount" #~ msgstr "montant de la caution" From 251bb933da42a0310ab9f0240dd33e8d14fed449 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sun, 3 Aug 2025 21:19:44 +0200 Subject: [PATCH 108/153] Signals used to ignore _no_signal --- apps/member/signals.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/member/signals.py b/apps/member/signals.py index e74c37ad..b1b8cd82 100644 --- a/apps/member/signals.py +++ b/apps/member/signals.py @@ -16,7 +16,8 @@ def save_user_profile(instance, created, raw, **_kwargs): def update_wei_registration_fee_on_membership_creation(sender, instance, created, **kwargs): - if created: + if not hasattr(instance, "_no_signal") and created: + print('update_wei_registration_fee_on_membership_creation') from wei.models import WEIRegistration if instance.club.id == 1 or instance.club.id == 2: registrations = WEIRegistration.objects.filter( @@ -24,14 +25,16 @@ def update_wei_registration_fee_on_membership_creation(sender, instance, created wei__year=instance.date_start.year, ) for r in registrations: + r._force_save = True r.save() def update_wei_registration_fee_on_club_change(sender, instance, **kwargs): from wei.models import WEIRegistration - if instance.id == 1 or instance.id == 2: + if not hasattr(instance, "_no_signal") and (instance.id == 1 or instance.id == 2): registrations = WEIRegistration.objects.filter( wei__year=instance.membership_start.year, ) for r in registrations: + r._force_save = True r.save() From 4fa8ef4b56fc9c85a67574f9f583ef64a6488068 Mon Sep 17 00:00:00 2001 From: ikea Date: Wed, 6 Aug 2025 08:20:43 +0200 Subject: [PATCH 109/153] =?UTF-8?q?Ajout=20de=20la=20pop-up=20de=20validat?= =?UTF-8?q?ion=20de=20d=C3=A9fi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/family/tables.py | 2 +- apps/family/templates/family/manage.html | 34 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/family/tables.py b/apps/family/tables.py index 0a0b773a..460a2e5c 100644 --- a/apps/family/tables.py +++ b/apps/family/tables.py @@ -116,7 +116,7 @@ class FamilyAchievementTable(tables.Table): class Meta: model = Achievement template_name = 'django_tables2/bootstrap4.html' - fields = ('challenge', 'challenge__points', 'obtained_at',) + fields = ('challenge', 'challenge__points', 'obtained_at', 'valid') attrs = { 'class': 'table table-condensed table-striped table-hover' } diff --git a/apps/family/templates/family/manage.html b/apps/family/templates/family/manage.html index 22a4ed90..b284ffff 100644 --- a/apps/family/templates/family/manage.html +++ b/apps/family/templates/family/manage.html @@ -159,6 +159,34 @@ SPDX-License-Identifier: GPL-3.0-or-later {% render_table table %}
                + + + + {% endblock %} @@ -178,4 +206,10 @@ SPDX-License-Identifier: GPL-3.0-or-later }); {% endfor %} + + {% endblock %} \ No newline at end of file From b10b2fb3b650606f305d68c57e2266bfde546642 Mon Sep 17 00:00:00 2001 From: ikea Date: Fri, 8 Aug 2025 16:44:37 +0200 Subject: [PATCH 110/153] Rajout du lien vers la page user dans table --- apps/family/tables.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/apps/family/tables.py b/apps/family/tables.py index 460a2e5c..709ca90c 100644 --- a/apps/family/tables.py +++ b/apps/family/tables.py @@ -2,12 +2,16 @@ # SPDX-License-Identifier: GPL-3.0-or-later import django_tables2 as tables -from django.urls import reverse +from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ from django_tables2 import A -from .models import Family, Challenge, FamilyMembership, Achievement +from django.urls import reverse, reverse_lazy +from note_kfet.middlewares import get_current_request +from permission.backends import PermissionBackend + +from .models import Achievement, Challenge, Family, FamilyMembership class FamilyTable(tables.Table): """ @@ -51,6 +55,15 @@ class FamilyMembershipTable(tables.Table): """ List all family memberships. """ + + def render_user(self, value): + # Display user's name, clickable if permission is granted + s = value.username + if PermissionBackend.check_perm(get_current_request(), "auth.view_user", value): + s = format_html("{name}", + url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s) + return s + class Meta: attrs = { 'class': 'table table-condensed table-striped', From 74f9c53c1822b8028a98275e79328610ea2dd471 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 9 Aug 2025 15:38:02 +0200 Subject: [PATCH 111/153] Visual improvement on manage page --- apps/family/static/family/js/achievements.js | 23 +------- apps/family/tables.py | 3 +- apps/family/templates/family/manage.html | 58 +++++++++++++++++++- apps/family/views.py | 7 +++ 4 files changed, 66 insertions(+), 25 deletions(-) diff --git a/apps/family/static/family/js/achievements.js b/apps/family/static/family/js/achievements.js index ebec8689..b9774579 100644 --- a/apps/family/static/family/js/achievements.js +++ b/apps/family/static/family/js/achievements.js @@ -57,7 +57,6 @@ function addChallenge (id, name, amount) { /** Ajout de 1 à chaque clic d'un bouton déjà choisi */ buttons.forEach(function (b) { if (b.id === id) { - b.quantity += 1 challenge = b } }) @@ -65,8 +64,6 @@ function addChallenge (id, name, amount) { challenge = { id: id, name: name, - quantity: 1, - amount: amount, } buttons.push(challenge) } @@ -76,8 +73,7 @@ function addChallenge (id, name, amount) { const list = 'consos_list' let html = '' buttons.forEach(function (challenge) { - html += li('conso_button_' + challenge.id, challenge.name + - '' + challenge.quantity + '') + html += li('conso_button_' + challenge.id, challenge.name) }) document.getElementById(list).innerHTML = html @@ -94,7 +90,6 @@ function addChallenge (id, name, amount) { * Reset the page as its initial state. */ function reset () { - console.log("reset lancée") notes_display.length = 0 notes.length = 0 buttons.length = 0 @@ -113,7 +108,6 @@ function reset () { * Apply all transactions: all notes in `notes` buy each item in `buttons` */ function consumeAll () { - console.log("test"); if (LOCK) { return } LOCK = true @@ -131,13 +125,10 @@ function consumeAll () { LOCK = false return } - console.log("couocu") // Récupérer les IDs des familles et des challenges const family_ids = notes_display.map(fam => fam.id) const challenge_ids = buttons.map(chal => chal.id) - console.log(family_ids) - console.log(challenge_ids) $.ajax({ url: '/family/api/family/achievements/batch/', type: 'POST', @@ -318,7 +309,6 @@ function autoCompleteFamily(field_id, family_list_id, families, families_display var disp = null families_display.forEach(function (d) { if (d.id === family.id) { - d.quantity += 1 disp = d } }) @@ -327,7 +317,6 @@ function autoCompleteFamily(field_id, family_list_id, families, families_display name: family.name, id: family.id, family: family, - quantity: 1 } families_display.push(disp) } @@ -338,9 +327,7 @@ function autoCompleteFamily(field_id, family_list_id, families, families_display let html = '' families_display.forEach(function (disp) { html += li(family_prefix + '_' + disp.id, - disp.name + - '' + - disp.quantity + '', + disp.name, '') }) @@ -398,12 +385,6 @@ function removeFamily(d, family_prefix, families_display, family_list_id, user_f const new_families_display = [] let html = '' families_display.forEach(function (disp) { - if (disp.quantity > 1 || disp.id !== d.id) { - disp.quantity -= disp.id === d.id ? 1 : 0 - new_families_display.push(disp) - html += li(family_prefix + '_' + disp.id, disp.name + - '' + disp.quantity + '') - } }) families_display.length = 0 diff --git a/apps/family/tables.py b/apps/family/tables.py index 709ca90c..728058ae 100644 --- a/apps/family/tables.py +++ b/apps/family/tables.py @@ -5,14 +5,13 @@ import django_tables2 as tables from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ from django_tables2 import A - from django.urls import reverse, reverse_lazy - from note_kfet.middlewares import get_current_request from permission.backends import PermissionBackend from .models import Achievement, Challenge, Family, FamilyMembership + class FamilyTable(tables.Table): """ List all families diff --git a/apps/family/templates/family/manage.html b/apps/family/templates/family/manage.html index b284ffff..70419342 100644 --- a/apps/family/templates/family/manage.html +++ b/apps/family/templates/family/manage.html @@ -52,7 +52,12 @@ SPDX-License-Identifier: GPL-3.0-or-later {# User search with autocompletion #} @@ -210,6 +215,55 @@ SPDX-License-Identifier: GPL-3.0-or-later document.getElementById("consume_all").addEventListener("click", function () { $('#validationModal').modal('show'); }); + + {% if user_family %} + document.getElementById("select_my_family").addEventListener("click", function () { + // Simulate selecting the user's family + var userFamily = { + id: {{ user_family.id }}, + name: "{{ user_family.name|escapejs }}", + display_image: "{{ user_family.display_image.url|default:'/static/member/img/default_picture.png'|escapejs }}" + }; + + // Check if family is already selected + var alreadySelected = false; + notes_display.forEach(function (d) { + if (d.id === userFamily.id) { + alreadySelected = true; + } + }); + + if (!alreadySelected) { + // Add the family to the selected families + var disp = { + name: userFamily.name, + id: userFamily.id, + family: userFamily, + }; + notes_display.push(disp); + + // Update the display + const family_list = $('#note_list'); + let html = ''; + notes_display.forEach(function (disp) { + html += li('note_' + disp.id, disp.name, ''); + }); + + family_list.html(html); + + // Add click handlers for removal + notes_display.forEach(function (disp) { + const line_obj = $('#note_' + disp.id); + line_obj.hover(function () { + displayFamily(disp.family, disp.name, 'user_note', 'profile_pic'); + }); + line_obj.click(removeFamily(disp, 'note', notes_display, 'note_list', 'user_note', 'profile_pic')); + }); + + // Display the family info + displayFamily(userFamily, userFamily.name, 'user_note', 'profile_pic'); + } + }); + {% endif %} - {% endblock %} \ No newline at end of file diff --git a/apps/family/views.py b/apps/family/views.py index aee6f276..fee0c5ad 100644 --- a/apps/family/views.py +++ b/apps/family/views.py @@ -301,6 +301,13 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView context["can_add_family"] = PermissionBackend.check_perm(self.request, "family.family_create") context["can_add_challenge"] = PermissionBackend.check_perm(self.request, "family.challenge_create") + # Get the user's family if they have one + try: + user_family_membership = FamilyMembership.objects.get(user=self.request.user) + context["user_family"] = user_family_membership.family + except FamilyMembership.DoesNotExist: + context["user_family"] = None + return context def get_table(self, **kwargs): From 4a9b7c131229c908b3c89d5b29b26197720fd61d Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Mon, 11 Aug 2025 19:09:30 +0200 Subject: [PATCH 112/153] Phone number link --- apps/family/templates/family/manage.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/family/templates/family/manage.html b/apps/family/templates/family/manage.html index 70419342..cd835c3d 100644 --- a/apps/family/templates/family/manage.html +++ b/apps/family/templates/family/manage.html @@ -183,7 +183,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
              • The name of the challenge
              • A photo or video as proof
              • -

                Send it via WhatsApp to: +33 6 30 21 12 44

                +

                Send it via WhatsApp to: +33 6 30 21 12 44

                diff --git a/apps/family/templates/family/family_detail.html b/apps/family/templates/family/family_detail.html index dc38edda..b81fa955 100644 --- a/apps/family/templates/family/family_detail.html +++ b/apps/family/templates/family/family_detail.html @@ -7,6 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {% load i18n perms %} {% block profile_content %} +{% if member_list.data %}
                {% trans "Family members" %} @@ -15,11 +16,14 @@ SPDX-License-Identifier: GPL-3.0-or-later
                +{% endif %} +{% if achievement_list.data %}
                {% trans "Completed challenges" %}
                {% render_table achievement_list %}
                +{% endif %} {% endblock %} \ No newline at end of file diff --git a/apps/family/templates/family/family_list.html b/apps/family/templates/family/family_list.html index 55feed5e..f06eba6e 100644 --- a/apps/family/templates/family/family_list.html +++ b/apps/family/templates/family/family_list.html @@ -16,9 +16,11 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans "Challenges" %} + {% if can_manage %} {% trans "Manage" %} + {% endif %}
                diff --git a/apps/family/templates/family/manage.html b/apps/family/templates/family/manage.html index cd835c3d..06df632b 100644 --- a/apps/family/templates/family/manage.html +++ b/apps/family/templates/family/manage.html @@ -26,6 +26,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
                + {% if can_add_achievement %}
                {# Family details column #}
                @@ -81,8 +82,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
                + {% endif %} {# Create family/challenge buttons #} + {% if can_add_family or can_add_challenge %}

                {% trans "Create a family or challenge" %} @@ -100,10 +103,12 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endif %}

                + {% endif %} {# Buttons column #}
                + {% if can_add_achievement %}
                {# Tabs for list and search #}
                @@ -149,10 +154,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
                + {% endif %}
                {# achievement history #} +{% if table.data %} - +{% endif %} {% endblock %} diff --git a/apps/family/views.py b/apps/family/views.py index fee0c5ad..07ecc23c 100644 --- a/apps/family/views.py +++ b/apps/family/views.py @@ -6,6 +6,7 @@ from datetime import date from django.conf import settings from django.shortcuts import redirect from django.contrib.auth.mixins import LoginRequiredMixin +from django.core.exceptions import PermissionDenied from django.db import transaction from django.views.generic import DetailView, UpdateView, ListView from django.views.generic.edit import DeleteView, FormMixin @@ -51,6 +52,24 @@ class FamilyListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): table_class = FamilyTable extra_context = {"title": _('Families list')} + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + fake_family = Family(name="", description="") + fake_challenge = Challenge(name="", description="", points=0) + can_add_family = PermissionBackend.check_perm(self.request, "family.add_family", fake_family) + can_add_challenge = PermissionBackend.check_perm(self.request, "family.add_challenge", fake_challenge) + + if Family.objects.exists() and Challenge.objects.exists(): + fake_achievement = Achievement(family=Family.objects.first(), challenge=Challenge.objects.first(), valid=False) + can_add_achievement = PermissionBackend.check_perm(self.request, "family.add_achievement", fake_achievement) + else: + can_add_achievement = False + + context["can_manage"] = can_add_family or can_add_challenge or can_add_achievement + + return context + class FamilyDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): """ @@ -231,6 +250,24 @@ class ChallengeListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableVie table_class = ChallengeTable extra_context = {"title": _('Challenges list')} + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + fake_family = Family(name="", description="") + fake_challenge = Challenge(name="", description="", points=0) + can_add_family = PermissionBackend.check_perm(self.request, "family.add_family", fake_family) + can_add_challenge = PermissionBackend.check_perm(self.request, "family.add_challenge", fake_challenge) + + if Family.objects.exists() and Challenge.objects.exists(): + fake_achievement = Achievement(family=Family.objects.first(), challenge=Challenge.objects.first(), valid=False) + can_add_achievement = PermissionBackend.check_perm(self.request, "family.add_achievement", fake_achievement) + else: + can_add_achievement = False + + context["can_manage"] = can_add_family or can_add_challenge or can_add_achievement + + return context + class ChallengeDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): """ @@ -283,6 +320,11 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView if not request.user.is_authenticated: return self.handle_no_permission() + perm = PermissionBackend.has_model_perm(self.request, Achievement(), "add") + perm = perm or PermissionBackend.has_model_perm(self.request, Challenge(), "add") + perm = perm or PermissionBackend.has_model_perm(self.request, Family(), "add") + if not perm: + raise PermissionDenied(_("You are not able to manage families and challenges.")) return super().dispatch(request, *args, **kwargs) def get_queryset(self, **kwargs): @@ -298,8 +340,9 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView PermissionBackend.filter_queryset(self.request, Challenge, "view") ).order_by('name') - context["can_add_family"] = PermissionBackend.check_perm(self.request, "family.family_create") - context["can_add_challenge"] = PermissionBackend.check_perm(self.request, "family.challenge_create") + context["can_add_family"] = PermissionBackend.has_model_perm(self.request, Family(), "add") + context["can_add_challenge"] = PermissionBackend.has_model_perm(self.request, Challenge(), "add") + context["can_add_achievement"] = PermissionBackend.has_model_perm(self.request, Achievement(), "add") # Get the user's family if they have one try: @@ -316,6 +359,13 @@ class FamilyManageView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView table.orderable = False return table + def get_table_data(self, **kwargs): + qs = super().get_queryset(**kwargs) + + qs = qs.filter(PermissionBackend.filter_queryset(self.request, Achievement, "view")) + + return qs + class AchievementListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView): """ @@ -325,6 +375,14 @@ class AchievementListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMi tables = [AchievementTable, AchievementTable, ] extra_context = {'title': _('Achievement list')} + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated: + return self.handle_no_permission() + + if not PermissionBackend.has_model_perm(self.request, Achievement(), "change"): + raise PermissionDenied(_("You are not able to see the achievement validation interface.")) + return super().dispatch(request, *args, **kwargs) + def get_tables(self, **kwargs): tables = super().get_tables(**kwargs) @@ -355,6 +413,19 @@ class AchievementValidateView(ProtectQuerysetMixin, LoginRequiredMixin, Template """ template_name = 'family/achievement_confirm_validate.html' + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated: + return self.handle_no_permission() + + fake_achievement = Achievement( + family=Family.objects.first(), + challenge=Challenge.objects.first(), + valid=False, + ) + if not PermissionBackend.check_perm(self.request, "family.change_achievement_valid", fake_achievement): + raise PermissionDenied() + return super().dispatch(request, *args, **kwargs) + def post(self, request, pk): achievement = Achievement.objects.get(pk=pk) @@ -370,5 +441,18 @@ class AchievementDeleteView(ProtectQuerysetMixin, LoginRequiredMixin, DeleteView """ model = Achievement + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated: + return self.handle_no_permission() + + fake_achievement = Achievement( + family=Family.objects.first(), + challenge=Challenge.objects.first(), + valid=False, + ) + if not PermissionBackend.check_perm(self.request, "family.change_achievement_valid", fake_achievement): + raise PermissionDenied() + return super().dispatch(request, *args, **kwargs) + def get_success_url(self): return reverse_lazy('family:achievement_list') diff --git a/apps/member/signals.py b/apps/member/signals.py index b1b8cd82..f07c8896 100644 --- a/apps/member/signals.py +++ b/apps/member/signals.py @@ -17,7 +17,6 @@ def save_user_profile(instance, created, raw, **_kwargs): def update_wei_registration_fee_on_membership_creation(sender, instance, created, **kwargs): if not hasattr(instance, "_no_signal") and created: - print('update_wei_registration_fee_on_membership_creation') from wei.models import WEIRegistration if instance.club.id == 1 or instance.club.id == 2: registrations = WEIRegistration.objects.filter( diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 248574e1..e642c4e6 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -4430,6 +4430,262 @@ "description": "Modifier le type de caution de mon inscription WEI tant qu'elle n'est pas validée" } }, + { + "model": "permission.permission", + "pk": 311, + "fields": { + "model": [ + "family", + "family" + ], + "query": "{}", + "type": "view", + "mask": 1, + "field": "", + "permanent": false, + "description": "Voir toutes les familles" + } + }, + { + "model": "permission.permission", + "pk": 312, + "fields": { + "model": [ + "family", + "family" + ], + "query": "{}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer une famille" + } + }, + { + "model": "permission.permission", + "pk": 313, + "fields": { + "model": [ + "family", + "family" + ], + "query": "{}", + "type": "change", + "mask": 2, + "field": "", + "permanent": false, + "description": "Modifier n'importe quelle famille" + } + }, + { + "model": "permission.permission", + "pk": 314, + "fields": { + "model": [ + "family", + "family" + ], + "query": "{\"pk\": [\"user\", \"family_memberships\", \"family\", \"pk\"]}", + "type": "change", + "mask": 2, + "field": "", + "permanent": false, + "description": "Modifier ma famille" + } + }, + { + "model": "permission.permission", + "pk": 315, + "fields": { + "model": [ + "family", + "familymembership" + ], + "query": "{}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir les membres de n'importe quelle famille" + } + }, + { + "model": "permission.permission", + "pk": 316, + "fields": { + "model": [ + "family", + "familymembership" + ], + "query": "{\"family\": [\"user\", \"family_memberships\", \"family\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": false, + "description": "Voir les membres de ma famille" + } + }, + { + "model": "permission.permission", + "pk": 317, + "fields": { + "model": [ + "family", + "familymembership" + ], + "query": "{}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Ajouter un membre à n'importe quelle famille" + } + }, + { + "model": "permission.permission", + "pk": 318, + "fields": { + "model": [ + "family", + "familymembership" + ], + "query": "{\"family\": [\"user\", \"family_memberships\", \"family\"]}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Ajouter un membre à ma famille" + } + }, + { + "model": "permission.permission", + "pk": 319, + "fields": { + "model": [ + "family", + "challenge" + ], + "query": "{}", + "type": "view", + "mask": 1, + "field": "", + "permanent": false, + "description": "Voir tous les défis" + } + }, + { + "model": "permission.permission", + "pk": 320, + "fields": { + "model": [ + "family", + "challenge" + ], + "query": "{}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer un défi" + } + }, + { + "model": "permission.permission", + "pk": 321, + "fields": { + "model": [ + "family", + "challenge" + ], + "query": "{}", + "type": "change", + "mask": 2, + "field": "", + "permanent": false, + "description": "Modifier un défi" + } + }, + { + "model": "permission.permission", + "pk": 322, + "fields": { + "model": [ + "family", + "challenge" + ], + "query": "{}", + "type": "delete", + "mask": 2, + "field": "{}", + "permanent": false, + "description": "Supprimer un défi" + } + }, + { + "model": "permission.permission", + "pk": 323, + "fields": { + "model": [ + "family", + "achievement" + ], + "query": "{}", + "type": "view", + "mask": 1, + "field": "", + "permanent": false, + "description": "Voir tous les succès" + } + }, + { + "model": "permission.permission", + "pk": 324, + "fields": { + "model": [ + "family", + "achievement" + ], + "query": "{}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer un succès" + } + }, + { + "model": "permission.permission", + "pk": 325, + "fields": { + "model": [ + "family", + "achievement" + ], + "query": "{}", + "type": "change", + "mask": 1, + "field": "valid", + "permanent": false, + "description": "Valider un succès" + } + }, + { + "model": "permission.permission", + "pk": 326, + "fields": { + "model": [ + "family", + "achievement" + ], + "query": "{}", + "type": "delete", + "mask": 1, + "field": "", + "permanent": false, + "description": "Supprimer un succès" + } + }, { "model": "permission.role", "pk": 1, @@ -4482,9 +4738,13 @@ 206, 248, 249, - 255, - 256, - 257 + 255, + 256, + 257, + 311, + 316, + 319, + 323 ] } }, @@ -5008,7 +5268,7 @@ 216 ] } - }, + }, { "model": "permission.role", "pk": 23, @@ -5021,7 +5281,7 @@ 32 ] } - }, + }, { "model": "permission.role", "pk": 24, @@ -5030,7 +5290,7 @@ "name": "Staffeur⋅euse (S&L,Respo Tech,...)", "permissions": [] } - }, + }, { "model": "permission.role", "pk": 25, @@ -5056,7 +5316,7 @@ 293 ] } - }, + }, { "model": "permission.role", "pk": 28, @@ -5086,7 +5346,7 @@ 269 ] } - }, + }, { "model": "permission.role", "pk": 30, @@ -5094,15 +5354,15 @@ "for_club": 10, "name": "Respo sorties", "permissions": [ - 49, - 62, - 141, - 241, - 242, + 49, + 62, + 141, + 241, + 242, 243 ] } - }, + }, { "model": "permission.role", "pk": 31, @@ -5114,7 +5374,7 @@ 244 ] } - }, + }, { "model": "permission.role", "pk": 32, @@ -5126,7 +5386,7 @@ 245 ] } - }, + }, { "model": "permission.role", "pk": 33, @@ -5134,15 +5394,48 @@ "for_club": 10, "name": "Respo Jam", "permissions": [ - 247, - 250, - 251, - 252, - 253, + 247, + 250, + 251, + 252, + 253, 254 ] } - }, + }, + { + "model": "permission.role", + "pk": 34, + "fields": { + "for_club": 1, + "name": "Chef·fe de famille", + "permissions": [ + 314, + 318, + 324 + ] + } + }, + { + "model": "permission.role", + "pk": 35, + "fields": { + "for_club": 1, + "name": "Respo familles", + "permissions": [ + 312, + 313, + 315, + 317, + 320, + 321, + 322, + 324, + 325, + 326 + ] + } + }, { "model": "wei.weirole", "pk": 12, From 80f28aa7711cb2df2df5a2b97cded9aeb9475db9 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Wed, 13 Aug 2025 16:02:59 +0200 Subject: [PATCH 115/153] No hard coded phone number in template --- apps/family/templates/family/manage.html | 7 ++++++- apps/family/views.py | 11 +++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/family/templates/family/manage.html b/apps/family/templates/family/manage.html index 06df632b..b344bb5b 100644 --- a/apps/family/templates/family/manage.html +++ b/apps/family/templates/family/manage.html @@ -190,7 +190,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
              • The name of the challenge
              • A photo or video as proof
              • -

                Send it via WhatsApp to: +33 6 30 21 12 44

                +

                + Send it via WhatsApp to: + {% for num in phone_numbers %} + {{ num }}{% if not forloop.last %}, {% endif %} + {% endfor %} +

                - + {% csrf_token %} {{ form|crispy }} {{ profile_form|crispy }} From 6eb192b82315e3ccd7d49d51cb57e133a3004214 Mon Sep 17 00:00:00 2001 From: sable Date: Sun, 31 Aug 2025 12:43:37 +0200 Subject: [PATCH 127/153] minor fixe --- apps/member/templates/member/includes/profile_info.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/member/templates/member/includes/profile_info.html b/apps/member/templates/member/includes/profile_info.html index 579fac25..802753cb 100644 --- a/apps/member/templates/member/includes/profile_info.html +++ b/apps/member/templates/member/includes/profile_info.html @@ -11,9 +11,8 @@
                {% trans 'family'|capfirst %}
                {% if families %} - test {% for fam in families %} - asfafs{{ fam.name }}{% if not forloop.last %}, {% endif %} + {{ fam.name }}{% if not forloop.last %}, {% endif %} {% endfor %} {% else %} Aucune From 4bc43ec3cb8914279c4e08a3e52d0ee68249888f Mon Sep 17 00:00:00 2001 From: sable Date: Sun, 31 Aug 2025 13:19:27 +0200 Subject: [PATCH 128/153] minor translate --- locale/fr/LC_MESSAGES/django.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index f1871681..2f3c6752 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -770,7 +770,7 @@ msgstr "Créer une famille ou un défi" #: apps/family/templates/family/manage.html:96 msgid "Add a family" -msgstr "Ajouter une famille" +msgstr "Fonder une famille" #: apps/family/templates/family/manage.html:101 msgid "Add a challenge" From 897d37f74dffdb30ff928c5631a6fb8fe0b24993 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sun, 31 Aug 2025 21:41:35 +0200 Subject: [PATCH 129/153] New informative questions --- apps/wei/forms/surveys/wei2025.py | 54 ++++++++++++++++-- .../wei/img/logo_auvergne_rhone_alpes.jpg | Bin 0 -> 35259 bytes 2 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 apps/wei/static/wei/img/logo_auvergne_rhone_alpes.jpg diff --git a/apps/wei/forms/surveys/wei2025.py b/apps/wei/forms/surveys/wei2025.py index 67439b6e..758776b8 100644 --- a/apps/wei/forms/surveys/wei2025.py +++ b/apps/wei/forms/surveys/wei2025.py @@ -17,7 +17,7 @@ from ...models import WEIMembership, Bus WORDS = { 'list': [ - 'Fiesta', 'Graillance', 'Move it move it', 'Calme', 'Nert et geek', 'Jeux de rôles et danse rock', + 'Fiesta', 'Graillance', 'Move it move it', 'Calme', 'Nerd et geek', 'Jeux de rôles et danse rock', 'Strass et paillettes', 'Spectaculaire', 'Splendide', 'Flow inégalable', 'Rap', 'Battles légendaires', 'Techno', 'Alcool', 'Kiffeur·euse', 'Rugby', 'Médiéval', 'Festif', 'Stylé', 'Chipie', 'Rétro', 'Vache', 'Farfadet', 'Fanfare', @@ -57,7 +57,7 @@ WORDS = { 42: "Un burgouzz de valouzz", 47: "Un ocarina (pour me téléporter hors de ce bourbier)", 48: "Des paillettes, un micro de karaoké et une enceinte bluetooth", - 45: "", + 45: "Un kebab", 44: "Une 86 et un caisson pour taper du pied", 46: "Une épée, un ballon et une tireuse", 43: "Des lunettes de soleil", @@ -176,7 +176,33 @@ WORDS = { 49: "Soirée raclette !" } ] - } + }, + 'stats': [ + { + "question": """Le WEI est structuré par bus, et au sein de chaque bus, par équipes. + Pour toi, être dans une équipe où tout le monde reste sobre (primo-entrants comme encadrants) c'est :""", + "answers": [ + (1, "Inenvisageable"), + (2, "À contre cœur"), + (3, "Pourquoi pas"), + (4, "Souhaitable"), + (5, "Nécessaire"), + ], + "help_text": "(De toute façon aucun alcool n'est consommé pendant les trajets du bus, ni aller, ni retour.)", + }, + { + "question": "Faire partie d'un bus qui n'apporte pas de boisson alcoolisée pour ses membres, pour toi c'est :", + "answers": [ + (1, "Inenvisageable"), + (2, "À contre cœur"), + (3, "Pourquoi pas"), + (4, "Souhaitable"), + (5, "Nécessaire"), + ], + "help_text": """(Tout les bus apportent de l'alcool cette année, cette question sert à l'organisation pour l'année prochaine. + De plus il y aura de toute façon de l'alcool commun au WEI et aucun alcool n'est consommé pendant les trajets en bus.)""", + }, + ] } IMAGES = { @@ -235,7 +261,7 @@ class WEISurveyForm2025(forms.Form): all_preferred_words = WORDS['list'] rng.shuffle(all_preferred_words) self.fields["words"].choices = [(w, w) for w in all_preferred_words] - else: + elif information.step <= len(WORDS['questions']): questions = list(WORDS['questions'].items()) idx = information.step - 1 if idx < len(questions): @@ -251,6 +277,15 @@ class WEISurveyForm2025(forms.Form): widget=OptionalImageRadioSelect(images=IMAGES.get(q, {})), required=True, ) + elif information.step == len(WORDS['questions']) + 1: + for i, v in enumerate(WORDS['stats']): + self.fields[f'stat_{i}'] = forms.ChoiceField( + label=v['question'], + choices=v['answers'], + widget=forms.RadioSelect(), + required=False, + help_text=_(v.get('help_text', '')) + ) def clean_words(self): data = self.cleaned_data['words'] @@ -377,7 +412,7 @@ class WEISurvey2025(WEISurvey): setattr(self.information, "word" + str(i), word) self.information.step += 1 self.save() - else: + elif 1 <= self.information.step <= len(WORDS['questions']): questions = list(WORDS['questions'].keys()) idx = self.information.step - 1 if idx < len(questions): @@ -385,6 +420,13 @@ class WEISurvey2025(WEISurvey): setattr(self.information, q, form.cleaned_data[q]) self.information.step += 1 self.save() + else: + for i, __ in enumerate(WORDS['stats']): + ans = form.cleaned_data.get(f'stat_{i}') + if ans is not None: + setattr(self.information, f'stat_{i}', ans) + self.information.step += 1 + self.save() @classmethod def get_algorithm_class(cls): @@ -394,7 +436,7 @@ class WEISurvey2025(WEISurvey): """ The survey is complete once the bus is chosen. """ - return self.information.step > len(WORDS['questions']) + return self.information.step > len(WORDS['questions']) + 1 @classmethod @lru_cache() diff --git a/apps/wei/static/wei/img/logo_auvergne_rhone_alpes.jpg b/apps/wei/static/wei/img/logo_auvergne_rhone_alpes.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d95f496b60e4a821f27f4537313a9486df5d356c GIT binary patch literal 35259 zcmeFZWl$x{wl%tN8h3YS+}&xQad&rjcN%EAfyUk4-QC?C8g~}%?(*op?>TRudm~=N z`}IY9aVsin)y&MAIdWvq%9+V5-WT6j0Kx!Ba0o~Ua7ai9NGK>sXc#0Im`|Tz&=3&e zkTB7(urSduFmUic6XM{K;$dJA(Gro8Q&3S;VH46Z(or&grlg|$qXYyB3JL}q1{DSd zl@bR7hw}gT@!kVKh6XJHuL1)h0e~WdfFXmt_W^JLAOJ9sj|1@U;{zo)80eo&oR8!` zng7lL0KmW@KtUkimjQ5KAOKJ#FeCr~WbPmN|9ktt4E$fm0P)kdk$)tz7xOMUc%xY7 zU@|Sf4suQ}lc=zf>cP+n+W7{aPF>xP3BUhrIXINUWGSkS(=ut&M+PWZi@5Pur~fSS zPYIEM_N-rm`moXuwK+j&CL%CFmAC;H4_m_ zGMSkzVFLO8to}dlp@KR**lhX1?Dl#qz5qZaC9#j~Hiiuly+I5rNfUKd=-e|Ukw*Y9 zry;Fuo`jW&6&vGLr28FpAXnGN{;3eziCLaz7Nu7F&Fnv9U%E=Yp?U9xCS9t0T~ zF3hgBe;zgA21-6Whh~=^-zJKin^^R67+fNLtrvX-W>%OTzCObJW@i5 z5>PxIYv^VF;;cf4G5`K}}m^@i8-o!-RHUdAi* z0wQma*g|ii#7O3xM_s5jRk*G5bOYsdX1Du=6(N(0y7;mrMMlhlhYF6-?wJxP_ZMzw z-t(FLEAstM+?kWRp;r6GdE`kO!s$InkFp88XWyfaZYxChHR)aWR1$C%uS?dHY| zYF!krH`Kuiyt2(&7HLiqwfEZ^V+AJ*OKqBVJZ4G*Hei%%S2|(t7VMn; zK0|vZj}SR9d?ls)UGohua-4&Cu4S8td)lc27&~(JEhvEw3rUxd7r6>D8 zSOAQ$o|K9au~v5hZk-kq)hOo=r@Wo(eB`W_I=Ou`^oxB))2)_XF!V(;7y;@GOA+bO zZD==N)T~vON~hjBv@%&NRa8zjJE?oMCRjC3;Wb6}!e$2NsZ}I!=7B99Ew=kdx%(Ye z|6vWT-?FZh+Y_V)JWjpO+9#omd!MWm_rGKxZ|age{={Hl)mslm_hKy?=5241zSi|i zX$Q5+OnB3d)Be4KVeT01m_Tju?|~t1>ph>2PtyGq7J76kzVg`G7ibzd_5!Ntnejx( zAuIaA?vi8XiLL0VYmqv(9a3sq0tj;&^Ou@aplFm{=e4qGhR>luZlpI(p=lNhV9Nir>x6G4R6ucvsgO)*LW-o-<|;<(*37SlvA&6jlRK&t>%dL zaOo24$G-z`naxQ|e(fT7T}C9j;29_H%i1Pp^mz^OgmK3GkM@B@(k(FS(E3K_Mj?9L zEJnQ8WtGpq^4?q;7g{26w>>B+jIzL~m*5YyDZFmh7MpyVjvm7}09h(F^CXu&P@MU1 z3RP@mx5qed>&IRaq{KZ=&<^Fka7F#MGXD@wpF|YraP*fRcTOWyBGBf2y5tQFOEYUI)KwAR<1rqS%@kfecH#E!t(D`u-?$$O`Q_R!+ zgvxUdY&|FZYU8DyfWx+@F4SIyz7c_|Z1W9`XVuB)*N2ji12mhA{Z6TP7;~}GHrsBr z9@iN(|KhKlzb;NU1jd;dDGm3d?Jp=IO9LL~?SU2BT0#}uQbHAUFimyT+f${XV!Qem zw5k#V<|^f~aC&(F0CxFIryIw%*IDy9N9W|?k)}>Z^4oV>1_9}p&c`cxo=7=&ula_n zknb7gz&+-~^Tdu3ZR=QnSA5(K05+Wt;drY(`c^4?6-XX{o|x{t~2$#LAKVt*keAe z}kaK;4yX>U3I; zk5Fx%>Ut)>aBCg@rfi>VH=nz9kF>H=zV3B^61MPeLb4xabD+L zCj&1T5iHs^sgZh!z2CN8(j)2-vF82xXX=}B6b z(LwFouvhK*N7aV(-sy@|t6KdD&B|Zo0e)mz zIjFJJnyJw@V0jIFq=D)MbFtcWq=j#mI|Bg1($j^d&o;a9_Y>m7=R*ZKT28O@YdDZW z$>VPLqnzgf$LWZjKRS8T*sH!Nu`!i%5FosqY7dGBm#pOGde3d^*P0&(r0kc-24$E# z_5)Ln(BC}MZpJ-Zw)>Tmf~wUdM{nUeaB=){3w3smciNMW_su6|u;pF@Jt|(FMQ(3s zw8g{+-S9btCs}z|w=JI7*meh~sa2_t6I0mso0j&1GH%D`gT40mDI&iwM7`&swP!36IoqBW1GRpZ(0U?brswH@tc zNHPVcSn$c`XeSj$>j}Ll>DY|tZrG$^^51y!74KIy0uw)J#L!_*)FQ&C>YXE+W!&EF zU9UOp&g!;Ez$Imu{LLm*IR8lkT;#q=`N7U?Omsq#55fafVP2b~kh69f8^e6rJD}`T ze_Q^;Ife3C7oPCjK6>_=Ow;aN_YmMg^x>cIeAtTi8uD1l%(}v@tzWgvEJSf|&3L?4 zt5-5BcYK+6@(P(&+-n;~3-k7mNb*>S9F$tu%dF%Mc}bAKoSeqX;^&{_Y?br&e&sz9 zsGUCNP1oN(B@__Lco>jw0fyFH7b4m}KJgw;_LiR$?<-_Do^4;>a7Ga{d1YKbK6y;x zeItnUh>U`}+m~+9nYeh4QEtPN+&knAJUwy4a$sNpA8mT{jPOF_`@6jW03o4S8Ub^~ zL+HVY`^)S9cv&L~@CUU8rT?q+fBgFR`2C0czrz1A@V^ZFpJL$8b`=8>00bNa6cPjs z91H;V2MHJmC^!TFi4_?I6%Cz;k@$-|6EllG2`QO{X0IU37JdYY-!zoq7T!DVfmm zGU3lAAznHSoT_IV*(qUC79`aNZW@m`@$OD|2ECq2pKK$&NUQHMPctFt*L1%3uO zQ`!7k9X4i@XQF@}V7FVgdk^uD-A7iY`NLi>=Xq9YPtO0|%>PYKjSFXK9+CTqnNzMJ zc@1%9-zfO0@Gw$W^-wvD$oSAEWEe$?c)6#F2+hs{JHnBz_JqJ?nm?&ld5XE#Hz5`7 zd;3g}lM!*4Fps(`IOxxE#;Xm3D7>&lcB7rvFY#O6T%X7YfzbcL#OJ#Nn%Wl0cw+gM2#fmrhHr^ zBho3OC^NjFG*x{4fw?dUDNbmNUAzdy(cTN`FsLEQlH7V&HT+>}9v~(S$D>8n5bkP{ zaU}kkS~IeW;qPjkqoI=6%@+E26i3(Ern6-ORtr59YBL1Z#{r(4A#{|TYQ3lL3 zj;Alx%OX?5s>CK3Mys>3JFe?K;RAOPkS6Y!R0 z%Z?5IOF~nRY=+e(LJ*k>YM=vb%KRfUd*ef zk*J}bSbZmkJe$G%i9tD3!p{&!nN%iLzR!Hg&sVnR)M{bybeL~&WgEK%y+n47_bzb+ zF#?%14QjY>QSaoeZ(NlWB72&ZI0C@MxUu?&bwwH}_@&EjtcK2hXUf=@R% z%t1w#>(Uw%ns53Jpkv1d^GY0h2hiqH(1yd@V9mK#h?z-a{sYOxTXqLl5gU&RdZVL9 zde&;0w-TA3rsx^z^Nc`(S^e20PFuw_;w0nCUl+q`=5a1F6Y1)^Ga8(P*kw`UzgR%M zm7Ar@xj~Gt@iN-^PV6sAxgJuk?kY^<**gl0T<4)lP}f!;x(X$zF@%FLB8Z&plpLS> zQnzRfel4BQGA^2@Y=y<|Dq}>X&)VUthID@FFk}DKVa_#`$3}~LJnHtrpN#_i zFtkpX8^7hB%~H8-#8f{C-XX0j0%V{wetVg!w1@1Ks)^UZ@g5(c!_y zEcCqAKCTK?23R-AoC-058$`*VVm8G5#csIAM}&JXI;$p5_K-vbEB^L$)oBrq?Mo_{ zjfgkF!Q-g&XfCnD$T*3P&XKH}-K%Da2>e@3Uf2+~`LPqs^_AF~VH9~}dx;OjF5{dv@E_?1I>Ea<~i_c+iuL~V%l_=t+*{0-=9D4B6u!3vo_ZjfgDtb8d zrq4@7d&d&4j69MQDm(^?o*1h%UDV-~$pi#F(rQ+12o}v0nXk;1_4$=SD2-CL1os2( z;kgxZjoUKbuEiurp`3QZ`a-%RzFxUI+B+F9B*5jxVb`EALULh1Xgb;>%YXwtk$m+P>T=_t z#5k6f`;HU0+8I#hLi6E$if+_nafP%BV5cYiYzTyCfvU8#8c%WyQbJP-*=FLp3^w1vKxO8*YX8EdC>-V_?1xe=fFjam#lbJM0HPoKscH5bZiD}+d9 z=Lq~J3yx<)D?F75-ELkBT|+L0`US5F1?Mvp7rAcE-R~e=jhL(uDPS;wp}Oi<%+I@0 z{5u8G76~EtpT3}#;9T5=S3AvJow@C_fq9I@Nv^FK$XONrZZ7SIt?c$ z|EBhzt0(091{@>{!+6Q9+~3|bPN>{kQ14XEt5((+*qsR*-T{xK{ZR^cs7_U>p*nPq zO?GfnMEKV{8XB_9aO^xdabmLe;CpGt0o24RL|j%?IcpXU9RYRp=UU^tsy!RNxONGt zFnnuw%d}3oJfE5uW%?_UniWCC(7U9C?`mq&$gv$YJZH3QMGKS2rK4&jBz4`JNxc$z z4pMa(8quC1N!rr?3nTHDC@K+LNiOuymhXTCLJ&(+O7U65lQR9hgyxdLZ$H(~qr|J1 zu=L=oJ>?IL>LO3Sr&lo#5Y2xk_8^;(HFrMS;*nA$ChuA@iE2Tm7}+b!!CW9`5@aM3 z3dP&85;R1`u${^D4K!2!;*~RsyDVx_7l%Z{@nCb7n97Z~;>k~(L+yA-Zf_JtCKRcK zAI|?VVH@5PSHx7yW@^_jD%jRFZ}co{gyyqjM_t}jG3XjA+~jJV18uHm4a8a-2^G867vy~pp{&^CKj=|iPkWZ+POSFnvHuDlTCbRt4uZob|}oF%G%uy zJ?0f12H_{GQL9MZ7ZoF%=&(31Uvl>jFylu3%>LyQ{yTt`k}M_*79bFE(KbT^F%y)t zg={z`B7`7Mi14KX9mk3}lkB@u2jnON*ciHFseWiSChgWeZ8+^z`W|+VNiZ3E4o)cf zDb@Xxde<3KMf|6AAp=J?udB*$f$N7JwOOxuM&ew^);5S$c1LV)pd#cO_WCGowcR}WEs`--wrjX8 z=B}8S0-OL^pc_#zWmM(|Z0#JGZ_Yl;9Ls3dG@s|Vnm*?EZD?^b;Swt*)S#gUKd<&r zXrG;M!Yr_6*sKgo0y%#KgrGEsi*gVQLS8~^k2{7E`Ywl!@uP0aZqzz5tUlHGerY3|fhQCq=o{Hj#YW&2 zxH#;zoJS{38$z;-nR?hqoJ<2PNuccRLrqxS2p^ubSy<@z2%^_+KT_f&R9H~)O9iF5 zFs}=hm`{Gis7zs2xDQ^``%WR=Ne{vPdDy062c{9mObwRST+&JApOtta zCeCALIejUoc^ESG_D%N|pREngsp64`tY`PFFPnCud~fR6Z2WwwHFT(2+b)#(QC*9u zad~Wc>$Se_ED`$n=KgB$(TOz1C*G0#9p64u(1(Yh!MV+~elf*G&HW9-ZtStZ!U@_k zB17?l?k%Pu>&xi9tb@ncISp05``GkOh}zeaaru$Ywgx*T2hALrG1lT><~=WxdRt1W z!4XQ;z~Eb-QP|fJQK*KAEriR4qk`%vo3Y$rsSEjSHTEd`q9SY1T|8OYZjoPmP>gG0 zzuM2>YrJaenpsf1(jc0Y)!0$vY!&+Q-_SUt$Y;Re?_nycrvxZJHT{|$+iKxV>jV^A zfj<-Y+^ewHC7$m3ld|**L6p^E5JRS3M~Dpz@~0)sk^T)0dvU+2zJ!;DrjmF`dI+zF zU4e(D`wza6jN&Oh1{XT-8o0K2Hqu=V-nhz{u-46>J%MH3S$Gymd7*M^gZprU8PiCr zlaig^aq(i}U0wIEF(rps>e|zWX!xqH%4;0o(D+14l38=<6&S~f?E2Xg$U{w3es5#y zw1xDV&|4UsRPz@-QNx&yy78$hijvZsSHpxYn?BDur#KMqj|)S_d8~~UA4$jT<2Ms0 zRG{4@;1v#or$h%i{KYIz^NrCMD(+{cQ)l}gh--4sAUwI!J9TI2Sa$uA&FOEgwEVd& z&XXqU46t$16Blz2dUs-QV5xJT`2+4-(wmL#l_L>oDYviJ!S4^Oq^=YSwV%A4# zs7w;^C3iTmrMqiM8ORsEiUNe|dp3D4fy_Z^!yu9V^VPX-Z zJAzQ^Q(~Li5kiowl!C_`&&RXrX#1H6gEE;~?~*zJ9#X5N?}vAs#*|eyhI~ z&ZPKPuxcT!fvpdm9IhFx_Phx}-vLs;#4>1JIOTiDVtq!DGkV3THThik+7q|* zmVD4(|M`RY+B7p*h-4Sz@soB!g-y2ouD*S4kn%Ho^Esxqxf;NR)H=^4mmVfWOajBq zA;kjkl#|Cv`P#D4$0+k)k}@D*S4qWc!%8avtum^UY4;3VTfKOjiMK9Ge3gQ;X|?Ex z?3YG>`_h{U(CjfWJabQ%-8cC!(e$xNp|_Ocg16o`m4;Xcr*ms~kVZmz_PCb0I8!h= z?v|Xq{%-Gk$KfqT(pS)oGSQQ8Ov<@Yf-4r2B3PDs_D&X8+haX4jol@f;*C>wzQoA8 zloeYQ6;VIPp*iuhSbB6m)oMDDemVI?$m71rquC`v9#dP>i^M5a-0A0%74y`h$}Nru zbSZf)EF*{!8>5tyTK3@yc{_M?nSg=ISoYL69I z+udz5{;5b42w^i!JSzcaQK9rl^e=aAa00(?fqq^oVq}(uc6CNe!Ym&Z7MJwmN9XL~ zG#jm}TG!*o3K&!G#=0-n6c(duYxens+pyQ#as`auXwF>6QJaIqH74LB-KNvbnS6Eh zQ+I>q&*SP02y#{>omLbpl}mh3X5J6-8Hv0$cJ5b_-n}zHNG0=~g%a**}uC!WmZx+~7!SbmMw)rks=AeZ2M_psvFUi0LrSD|uyFI8BHdB%~O=xLk_ic%w4*iP|oprZDK zZ@!vH>1l0>lax$kdUT6$d`Uvo7kZCJE3(;N@lpWeFG!iN+B398GC*gFn!F-wKtKIR zLzd(yOHAk#+63#Q7u1>Z^x;>W6mPsjj=aK!uhaInxy}(klex+`^6yomxbD2Tey$3( zWx)ULKTnQyujy;%_|Z8IlU;!Q6PV}%%&I>tF7WPadh)XWuaPqpD%{xwaSA(QnrxDLsDV5;~|_ zC#iAxTt7I@9s~OYEvTiVuU@%|mpF>0g2XpO52u?mviqa?-lB4PwcTP(&}e^Y z{20^3pZsMGxy8*c@Eb#Pd45xU!3hr<$}knqW|hOw-0rioi8(?roGATab4dY;<~Vd6 zPbOT>{Z=;qd7KBBu!lXI`64*Lwi_eJY4afI9Uw1b{}u;{l{8_^A(7 zQg2^FMR_(DKtUD@dU4&re&iT6+J6D5dNo2tG$zsC;oG2m2LP=;93JMs93BWz2yhUH zzxUYxc6g9J93Cb#79tX6K?PFAFNOxJLh_0Z|8jOf|K;px>RQj?BR%Io(-$Gw8Plh4o#~i#@1QjTJs93@rAi0uwMsIFy7A!XpC_ zHbuf42~C5u23zR#ca6i>M?9IVYH?`GZ4FhVmm^jp(Tv$P;~sNIz1{&o{qfZ_*s^na zdk%pX)%>4GQQ-0=u)~++{4<-7f!*lv{p&4Qzi$c{7=bth%aw1x2QgB^$Zq4|S@JiQ zV6cA2v9Bcw!{+l43r#vd(I6-Od?K&UTVph}|MXi1^u7m2O`>^`K9KD*e4~LjwWk4% ztt6|icW%JcKnYct*0YZeucmZkpa!_jSlRyCNQCw2$hS6h2Q!nw#=q*vO2fI>&eTe7 zFr%iB#6vg|bd3&2w2=X%#*MHuuE{Ab4y+cfq;Hb$*beu&;YO*@WN4X{%$t0BQ{iD2 zMVAq-HL)xxws%!S=ddnnH;_1>hP32m6Mtnl70nT{bCF<*(pW7yI1%>GJ}qfpBaAOe z$$q?QDv5TF+*PdQ{n4*RpL-PmM;yIN>zpx{tbK8K{-?8%ncaoA(u!A=ugeX~Bc!gNB{*8dErkZo z{v)q;4ZZZEny4YT z?%@T9aJ;c>R$#|73{~HyL#Kz^5&m9tdO(;|$rC1Y9xcN^WqZ~> zC%G2fTwQhuQfOmKj=i*^$jh>I9$mnm>e=k7h>rq$cgAGOYRwG--zv4XQMA3In_q(( zK90^w!;JdZ7(=Tvvn2;R+e?^W!|?qBCVYS+DfrF3HPo zk^a?9YBGYu8Lqe7pU5JlATiE86|me5gVMAo%;ammSBFRG@S{MGdl>b z{?#m-CQ-Evxm|ezQ8s4Fj&b?Hg4N=i%g(3(Jc%+ZKGILyb0+b$2%%|Kk!gqn5n`gY zziHOaP)pZNP}lcUTVLpKuZHc?sCQ^h*N;MQUxf;br`cYsqxzy6no6O_){2=c;oyo}fXE4!Cbtk>2#oBvy1% z;?7#$fzDL_z1bIlYnv0R&>W4q3&5d8-7>nj*ZI4{oU6U*6Bq(!`Y&3{I~a=-WioJl z3vu&wmVF;m=&cJwC6<(tEnBHWKuAMvn`8nnlRqga?PI4|W6I&)88vD^yDFxO#jm6% zB$(}(9(1F+I^FA#_pJ#clus2Jc}1+r$QABEZV7*iJE{>sZMSEqyHU&1^|8Q{v7#DYEYl<5Hu30e>z=ON$68HNN(ls>zO*U?k z4%&Sp|7V}uK^2{2%vd$M+B(}cV^`~p z@9i;HoW{_~A&QQ!Smd8uO-BwWYApSsMly!NEh|FX4;N|s$lYxtF9q)-w z#?vh2G)(XNGX3+-9cR0Q6dZD|#v0C|!@e*&NB<%dmYgeTmxV0M#EsB0rBrU@42dW- z5_cVoepf->WYJ-(z|;dYL-7zk*4RmAPQAA8$C`4F3GwAwu>n)d*{;BEWqsNCre~^+ z^qm~;r2ghlbup+-a&XoOh81Vj7Q(g*7Yez&btgIS*0xqEnDkw~lFDRK1ktc?O-uE9u1)W<7zfs#K{ zAmsZsMkPxS^ZfiOq~%3LMS#D9OJzF*MmV_=V%C4CFkii19;>@x@8BG6S5!=s!uRQl zTiDJ0J875`YK_(EI?UMd`U4blcNeaGSR`-SCyIDkQiqWn^SnFRdR~*n)vRsKUZ(o0 zIbX~R6gf^z)Df}_c`}C|x_cK2WLFFX2bs1$a*Ix?Ed@e1;hRLoGGsx; zIQ)YtGTVzT&q7x{1R|Z@0Rsykv#}5o02BZQ1`P=f2@3ewf%y0s<3mP9B4$D%B4H6! zaBxIr7BcvrP0A>*7#&mH4gST@$^Sw>N7%@|s%E-t>+-+;2o5Rmv5Vf3SjebmA1Oka zamVDKvPtqdc^Pg!nL?k+vKLy&5q_D6+?G2-Ly@Mc75K6JA#}Ll>nx^4?8Z=1l0tBh zw;|r)pkcGsxKz&YIYRbeuFd2pHM`pUl!<_Qq^NTv;YH4bc@E5Q3HfW`0;HlK?pN>^ znZfm2zg%=KEm9qpy`ML;O!$>U)uPmv49FG9*P<Bs79iXlbMj*=ah#I-etku{KP%mU{y>9c=3-tYKkI3{kSDlw3p@SuOcNEBv z7~6Dn(vOCZa#t%JH#QOGiB#A%;Wy#ZlRxEe$4`$nwQN?<4o2O|XorEHMQ%3UkaWh; zM3oGxY+o|PSf5DWI*ObY)5MB$$2HHww00w!@xH2RLEs z=`L3u4TG7in%2@qlW3as@H-Ru@VaCVMjX-Ik`|;Mx|CA_B1RT9#sSMlIt)M%RZA4F zYwA9b4V;k+=S&remwu&epLl4{nf|B^MIfP}`+;z7wNp`Se|~Ed z{RMIs-#PSEOptM%yCd%dou~tyJ(8*h4b5?KJY7h}!Ay%GFlANLf&#?4l%ahB7CmVl zmj&S2*1nR(j8j~dzzPa8`q|{0O$)hX>H=E!hH-{~6@`6`99uI@K8NeUNJ@W%K71jA zgWuJ-@)Aq$v2laZ7gFc;P;(E{K(H{^NXr|kd2e^ms!|WeBBqsriDb|6NJ4pR8R&o{ zrj^0p2wteaCY14O4Mv(Vu?mGc7)^)K3P^^1OU2llLS1^;I^<1;!KwOE(NR%hegLT8 zPW{jWlOB??20${HH-<#ul6`5NYdlj$kKulNZ&R;PvpF>t@^%wV*S=4hfe_R9*nFc}3l32N*3F&3>~6H_vT?pp*my`dVh=Q!$>oYSfg^S;I5W=~1ZqkT$X z#s#mcGH0w+9SiKcHo{tDm(LnJ0WltB@eGgZ+IJ9TL68`^&pp+Dn9bwFXwb5?f}*-B z_|kuNacf2A+LN;A=r4U*fX-*c7k3n}s^M%O_1KhRDZFK~h7QLTyW zYK-HyB&(+02KfzRJSPWu_vJP2X}3V*GbiIt!tz?{fAi!)o4%FIlC(sTK<@s{I(A@g zzsfPw()dJBPHm=Y#y5|p0~E)|(E0#3q#prEsW#-|7Lj3%ZSs!^rKuU}=%;d;sDa=x z|91a+9HhKNlVsY6oScw5Ly_)RoQ*_iJH27p*fLMamo47ISgXMR#P{&U#X7M%Va5V_ zE|U?TTm74axHk-Dg2npuDg||lZ!xouGylZHbIx`vF^eO$0w{+*i}-GjS)ZM-%@B~( zY9yxK7^x6@PVc^7g+E=Q7iO6V#L0gcDE47QxI}fI?~fZpU+`Hh<^DdvO~vseRO2s% zsc|ihSL}MFRw;uCUP9P}Wkkro{K%tXc`lgB5>HEytE*XqN|eGVab`phZFXz5vR3Ke?LLy69v<=-O}7}%vRo|Y)W zKf={_`Dc?>;+{fEnDBe3hq~NCzv1e%8l%{e4^ZX>v@K0HD!etAR4TkRU&$R!;yoVr zfFBx`F=0nMNXg6mJ*vhnY#K+fu@nN*QPWv_+ce@5We6N%nIzN3lC6}``X|U|u5QRZ zusRg@j=goL-qde0!o9a*)U#TT|82kRWT^!|_cHF`tPGS4;Nm(E+yY?MS1m&v_)x zd8Cas2dn-`zM2W+InC>bZ$S)x#YFq@3bpCDN=BQ%^|+ zxfijf+%7Rv+0$np&4pta%j<(5pdZzRWblaoJt9!@(i01zJWx8;<(w#rf44J^j`bl7 zXxHUB+@+mqzol2(RzlM*fOs2s=Gw%sn5G17lPYFHPl@P)j<^(fb$e}ypRc;#{l$jCjO zjF0ChC#-#aTAHp@;BPR%5=H*`k4q}M+2!5J7-*g_DM<*-TE+-^O6pfAX~aEjZnoy) z6SNd|^R#{xb;f0{?XyW(#<)o_7rQ&mW|8D{dnybkE zoKs^QS@#bZq445dlS?Nspv?(Xn3}Qn@#R=^BG5$Lj|Zs3uoqGc?xx`_&&bcDN`&yh zhYMK%G)*nJWD-dW8W@4p3r}}>SQ6&e8%Gy~bl_y}zX43jTkv{sVYdKbwPYD%u zjfvyAfiQ;p1kEieE1h9)Y=jkvIgxlR8mxBR5D@3SB%1PLfE8cs``Bz!Y2QHDZ+`x) z*}5gtpOP*;$xN~J@N~(~nO>CYh*&MGdn6gxIH>4Ncq7#Sw}FosmAUY&oBjvu z`yf#D>-Nayk{5oS?LaW}?m_jhXTtcT){x8x;t$Xc%!%pI(-Oz0Jh==8r@T5$pMFqW z0(OvJ^ZvY`%+3hQ@V-k3Z{d5mgckuPF!Wb``O9>Pek}?10`&E3m$->4Djc?Ap%kY4 zus5n@ccY~Mvu+l~*~LnE9w+(eXK90Z8bN%rUddDUlATU_zld+hEvaQARukPFR9Hk6J# zCS?@u;A!b+3{8|C0b0d3Xnv0EqN@XyaYBgslAhsKG zcxerI@m_s*S<7P(2>5O51SngvE)owfBUAhjxUbkr>HeR}y(U2Elj1{gi$l%>T)INu zM53Px;2JJ?-T^cT!0E!|TM0^65oz+f8FrtQFQj0o|KdCc#eSD@W`feY4CRjK=lZfJ zVbjhpWn3npQTCcM<>LJZ+bFwfpE@Y%k%k4vmRgB|kU!g$=J(k$p57~GRQDIqIWfcmvE(3a|VR#=)oUk%A7EVM|@k^Hau5%Wa1`aPBPEG z%j;fl>RG7GO{M=ZR(<@+d?Sbxwy!i{(B-jzafd|int5nNeJiv5fgp5i(nmZXp_Mj? zeI^I=#6euRij}p2h|Q9wp=&Y@8m$FGbN+|#PUjv%v40t**iH{UuIvG{e+Zw9G;eR+ zK=Frh&Y;6s)Bu(fzn-bTk#B61$WaCdHzW!zKu$BNZ!&z>T~S^V3^wn2x%~bJ1iS7l zG;U&(x#WrJv1gavBJ5k9TSIYwE%RM{Ugp|%`zf($L@hqOm#6RJ{_%vT+M^Izw_z_s^Ea}``-2kG|y?eRy< zXJ6O8NyI$KT@2YR_~B2J@Ke?UPohx!VThBM<-T#8DU?#GNF$;Ku{CT;BxaIfIjPo3 z&!7Cj^RmJE?`A0sa_tot^B`&qGiu-YtxS8Jq0yN92z>_|^PBqb z(MN+PE=w#ud;e6tuUk*j4DKJ%A6+^q9wL!a-)E418nK_h_6--tsRNyqg<3XydImvY zf)nGsa@>f2ZdyBkxJuxmQ|m;3Jn~Vf8NPL7itxyfSB)62?8VO{9`Zl}g~2kqA@35P zeSk|j!pp}{OM_3e!Yswn5q5(8Ctc5};zFF4K&zT{Mr1k;=;W=NVC|gYRpMsi;*djO z;?q#Iyz=Kx&Bhj?UzcV4P_ukCLv=I$g@;1s<84|u^nw3E_@>@e~hNA$plb}D$RBxr@ zr4Nhac#98Af2gRZ5xx)R^_dqq`*wWC@4G8IxTG!01S51p@oT2D(DK0m9PjLsu+Qi`ap@sKKSU>(dk%AagN zT2G5{!cXTeX!SAgtkp|frUGHIZ5MMuGpgI()W{73k>ek}5uc?QhC%a0mgeQt8qr+h zh^;MQdiIP{N#U&)$IPXALnwS9{rsEpJX|k1^6PgaYUBDp3uIWBrjqn%-2f*(5=bCU z=N;3*ZV}H3bx+oSqLhlw9S%&AiiZbLifWVQ+cgX{d^Lpi2y;TyN-UG`$83uNQE^OZ zqzna{tBi=d?I&*2O?mdGnFi@OC>cRN?_`TPfq$o|05N>;!+D2N&Q_!}NY%cVg>Q%IuiR zih<1xs#bHTFs?-71J&nR(K#|o`eolskxb6|^%~wzc4>BTizE%x#Ejo8iNL^{7@%m! zny4f~t?z>q4pI>Jv&*-Zjx~jsBiU6DD(|1Jnl(;2?vA07+{`okqK8mK3XEtVg*%>< z0aqv8zJBk3Z9{!#HZ62I{gKh9cR)a1i)?2z5c28G@ki)z{UOP<+BL{F=v@H zCMAzUE(6Hyj1x%ES2vc3jrfCbT>L|0_C(0a0sU*ehhLR2GS(|L?*JT#Et9!RVJ;tn zfRnBD#us=ZYn{U{)@?;|j|oj@{rvQ=9oXnmUCRONnvB9AFt zN%Xl~QmbF;P>PZ{NZu>i_5Rh=H*#^b(tI0xkqo;?5FvbXNibrzZFjG`2V+%Hu-E_x3GUFj9=$n+ zUFjHeM-O-G^5M?UfUcn=6hX-rRbde;-gf|2KFrr)s?p?B5ALmbzC~w5CxpZ>E2N!m ztBj$c9d|+w_vYd9VK9W+#U8J5wc*06yw@}bSBiynwCPxjcYrGXsfR%2fViaJT3JJ) z*X9qN>fTo$@Ty|`lq&z~6}8O+-3k0-Ulium9an>IxCN#cMb&&VY&LJ=tKGy6iq|I} zeRVOW0Ci{{{9{<4WJsZQxB)b-%@L^aC0hoZOVtq~JiHk2SsjMD)Y%KCp*JSC1bmoZM-{H^dHuFUi_qrjUBQ){2D~&#)m>>p^5+{QR=cJfE@nMk_$c;U}0~C7w z;>$%`*;xhff>(YY&46k_Xl_+cSy8QXIb=x^5O(`G%i;(?3EkPBYdxe{o21j6Xc~h^ zG2Q1esPD?7rebNVZ1iq4Uh+%RLy)6_Jz!CV3y{0(RMp7bRPfXWIOwENzXNI?>D7ss z5yQ~w2S>raw|9bWaE6FZ@#1p2M7rXGt@h?`-Z8h_3AiH-lX0~-9ad0MawV86#49ja7<`M8qh^FsKrDWF2WTKs`A$t zM=w|yV|dLuh^`j~{~kaTuw5+5zc5bmEn4YTDI)OCeGz>&z?gsW@X@4#)QUkTOphNK zEjJACVq9fBKyy@gWgjAxi6_c)C;bcH7C#=}Dk<(epc9;K_ql5Bq@r}Ow|x^h-JW^- zofj98-42*gQ{MYjK#ia|9zSYiOa+_KF;+FKz{5$R77ZQdgVsZh4HTyGd5!P#RQse2 z+EFXaC^Py)Oat*87D96GDJc*%Je{5KnBasPsyRriyWybI8-8z+r4jEQw7`jt&T*m< z&_Yvk$9V3}d1GhxD$ho0b?ZZ9b^H4vuQn!2ZH<*lZ@0S~3zOZ2S`c-wHS>ob)=g(* zwil;WT42XJK+m_bb7AAy-$;@}H~WV*F$B_@C!zln_i}K40z<8#R|)-DVDpX|UH_F>ob~Bxwtu61T!l7mxmhUFulQ zSc~{Jt82+40^evY=*vChp}R0PA37;0B=Stc%#IADq8*cqWc*%=6E1IU=Jk)H&M{}k zqxLYKbp$@#rFVcbo+Q2i6e?V~gZ_w7bbe|}K7w+^83e>e#kzcWiXranfIY8)u)h z@4esOcigc@tx=PLNf_Lv(@uZxaNd$~Ja6P3$K6V`8$cj>nL<+Zf_Toy@0)Rzx2q8dqR<)tWYOFEyliyvI|Stu_gfoH25)8Tw@=(F&J z*B10f0EP0WsJT+u&29zw0?_l_@-jAch?Kvl7>{{Yf(isYy$xeckln-u)F$%NTQm?d zyrIT+3mY;*!|JG<5?ke3Y^g7qT%hr>98)rNh=)v04FFANR4#&svHtwS6;eVoHnLe( z@3RS$7O}BE04qu;nLua3*@OAF0hTSBYYN086}H6(0VbjBpvU!$aVIuhEFMouSRopT zeZ^3-r=A*=gzcYy08FS!!k7KdEZI3uRu%T>dHZYS`TDflj8gfSkW)x+3V8fAm|UWk zD0aw@$;h350O5W?q#+eN9dAjJ?%afIB=3hxWk|(c79GCe?Q3^`0O)`yX&a?(;iM-| z<(}toGPY-^#r1USN90p2(jyJI!3AR)pF}{OsEeVw4_;qdbocZ6uQq=#g`%A5NEX`@BoBIM{GSeY zJ3FB|Ufz^_(*(f9y*_qyCq?1fSp!Qj9{NEC4@j( zEf?AuTb1%BZ`$)I5T6Bpw;^@Zeq3-!9vc$@!6tkv&{%`Itm^C&XiDM1`i*q0CELf# zqlk?}8pD?!(}bV`7z@A?g6w{->9)Gccs=@G50XhMpU7^#@!n{`+mvlg2pNl!SJRGs z`G2_C%KTmchyyGa?W@9#@H|b?$)JxiG?ClEFhF(6kc(%r?3sbI#GVPQX~kSbG=9sa z)2kSRepD6Mi=2gEWHD;;!Ht$SJc?wgLIzpbEMdw4>aZPP39WwO0*J6$Y(?y~-3+C= zf!07a&des7Oj!4K4S7%6_B{dkccNDyZ17EAR<{ncL7j@AqA~ga)<<@4RF=2`#$0@3(>t z$u3i6_R?7FMl>25mILFt*a<+VQt{8%DLg*5IMab+LBM1vn2$_hD`F_bxMF~edp?l$Dzje#uH7p8|wn^5mfAd`68u3xgH81wk ziB4R$JFdxpx8*C5S*UF=?l_dn5HJx4CTpK$wchYnYCf=b+L8rbh__yz$R?uRe%_sX z#qb>@S+HKxQC{=V&#|(8UKW!x)8k@&wH~2_rPy~lPNo)fV6y=B{k+YCkoxJxR0w~U zks1xf`OFTJpl7IIb(Nmm=@Ejs*@PZ~HC^x)k%&=z+U8#+Ls{uHVg?{)b;;6Bp<{Ks z*wvc_#*wGUEe%16+7vP8ch0j++e+JhMWKG1*B^DQVq-D<1AxIGMKFgu;QRwfRZ6{n zQ{LM${v^^kdm*j#CGiW8I+hmYmZx0OmnEw%)jg(jWTN-VPVhN%H$}9)*v00lwlxt= z;2F84#%qI`c$MsyX<6K1&`kw3Q7DFcq^-9ReF4WbU-c8hQ9K|o0I%nSUnc>8gOq^J zee)rAjx_@VOwpiRGtb08`eD9|UsR?n2;MDGRuf&Jz7a#8 z%LB!JvQoj#mD==)@0xd1!$<9STOfdU$3A3F>T@p*Lr(L2BOTN;l!u`8A;4Zr+<9Mo z(@Q-s=2nX%B%Q3RRU9ZYxcaIBSjhMVK<%D}&LfSEV4!ebv)12hX20UZWO%%;MSVidp`ogK+Z zVXan;(N_Et1m5&G1?Nd|mMbqa))uPRx6xdHqc9hn{=C@5x!h91(94jTH7{f_x^CvF5afU)Zi~ft#9XA|!lIl9qx6mVd4O09D#EnoO|dT0mhVN0 z3@bp|>I~yItSDoIsAaPLItO^=n%M*{Q4K-QL-10*vP1nd;WFemek7Jm zi}*lZ5PC=t>>BI!4#-skAdu!E%*RzuK0~fmwHI8;EBxcC;VN^vK*gQ9$zMa)$6VlxTpK7mA!&llW&1qf z2`h`VXXtDKLeW1k1fJ7B0o!UWU&-@y8qA(4vyun$3wKGvJsXe7$!0Nm<&THy(bTz( zQ_Tku0cB*-Jer$<$}R%Ax6-%}v8FXSa|?-g3CDA}H2mrmS0!A(!rc0|FgNsG3Sv~DS2y+SVN@;MJii9kXDMmd1 zeLAv1qDD8;eGCyv^9_tu+_cv7Mfg-lUnp@yfDH64Rn*$VG^J3){PuigC|O#_8NX~z zd;C?=Y$FD(+tMJV+D5RktlJ7fH+qDkivec3g z8cyOBT4}J1tv`UVdn=`pIHQBx=S@D71W>556$^ZGh4fP8!) zpM6|kDYBHUkSE`VHGqXAH!04uijBShfypJ)c(`?Ibn<&%3$6=zFKOChoh0r>v9Tns zs0h5@bu6(6&zwl5e>fRBX%&x1JX^j(miB_A2&O4L4!aT&I!Jyf>?%c4I^GYJ#lZ6z z4<}XfULgYz$4G7CA7U4MU0m^2*UGHt^q|DElA%m2^ybh+ntWe0nW=>^sn|*_nq{p* zJ=XmI5(DFaBJ=NS9-&22AB+{^Q{AB#}&XeT#pS&xU zITvU;qRC%1sh14aR0&(uz+zkyw-yIP+=3GxSh^}O?@AzlF+&*M3-@1qa6yoIK^o5Rte>NBNk?8~9t)(m%Lc|K`1m zd^f}QpY$ID{+~r)G$0&{^YPy&`NLo1jrIEf8Fdu0FgUV^?F`@T^WTf%PChl>jQyo7 zl}P_*l_BZ_M;3)fR(71>yYb~ag8q6pF7#At1Ezl;>wIc7>A&KUEPVC9igOns3qx=s ze#(BpWp_r$UTx{@@s$cY}fQ35A$5)Sv}A1?J5{&xcZtYX|>Ws7+P&we%!{cc7;<==$&c#`$` zI{uNV^xyk?yVn;*6aY<>2;$3k*MIdo4S41{kE2sjcv;`|?Ysfva|o2B6fGRQzuj(m zJwHAicb!T8t)A;%|4&dqFU88|HEiG3YvN*)dw5Buy}H6$*XNgI;0sR6*JO1n22FeaxQQ zb-UBZQqcL24xd7SgZjVz)w<5@{;yBZ5C)$W&CeI+Z$2Y@x7*fj+q&=DUfmNtw<~-d zzmaZ_y_o+6gV!IxzfS%R8olGc=lnhYKl}e6@E-*J??B-5!y+BZXXq3F3;cd9IKB z)sQ>1<4(YXppCyjbEr?dm3zWU@P;#|hu;^uv^#7SW+|J73ha$n6C z)BP>oc79E9Yv;xFq)|@p20YxW*b3GIOKhwd=hQd3>w3OTg5sKa2x`=Zb-6m8zL$6W zZAodC=U>`G5!Ln%5B{w=BS#wD11*$^9V{=t%*2{g-;^?9=76`HjuF;SJ(H-#gHHkE zi5wO2-R!+c`e`IYu-~oZQAX`Wbv1|08SAhp(b?Bjd7-#U23FrD(YbPw0VzzvnT(x{ zGgOEY%m_&yZxacHE2*3G-$vx<5cPXXS7`mI9%=xGObXY6o$ntZ^|QYh_D@bJ^!dv+ z83_4F(@~}kRhKceh6%;4{^}M}oSj)ebua#`rh3aB07o!k*WX&Bv)5|!9EmH&;9$gC z+(Y~;abY~G1-uhm`g;h@bo`d{^gp85_XmQ~kX>|tmJuX7$s7ZqA~ zP}471;`ZHL*RPcrIY_=Mi`WvBq?0BKHGN+w^cI_!e*j>mgN;yuPriiW19Kbm!V+T#NZ|)aVf@2;&q=rUQZH4<)r8?IxDM{T4UDCC z2={f|A3IrO0n?%{GG_UAS}Jfpik4{y;NH%WH|W2I{{VjWMtJlAXO-_JT!K#Vk9B_W z1a`V6>@ufl3s}+abQ~TI-pvNua?qV*uN~P@3iBjhwhK-#@nd%uR>1X=3Z$pMq}u%u zRA9erECri%6UQnK9Eq)QKfxH>SjLqT8TKX09i_a5NpeW&RSfJ0UAw?lD(DnWRmE(r zipbOOht9)e@}s7USf>!picV?9&g$&vszR_5G=D|Ca{~&8P~I#9N~vgO3|1i{Nxp=c z0PI$yG4|j|{J1a>M22Yq@=%+egX^&n_HMWZm?>F#TKv=ed*dJ{l2r^N{MdYX18LhW zVKqj-%A7w)bd1t{Q6@A>CrcZBhw8BRp4S{FB3j0WhKAmdzmhF$JSTnlI^@n=syx4s z`wF%x96zGHeeBowPgfo7-!s8g5oZm@Htm)>JfQ`?1SEUw^q?f1lr{pIH=5hOrk67K z*ruD-h7#VOGBgIOQ$z=7d`0Xru!)=Es6Q-n0!pR-GJ*x;!?^h$N$sX?`Z0E-X8wew z!|Tw#--+iBK)xY_WVl|P9VgsvamaWoM!Od<7VFC3#YY6U6*Ss?eRy0%UqQ2<>FB^| zYl>XeaRdD*i4}walw@U0Yn@+Dy~48VRy;A2avpAjtWVfF67W#4hy0a|yKd^}LVWsm z=??jFik~A$if`~{Mk)1fP6B6o7yLYpNco;tWMd--hjV&LSN5V{B=Lk>W^`O+6p$X1 zsViUA*^>%E0ixHl{?tA$;nfJ2`Px?c>p} zq9`~rxADnl5K;OjA6?1h=;-!N;>rilc1^(_2~Wr=mcQhZeSE_^a%3+~n+EhZ3Vw46 zUYa6sD1)I1XZ68M^`o3Ci?acjQBWgOvGlfnY{; z)C4p%6}!NnF7`)7m9qvl8=x@RGfWc-7J#JZrgjmH(mV!kTyg4UJ~&rREBM^=KbF6eX+3q ziKK~IT{Yk(8wDI^u&=v%ZHw|Vv8dfU^*}4+l?{X>=qB4vvE`taFT0GjVWSSpCLRcu z-TFRAd3Y`U6}5dVl`#!1nL7Y>U;5MOBu`o!U zaS;keqnUSbF$+3HaODWk0s~pUy`T{*i2ho%5qRl&>in05{^WM~-&p9sx&HL+G6~Jy z{L@1J73Tbxh1TA+*1+K+Tuac;VVXdaPDhgN!0RZFs@Sb`iSX6ZrcVP9@V+BGwOEJr zj7e3D$bJH3&MD~|6y9+-jHJ*X4;I_U0`LGPOOA$1N|(In3t`3t4cxMPJk<|inFdhS zyh&`r?||i9&$doJ6yuRHf-Oz{Y8z~=1~;F&8pkksm7jJ`Sm ziFiE3`s#%hwm_{VyXuHqw^~JhMGRp;*XS(53kPP{;uQymaW&2~X!ngKYwrPT(UL(% z9YeY68Hl|8UbzEVikTa{{wq2Z|BAbl(JatMZ^{c_XL{XjRYXXf*^Lozr>*SQ^wBG7 z#_895SxeinV9(su%Cqq*2PO)Z7+ER$%$@MLWh?}Huf26e!yq^VyJ08B#Fm zH^QvSdo)@!{5`o7ET~D1a7ZKR6u&KN`nu;b#Dw3F2LWWFGq z)p0Y&CrY{7>1wB|U(Wit!tAHhIOLV9CW6$WUv^R5kySRf(C7xj!AElWyLg*0lBNZh zvV1GJ{s1%#QqCx?hpzQ28qW-NCi~qx0L6&bmYbahg~qU@kvhsZ}yxHNCnjav`CxC|FmrL~go>GE0}S zRjtWZO1VHg?>Br-e46sgL?N!G$@+OF)NJ3hF;WncI#;=6GNXFxMsOx(&h+2CW&Qwa z%6)(TX8h1?0I?FP&|CQ<_an3R2QsJ=t3*vzX=PhK6TR$JjcO6w`pMGCS>K- z9`uF9?%&%Cb76vQAjr)N!;Xt`=|!gt0SzDxGc!H@=jQUDv-}+@6ze#Cn;uSigTWNVH}9`&`Rd#ULczRRs{~XwD4VAdnxAD{kqJ?gokT89++Ni;aVF-m^2{hkTA;eE8ppD}l8IW>dzK@E8?;=iUwfB!Nd-0Zq4`)%1LP}o|xZ@j%O*Q^` znN>V@6(&`-4c`^uF_|Md=|lVr(_>G|oa3gHL8WMI*(?5BJ^T?ACvv63Ox5$R66Cvh zujdFyNbcFA>7BV@@CVl3hL&2%GSdJO<_v*@sbgLNrR$1uFy0eYE z|FAzGpPr|GIi4ba;+#)Z*LTe%q&T;G^FKUKKMl|?dv?L6sQG;OD_q1nX>%bY<8F&) z@i<0zWlNa#S85p1$LSIoo45PcUgKE9VlaN0OR5_n+*d+#FZDXNWbU+VbKmQf#PKjp z7ryLr2x*Ho+&%B+Hx0X6|1oOEhy`Kp-KDJp1hj3!6vF1Q=wv_ij-(;qy0??Q#W-nZ zl%%nRTNWW0H9xAhZ0HU(%3@(E;eIRZte!pp58#eqs-?S3JUk&^CgwJc?XyavEXZ;k z6+dw+G;>4FtI8PirxUTRXLK~|Vjg$7om;GKa*!SiP zLE+y#ZkRPKZ|b+2luaa5aVc_K{6%m!v`~)x@<~A?r0FLOXt{4!CAn%=9Y^|ES0XL zSFwA>L-K%MKs6crN+XfKezSJkG33z#=@=+;GaPUHyceb*a<*WPB=H<{FH__xax)*F zhU1iCEPyBwLhbWGAh8TsT+QpBF9P{7zl=Cq*k;Ry%j&N5X8Vssde3Fk>h{8@^vfMWPS*BfxC=Y{-Bm}P>HK$0WT^aj(2#8*QvglCpV)qRV zamta~!_LxvHxbtI1QpF>V3&10UFr*_DByG?+rY50Z)+g&yDEwd(cq$NqJo5wUBwQB z*aLNQRJINcaS+@%vH%r<85T8@Ix~3~?^H0S!`IWOn4WH6C3l zG@Ru<{)m1rag3L6eAxjwUL_&L*i*#kq~d3M()+ou@?##KhJoW7sh>ldt(dTw)Yl(V zkmONCvN~;WAt-$g(ACAI=^j3%3J~zSahgKPuDGXn6njt%U!dJ(IK zapLk}F@0>a+y!f*2G1q4voLD4=q6lk7m#qS#YDpK$e!qL$oSuxNZm3?gRbQ)SPpi$ zQ%}zOr{@>Kyu!K=!OtXB;eMUXv+PWdO1i}6wFvPR3Q)y?iaJ7#rItdL5@0jsizT@~ z;J+bok{OK^iij4=`Q>w<-u}YWQ`F#k_E9YhB+5(2<`(!#a1Tje+n&ZZMRgAkq2g%# z#fG7VM#>}zaj}W008v6Zy$`|wVbsgb=Y%oGMF_ZUOlpT( z0Z_6Kz~~uou&%0tVWm5&$Bkm=8JTyIOCad+S7+e<{OySJ#tp__Ge94Z3bFQHsBr!d4$)Pk{J-hwKnixu(k67~D@7D~0qVQegXo%l&Q2gw(9 z^#bq*vVN@%^D*eqWI9~-$@p_x`Z!_*ykjQ2=KV?3+(e>VZlXg2u3(+*Hz78PQc&@y zr+wukAr2K6;|x8geUWJkkp35nJGc>yV`C=+^HFY2oOE;KV(}~vRU1C$dZZ9CEJqC4 z5diHG2y<=S6z?p8FTO~Z(Qq;nT)AL1`*m4TOm*BCpuQV~>=qWXwz-?%MU}BC`L6O? zl{nB>Nu)K!Zy-(DbMSk>ikQ|QCA)+NVXzJGu?1oNS|W;xI4^`g97^Xad8Tdz5X$Ky zzO`tlP4X33iIbp~!R62PRBwb^?2RtQ;5y`?uNVJFE3icp!G==8HV+ zh|!{jDw-^Up^%@{|9q^WthuPBU*xS-_fP&Bsw_3Bop3)lZwF~~9uh2pN+C|BJD*{W zM=&1)}XP z$DXNuz(^;IjMg-~&59)gfL%|(ywb|}1O6vAv|M$i&Dv~BOLGbOq_Eqg`}>{6{Q(Me z1I!LF!^?+V2>C-(Uj8iQyfhA`N`qr=lRQ<>3Xn{dUD!51uN&;+A58ZOulPNVLNW%W z7L4+$Zn1-`29;F7yHODJ2TDpLeG+5ll^VN}gkky_7DKKvbO{A<%$pFQ%6#@i)~c$FOT9@y*Ny^)RyonH(C^rlO*ITVrFp#{ZW$Hj|tWcJ(0 z^oNJ=pSfR$`C9;a zg=y=8AQG$R00Pm&M$;9I%dn<{^co}+qZL5^0Bs+ zB`EqgHU#WgQpkuo4=2^p%DbqF7gbo_WT#`fWZE_xAsSv28Zv zl7Jmj6lw}rQA_6}AjPAUVjr}%@cG8?SPWOmVDO+b>gdYH=f7j?D2YeQ(9 z=Sq-W!vO_vZ0%Eqm-K1kz5ps31I-hq4ql<3Y82(7@u8&b!Qdqq#W6G+ubq%5vepWK z8U`g_tR7deoAWg>Tvs@-ZP6S{Ky2gaQn6V!cDM3;!Mu5Fr3s?z*p9;LZ;OL0b)EzH z#P{j1wT{aTu1`qqFO-W;A(KC=Dl`jeTBazhPQw3d7OBEByS7Ymy%cc0NL19y%!jA- zm^OT^Y{Gx03nmB_RHPHFCJg+LEb%L*d$!b)|+^5#+F`+U@9yu+*FF@|NA zN>69@ZJhonS#@drXTD!5RTnO^*aUAVp|GZRN1RtxIHCY!Nyz$AXx4#4r{Bz2QNIcw znmnQ5HYr6K8d^Y@;895PtdpQOZB7wrg}3lZkkIeX)YUqKgd%e{eh3{nI?v>q)$s`Rd zM@fLv?LFp21<6_(g5tZ!K2C_o3|>2sdISF# zft(+S(dx6DA8k!*sH}R8=@BX%XEL3fI1bwzdH0*`euzUUN+#%7X_Jeqa(VxS9bU6) zU{?CFJg(~%P8ybaQg`tD1Pz7JX)rBjT3EtGUMt3%GObu$O;`J+3I~)1HXyI;zjk@f zdQ0Imv(1J%l}<#bPg+i>>b)Lt%SBCzoOQRIb3!0dgNY9%VN{2UmoWe#8JF(zI}z&Y zxC?1XYbGOQyv>dXFfl^b00ri=5)dj^N;2P2mJ&`QnA_?Qywl_OGGhVLj_*nd;9;W~ zAK|DHNBl_6nqMNmYdR@-d{X@xC&td7y@yOm;ftIM$mlK^W4ZZZ4Y}Y@z%WK-SKQ>B39y`yn%-Sk{zhYk^)V)5VR?n{f%#_-Ayco zKdtVL*1D=6_fq*qziw!Bd1b$DN^mDKe0b$NdKs#Q)s2|n=TjIhA}?7NWgr^76k?Sv z4e2XYWC%EtieD0XxgXVSH7tKfdh(u-vw1KbGOYv`hH@XwV?I?YOfH*083-%4Iv$6# zhXOcw3ea$#A}j2(y3#&8g^Q|KDEt{w;Yd3r?hm;r&>_pNRi;p|2Pn(R@a(Bw#* z7Vkt2c+R(~&H@2*5qS{DDFBtkp4spd6F}k)2*)f~AAD*^a1Q_=Lqb6aafM}n35pjB zTcg(e&LWv1K^}qf>ts`&v~dc>zPA28_iHfE4|EoIg8sz`5ecQIvH84G)|$8Qg->92 zp43lLP4df>$-=+J09?|G3T3!gy&8wqNGm{NC%ElBn$1dB8s}+YWzH}V3Wu<$CqQn*33Hz9m3SG)V^OUEm#(*M*ISANn&Po15VCu_~@&Z=%`I^W>bZOPCWWOY?G`s3<(h z6pizi|apdGy9HOG0Lo z){gPuV38x)xlnb>E3BXv>Xw9RI}7TdL^sxp0x2T+u$`RTP$S2ttz`SM^Ac~?n)CwA zGwJ18x{BJ^d!D(0Q)fGfor5`ppc5j$1FP>#vExMYRoxcPLlJjqXz6T03izuGMu}1T zjkB#9M6Bx@oK?W-hQs*yQ56?5X5sCZLa|SF%CI!PG;`)`Z{Fc)%HKiM9nGmxLlP4kM{bR-i^V9Kj$J(NyezXNl_`ODbBtr_J%NXH> zFpBaBB1&TYazT$A)V1gn4nc)t9xlQxl8`^2qMp4fOR!@=!hd*O*|t#HE4Mr9o?dbu zg`2uL*ma{-z3Qg^PPyFc6p$F2LjL8LvI@-*oVAYUDF#(aMtq3sTtyA~G1l%*U$nXi z@{C8y8TwqTQCJ8bP*E@B93WENqTEi|7OIVF2?{X4Lxb>fgOM-FVm6WS51tYSwX>ee z*fiON)%i^vvzHZJZDIJJRxyBgMX8q0-eyY7H!51uS0aR$BBG2heJ;zF;Sn2WY3UYJ zE(E(83clMX*D3psYKg3mw8YTST!9Ro?yAB5{aTvpL{mycSLsw(4pwA`Mc8XXYW!{u zP5J(JpYzJ9;$TRmGX*ymUMtH21d%-siuH)SsZA)bImy2um3PWb4MZyqAoq;yv_lcu zA_2B*DQQHpPjF@v?;p0>RgweU7XD@MS>&E-2SJsR%g{B1N9|;q2l=GGzCD2o)xIu41vNs$$z+|_ zWbsl_FjlTSA>C<($ikq?cY-D~7GTv|Tw$OZ&cU(Ki3VGz{*-riziw~c>*k1|<{K$E zKVw_ome;wHz>{+PqOH9_NsCA2?UO^m8N3O?3{%DxB|gNQ&c)~B1&)jfJ$=tw3vDbk zwpg~KiiNFJb4_>@W)Q{HP6e6=uR)Wlro~C$3F_wuV-1an6h40*fmm5x^i9MlypMHM7m5m0>wd*%zGZw1d)O7}MM zLz6;a-j)Jie{o?$OKC%J9N-We`X~94@GN8XI!y&XxRxaLA4z{yCz%8>C+#_6^Vp@M zb^FOkA(tB<(^Q^(8iAUYmHsJuU9#|kpAz;A_$555$%Cu8(~IlDppDhba`eOCvY1iM>VWD(qX#AAqWa^*NAV9cDuOG&e<4rOI!JA@r8y;fv4#Zwor z15!5q48zctB`z7L{c$W5rQkV6a~QvQ%uAGYv=}^ru_DZ~wO6}bL>LCtD1m4aW8C$> zOCDONZ}rdxdK8dw`>!yGMX?%2yQmO@eH`-_J*ftQryw{2$ZfmWiOS`bDG(Tyl$7+p z$X3jz788IcP+(+(T`z$2oB2f!hV-YCs1uKQRP_>x(3lJ00@c99d*-DS(#%@tai*2 z1|?AhvT>AAQDL7MR(bq0?tu6~}A+X#g z3<$0^(l7Sa4f@`t@O#u77l`-U;%9M>!s@C5x(6fjuu33oSa%MNghzo~ap+B&vG%2F zPBE?@<*R6T^JxPHS zqfW&U@zZ{qYg5}exxgAIH^(AgZ-*GZBZ{A`WOoFSHEjzDLEQ*mE`$iDd?nj(;7{m< z-wpQL5eymVK$hZ8T#LqnZt}D~gL1g+Uf%imY~fm)!AYc&BO^tm(;Ptt+Cz3p7`ez4 zs4DT`NbrO6-=Kp8sH5vFftj`KLpp2TSj&psPzJ@Pf!uB%AlqRHsKAsIxT$w<>VX_3 zvLU33%t-ZZ=7@ll#K0qqVG1X;E!4auW<2r8jA(=emCbw;1eW%}Z~x5)a1O-1lv$qX zJr+{Tg9a15mGqJSv2`y7fCgg}o(0h*4mRF$M1UPMlpwGunjamp$X_#rk>S)RvL(i= zHBh2+!Gn(hr!X`|C#r;0m+uaQn#5K~3|FUJ4crQi`}_k{N`$S*K*H#pB|02Y+;tYo zL^wqFR?q_qHvq)ye%TJcwXWWl=$T zP`_cS2AIohEE_;PCw1Wf9pxtn{B8nT{ScakzaIyZyzV2@T%liBQh?|X3DGH7*eufA zXd$?1LAbvSq<4Qo_DOKkNMLv`Ks<@5;NF@G8Z0E<%kHq4{~&-@4h*di`M1n4x;sP) zG@l|e(<(s74nqY?iS-6J0H3eSyt4xWLyn3+r2dEjg>H;#Qpq)I=fsa-aPrCdT^)>3 z;MWTWm6ty{lGl4DQf36>v>s Date: Sun, 31 Aug 2025 22:04:45 +0200 Subject: [PATCH 130/153] tests --- apps/wei/tests/test_wei_algorithm_2025.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/wei/tests/test_wei_algorithm_2025.py b/apps/wei/tests/test_wei_algorithm_2025.py index e1eac7e0..97fac3b8 100644 --- a/apps/wei/tests/test_wei_algorithm_2025.py +++ b/apps/wei/tests/test_wei_algorithm_2025.py @@ -53,9 +53,11 @@ class TestWEIAlgorithm(TestCase): birth_date='2000-01-01', ) information = WEISurveyInformation2025(registration) - for j in range(1, 21): + for j in range(1, 1 + NB_WORDS): setattr(information, f'word{j}', random.choice(WORDS['list'])) - information.step = 20 + for q in WORDS['questions']: + setattr(information, q, random.choice(list(WORDS['questions'][q][1].keys()))) + information.step = len(WORDS['questions']) + 2 information.save(registration) registration.save() @@ -87,7 +89,7 @@ class TestWEIAlgorithm(TestCase): setattr(information, f'word{j}', random.choice(WORDS['list'])) for q in WORDS['questions']: setattr(information, q, random.choice(list(WORDS['questions'][q][1].keys()))) - information.step = len(WORDS['questions']) + 1 + information.step = len(WORDS['questions']) + 2 information.save(registration) registration.save() survey = WEISurvey2025(registration) From 8700144dea6dfb820450add7158168a719609efd Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Wed, 24 Sep 2025 21:48:56 +0200 Subject: [PATCH 131/153] Permissions --- apps/permission/fixtures/initial.json | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index e642c4e6..f3d4cf33 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -4833,7 +4833,10 @@ 221, 247, 258, - 259 + 259, + 260, + 263, + 265 ] } }, @@ -4845,7 +4848,6 @@ "name": "Pr\u00e9sident\u22c5e de club", "permissions": [ 62, - 135, 142 ] } @@ -5122,7 +5124,8 @@ 289, 290, 291, - 293 + 293, + 298 ] } }, @@ -5233,7 +5236,9 @@ 168, 176, 177, - 197 + 197, + 311, + 319 ] } }, @@ -5313,7 +5318,8 @@ 289, 290, 291, - 293 + 293, + 298 ] } }, From 5d8720cf462ac91fdcb7c83b337e0e9e7f41885a Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Wed, 24 Sep 2025 22:22:23 +0200 Subject: [PATCH 132/153] Phone input without permission fixed --- apps/member/templates/member/profile_update.html | 4 ++-- apps/wei/templates/wei/weiregistration_form.html | 4 ++-- note_kfet/templates/registration/signup.html | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/member/templates/member/profile_update.html b/apps/member/templates/member/profile_update.html index e0975c22..36029cb3 100644 --- a/apps/member/templates/member/profile_update.html +++ b/apps/member/templates/member/profile_update.html @@ -29,8 +29,8 @@ SPDX-License-Identifier: GPL-3.0-or-later const input = document.querySelector("input[name='phone_number']"); const form = document.querySelector("#profile-form"); - if (!input || !form) { - console.error("Input phone_number ou form introuvable."); + if (!input || !form || input.type === "hidden" || input.disabled || input.readOnly) { + return; } const iti = window.intlTelInput(input, { diff --git a/apps/wei/templates/wei/weiregistration_form.html b/apps/wei/templates/wei/weiregistration_form.html index 4964814d..dc5f66e5 100644 --- a/apps/wei/templates/wei/weiregistration_form.html +++ b/apps/wei/templates/wei/weiregistration_form.html @@ -28,8 +28,8 @@ SPDX-License-Identifier: GPL-3.0-or-later const input = document.querySelector("input[name='emergency_contact_phone']"); const form = document.querySelector("#registration-form"); - if (!input || !form) { - console.error("Input phone_number ou form introuvable."); + if (!input || !form || input.type === "hidden" || input.disabled || input.readOnly) { + return; } const iti = window.intlTelInput(input, { diff --git a/note_kfet/templates/registration/signup.html b/note_kfet/templates/registration/signup.html index 5428ff95..71fe2511 100644 --- a/note_kfet/templates/registration/signup.html +++ b/note_kfet/templates/registration/signup.html @@ -39,8 +39,8 @@ SPDX-License-Identifier: GPL-3.0-or-later const input = document.querySelector("input[name='phone_number']"); const form = document.querySelector("#profile_form"); - if (!input || !form) { - console.error("Input phone_number ou form introuvable."); + if (!input || !form || input.type === "hidden" || input.disabled || input.readOnly) { + return; } const iti = window.intlTelInput(input, { From 47d2476b51c6a10f4e95230c14d657a3af5227f0 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Thu, 25 Sep 2025 00:08:56 +0200 Subject: [PATCH 133/153] Allow to view activity entries on Activity tab --- .../activity/includes/activity_info.html | 8 +++++++- apps/activity/templatetags/__init__.py | 0 apps/activity/templatetags/dict_get.py | 12 ++++++++++++ apps/activity/views.py | 19 +++++++++++++++++++ apps/permission/fixtures/initial.json | 16 ++++++++++++++++ 5 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 apps/activity/templatetags/__init__.py create mode 100644 apps/activity/templatetags/dict_get.py diff --git a/apps/activity/templates/activity/includes/activity_info.html b/apps/activity/templates/activity/includes/activity_info.html index f9ea634b..4565a086 100644 --- a/apps/activity/templates/activity/includes/activity_info.html +++ b/apps/activity/templates/activity/includes/activity_info.html @@ -1,7 +1,7 @@ {% comment %} SPDX-License-Identifier: GPL-3.0-or-later {% endcomment %} -{% load i18n perms pretty_money %} +{% load i18n perms pretty_money dict_get %} {% url 'activity:activity_detail' activity.pk as activity_detail_url %}
                @@ -53,6 +53,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
                {% trans 'opened'|capfirst %}
                {{ activity.open|yesno }}
                + {% if show_entries|dict_get:activity %} +

                + {{ entries_count|dict_get:activity }} + {% if entries_count|dict_get:activity >= 2 %}{% trans "entries" %}{% else %}{% trans "entry" %}{% endif %} +

                + {% endif %} + +{{ block.super }} {% endblock %} diff --git a/apps/activity/views.py b/apps/activity/views.py index 9011f0e1..9ef0d4f2 100644 --- a/apps/activity/views.py +++ b/apps/activity/views.py @@ -67,27 +67,49 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin tables = [ lambda data: ActivityTable(data, prefix="all-"), lambda data: ActivityTable(data, prefix="upcoming-"), + lambda data: ActivityTable(data, prefix="search-"), ] extra_context = {"title": _("Activities")} def get_queryset(self, **kwargs): - return super().get_queryset(**kwargs).distinct() + """ + Filter the user list with the given pattern. + """ + return super().get_queryset().distinct() def get_tables_data(self): - # first table = all activities, second table = upcoming + # first table = all activities, second table = upcoming, third table = search + + # table search + qs = self.get_queryset().order_by('-date_start') + if "search" in self.request.GET and self.request.GET['search']: + pattern = self.request.GET['search'] + + # check regex + valid_regex = is_regex(pattern) + suffix = '__iregex' if valid_regex else '__istartswith' + prefix = '^' if valid_regex else '' + qs = qs.filter(Q(**{f'name{suffix}': prefix + pattern}) + | Q(**{f'organizer__name{suffix}': prefix + pattern}) + | Q(**{f'organizer__note__alias__name{suffix}': prefix + pattern})) + else: + qs = qs.none() + search_table = qs.filter(PermissionBackend.filter_queryset(self.request, Activity, 'view')) + return [ self.get_queryset().order_by("-date_start"), Activity.objects.filter(date_end__gt=timezone.now()) .filter(PermissionBackend.filter_queryset(self.request, Activity, "view")) .distinct() - .order_by("date_start") + .order_by("date_start"), + search_table, ] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) tables = context["tables"] - for name, table in zip(["table", "upcoming"], tables): + for name, table in zip(["all", "upcoming", "table"], tables): context[name] = table started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all() From 9907cfbd860985530f6af2329524918c62acb911 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 27 Sep 2025 01:17:33 +0200 Subject: [PATCH 135/153] Autocomplete Credit reason with 'Rechargement note' --- apps/note/static/note/js/transfer.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/note/static/note/js/transfer.js b/apps/note/static/note/js/transfer.js index 509d9b48..ce6ff6ff 100644 --- a/apps/note/static/note/js/transfer.js +++ b/apps/note/static/note/js/transfer.js @@ -66,6 +66,8 @@ $(document).ready(function () { arr.push(last) last.quantity = 1 + + if (last.note.club) { $('#last_name').val(last.note.name) @@ -111,7 +113,8 @@ $(document).ready(function () { dest.removeClass('d-none') $('#dest_note_list').removeClass('d-none') $('#debit_type').addClass('d-none') - + $('#reason').val('') + $('#source_note_label').text(select_emitters_label) $('#dest_note_label').text(select_receveirs_label) @@ -134,6 +137,7 @@ $(document).ready(function () { dest.val('') dest.tooltip('hide') $('#debit_type').addClass('d-none') + $('#reason').val('Rechargement note') $('#source_note_label').text(transfer_type_label) $('#dest_note_label').text(select_receveir_label) @@ -162,6 +166,7 @@ $(document).ready(function () { dest.addClass('d-none') dest.tooltip('hide') $('#debit_type').removeClass('d-none') + $('#reason').val('') $('#source_note_label').text(select_emitter_label) $('#dest_note_label').text(transfer_type_label) From 0962a3735eb025cb811d9071e3691ec2d74229b4 Mon Sep 17 00:00:00 2001 From: Ehouarn Date: Sat, 27 Sep 2025 13:19:48 +0200 Subject: [PATCH 136/153] Better Food search --- apps/food/tables.py | 18 +++++++++++++++++- apps/food/templates/food/food_list.html | 25 +++++++++++++++++++++++++ apps/food/views.py | 9 +++++++-- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/apps/food/tables.py b/apps/food/tables.py index 7789ad76..5b854e64 100644 --- a/apps/food/tables.py +++ b/apps/food/tables.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later import django_tables2 as tables +from django.utils.translation import gettext_lazy as _ from .models import Food @@ -10,10 +11,25 @@ class FoodTable(tables.Table): """ List all foods. """ + qr_code_numbers = tables.Column(empty_values=(), verbose_name=_("QR Codes"), orderable=False) + + date = tables.Column(empty_values=(), verbose_name=_("Arrival/creation date"), orderable=False) + + def render_date(self, record): + if record.__class__.__name__ == "BasicFood": + return record.arrival_date.strftime("%d/%m/%Y %H:%M") + elif record.__class__.__name__ == "TransformedFood": + return record.creation_date.strftime("%d/%m/%Y %H:%M") + else: + return "--" + + def render_qr_code_numbers(self, record): + return ", ".join(str(q.qr_code_number) for q in record.QR_code.all()) + class Meta: model = Food template_name = 'django_tables2/bootstrap4.html' - fields = ('name', 'owner', 'allergens', 'expiry_date') + fields = ('name', 'owner', 'qr_code_numbers', 'allergens', 'date', 'expiry_date') row_attrs = { 'class': 'table-row', 'data-href': lambda record: 'detail/' + str(record.pk), diff --git a/apps/food/templates/food/food_list.html b/apps/food/templates/food/food_list.html index bd54ece9..8e52a00a 100644 --- a/apps/food/templates/food/food_list.html +++ b/apps/food/templates/food/food_list.html @@ -34,6 +34,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
                +
                + +
                @@ -114,7 +120,26 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endif %} +