diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 97110ecd..2ba35d31 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -7,25 +7,10 @@ stages: variables: GIT_SUBMODULE_STRATEGY: recursive -# Debian Buster -py37-django22: +# Ubuntu 22.04 +py310-django42: stage: test - image: debian:buster-backports - before_script: - - > - apt-get update && - apt-get install --no-install-recommends -t buster-backports -y - python3-django python3-django-crispy-forms - python3-django-extensions python3-django-filters python3-django-polymorphic - 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 py37-django22 - -# Ubuntu 20.04 -py38-django22: - stage: test - image: ubuntu:20.04 + image: ubuntu:22.04 before_script: # Fix tzdata prompt - ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone @@ -37,12 +22,12 @@ py38-django22: 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 py38-django22 + script: tox -e py310-django42 -# Debian Bullseye -py39-django22: +# Debian Bookworm +py311-django42: stage: test - image: debian:bullseye + image: debian:bookworm before_script: - > apt-get update && @@ -52,11 +37,11 @@ py39-django22: 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 py39-django22 + script: tox -e py311-django42 linters: stage: quality-assurance - image: debian:buster-backports + image: debian:bookworm before_script: - apt-get update && apt-get install -y tox script: tox -e linters diff --git a/.gitmodules b/.gitmodules index 925f7178..ffc15af5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "apps/scripts"] path = apps/scripts - url = https://gitlab.crans.org/bde/nk20-scripts.git + url = https://gitlab.crans.org/bde/nk20-scripts diff --git a/README.md b/README.md index 98fe3713..7297a1b6 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Bien que cela permette de créer une instance sur toutes les distributions, (env)$ ./manage.py makemigrations (env)$ ./manage.py migrate (env)$ ./manage.py loaddata initial - (env)$ ./manage.py createsuperuser # Création d'un utilisateur initial + (env)$ ./manage.py createsuperuser # Création d'un⋅e utilisateur⋅rice initial ``` 6. Enjoy : diff --git a/apps/activity/admin.py b/apps/activity/admin.py index 88496361..3355d1aa 100644 --- a/apps/activity/admin.py +++ b/apps/activity/admin.py @@ -5,7 +5,7 @@ from django.contrib import admin from note_kfet.admin import admin_site from .forms import GuestForm -from .models import Activity, ActivityType, Entry, Guest +from .models import Activity, ActivityType, Entry, Guest, Opener @admin.register(Activity, site=admin_site) @@ -45,3 +45,11 @@ class EntryAdmin(admin.ModelAdmin): Admin customisation for Entry """ list_display = ('note', 'activity', 'time', 'guest') + + +@admin.register(Opener, site=admin_site) +class OpenerAdmin(admin.ModelAdmin): + """ + Admin customisation for Opener + """ + list_display = ('activity', 'opener') diff --git a/apps/activity/api/serializers.py b/apps/activity/api/serializers.py index e4bc50b8..31c23cb8 100644 --- a/apps/activity/api/serializers.py +++ b/apps/activity/api/serializers.py @@ -1,9 +1,11 @@ # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers +from rest_framework.validators import UniqueTogetherValidator -from ..models import Activity, ActivityType, Entry, Guest, GuestTransaction +from ..models import Activity, ActivityType, Entry, Guest, GuestTransaction, Opener class ActivityTypeSerializer(serializers.ModelSerializer): @@ -59,3 +61,17 @@ class GuestTransactionSerializer(serializers.ModelSerializer): class Meta: model = GuestTransaction fields = '__all__' + + +class OpenerSerializer(serializers.ModelSerializer): + """ + REST API Serializer for Openers. + The djangorestframework plugin will analyse the model `Opener` and parse all fields in the API. + """ + + class Meta: + model = Opener + fields = '__all__' + validators = [UniqueTogetherValidator( + queryset=Opener.objects.all(), fields=("opener", "activity"), + message=_("This opener already exists"))] diff --git a/apps/activity/api/urls.py b/apps/activity/api/urls.py index 5906705b..4ff977fe 100644 --- a/apps/activity/api/urls.py +++ b/apps/activity/api/urls.py @@ -1,7 +1,7 @@ # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from .views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet +from .views import ActivityTypeViewSet, ActivityViewSet, EntryViewSet, GuestViewSet, OpenerViewSet def register_activity_urls(router, path): @@ -12,3 +12,4 @@ def register_activity_urls(router, path): router.register(path + '/type', ActivityTypeViewSet) router.register(path + '/guest', GuestViewSet) router.register(path + '/entry', EntryViewSet) + router.register(path + '/opener', OpenerViewSet) diff --git a/apps/activity/api/views.py b/apps/activity/api/views.py index 97e6c40d..afa41ea7 100644 --- a/apps/activity/api/views.py +++ b/apps/activity/api/views.py @@ -1,12 +1,15 @@ # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +from api.filters import RegexSafeSearchFilter from api.viewsets import ReadProtectedModelViewSet +from django.core.exceptions import ValidationError from django_filters.rest_framework import DjangoFilterBackend -from rest_framework.filters import SearchFilter +from rest_framework.response import Response +from rest_framework import status -from .serializers import ActivitySerializer, ActivityTypeSerializer, EntrySerializer, GuestSerializer -from ..models import Activity, ActivityType, Entry, Guest +from .serializers import ActivitySerializer, ActivityTypeSerializer, EntrySerializer, GuestSerializer, OpenerSerializer +from ..models import Activity, ActivityType, Entry, Guest, Opener class ActivityTypeViewSet(ReadProtectedModelViewSet): @@ -29,7 +32,7 @@ class ActivityViewSet(ReadProtectedModelViewSet): """ queryset = Activity.objects.order_by('id') serializer_class = ActivitySerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['name', 'description', 'activity_type', 'location', 'creater', 'organizer', 'attendees_club', 'date_start', 'date_end', 'valid', 'open', ] search_fields = ['$name', '$description', '$location', '$creater__last_name', '$creater__first_name', @@ -47,7 +50,7 @@ class GuestViewSet(ReadProtectedModelViewSet): """ queryset = Guest.objects.order_by('id') serializer_class = GuestSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['activity', 'activity__name', 'last_name', 'first_name', 'inviter', 'inviter__alias__name', 'inviter__alias__normalized_name', ] search_fields = ['$activity__name', '$last_name', '$first_name', '$inviter__user__email', '$inviter__alias__name', @@ -62,7 +65,36 @@ class EntryViewSet(ReadProtectedModelViewSet): """ queryset = Entry.objects.order_by('id') serializer_class = EntrySerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['activity', 'time', 'note', 'guest', ] search_fields = ['$activity__name', '$note__user__email', '$note__alias__name', '$note__alias__normalized_name', '$guest__last_name', '$guest__first_name', ] + + +class OpenerViewSet(ReadProtectedModelViewSet): + """ + REST Opener View set. + The djangorestframework plugin will get all `Opener` objects, serialize it to JSON with the given serializer, + then render it on /api/activity/opener/ + """ + queryset = Opener.objects + serializer_class = OpenerSerializer + filter_backends = [RegexSafeSearchFilter, DjangoFilterBackend] + search_fields = ['$opener__alias__name', '$opener__alias__normalized_name', + '$activity__name'] + filterset_fields = ['opener', 'opener__noteuser__user', 'activity'] + + def get_serializer_class(self): + serializer_class = self.serializer_class + if self.request.method in ['PUT', 'PATCH']: + # opener-activity can't change + serializer_class.Meta.read_only_fields = ('opener', 'acitivity',) + return serializer_class + + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + try: + self.perform_destroy(instance) + except ValidationError as e: + return Response({e.code: str(e)}, status.HTTP_400_BAD_REQUEST) + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apps/activity/forms.py b/apps/activity/forms.py index 6e1c35ff..1070f19d 100644 --- a/apps/activity/forms.py +++ b/apps/activity/forms.py @@ -4,13 +4,14 @@ from datetime import timedelta from random import shuffle +from bootstrap_datepicker_plus.widgets import DateTimePickerInput from django import forms from django.contrib.contenttypes.models import ContentType from django.utils import timezone from django.utils.translation import gettext_lazy as _ from member.models import Club from note.models import Note, NoteUser -from note_kfet.inputs import Autocomplete, DateTimePickerInput +from note_kfet.inputs import Autocomplete from note_kfet.middlewares import get_current_request from permission.backends import PermissionBackend @@ -43,7 +44,7 @@ class ActivityForm(forms.ModelForm): class Meta: model = Activity - exclude = ('creater', 'valid', 'open', ) + exclude = ('creater', 'valid', 'open', 'opener', ) widgets = { "organizer": Autocomplete( model=Club, diff --git a/apps/activity/migrations/0004_opener.py b/apps/activity/migrations/0004_opener.py new file mode 100644 index 00000000..942f5e76 --- /dev/null +++ b/apps/activity/migrations/0004_opener.py @@ -0,0 +1,28 @@ +# Generated by Django 2.2.28 on 2024-08-01 12:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('note', '0006_trust'), + ('activity', '0003_auto_20240323_1422'), + ] + + operations = [ + migrations.CreateModel( + name='Opener', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opener', to='activity.Activity', verbose_name='activity')), + ('opener', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activity_responsible', to='note.Note', verbose_name='opener')), + ], + options={ + 'verbose_name': 'opener', + 'verbose_name_plural': 'openers', + 'unique_together': {('opener', 'activity')}, + }, + ), + ] diff --git a/apps/activity/migrations/0005_alter_opener_options_alter_opener_opener.py b/apps/activity/migrations/0005_alter_opener_options_alter_opener_opener.py new file mode 100644 index 00000000..c09500e1 --- /dev/null +++ b/apps/activity/migrations/0005_alter_opener_options_alter_opener_opener.py @@ -0,0 +1,24 @@ +# 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 = [ + ('note', '0006_trust'), + ('activity', '0004_opener'), + ] + + operations = [ + migrations.AlterModelOptions( + name='opener', + options={'verbose_name': 'Opener', 'verbose_name_plural': 'Openers'}, + ), + migrations.AlterField( + model_name='opener', + name='opener', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='activity_responsible', to='note.note', verbose_name='Opener'), + ), + ] diff --git a/apps/activity/models.py b/apps/activity/models.py index 88cce457..c9f5842e 100644 --- a/apps/activity/models.py +++ b/apps/activity/models.py @@ -11,7 +11,7 @@ from django.db import models, transaction from django.db.models import Q from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from note.models import NoteUser, Transaction +from note.models import NoteUser, Transaction, Note from rest_framework.exceptions import ValidationError @@ -310,3 +310,31 @@ class GuestTransaction(Transaction): @property def type(self): return _('Invitation') + + +class Opener(models.Model): + """ + Allow the user to make activity entries without more rights + """ + activity = models.ForeignKey( + Activity, + on_delete=models.CASCADE, + related_name='opener', + verbose_name=_('activity') + ) + + opener = models.ForeignKey( + Note, + on_delete=models.CASCADE, + related_name='activity_responsible', + verbose_name=_('Opener') + ) + + class Meta: + verbose_name = _("Opener") + verbose_name_plural = _("Openers") + unique_together = ("opener", "activity") + + def __str__(self): + return _("{opener} is opener of activity {acivity}").format( + opener=str(self.opener), acivity=str(self.activity)) diff --git a/apps/activity/static/activity/js/opener.js b/apps/activity/static/activity/js/opener.js new file mode 100644 index 00000000..801f27a8 --- /dev/null +++ b/apps/activity/static/activity/js/opener.js @@ -0,0 +1,57 @@ +/** + * On form submit, add a new opener + */ +function form_create_opener (e) { + // Do not submit HTML form + e.preventDefault() + + // Get data and send to API + const formData = new FormData(e.target) + $.getJSON('/api/note/alias/'+formData.get('opener') + '/', + function (opener_alias) { + create_opener(formData.get('activity'), opener_alias.note) + }).fail(function (xhr, _textStatus, _error) { + errMsg(xhr.responseJSON) + }) +} + +/** + * Add an opener between an activity and a user + * @param activity:Integer activity id + * @param opener:Integer user note id + */ +function create_opener(activity, opener) { + $.post('/api/activity/opener/', { + activity: activity, + opener: opener, + csrfmiddlewaretoken: CSRF_TOKEN + }).done(function () { + // Reload tables + $('#opener_table').load(location.pathname + ' #opener_table') + addMsg(gettext('Opener successfully added'), 'success') + }).fail(function (xhr, _textStatus, _error) { + errMsg(xhr.responseJSON) + }) +} + +/** + * On click of "delete", delete the opener + * @param button_id:Integer Opener id to remove + */ +function delete_button (button_id) { + $.ajax({ + url: '/api/activity/opener/' + button_id + '/', + method: 'DELETE', + headers: { 'X-CSRFTOKEN': CSRF_TOKEN } + }).done(function () { + addMsg(gettext('Opener successfully deleted'), 'success') + $('#opener_table').load(location.pathname + ' #opener_table') + }).fail(function (xhr, _textStatus, _error) { + errMsg(xhr.responseJSON) + }) +} + +$(document).ready(function () { + // Attach event + document.getElementById('form_opener').addEventListener('submit', form_create_opener) +}) diff --git a/apps/activity/tables.py b/apps/activity/tables.py index 6485952a..3128aaec 100644 --- a/apps/activity/tables.py +++ b/apps/activity/tables.py @@ -7,11 +7,13 @@ from django.utils import timezone from django.utils.html import escape from django.utils.safestring import mark_safe from django.utils.translation import gettext_lazy as _ +from note_kfet.middlewares import get_current_request import django_tables2 as tables from django_tables2 import A +from permission.backends import PermissionBackend from note.templatetags.pretty_money import pretty_money -from .models import Activity, Entry, Guest +from .models import Activity, Entry, Guest, Opener class ActivityTable(tables.Table): @@ -118,3 +120,34 @@ class EntryTable(tables.Table): 'data-last-name': lambda record: record.last_name, 'data-first-name': lambda record: record.first_name, } + + +# function delete_button(id) provided in template file +DELETE_TEMPLATE = """ + +""" + + +class OpenerTable(tables.Table): + class Meta: + attrs = { + 'class': 'table table condensed table-striped', + 'id': "opener_table" + } + model = Opener + fields = ("opener",) + template_name = 'django_tables2/bootstrap4.html' + + show_header = False + opener = tables.Column(attrs={'td': {'class': 'text-center'}}) + + delete_col = tables.TemplateColumn( + template_code=DELETE_TEMPLATE, + extra_context={"delete_trans": _('Delete')}, + attrs={ + 'td': { + 'class': lambda record: 'col-sm-1' + + (' d-none' if not PermissionBackend.check_perm( + get_current_request(), "activity.delete_opener", record) + else '')}}, + verbose_name=_("Delete"),) diff --git a/apps/activity/templates/activity/activity_detail.html b/apps/activity/templates/activity/activity_detail.html index 0ba1d481..a94d1e37 100644 --- a/apps/activity/templates/activity/activity_detail.html +++ b/apps/activity/templates/activity/activity_detail.html @@ -4,11 +4,31 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endcomment %} {% load i18n perms %} {% load render_table from django_tables2 %} +{% load static django_tables2 i18n %} {% block content %}

{{ title }}

{% include "activity/includes/activity_info.html" %} +{% if activity.activity_type.manage_entries and ".change__opener"|has_perm:activity %} +
+

+ {% trans "Openers" %} +

+
+
+ {% csrf_token %} + + {%include "autocomplete_model.html" %} +
+ +
+
+
+ {% render_table opener %} +
+{% endif %} + {% if guests.data %}

@@ -22,6 +42,8 @@ SPDX-License-Identifier: GPL-3.0-or-later {% endblock %} {% block extrajavascript %} + + -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/apps/member/templates/member/picture_update.html b/apps/member/templates/member/picture_update.html index 51d05f91..60707dcb 100644 --- a/apps/member/templates/member/picture_update.html +++ b/apps/member/templates/member/picture_update.html @@ -14,6 +14,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% csrf_token %} {{ form |crispy }} + {% if user.note.display_image != "pic/default.png" %} + + {% endif %}

diff --git a/apps/member/tests/test_memberships.py b/apps/member/tests/test_memberships.py index de9f3d3d..1a59c253 100644 --- a/apps/member/tests/test_memberships.py +++ b/apps/member/tests/test_memberships.py @@ -291,7 +291,7 @@ class TestMemberships(TestCase): response = self.client.post(reverse("member:club_manage_roles", args=(self.membership.pk,)), data=dict( roles=[role.id for role in Role.objects.filter( - Q(name="Membre de club") | Q(name="Trésorier·ère de club") | Q(name="Bureau de club")).all()], + Q(name="Membre de club") | Q(name="Trésorièr⋅e de club") | Q(name="Bureau de club")).all()], )) self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) self.membership.refresh_from_db() diff --git a/apps/member/views.py b/apps/member/views.py index 7c72d3d4..348bf089 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -16,8 +16,9 @@ from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.views.generic import DetailView, UpdateView, TemplateView from django.views.generic.edit import FormMixin -from django_tables2.views import SingleTableView +from django_tables2.views import MultiTableMixin, SingleTableMixin, SingleTableView from rest_framework.authtoken.models import Token +from api.viewsets import is_regex from note.models import Alias, NoteClub, NoteUser, Trust from note.models.transactions import Transaction, SpecialTransaction from note.tables import HistoryTable, AliasTable, TrustTable, TrustedTable @@ -26,7 +27,7 @@ from permission.backends import PermissionBackend from permission.models import Role from permission.views import ProtectQuerysetMixin, ProtectedCreateView -from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\ +from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \ CustomAuthenticationForm, MembershipRolesForm from .models import Club, Membership from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable @@ -219,16 +220,20 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): if "search" in self.request.GET and self.request.GET["search"]: pattern = self.request.GET["search"] + # Check if this is a valid regex. If not, we won't check regex + valid_regex = is_regex(pattern) + suffix = "__iregex" if valid_regex else "__istartswith" + prefix = "^" if valid_regex else "" qs = qs.filter( - username__iregex="^" + pattern + Q(**{f"username{suffix}": prefix + pattern}) ).union( qs.filter( - (Q(alias__iregex="^" + pattern) - | Q(normalized_alias__iregex="^" + Alias.normalize(pattern)) - | Q(last_name__iregex="^" + pattern) - | Q(first_name__iregex="^" + pattern) + (Q(**{f"alias{suffix}": prefix + pattern}) + | Q(**{f"normalized_alias{suffix}": prefix + Alias.normalize(pattern)}) + | Q(**{f"last_name{suffix}": prefix + pattern}) + | Q(**{f"first_name{suffix}": prefix + pattern}) | Q(email__istartswith=pattern)) - & ~Q(username__iregex="^" + pattern) + & ~Q(**{f"username{suffix}": prefix + pattern}) ), all=True) else: qs = qs.none() @@ -243,7 +248,7 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): return context -class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): +class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, DetailView): """ View and manage user trust relationships """ @@ -252,13 +257,25 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context_object_name = 'user_object' extra_context = {"title": _("Note friendships")} + tables = [ + lambda data: TrustTable(data, prefix="trust-"), + lambda data: TrustedTable(data, prefix="trusted-"), + ] + + def get_tables_data(self): + note = self.object.note + return [ + note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct(), + note.trusted.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct(), + ] + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - note = context['object'].note - context["trusting"] = TrustTable( - note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all()) - context["trusted_by"] = TrustedTable( - note.trusted.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all()) + + tables = context["tables"] + for name, table in zip(["trusting", "trusted_by"], tables): + context[name] = table + context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust( trusting=context["object"].note, trusted=context["object"].note @@ -277,7 +294,7 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): return context -class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): +class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableMixin, DetailView): """ View and manage user aliases. """ @@ -286,12 +303,15 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context_object_name = 'user_object' extra_context = {"title": _("Note aliases")} + table_class = AliasTable + context_table_name = "aliases" + + def get_table_data(self): + return self.object.note.alias.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct() \ + .order_by('normalized_name') + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - note = context['object'].note - context["aliases"] = AliasTable( - note.alias.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct() - .order_by('normalized_name').all()) context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias( note=context["object"].note, name="", @@ -326,12 +346,15 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det """Save image to note""" image = form.cleaned_data['image'] - # Rename as a PNG or GIF - extension = image.name.split(".")[-1] - if extension == "gif": - image.name = "{}_pic.gif".format(self.object.note.pk) + if image is None: + image = "pic/default.png" else: - image.name = "{}_pic.png".format(self.object.note.pk) + # Rename as a PNG or GIF + extension = image.name.split(".")[-1] + if extension == "gif": + image.name = "{}_pic.gif".format(self.object.note.pk) + else: + image.name = "{}_pic.png".format(self.object.note.pk) # Save self.object.note.display_image = image @@ -407,10 +430,15 @@ class ClubListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): if "search" in self.request.GET: pattern = self.request.GET["search"] + # Check if this is a valid regex. If not, we won't check regex + valid_regex = is_regex(pattern) + suffix = "__iregex" if valid_regex else "__istartswith" + prefix = "^" if valid_regex else "" + qs = qs.filter( - Q(name__iregex=pattern) - | Q(note__alias__name__iregex=pattern) - | Q(note__alias__normalized_name__iregex=Alias.normalize(pattern)) + Q(**{f"name{suffix}": prefix + pattern}) + | Q(**{f"note__alias__name{suffix}": prefix + pattern}) + | Q(**{f"note__alias__normalized_name{suffix}": prefix + Alias.normalize(pattern)}) ) return qs @@ -507,7 +535,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): return context -class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): +class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableMixin, DetailView): """ Manage aliases of a club. """ @@ -516,11 +544,16 @@ class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context_object_name = 'club' extra_context = {"title": _("Note aliases")} + table_class = AliasTable + context_table_name = "aliases" + + def get_table_data(self): + return self.object.note.alias.filter( + PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct() + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - note = context['object'].note - context["aliases"] = AliasTable(note.alias.filter( - PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct().all()) + context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias( note=context["object"].note, name="", @@ -824,8 +857,8 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): ret = super().form_valid(form) - member_role = Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all() \ - if club.name == "BDE" else Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all() \ + member_role = Role.objects.filter(Q(name="Adhérent⋅e BDE") | Q(name="Membre de club")).all() \ + if club.name == "BDE" else Role.objects.filter(Q(name="Adhérent⋅e Kfet") | Q(name="Membre de club")).all() \ if club.name == "Kfet"else Role.objects.filter(name="Membre de club").all() # Set the same roles as before if old_membership: @@ -861,7 +894,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView): membership.refresh_from_db() if old_membership.exists(): membership.roles.set(old_membership.get().roles.all()) - membership.roles.set(Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all()) + membership.roles.set(Role.objects.filter(Q(name="Adhérent⋅e Kfet") | Q(name="Membre de club")).all()) membership.save() return ret @@ -909,10 +942,15 @@ class ClubMembersListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableV if 'search' in self.request.GET: pattern = self.request.GET['search'] + + # Check if this is a valid regex. If not, we won't check regex + valid_regex = is_regex(pattern) + suffix = "__iregex" if valid_regex else "__istartswith" + prefix = "^" if valid_regex else "" qs = qs.filter( - Q(user__first_name__iregex='^' + pattern) - | Q(user__last_name__iregex='^' + pattern) - | Q(user__note__alias__normalized_name__iregex='^' + Alias.normalize(pattern)) + Q(**{f"user__first_name{suffix}": prefix + pattern}) + | Q(**{f"user__last_name{suffix}": prefix + pattern}) + | Q(**{f"user__note__alias__normalized_name{suffix}": prefix + Alias.normalize(pattern)}) ) only_active = "only_active" not in self.request.GET or self.request.GET["only_active"] != '0' diff --git a/apps/note/api/views.py b/apps/note/api/views.py index 39b9b270..5acda17c 100644 --- a/apps/note/api/views.py +++ b/apps/note/api/views.py @@ -1,19 +1,19 @@ # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -import re from django.conf import settings from django.db.models import Q from django.core.exceptions import ValidationError from django_filters.rest_framework import DjangoFilterBackend -from rest_framework.filters import OrderingFilter, SearchFilter -from rest_framework import viewsets +from rest_framework.filters import OrderingFilter +from rest_framework import status, viewsets from rest_framework.response import Response -from rest_framework import status -from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet +from api.filters import RegexSafeSearchFilter +from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet, \ + is_regex from permission.backends import PermissionBackend -from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\ +from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer, \ TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \ TrustSerializer from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust @@ -29,7 +29,7 @@ class NotePolymorphicViewSet(ReadProtectedModelViewSet): """ queryset = Note.objects.order_by('id') serializer_class = NotePolymorphicSerializer - filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter, OrderingFilter] filterset_fields = ['alias__name', 'polymorphic_ctype', 'is_active', 'balance', 'last_negative', 'created_at', ] search_fields = ['$alias__normalized_name', '$alias__name', '$polymorphic_ctype__model', '$noteuser__user__last_name', '$noteuser__user__first_name', '$noteuser__user__email', @@ -48,10 +48,14 @@ class NotePolymorphicViewSet(ReadProtectedModelViewSet): .distinct() alias = self.request.query_params.get("alias", ".*") + # Check if this is a valid regex. If not, we won't check regex + valid_regex = is_regex(alias) + suffix = '__iregex' if valid_regex else '__istartswith' + alias_prefix = '^' if valid_regex else '' queryset = queryset.filter( - Q(alias__name__iregex="^" + alias) - | Q(alias__normalized_name__iregex="^" + Alias.normalize(alias)) - | Q(alias__normalized_name__iregex="^" + alias.lower()) + Q(**{f"alias__name{suffix}": alias_prefix + alias}) + | Q(**{f"alias__normalized_name{suffix}": alias_prefix + Alias.normalize(alias)}) + | Q(**{f"alias__normalized_name{suffix}": alias_prefix + alias.lower()}) ) return queryset.order_by("id") @@ -65,7 +69,7 @@ class TrustViewSet(ReadProtectedModelViewSet): """ queryset = Trust.objects serializer_class = TrustSerializer - filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter] + filter_backends = [RegexSafeSearchFilter, DjangoFilterBackend, OrderingFilter] search_fields = ['$trusting__alias__name', '$trusting__alias__normalized_name', '$trusted__alias__name', '$trusted__alias__normalized_name'] filterset_fields = ['trusting', 'trusting__noteuser__user', 'trusted', 'trusted__noteuser__user'] @@ -91,11 +95,11 @@ class AliasViewSet(ReadProtectedModelViewSet): """ REST API View set. The djangorestframework plugin will get all `Alias` objects, serialize it to JSON with the given serializer, - then render it on /api/note/aliases/ + then render it on /api/note/alias/ """ queryset = Alias.objects serializer_class = AliasSerializer - filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter] + filter_backends = [RegexSafeSearchFilter, DjangoFilterBackend, OrderingFilter] search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ] filterset_fields = ['name', 'normalized_name', 'note', 'note__noteuser__user', 'note__noteclub__club', 'note__polymorphic_ctype__model', ] @@ -126,18 +130,22 @@ class AliasViewSet(ReadProtectedModelViewSet): alias = self.request.query_params.get("alias", None) if alias: + # Check if this is a valid regex. If not, we won't check regex + valid_regex = is_regex(alias) + suffix = '__iregex' if valid_regex else '__istartswith' + alias_prefix = '^' if valid_regex else '' queryset = queryset.filter( - name__iregex="^" + alias + **{f"name{suffix}": alias_prefix + alias} ).union( queryset.filter( - Q(normalized_name__iregex="^" + Alias.normalize(alias)) - & ~Q(name__iregex="^" + alias) + Q(**{f"normalized_name{suffix}": alias_prefix + Alias.normalize(alias)}) + & ~Q(**{f"name{suffix}": alias_prefix + alias}) ), all=True).union( queryset.filter( - Q(normalized_name__iregex="^" + alias.lower()) - & ~Q(normalized_name__iregex="^" + Alias.normalize(alias)) - & ~Q(name__iregex="^" + alias) + Q(**{f"normalized_name{suffix}": "^" + alias.lower()}) + & ~Q(**{f"normalized_name{suffix}": "^" + Alias.normalize(alias)}) + & ~Q(**{f"name{suffix}": "^" + alias}) ), all=True) @@ -147,7 +155,7 @@ class AliasViewSet(ReadProtectedModelViewSet): class ConsumerViewSet(ReadOnlyProtectedModelViewSet): queryset = Alias.objects serializer_class = ConsumerSerializer - filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend] + filter_backends = [RegexSafeSearchFilter, OrderingFilter, DjangoFilterBackend] search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ] filterset_fields = ['name', 'normalized_name', 'note', 'note__noteuser__user', 'note__noteclub__club', 'note__polymorphic_ctype__model', ] @@ -166,11 +174,7 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet): alias = self.request.query_params.get("alias", None) # Check if this is a valid regex. If not, we won't check regex - try: - re.compile(alias) - valid_regex = True - except (re.error, TypeError): - valid_regex = False + valid_regex = is_regex(alias) suffix = '__iregex' if valid_regex else '__istartswith' alias_prefix = '^' if valid_regex else '' queryset = queryset.prefetch_related('note') @@ -179,19 +183,10 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet): # We match first an alias if it is matched without normalization, # then if the normalized pattern matches a normalized alias. queryset = queryset.filter( - **{f'name{suffix}': alias_prefix + alias} - ).union( - queryset.filter( - Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)}) - & ~Q(**{f'name{suffix}': alias_prefix + alias}) - ), - all=True).union( - queryset.filter( - Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()}) - & ~Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)}) - & ~Q(**{f'name{suffix}': alias_prefix + alias}) - ), - all=True) + Q(**{f'name{suffix}': alias_prefix + alias}) + | Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)}) + | Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()}) + ) queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \ else queryset.order_by("name") @@ -207,7 +202,7 @@ class TemplateCategoryViewSet(ReadProtectedModelViewSet): """ queryset = TemplateCategory.objects.order_by('name') serializer_class = TemplateCategorySerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['name', 'templates', 'templates__name'] search_fields = ['$name', '$templates__name', ] @@ -220,7 +215,7 @@ class TransactionTemplateViewSet(viewsets.ModelViewSet): """ queryset = TransactionTemplate.objects.order_by('name') serializer_class = TransactionTemplateSerializer - filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter] + filter_backends = [RegexSafeSearchFilter, DjangoFilterBackend, OrderingFilter] filterset_fields = ['name', 'amount', 'display', 'category', 'category__name', ] search_fields = ['$name', '$category__name', ] ordering_fields = ['amount', ] @@ -234,7 +229,7 @@ class TransactionViewSet(ReadProtectedModelViewSet): """ queryset = Transaction.objects.order_by('-created_at') serializer_class = TransactionPolymorphicSerializer - filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter] + filter_backends = [RegexSafeSearchFilter, DjangoFilterBackend, OrderingFilter] filterset_fields = ['source', 'source_alias', 'source__alias__name', 'source__alias__normalized_name', 'destination', 'destination_alias', 'destination__alias__name', 'destination__alias__normalized_name', 'quantity', 'polymorphic_ctype', 'amount', diff --git a/apps/note/forms.py b/apps/note/forms.py index 209b49d9..aa722b57 100644 --- a/apps/note/forms.py +++ b/apps/note/forms.py @@ -2,12 +2,13 @@ # SPDX-License-Identifier: GPL-3.0-or-later from datetime import datetime +from bootstrap_datepicker_plus.widgets import DateTimePickerInput from django import forms from django.contrib.contenttypes.models import ContentType from django.forms import CheckboxSelectMultiple from django.utils.timezone import make_aware from django.utils.translation import gettext_lazy as _ -from note_kfet.inputs import Autocomplete, AmountInput, DateTimePickerInput +from note_kfet.inputs import Autocomplete, AmountInput from .models import TransactionTemplate, NoteClub, Alias diff --git a/apps/note/migrations/0002_create_special_notes.py b/apps/note/migrations/0002_create_special_notes.py index 12fa8583..07935d54 100644 --- a/apps/note/migrations/0002_create_special_notes.py +++ b/apps/note/migrations/0002_create_special_notes.py @@ -18,6 +18,7 @@ def create_special_notes(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ ('note', '0001_initial'), + ('logs', '0001_initial'), ] operations = [ diff --git a/apps/note/migrations/0007_alter_note_polymorphic_ctype_and_more.py b/apps/note/migrations/0007_alter_note_polymorphic_ctype_and_more.py new file mode 100644 index 00000000..8d0e8a19 --- /dev/null +++ b/apps/note/migrations/0007_alter_note_polymorphic_ctype_and_more.py @@ -0,0 +1,25 @@ +# 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'), + ('note', '0006_trust'), + ] + + operations = [ + migrations.AlterField( + model_name='note', + 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'), + ), + migrations.AlterField( + model_name='transaction', + 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/note/tables.py b/apps/note/tables.py index 3ca2d1d4..a4e944f9 100644 --- a/apps/note/tables.py +++ b/apps/note/tables.py @@ -260,11 +260,13 @@ class ButtonTable(tables.Table): text=_('edit'), accessor='pk', verbose_name=_("Edit"), + orderable=False, ) hideshow = tables.Column( verbose_name=_("Hide/Show"), accessor="pk", + orderable=False, attrs={ 'td': { 'class': 'col-sm-1', @@ -276,7 +278,8 @@ class ButtonTable(tables.Table): delete_col = tables.TemplateColumn(template_code=DELETE_TEMPLATE, extra_context={"delete_trans": _('delete')}, attrs={'td': {'class': 'col-sm-1'}}, - verbose_name=_("Delete"), ) + verbose_name=_("Delete"), + orderable=False, ) def render_amount(self, value): return pretty_money(value) diff --git a/apps/note/templates/note/amount_input.html b/apps/note/templates/note/amount_input.html index d4873115..cbe9d160 100644 --- a/apps/note/templates/note/amount_input.html +++ b/apps/note/templates/note/amount_input.html @@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-3.0-or-later name="{{ widget.name }}" {# Other attributes are loaded #} {% for name, value in widget.attrs.items %} - {% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %} + {% if value is not False %}{{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %} {% endfor %}>
diff --git a/apps/note/templates/note/mails/negative_balance.html b/apps/note/templates/note/mails/negative_balance.html index 8c869a54..04b2dd6a 100644 --- a/apps/note/templates/note/mails/negative_balance.html +++ b/apps/note/templates/note/mails/negative_balance.html @@ -22,8 +22,8 @@

- Par ailleurs, le BDE ne sert pas d'alcool aux adhérents dont le solde - est inférieur à 0 € depuis plus de 24h. + Par ailleurs, le BDE ne sert pas d'alcool aux adhérent⋅es dont le solde + est inférieur à 0 €.

@@ -43,4 +43,4 @@ {% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %}

- \ No newline at end of file + diff --git a/apps/note/templates/note/mails/negative_balance.txt b/apps/note/templates/note/mails/negative_balance.txt index 8707bde5..638d82f6 100644 --- a/apps/note/templates/note/mails/negative_balance.txt +++ b/apps/note/templates/note/mails/negative_balance.txt @@ -22,4 +22,4 @@ virement bancaire. -- Le BDE -{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %} \ No newline at end of file +{% trans "Mail generated by the Note Kfet on the" %} {% now "j F Y à H:i:s" %} diff --git a/apps/note/tests/test_transactions.py b/apps/note/tests/test_transactions.py index a11a1962..574e3575 100644 --- a/apps/note/tests/test_transactions.py +++ b/apps/note/tests/test_transactions.py @@ -10,7 +10,7 @@ from django.urls import reverse from django.utils import timezone from permission.models import Role -from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet,\ +from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet, \ TransactionTemplateViewSet, TransactionViewSet from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \ MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note diff --git a/apps/note/views.py b/apps/note/views.py index a84e3636..7055ea12 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -13,6 +13,7 @@ from django.views.generic import CreateView, UpdateView, DetailView from django.urls import reverse_lazy from django_tables2 import SingleTableView from activity.models import Entry +from api.viewsets import is_regex from permission.backends import PermissionBackend from permission.views import ProtectQuerysetMixin from note_kfet.inputs import AmountInput @@ -89,11 +90,15 @@ class TransactionTemplateListView(ProtectQuerysetMixin, LoginRequiredMixin, Sing qs = super().get_queryset().distinct() if "search" in self.request.GET: pattern = self.request.GET["search"] + + # Check if this is a valid regex. If not, we won't check regex + valid_regex = is_regex(pattern) + suffix = "__iregex" if valid_regex else "__icontains" qs = qs.filter( - Q(name__iregex=pattern) - | Q(destination__club__name__iregex=pattern) - | Q(category__name__iregex=pattern) - | Q(description__iregex=pattern) + Q(**{f"name{suffix}": pattern}) + | Q(**{f"destination__club__name{suffix}": pattern}) + | Q(**{f"category__name{suffix}": pattern}) + | Q(**{f"description{suffix}": pattern}) ) qs = qs.order_by('-display', 'category__name', 'destination__club__name', 'name') @@ -223,7 +228,10 @@ class TransactionSearchView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView if "type" in data and data["type"]: transactions = transactions.filter(polymorphic_ctype__in=data["type"]) if "reason" in data and data["reason"]: - transactions = transactions.filter(reason__iregex=data["reason"]) + # Check if this is a valid regex. If not, we won't check regex + valid_regex = is_regex(data["reason"]) + suffix = "__iregex" if valid_regex else "__istartswith" + transactions = transactions.filter(Q(**{f"reason{suffix}": data["reason"]})) if "valid" in data and data["valid"]: transactions = transactions.filter(valid=data["valid"]) if "amount_gte" in data and data["amount_gte"]: diff --git a/apps/permission/api/views.py b/apps/permission/api/views.py index 5296e29b..39ff353c 100644 --- a/apps/permission/api/views.py +++ b/apps/permission/api/views.py @@ -1,9 +1,9 @@ # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later -from api.viewsets import ReadOnlyProtectedModelViewSet from django_filters.rest_framework import DjangoFilterBackend -from rest_framework.filters import SearchFilter +from api.filters import RegexSafeSearchFilter +from api.viewsets import ReadOnlyProtectedModelViewSet from .serializers import PermissionSerializer, RoleSerializer from ..models import Permission, Role @@ -17,9 +17,9 @@ class PermissionViewSet(ReadOnlyProtectedModelViewSet): """ queryset = Permission.objects.order_by('id') serializer_class = PermissionSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['model', 'type', 'query', 'mask', 'field', 'permanent', ] - search_fields = ['$model__name', '$query', '$description', ] + search_fields = ['$model__model', '$query', '$description', ] class RoleViewSet(ReadOnlyProtectedModelViewSet): @@ -30,6 +30,6 @@ class RoleViewSet(ReadOnlyProtectedModelViewSet): """ queryset = Role.objects.order_by('id') serializer_class = RoleSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['name', 'permissions', 'for_club', 'memberships__user', ] search_fields = ['$name', '$for_club__name', ] diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 07b16d40..2da84b3b 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -1,3668 +1,4466 @@ [ - { - "model": "permission.permissionmask", - "pk": 1, - "fields": { - "rank": 0, - "description": "Droits basiques" - } - }, - { - "model": "permission.permissionmask", - "pk": 2, - "fields": { - "rank": 1, - "description": "Droits note seulement" - } - }, - { - "model": "permission.permissionmask", - "pk": 3, - "fields": { - "rank": 42, - "description": "Tous mes droits" - } - }, - { - "model": "permission.permission", - "pk": 1, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{\"pk\": [\"user\", \"pk\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir son compte utilisateur" - } - }, - { - "model": "permission.permission", - "pk": 2, - "fields": { - "model": [ - "member", - "profile" - ], - "query": "{\"user\": [\"user\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir son profil" - } - }, - { - "model": "permission.permission", - "pk": 3, - "fields": { - "model": [ - "note", - "note" - ], - "query": "{\"pk\": [\"user\", \"note\", \"pk\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir sa propre note d'utilisateur" - } - }, - { - "model": "permission.permission", - "pk": 4, - "fields": { - "model": [ - "authtoken", - "token" - ], - "query": "{\"user\": [\"user\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir son jeton d'authentification à l'API" - } - }, - { - "model": "permission.permission", - "pk": 5, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "[\"OR\", {\"source\": [\"user\", \"note\"]}, {\"destination\": [\"user\", \"note\"]}]", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir ses propres transactions" - } - }, - { - "model": "permission.permission", - "pk": 6, - "fields": { - "model": [ - "note", - "alias" - ], - "query": "[\"AND\", [\"OR\", {\"note__noteuser__user__memberships__club__name\": \"BDE\", \"note__noteuser__user__memberships__date_start__lte\": [\"today\"], \"note__noteuser__user__memberships__date_end__gte\": [\"today\"]}, {\"note__noteclub__isnull\": false}], {\"note__is_active\": true}]", - "type": "view", - "mask": 1, - "field": "", - "permanent": false, - "description": "Voir les aliases des notes des clubs et des adhérents du club BDE" - } - }, - { - "model": "permission.permission", - "pk": 7, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{\"pk\": [\"user\", \"pk\"]}", - "type": "change", - "mask": 1, - "field": "last_login", - "permanent": true, - "description": "Modifier sa propre date de dernière connexion" - } - }, - { - "model": "permission.permission", - "pk": 8, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{\"pk\": [\"user\", \"pk\"]}", - "type": "change", - "mask": 1, - "field": "username", - "permanent": true, - "description": "Changer son propre pseudo" - } - }, - { - "model": "permission.permission", - "pk": 9, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{\"pk\": [\"user\", \"pk\"]}", - "type": "change", - "mask": 1, - "field": "first_name", - "permanent": true, - "description": "Changer son propre prénom" - } - }, - { - "model": "permission.permission", - "pk": 10, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{\"pk\": [\"user\", \"pk\"]}", - "type": "change", - "mask": 1, - "field": "last_name", - "permanent": true, - "description": "Changer son propre nom de famille" - } - }, - { - "model": "permission.permission", - "pk": 11, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{\"pk\": [\"user\", \"pk\"]}", - "type": "change", - "mask": 1, - "field": "email", - "permanent": true, - "description": "Changer sa propre adresse e-mail" - } - }, - { - "model": "permission.permission", - "pk": 12, - "fields": { - "model": [ - "authtoken", - "token" - ], - "query": "{\"user\": [\"user\"]}", - "type": "delete", - "mask": 1, - "field": "", - "permanent": true, - "description": "Supprimer son jeton d'authentification à l'API" - } - }, - { - "model": "permission.permission", - "pk": 13, - "fields": { - "model": [ - "authtoken", - "token" - ], - "query": "{\"user\": [\"user\"]}", - "type": "add", - "mask": 1, - "field": "", - "permanent": true, - "description": "Créer un jeton d'authentification à l'API" - } - }, - { - "model": "permission.permission", - "pk": 14, - "fields": { - "model": [ - "note", - "alias" - ], - "query": "{\"note\": [\"user\", \"note\"]}", - "type": "delete", - "mask": 1, - "field": "", - "permanent": true, - "description": "Supprimer un alias à sa note" - } - }, - { - "model": "permission.permission", - "pk": 15, - "fields": { - "model": [ - "note", - "alias" - ], - "query": "{\"note\": [\"user\", \"note\"]}", - "type": "add", - "mask": 1, - "field": "", - "permanent": false, - "description": "Ajouter un alias à sa note" - } - }, - { - "model": "permission.permission", - "pk": 16, - "fields": { - "model": [ - "note", - "noteuser" - ], - "query": "{\"pk\": [\"user\", \"note\", \"pk\"]}", - "type": "change", - "mask": 1, - "field": "display_image", - "permanent": false, - "description": "Changer l'image de sa note" - } - }, - { - "model": "permission.permission", - "pk": 17, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "[\"AND\", {\"source\": [\"user\", \"note\"]}, [\"OR\", {\"source__balance__gte\": {\"F\": [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]]}}, {\"valid\": false}]]", - "type": "add", - "mask": 1, - "field": "", - "permanent": false, - "description": "Transférer de l'argent depuis sa propre note en restant positif" - } - }, - { - "model": "permission.permission", - "pk": 19, - "fields": { - "model": [ - "note", - "note" - ], - "query": "[\"OR\", {\"pk\": [\"club\", \"note\", \"pk\"]}, {\"noteuser__user__memberships__club\": [\"club\"], \"noteuser__user__memberships__date_start__lte\": [\"today\"], \"noteuser__user__memberships__date_end__gte\": [\"today\"]}]", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir les notes des membres du club" - } - }, - { - "model": "permission.permission", - "pk": 20, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]]", - "type": "add", - "mask": 2, - "field": "", - "permanent": false, - "description": "Créer une transaction de ou vers la note d'un club" - } - }, - { - "model": "permission.permission", - "pk": 21, - "fields": { - "model": [ - "note", - "recurrenttransaction" - ], - "query": "[\"AND\", {\"destination\": [\"club\", \"note\"]}, [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]]", - "type": "add", - "mask": 2, - "field": "", - "permanent": false, - "description": "Créer une transaction en appuyant sur un bouton lié à un club" - } - }, - { - "model": "permission.permission", - "pk": 22, - "fields": { - "model": [ - "member", - "club" - ], - "query": "{\"pk\": [\"club\", \"pk\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": false, - "description": "Voir les informations d'un club" - } - }, - { - "model": "permission.permission", - "pk": 23, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "{}", - "type": "change", - "mask": 2, - "field": "valid", - "permanent": false, - "description": "Mettre à jour le statut de validation d'une transaction" - } - }, - { - "model": "permission.permission", - "pk": 24, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "{}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir toutes les transactions" - } - }, - { - "model": "permission.permission", - "pk": 25, - "fields": { - "model": [ - "note", - "notespecial" - ], - "query": "{}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Afficher l'interface crédit/retrait" - } - }, - { - "model": "permission.permission", - "pk": 26, - "fields": { - "model": [ - "note", - "specialtransaction" - ], - "query": "{}", - "type": "add", - "mask": 2, - "field": "", - "permanent": false, - "description": "Créer un crédit ou un retrait quelconque" - } - }, - { - "model": "permission.permission", - "pk": 27, - "fields": { - "model": [ - "note", - "templatecategory" - ], - "query": "{}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir toutes les catégories de boutons" - } - }, - { - "model": "permission.permission", - "pk": 28, - "fields": { - "model": [ - "note", - "templatecategory" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Changer une catégorie de boutons" - } - }, - { - "model": "permission.permission", - "pk": 29, - "fields": { - "model": [ - "note", - "templatecategory" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Créer une catégorie de boutons" - } - }, - { - "model": "permission.permission", - "pk": 30, - "fields": { - "model": [ - "note", - "transactiontemplate" - ], - "query": "{}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir tous les boutons" - } - }, - { - "model": "permission.permission", - "pk": 31, - "fields": { - "model": [ - "note", - "transactiontemplate" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Ajouter un bouton" - } - }, - { - "model": "permission.permission", - "pk": 32, - "fields": { - "model": [ - "note", - "transactiontemplate" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier un bouton" - } - }, - { - "model": "permission.permission", - "pk": 33, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "{}", - "type": "add", - "mask": 2, - "field": "", - "permanent": false, - "description": "Créer n'importe quelle transaction" - } - }, - { - "model": "permission.permission", - "pk": 34, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "[\"OR\", {\"valid\": true}, {\"creater\": [\"user\"]}]", - "type": "view", - "mask": 1, - "field": "", - "permanent": false, - "description": "Voir toutes les activités valides" - } - }, - { - "model": "permission.permission", - "pk": 36, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "{\"creater\": [\"user\"], \"valid\": false}", - "type": "add", - "mask": 1, - "field": "", - "permanent": false, - "description": "Proposer des activités" - } - }, - { - "model": "permission.permission", - "pk": 37, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "{}", - "type": "change", - "mask": 2, - "field": "valid", - "permanent": false, - "description": "Valider des activités" - } - }, - { - "model": "permission.permission", - "pk": 38, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "{}", - "type": "change", - "mask": 2, - "field": "open", - "permanent": false, - "description": "Ouvrir des activités" - } - }, - { - "model": "permission.permission", - "pk": 39, - "fields": { - "model": [ - "activity", - "guest" - ], - "query": "{\"inviter\": [\"user\", \"note\"], \"activity__activity_type__can_invite\": true}", - "type": "add", - "mask": 1, - "field": "", - "permanent": false, - "description": "Inviter des personnes à des activités" - } - }, - { - "model": "permission.permission", - "pk": 40, - "fields": { - "model": [ - "activity", - "guest" - ], - "query": "{\"inviter\": [\"user\", \"note\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir les personnes qu'on a invitées" - } - }, - { - "model": "permission.permission", - "pk": 41, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "{}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir toutes les activités" - } - }, - { - "model": "permission.permission", - "pk": 42, - "fields": { - "model": [ - "activity", - "guest" - ], - "query": "{}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir toutes les personnes invitées" - } - }, - { - "model": "permission.permission", - "pk": 43, - "fields": { - "model": [ - "activity", - "entry" - ], - "query": "{\"activity__open\": true}", - "type": "add", - "mask": 2, - "field": "", - "permanent": false, - "description": "Gérer les entrées d'une activité ouverte" - } - }, - { - "model": "permission.permission", - "pk": 44, - "fields": { - "model": [ - "activity", - "guesttransaction" - ], - "query": "{}", - "type": "add", - "mask": 2, - "field": "", - "permanent": false, - "description": "Créer une transaction d'invitation" - } - }, - { - "model": "permission.permission", - "pk": 45, - "fields": { - "model": [ - "activity", - "guesttransaction" - ], - "query": "{}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir toutes les transactions d'invitation" - } - }, - { - "model": "permission.permission", - "pk": 46, - "fields": { - "model": [ - "activity", - "guesttransaction" - ], - "query": "{}", - "type": "change", - "mask": 2, - "field": "valid", - "permanent": false, - "description": "Valider les transactions d'invitation" - } - }, - { - "model": "permission.permission", - "pk": 47, - "fields": { - "model": [ - "member", - "club" - ], - "query": "{\"pk\": [\"club\", \"pk\"]}", - "type": "change", - "mask": 2, - "field": "", - "permanent": false, - "description": "Modifier un club" - } - }, - { - "model": "permission.permission", - "pk": 48, - "fields": { - "model": [ - "member", - "membership" - ], - "query": "{\"user\": [\"user\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir mes adhésions" - } - }, - { - "model": "permission.permission", - "pk": 49, - "fields": { - "model": [ - "member", - "membership" - ], - "query": "{\"club\": [\"club\"]}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir les adhérents du club" - } - }, - { - "model": "permission.permission", - "pk": 50, - "fields": { - "model": [ - "member", - "membership" - ], - "query": "{\"club\": [\"club\"]}", - "type": "add", - "mask": 2, - "field": "", - "permanent": false, - "description": "Ajouter un membre à un club" - } - }, - { - "model": "permission.permission", - "pk": 51, - "fields": { - "model": [ - "member", - "membership" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "roles", - "permanent": false, - "description": "Modifier les rôles d'une adhésion" - } - }, - { - "model": "permission.permission", - "pk": 52, - "fields": { - "model": [ - "member", - "profile" - ], - "query": "{\"user\": [\"user\"]}", - "type": "change", - "mask": 1, - "field": "", - "permanent": true, - "description": "Modifier son profil" - } - }, - { - "model": "permission.permission", - "pk": 53, - "fields": { - "model": [ - "member", - "profile" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier n'importe quel profil" - } - }, - { - "model": "permission.permission", - "pk": 54, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier n'importe quel utilisateur" - } - }, - { - "model": "permission.permission", - "pk": 55, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Ajouter un utilisateur" - } - }, - { - "model": "permission.permission", - "pk": 56, - "fields": { - "model": [ - "member", - "profile" - ], - "query": "{\"email_confirmed\": false, \"registration_valid\": false}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Ajouter un profil" - } - }, - { - "model": "permission.permission", - "pk": 57, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{\"profile__registration_valid\": false}", - "type": "delete", - "mask": 3, - "field": "", - "permanent": false, - "description": "Supprimer une pré-inscription" - } - }, - { - "model": "permission.permission", - "pk": 58, - "fields": { - "model": [ - "member", - "profile" - ], - "query": "{\"registration_valid\": false}", - "type": "delete", - "mask": 3, - "field": "", - "permanent": false, - "description": "Supprimer le profil d'une pré-inscription" - } - }, - { - "model": "permission.permission", - "pk": 59, - "fields": { - "model": [ - "note", - "transactiontemplate" - ], - "query": "{\"destination\": [\"club\", \"note\"]}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir les boutons d'un club" - } - }, - { - "model": "permission.permission", - "pk": 60, - "fields": { - "model": [ - "note", - "transactiontemplate" - ], - "query": "{\"destination\": [\"club\", \"note\"]}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Créer un bouton d'un club" - } - }, - { - "model": "permission.permission", - "pk": 61, - "fields": { - "model": [ - "note", - "transactiontemplate" - ], - "query": "{\"destination\": [\"club\", \"note\"]}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier le bouton d'un club" - } - }, - { - "model": "permission.permission", - "pk": 62, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "[\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}]", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir les transactions d'un club" - } - }, - { - "model": "permission.permission", - "pk": 63, - "fields": { - "model": [ - "treasury", - "invoice" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir les factures" - } - }, - { - "model": "permission.permission", - "pk": 64, - "fields": { - "model": [ - "treasury", - "invoice" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Ajouter une facture" - } - }, - { - "model": "permission.permission", - "pk": 65, - "fields": { - "model": [ - "treasury", - "invoice" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier une facture" - } - }, - { - "model": "permission.permission", - "pk": 66, - "fields": { - "model": [ - "treasury", - "product" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir les produits" - } - }, - { - "model": "permission.permission", - "pk": 67, - "fields": { - "model": [ - "treasury", - "product" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Ajouter des produits" - } - }, - { - "model": "permission.permission", - "pk": 68, - "fields": { - "model": [ - "treasury", - "product" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier un produit" - } - }, - { - "model": "permission.permission", - "pk": 69, - "fields": { - "model": [ - "treasury", - "product" - ], - "query": "{}", - "type": "delete", - "mask": 3, - "field": "", - "permanent": false, - "description": "Supprimer un produit" - } - }, - { - "model": "permission.permission", - "pk": 70, - "fields": { - "model": [ - "treasury", - "sogecredit" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Ajouter un crédit de la Soci\u00e9t\u00e9 g\u00e9n\u00e9rale" - } - }, - { - "model": "permission.permission", - "pk": 71, - "fields": { - "model": [ - "treasury", - "sogecredit" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir tous les crédits de la Soci\u00e9t\u00e9 g\u00e9n\u00e9rale" - } - }, - { - "model": "permission.permission", - "pk": 72, - "fields": { - "model": [ - "treasury", - "sogecredit" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier un crédit de la Soci\u00e9t\u00e9 g\u00e9n\u00e9rale" - } - }, - { - "model": "permission.permission", - "pk": 73, - "fields": { - "model": [ - "treasury", - "sogecredit" - ], - "query": "{}", - "type": "delete", - "mask": 3, - "field": "", - "permanent": false, - "description": "Supprimer un crédit de la Soci\u00e9t\u00e9 g\u00e9n\u00e9rale" - } - }, - { - "model": "permission.permission", - "pk": 74, - "fields": { - "model": [ - "wei", - "weiclub" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Créer un WEI" - } - }, - { - "model": "permission.permission", - "pk": 75, - "fields": { - "model": [ - "wei", - "weiclub" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier tous les WEI" - } - }, - { - "model": "permission.permission", - "pk": 76, - "fields": { - "model": [ - "wei", - "weiclub" - ], - "query": "{\"pk\": [\"club\", \"pk\"]}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier ce WEI" - } - }, - { - "model": "permission.permission", - "pk": 77, - "fields": { - "model": [ - "wei", - "weiclub" - ], - "query": "{\"pk\": [\"club\", \"pk\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir mon WEI" - } - }, - { - "model": "permission.permission", - "pk": 78, - "fields": { - "model": [ - "wei", - "weiclub" - ], - "query": "{\"membership_start__lte\": [\"today\"], \"year\": [\"today\", \"year\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir le dernier WEI" - } - }, - { - "model": "permission.permission", - "pk": 79, - "fields": { - "model": [ - "wei", - "weirole" - ], - "query": "{}", - "type": "view", - "mask": 1, - "field": "", - "permanent": false, - "description": "Voir les rôles pour le WEI" - } - }, - { - "model": "permission.permission", - "pk": 83, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"user\": [\"user\"], \"wei__year\": [\"today\", \"year\"], \"wei__membership_start__lte\": [\"today\"], \"wei__membership_end__gte\": [\"today\"], \"membership\": null}", - "type": "add", - "mask": 1, - "field": "", - "permanent": true, - "description": "M'inscrire au dernier WEI" - } - }, - { - "model": "permission.permission", - "pk": 84, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"], \"first_year\": true, \"wei__membership_start__lte\": [\"today\"], \"wei__membership_end__gte\": [\"today\"], \"membership\": null}", - "type": "add", - "mask": 2, - "field": "", - "permanent": false, - "description": "Inscrire un 1A au WEI" - } - }, - { - "model": "permission.permission", - "pk": 85, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"], \"membership\": null}", - "type": "add", - "mask": 2, - "field": "", - "permanent": false, - "description": "Inscrire n'importe qui au WEI" - } - }, - { - "model": "permission.permission", - "pk": 86, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"]}", - "type": "delete", - "mask": 3, - "field": "", - "permanent": false, - "description": "Supprimer une inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 87, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"wei\": [\"club\"]}, {\"wei__year\": [\"today\", \"year\"], \"membership\": null}]]", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir ma propre inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 88, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"]}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir toutes les inscriptions WEI" - } - }, - { - "model": "permission.permission", - "pk": 89, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "soge_credit", - "permanent": false, - "description": "Indiquer si une inscription WEI est payée par la Société générale" - } - }, - { - "model": "permission.permission", - "pk": 90, - "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": "soge_credit", - "permanent": true, - "description": "Indiquer si mon inscription WEI est payée par la Société générale tant qu'elle n'est pas validée" - } - }, - { - "model": "permission.permission", - "pk": 91, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 2, - "field": "caution_check", - "permanent": false, - "description": "Dire si un chèque de caution est donné pour une inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 92, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "birth_date", - "permanent": false, - "description": "Modifier la date de naissance d'une inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 93, - "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": "birth_date", - "permanent": true, - "description": "Modifier la date de naissance de ma propre inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 94, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "gender", - "permanent": false, - "description": "Modifier le genre de toute inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 95, - "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": "gender", - "permanent": true, - "description": "Modifier le genre de ma propre inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 96, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "health_issues", - "permanent": false, - "description": "Modifier les problèmes de santé de toutes les inscriptions WEI" - } - }, - { - "model": "permission.permission", - "pk": 97, - "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": "health_issues", - "permanent": true, - "description": "Modifier mes problèmes de santé de mon inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 98, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "emergency_contact_name", - "permanent": false, - "description": "Modifier le nom du contact en cas d'urgence de toute inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 99, - "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": "emergency_contact_name", - "permanent": true, - "description": "Modifier le nom du contact en cas d'urgence de mon inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 100, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "emergency_contact_phone", - "permanent": false, - "description": "Modifier le téléphone du contact en cas d'urgence de toute inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 101, - "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": "emergency_contact_phone", - "permanent": true, - "description": "Modifier le téléphone du contact en cas d'urgence de mon inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 102, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "information_json", - "permanent": false, - "description": "Modifier les informations (sondage 1A, ...) d'une inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 103, - "fields": { - "model": [ - "wei", - "bus" - ], - "query": "{\"wei\": [\"club\"]}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Ajouter un bus au WEI" - } - }, - { - "model": "permission.permission", - "pk": 104, - "fields": { - "model": [ - "wei", - "bus" - ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "name", - "permanent": false, - "description": "Modifier le nom d'un bus d'un WEI" - } - }, - { - "model": "permission.permission", - "pk": 105, - "fields": { - "model": [ - "wei", - "bus" - ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "description", - "permanent": false, - "description": "Modifier la description d'un bus d'un WEI" - } - }, - { - "model": "permission.permission", - "pk": 106, - "fields": { - "model": [ - "wei", - "busteam" - ], - "query": "{\"bus__wei\": [\"club\"], \"bus__wei__membership_end__gte\": [\"today\"]}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Créer une équipe WEI" - } - }, - { - "model": "permission.permission", - "pk": 107, - "fields": { - "model": [ - "wei", - "busteam" - ], - "query": "{\"bus__wei\": [\"club\"], \"bus__wei__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier une équipe WEI" - } - }, - { - "model": "permission.permission", - "pk": 108, - "fields": { - "model": [ - "wei", - "bus" - ], - "query": "[\"AND\", {\"wei\": [\"club\"]}, [\"OR\", [\"NOT\", [\"membership\", \"weimembership\", \"registration\", \"first_year\"]], {\"wei__date_end__lte\": [\"today\"]}]]", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir tous les bus WEI si on est en 2A+ ou que le WEI est terminé" - } - }, - { - "model": "permission.permission", - "pk": 109, - "fields": { - "model": [ - "wei", - "busteam" - ], - "query": "[\"AND\", {\"bus__wei\": [\"club\"]}, [\"OR\", [\"NOT\", [\"membership\", \"weimembership\", \"registration\", \"first_year\"]], {\"bus__wei__date_end__lte\": [\"today\"]}]]", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir toutes les équipes WEI si on est en 2A+ ou que le WEI est terminé" - } - }, - { - "model": "permission.permission", - "pk": 110, - "fields": { - "model": [ - "wei", - "weimembership" - ], - "query": "[\"AND\", {\"club\": [\"club\"], \"club__weiclub__membership_end__gte\": [\"today\"]}, [\"OR\", {\"registration__soge_credit\": true}, {\"user__note__balance__gte\": {\"F\": [\"F\", \"fee\"]}}]]", - "type": "add", - "mask": 2, - "field": "", - "permanent": false, - "description": "Créer une adhésion WEI pour le dernier WEI" - } - }, - { - "model": "permission.permission", - "pk": 111, - "fields": { - "model": [ - "wei", - "weimembership" - ], - "query": "{\"club\": [\"club\"], \"club__weiclub__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 2, - "field": "bus", - "permanent": false, - "description": "Modifier le bus d'une adhésion WEI" - } - }, - { - "model": "permission.permission", - "pk": 112, - "fields": { - "model": [ - "wei", - "weimembership" - ], - "query": "{\"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" - } - }, - { - "model": "permission.permission", - "pk": 113, - "fields": { - "model": [ - "wei", - "weimembership" - ], - "query": "{\"club\": [\"club\"]}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir toutes les adhésions au WEI" - } - }, - { - "model": "permission.permission", - "pk": 114, - "fields": { - "model": [ - "wei", - "weimembership" - ], - "query": "[\"AND\", {\"user\": [\"user\"], \"club\": [\"club\"]}, [\"OR\", {\"registration__first_year\": false}, {\"club__weiclub__date_end__lte\": [\"today\"]}]]", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir mes adhésions WEI passées" - } - }, - { - "model": "permission.permission", - "pk": 115, - "fields": { - "model": [ - "wei", - "weimembership" - ], - "query": "{\"club\": [\"club\"], \"bus\": [\"membership\", \"weimembership\", \"bus\"]}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir les membres du bus" - } - }, - { - "model": "permission.permission", - "pk": 116, - "fields": { - "model": [ - "wei", - "weimembership" - ], - "query": "{\"club\": [\"club\"], \"team\": [\"membership\", \"weimembership\", \"team\"]}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir les membres de l'équipe" - } - }, - { - "model": "permission.permission", - "pk": 117, - "fields": { - "model": [ - "wei", - "bus" - ], - "query": "{\"pk\": [\"membership\", \"weimembership\", \"bus\", \"pk\"], \"wei__date_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "name", - "permanent": false, - "description": "Modifier le nom du bus" - } - }, - { - "model": "permission.permission", - "pk": 118, - "fields": { - "model": [ - "wei", - "bus" - ], - "query": "{\"pk\": [\"membership\", \"weimembership\", \"bus\", \"pk\"], \"wei__date_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "description", - "permanent": false, - "description": "Modifier la description du bus" - } - }, - { - "model": "permission.permission", - "pk": 119, - "fields": { - "model": [ - "wei", - "busteam" - ], - "query": "{\"bus\": [\"membership\", \"weimembership\", \"bus\"], \"bus__wei__date_end__gte\": [\"today\"]}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Ajouter une équipe à mon bus" - } - }, - { - "model": "permission.permission", - "pk": 120, - "fields": { - "model": [ - "wei", - "busteam" - ], - "query": "{\"bus\": [\"membership\", \"weimembership\", \"bus\"], \"bus__wei__date_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "name", - "permanent": false, - "description": "Modifier le nom d'une équipe de mon bus" - } - }, - { - "model": "permission.permission", - "pk": 121, - "fields": { - "model": [ - "wei", - "busteam" - ], - "query": "{\"bus\": [\"membership\", \"weimembership\", \"bus\"], \"bus__wei__date_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "color", - "permanent": false, - "description": "Modifier la couleur d'une équipe de mon bus" - } - }, - { - "model": "permission.permission", - "pk": 122, - "fields": { - "model": [ - "wei", - "busteam" - ], - "query": "{\"bus\": [\"membership\", \"weimembership\", \"bus\"], \"bus__wei__date_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "description", - "permanent": false, - "description": "Modifier la description d'une équipe de mon bus" - } - }, - { - "model": "permission.permission", - "pk": 123, - "fields": { - "model": [ - "wei", - "busteam" - ], - "query": "{\"pk\": [\"membership\", \"weimembership\", \"team\", \"pk\"], \"bus__wei__date_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "name", - "permanent": false, - "description": "Modifier le nom de mon équipe" - } - }, - { - "model": "permission.permission", - "pk": 124, - "fields": { - "model": [ - "wei", - "busteam" - ], - "query": "{\"pk\": [\"membership\", \"weimembership\", \"team\", \"pk\"], \"bus__wei__date_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "color", - "permanent": false, - "description": "Modifier la couleur de mon équipe" - } - }, - { - "model": "permission.permission", - "pk": 125, - "fields": { - "model": [ - "wei", - "busteam" - ], - "query": "{\"pk\": [\"membership\", \"weimembership\", \"team\", \"pk\"], \"bus__wei__date_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "description", - "permanent": false, - "description": "Modifier la description de mon équipe" - } - }, - { - "model": "permission.permission", - "pk": 126, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "{\"entries__note__user\": [\"user\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir mes activitées passées, même après la fin de l'adhésion BDE" - } - }, - { - "model": "permission.permission", - "pk": 127, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": false}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": true}]]", - "type": "change", - "mask": 2, - "field": "valid", - "permanent": false, - "description": "Modifier le statut de validation d'une transaction de club si c'est possible" - } - }, - { - "model": "permission.permission", - "pk": 128, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "clothing_cut", - "permanent": false, - "description": "Modifier la coupe de vêtements d'une inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 129, - "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": "clothing_cut", - "permanent": true, - "description": "Modifier ma coupe de vêtements de mon inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 130, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", - "type": "change", - "mask": 3, - "field": "clothing_size", - "permanent": false, - "description": "Modifier la taille de vêtements d'une inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 131, - "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": "clothing_size", - "permanent": true, - "description": "Modifier la taille de vêtements de mon inscription WEI" - } - }, - { - "model": "permission.permission", - "pk": 132, - "fields": { - "model": [ - "note", - "recurrenttransaction" - ], - "query": "{}", - "type": "add", - "mask": 2, - "field": "", - "permanent": false, - "description": "Créer une transaction depuis un bouton" - } - }, - { - "model": "permission.permission", - "pk": 133, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": true}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": false}]]", - "type": "change", - "mask": 2, - "field": "invalidity_reason", - "permanent": false, - "description": "Modifier la raison d'invalidité d'une transaction de club" - } - }, - { - "model": "permission.permission", - "pk": 134, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "{}", - "type": "change", - "mask": 2, - "field": "invalidity_reason", - "permanent": false, - "description": "Modifier la raison d'invalidité d'une transaction" - } - }, - { - "model": "permission.permission", - "pk": 135, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir n'importe quel utilisateur" - } - }, - { - "model": "permission.permission", - "pk": 136, - "fields": { - "model": [ - "member", - "profile" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir n'importe quel profil" - } - }, - { - "model": "permission.permission", - "pk": 137, - "fields": { - "model": [ - "member", - "club" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir n'importe quel club" - } - }, - { - "model": "permission.permission", - "pk": 138, - "fields": { - "model": [ - "member", - "club" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier n'importe quel club" - } - }, - { - "model": "permission.permission", - "pk": 139, - "fields": { - "model": [ - "note", - "noteclub" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Créer une note de club" - } - }, - { - "model": "permission.permission", - "pk": 140, - "fields": { - "model": [ - "member", - "club" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Créer un club" - } - }, - { - "model": "permission.permission", - "pk": 141, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{\"memberships__club\": [\"club\"], \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir les membres de mon club" - } - }, - { - "model": "permission.permission", - "pk": 142, - "fields": { - "model": [ - "note", - "note" - ], - "query": "{\"noteclub__club\": [\"club\"]}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir la note de mon club" - } - }, - { - "model": "permission.permission", - "pk": 143, - "fields": { - "model": [ - "note", - "noteuser" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Créer une note d'utilisateur" - } - }, - { - "model": "permission.permission", - "pk": 144, - "fields": { - "model": [ - "wei", - "weiregistration" - ], - "query": "[\"AND\", {\"user\": [\"user\"], \"wei__membership_start__lte\": [\"today\"], \"wei__membership_end__gte\": [\"today\"], \"first_year\": false, \"membership\": null}]", - "type": "change", - "mask": 1, - "field": "information_json", - "permanent": true, - "description": "Modifier mes préférences en terme de bus et d'équipe si mon inscription n'est pas validée et que je suis en 2A+" - } - }, - { - "model": "permission.permission", - "pk": 145, - "fields": { - "model": [ - "note", - "noteclub" - ], - "query": "{}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir toutes les notes de club" - } - }, - { - "model": "permission.permission", - "pk": 146, - "fields": { - "model": [ - "member", - "membership" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir tous les adhérents de tous les clubs" - } - }, - { - "model": "permission.permission", - "pk": 147, - "fields": { - "model": [ - "member", - "membership" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Ajouter un membre à n'importe quel club" - } - }, - { - "model": "permission.permission", - "pk": 148, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "{\"valid\": false}", - "type": "change", - "mask": 2, - "field": "", - "permanent": false, - "description": "Modifier une activité non validée" - } - }, - { - "model": "permission.permission", - "pk": 149, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "{\"valid\": false}", - "type": "delete", - "mask": 2, - "field": "", - "permanent": false, - "description": "Supprimer une activité non validée" - } - }, - { - "model": "permission.permission", - "pk": 150, - "fields": { - "model": [ - "note", - "note" - ], - "query": "{}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir toutes les notes" - } - }, - { - "model": "permission.permission", - "pk": 151, - "fields": { - "model": [ - "treasury", - "invoice" - ], - "query": "{}", - "type": "delete", - "mask": 3, - "field": "", - "permanent": false, - "description": "Supprimer une facture" - } - }, - { - "model": "permission.permission", - "pk": 152, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", - "type": "change", - "mask": 1, - "field": "name", - "permanent": false, - "description": "Modifier le nom d'une activité non validée dont on est l'auteur" - } - }, - { - "model": "permission.permission", - "pk": 153, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", - "type": "change", - "mask": 1, - "field": "description", - "permanent": false, - "description": "Modifier la description d'une activité non validée dont on est l'auteur" - } - }, - { - "model": "permission.permission", - "pk": 154, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", - "type": "change", - "mask": 1, - "field": "location", - "permanent": false, - "description": "Modifier le lieu d'une activité non validée dont on est l'auteur" - } - }, - { - "model": "permission.permission", - "pk": 155, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", - "type": "change", - "mask": 1, - "field": "activity_type", - "permanent": false, - "description": "Modifier le type d'une activité non validée dont on est l'auteur" - } - }, - { - "model": "permission.permission", - "pk": 156, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", - "type": "change", - "mask": 1, - "field": "organizer", - "permanent": false, - "description": "Modifier l'organisateur d'une activité non validée dont on est l'auteur" - } - }, - { - "model": "permission.permission", - "pk": 157, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", - "type": "change", - "mask": 1, - "field": "attendees_club", - "permanent": false, - "description": "Modifier le club attendu d'une activité non validée dont on est l'auteur" - } - }, - { - "model": "permission.permission", - "pk": 158, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", - "type": "change", - "mask": 1, - "field": "date_start", - "permanent": false, - "description": "Modifier la date de début d'une activité non validée dont on est l'auteur" - } - }, - { - "model": "permission.permission", - "pk": 159, - "fields": { - "model": [ - "activity", - "activity" - ], - "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", - "type": "change", - "mask": 1, - "field": "date_end", - "permanent": false, - "description": "Modifier la date de fin d'une activité non validée dont on est l'auteur" - } - }, - { - "model": "permission.permission", - "pk": 160, - "fields": { - "model": [ - "activity", - "guest" - ], - "query": "{\"inviter\": [\"user\", \"note\"], \"entry\": null}", - "type": "delete", - "mask": 1, - "field": "", - "permanent": false, - "description": "Supprimer ses propres invitations non validées à une activité" - } - }, - { - "model": "permission.permission", - "pk": 161, - "fields": { - "model": [ - "note", - "noteuser" - ], - "query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]", - "type": "change", - "mask": 1, - "field": "is_active", - "permanent": true, - "description": "(Dé)bloquer sa propre note manuellement" - } - }, - { - "model": "permission.permission", - "pk": 162, - "fields": { - "model": [ - "note", - "noteuser" - ], - "query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]", - "type": "change", - "mask": 1, - "field": "inactivity_reason", - "permanent": true, - "description": "(Dé)bloquer sa propre note et indiquer que cela a été fait manuellement" - } - }, - { - "model": "permission.permission", - "pk": 163, - "fields": { - "model": [ - "note", - "note" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "is_active", - "permanent": false, - "description": "(Dé)bloquer n'importe quelle note, y compris en mode forcé" - } - }, - { - "model": "permission.permission", - "pk": 164, - "fields": { - "model": [ - "note", - "note" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "inactivity_reason", - "permanent": false, - "description": "(Dé)bloquer n'importe quelle note et indiquer la raison" - } - }, - { - "model": "permission.permission", - "pk": 165, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{}", - "type": "change", - "mask": 1, - "field": "password", - "permanent": true, - "description": "Changer son mot de passe" - } - }, - { - "model": "permission.permission", - "pk": 166, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]", - "type": "add", - "mask": 2, - "field": "", - "permanent": false, - "description": "Créer une transaction quelconque tant que la source reste au-dessus de -20 €" - } - }, - { - "model": "permission.permission", - "pk": 167, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": false}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": true}]", - "type": "change", - "mask": 2, - "field": "valid", - "permanent": false, - "description": "Modifier le statut de validation d'une transaction si tout le monde reste au dessus de -20 €" - } - }, - { - "model": "permission.permission", - "pk": 168, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": false}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": true}]", - "type": "change", - "mask": 2, - "field": "invalidity_reason", - "permanent": false, - "description": "Modifier la raison d'invalidité d'une transaction si tout le monde reste au dessus de -20 €" - } - }, - { - "model": "permission.permission", - "pk": 169, - "fields": { - "model": [ - "note", - "noteclub" - ], - "query": "{\"club\": [\"club\"]}", - "type": "change", - "mask": 2, - "field": "display_image", - "permanent": false, - "description": "Changer l'image de la note de son club" - } - }, - { - "model": "permission.permission", - "pk": 170, - "fields": { - "model": [ - "note", - "alias" - ], - "query": "{\"note__is_active\": true}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Ajouter n'importe quel alias à une note non bloquée" - } - }, - { - "model": "permission.permission", - "pk": 171, - "fields": { - "model": [ - "note", - "alias" - ], - "query": "{\"note__is_active\": true}", - "type": "delete", - "mask": 3, - "field": "", - "permanent": false, - "description": "Supprimer n'importe quel alias à une note non bloquée" - } - }, - { - "model": "permission.permission", - "pk": 172, - "fields": { - "model": [ - "treasury", - "remittance" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir toutes les remises" - } - }, - { - "model": "permission.permission", - "pk": 173, - "fields": { - "model": [ - "treasury", - "remittance" - ], - "query": "{}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Ajouter une remise" - } - }, - { - "model": "permission.permission", - "pk": 174, - "fields": { - "model": [ - "treasury", - "remittance" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier une remise" - } - }, - { - "model": "permission.permission", - "pk": 175, - "fields": { - "model": [ - "treasury", - "remittance" - ], - "query": "{}", - "type": "delete", - "mask": 3, - "field": "", - "permanent": false, - "description": "Supprimer une remise" - } - }, - { - "model": "permission.permission", - "pk": 176, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{\"profile__registration_valid\": false}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier n'importe quel utilisateur non encore inscrit" - } - }, - { - "model": "permission.permission", - "pk": 177, - "fields": { - "model": [ - "member", - "profile" - ], - "query": "{\"registration_valid\": false}", - "type": "change", - "mask": 3, - "field": "", - "permanent": false, - "description": "Modifier n'importe quel profil non encore inscrit" - } - }, - { - "model": "permission.permission", - "pk": 178, - "fields": { - "model": [ - "note", - "alias" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir tous les alias, y compris ceux des non adhérents" - } - }, - { - "model": "permission.permission", - "pk": 179, - "fields": { - "model": [ - "note", - "alias" - ], - "query": "{\"note__noteuser__user\": [\"user\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir ses propres alias, pour toujours" - } - }, - { - "model": "permission.permission", - "pk": 180, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{\"profile__registration_valid\": false}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir n'importe quel utilisateur non encore inscrit" - } - }, - { - "model": "permission.permission", - "pk": 181, - "fields": { - "model": [ - "member", - "profile" - ], - "query": "{\"registration_valid\": false}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir n'importe quel profil non encore inscrit" - } - }, - { - "model": "permission.permission", - "pk": 182, - "fields": { - "model": [ - "auth", - "user" - ], - "query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}", - "type": "view", - "mask": 2, - "field": "", - "permanent": false, - "description": "Voir n'importe quel utilisateur qui est adhérent BDE" - } - }, - { - "model": "permission.permission", - "pk": 183, - "fields": { - "model": [ - "note", - "note" - ], - "query": "{}", - "type": "change", - "mask": 3, - "field": "display_image", - "permanent": false, - "description": "Changer l'image de n'importe quelle note" - } - }, - { - "model": "permission.permission", - "pk": 184, - "fields": { - "model": [ - "note", - "noteclub" - ], - "query": "[\"AND\", {\"club\": [\"club\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]", - "type": "change", - "mask": 3, - "field": "is_active", - "permanent": false, - "description": "(Dé)bloquer la note de son club manuellement" - } - }, - { - "model": "permission.permission", - "pk": 185, - "fields": { - "model": [ - "note", - "noteclub" - ], - "query": "[\"AND\", {\"club\": [\"club\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]", - "type": "change", - "mask": 3, - "field": "inactivity_reason", - "permanent": false, - "description": "(Dé)bloquer la note de son club et indiquer que cela a été fait manuellement" - } - }, - { - "model": "permission.permission", - "pk": 186, - "fields": { - "model": [ - "oauth2_provider", - "application" - ], - "query": "{\"user\": [\"user\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir ses applications OAuth2" - } - }, - { - "model": "permission.permission", - "pk": 187, - "fields": { - "model": [ - "oauth2_provider", - "application" - ], - "query": "{\"user\": [\"user\"]}", - "type": "add", - "mask": 1, - "field": "", - "permanent": true, - "description": "Créer une application OAuth2" - } - }, - { - "model": "permission.permission", - "pk": 188, - "fields": { - "model": [ - "oauth2_provider", - "application" - ], - "query": "{\"user\": [\"user\"]}", - "type": "change", - "mask": 1, - "field": "", - "permanent": true, - "description": "Modifier une application OAuth2" - } - }, - { - "model": "permission.permission", - "pk": 189, - "fields": { - "model": [ - "oauth2_provider", - "application" - ], - "query": "{\"user\": [\"user\"]}", - "type": "delete", - "mask": 1, - "field": "", - "permanent": true, - "description": "Supprimer une application OAuth2" - } - }, - { - "model": "permission.permission", - "pk": 190, - "fields": { - "model": [ - "note", - "trust" - ], - "query": "{\"trusting\": [\"user\", \"note\"]}", - "type": "delete", - "mask": 1, - "field": "", - "permanent": true, - "description": "Supprimer une amitié à sa note" - } - }, - { - "model": "permission.permission", - "pk": 191, - "fields": { - "model": [ - "note", - "trust" - ], - "query": "{\"trusting\": [\"user\", \"note\"]}", - "type": "add", - "mask": 1, - "field": "", - "permanent": false, - "description": "Ajouter une amitié à sa note" - } - }, - { - "model": "permission.permission", - "pk": 192, - "fields": { - "model": [ - "note", - "trust" - ], - "query": "{\"trusting__is_active\": true}", - "type": "add", - "mask": 3, - "field": "", - "permanent": false, - "description": "Ajouter une amitié à une note non bloquée" - } - }, - { - "model": "permission.permission", - "pk": 193, - "fields": { - "model": [ - "note", - "trust" - ], - "query": "{\"trusting__is_active\": true}", - "type": "delete", - "mask": 3, - "field": "", - "permanent": false, - "description": "Supprimer une amitié à une note non bloquée" - } - }, - { - "model": "permission.permission", - "pk": 194, - "fields": { - "model": [ - "note", - "trust" - ], - "query": "{}", - "type": "view", - "mask": 3, - "field": "", - "permanent": false, - "description": "Voir toutes les amitiés, y compris celles des non adhérents" - } - }, - { - "model": "permission.permission", - "pk": 195, - "fields": { - "model": [ - "note", - "trust" - ], - "query": "{\"trusting__noteuser__user\": [\"user\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir ses propres amitiés, pour toujours" - } - }, - { - "model": "permission.permission", - "pk": 196, - "fields": { - "model": [ - "note", - "transaction" - ], - "query": "[\"AND\", {\"source__trusting__trusted\": [\"user\", \"note\"]}, [\"OR\", {\"source__balance__gte\": {\"F\": [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]]}}, {\"valid\": false}]]", - "type": "add", - "mask": 1, - "field": "", - "permanent": false, - "description": "Transférer de l'argent depuis une note amie en restant positif" - } - }, - { - "model": "permission.permission", - "pk": 197, - "fields": { - "model": [ - "note", - "specialtransaction" - ], - "query": "{\"source__notespecial__gte\":0}", - "type": "add", - "mask": 2, - "field": "", - "permanent": false, - "description": "Créer un crédit quelconque" + { + "model": "permission.permissionmask", + "pk": 1, + "fields": { + "rank": 0, + "description": "Droits basiques" } }, - { - "model": "permission.permission", - "pk": 198, - "fields": { - "model": [ - "note", - "trust" - ], - "query": "{\"trusted__noteuser__user\": [\"user\"]}", - "type": "view", - "mask": 1, - "field": "", - "permanent": true, - "description": "Voir ceux nous ayant pour ami, pour toujours" - } - }, - { - "model": "permission.permission", - "pk": 199, - "fields": { - "model": [ - "member", - "membership" - ], - "query": "{\"club\": 2}", - "type": "view", - "mask": 1, - "field": "", - "permanent": false, - "description": "Voir les adhérents Kfet" - } - }, - { - "model": "permission.role", - "pk": 1, - "fields": { - "for_club": 1, - "name": "Adh\u00e9rent BDE", - "permissions": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, - 14, - 15, - 16, - 17, - 22, - 34, - 48, - 52, - 126, - 161, - 162, - 165, - 179, - 186, - 187, - 188, - 189, + { + "model": "permission.permissionmask", + "pk": 2, + "fields": { + "rank": 1, + "description": "Droits note seulement" + } + }, + { + "model": "permission.permissionmask", + "pk": 3, + "fields": { + "rank": 42, + "description": "Tous mes droits" + } + }, + { + "model": "permission.permission", + "pk": 1, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{\"pk\": [\"user\", \"pk\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir son compte utilisateur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 2, + "fields": { + "model": [ + "member", + "profile" + ], + "query": "{\"user\": [\"user\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir son profil" + } + }, + { + "model": "permission.permission", + "pk": 3, + "fields": { + "model": [ + "note", + "note" + ], + "query": "{\"pk\": [\"user\", \"note\", \"pk\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir sa propre note d'utilisateur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 4, + "fields": { + "model": [ + "authtoken", + "token" + ], + "query": "{\"user\": [\"user\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir son jeton d'authentification à l'API" + } + }, + { + "model": "permission.permission", + "pk": 5, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"OR\", {\"source\": [\"user\", \"note\"]}, {\"destination\": [\"user\", \"note\"]}]", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir ses propres transactions" + } + }, + { + "model": "permission.permission", + "pk": 6, + "fields": { + "model": [ + "note", + "alias" + ], + "query": "[\"AND\", [\"OR\", {\"note__noteuser__user__memberships__club__name\": \"BDE\", \"note__noteuser__user__memberships__date_start__lte\": [\"today\"], \"note__noteuser__user__memberships__date_end__gte\": [\"today\"]}, {\"note__noteclub__isnull\": false}], {\"note__is_active\": true}]", + "type": "view", + "mask": 1, + "field": "", + "permanent": false, + "description": "Voir les alias des notes des clubs et des adhérent⋅es du club BDE" + } + }, + { + "model": "permission.permission", + "pk": 7, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{\"pk\": [\"user\", \"pk\"]}", + "type": "change", + "mask": 1, + "field": "last_login", + "permanent": true, + "description": "Modifier sa propre date de dernière connexion" + } + }, + { + "model": "permission.permission", + "pk": 8, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{\"pk\": [\"user\", \"pk\"]}", + "type": "change", + "mask": 1, + "field": "username", + "permanent": true, + "description": "Changer son propre pseudo" + } + }, + { + "model": "permission.permission", + "pk": 9, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{\"pk\": [\"user\", \"pk\"]}", + "type": "change", + "mask": 1, + "field": "first_name", + "permanent": true, + "description": "Changer son propre prénom" + } + }, + { + "model": "permission.permission", + "pk": 10, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{\"pk\": [\"user\", \"pk\"]}", + "type": "change", + "mask": 1, + "field": "last_name", + "permanent": true, + "description": "Changer son propre nom de famille" + } + }, + { + "model": "permission.permission", + "pk": 11, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{\"pk\": [\"user\", \"pk\"]}", + "type": "change", + "mask": 1, + "field": "email", + "permanent": true, + "description": "Changer sa propre adresse e-mail" + } + }, + { + "model": "permission.permission", + "pk": 12, + "fields": { + "model": [ + "authtoken", + "token" + ], + "query": "{\"user\": [\"user\"]}", + "type": "delete", + "mask": 1, + "field": "", + "permanent": true, + "description": "Supprimer son jeton d'authentification à l'API" + } + }, + { + "model": "permission.permission", + "pk": 13, + "fields": { + "model": [ + "authtoken", + "token" + ], + "query": "{\"user\": [\"user\"]}", + "type": "add", + "mask": 1, + "field": "", + "permanent": true, + "description": "Créer un jeton d'authentification à l'API" + } + }, + { + "model": "permission.permission", + "pk": 14, + "fields": { + "model": [ + "note", + "alias" + ], + "query": "{\"note\": [\"user\", \"note\"]}", + "type": "delete", + "mask": 1, + "field": "", + "permanent": true, + "description": "Supprimer un alias à sa note" + } + }, + { + "model": "permission.permission", + "pk": 15, + "fields": { + "model": [ + "note", + "alias" + ], + "query": "{\"note\": [\"user\", \"note\"]}", + "type": "add", + "mask": 1, + "field": "", + "permanent": false, + "description": "Ajouter un alias à sa note" + } + }, + { + "model": "permission.permission", + "pk": 16, + "fields": { + "model": [ + "note", + "noteuser" + ], + "query": "{\"pk\": [\"user\", \"note\", \"pk\"]}", + "type": "change", + "mask": 1, + "field": "display_image", + "permanent": false, + "description": "Changer l'image de sa note" + } + }, + { + "model": "permission.permission", + "pk": 17, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"AND\", {\"source\": [\"user\", \"note\"]}, [\"OR\", {\"source__balance__gte\": {\"F\": [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]]}}, {\"valid\": false}]]", + "type": "add", + "mask": 1, + "field": "", + "permanent": false, + "description": "Transférer de l'argent depuis sa propre note en restant positif" + } + }, + { + "model": "permission.permission", + "pk": 19, + "fields": { + "model": [ + "note", + "note" + ], + "query": "[\"OR\", {\"pk\": [\"club\", \"note\", \"pk\"]}, {\"noteuser__user__memberships__club\": [\"club\"], \"noteuser__user__memberships__date_start__lte\": [\"today\"], \"noteuser__user__memberships__date_end__gte\": [\"today\"]}]", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir les notes des membres du club" + } + }, + { + "model": "permission.permission", + "pk": 20, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]]", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer une transaction de ou vers la note d'un club" + } + }, + { + "model": "permission.permission", + "pk": 21, + "fields": { + "model": [ + "note", + "recurrenttransaction" + ], + "query": "[\"AND\", {\"destination\": [\"club\", \"note\"]}, [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]]", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer une transaction en appuyant sur un bouton lié à un club" + } + }, + { + "model": "permission.permission", + "pk": 22, + "fields": { + "model": [ + "member", + "club" + ], + "query": "{\"pk\": [\"club\", \"pk\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": false, + "description": "Voir les informations d'un club" + } + }, + { + "model": "permission.permission", + "pk": 23, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "{}", + "type": "change", + "mask": 2, + "field": "valid", + "permanent": false, + "description": "Mettre à jour le statut de validation d'une transaction" + } + }, + { + "model": "permission.permission", + "pk": 24, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "{}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir toutes les transactions" + } + }, + { + "model": "permission.permission", + "pk": 25, + "fields": { + "model": [ + "note", + "notespecial" + ], + "query": "{}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Afficher l'interface crédit/retrait" + } + }, + { + "model": "permission.permission", + "pk": 26, + "fields": { + "model": [ + "note", + "specialtransaction" + ], + "query": "{}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer un crédit ou un retrait quelconque" + } + }, + { + "model": "permission.permission", + "pk": 27, + "fields": { + "model": [ + "note", + "templatecategory" + ], + "query": "{}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir toutes les catégories de boutons" + } + }, + { + "model": "permission.permission", + "pk": 28, + "fields": { + "model": [ + "note", + "templatecategory" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Changer une catégorie de boutons" + } + }, + { + "model": "permission.permission", + "pk": 29, + "fields": { + "model": [ + "note", + "templatecategory" + ], + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Créer une catégorie de boutons" + } + }, + { + "model": "permission.permission", + "pk": 30, + "fields": { + "model": [ + "note", + "transactiontemplate" + ], + "query": "{}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir tous les boutons" + } + }, + { + "model": "permission.permission", + "pk": 31, + "fields": { + "model": [ + "note", + "transactiontemplate" + ], + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter un bouton" + } + }, + { + "model": "permission.permission", + "pk": 32, + "fields": { + "model": [ + "note", + "transactiontemplate" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier un bouton" + } + }, + { + "model": "permission.permission", + "pk": 33, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "{}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer n'importe quelle transaction" + } + }, + { + "model": "permission.permission", + "pk": 34, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "[\"OR\", {\"valid\": true}, {\"creater\": [\"user\"]}]", + "type": "view", + "mask": 1, + "field": "", + "permanent": false, + "description": "Voir toutes les activités valides" + } + }, + { + "model": "permission.permission", + "pk": 36, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "{\"creater\": [\"user\"], \"valid\": false}", + "type": "add", + "mask": 1, + "field": "", + "permanent": false, + "description": "Proposer des activités" + } + }, + { + "model": "permission.permission", + "pk": 37, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "{}", + "type": "change", + "mask": 2, + "field": "valid", + "permanent": false, + "description": "Valider des activités" + } + }, + { + "model": "permission.permission", + "pk": 38, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "{}", + "type": "change", + "mask": 2, + "field": "open", + "permanent": false, + "description": "Ouvrir des activités" + } + }, + { + "model": "permission.permission", + "pk": 39, + "fields": { + "model": [ + "activity", + "guest" + ], + "query": "{\"inviter\": [\"user\", \"note\"], \"activity__activity_type__can_invite\": true}", + "type": "add", + "mask": 1, + "field": "", + "permanent": false, + "description": "Inviter des personnes à des activités" + } + }, + { + "model": "permission.permission", + "pk": 40, + "fields": { + "model": [ + "activity", + "guest" + ], + "query": "{\"inviter\": [\"user\", \"note\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir les personnes qu'on a invitées" + } + }, + { + "model": "permission.permission", + "pk": 41, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "{}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir toutes les activités" + } + }, + { + "model": "permission.permission", + "pk": 42, + "fields": { + "model": [ + "activity", + "guest" + ], + "query": "{}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir toutes les personnes invitées" + } + }, + { + "model": "permission.permission", + "pk": 43, + "fields": { + "model": [ + "activity", + "entry" + ], + "query": "{\"activity__open\": true}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Gérer les entrées d'une activité ouverte" + } + }, + { + "model": "permission.permission", + "pk": 44, + "fields": { + "model": [ + "activity", + "guesttransaction" + ], + "query": "{}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer une transaction d'invitation" + } + }, + { + "model": "permission.permission", + "pk": 45, + "fields": { + "model": [ + "activity", + "guesttransaction" + ], + "query": "{}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir toutes les transactions d'invitation" + } + }, + { + "model": "permission.permission", + "pk": 46, + "fields": { + "model": [ + "activity", + "guesttransaction" + ], + "query": "{}", + "type": "change", + "mask": 2, + "field": "valid", + "permanent": false, + "description": "Valider les transactions d'invitation" + } + }, + { + "model": "permission.permission", + "pk": 47, + "fields": { + "model": [ + "member", + "club" + ], + "query": "{\"pk\": [\"club\", \"pk\"]}", + "type": "change", + "mask": 2, + "field": "", + "permanent": false, + "description": "Modifier un club" + } + }, + { + "model": "permission.permission", + "pk": 48, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{\"user\": [\"user\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir mes adhésions" + } + }, + { + "model": "permission.permission", + "pk": 49, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{\"club\": [\"club\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir les adhérent⋅es du club" + } + }, + { + "model": "permission.permission", + "pk": 50, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{\"club\": [\"club\"]}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Ajouter un⋅e membre à un club" + } + }, + { + "model": "permission.permission", + "pk": 51, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "roles", + "permanent": false, + "description": "Modifier les rôles d'une adhésion" + } + }, + { + "model": "permission.permission", + "pk": 52, + "fields": { + "model": [ + "member", + "profile" + ], + "query": "{\"user\": [\"user\"]}", + "type": "change", + "mask": 1, + "field": "", + "permanent": true, + "description": "Modifier son profil" + } + }, + { + "model": "permission.permission", + "pk": 53, + "fields": { + "model": [ + "member", + "profile" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier n'importe quel profil" + } + }, + { + "model": "permission.permission", + "pk": 54, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier n'importe quel⋅le utilisateur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 55, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter un⋅e utilisateur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 56, + "fields": { + "model": [ + "member", + "profile" + ], + "query": "{\"email_confirmed\": false, \"registration_valid\": false}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter un profil" + } + }, + { + "model": "permission.permission", + "pk": 57, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{\"profile__registration_valid\": false}", + "type": "delete", + "mask": 3, + "field": "", + "permanent": false, + "description": "Supprimer une pré-inscription" + } + }, + { + "model": "permission.permission", + "pk": 58, + "fields": { + "model": [ + "member", + "profile" + ], + "query": "{\"registration_valid\": false}", + "type": "delete", + "mask": 3, + "field": "", + "permanent": false, + "description": "Supprimer le profil d'une pré-inscription" + } + }, + { + "model": "permission.permission", + "pk": 59, + "fields": { + "model": [ + "note", + "transactiontemplate" + ], + "query": "{\"destination\": [\"club\", \"note\"]}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir les boutons d'un club" + } + }, + { + "model": "permission.permission", + "pk": 60, + "fields": { + "model": [ + "note", + "transactiontemplate" + ], + "query": "{\"destination\": [\"club\", \"note\"]}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Créer un bouton d'un club" + } + }, + { + "model": "permission.permission", + "pk": 61, + "fields": { + "model": [ + "note", + "transactiontemplate" + ], + "query": "{\"destination\": [\"club\", \"note\"]}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier le bouton d'un club" + } + }, + { + "model": "permission.permission", + "pk": 62, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}]", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir les transactions d'un club" + } + }, + { + "model": "permission.permission", + "pk": 63, + "fields": { + "model": [ + "treasury", + "invoice" + ], + "query": "{}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir les factures" + } + }, + { + "model": "permission.permission", + "pk": 64, + "fields": { + "model": [ + "treasury", + "invoice" + ], + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter une facture" + } + }, + { + "model": "permission.permission", + "pk": 65, + "fields": { + "model": [ + "treasury", + "invoice" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier une facture" + } + }, + { + "model": "permission.permission", + "pk": 66, + "fields": { + "model": [ + "treasury", + "product" + ], + "query": "{}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir les produits" + } + }, + { + "model": "permission.permission", + "pk": 67, + "fields": { + "model": [ + "treasury", + "product" + ], + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter des produits" + } + }, + { + "model": "permission.permission", + "pk": 68, + "fields": { + "model": [ + "treasury", + "product" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier un produit" + } + }, + { + "model": "permission.permission", + "pk": 69, + "fields": { + "model": [ + "treasury", + "product" + ], + "query": "{}", + "type": "delete", + "mask": 3, + "field": "", + "permanent": false, + "description": "Supprimer un produit" + } + }, + { + "model": "permission.permission", + "pk": 70, + "fields": { + "model": [ + "treasury", + "sogecredit" + ], + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter un crédit de la Soci\u00e9t\u00e9 g\u00e9n\u00e9rale" + } + }, + { + "model": "permission.permission", + "pk": 71, + "fields": { + "model": [ + "treasury", + "sogecredit" + ], + "query": "{}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir tous les crédits de la Soci\u00e9t\u00e9 g\u00e9n\u00e9rale" + } + }, + { + "model": "permission.permission", + "pk": 72, + "fields": { + "model": [ + "treasury", + "sogecredit" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier un crédit de la Soci\u00e9t\u00e9 g\u00e9n\u00e9rale" + } + }, + { + "model": "permission.permission", + "pk": 73, + "fields": { + "model": [ + "treasury", + "sogecredit" + ], + "query": "{}", + "type": "delete", + "mask": 3, + "field": "", + "permanent": false, + "description": "Supprimer un crédit de la Soci\u00e9t\u00e9 g\u00e9n\u00e9rale" + } + }, + { + "model": "permission.permission", + "pk": 74, + "fields": { + "model": [ + "wei", + "weiclub" + ], + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Créer un WEI" + } + }, + { + "model": "permission.permission", + "pk": 75, + "fields": { + "model": [ + "wei", + "weiclub" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier tous les WEI" + } + }, + { + "model": "permission.permission", + "pk": 76, + "fields": { + "model": [ + "wei", + "weiclub" + ], + "query": "{\"pk\": [\"club\", \"pk\"]}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier ce WEI" + } + }, + { + "model": "permission.permission", + "pk": 77, + "fields": { + "model": [ + "wei", + "weiclub" + ], + "query": "{\"pk\": [\"club\", \"pk\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir mon WEI" + } + }, + { + "model": "permission.permission", + "pk": 78, + "fields": { + "model": [ + "wei", + "weiclub" + ], + "query": "{\"membership_start__lte\": [\"today\"], \"year\": [\"today\", \"year\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir le dernier WEI" + } + }, + { + "model": "permission.permission", + "pk": 79, + "fields": { + "model": [ + "wei", + "weirole" + ], + "query": "{}", + "type": "view", + "mask": 1, + "field": "", + "permanent": false, + "description": "Voir les rôles pour le WEI" + } + }, + { + "model": "permission.permission", + "pk": 83, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"user\": [\"user\"], \"wei__year\": [\"today\", \"year\"], \"wei__membership_start__lte\": [\"today\"], \"wei__membership_end__gte\": [\"today\"], \"membership\": null}", + "type": "add", + "mask": 1, + "field": "", + "permanent": true, + "description": "M'inscrire au dernier WEI" + } + }, + { + "model": "permission.permission", + "pk": 84, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"], \"first_year\": true, \"wei__membership_start__lte\": [\"today\"], \"wei__membership_end__gte\": [\"today\"], \"membership\": null}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Inscrire un⋅e 1A au WEI" + } + }, + { + "model": "permission.permission", + "pk": 85, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"], \"membership\": null}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Inscrire n'importe qui au WEI" + } + }, + { + "model": "permission.permission", + "pk": 86, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"]}", + "type": "delete", + "mask": 3, + "field": "", + "permanent": false, + "description": "Supprimer une inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 87, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"wei\": [\"club\"]}, {\"wei__year\": [\"today\", \"year\"], \"membership\": null}]]", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir ma propre inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 88, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"]}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir toutes les inscriptions WEI" + } + }, + { + "model": "permission.permission", + "pk": 89, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "soge_credit", + "permanent": false, + "description": "Indiquer si une inscription WEI est payée par la Société générale" + } + }, + { + "model": "permission.permission", + "pk": 90, + "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": "soge_credit", + "permanent": true, + "description": "Indiquer si mon inscription WEI est payée par la Société générale tant qu'elle n'est pas validée" + } + }, + { + "model": "permission.permission", + "pk": 91, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 2, + "field": "caution_check", + "permanent": false, + "description": "Dire si un chèque de caution est donné pour une inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 92, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "birth_date", + "permanent": false, + "description": "Modifier la date de naissance d'une inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 93, + "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": "birth_date", + "permanent": true, + "description": "Modifier la date de naissance de ma propre inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 94, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "gender", + "permanent": false, + "description": "Modifier le genre de toute inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 95, + "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": "gender", + "permanent": true, + "description": "Modifier le genre de ma propre inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 96, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "health_issues", + "permanent": false, + "description": "Modifier les problèmes de santé de toutes les inscriptions WEI" + } + }, + { + "model": "permission.permission", + "pk": 97, + "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": "health_issues", + "permanent": true, + "description": "Modifier mes problèmes de santé de mon inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 98, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "emergency_contact_name", + "permanent": false, + "description": "Modifier le nom du contact en cas d'urgence de toute inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 99, + "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": "emergency_contact_name", + "permanent": true, + "description": "Modifier le nom du contact en cas d'urgence de mon inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 100, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "emergency_contact_phone", + "permanent": false, + "description": "Modifier le téléphone du contact en cas d'urgence de toute inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 101, + "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": "emergency_contact_phone", + "permanent": true, + "description": "Modifier le téléphone du contact en cas d'urgence de mon inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 102, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "information_json", + "permanent": false, + "description": "Modifier les informations (sondage 1A, ...) d'une inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 103, + "fields": { + "model": [ + "wei", + "bus" + ], + "query": "{\"wei\": [\"club\"]}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter un bus au WEI" + } + }, + { + "model": "permission.permission", + "pk": 104, + "fields": { + "model": [ + "wei", + "bus" + ], + "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "name", + "permanent": false, + "description": "Modifier le nom d'un bus d'un WEI" + } + }, + { + "model": "permission.permission", + "pk": 105, + "fields": { + "model": [ + "wei", + "bus" + ], + "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "description", + "permanent": false, + "description": "Modifier la description d'un bus d'un WEI" + } + }, + { + "model": "permission.permission", + "pk": 106, + "fields": { + "model": [ + "wei", + "busteam" + ], + "query": "{\"bus__wei\": [\"club\"], \"bus__wei__membership_end__gte\": [\"today\"]}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Créer une équipe WEI" + } + }, + { + "model": "permission.permission", + "pk": 107, + "fields": { + "model": [ + "wei", + "busteam" + ], + "query": "{\"bus__wei\": [\"club\"], \"bus__wei__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier une équipe WEI" + } + }, + { + "model": "permission.permission", + "pk": 108, + "fields": { + "model": [ + "wei", + "bus" + ], + "query": "[\"AND\", {\"wei\": [\"club\"]}, [\"OR\", [\"NOT\", [\"membership\", \"weimembership\", \"registration\", \"first_year\"]], {\"wei__date_end__lte\": [\"today\"]}]]", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir tous les bus WEI si on est en 2A+ ou que le WEI est terminé" + } + }, + { + "model": "permission.permission", + "pk": 109, + "fields": { + "model": [ + "wei", + "busteam" + ], + "query": "[\"AND\", {\"bus__wei\": [\"club\"]}, [\"OR\", [\"NOT\", [\"membership\", \"weimembership\", \"registration\", \"first_year\"]], {\"bus__wei__date_end__lte\": [\"today\"]}]]", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir toutes les équipes WEI si on est en 2A+ ou que le WEI est terminé" + } + }, + { + "model": "permission.permission", + "pk": 110, + "fields": { + "model": [ + "wei", + "weimembership" + ], + "query": "[\"AND\", {\"club\": [\"club\"], \"club__weiclub__membership_end__gte\": [\"today\"]}, [\"OR\", {\"registration__soge_credit\": true}, {\"user__note__balance__gte\": {\"F\": [\"F\", \"fee\"]}}]]", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer une adhésion WEI pour le dernier WEI" + } + }, + { + "model": "permission.permission", + "pk": 111, + "fields": { + "model": [ + "wei", + "weimembership" + ], + "query": "{\"club\": [\"club\"], \"club__weiclub__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 2, + "field": "bus", + "permanent": false, + "description": "Modifier le bus d'une adhésion WEI" + } + }, + { + "model": "permission.permission", + "pk": 112, + "fields": { + "model": [ + "wei", + "weimembership" + ], + "query": "{\"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" + } + }, + { + "model": "permission.permission", + "pk": 113, + "fields": { + "model": [ + "wei", + "weimembership" + ], + "query": "{\"club\": [\"club\"]}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir toutes les adhésions au WEI" + } + }, + { + "model": "permission.permission", + "pk": 114, + "fields": { + "model": [ + "wei", + "weimembership" + ], + "query": "[\"AND\", {\"user\": [\"user\"], \"club\": [\"club\"]}, [\"OR\", {\"registration__first_year\": false}, {\"club__weiclub__date_end__lte\": [\"today\"]}]]", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir mes adhésions WEI passées" + } + }, + { + "model": "permission.permission", + "pk": 115, + "fields": { + "model": [ + "wei", + "weimembership" + ], + "query": "{\"club\": [\"club\"], \"bus\": [\"membership\", \"weimembership\", \"bus\"]}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir les membres du bus" + } + }, + { + "model": "permission.permission", + "pk": 116, + "fields": { + "model": [ + "wei", + "weimembership" + ], + "query": "{\"club\": [\"club\"], \"team\": [\"membership\", \"weimembership\", \"team\"]}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir les membres de l'équipe" + } + }, + { + "model": "permission.permission", + "pk": 117, + "fields": { + "model": [ + "wei", + "bus" + ], + "query": "{\"pk\": [\"membership\", \"weimembership\", \"bus\", \"pk\"], \"wei__date_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "name", + "permanent": false, + "description": "Modifier le nom du bus" + } + }, + { + "model": "permission.permission", + "pk": 118, + "fields": { + "model": [ + "wei", + "bus" + ], + "query": "{\"pk\": [\"membership\", \"weimembership\", \"bus\", \"pk\"], \"wei__date_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "description", + "permanent": false, + "description": "Modifier la description du bus" + } + }, + { + "model": "permission.permission", + "pk": 119, + "fields": { + "model": [ + "wei", + "busteam" + ], + "query": "{\"bus\": [\"membership\", \"weimembership\", \"bus\"], \"bus__wei__date_end__gte\": [\"today\"]}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter une équipe à mon bus" + } + }, + { + "model": "permission.permission", + "pk": 120, + "fields": { + "model": [ + "wei", + "busteam" + ], + "query": "{\"bus\": [\"membership\", \"weimembership\", \"bus\"], \"bus__wei__date_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "name", + "permanent": false, + "description": "Modifier le nom d'une équipe de mon bus" + } + }, + { + "model": "permission.permission", + "pk": 121, + "fields": { + "model": [ + "wei", + "busteam" + ], + "query": "{\"bus\": [\"membership\", \"weimembership\", \"bus\"], \"bus__wei__date_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "color", + "permanent": false, + "description": "Modifier la couleur d'une équipe de mon bus" + } + }, + { + "model": "permission.permission", + "pk": 122, + "fields": { + "model": [ + "wei", + "busteam" + ], + "query": "{\"bus\": [\"membership\", \"weimembership\", \"bus\"], \"bus__wei__date_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "description", + "permanent": false, + "description": "Modifier la description d'une équipe de mon bus" + } + }, + { + "model": "permission.permission", + "pk": 123, + "fields": { + "model": [ + "wei", + "busteam" + ], + "query": "{\"pk\": [\"membership\", \"weimembership\", \"team\", \"pk\"], \"bus__wei__date_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "name", + "permanent": false, + "description": "Modifier le nom de mon équipe" + } + }, + { + "model": "permission.permission", + "pk": 124, + "fields": { + "model": [ + "wei", + "busteam" + ], + "query": "{\"pk\": [\"membership\", \"weimembership\", \"team\", \"pk\"], \"bus__wei__date_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "color", + "permanent": false, + "description": "Modifier la couleur de mon équipe" + } + }, + { + "model": "permission.permission", + "pk": 125, + "fields": { + "model": [ + "wei", + "busteam" + ], + "query": "{\"pk\": [\"membership\", \"weimembership\", \"team\", \"pk\"], \"bus__wei__date_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "description", + "permanent": false, + "description": "Modifier la description de mon équipe" + } + }, + { + "model": "permission.permission", + "pk": 126, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "{\"entries__note__user\": [\"user\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir mes activités passées, même après la fin de l'adhésion BDE" + } + }, + { + "model": "permission.permission", + "pk": 127, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": false}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": true}]]", + "type": "change", + "mask": 2, + "field": "valid", + "permanent": false, + "description": "Modifier le statut de validation d'une transaction de club si c'est possible" + } + }, + { + "model": "permission.permission", + "pk": 128, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "clothing_cut", + "permanent": false, + "description": "Modifier la coupe de vêtements d'une inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 129, + "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": "clothing_cut", + "permanent": true, + "description": "Modifier ma coupe de vêtements de mon inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 130, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "{\"wei\": [\"club\"], \"wei__membership_end__gte\": [\"today\"]}", + "type": "change", + "mask": 3, + "field": "clothing_size", + "permanent": false, + "description": "Modifier la taille de vêtements d'une inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 131, + "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": "clothing_size", + "permanent": true, + "description": "Modifier la taille de vêtements de mon inscription WEI" + } + }, + { + "model": "permission.permission", + "pk": 132, + "fields": { + "model": [ + "note", + "recurrenttransaction" + ], + "query": "{}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer une transaction depuis un bouton" + } + }, + { + "model": "permission.permission", + "pk": 133, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": true}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": false}]]", + "type": "change", + "mask": 2, + "field": "invalidity_reason", + "permanent": false, + "description": "Modifier la raison d'invalidité d'une transaction de club" + } + }, + { + "model": "permission.permission", + "pk": 134, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "{}", + "type": "change", + "mask": 2, + "field": "invalidity_reason", + "permanent": false, + "description": "Modifier la raison d'invalidité d'une transaction" + } + }, + { + "model": "permission.permission", + "pk": 135, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir n'importe quel⋅le utilisateur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 136, + "fields": { + "model": [ + "member", + "profile" + ], + "query": "{}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir n'importe quel profil" + } + }, + { + "model": "permission.permission", + "pk": 137, + "fields": { + "model": [ + "member", + "club" + ], + "query": "{}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir n'importe quel club" + } + }, + { + "model": "permission.permission", + "pk": 138, + "fields": { + "model": [ + "member", + "club" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier n'importe quel club" + } + }, + { + "model": "permission.permission", + "pk": 139, + "fields": { + "model": [ + "note", + "noteclub" + ], + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Créer une note de club" + } + }, + { + "model": "permission.permission", + "pk": 140, + "fields": { + "model": [ + "member", + "club" + ], + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Créer un club" + } + }, + { + "model": "permission.permission", + "pk": 141, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{\"memberships__club\": [\"club\"], \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir les membres de mon club" + } + }, + { + "model": "permission.permission", + "pk": 142, + "fields": { + "model": [ + "note", + "note" + ], + "query": "{\"noteclub__club\": [\"club\"]}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir la note de mon club" + } + }, + { + "model": "permission.permission", + "pk": 143, + "fields": { + "model": [ + "note", + "noteuser" + ], + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Créer une note d'utilisateur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 144, + "fields": { + "model": [ + "wei", + "weiregistration" + ], + "query": "[\"AND\", {\"user\": [\"user\"], \"wei__membership_start__lte\": [\"today\"], \"wei__membership_end__gte\": [\"today\"], \"first_year\": false, \"membership\": null}]", + "type": "change", + "mask": 1, + "field": "information_json", + "permanent": true, + "description": "Modifier mes préférences en terme de bus et d'équipe si mon inscription n'est pas validée et que je suis en 2A+" + } + }, + { + "model": "permission.permission", + "pk": 145, + "fields": { + "model": [ + "note", + "noteclub" + ], + "query": "{}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir toutes les notes de club" + } + }, + { + "model": "permission.permission", + "pk": 146, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir toustes les adhérent⋅es de tous les clubs" + } + }, + { + "model": "permission.permission", + "pk": 147, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter un⋅e membre à n'importe quel club" + } + }, + { + "model": "permission.permission", + "pk": 148, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "{\"valid\": false}", + "type": "change", + "mask": 2, + "field": "", + "permanent": false, + "description": "Modifier une activité non validée" + } + }, + { + "model": "permission.permission", + "pk": 149, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "{\"valid\": false}", + "type": "delete", + "mask": 2, + "field": "", + "permanent": false, + "description": "Supprimer une activité non validée" + } + }, + { + "model": "permission.permission", + "pk": 150, + "fields": { + "model": [ + "note", + "note" + ], + "query": "{}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir toutes les notes" + } + }, + { + "model": "permission.permission", + "pk": 151, + "fields": { + "model": [ + "treasury", + "invoice" + ], + "query": "{}", + "type": "delete", + "mask": 3, + "field": "", + "permanent": false, + "description": "Supprimer une facture" + } + }, + { + "model": "permission.permission", + "pk": 152, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", + "type": "change", + "mask": 1, + "field": "name", + "permanent": false, + "description": "Modifier le nom d'une activité non validée dont on est l'auteur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 153, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", + "type": "change", + "mask": 1, + "field": "description", + "permanent": false, + "description": "Modifier la description d'une activité non validée dont on est l'auteur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 154, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", + "type": "change", + "mask": 1, + "field": "location", + "permanent": false, + "description": "Modifier le lieu d'une activité non validée dont on est l'auteur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 155, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", + "type": "change", + "mask": 1, + "field": "activity_type", + "permanent": false, + "description": "Modifier le type d'une activité non validée dont on est l'auteur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 156, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", + "type": "change", + "mask": 1, + "field": "organizer", + "permanent": false, + "description": "Modifier l'organisateur d'une activité non validée dont on est l'auteur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 157, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", + "type": "change", + "mask": 1, + "field": "attendees_club", + "permanent": false, + "description": "Modifier le club attendu d'une activité non validée dont on est l'auteur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 158, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", + "type": "change", + "mask": 1, + "field": "date_start", + "permanent": false, + "description": "Modifier la date de début d'une activité non validée dont on est l'auteur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 159, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", + "type": "change", + "mask": 1, + "field": "date_end", + "permanent": false, + "description": "Modifier la date de fin d'une activité non validée dont on est l'auteur⋅rice" + } + }, + { + "model": "permission.permission", + "pk": 160, + "fields": { + "model": [ + "activity", + "guest" + ], + "query": "{\"inviter\": [\"user\", \"note\"], \"entry\": null}", + "type": "delete", + "mask": 1, + "field": "", + "permanent": false, + "description": "Supprimer ses propres invitations non validées à une activité" + } + }, + { + "model": "permission.permission", + "pk": 161, + "fields": { + "model": [ + "note", + "noteuser" + ], + "query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]", + "type": "change", + "mask": 1, + "field": "is_active", + "permanent": true, + "description": "(Dé)bloquer sa propre note manuellement" + } + }, + { + "model": "permission.permission", + "pk": 162, + "fields": { + "model": [ + "note", + "noteuser" + ], + "query": "[\"AND\", {\"user\": [\"user\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]", + "type": "change", + "mask": 1, + "field": "inactivity_reason", + "permanent": true, + "description": "(Dé)bloquer sa propre note et indiquer que cela a été fait manuellement" + } + }, + { + "model": "permission.permission", + "pk": 163, + "fields": { + "model": [ + "note", + "note" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "is_active", + "permanent": false, + "description": "(Dé)bloquer n'importe quelle note, y compris en mode forcé" + } + }, + { + "model": "permission.permission", + "pk": 164, + "fields": { + "model": [ + "note", + "note" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "inactivity_reason", + "permanent": false, + "description": "(Dé)bloquer n'importe quelle note et indiquer la raison" + } + }, + { + "model": "permission.permission", + "pk": 165, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{}", + "type": "change", + "mask": 1, + "field": "password", + "permanent": true, + "description": "Changer son mot de passe" + } + }, + { + "model": "permission.permission", + "pk": 166, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"OR\", {\"source__balance__gte\": 0}, [\"AND\", [\"NOT\", {\"recurrenttransaction__template__category__name\": \"Alcool\"}], {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}], {\"valid\": false}]", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer une transaction quelconque tant que la source reste positive s'il s'agit d'alcool, sinon au-dessus de -20€" + } + }, + { + "model": "permission.permission", + "pk": 167, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": false}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": true}]", + "type": "change", + "mask": 2, + "field": "valid", + "permanent": false, + "description": "Modifier le statut de validation d'une transaction si tout le monde reste au dessus de -20 €" + } + }, + { + "model": "permission.permission", + "pk": 168, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": false}, {\"destination__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}, \"valid\": true}]", + "type": "change", + "mask": 2, + "field": "invalidity_reason", + "permanent": false, + "description": "Modifier la raison d'invalidité d'une transaction si tout le monde reste au dessus de -20 €" + } + }, + { + "model": "permission.permission", + "pk": 169, + "fields": { + "model": [ + "note", + "noteclub" + ], + "query": "{\"club\": [\"club\"]}", + "type": "change", + "mask": 2, + "field": "display_image", + "permanent": false, + "description": "Changer l'image de la note de son club" + } + }, + { + "model": "permission.permission", + "pk": 170, + "fields": { + "model": [ + "note", + "alias" + ], + "query": "{\"note__is_active\": true}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter n'importe quel alias à une note non bloquée" + } + }, + { + "model": "permission.permission", + "pk": 171, + "fields": { + "model": [ + "note", + "alias" + ], + "query": "{\"note__is_active\": true}", + "type": "delete", + "mask": 3, + "field": "", + "permanent": false, + "description": "Supprimer n'importe quel alias à une note non bloquée" + } + }, + { + "model": "permission.permission", + "pk": 172, + "fields": { + "model": [ + "treasury", + "remittance" + ], + "query": "{}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir toutes les remises" + } + }, + { + "model": "permission.permission", + "pk": 173, + "fields": { + "model": [ + "treasury", + "remittance" + ], + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter une remise" + } + }, + { + "model": "permission.permission", + "pk": 174, + "fields": { + "model": [ + "treasury", + "remittance" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier une remise" + } + }, + { + "model": "permission.permission", + "pk": 175, + "fields": { + "model": [ + "treasury", + "remittance" + ], + "query": "{}", + "type": "delete", + "mask": 3, + "field": "", + "permanent": false, + "description": "Supprimer une remise" + } + }, + { + "model": "permission.permission", + "pk": 176, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{\"profile__registration_valid\": false}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier n'importe quel⋅le utilisateur⋅rice non encore inscrit⋅e" + } + }, + { + "model": "permission.permission", + "pk": 177, + "fields": { + "model": [ + "member", + "profile" + ], + "query": "{\"registration_valid\": false}", + "type": "change", + "mask": 3, + "field": "", + "permanent": false, + "description": "Modifier n'importe quel profil non encore inscrit" + } + }, + { + "model": "permission.permission", + "pk": 178, + "fields": { + "model": [ + "note", + "alias" + ], + "query": "{}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir tous les alias, y compris ceux des non adhérent⋅es" + } + }, + { + "model": "permission.permission", + "pk": 179, + "fields": { + "model": [ + "note", + "alias" + ], + "query": "{\"note__noteuser__user\": [\"user\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir ses propres alias, pour toujours" + } + }, + { + "model": "permission.permission", + "pk": 180, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{\"profile__registration_valid\": false}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir n'importe quel⋅le utilisateur⋅rice non encore inscrit⋅e" + } + }, + { + "model": "permission.permission", + "pk": 181, + "fields": { + "model": [ + "member", + "profile" + ], + "query": "{\"registration_valid\": false}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir n'importe quel profil non encore inscrit" + } + }, + { + "model": "permission.permission", + "pk": 182, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "{\"memberships__club__name\": \"BDE\", \"memberships__roles__name\": \"Adhérent⋅e BDE\", \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir n'importe quel⋅le utilisateur⋅rice qui est adhérent⋅e BDE" + } + }, + { + "model": "permission.permission", + "pk": 183, + "fields": { + "model": [ + "note", + "note" + ], + "query": "{}", + "type": "change", + "mask": 3, + "field": "display_image", + "permanent": false, + "description": "Changer l'image de n'importe quelle note" + } + }, + { + "model": "permission.permission", + "pk": 184, + "fields": { + "model": [ + "note", + "noteclub" + ], + "query": "[\"AND\", {\"club\": [\"club\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]", + "type": "change", + "mask": 3, + "field": "is_active", + "permanent": false, + "description": "(Dé)bloquer la note de son club manuellement" + } + }, + { + "model": "permission.permission", + "pk": 185, + "fields": { + "model": [ + "note", + "noteclub" + ], + "query": "[\"AND\", {\"club\": [\"club\"]}, [\"OR\", {\"inactivity_reason\": \"manual\"}, {\"is_active\": true}]]", + "type": "change", + "mask": 3, + "field": "inactivity_reason", + "permanent": false, + "description": "(Dé)bloquer la note de son club et indiquer que cela a été fait manuellement" + } + }, + { + "model": "permission.permission", + "pk": 186, + "fields": { + "model": [ + "oauth2_provider", + "application" + ], + "query": "{\"user\": [\"user\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir ses applications OAuth2" + } + }, + { + "model": "permission.permission", + "pk": 187, + "fields": { + "model": [ + "oauth2_provider", + "application" + ], + "query": "{\"user\": [\"user\"]}", + "type": "add", + "mask": 1, + "field": "", + "permanent": true, + "description": "Créer une application OAuth2" + } + }, + { + "model": "permission.permission", + "pk": 188, + "fields": { + "model": [ + "oauth2_provider", + "application" + ], + "query": "{\"user\": [\"user\"]}", + "type": "change", + "mask": 1, + "field": "", + "permanent": true, + "description": "Modifier une application OAuth2" + } + }, + { + "model": "permission.permission", + "pk": 189, + "fields": { + "model": [ + "oauth2_provider", + "application" + ], + "query": "{\"user\": [\"user\"]}", + "type": "delete", + "mask": 1, + "field": "", + "permanent": true, + "description": "Supprimer une application OAuth2" + } + }, + { + "model": "permission.permission", + "pk": 190, + "fields": { + "model": [ + "note", + "trust" + ], + "query": "{\"trusting\": [\"user\", \"note\"]}", + "type": "delete", + "mask": 1, + "field": "", + "permanent": true, + "description": "Supprimer une amitié à sa note" + } + }, + { + "model": "permission.permission", + "pk": 191, + "fields": { + "model": [ + "note", + "trust" + ], + "query": "{\"trusting\": [\"user\", \"note\"]}", + "type": "add", + "mask": 1, + "field": "", + "permanent": false, + "description": "Ajouter une amitié à sa note" + } + }, + { + "model": "permission.permission", + "pk": 192, + "fields": { + "model": [ + "note", + "trust" + ], + "query": "{\"trusting__is_active\": true}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Ajouter une amitié à une note non bloquée" + } + }, + { + "model": "permission.permission", + "pk": 193, + "fields": { + "model": [ + "note", + "trust" + ], + "query": "{\"trusting__is_active\": true}", + "type": "delete", + "mask": 3, + "field": "", + "permanent": false, + "description": "Supprimer une amitié à une note non bloquée" + } + }, + { + "model": "permission.permission", + "pk": 194, + "fields": { + "model": [ + "note", + "trust" + ], + "query": "{}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir toutes les amitiés, y compris celles des non adhérent⋅es" + } + }, + { + "model": "permission.permission", + "pk": 195, + "fields": { + "model": [ + "note", + "trust" + ], + "query": "{\"trusting__noteuser__user\": [\"user\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir ses propres amitiés, pour toujours" + } + }, + { + "model": "permission.permission", + "pk": 196, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"AND\", {\"source__trusting__trusted\": [\"user\", \"note\"]}, [\"OR\", {\"source__balance__gte\": {\"F\": [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]]}}, {\"valid\": false}]]", + "type": "add", + "mask": 1, + "field": "", + "permanent": false, + "description": "Transférer de l'argent depuis une note amie en restant positif" + } + }, + { + "model": "permission.permission", + "pk": 197, + "fields": { + "model": [ + "note", + "specialtransaction" + ], + "query": "{\"source__notespecial__gte\":0}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer un crédit quelconque" + } + }, + { + "model": "permission.permission", + "pk": 198, + "fields": { + "model": [ + "note", + "trust" + ], + "query": "{\"trusted__noteuser__user\": [\"user\"]}", + "type": "view", + "mask": 1, + "field": "", + "permanent": true, + "description": "Voir ceux nous ayant pour ami, pour toujours" + } + }, + { + "model": "permission.permission", + "pk": 199, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "{\"opener__in\": [\"user\", \"note\", \"activity_responsible\", [\"all\"]], \"open\": true, \"activity_type__manage_entries\":true}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir les activités ouvertes dont l'utilisateur⋅rice est ouvreur⋅se" + } + }, + { + "model": "permission.permission", + "pk": 200, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "{\"opener__in\": [\"user\", \"note\", \"activity_responsible\", [\"all\"]], \"open\": true, \"activity_type__manage_entries\":true}", + "type": "change", + "mask": 2, + "field": "open", + "permanent": false, + "description": "Fermer les activités ouvertes dont l'utilisateur⋅rice est ouvreur⋅se" + } + }, + { + "model": "permission.permission", + "pk": 201, + "fields": { + "model": [ + "activity", + "entry" + ], + "query": "{\"activity__opener__in\": [\"user\", \"note\", \"activity_responsible\", [\"all\"]], \"activity__open\": true, \"activity__activity_type__manage_entries\":true}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Faire les entrées des activités ouvertes dont l'utilisateur⋅rice est ouvreur⋅se" + } + }, + { + "model": "permission.permission", + "pk": 202, + "fields": { + "model": [ + "activity", + "entry" + ], + "query": "{\"activity__opener__in\": [\"user\", \"note\", \"activity_responsible\", [\"all\"]]}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir les entrées des activités dont l'utilisateur⋅rice est ouvreur⋅se" + } + }, + { + "model": "permission.permission", + "pk": 203, + "fields": { + "model": [ + "activity", + "guest" + ], + "query": "{\"activity__opener__in\": [\"user\", \"note\", \"activity_responsible\", [\"all\"]]}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir les invité⋅es des activités dont l'utilisateur⋅rice est ouvreur⋅se" + } + }, + { + "model": "permission.permission", + "pk": 204, + "fields": { + "model": [ + "activity", + "guesttransaction" + ], + "query": "[\"NOT\", {\"pk__isnull\": [\"user\", \"note\", \"activity_responsible\", [\"filter\", {\"activity__open\": true, \"activity__activity_type__manage_entries\":true}], [\"exists\"]]}]", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer une transaction d'invitation lorsque l'utilisateur⋅rice est ouvreur⋅se d'une activité ouverte" + } + }, + { + + "model": "permission.permission", + "pk": 205, + "fields": { + "model": [ + "note", + "specialtransaction" + ], + "query": "[\"NOT\", {\"pk__isnull\": [\"user\", \"note\", \"activity_responsible\", [\"filter\", {\"activity__open\": true, \"activity__activity_type__manage_entries\":true}], [\"exists\"]]}]", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer un crédit ou un retrait quelconque lorsque l'utilisateur⋅rice est ouvreur⋅se d'une activité ouverte" + } + }, + { + "model": "permission.permission", + "pk": 206, + "fields": { + "model": [ + "note", + "notespecial" + ], + "query": "[\"NOT\", {\"pk__isnull\": [\"user\", \"note\", \"activity_responsible\", [\"filter\", {\"activity__open\": true, \"activity__activity_type__manage_entries\":true}], [\"exists\"]]}]", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Afficher l'interface crédit/retrait lorsque l'utilisateur⋅rice est ouvreur⋅se d'une activité ouverte" + } + }, + { + "model": "permission.permission", + "pk": 207, + "fields": { + "model": [ + "activity", + "opener" + ], + "query": "{}", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir les ouvreur⋅ses des activités" + } + }, + { + "model": "permission.permission", + "pk": 208, + "fields": { + "model": [ + "activity", + "opener" + ], + "query": "{}", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Ajouter des ouvreur⋅ses aux activités" + } + }, + { + "model": "permission.permission", + "pk": 209, + "fields": { + "model": [ + "activity", + "opener" + ], + "query": "{}", + "type": "delete", + "mask": 2, + "field": "", + "permanent": false, + "description": "Supprimer des ouvreur⋅ses aux activités" + } + }, + { + "model": "permission.permission", + "pk": 210, + "fields": { + "model": [ + "activity", + "activity" + ], + "query": "{}", + "type": "change", + "mask": 2, + "field": "opener", + "permanent": false, + "description": "Voir le tableau des ouvreur⋅ses" + } + }, + { + "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": 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": 213, + "fields": { + "model": [ + "food", + "transformedfood" + ], + "query": "{\"is_ready\": true, \"is_active\": true, \"was_eaten\": false}", + "type": "view", + "mask": 1, + "field": "", + "permanent": false, + "description": "Voir les plats préparés actifs servis" + } + }, + { + "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": 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": 216, + "fields": { + "model": [ + "food", + "basicfood" + ], + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "permanent": false, + "description": "Créer un nouvel ingrédient" + } + }, + { + "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": 218, + "fields": { + "model": [ + "food", + "basicfood" + ], + "query": "{\"is_active\": true}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir toute la bouffe active" + } + }, + { + "model": "permission.permission", + "pk": 219, + "fields": { + "model": [ + "food", + "basicfood" + ], + "query": "{\"is_active\": true, \"owner\": [\"club\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir la bouffe active de 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": 221, + "fields": { + "model": [ + "food", + "basicfood" + ], + "query": "{\"is_active\": true, \"was_eaten\": false}", + "type": "change", + "mask": 3, + "field": "allergens", + "permanent": false, + "description": "Modifier les allergènes de la bouffe existante" + } + }, + { + "model": "permission.permission", + "pk": 222, + "fields": { + "model": [ + "food", + "basicfood" + ], + "query": "{\"is_active\": true, \"was_eaten\": false, \"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_active\": true}", + "type": "change", + "mask": 3, + "field": "was_eaten", + "permanent": false, + "description": "Indiquer si un plat a été mangé" + } + }, + { + "model": "permission.permission", + "pk": 227, + "fields": { + "model": [ + "food", + "transformedfood" + ], + "query": "{\"is_active\": 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_active\": true}", + "type": "change", + "mask": 3, + "field": "is_active", + "permanent": false, + "description": "Archiver un plat" + } + }, + { + "model": "permission.permission", + "pk": 229, + "fields": { + "model": [ + "food", + "basicfood" + ], + "query": "{\"is_active\": true}", + "type": "change", + "mask": 3, + "field": "is_active", + "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, + "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_active\": 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_active\": 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\"], \"is_active\": true}", + "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": "{\"is_active\": true}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir bouffe active" + } + }, + { + "model": "permission.permission", + "pk": 237, + "fields": { + "model": [ + "food", + "food" + ], + "query": "{\"is_active\": true, \"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": 239, + "fields": { + "model": [ + "note", + "alias" + ], + "query": "[\"AND\", {\"note__noteuser__user__memberships__club\": [\"club\"], \"note__noteuser__user__memberships__date_start__lte\": [\"today\"], \"note__noteuser__user__memberships__date_end__gte\": [\"today\"]}, {\"note__is_active\": true}]", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir les alias des notes des adhérent⋅es du club" + } + }, + { + "model": "permission.permission", + "pk": 240, + "fields": { + "model": [ + "note", + "alias" + ], + "query": "[\"AND\", {\"note__noteuser__user__memberships__club\": [\"club\", \"parent_club\"], \"note__noteuser__user__memberships__date_start__lte\": [\"today\"], \"note__noteuser__user__memberships__date_end__gte\": [\"today\"]}, {\"note__is_active\": true}]", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir les alias des notes des adhérent⋅es du club parent" + } + }, + { + "model": "permission.permission", + "pk": 241, + "fields": { + "model": [ + "auth", + "user" + ], + "query": "[\"AND\", {\"memberships__club\": [\"club\", \"parent_club\"], \"memberships__date_start__lte\": [\"today\"], \"memberships__date_end__gte\": [\"today\"]}, {\"note__is_active\": true}]", + "type": "view", + "mask": 2, + "field": "", + "permanent": false, + "description": "Voir les utilisateurs adhérents au club parent" + } + }, + { + "model": "permission.permission", + "pk": 242, + "fields": { + "model": [ + "note", + "transaction" + ], + "query": "[\"AND\", {\"destination\": [\"club\", \"note\"]}, [\"OR\", {\"source__balance__gte\": {\"F\": [\"SUB\", [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]], 2000]}}, {\"valid\": false}]]", + "type": "add", + "mask": 2, + "field": "", + "permanent": false, + "description": "Créer une transaction vers la note d'un club" + } + }, + { + "model": "permission.permission", + "pk": 243, + "fields": { + "model": [ + "member", + "profile" + ], + "query": "{\"user__memberships__club\": [\"club\"], \"user__memberships__date_start__lte\": [\"today\"],\"user__memberships__date_end__gte\": [\"today\"]}", + "type": "view", + "mask": 3, + "field": "", + "permanent": false, + "description": "Voir les profils des membres du club" + } + }, + { + "model": "permission.permission", + "pk": 244, + "fields": { + "model": [ + "member", + "membership" + ], + "query": "{\"club\": 2}", + "type": "view", + "mask": 1, + "field": "", + "permanent": false, + "description": "Voir les adhérent·e·s Kfet" + } + }, + { + "model": "permission.role", + "pk": 1, + "fields": { + "for_club": 1, + "name": "Adh\u00e9rent\u22c5e BDE", + "permissions": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 22, + 34, + 48, + 52, + 126, + 161, + 162, + 165, + 179, + 186, + 187, + 188, + 189, 190, 191, 195, 196, - 198 - ] - } - }, - { - "model": "permission.role", - "pk": 2, - "fields": { - "for_club": 2, - "name": "Adh\u00e9rent Kfet", - "permissions": [ - 22, - 36, - 39, - 40, - 78, - 79, - 83, - 87, - 90, - 93, - 95, - 97, - 99, - 101, - 108, - 109, - 129, - 131, - 144, - 152, - 153, - 154, - 155, - 156, - 157, - 158, - 159, - 160 - ] - } - }, - { - "model": "permission.role", - "pk": 3, - "fields": { - "for_club": null, - "name": "Membre de club", - "permissions": [ - 22 - ] - } - }, - { - "model": "permission.role", - "pk": 4, - "fields": { - "for_club": null, - "name": "Bureau de club", - "permissions": [ - 47, - 49, - 50, - 141, - 169 - ] - } - }, - { - "model": "permission.role", - "pk": 5, - "fields": { - "for_club": null, - "name": "Pr\u00e9sident\u00b7e de club", - "permissions": [ - 62, - 142, - 135 - ] - } - }, - { - "model": "permission.role", - "pk": 6, - "fields": { - "for_club": null, - "name": "Tr\u00e9sorier\u00b7\u00e8re de club", - "permissions": [ - 19, - 20, - 21, - 27, - 59, - 60, - 61, - 62, - 127, - 133, - 142, - 182, - 184, - 185 - ] - } - }, - { - "model": "permission.role", - "pk": 7, - "fields": { - "for_club": 1, - "name": "Pr\u00e9sident\u00b7e BDE", - "permissions": [ - 24, - 25, - 26, - 27, - 30, - 41, - 42, - 63, - 66, - 135, - 136, - 137, - 150, - 163, - 164, - 166, - 167, - 168, - 172 - ] - } - }, - { - "model": "permission.role", - "pk": 8, - "fields": { - "for_club": 1, - "name": "Tr\u00e9sorier\u00b7\u00e8re BDE", - "permissions": [ - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 33, - 37, - 38, - 41, - 42, - 43, - 51, - 53, - 54, - 55, - 56, - 57, - 58, - 63, - 64, - 65, - 66, - 67, - 68, - 69, - 70, - 71, - 72, - 73, - 132, - 134, - 135, - 136, - 137, - 138, - 139, - 140, - 143, - 146, - 147, - 148, - 149, - 150, - 151, - 163, - 164, - 172, - 173, - 174, - 175, - 176, - 177, - 178, - 183 - ] - } - }, - { - "model": "permission.role", - "pk": 9, - "fields": { - "for_club": 1, - "name": "Respo info", - "permissions": [] - } - }, - { - "model": "permission.role", - "pk": 10, - "fields": { - "for_club": 2, - "name": "GC Kfet", - "permissions": [ - 23, - 24, - 25, - 26, - 27, - 28, - 29, - 30, - 31, - 32, - 37, - 38, - 41, - 43, - 148, - 149, - 166, - 167, - 168, - 182 - ] - } - }, - { - "model": "permission.role", - "pk": 11, - "fields": { - "for_club": 2, - "name": "Res[pot]", - "permissions": [ - 19, - 25, - 26, - 37, - 38, - 41, - 42, - 43, - 44, - 45, - 46, - 148, - 149, - 182 - ] - } - }, - { - "model": "permission.role", - "pk": 12, - "fields": { - "for_club": null, - "name": "GC WEI", - "permissions": [ - 22, - 70, - 72, - 76, - 85, - 86, - 88, - 89, - 91, - 92, - 94, - 96, - 98, - 100, - 102, - 103, - 104, - 105, - 106, - 107, - 110, - 111, - 112, - 113, - 128, - 130 - ] - } - }, - { - "model": "permission.role", - "pk": 13, - "fields": { - "for_club": null, - "name": "Chef de bus", - "permissions": [ - 22, - 84, - 115, - 117, - 118, - 119, - 120, - 121, - 122 - ] - } - }, - { - "model": "permission.role", - "pk": 14, - "fields": { - "for_club": null, - "name": "Chef d'\u00e9quipe", - "permissions": [ - 22, - 84, - 116, - 123, - 124, - 125 - ] - } - }, - { - "model": "permission.role", - "pk": 15, - "fields": { - "for_club": null, - "name": "\u00c9lectron libre", - "permissions": [ - 22, - 84 - ] - } - }, - { - "model": "permission.role", - "pk": 16, - "fields": { - "for_club": null, - "name": "\u00c9lectron libre (avec perm)", - "permissions": [ - 22, - 84 - ] - } - }, - { - "model": "permission.role", - "pk": 17, - "fields": { - "for_club": null, - "name": "1A", - "permissions": [] - } - }, - { - "model": "permission.role", - "pk": 18, - "fields": { - "for_club": null, - "name": "Adhérent WEI", - "permissions": [ - 77, - 114 - ] - } - }, - { - "model": "permission.role", - "pk": 19, - "fields": { - "for_club": 1, - "name": "Secrétaire BDE", - "permissions": [ - 37, - 41, - 53, - 54, - 55, - 56, - 57, - 58, - 135, - 136, - 137, - 138, - 139, - 140, - 143, - 145, - 146, - 147, - 148, - 149, - 150, - 176, - 177, - 180, - 181, - 183 - ] - } - }, - { - "model": "permission.role", - "pk": 20, - "fields": { - "for_club": 1, - "name": "PC Kfet", - "permissions": [ - 6, - 24, - 25, - 27, - 30, - 34, - 49, - 50, - 135, - 137, - 147, - 150, - 166, - 167, - 168, - 176, - 177, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206 + ] + } + }, + { + "model": "permission.role", + "pk": 2, + "fields": { + "for_club": 2, + "name": "Adh\u00e9rent\u22c5e Kfet", + "permissions": [ + 22, + 36, + 39, + 40, + 78, + 79, + 83, + 87, + 90, + 93, + 95, + 97, + 99, + 101, + 108, + 109, + 129, + 131, + 144, + 152, + 153, + 154, + 155, + 156, + 157, + 158, + 159, + 160, + 213 + ] + } + }, + { + "model": "permission.role", + "pk": 3, + "fields": { + "for_club": null, + "name": "Membre de club", + "permissions": [ + 1, + 2, + 3, + 4, + 5, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 22, + 48 + ] + } + }, + { + "model": "permission.role", + "pk": 4, + "fields": { + "for_club": null, + "name": "Bureau de club", + "permissions": [ + 47, + 49, + 50, + 141, + 169, + 212, + 214, + 215, + 219, + 222, + 224, + 227, + 233, + 234, + 237 + ] + } + }, + { + "model": "permission.role", + "pk": 5, + "fields": { + "for_club": null, + "name": "Pr\u00e9sident\u22c5e de club", + "permissions": [ + 62, + 142, + 135 + ] + } + }, + { + "model": "permission.role", + "pk": 6, + "fields": { + "for_club": null, + "name": "Tr\u00e9sorièr\u22c5e de club", + "permissions": [ + 6, + 19, + 20, + 21, + 27, + 59, + 60, + 61, + 62, + 127, + 133, + 142, + 182, + 184, + 185, + 239, + 240, + 241 + ] + } + }, + { + "model": "permission.role", + "pk": 7, + "fields": { + "for_club": 1, + "name": "Pr\u00e9sident\u22c5e BDE", + "permissions": [ + 24, + 25, + 26, + 27, + 30, + 41, + 42, + 63, + 66, + 135, + 136, + 137, + 150, + 163, + 164, + 166, + 167, + 168, + 172 + ] + } + }, + { + "model": "permission.role", + "pk": 8, + "fields": { + "for_club": 1, + "name": "Tr\u00e9sorièr\u22c5e BDE", + "permissions": [ + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 37, + 38, + 41, + 42, + 43, + 51, + 53, + 54, + 55, + 56, + 57, + 58, + 63, + 64, + 65, + 66, + 67, + 68, + 69, + 70, + 71, + 72, + 73, + 132, + 134, + 135, + 136, + 137, + 138, + 139, + 140, + 143, + 146, + 147, + 148, + 149, + 150, + 151, + 163, + 164, + 172, + 173, + 174, + 175, + 176, + 177, 178, - 197, - 199 - ] - } - }, - { - "model": "permission.role", - "pk": 21, - "fields": { - "for_club": 1, - "name": "GC anti-VSS", - "permissions": [ - 150, - 163, - 164, - 182 - ] - } - }, - { - "model": "wei.weirole", - "pk": 12, - "fields": {} - }, - { - "model": "wei.weirole", - "pk": 13, - "fields": {} - }, - { - "model": "wei.weirole", - "pk": 14, - "fields": {} - }, - { - "model": "wei.weirole", - "pk": 15, - "fields": {} - }, - { - "model": "wei.weirole", - "pk": 16, - "fields": {} - }, - { - "model": "wei.weirole", - "pk": 17, - "fields": {} - }, - { - "model": "wei.weirole", - "pk": 18, - "fields": {} - } + 183 + ] + } + }, + { + "model": "permission.role", + "pk": 9, + "fields": { + "for_club": 1, + "name": "Respo info", + "permissions": [] + } + }, + { + "model": "permission.role", + "pk": 10, + "fields": { + "for_club": 2, + "name": "GC Kfet", + "permissions": [ + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 37, + 38, + 41, + 43, + 148, + 149, + 166, + 167, + 168, + 182, + 212, + 214, + 215, + 218, + 221, + 224, + 226, + 227, + 228, + 229, + 230, + 232, + 234, + 236 + ] + } + }, + { + "model": "permission.role", + "pk": 11, + "fields": { + "for_club": 2, + "name": "Res[pot]", + "permissions": [ + 19, + 25, + 26, + 37, + 38, + 41, + 42, + 43, + 44, + 45, + 46, + 148, + 149, + 182, + 207, + 208, + 209, + 210 + ] + } + }, + { + "model": "permission.role", + "pk": 12, + "fields": { + "for_club": null, + "name": "GC WEI", + "permissions": [ + 22, + 70, + 72, + 76, + 85, + 86, + 88, + 89, + 91, + 92, + 94, + 96, + 98, + 100, + 102, + 103, + 104, + 105, + 106, + 107, + 110, + 111, + 112, + 113, + 128, + 130 + ] + } + }, + { + "model": "permission.role", + "pk": 13, + "fields": { + "for_club": null, + "name": "Chef\u22c5fe de bus", + "permissions": [ + 22, + 84, + 115, + 117, + 118, + 119, + 120, + 121, + 122 + ] + } + }, + { + "model": "permission.role", + "pk": 14, + "fields": { + "for_club": null, + "name": "Chef\u22c5fe d'\u00e9quipe", + "permissions": [ + 22, + 84, + 116, + 123, + 124, + 125 + ] + } + }, + { + "model": "permission.role", + "pk": 15, + "fields": { + "for_club": null, + "name": "\u00c9lectron libre", + "permissions": [ + 22, + 84 + ] + } + }, + { + "model": "permission.role", + "pk": 16, + "fields": { + "for_club": null, + "name": "\u00c9lectron libre (avec perm)", + "permissions": [ + 22, + 84 + ] + } + }, + { + "model": "permission.role", + "pk": 17, + "fields": { + "for_club": null, + "name": "1A", + "permissions": [] + } + }, + { + "model": "permission.role", + "pk": 18, + "fields": { + "for_club": null, + "name": "Adhérent\u22c5e WEI", + "permissions": [ + 77, + 114 + ] + } + }, + { + "model": "permission.role", + "pk": 19, + "fields": { + "for_club": 1, + "name": "Secrétaire BDE", + "permissions": [ + 37, + 41, + 53, + 54, + 55, + 56, + 57, + 58, + 135, + 136, + 137, + 138, + 139, + 140, + 143, + 145, + 146, + 147, + 148, + 149, + 150, + 176, + 177, + 180, + 181, + 183 + ] + } + }, + { + "model": "permission.role", + "pk": 20, + "fields": { + "for_club": 1, + "name": "PC Kfet", + "permissions": [ + 6, + 24, + 25, + 27, + 30, + 34, + 49, + 50, + 135, + 137, + 147, + 150, + 166, + 167, + 168, + 176, + 177, + 178, + 197, + 211, + 244 + ] + } + }, + { + "model": "permission.role", + "pk": 21, + "fields": { + "for_club": 1, + "name": "GC anti-VSS", + "permissions": [ + 150, + 163, + 164, + 182 + ] + } + }, + { + "model": "permission.role", + "pk": 22, + "fields": { + "for_club": 2, + "name": "Respo Bouffe", + "permissions": [ + 137, + 211, + 214, + 216, + 217, + 220, + 223, + 225, + 231, + 235, + 238 + ] + } + }, + { + "model": "wei.weirole", + "pk": 12, + "fields": {} + }, + { + "model": "wei.weirole", + "pk": 13, + "fields": {} + }, + { + "model": "wei.weirole", + "pk": 14, + "fields": {} + }, + { + "model": "wei.weirole", + "pk": 15, + "fields": {} + }, + { + "model": "wei.weirole", + "pk": 16, + "fields": {} + }, + { + "model": "wei.weirole", + "pk": 17, + "fields": {} + }, + { + "model": "wei.weirole", + "pk": 18, + "fields": {} + } ] diff --git a/apps/permission/models.py b/apps/permission/models.py index fbb46a90..e975485a 100644 --- a/apps/permission/models.py +++ b/apps/permission/models.py @@ -135,18 +135,18 @@ class Permission(models.Model): # A json encoded Q object with the following grammar # query -> [] | {} (the empty query representing all objects) - # query -> ["AND", query, …] AND multiple queries - # | ["OR", query, …] OR multiple queries + # query -> ["AND", query, ...] AND multiple queries + # | ["OR", query, ...] OR multiple queries # | ["NOT", query] Opposite of query - # query -> {key: value, …} A list of fields and values of a Q object + # query -> {key: value, ...} A list of fields and values of a Q object # key -> string A field name # value -> int | string | bool | null Literal values - # | [parameter, …] A parameter. See compute_param for more details. + # | [parameter, ...] A parameter. See compute_param for more details. # | {"F": oper} An F object - # oper -> [string, …] A parameter. See compute_param for more details. - # | ["ADD", oper, …] Sum multiple F objects or literal + # oper -> [string, ...] A parameter. See compute_param for more details. + # | ["ADD", oper, ...] Sum multiple F objects or literal # | ["SUB", oper, oper] Substract two F objects or literal - # | ["MUL", oper, …] Multiply F objects or literals + # | ["MUL", oper, ...] Multiply F objects or literals # | int | string | bool | null Literal values # | ["F", string] A field # diff --git a/apps/permission/scopes.py b/apps/permission/scopes.py index c3edfd93..af504b18 100644 --- a/apps/permission/scopes.py +++ b/apps/permission/scopes.py @@ -35,6 +35,8 @@ class PermissionScopes(BaseScopes): class PermissionOAuth2Validator(OAuth2Validator): + oidc_claim_scope = None # fix breaking change of django-oauth-toolkit 2.0.0 + def validate_scopes(self, client_id, scopes, client, request, *args, **kwargs): """ User can request as many scope as he wants, including invalid scopes, diff --git a/apps/permission/tables.py b/apps/permission/tables.py index 894d4d13..955b14bb 100644 --- a/apps/permission/tables.py +++ b/apps/permission/tables.py @@ -36,8 +36,8 @@ class RightsTable(tables.Table): def render_roles(self, record): # If the user has the right to manage the roles, display the link to manage them - roles = record.roles.filter((~(Q(name="Adhérent BDE") - | Q(name="Adhérent Kfet") + roles = record.roles.filter((~(Q(name="Adhérent⋅e BDE") + | Q(name="Adhérent⋅e Kfet") | Q(name="Membre de club") | Q(name="Bureau de club")) & Q(weirole__isnull=True))).all() diff --git a/apps/permission/tests/test_oauth2.py b/apps/permission/tests/test_oauth2.py index b37902b5..889ad588 100644 --- a/apps/permission/tests/test_oauth2.py +++ b/apps/permission/tests/test_oauth2.py @@ -58,7 +58,7 @@ class OAuth2TestCase(TestCase): # Create membership to validate permissions NoteUser.objects.create(user=self.user) membership = Membership.objects.create(user=self.user, club_id=bde.pk) - membership.roles.add(Role.objects.get(name="Adhérent BDE")) + membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE")) membership.save() # User is now a member and can now see its own user detail @@ -85,7 +85,7 @@ class OAuth2TestCase(TestCase): bde = Club.objects.get(name="BDE") NoteUser.objects.create(user=self.user) membership = Membership.objects.create(user=self.user, club_id=bde.pk) - membership.roles.add(Role.objects.get(name="Adhérent BDE")) + membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE")) membership.save() resp = self.client.get(reverse('permission:scopes')) diff --git a/apps/permission/views.py b/apps/permission/views.py index 77e5a4d0..053efa39 100644 --- a/apps/permission/views.py +++ b/apps/permission/views.py @@ -12,6 +12,7 @@ from django.forms import HiddenInput from django.http import Http404 from django.utils.translation import gettext_lazy as _ from django.views.generic import UpdateView, TemplateView, CreateView +from django_tables2 import MultiTableMixin from member.models import Membership from .backends import PermissionBackend @@ -35,11 +36,9 @@ class ProtectQuerysetMixin: try: return super().get_object(queryset) except Http404 as e: - try: - super().get_object(self.get_queryset(filter_permissions=False)) - raise PermissionDenied() - except Http404: + if self.get_queryset(filter_permissions=False).count() == self.get_queryset().count(): raise e + raise PermissionDenied() def get_form(self, form_class=None): form = super().get_form(form_class) @@ -107,10 +106,31 @@ class ProtectedCreateView(LoginRequiredMixin, CreateView): return super().dispatch(request, *args, **kwargs) -class RightsView(TemplateView): +class RightsView(MultiTableMixin, TemplateView): template_name = "permission/all_rights.html" extra_context = {"title": _("Rights")} + tables = [ + lambda data: RightsTable(data, prefix="clubs-"), + lambda data: SuperuserTable(data, prefix="superusers-"), + ] + + def get_tables_data(self): + special_memberships = Membership.objects.filter( + date_start__lte=date.today(), + date_end__gte=date.today(), + ).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent⋅e BDE") + | Q(name="Adhérent⋅e Kfet") + | Q(name="Membre de club") + | Q(name="Bureau de club")) + & Q(weirole__isnull=True))))\ + .order_by("club__name", "user__last_name")\ + .distinct().all() + return [ + special_memberships, + User.objects.filter(is_superuser=True).order_by("last_name"), + ] + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -128,19 +148,9 @@ class RightsView(TemplateView): role.clubs = [membership.club for membership in active_memberships if role in membership.roles.all()] if self.request.user.is_authenticated: - special_memberships = Membership.objects.filter( - date_start__lte=date.today(), - date_end__gte=date.today(), - ).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent BDE") - | Q(name="Adhérent Kfet") - | Q(name="Membre de club") - | Q(name="Bureau de club")) - & Q(weirole__isnull=True))))\ - .order_by("club__name", "user__last_name")\ - .distinct().all() - context["special_memberships_table"] = RightsTable(special_memberships, prefix="clubs-") - context["superusers"] = SuperuserTable(User.objects.filter(is_superuser=True).order_by("last_name").all(), - prefix="superusers-") + tables = context["tables"] + for name, table in zip(["special_memberships_table", "superusers"], tables): + context[name] = table return context diff --git a/apps/registration/forms.py b/apps/registration/forms.py index f6c980c9..89dc5de4 100644 --- a/apps/registration/forms.py +++ b/apps/registration/forms.py @@ -5,7 +5,6 @@ from django import forms from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User from django.utils.translation import gettext_lazy as _ -# from member.models import Club from note.models import NoteSpecial, Alias from note_kfet.inputs import AmountInput @@ -115,12 +114,3 @@ class ValidationForm(forms.Form): required=False, initial=True, ) - -# If the bda exists -# if Club.objects.filter(name__iexact="bda").exists(): -# The user can join the bda club at the inscription -# join_bda = forms.BooleanField( -# label=_("Join BDA Club"), -# required=False, -# initial=True, -# ) diff --git a/apps/registration/views.py b/apps/registration/views.py index 02d6f05e..bb23a0b4 100644 --- a/apps/registration/views.py +++ b/apps/registration/views.py @@ -1,6 +1,7 @@ # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +from django import forms from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User @@ -15,6 +16,7 @@ from django.views import View from django.views.generic import CreateView, TemplateView, DetailView from django.views.generic.edit import FormMixin from django_tables2 import SingleTableView +from api.viewsets import is_regex from member.forms import ProfileForm from member.models import Membership, Club from note.models import SpecialTransaction, Alias @@ -191,11 +193,16 @@ class FutureUserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableVi if "search" in self.request.GET and self.request.GET["search"]: pattern = self.request.GET["search"] + # Check if this is a valid regex. If not, we won't check regex + valid_regex = is_regex(pattern) + suffix_username = "__iregex" if valid_regex else "__icontains" + suffix = "__iregex" if valid_regex else "__istartswith" + prefix = "^" if valid_regex else "" qs = qs.filter( - Q(first_name__iregex=pattern) - | Q(last_name__iregex=pattern) - | Q(profile__section__iregex=pattern) - | Q(username__iregex="^" + pattern) + Q(**{f"first_name{suffix}": pattern}) + | Q(**{f"last_name{suffix}": pattern}) + | Q(**{f"profile__section{suffix}": pattern}) + | Q(**{f"username{suffix_username}": prefix + pattern}) ) return qs @@ -238,9 +245,8 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid kfet = Club.objects.get(name="Kfet") fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid - if Club.objects.filter(name__iexact="BDA").exists(): - bda = Club.objects.get(name__iexact="BDA") - fee += bda.membership_fee_paid if user.profile.paid else bda.membership_fee_unpaid + for club in Club.objects.filter(add_registration_form=True): + fee += club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid ctx["total_fee"] = "{:.02f}".format(fee / 100, ) # ctx["declare_soge_account"] = SogeCredit.objects.filter(user=user).exists() @@ -249,6 +255,16 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, def get_form(self, form_class=None): form = super().get_form(form_class) + + # add clubs that are in registration form + for club in Club.objects.filter(add_registration_form=True).order_by("name"): + form_join_club = forms.BooleanField( + label=_("Join %(club)s Club") % {'club': club.name}, + required=False, + initial=False, + ) + form.fields.update({f"join_{club.id}": form_join_club}) + user = self.get_object() form.fields["last_name"].initial = user.last_name form.fields["first_name"].initial = user.first_name @@ -266,11 +282,6 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, form.add_error(None, _("An alias with a similar name already exists.")) return self.form_invalid(form) - # Check if BDA exist to propose membership at regisration - bda_exists = False - if Club.objects.filter(name__iexact="BDA").exists(): - bda_exists = True - # Get form data # soge = form.cleaned_data["soge"] credit_type = form.cleaned_data["credit_type"] @@ -280,17 +291,22 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, bank = form.cleaned_data["bank"] join_bde = form.cleaned_data["join_bde"] join_kfet = form.cleaned_data["join_kfet"] - if bda_exists: - join_bda = form.cleaned_data["join_bda"] + + clubs_registration = Club.objects.filter(add_registration_form=True).order_by("name") + join_clubs = [(club, form.cleaned_data[f"join_{club.id}"]) for club in clubs_registration] # if soge: # # If Société Générale pays the inscription, the user automatically joins the two clubs. # join_bde = True # join_kfet = True - if not join_bde: + if not (join_bde or any(b for _, b in join_clubs)): # This software belongs to the BDE. - form.add_error('join_bde', _("You must join the BDE.")) + form.add_error('join_bde', _("You must join a club.")) + return super().form_invalid(form) + + if join_kfet and not join_bde: + form.add_error('join_bde', _("You must also join the parent club BDE.")) return super().form_invalid(form) # Calculate required registration fee @@ -303,11 +319,12 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid # Add extra fee for the full membership fee += kfet_fee if join_kfet else 0 - if bda_exists: - bda = Club.objects.get(name__iexact="BDA") - bda_fee = bda.membership_fee_paid if user.profile.paid else bda.membership_fee_unpaid - # Add extra fee for the bda membership - fee += bda_fee if join_bda else 0 + clubs_fee = dict() + for club, join_club in join_clubs: + club_fee = club.membership_fee_paid if user.profile.paid else club.membership_fee_unpaid + # Add extra fee for the club membership + clubs_fee[club] = club_fee + fee += club_fee if join_club else 0 # # If the bank pays, then we don't credit now. Treasurers will validate the transaction # # and credit the note later. @@ -370,7 +387,7 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, # membership._soge = True membership.save() membership.refresh_from_db() - membership.roles.add(Role.objects.get(name="Adhérent BDE")) + membership.roles.add(Role.objects.get(name="Adhérent⋅e BDE")) membership.save() if join_kfet: @@ -384,20 +401,21 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, # membership._soge = True membership.save() membership.refresh_from_db() - membership.roles.add(Role.objects.get(name="Adhérent Kfet")) + membership.roles.add(Role.objects.get(name="Adhérent⋅e Kfet")) membership.save() - if bda_exists and join_bda: - # Create membership for the user to the BDA starting today - membership = Membership( - club=bda, - user=user, - fee=bda_fee, - ) - membership.save() - membership.refresh_from_db() - membership.roles.add(Role.objects.get(name="Membre de club")) - membership.save() + for club, join_club in join_clubs: + if join_club: + # Create membership for the user to the BDA starting today + membership = Membership( + club=club, + user=user, + fee=clubs_fee[club], + ) + membership.save() + membership.refresh_from_db() + membership.roles.add(Role.objects.get(name="Membre de club")) + membership.save() # if soge: # soge_credit = SogeCredit.objects.get(user=user) diff --git a/apps/treasury/api/views.py b/apps/treasury/api/views.py index c7bf68e6..418bf220 100644 --- a/apps/treasury/api/views.py +++ b/apps/treasury/api/views.py @@ -2,10 +2,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later from django_filters.rest_framework import DjangoFilterBackend -from rest_framework.filters import SearchFilter +from api.filters import RegexSafeSearchFilter from api.viewsets import ReadProtectedModelViewSet -from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer,\ +from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer, \ SogeCreditSerializer from ..models import Invoice, Product, RemittanceType, Remittance, SogeCredit @@ -18,7 +18,7 @@ class InvoiceViewSet(ReadProtectedModelViewSet): """ queryset = Invoice.objects.order_by('id') serializer_class = InvoiceSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['bde', 'object', 'description', 'name', 'address', 'date', 'acquitted', 'locked', ] search_fields = ['$object', '$description', '$name', '$address', ] @@ -31,7 +31,7 @@ class ProductViewSet(ReadProtectedModelViewSet): """ queryset = Product.objects.order_by('invoice_id', 'id') serializer_class = ProductSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['invoice', 'designation', 'quantity', 'amount', ] search_fields = ['$designation', '$invoice__object', ] @@ -44,7 +44,7 @@ class RemittanceTypeViewSet(ReadProtectedModelViewSet): """ queryset = RemittanceType.objects.order_by('id') serializer_class = RemittanceTypeSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['note', ] search_fields = ['$note__special_type', ] @@ -57,7 +57,7 @@ class RemittanceViewSet(ReadProtectedModelViewSet): """ queryset = Remittance.objects.order_by('id') serializer_class = RemittanceSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['date', 'remittance_type', 'comment', 'closed', 'transaction_proxies__transaction', ] search_fields = ['$remittance_type__note__special_type', '$comment', ] @@ -70,7 +70,7 @@ class SogeCreditViewSet(ReadProtectedModelViewSet): """ queryset = SogeCredit.objects.order_by('id') serializer_class = SogeCreditSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['user', 'user__last_name', 'user__first_name', 'user__email', 'user__note__alias__name', 'user__note__alias__normalized_name', 'transactions', 'credit_transaction', ] search_fields = ['$user__last_name', '$user__first_name', '$user__email', '$user__note__alias__name', diff --git a/apps/treasury/migrations/0009_alter_sogecredit_transactions.py b/apps/treasury/migrations/0009_alter_sogecredit_transactions.py new file mode 100644 index 00000000..e3a46576 --- /dev/null +++ b/apps/treasury/migrations/0009_alter_sogecredit_transactions.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.15 on 2024-08-28 08:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('note', '0007_alter_note_polymorphic_ctype_and_more'), + ('treasury', '0008_auto_20240322_0045'), + ] + + operations = [ + migrations.AlterField( + model_name='sogecredit', + name='transactions', + field=models.ManyToManyField(blank=True, related_name='+', to='note.membershiptransaction', verbose_name='membership transactions'), + ), + ] diff --git a/apps/treasury/tables.py b/apps/treasury/tables.py index c5272091..ae87ab1a 100644 --- a/apps/treasury/tables.py +++ b/apps/treasury/tables.py @@ -37,6 +37,7 @@ class InvoiceTable(tables.Table): args=[A('id')], verbose_name=_("delete"), text=_("Delete"), + orderable=False, attrs={ 'th': { 'id': 'delete-membership-header' @@ -70,6 +71,7 @@ class RemittanceTable(tables.Table): verbose_name=_("View"), args=[A("pk")], text=_("View"), + orderable=False, attrs={ 'a': {'class': 'btn btn-primary'} }, ) @@ -97,6 +99,7 @@ class SpecialTransactionTable(tables.Table): verbose_name=_("Remittance"), args=[A("specialtransactionproxy__pk")], text=_("Add"), + orderable=False, attrs={ 'a': {'class': 'btn btn-primary'} }, ) @@ -105,6 +108,7 @@ class SpecialTransactionTable(tables.Table): verbose_name=_("Remittance"), args=[A("specialtransactionproxy__pk")], text=_("Remove"), + orderable=False, attrs={ 'a': {'class': 'btn btn-primary btn-danger'} }, ) @@ -130,10 +134,12 @@ class SogeCreditTable(tables.Table): amount = tables.Column( verbose_name=_("Amount"), + orderable=False, ) valid = tables.Column( verbose_name=_("Valid"), + orderable=False, ) def render_amount(self, value): diff --git a/apps/treasury/templates/treasury/invoice_sample.tex b/apps/treasury/templates/treasury/invoice_sample.tex index 29db4cfb..3ce36505 100644 --- a/apps/treasury/templates/treasury/invoice_sample.tex +++ b/apps/treasury/templates/treasury/invoice_sample.tex @@ -109,7 +109,7 @@ \renewcommand{\headrulewidth}{0pt} \cfoot{ \small{\MonNom ~--~ \MonAdresseRue ~ \MonAdresseVille ~--~ Téléphone : +33(0)7 78 17 22 34\newline - Site web : bde.ens-cachan.fr ~--~ E-mail : tresorerie.bde@lists.crans.org \newline Numéro SIRET : 399 485 838 00029 + E-mail : tresorerie.bde@lists.crans.org ~--~ Numéro SIRET : 399 485 838 00029 } } diff --git a/apps/treasury/urls.py b/apps/treasury/urls.py index 2759358f..6e2517b3 100644 --- a/apps/treasury/urls.py +++ b/apps/treasury/urls.py @@ -3,8 +3,8 @@ from django.urls import path -from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceDeleteView, InvoiceRenderView,\ - RemittanceListView, RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView,\ +from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceDeleteView, InvoiceRenderView, \ + RemittanceListView, RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView, \ UnlinkTransactionToRemittanceView, SogeCreditListView, SogeCreditManageView app_name = 'treasury' diff --git a/apps/treasury/views.py b/apps/treasury/views.py index be989f99..33bdb88c 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -19,7 +19,8 @@ from django.utils.translation import gettext_lazy as _ from django.views.generic import UpdateView, DetailView from django.views.generic.base import View, TemplateView from django.views.generic.edit import BaseFormView, DeleteView -from django_tables2 import SingleTableView +from django_tables2 import MultiTableMixin, SingleTableMixin, SingleTableView +from api.viewsets import is_regex from note.models import SpecialTransaction, NoteSpecial, Alias from note_kfet.settings.base import BASE_DIR from permission.backends import PermissionBackend @@ -251,21 +252,26 @@ class RemittanceCreateView(ProtectQuerysetMixin, ProtectedCreateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - context["table"] = RemittanceTable( - data=Remittance.objects.filter( - PermissionBackend.filter_queryset(self.request, Remittance, "view")).all()) context["special_transactions"] = SpecialTransactionTable(data=SpecialTransaction.objects.none()) return context -class RemittanceListView(LoginRequiredMixin, TemplateView): +class RemittanceListView(LoginRequiredMixin, MultiTableMixin, TemplateView): """ List existing Remittances """ template_name = "treasury/remittance_list.html" extra_context = {"title": _("Remittances list")} + tables = [ + lambda data: RemittanceTable(data, prefix="opened-remittances-"), + lambda data: RemittanceTable(data, prefix="closed-remittances-"), + lambda data: SpecialTransactionTable(data, prefix="no-remittance-", exclude=('remittance_remove', )), + lambda data: SpecialTransactionTable(data, prefix="with-remittance-", exclude=('remittance_add', )), + ] + paginate_by = 10 # number of rows in tables + def dispatch(self, request, *args, **kwargs): # Check that the user is authenticated if not request.user.is_authenticated: @@ -275,49 +281,37 @@ class RemittanceListView(LoginRequiredMixin, TemplateView): raise PermissionDenied(_("You are not able to see the treasury interface.")) return super().dispatch(request, *args, **kwargs) + def get_tables_data(self): + return [ + Remittance.objects.filter(closed=False).filter( + PermissionBackend.filter_queryset(self.request, Remittance, "view")), + Remittance.objects.filter(closed=True).filter( + PermissionBackend.filter_queryset(self.request, Remittance, "view")), + SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)), + specialtransactionproxy__remittance=None).filter( + PermissionBackend.filter_queryset(self.request, Remittance, "view")), + SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)), + specialtransactionproxy__remittance__closed=False).filter( + PermissionBackend.filter_queryset(self.request, Remittance, "view")), + ] + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) - opened_remittances = RemittanceTable( - data=Remittance.objects.filter(closed=False).filter( - PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(), - prefix="opened-remittances-", - ) - opened_remittances.paginate(page=self.request.GET.get("opened-remittances-page", 1), per_page=10) - context["opened_remittances"] = opened_remittances - - closed_remittances = RemittanceTable( - data=Remittance.objects.filter(closed=True).filter( - PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(), - prefix="closed-remittances-", - ) - closed_remittances.paginate(page=self.request.GET.get("closed-remittances-page", 1), per_page=10) - context["closed_remittances"] = closed_remittances - - no_remittance_tr = SpecialTransactionTable( - data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)), - specialtransactionproxy__remittance=None).filter( - PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(), - exclude=('remittance_remove', ), - prefix="no-remittance-", - ) - no_remittance_tr.paginate(page=self.request.GET.get("no-remittance-page", 1), per_page=10) - context["special_transactions_no_remittance"] = no_remittance_tr - - with_remittance_tr = SpecialTransactionTable( - data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)), - specialtransactionproxy__remittance__closed=False).filter( - PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(), - exclude=('remittance_add', ), - prefix="with-remittance-", - ) - with_remittance_tr.paginate(page=self.request.GET.get("with-remittance-page", 1), per_page=10) - context["special_transactions_with_remittance"] = with_remittance_tr + tables = context["tables"] + names = [ + "opened_remittances", + "closed_remittances", + "special_transactions_no_remittance", + "special_transactions_with_remittance", + ] + for name, table in zip(names, tables): + context[name] = table return context -class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): +class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableMixin, UpdateView): """ Update Remittance """ @@ -325,19 +319,18 @@ class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView) form_class = RemittanceForm extra_context = {"title": _("Update a remittance")} + table_class = SpecialTransactionTable + context_table_name = "special_transactions" + def get_success_url(self): return reverse_lazy('treasury:remittance_list') - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) + def get_table_data(self): + return SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).filter( + PermissionBackend.filter_queryset(self.request, Remittance, "view")) - data = SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).filter( - PermissionBackend.filter_queryset(self.request, Remittance, "view")).all() - context["special_transactions"] = SpecialTransactionTable( - data=data, - exclude=('remittance_add', 'remittance_remove', ) if self.object.closed else ('remittance_add', )) - - return context + def get_table_kwargs(self): + return {"exclude": ('remittance_add', 'remittance_remove', ) if self.object.closed else ('remittance_add', )} class LinkTransactionToRemittanceView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): @@ -411,11 +404,16 @@ class SogeCreditListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableVi if "search" in self.request.GET: pattern = self.request.GET["search"] if pattern: + # Check if this is a valid regex. If not, we won't check regex + valid_regex = is_regex(pattern) + suffix_alias = "__iregex" if valid_regex else "__icontains" + suffix = "__iregex" if valid_regex else "__istartswith" + prefix = "^" if valid_regex else "" qs = qs.filter( - Q(user__first_name__iregex=pattern) - | Q(user__last_name__iregex=pattern) - | Q(user__note__alias__name__iregex="^" + pattern) - | Q(user__note__alias__normalized_name__iregex="^" + Alias.normalize(pattern)) + Q(**{f"user__first_name{suffix}": pattern}) + | Q(**{f"user__last_name{suffix}": pattern}) + | Q(**{f"user__note__alias__name{suffix_alias}": prefix + pattern}) + | Q(**{f"user__note__alias__normalized_name{suffix_alias}": prefix + Alias.normalize(pattern)}) ) if "valid" not in self.request.GET or not self.request.GET["valid"]: diff --git a/apps/wei/api/views.py b/apps/wei/api/views.py index e16a5374..13f04a91 100644 --- a/apps/wei/api/views.py +++ b/apps/wei/api/views.py @@ -2,7 +2,8 @@ # SPDX-License-Identifier: GPL-3.0-or-later from django_filters.rest_framework import DjangoFilterBackend -from rest_framework.filters import OrderingFilter, SearchFilter +from rest_framework.filters import OrderingFilter +from api.filters import RegexSafeSearchFilter from api.viewsets import ReadProtectedModelViewSet from .serializers import WEIClubSerializer, BusSerializer, BusTeamSerializer, WEIRoleSerializer, \ @@ -18,7 +19,7 @@ class WEIClubViewSet(ReadProtectedModelViewSet): """ queryset = WEIClub.objects.order_by('id') serializer_class = WEIClubSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['name', 'year', 'date_start', 'date_end', 'email', 'note__alias__name', 'note__alias__normalized_name', 'parent_club', 'parent_club__name', 'require_memberships', 'membership_fee_paid', 'membership_fee_unpaid', 'membership_duration', 'membership_start', @@ -34,7 +35,7 @@ class BusViewSet(ReadProtectedModelViewSet): """ queryset = Bus.objects.order_by('id') serializer_class = BusSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['name', 'wei', 'description', ] search_fields = ['$name', '$wei__name', '$description', ] @@ -47,7 +48,7 @@ class BusTeamViewSet(ReadProtectedModelViewSet): """ queryset = BusTeam.objects.order_by('id') serializer_class = BusTeamSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['name', 'bus', 'color', 'description', 'bus__wei', ] search_fields = ['$name', '$bus__name', '$bus__wei__name', '$description', ] @@ -60,7 +61,7 @@ class WEIRoleViewSet(ReadProtectedModelViewSet): """ queryset = WEIRole.objects.order_by('id') serializer_class = WEIRoleSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + filter_backends = [DjangoFilterBackend, RegexSafeSearchFilter] filterset_fields = ['name', 'permissions', 'memberships', ] search_fields = ['$name', ] @@ -73,7 +74,7 @@ class WEIRegistrationViewSet(ReadProtectedModelViewSet): """ queryset = WEIRegistration.objects.order_by('id') serializer_class = WEIRegistrationSerializer - filter_backends = [DjangoFilterBackend, SearchFilter] + 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', 'caution_check', 'birth_date', 'gender', @@ -92,7 +93,7 @@ class WEIMembershipViewSet(ReadProtectedModelViewSet): """ queryset = WEIMembership.objects.order_by('id') serializer_class = WEIMembershipSerializer - filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter] + filter_backends = [DjangoFilterBackend, OrderingFilter, RegexSafeSearchFilter] filterset_fields = ['club__name', 'club__email', 'club__note__alias__name', 'club__note__alias__normalized_name', 'user__username', 'user__last_name', 'user__first_name', 'user__email', 'user__note__alias__name', diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py index 808d7eda..cf0579aa 100644 --- a/apps/wei/forms/registration.py +++ b/apps/wei/forms/registration.py @@ -1,13 +1,14 @@ # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +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.utils.translation import gettext_lazy as _ from note.models import NoteSpecial, NoteUser -from note_kfet.inputs import AmountInput, DatePickerInput, Autocomplete, ColorWidget +from note_kfet.inputs import AmountInput, Autocomplete, ColorWidget from ..models import WEIClub, WEIRegistration, Bus, BusTeam, WEIMembership, WEIRole @@ -74,12 +75,17 @@ class WEIChooseBusForm(forms.Form): queryset=WEIRole.objects.filter(~Q(name="1A")), label=_("WEI Roles"), help_text=_("Select the roles that you are interested in."), - initial=WEIRole.objects.filter(name="Adhérent WEI").all(), + initial=WEIRole.objects.filter(name="Adhérent⋅e WEI").all(), widget=CheckboxSelectMultiple(), ) class WEIMembershipForm(forms.ModelForm): + caution_check = forms.BooleanField( + required=False, + label=_("Caution check given"), + ) + roles = forms.ModelMultipleChoiceField( queryset=WEIRole.objects, label=_("WEI Roles"), @@ -148,6 +154,7 @@ class WEIMembership1AForm(WEIMembershipForm): """ Used to confirm registrations of first year members without choosing a bus now. """ + caution_check = None roles = None def clean(self): diff --git a/apps/wei/forms/surveys/__init__.py b/apps/wei/forms/surveys/__init__.py index 0e0c37e2..733f7c37 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 .wei2023 import WEISurvey2023 +from .wei2024 import WEISurvey2024 __all__ = [ 'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey', ] -CurrentSurvey = WEISurvey2023 +CurrentSurvey = WEISurvey2024 diff --git a/apps/wei/forms/surveys/wei2023.py b/apps/wei/forms/surveys/wei2023.py index 0ec017bb..488ee7c3 100644 --- a/apps/wei/forms/surveys/wei2023.py +++ b/apps/wei/forms/surveys/wei2023.py @@ -82,7 +82,7 @@ WORDS = { 5: "La quoi ?" }], "kokarde": ["Qu'est-ce que le mot Kokarde t'évoque ?", { - 1: "Vraiment pas mon truc les soirées…", + 1: "Vraiment pas mon truc les soirées...", 2: "Bof, je viens pour manger et je repars aussitôt", 3: "Je kiffe, good vibes", 4: "Perso, je ne m'arrêterai pas de danser sur la piste !", @@ -117,15 +117,15 @@ WORDS = { 5: "Je pourrais en faire à n'importe qui. Pourquoi ne pas créer le club Câl[ENS] ?" }], "vomi": ["Quel est ton rapport au vomi ?", { - 1: "C'est compliqué…", + 1: "C'est compliqué...", 2: "Jamais je ne vomis mais je nettoie quand mes potes vomissent", 3: "Jamais je ne vomis et jamais je ne nettoie celui de quelqu'un d'autre", 4: "Je vomis quelquefois, ça arrive, faites pas cette tête, mais je fins toujours par nettoyer !", 5: "Je vomis à chaque soirée et ce n'est jamais moi qui nettoie" }], "kfet": ["Qu'est ce que la Kfet t'évoque ?", { - 1: "La Kfet, quel lieu de dépravé⋅es sérieux…", - 2: "C'est un endroit à l'hygiène plus que douteuse…", + 1: "La Kfet, quel lieu de dépravé⋅es sérieux...", + 2: "C'est un endroit à l'hygiène plus que douteuse...", 3: "Téma les prix des boissons et des snacks, c'est aberrant !", 4: "En vrai, c'est cool, petit billard, petit canapé, chill !", 5: "Banger, j'y reste jusqu'à la fin de mes jours" @@ -147,7 +147,7 @@ WORDS = { "scolarite": ["Comment tu vois ton cursus à l'ENS ?", { 1: "La tranquillité et le travail", 2: "On va s'amuser tout en bossant", - 3: "Ça va profiter et réviser au dernier moment pour les exams…", + 3: "Ça va profiter et réviser au dernier moment pour les exams...", 4: "Nous festoierons sans songer aux conséquences", 5: "Je ne vois qu'une seule issue : la débauche" }] diff --git a/apps/wei/forms/surveys/wei2024.py b/apps/wei/forms/surveys/wei2024.py new file mode 100644 index 00000000..518d58f0 --- /dev/null +++ b/apps/wei/forms/surveys/wei2024.py @@ -0,0 +1,378 @@ +# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from functools import lru_cache + +from django import forms +from django.utils.safestring import mark_safe +from django.db import transaction +from django.db.models import Q + +from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation +from ...models import WEIMembership + + +buses_descr = [ + [ + "Magi[Kar]p 🐙🎮🎲", "#ef5568", 1, + """Vous l'aurez compris au nom du bus, l'ambiance est aux jeux et à la culture geek ! Ici, vous trouverez une ambiance + calme avec une bonne dose d'autodérision et de second degré. Que vous ayez besoin de beaucoup dormir pour tenir la soirée + du lendemain, ou que vous souhaitiez faire nuit blanche pour jouer toute la nuit, vous pouvez nous rejoindre. Votre voix + n'y survivra peut-être pas à force de chanter. PS : les meilleurs cocktails du WEI sont chez nous, à déguster, pas à + siphonner !""", + ], + [ + "Va[car]me 🎷🍎🔊", "#fd7a28", 3, + """Ici, c'est le bus du bruit. Si vous voulez réveiller les autres bus en musique, apprendre de merveilleuses + mélodies au kazoo tout le week-end, ou simplement profiter d'une bonne ambiance musicale, le BDA et la + F[ENS]foire sont là pour vous. Vous pourrez également goûter au célèbre cocktail de la fanfare, concocté + pour l'occasion par les tout nouveaux "meilleurs artisans v*********** de France" ! Alors que vous soyez artiste + dans l'âme ou que vous souhaitiez juste faire le plus grand Vacarme, rejoignez-nous !""", + ], + [ + "[Kar]aïbes 🏝️🏴‍☠️🥥", "#a5cfdd", 3, + """Ahoy, explorateurs du WEI ! Le bus Karaibes t’invite à une traversée sous les tropiques, où l’ambiance est + toujours au beau fixe ! ☀️🍹 Ici, c’est soleil, rhum, et bonne humeur assurée : une atmosphère de vacances où + l’on se laisse porter par la chaleur humaine et la fête. Que tu sois un pirate en quête de sensations fortes ou + un amateur de chill avec un cocktail à la main, tu seras à ta place dans notre bus. Les soirées seront marquées + par des rythmes tropicaux qui te feront vibrer jusqu’à l’aube. Prêt à embarquer pour une aventure inoubliable + avec les meilleurs matelots du WEI ? On t’attend sur le pont du Karaibes pour lever l’ancre ensemble !""", + ], + [ + "[Kar]di [Bus] 🎙️💅", "#e46398", 2.5, + """Bienvenue à bord du Kardi Bus, la seul, l’unique, l’inimitable pépite de ce weekend d’intégration ! Inspiré par les + icônes suprêmes de la pop culture telles les Bratz, les Winx et autres Mean Girls, notre bus est un sanctuaire de style, + d’audace et de pur plaisir. A nos cotés attends toi à siroter tes meilleurs Cosmo, sex on the Beach et autres cocktails + de maxi pétasse tout en papotant entre copains copines ! Si tu rejoins le Kardi Bus, tu entres dans un monde où tu + pourras te déhancher sur du Beyoncé, Britney, Aya et autres reines de la pop ! À très vite, les futures stars du Kardi + Bus !""", + ], + [ + "Sparta[bus] 🐺🐒🏉", "#ebdac2", 5, + """Dans notre bus, on vous donne un avant goût des plus grandes assos de l'ENS : les Kyottes et l'Aspique (clubs de rugby + féminin et masculin, mais pas que). Bien entendu, qui dit rugby dit les copaings, le pastaga et la Pena Bayona, mais vous + verrez par vous même qu'on est ouvert⋅e à toutes propositions quand il s'agit de faire la fête. Pour les casse-cous comme + pour les plus calmes, vous trouverez au bus Aspique-Kyottes les 2A+ qui vous feront kiffer votre WEI.""", + ], + [ + "Zanzo[Bus] 🤯🚸🐒", "#FFFF", 3, + """Dans un entre-trois bien senti entre zinzinerie, enfance et vieillerie, le Zanzo[BUS] est un concentré de fun mêlé à + de la dinguerie à gogo. N'hésitez plus et rejoignez-nous pour un WEI toujours plus déjanté !""", + ], + [ + "Bran[Kar] 🍹🥳", "#6da1ac", 4, + """Si vous ne connaissez pas le Bran[Kar], c’est comme une grande famille qui fait un apéro, qui se bourre un peu la + gueule en discutant des heures autour d’une table remplie de bouffe et de super bons cocktails (la plupart des + barmen/barwomen du bus sont les barmans de Shakens), sauf qu’on est un bus du Wei (vous comprendrez bien le nom de notre + bus en voyant l’état de certain·e·s). Il nous arrive de faire quelques conneries, mais surtout de jouer au Bière-pong en + musique !""", + ], + [ + "Techno [kar]ade 🔊🚩", "#8065a3", 3, + """Avis à tous·tes les gauchos, amoureux·ses de la fête et des manifs : le Techno [kar]ade vous ouvre grand ses bras pour + finir en beauté votre première inté. Préparez-vous à vous abreuver de cocktails (savamment élaborés) à la vibration d’un + système son fabriqué pour l’occasion. Des sets technos à « Mon père était tellement de gauche » en passant par « Female + Body », le car accueillant les meilleures DJs du plateau saura animer le trajet aussi bien que les soirées. Si alcool et + musique seront au rendez-vous, les maîtres mots sont sécurité et inclusivité. Qui que vous soyez et quelle que soit votre + manière de vous amuser, notre objectif est que vous vous sentiez à l’aise pour rencontrer au mieux les 1A, les 2A et les + (nombreux⋅ses) 3A+ qui auront répondu à l’appel. Bref, rejoignez-nous, on est super cools :)""" + ], + [ + "[Bus]ka-P 🥇🍻🎤", "#7c4768", 4.5, + """Booska-p, c’est le « site N°1 du Rap français ». Le [Bus]ka-p ? Le bus N°1 sur l’ambiance au WEI. Les nuits vont être + courtes, les cocktails vont couler à flots : tout sera réuni pour vivre un week-end dont tu te souviendras toute ta vie. + Au programme pas un seul temps mort et un maximum de rencontres pour bien commencer ta première année à l’ENS. Et bien + entendu, le tout accompagné des meilleurs sons, de Jul à Aya, en passant par ABBA et Sexion d’Assaut. Bref, si tu veux + vivre un WEI d’anthologie et faire la fête, de jour comme de nuit, nous t’accueillons avec plaisir !""", + ], +] + + +def print_bus(i): + return f"""

{buses_descr[i][0]}


+ Alcoolomètre : {buses_descr[i][2]} / 5 🍻

{buses_descr[i][3]}
""" + + +def print_all_buses(): + liste = [print_bus(i) for i in range(len(buses_descr))] + return "



".join(liste) + + +def get_number_comment(i): + if i == 1: + return "Même pas en rêve" + elif i == 2: + return "Pas envie" + elif i == 3: + return "Mouais..." + elif i == 4: + return "Pourquoi pas !" + elif i == 5: + return "Ce bus ou rien !!!" + else: + return "" + + +WORDS = { + "recap": + [ + """Chèr⋅e 1A, te voilà arrivé⋅e au moment fatidique du choix de ton bus !


+ Ton bus est constitué des gens avec qui tu passeras la majorité de ton temps : que ce soit le voyage d'aller et de + retour et les différentes activité qu'ils pourront te proposer tout au long du WEI donc choisis le bien ! +

Tu trouveras ci-dessous la liste de tous les bus ainsi qu'une description détaillée de ces derniers. + Prends ton temps pour étudier chacun d'eux et quand tu te sens prêt⋅e, appuie sur le bouton « J'ai pris connaissance + des bus » pour continuer +
(pas besoin d'apprendre par cœur chaque bus, la description de chaque bus te sera rappeler avant de lui attribuer + une note !)



""" + print_all_buses(), + { + "1": "J'ai pris connaissance des différents bus et me sent fin prêt à choisir celui qui me convient le mieux !", + } + ] +} + +WORDS.update({ + f"bus{id}": [print_bus(id), {i: f"{get_number_comment(i)} ({i}/5)" for i in range(1, 5 + 1)}] for id in range(len(buses_descr)) +}) + + +class WEISurveyForm2024(forms.Form): + """ + Survey form for the year 2024. + Members score the different buses, from which we calculate the best associated bus. + """ + def set_registration(self, registration): + """ + Filter the bus selector with the buses of the current WEI. + """ + information = WEISurveyInformation2024(registration) + + question = information.questions[information.step] + self.fields[question] = forms.ChoiceField( + label=mark_safe(WORDS[question][0]), + widget=forms.RadioSelect(), + ) + answers = [(answer, WORDS[question][1][answer]) for answer in WORDS[question][1]] + self.fields[question].choices = answers + + +class WEIBusInformation2024(WEIBusInformation): + """ + For each question, the bus has ordered answers + """ + scores: dict + + def __init__(self, bus): + self.scores = {} + for question in WORDS: + self.scores[question] = [] + super().__init__(bus) + + +class WEISurveyInformation2024(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. + """ + + step = 0 + questions = list(WORDS.keys()) + + def __init__(self, registration): + for question in WORDS: + setattr(self, str(question), None) + super().__init__(registration) + + +class WEISurvey2024(WEISurvey): + """ + Survey for the year 2024. + """ + + @classmethod + def get_year(cls): + return 2024 + + @classmethod + def get_survey_information_class(cls): + return WEISurveyInformation2024 + + def get_form_class(self): + return WEISurveyForm2024 + + 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): + self.information.step += 1 + for question in WORDS: + if question in form.cleaned_data: + answer = form.cleaned_data[question] + setattr(self.information, question, answer) + self.save() + + @classmethod + def get_algorithm_class(cls): + return WEISurveyAlgorithm2024 + + def is_complete(self) -> bool: + """ + The survey is complete once the bus is chosen. + """ + for question in WORDS: + if not getattr(self.information, question): + return False + return True + + @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 = 0 + for question in WORDS: + s += bus_info.scores[question][str(getattr(self.information, question))] + 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): + return super().clear_cache() + + +class WEISurveyAlgorithm2024(WEISurveyAlgorithm): + """ + The algorithm class for the year 2024. + We use Gale-Shapley algorithm to attribute 1y students into buses. + """ + + @classmethod + def get_survey_class(cls): + return WEISurvey2024 + + @classmethod + def get_bus_information_class(cls): + return WEIBusInformation2024 + + 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 s.bus_id != None] + # surveys = [s for s in surveys if not hasattr(s.information, 'hardcoded') or not s.information.hardcoded] + + # surveys = [s for s in surveys if s.registration.user_id in free_users] + + # hardcoded_first_year_mb = WEIMembership.objects.filter(bus != None,registration__first_year=True) + # hardcoded_first_year = hardcoded_first_year_mb.values_list('user__id', 'bus__id') + + hardcoded_first_year_mb = WEIMembership.objects.filter(registration__first_year=True) + hardcoded_first_year = {mb.user.id if mb.bus else None: mb.bus.id if mb.bus else None for mb in hardcoded_first_year_mb} + + # Reset previous algorithm run + for survey in surveys: + survey.free() + if survey.registration.user_id in hardcoded_first_year.keys(): + survey.select_bus(hardcoded_first_year[survey.registration.user_id]) + 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() + free_seats -= sum(1 for s in non_men if s.information.selected_bus_pk == bus.pk) + 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) + 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 + WEISurvey2024.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/management/commands/export_wei_registrations.py b/apps/wei/management/commands/export_wei_registrations.py index ca3dc06c..39fd238a 100644 --- a/apps/wei/management/commands/export_wei_registrations.py +++ b/apps/wei/management/commands/export_wei_registrations.py @@ -84,5 +84,5 @@ class Command(BaseCommand): s += sep + user.profile.section_generated s += sep + bus.name s += sep + (team.name if team else "--") - s += sep + ", ".join(role.name for role in membership.roles.filter(~Q(name="Adhérent WEI")).all()) + s += sep + ", ".join(role.name for role in membership.roles.filter(~Q(name="Adhérent⋅e WEI")).all()) self.stdout.write(s) diff --git a/apps/wei/migrations/0009_weiregistration_specific_diet.py b/apps/wei/migrations/0009_weiregistration_specific_diet.py new file mode 100644 index 00000000..8fa0f82a --- /dev/null +++ b/apps/wei/migrations/0009_weiregistration_specific_diet.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.15 on 2024-08-28 20:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('wei', '0008_auto_20240111_1545'), + ] + + operations = [ + migrations.AddField( + model_name='weiregistration', + name='specific_diet', + field=models.TextField(blank=True, default='', verbose_name='specific diet'), + ), + ] diff --git a/apps/wei/migrations/0010_remove_weiregistration_specific_diet.py b/apps/wei/migrations/0010_remove_weiregistration_specific_diet.py new file mode 100644 index 00000000..119927c0 --- /dev/null +++ b/apps/wei/migrations/0010_remove_weiregistration_specific_diet.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.15 on 2024-08-29 20:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('wei', '0009_weiregistration_specific_diet'), + ] + + operations = [ + migrations.RemoveField( + model_name='weiregistration', + name='specific_diet', + ), + ] diff --git a/apps/wei/templates/wei/1A_list.html b/apps/wei/templates/wei/1A_list.html index d9b82937..9229fa22 100644 --- a/apps/wei/templates/wei/1A_list.html +++ b/apps/wei/templates/wei/1A_list.html @@ -12,7 +12,7 @@
{% render_table bus_repartition_table %}
- {% trans "Start attribution!" %} + {% trans "Start attribution !" %}
{% render_table table %}
diff --git a/apps/wei/templates/wei/attribute_bus_1A.html b/apps/wei/templates/wei/attribute_bus_1A.html index 3305981b..1140ed49 100644 --- a/apps/wei/templates/wei/attribute_bus_1A.html +++ b/apps/wei/templates/wei/attribute_bus_1A.html @@ -25,7 +25,7 @@
{% trans 'department'|capfirst %}
{{ object.user.profile.get_department_display }}
-
{% trans 'health issues'|capfirst %}
+
{% trans 'health issues or specific diet'|capfirst %}
{{ object.health_issues|default:"—" }}
{% trans 'suggested bus'|capfirst %}
diff --git a/apps/wei/templates/wei/weimembership_form.html b/apps/wei/templates/wei/weimembership_form.html index 017b0dd7..ff3024ca 100644 --- a/apps/wei/templates/wei/weimembership_form.html +++ b/apps/wei/templates/wei/weimembership_form.html @@ -64,7 +64,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% trans 'birth date'|capfirst %}
{{ registration.birth_date }}
-
{% trans 'health issues'|capfirst %}
+
{% trans 'health issues or specific diet'|capfirst %}
{{ registration.health_issues }}
{% trans 'emergency contact name'|capfirst %}
diff --git a/apps/wei/tests/test_wei_algorithm_2023.py b/apps/wei/tests/test_wei_algorithm_2023.py index c94cbb0c..55c4a6d7 100644 --- a/apps/wei/tests/test_wei_algorithm_2023.py +++ b/apps/wei/tests/test_wei_algorithm_2023.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.wei2023 import WEIBusInformation2023, WEISurvey2023, WORDS, WEISurveyInformation2023 from ..models import Bus, WEIClub, WEIRegistration @@ -127,44 +125,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, 20 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 = WEISurvey2023(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 = WEISurvey2023(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_2024.py b/apps/wei/tests/test_wei_algorithm_2024.py new file mode 100644 index 00000000..a22b0a3a --- /dev/null +++ b/apps/wei/tests/test_wei_algorithm_2024.py @@ -0,0 +1,172 @@ +# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +import random +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 + + +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.user = User.objects.create_superuser( + username="weiadmin", + password="admin", + email="admin@example.com", + ) + self.user.save() + self.client.force_login(self.user) + sess = self.client.session + sess["permission_mask"] = 42 + sess.save() + + self.wei = WEIClub.objects.create( + name="WEI 2024", + email="wei2024@example.com", + parent_club_id=2, + membership_fee_paid=12500, + membership_fee_unpaid=5500, + membership_start='2024-01-01', + membership_end='2024-12-31', + date_start=date.today() + timedelta(days=2), + date_end='2024-12-31', + year=2024, + ) + + 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 = WEIBusInformation2024(bus) + for question in WORDS: + information.scores[question] = {answer: random.randint(1, 5) for answer in WORDS[question][1]} + 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 = WEISurveyInformation2024(registration) + for question in WORDS: + options = list(WORDS[question][1].keys()) + setattr(information, question, random.choice(options)) + information.step = 20 + information.save(registration) + registration.save() + + # Run algorithm + WEISurvey2024.get_algorithm_class()().run_algorithm() + + # Ensure that everyone has its first choice + for r in WEIRegistration.objects.filter(wei=self.wei).all(): + survey = WEISurvey2024(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 = WEISurveyInformation2024(registration) + for question in WORDS: + options = list(WORDS[question][1].keys()) + setattr(information, question, random.choice(options)) + information.step = 20 + information.save(registration) + registration.save() + + # Run algorithm + WEISurvey2024.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 = WEISurvey2024(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 % + + 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_registration.py b/apps/wei/tests/test_wei_registration.py index 74fdcf0b..21da07f8 100644 --- a/apps/wei/tests/test_wei_registration.py +++ b/apps/wei/tests/test_wei_registration.py @@ -439,7 +439,7 @@ class TestWEIRegistration(TestCase): emergency_contact_phone='+33123456789', )) self.assertEqual(response.status_code, 200) - self.assertTrue("This user can't be in her/his first year since he/she has already participated to a WEI." + self.assertTrue("This user can't be in her/his first year since he/she has already participated to a WEI." in str(response.context["form"].errors)) # Check that if the WEI is started, we can't register anyone @@ -504,7 +504,7 @@ class TestWEIRegistration(TestCase): emergency_contact_phone='+33600000000', bus=[self.bus.id], team=[self.team.id], - roles=[role.id for role in WEIRole.objects.filter(name="Adhérent WEI").all()], + roles=[role.id for role in WEIRole.objects.filter(name="Adhérent⋅e WEI").all()], information_json=self.registration.information_json, ) ) @@ -558,7 +558,7 @@ class TestWEIRegistration(TestCase): emergency_contact_phone='+33600000000', bus=[self.bus.id], team=[self.team.id], - roles=[role.id for role in WEIRole.objects.filter(name="Adhérent WEI").all()], + roles=[role.id for role in WEIRole.objects.filter(name="Adhérent⋅e WEI").all()], information_json=self.registration.information_json, ) ) @@ -635,7 +635,7 @@ class TestWEIRegistration(TestCase): )) self.assertEqual(response.status_code, 200) self.assertFalse(response.context["form"].is_valid()) - self.assertTrue("This team doesn't belong to the given bus." in str(response.context["form"].errors)) + 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], @@ -767,7 +767,7 @@ class TestDefaultWEISurvey(TestCase): WEISurvey.update_form(None, None) self.assertEqual(CurrentSurvey.get_algorithm_class().get_survey_class(), CurrentSurvey) - self.assertEqual(CurrentSurvey.get_year(), 2023) + self.assertEqual(CurrentSurvey.get_year(), 2024) class TestWeiAPI(TestAPI): diff --git a/apps/wei/views.py b/apps/wei/views.py index 4771cb5b..76943198 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -22,7 +22,8 @@ from django.views import View 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 +from django_tables2 import SingleTableView, MultiTableMixin +from api.viewsets import is_regex from member.models import Membership, Club from note.models import Transaction, NoteClub, Alias, SpecialTransaction, NoteSpecial from note.tables import HistoryTable @@ -100,7 +101,7 @@ class WEICreateView(ProtectQuerysetMixin, ProtectedCreateView): return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.pk}) -class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): +class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, DetailView): """ View WEI information """ @@ -108,34 +109,40 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context_object_name = "club" extra_context = {"title": _("WEI Detail")} - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - - club = context["club"] + tables = [ + lambda data: HistoryTable(data, prefix="history-"), + lambda data: WEIMembershipTable(data, prefix="membership-"), + lambda data: WEIRegistrationTable(data, prefix="pre-registration-"), + lambda data: BusTable(data, prefix="bus-"), + ] + paginate_by = 20 # number of rows in tables + def get_tables_data(self): + club = self.object club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note)) \ .filter(PermissionBackend.filter_queryset(self.request, Transaction, "view")) \ .order_by('-created_at', '-id') - history_table = HistoryTable(club_transactions, prefix="history-") - history_table.paginate(per_page=20, page=self.request.GET.get('history-page', 1)) - context['history_list'] = history_table - club_member = WEIMembership.objects.filter( club=club, date_end__gte=date.today(), ).filter(PermissionBackend.filter_queryset(self.request, WEIMembership, "view")) - membership_table = WEIMembershipTable(data=club_member, prefix="membership-") - membership_table.paginate(per_page=20, page=self.request.GET.get('membership-page', 1)) - context['member_list'] = membership_table - pre_registrations = WEIRegistration.objects.filter( PermissionBackend.filter_queryset(self.request, WEIRegistration, "view")).filter( membership=None, wei=club ) - pre_registrations_table = WEIRegistrationTable(data=pre_registrations, prefix="pre-registration-") - pre_registrations_table.paginate(per_page=20, page=self.request.GET.get('pre-registration-page', 1)) - context['pre_registrations'] = pre_registrations_table + 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, ] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + club = context["club"] + + tables = context["tables"] + for name, table in zip(["history_list", "member_list", "pre_registrations", "buses"], tables): + context[name] = table my_registration = WEIRegistration.objects.filter(wei=club, user=self.request.user) if my_registration.exists(): @@ -144,11 +151,6 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): my_registration = None context["my_registration"] = my_registration - buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request, Bus, "view")) \ - .filter(wei=self.object).annotate(count=Count("memberships")).order_by("name") - bus_table = BusTable(data=buses, prefix="bus-") - context['buses'] = bus_table - random_user = User.objects.filter(~Q(wei__wei__in=[club])).first() if random_user is None: @@ -219,13 +221,18 @@ class WEIMembershipsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableVi if not pattern: return qs.none() + # Check if this is a valid regex. If not, we won't check regex + valid_regex = is_regex(pattern) + suffix_alias = "__iregex" if valid_regex else "__istartswith" + suffix = "__iregex" if valid_regex else "__icontains" + prefix = "^" if valid_regex else "" qs = qs.filter( - Q(user__first_name__iregex=pattern) - | Q(user__last_name__iregex=pattern) - | Q(user__note__alias__name__iregex="^" + pattern) - | Q(user__note__alias__normalized_name__iregex="^" + Alias.normalize(pattern)) - | Q(bus__name__iregex=pattern) - | Q(team__name__iregex=pattern) + Q(**{f"user__first_name{suffix}": pattern}) + | Q(**{f"user__last_name{suffix}": pattern}) + | Q(**{f"user__note__alias__name{suffix_alias}": prefix + pattern}) + | Q(**{f"user__note__alias__normalized_name{suffix_alias}": prefix + Alias.normalize(pattern)}) + | Q(**{f"bus__name{suffix}": pattern}) + | Q(**{f"team__name{suffix}": pattern}) ) return qs @@ -255,11 +262,16 @@ class WEIRegistrationsView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTable pattern = self.request.GET.get("search", "") if pattern: + # Check if this is a valid regex. If not, we won't check regex + valid_regex = is_regex(pattern) + suffix_alias = "__iregex" if valid_regex else "__istartswith" + suffix = "__iregex" if valid_regex else "__icontains" + prefix = "^" if valid_regex else "" qs = qs.filter( - Q(user__first_name__iregex=pattern) - | Q(user__last_name__iregex=pattern) - | Q(user__note__alias__name__iregex="^" + pattern) - | Q(user__note__alias__normalized_name__iregex="^" + Alias.normalize(pattern)) + Q(**{f"user__first_name{suffix}": pattern}) + | Q(**{f"user__last_name{suffix}": pattern}) + | Q(**{f"user__note__alias__name{suffix_alias}": prefix + pattern}) + | Q(**{f"user__note__alias__normalized_name{suffix_alias}": prefix + Alias.normalize(pattern)}) ) return qs @@ -888,6 +900,9 @@ 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 + if registration.soge_credit: form.fields["credit_type"].disabled = True form.fields["credit_type"].initial = NoteSpecial.objects.get(special_type="Virement bancaire") @@ -916,7 +931,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): form["team"].initial = BusTeam.objects.get(pk=information["preferred_team_pk"][0]) if "preferred_roles_pk" in information: form["roles"].initial = WEIRole.objects.filter( - Q(pk__in=information["preferred_roles_pk"]) | Q(name="Adhérent WEI") + Q(pk__in=information["preferred_roles_pk"]) | Q(name="Adhérent⋅e WEI") ).all() return form @@ -929,6 +944,9 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): club = registration.wei user = registration.user + if "caution_check" in form.data: + registration.caution_check = form.data["caution_check"] == "on" + registration.save() membership = form.instance membership.user = user membership.club = club @@ -1008,7 +1026,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView): membership.save() membership.refresh_from_db() - membership.roles.add(WEIRole.objects.get(name="Adhérent WEI")) + membership.roles.add(WEIRole.objects.get(name="Adhérent⋅e WEI")) return super().form_valid(form) diff --git a/docs/_static/img/create_transaction.png b/docs/_static/img/create_transaction.png index 4805b131..05ce4b42 100644 Binary files a/docs/_static/img/create_transaction.png and b/docs/_static/img/create_transaction.png differ diff --git a/docs/api/index.rst b/docs/api/index.rst index fc55fc5c..2f75dbb8 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -23,7 +23,7 @@ Pages de l'API Il suffit d'ajouter le préfixe ``/api/`` pour arriver sur ces pages. * `models `_ : liste des différents modèles enregistrés en base de données -* `user `_ : liste des différents utilisateurs enregistrés +* `user `_ : liste des différent⋅es utilisateur⋅rices enregistrés * `members/profile `_ : liste des différents profils associés à des utilisateurs * `members/club `_ : liste des différents clubs enregistrés * `members/membership `_ : liste des adhésions enregistrées @@ -69,7 +69,7 @@ S'authentifier L'authentification peut se faire soit par session en se connectant via la page de connexion classique, soit via un jeton d'authentification. Le jeton peut se récupérer via la page de son propre compte, en cliquant -sur le bouton « `Accès API `_ ». Il peut être révoqué et regénéré +sur le bouton « `Accès API `_ ». Il peut être révoqué et régénéré en un clic. Pour s'authentifier via ce jeton, il faut ajouter l'en-tête ``Authorization: Token `` aux paramètres HTTP. @@ -111,7 +111,7 @@ Trois types de filtres sont implémentés : Les filtres disponibles sont indiqués sur chacune des pages de documentation. -Le résultat est déjà par défaut filtré par droits : seuls les éléments que l'utilisateur à le droit de voir sont affichés. +Le résultat est déjà par défaut filtré par droits : seuls les éléments que l'utilisateur⋅rice a le droit de voir sont affichés. Cela est possible grâce à la structure des permissions, générant justement des filtres de requêtes de base de données. Une requête à l'adresse ``/api///`` affiche directement les informations du modèle demandé au format JSON. @@ -126,8 +126,9 @@ Des exceptions sont faites sur certaines pages : les pages de logs et de content Les formats supportés sont multiples : ``application/json``, ``application/x-www-url-encoded``, ``multipart/form-data``. Cela facilite l'envoi de requêtes. Le module construit ensuite l'instance du modèle et le sauvegarde dans la base de -données. L'application ``permission`` s'assure que l'utilisateur à le droit de faire ce type de modification. La réponse -renvoyée est l'objet enregistré au format JSON si l'ajout s'est bien déroulé, sinon un message d'erreur au format JSON. +données. L'application ``permission`` s'assure que l'utilisateur⋅rice a le droit de faire ce type de modification. +La réponse renvoyée est l'objet enregistré au format JSON si l'ajout s'est bien déroulé, sinon un message d'erreur au +format JSON. PATCH ~~~~~ diff --git a/docs/api/member.rst b/docs/api/member.rst index 88d4c874..5aa1fe84 100644 --- a/docs/api/member.rst +++ b/docs/api/member.rst @@ -135,7 +135,7 @@ Options "required": false, "read_only": false, "label": "Pay\u00e9", - "help_text": "Indique si l'utilisateur per\u00e7oit un salaire." + "help_text": "Indique si l'utilisateur⋅rice per\u00e7oit un salaire." }, "ml_events_registration": { "type": "choice", diff --git a/docs/api/wei.rst b/docs/api/wei.rst index 125ac9b4..cfe3f411 100644 --- a/docs/api/wei.rst +++ b/docs/api/wei.rst @@ -511,7 +511,7 @@ Options "required": false, "read_only": false, "label": "Premi\u00e8re ann\u00e9e", - "help_text": "Indique si l'utilisateur est nouveau dans l'\u00e9cole." + "help_text": "Indique si l'utilisateur⋅rice est nouvelleau dans l'\u00e9cole." }, "information_json": { "type": "string", @@ -524,7 +524,7 @@ Options "type": "field", "required": true, "read_only": false, - "label": "Utilisateur" + "label": "Utilisateur⋅rice" }, "wei": { "type": "field", diff --git a/docs/apps/activity.rst b/docs/apps/activity.rst index f4df6062..e7372d44 100644 --- a/docs/apps/activity.rst +++ b/docs/apps/activity.rst @@ -3,12 +3,12 @@ Application Activités L'application activités gère les différentes activités liées au BDE. Elle permet entre autres de créer des activités qui peuvent être diffusées via des calendriers ou la mailing list d'événements. Elle permet aussi de réguler l'accès aux -événements, en s'assurant que leur note est positive. Elle permet enfin de gérer les invités. +événements, en s'assurant que leur note est positive. Elle permet enfin de gérer les invité⋅es. Modèles ------- -L'application comporte 5 modèles : activités, types d'activité, invités, entrées et transactions d'invitation. +L'application comporte 6 modèles : activités, types d'activité, invité⋅es, entrées et transactions d'invitation et les ouvreur⋅ses. Types d'activité ~~~~~~~~~~~~~~~~ @@ -16,7 +16,7 @@ Types d'activité Les activités sont triées par type (pots, soirées de club, ...), et chaque type regroupe diverses informations : * Nom du type -* Possibilité d'inviter des non-adhérents (booléen) +* Possibilité d'inviter des non-adhérent⋅es (booléen) * Prix d'invitation (entier, centimes à débiter sur la note de l'hôte) Activités @@ -26,7 +26,7 @@ Le modèle d'activité regroupe les informations liées à l'activité même : * Nom de l'activité * Description de l'activité -* Créateur, personne qui a proposé l'activité +* Créateur⋅rice, personne qui a proposé l'activité * Club ayant organisé l'activité * Note sur laquelle verser les crédits d'invitation (peut être nul si non concerné) * Club invité (généralement le club Kfet) @@ -38,19 +38,19 @@ Le modèle d'activité regroupe les informations liées à l'activité même : Entrées ~~~~~~~ -Une instance de ce modèle est créé dès que quelqu'un est inscrit à l'activité. Sont stockées les informations suivantes : +Une instance de ce modèle est créé dès que quelqu'un⋅e est inscrit⋅e à l'activité. Sont stockées les informations suivantes : * Activité concernée (clé étrangère) * Heure d'entrée -* Note de la personne entrée, ou hôte s'il s'agit d'un invité (clé étrangère vers ``NoteUser``) -* Invité (``OneToOneField`` vers ``Guest``, ``None`` si c'est la personne elle-même qui rentre et non son invité) +* Note de la personne entrée, ou hôte s'il s'agit d'un⋅e invité⋅e (clé étrangère vers ``NoteUser``) +* Invité⋅e (``OneToOneField`` vers ``Guest``, ``None`` si c'est la personne elle-même qui rentre et non saon invité⋅e) Il n'est pas possible de créer une entrée si la note est en négatif. -Invités -~~~~~~~ +Invité⋅es +~~~~~~~~~ -Les adhérents ont la possibilité d'inviter des amis. Pour cela, les différentes informations sont enregistrées : +Les adhérent⋅es ont la possibilité d'inviter des ami⋅es. Pour cela, les différentes informations sont enregistrées : * Activité concernée (clé étrangère) * Nom de famille @@ -60,7 +60,7 @@ Les adhérents ont la possibilité d'inviter des amis. Pour cela, les différent Certaines contraintes s'appliquent : * Une personne ne peut pas être invitée plus de 5 fois par an (coupe nom/prénom) -* Un adhérent ne peut pas inviter plus de 3 personnes par activité. +* Un⋅e adhérent⋅e ne peut pas inviter plus de 3 personnes par activité. Transactions d'invitation ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -71,6 +71,17 @@ comportent qu'un champ supplémentaire, de type ``OneToOneField`` vers ``Guest`` Ce modèle aurait pu appartenir à l'application ``note``, mais afin de rester modulaire et que l'application ``note`` ne dépende pas de cette application, on procède de cette manière. +Ouvreur⋅ses +~~~~~~~~~~~ + +Depuis la page d'une activité, il est possible d'ajouter des personnes en tant qu'« ouvreur⋅se ». Cela permet à une +personne sans aucun droit note de pouvoir faire les entrées d'une ``Activity``. Ce rôle n'est valable que pendant que +l'activité est ouverte et sur aucune autre activité. Les ouvreur⋅ses ont aussi accès à l'interface des transactions. + +Ce modèle regroupe : +* Activité (clé étrangère) +* Note (clé étrangère) + Graphe ~~~~~~ @@ -83,15 +94,15 @@ UI Création d'activités ~~~~~~~~~~~~~~~~~~~~ -N'importe quel adhérent Kfet peut suggérer l'ajout d'une activité via un formulaire. +N'importe quel⋅le adhérent⋅e Kfet peut suggérer l'ajout d'une activité via un formulaire. Gestion des activités ~~~~~~~~~~~~~~~~~~~~~ Les ayant-droit (Res[pot] et respos infos) peuvent valider les activités proposées. Ils peuvent également la modifier -si besoin. Ils peuvent enfin la déclarer ouvertes pour lancer l'accès aux entrées. +si besoin. Iels peuvent enfin la déclarer ouverte pour lancer l'accès aux entrées. -N'importe qui peut inviter des amis non adhérents, tant que les contraintes de nombre (un adhérent n'invite pas plus de +N'importe qui peut inviter des ami⋅es non adhérent⋅es, tant que les contraintes de nombre (un⋅e adhérent⋅e n'invite pas plus de trois personnes par activité et une personne ne peut pas être invitée plus de 5 fois par an). L'invitation est facturée à l'entrée. @@ -99,12 +110,15 @@ Entrées aux soirées ~~~~~~~~~~~~~~~~~~~ L'interface d'entrées est simple et ergonomique. Elle contient un champ de texte. À chaque fois que le champ est -modifié, un tableau est affiché comprenant la liste des invités et des adhérents dont le prénom, le nom ou un alias +modifié, un tableau est affiché comprenant la liste des invité⋅es et des adhérent⋅es dont le prénom, le nom ou un alias de la note est acceptée par le texte entré. -En cliquant sur la ligne de la personne qui souhaite rentrée, s'il s'agit d'un adhérent, alors la personne est comptée -comme entrée à l'activité, sous réserve que sa note soit positive. S'il s'agit d'un invité, alors 3 boutons +En cliquant sur la ligne de la personne qui souhaite rentrer, s'il s'agit d'un⋅e adhérent⋅e, alors la personne est comptée +comme entrée à l'activité, sous réserve que sa note soit positive. S'il s'agit d'un⋅e invité⋅e, alors 3 boutons apparaîssent, afin de régler la taxe d'invitation : l'un prélève directement depuis la note de l'hôte, les deux autres permettent un paiement par espèces ou par carte bancaire. En réalité, les deux derniers boutons enregistrent automatiquement un crédit sur la note de l'hôte, puis une transaction (de type ``GuestTransaction``) est faite depuis -la note de l'hôte vers la note de l'organisateur de l'événement. +la note de l'hôte vers la note du club organisateur de l'événement. + +Si une personne souhaite faire les entrées, il est possible de l'ajouter dans la liste des ouvreur⋅ses depuis la page +de l'activité. diff --git a/docs/apps/food.rst b/docs/apps/food.rst new file mode 100644 index 00000000..e34ad40f --- /dev/null +++ b/docs/apps/food.rst @@ -0,0 +1,83 @@ +Application Food +================ + +L'application ``food`` s'occupe de la traçabilité et permet notamment l'obtention de la liste des allergènes. + +Modèles +------- + +L'application comporte 5 modèles : Allergen, QRCode, Food, BasicFood, TransformedFood. + +Food +~~~~ + +Ce modèle est un PolymorphicModel et ne sert uniquement à créer BasicFood et TransformedFood. + +Le modèle regroupe : + +* Nom du produit +* Propriétaire (doit-être un Club) +* Allergènes (ManyToManyField) +* date d'expiration +* a été mangé (booléen) +* est prêt (booléen) + +BasicFood +~~~~~~~~~ + +Les BasicFood correspondent aux produits non modifiés à la Kfet. Ils peuvent correspondre à la fois à des produits achetés en magasin ou à des produits Terre à Terre. Ces produits seront les ingrédients de tous les plats préparés et en conséquent sont les seuls produits à nécessité une saisie manuelle des allergènes. + +Le modèle regroupe : + +* Type de date (DLC = date limite de consommation, DDM = date de durabilité minimale) +* Date d'arrivée +* Champs de Food + +TransformedFood +~~~~~~~~~~~~~~~ + +Les TransformedFood correspondent aux produits préparés à la Kfet. Ils peuvent être composés de BasicFood et/ou de TransformedFood. La date d'expiration et les allergènes sont automatiquement mis à jour par update (qui doit être exécuté après modification des ingrédients dans les forms par exemple). + +Le modèle regroupe : + +* Durée de consommation (par défaut 3 jours) +* Ingrédients (ManyToManyField vers Food) +* Date de création +* Champs de Food + +Allergen +~~~~~~~~ + +Le modèle regroupe : + +* Nom + +QRCode +~~~~~~ + +Le modèle regroupe : + +* nombre (unique, entier positif) +* food (OneToOneField vers Food) + +Création de BasicFood +~~~~~~~~~~~~~~~~~~~~~ + +Un BasicFood a toujours besoin d'un QRCode (depuis l'interface web). Il convient donc de coller le QRCode puis de le scanner et de compléter le formulaire. + +Création de TransformedFood +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Pour créer un TransformedFood, il suffit d'aller dans l'onglet ``traçabilité`` et de cliquer sur l'onglet. + +Ajouter un ingrédient +~~~~~~~~~~~~~~~~~~~~~ + +Un ingrédient a forcément un QRCode. Il convient donc de scanner le QRCode de l'ingrédient et de sélectionner le produit auquel il doit être ajouté. + +Remarque : Un produit fini doit avoir un QRCode et inversement. + +Terminer un plat +~~~~~~~~~~~~~~~~ + +Il suffit de coller le QRCode sur le plat, de le scanner et de sélectionner le produit. diff --git a/docs/apps/index.rst b/docs/apps/index.rst index d1fa1ee0..e16f5196 100644 --- a/docs/apps/index.rst +++ b/docs/apps/index.rst @@ -1,5 +1,5 @@ -Applications de la NoteKfet2020 -=============================== +Applications de la Note Kfet 2020 +================================= .. toctree:: :maxdepth: 2 @@ -15,27 +15,26 @@ Applications de la NoteKfet2020 treasury wei -La NoteKfet est un projet Django, décomposé en applications. -Certaines Applications sont développées uniquement pour ce projet, et sont indispensables, -d'autres sont packagesé et sont installées comme dépendances. -Enfin des fonctionnalités annexes ont été rajouté, mais ne sont pas essentiel au déploiement de la NoteKfet; -leur usage est cependant recommandé. +La Note Kfet 2020 est un projet Django, décomposé en applications. +Certaines applications sont développées uniquement pour ce projet, et sont indispensables, +d'autres sont packagées et sont installées comme dépendances. +Enfin, des fonctionnalités annexes ont été rajoutées, mais ne sont pas essentielles au déploiement de la Note Kfet 2020. Leur usage est cependant recommandé. -Le front utilise le framework Bootstrap4 et quelques morceaux de javascript custom. +L'affichage Web utilise le framework Bootstrap4 et quelques morceaux de JavaScript personnalisés. Applications indispensables --------------------------- * ``note_kfet`` : - Application "projet" de django, c'est ici que la config de la note est gérée. + Application "projet" de django, c'est ici que la configuration de la note est gérée. * `Member `_ : - Gestion des profils d'utilisateurs, des clubs et de leur membres. + Gestion des profils d'utilisateur⋅rices, des clubs et de leur membres. * `Note `_ : - Les notes associés a des utilisateurs ou des clubs. + Les notes associées à des utilisateur⋅rices ou des clubs. * `Activity `_ : - La gestion des Activités (créations, gestion, entrée...) + La gestion des activités (créations, gestion, entrées, ...) * `Permission `_ : - Backend de droits, limites les pouvoirs des utilisateurs + Backend de droits, limites les pouvoirs des utilisateur⋅rices * `API <../api>`_ : API REST de la note, est notamment utilisée pour rendre la note dynamique (notamment la page de conso) @@ -52,9 +51,9 @@ Applications packagées ``_ * ``crispy_forms`` - Utiliser pour générer des forms avec bootstrap4 + Utiliser pour générer des formulaires avec Bootstrap4 * ``django_tables2`` - utiliser pour afficher des tables de données et les formater, en python plutôt qu'en HTML. + utiliser pour afficher des tables de données et les formater, en Python plutôt qu'en HTML. * ``restframework`` Base de l'`API <../api>`_. @@ -63,11 +62,11 @@ Applications facultatives * `Logs `_ Enregistre toute les modifications effectuées en base de donnée. * ``cas-server`` - Serveur central d'authenfication, permet d'utiliser son compte de la NoteKfet2020 pour se connecter à d'autre application ayant intégrer un client. -* `Script `_ - Ensemble de commande `./manage.py` pour la gestion de la note: import de données, verification d'intégrité, etc ... + 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... * `Treasury `_ : - Interface de gestion pour les trésoriers, émission de facture, remise de chèque, statistiques ... + Interface de gestion pour les trésorièr⋅es, émission de factures, remises de chèque, statistiques... * `WEI `_ : Interface de gestion du WEI. diff --git a/docs/apps/logs.rst b/docs/apps/logs.rst index afb3cf70..b561e4f2 100644 --- a/docs/apps/logs.rst +++ b/docs/apps/logs.rst @@ -6,22 +6,22 @@ Chaque modification effectuée sur un modèle est enregistrée dans la base dans Dès qu'un modèle veut être sauvegardé, deux signaux sont envoyés dans ``logs.signals`` : un avant et un après la sauvegarde. En pré-sauvegarde, on récupère l'ancienne version du modèle, si elle existe. -En post-sauvegarde, on récupère l'utilisateur et l'IP courants (voir ci-dessous), on convertit les modèles en JSON +En post-sauvegarde, on récupère l'utilisateur⋅rice et l'IP courant⋅es (voir ci-dessous), on convertit les modèles en JSON et on enregistre une entrée ``Changelog`` dans la base de données. -Pour récupérer l'utilisateur et son IP, le middleware ``logs.middlewares.LogsMiddlewares`` récupère à chaque requête -l'utilisateur et l'adresse IP, et les stocke dans le processus courant, afin qu'ils puissent être +Pour récupérer l'utilisateur⋅rice et son IP, le middleware ``logs.middlewares.LogsMiddlewares`` récupère à chaque requête +l'utilisateur⋅rice et l'adresse IP, et les stocke dans le processus courant, afin qu'ils puissent être récupérés par les signaux. Si jamais la modification ne provient pas d'une requête Web, on suppose qu'elle vient d'une instruction lancée avec ``manage.py``. -On récupère alors le nom de l'utilisateur dans l'interface de commandes, et si une note est associée à cet alias, +On récupère alors le nom de l'utilisateur⋅rice dans l'interface de commandes, et si une note est associée à cet alias, alors on considère que c'est le détenteur de la note qui a effectué cette modification, sur l'adresse IP ``127.0.0.1``. Sinon, le champ est laissé à ``None``. Une entrée de ``Changelog`` contient les informations suivantes : - * Utilisateur (``ForeignKey`` vers ``User``, nullable) + * Utilisateur⋅rice (``ForeignKey`` vers ``User``, nullable) * Adresse IP (``GenericIPAddressField``) * Type de modèle enregistré (``ForeignKey`` vers ``Model``) * Identifiant ``pk`` de l'instance enregistrée (``CharField``) @@ -54,4 +54,4 @@ Graphe ~~~~~~ .. image:: ../_static/img/graphs/logs.svg - :alt: Logs graphe + :alt: Logs graph diff --git a/docs/apps/member.rst b/docs/apps/member.rst index 7ec7cd8d..be44621c 100644 --- a/docs/apps/member.rst +++ b/docs/apps/member.rst @@ -1,63 +1,63 @@ Application Member ================== -L'application ``member`` s'occcupe de la gestion des utilisateurs enregistrés. +L'application ``member`` s'occcupe de la gestion des utilisateur⋅rices enregistré⋅es. -Le model d'utilisateur ``django.contrib.auth.model.User`` est complété par un ``Profile`` utilisateur. +Le model d'utilisateur⋅rice ``django.contrib.auth.model.User`` est complété par un ``Profile`` utilisateur⋅rice. -Tous les utilisateurs peuvent être membre de ``Club``. Cela se traduit par une adhésion ``Membership``, dont les +Toustes les utilisateur⋅rices peuvent être membre de ``Club``. Cela se traduit par une adhésion ``Membership``, dont les caractéristiques sont propres à chaque club. -En pratique, la NoteKfet possède au minimum deux Club: **Bde** et **Kfet** (instanciés via les fixtures). Et tous -les personnes à jour de cotisation sont membre à minima de Bde. -Être adhérent du club Kfet permet d'utiliser sa note pour consommer. +En pratique, la Note Kfet possède au minimum deux clubs : **Bde** et **Kfet** (instanciés +via les fixtures). Et toutes les personnes à jour de cotisation sont membre à minima de +BDE. Être adhérent⋅e du club Kfet permet d'utiliser sa note pour consommer. Modèles ------- -Utilisateur -~~~~~~~~~~~ +Utilisateur⋅rice +~~~~~~~~~~~~~~~~ Le modèle ``User`` est directement implémenté dans Django et n'appartient pas à l'application ``member``, mais il est bon de rappeler à quoi ressemble ce modèle. -* ``date_joined`` : ``DateTimeField``, date à laquelle l'utilisateur a été inscrit (*inutilisé dans la Note*) -* ``email`` : ``EmailField``, adresse e-mail de l'utilisateur. -* ``first_name`` : ``CharField``, prénom de l'utilisateur. +* ``date_joined`` : ``DateTimeField``, date à laquelle l'utilisateur⋅rice a été inscrit⋅e (*inutilisé dans la Note*) +* ``email`` : ``EmailField``, adresse e-mail de l'utilisateur⋅rice. +* ``first_name`` : ``CharField``, prénom de l'utilisateur⋅rice. * ``is_active`` : ``BooleanField``, indique si le compte est actif et peut se connecter. -* ``is_staff`` : ``BooleanField``, indique si l'utilisateur peut se connecter à l'interface Django-admin. -* ``is_superuser`` : ``BooleanField``, indique si l'utilisateur dispose de droits super-utilisateurs, permettant n'importe quelle action en base de donnée (lecture, ajout, modification, suppression). +* ``is_staff`` : ``BooleanField``, indique si l'utilisateur⋅rice peut se connecter à l'interface Django-admin. +* ``is_superuser`` : ``BooleanField``, indique si l'utilisateur⋅rice dispose de droits super-utilisateur⋅rices, permettant n'importe quelle action en base de donnée (lecture, ajout, modification, suppression). * ``last_login`` : ``DateTimeField``, date et heure de dernière connexion. -* ``last_name`` : ``CharField``, nom de famille de l'utilisateur. -* ``password`` : ``CharField``, contient le hash du mot de passe de l'utilisateur. L'algorithme utilisé est celui par défaut de Django : PBKDF2 + HMAC + SHA256 avec 150000 itérations. -* ``username`` : ``CharField`` (unique), pseudo de l'utilisateur. +* ``last_name`` : ``CharField``, nom de famille de l'utilisateur⋅rice. +* ``password`` : ``CharField``, contient le hash du mot de passe de l'utilisateur⋅rice. L'algorithme utilisé est celui par défaut de Django : PBKDF2 + HMAC + SHA256 avec 150000 itérations. +* ``username`` : ``CharField`` (unique), pseudo de l'utilisateur⋅rice. Profil ~~~~~~ Le modèle ``Profile`` contient un champ ``user`` de type ``OneToOneField``, ce qui permet de voir ce modèle comme une extension du modèle ``User``, sans avoir à le réécrire. Il contient diverses informations personnelles sur -l'utilisateur, utiles pour l'adhésion au BDE : +l'utilisateur⋅rice, utiles pour l'adhésion au BDE : -* ``user`` : ``OneToOneField(User)``, utilisateur lié à ce profil -* ``address`` : ``CharField``, adresse physique de l'utilisateur -* ``paid`` : ``BooleanField``, indique si l'utilisateur normalien est rémunéré ou non (utile pour différencier les montants d'adhésion aux clubs) -* ``phone_number`` : ``CharField``, numéro de téléphone de l'utilisateur -* ``section`` : ``CharField``, section de l'ENS à laquelle apartient l'utilisateur (exemple : 1A0, ...) +* ``user`` : ``OneToOneField(User)``, utilisateur⋅rice lié à ce profil +* ``address`` : ``CharField``, adresse physique de l'utilisateur⋅rice +* ``paid`` : ``BooleanField``, indique si l'utilisateur⋅rice normalien⋅ne est rémunéré⋅e ou non (utile pour différencier les montants d'adhésion aux clubs) +* ``phone_number`` : ``CharField``, numéro de téléphone de l'utilisateur⋅rice +* ``section`` : ``CharField``, section de l'ENS à laquelle appartient l'utilisateur⋅rice (exemple : 1A0, ...) Clubs ~~~~~ La gestion des clubs est une différence majeure avec la Note Kfet 2015. La Note gère ainsi les adhésions des -utilisateurs aux différents clubs. +utilisateur⋅rices aux différents clubs. * ``parent_club`` : ``ForeignKey(Club)``. La présence d'un club parent force l'adhésion au club parent avant de pouvoir adhérer au dit club. Tout club qui n'est pas le club BDE doit avoir le club BDE dans son arborescence. * ``email`` : ``EmailField``, adresse e-mail sur laquelle contacter le bureau du club. -* ``membership_start`` : ``DateField``, date à partir de laquelle il est possible d'adhérer à un club pour l'année suivante (si adhésions à l'année), en ignorant l'année. Par exemple, l'adhésion BDE est possible à partir du 31/08 par défaut, et c'est à cette date que les adhésions pour l'année future est possible. +* ``membership_start`` : ``DateField``, date à partir de laquelle il est possible d'adhérer à un club pour l'année suivante (si adhésions à l'année), en ignorant l'année. Par exemple, l'adhésion BDE est possible à partir du 01/08 par défaut, et c'est à cette date que les adhésions pour l'année future est possible. * ``membership_end`` : ``DateField``, date maximale de fin d'adhésion. Pour le club BDE, il s'agit du 30/09 de l'année suivante. Si cette valeur vaut ``null``, la fin d'adhésion n'est pas limitée. * ``membership_duration`` : ``PositiveIntegerField``, durée (en jours) maximale d'adhésion. Par exemple, le club BDE permet des adhésions maximales de 13 mois, soit 396 jours. -* ``membership_fee_paid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un élève normalien (donc rémunéré) puisse adhérer. -* ``membership_fee_unpaid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un étudiant normalien (donc non rémunéré) puisse adhérer. +* ``membership_fee_paid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un⋅e élève normalien⋅ne (donc rémunéré⋅e) puisse adhérer. +* ``membership_fee_unpaid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un⋅e étudiant⋅e normalien⋅ne (donc non rémunéré) puisse adhérer. * ``name`` : ``CharField``, nom du club. * ``require_memberships`` : ``BooleanField``, indique si le club est un vrai club BDE qui nécessite des adhésions de club, ou s'il s'agit d'une note "pot commun" (organisation d'une activité, note de département, ...) @@ -67,16 +67,16 @@ Adhésions Comme indiqué précédemment, la note gère les adhésions. * ``club`` : ``ForeignKey(Club)``, club lié à l'adhésion. -* ``user`` : ``ForeignKey(User)``, utilisateur adhéré. +* ``user`` : ``ForeignKey(User)``, utilisateur⋅rice qui a adhéré. * ``date_start`` : ``DateField``, date de début d'adhésion. * ``date_end`` : ``DateField``, date de fin d'adhésion. * ``fee`` : ``PositiveIntegerField``, montant de la cotisation payée. -* ``roles`` : ``ManyToManyField(Role)``, liste des rôles endossés par l'adhérent. +* ``roles`` : ``ManyToManyField(Role)``, liste des rôles endossés par l'adhérent⋅e. Rôles ~~~~~ -Comme indiqué le modèle des adhésions, les adhésions octroient des rôles aux adhérents, qui offrent des permissions +Comme indiqué le modèle des adhésions, les adhésions octroient des rôles aux adhérent⋅es, qui offrent des permissions (cf ``RolesPermissions`` dans la page des permissions). Le modèle ``RolesPermissions`` possède un ``OneToOneField(Role)``, qui implémente les permissions des rôles. Le modèle ``Role`` à proprement parler ne contient que le champ de son nom (``CharField``). @@ -88,7 +88,7 @@ Si le modèle ``MembershipTransaction`` appartient à l'application ``note``, il Le modèle ``MembershipTransaction`` est une extension du modèle ``Transaction`` (application ``note``) qui est de type polymorphique, et contient en plus des informations de base de la transaction un champ ``OneToOneField(Membership)`` faisant le lien entre l'adhésion et la transaction liée. Une adhésion club, si elle n'est pas gratuite, -génère en effet automatiquement une transaction de l'utilisateur vers le club (voir section adhésions). +génère en effet automatiquement une transaction de l'utilisateur⋅rice vers le club (voir section adhésions). Graphe ------ @@ -100,28 +100,28 @@ Adhésions --------- La Note Kfet offre la possibilité aux clubs de gérer l'adhésion de leurs membres. En plus de réguler les cotisations -des adhérents, des permissions sont octroyées sur la note en fonction des rôles au sein des clubs. Un rôle est une -fonction occupée au sein d'un club (Trésorier de club, président de club, GCKfet, Res[pot], respo info, ...). -Une adhésion attribue à un adhérent ses rôles. Les rôles fournissent les permissions. Par exemple, le trésorier d'un +des adhérent⋅es, des permissions sont octroyées sur la note en fonction des rôles au sein des clubs. Un rôle est une +fonction occupée au sein d'un club (Trésorièr⋅e de club, président⋅e de club, GC Kfet, Res[pot], respo info, ...). +Une adhésion attribue à un⋅e adhérent⋅e ses rôles. Les rôles fournissent les permissions. Par exemple, læ trésorièr⋅e d'un club a le droit de faire des transferts de et vers la note du club, tant que la source reste au-dessus de -50 €. Une adhésion est considérée comme valide si la date du jour est comprise (au sens large) entre les dates de début et de fin d'adhésion. -On peut ajouter une adhésion à un utilisateur dans un club à tout non adhérent de ce club. La personne en charge -d'adhérer quelqu'un choisit l'utilisateur, les rôles au sein du club et la date de début d'adhésion. Cette date de +On peut ajouter une adhésion à un⋅e utilisateur⋅rice dans un club à tout⋅e non adhérent⋅e de ce club. La personne en charge +d'adhérer quelqu'un choisit l'utilisateur⋅rice, les rôles au sein du club et la date de début d'adhésion. Cette date de début d'adhésion doit se situer entre les champs ``club.membership_start`` et ``club.membership_end``, -si ces champs sont non nuls. Si ``club.parent_club`` n'est pas nul, l'utilisateur doit être membre de ce club. -Le montant de la cotisation est fixé en fonction du statut normalien de l'utilisateur (``club.membership_fee_paid`` -centimes pour les élèves et ``club.membership_fee_unpaid`` centimes pour les étudiants). La date de fin est calculée +si ces champs sont non nuls. Si ``club.parent_club`` n'est pas nul, l'utilisateur⋅rice doit être membre de ce club. +Le montant de la cotisation est fixé en fonction du statut normalien de l'utilisateur⋅rice (``club.membership_fee_paid`` +centimes pour les élèves et ``club.membership_fee_unpaid`` centimes pour les étudiant⋅es). La date de fin est calculée comme ce qui suit : * Si ``club.membership_duration`` est non nul, alors ``date_end`` = ``date_start`` + ``club.membership_duration`` * Sinon ``club``, ``date_end`` = ``date_start`` + 424242 jours (suffisant pour tenir au moins une vie) * Si ``club.membership_end`` est non nul, alors ``date_end`` = min(``date_end``, ``club.membership_end``) -Si l'utilisateur n'est pas membre du club ``Kfet``, l'adhésion n'est pas possible si le solde disponible sur sa note est +Si l'utilisateur⋅rice n'est pas membre du club ``Kfet``, l'adhésion n'est pas possible si le solde disponible sur sa note est insuffisant. Une fois toute ces contraintes vérifiées, l'adhésion est créée. Une transaction de type -``MembershipTransaction`` est automatiquement créée de la note de l'utilisateur vers la note du club, finalisant l'adhésion. +``MembershipTransaction`` est automatiquement créée de la note de l'utilisateur⋅rice vers la note du club, finalisant l'adhésion. Réadhésions ~~~~~~~~~~~ @@ -137,7 +137,7 @@ Il est possible de réadhérer si : * Il n'y a pas encore de réadhésion (pas d'adhésion au même club vérifiant ``new_membership.date_start`` >= ``club.membership_start``) Un bouton ``Réadhérer`` apparaît dans la liste des adhésions si le droit est permis et si ces contraintes sont vérifiées. -En réadhérant, une nouvelle adhésion est créée pour l'utilisateur avec les mêmes rôles, commençant le lendemain de la +En réadhérant, une nouvelle adhésion est créée pour l'utilisateur⋅rice avec les mêmes rôles, commençant le lendemain de la date d'expiration de la précédente adhésion. Si on réadhère le 16 août pour une adhésion finissant le 30 septembre, la nouvelle adhésion commencera le 1er octobre). diff --git a/docs/apps/note/consumptions.rst b/docs/apps/note/consumptions.rst index 8b2209fe..772ba01b 100644 --- a/docs/apps/note/consumptions.rst +++ b/docs/apps/note/consumptions.rst @@ -7,23 +7,23 @@ Affichage La page de consommations est principalement une communication entre l'`API <../api>`_ et la page en JavaScript. Elle est disponible à l'adresse ``/note/consos/``, et l'onglet n'est visible que pour ceux ayant le droit de voir au moins un bouton. L'affichage, comme tout le reste de la page, est géré avec Boostrap 4. -Les boutons que l'utilisateur a le droit de voir sont triés par catégorie. +Les boutons que l'utilisateur⋅rice a le droit de voir sont triés par catégorie. Sélection des consommations --------------------------- -Lorsque l'utilisateur commence à taper un nom de note, un appel à l'API sur la page ``/api/note/alias`` est fait, -récupérant les 20 premiers aliases en accord avec la requête. Quand l'utilisateur survole un alias, un appel à la page +Lorsque l'utilisateur⋅rice commence à taper un nom de note, un appel à l'API sur la page ``/api/note/alias`` est fait, +récupérant les 20 premiers aliases en accord avec la requête. Quand l'utilisateur⋅rice survole un alias, un appel à la page ``/api/note/note//`` est fait pour récupérer plus d'infos sur la note telles que le solde, le vrai nom de la -note et la photo, si toutefois l'utilisateur a le droit de voir ceci. +note et la photo, si toutefois l'utilisateur⋅rice a le droit de voir ceci. -L'utilisateur peut cliquer sur des aliases pour ajouter des émetteurs, et sur des boutons pour ajouter des consommations. -Cliquer dans la liste des émetteurs supprime l'élément sélectionné. +L'utilisateur⋅rice peut cliquer sur des aliases pour ajouter des émetteur⋅rices, et sur des boutons pour ajouter des consommations. +Cliquer dans la liste des émetteur⋅rices supprime l'élément sélectionné. -Il ya deux possibilités pour faire consommer des adhérents : - - En mode **consommation simple** (mode par défaut), les consommations sont débitées dès que émetteurs et consommations +Il y a deux possibilités pour faire consommer des adhérent⋅es : + - En mode **consommation simple** (mode par défaut), les consommations sont débitées dès que émetteur⋅rices et consommations sont renseignées. - - En mode **consommation double**, l'utilisateur doit cliquer sur "Consommer !" pour débiter toutes les consommations. + - En mode **consommation double**, l'utilisateur⋅rice doit cliquer sur « **Consommer !** »" pour débiter toutes les consommations. Débit des consommations ----------------------- @@ -71,7 +71,7 @@ des types. Il vaut `42` lors de la rédaction de cette documentation, mais pourr Si une erreur survient lors de la requête (droits insuffisants), un message apparaîtra en haut de page. Dans tous les cas, tous les champs sont réinitialisés. -L'historique et la balance de l'utilisateur sont ensuite mis à jour via jQuery, qui permet de recharger une partie de page Web. +L'historique et le solde de l'utilisateur⋅rice sont ensuite mis à jour via jQuery, qui permet de recharger une partie de page Web. Validation/dévalidation des transactions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -86,4 +86,4 @@ une requête PATCH est faite à l'API sur l'adresse ``/api/note/transaction/tran "valid": false } -L'historique et la balance sont ensuite rafraîchis. Si une erreur survient, un message apparaîtra. +L'historique et le solde sont ensuite rafraîchis. Si une erreur survient, un message apparaîtra. diff --git a/docs/apps/note/index.rst b/docs/apps/note/index.rst index 1291dad9..19413f9b 100644 --- a/docs/apps/note/index.rst +++ b/docs/apps/note/index.rst @@ -1,12 +1,12 @@ Application Note ================ -L'application ``note`` gère tout ce qui est en lien avec les flux d'argent et les notes (balances) des utilisateurs. +L'application ``note`` gère tout ce qui est en lien avec les flux d'argent et les notes (soldes) des utilisateur⋅rices. La gestion des consommations s'effectue principalement via la page dédiée, dont le fonctionnement est expliqué dans la page `Consommations `_. -Le fonctionnnemnent des crédit/débit de note (avec le "monde extérieur" donc avec de l'argent réel) ainsi que les +Le fonctionnement des crédit/débit de note (avec le « monde extérieur » donc avec de l'argent réel) ainsi que les transferts/dons entre notes est détaillé sur la page `Transferts `_. .. toctree:: diff --git a/docs/apps/note/transfers.rst b/docs/apps/note/transfers.rst index 5438665e..26236ca2 100644 --- a/docs/apps/note/transfers.rst +++ b/docs/apps/note/transfers.rst @@ -6,7 +6,7 @@ Affichage L'interface de la page de transferts est semblable à celles des consommations, et l'auto-complétion de note est géré de la même manière. La page se trouve à l'adresse ``/note/transfer/``. La liste des 20 transactions les plus récentes que -l'utilisateur a le droit de voir est également présente. +l'utilisateur⋅rice a le droit de voir est également présente. Des boutons ``Don``, ``Transfert``, ``Crédit``, ``Retrait`` sont présents, représentant les différents modes de transfert. Pour chaque transfert, un montant et une description sont attendus. @@ -23,7 +23,7 @@ Onglets Crédit et retrait Ces onglets ne sont visibles que par ceux qui ont le droit de voir les ``SpecialNote``. Une boîte supplémentaire apparaît, demandant en plus de la note, du montant et de la raison le nom, le prénom et -la banque de la personne à recharger/retirer. Lorsqu'une note est sélectionnée, les champs "nom" et "prénom" sont +la banque de la personne à recharger/retirer. Lorsqu'une note est sélectionnée, les champs « nom » et « prénom » sont remplis automatiquement. Par ailleurs, seule une note peut être choisie. Transfert diff --git a/docs/apps/permission.rst b/docs/apps/permission.rst index ac859151..2d78d003 100644 --- a/docs/apps/permission.rst +++ b/docs/apps/permission.rst @@ -1,8 +1,8 @@ Droits ====== -Le système de droit par défault de django n'est pas suffisament granulaire pour les besoins de la NoteKfet2020. -Un système custom a donc été développé. +Le système de droit par défaut de Django n'est pas suffisamment granulaire pour les besoins de la Note Kfet 2020. +Un système personnalisé a donc été développé. Il permet la création de Permission, qui autorise ou non a faire une action précise sur un ou des objets de la base de données. @@ -22,12 +22,12 @@ Une permission est un Model Django dont les principaux attributs sont : * ``query`` : Requête sur la cible, encodé en JSON, traduit en un Q object (cf `Query <#compilation-de-la-query>`_) * ``field`` : le champ cible qui pourra être modifié. (tous les champs si vide) -Pour savoir si un utilisateur a le droit sur un modèle ou non, la requête est compilée (voir ci-dessous) en un filtre -de requête dans la base de données, un objet de la classe ``Q`` (En SQL l'objet Q s'interprete comme tout ce qui suit +Pour savoir si un⋅e utilisateur⋅rice a le droit sur un modèle ou non, la requête est compilée (voir ci-dessous) en un filtre +de requête dans la base de données, un objet de la classe ``Q`` (En SQL l'objet Q s'interprète comme tout ce qui suit un ``WHERE ...`` Ils peuvent être combiné à l'aide d'opérateurs logiques. Plus d'information sur les Q object dans la `documentation officielle `_. -Ce Q object sera donc utilisé pour savoir si l'instance que l'on veux modifier est concernée par notre permission. +Ce Q object sera donc utilisé pour savoir si l'instance que l'on veut modifier est concernée par notre permission. Exception faite sur l'ajout d'objets : l'objet n'existant pas encore en base de données, il est ajouté puis supprimé à la volée, en prenant soin de désactiver les signaux. @@ -36,7 +36,7 @@ Compilation de la query ----------------------- La query est enregistrée sous un format JSON, puis est traduite en requête ``Q`` récursivement en appliquant certains paramètres. -Le fonctionnemente de base des permission peux être décris avec les differents opérations : +Le fonctionnemente de base des permission peux être décris avec les différents opérations : +----------------+-----------------------------+-------------------------------------+ | opérations | JSON | Q object | @@ -64,7 +64,7 @@ Exemples {"is_superuser": true} - | si l'utilisateur cible est un super utilisateur. + | si l'utilisateur⋅rice cible est un⋅e super utilisateur⋅rice. * sur le model ``Note`` : @@ -74,7 +74,7 @@ Exemples ["user","note", "pk"] } - | si l'identifiant de la note cible est l'identifiant de l'utilisateur dont on regarde la permission. + | si l'identifiant de la note cible est l'identifiant de l'utilisateur⋅rice dont on regarde la permission. * sur le model ``Transaction``: @@ -87,7 +87,7 @@ Exemples ["user", "note", "balance"]} ] - | si la source est la note de l'utilisateur et si le montant est inférieur à son solde. + | si la source est la note de l'utilisateur⋅rice et si le montant est inférieur à son solde. * Sur le model ``Alias`` @@ -106,7 +106,7 @@ Exemples } ] - | si l'alias appartient à une note de club ou s'il appartient à la note d'un utilisateur membre du club Kfet. + | si l'alias appartient à une note de club ou s'il appartient à la note d'un⋅e utilisateur⋅rice membre du club Kfet. * sur le model ``Transaction`` @@ -130,19 +130,19 @@ Exemples Masques de permissions ---------------------- -Chaque permission est associée à un masque. À la connexion, l'utilisateur choisit le masque de droits avec lequel il -souhaite se connecter. Les masques sont ordonnés totalement, et l'utilisateur aura effectivement une permission s'il est +Chaque permission est associée à un masque. À la connexion, l'utilisateur⋅rice choisit le masque de droits avec lequel iel +souhaite se connecter. Les masques sont ordonnés totalement, et l'utilisateur⋅rice aura effectivement une permission si iel est en droit d'avoir la permission et si son masque est suffisamment haut. -Par exemple, si la permission de voir toutes les transactions est associée au masque "Droits note uniquement", -se connecter avec le masque "Droits basiques" n'octroiera pas cette permission tandis que le masque "Tous mes droits" oui. +Par exemple, si la permission de voir toutes les transactions est associée au masque « Droits note uniquement », +se connecter avec le masque « Droits basiques » n'octroiera pas cette permission tandis que le masque « Tous mes droits » oui. Signaux ------- À chaque fois qu'un modèle est modifié, ajouté ou supprimé, les droits sont contrôlés. Si les droits ne sont pas suffisants, une erreur est lancée. Pour ce qui est de la modification, on ne contrôle que les champs réellement -modifiés en comparant l'ancienne et la nouvele instance. +modifiés en comparant l'ancienne et la nouvelle instance. Graphe des modèles ------------------ diff --git a/docs/apps/registration.rst b/docs/apps/registration.rst index c5947349..22b99b23 100644 --- a/docs/apps/registration.rst +++ b/docs/apps/registration.rst @@ -4,7 +4,7 @@ Inscriptions L'inscription a la note se fait via une application dédiée, sans toutefois avoir de modèle en base de données. Un formulaire d'inscription est disponible sur la page ``/registration/signup``, accessible depuis n'importe qui, -authentifié ou non. Les informations suivantes sont demandées : +authentifié⋅e ou non. Les informations suivantes sont demandées : * Prénom * Nom de famille @@ -15,7 +15,7 @@ authentifié ou non. Les informations suivantes sont demandées : * Département d'études * Promotion, année d'entrée à l'ENS * Adresse (optionnel) -* Payé (si la personne perçoit un salaire) +* Payé⋅e (si la personne perçoit un salaire) Le mot de passe doit vérifier des contraintes de longueur, de complexité et d'éloignement des autres informations personnelles. @@ -34,28 +34,28 @@ le compte sera enfin actif. Pour récapituler : compte actif = adresse e-mail validée + inscription validée par le BDE. Lors de la validation de l'inscription, le BDE peut (et doit même) faire un crédit initial sur la future note de -l'utilisateur. Il peut spécifier le type de crédit (carte bancaire/espèces/chèque/virement bancaire), le prénom, +l'utilisateur⋅rice. Il peut spécifier le type de crédit (carte bancaire/espèces/chèque/virement bancaire), le prénom, le nom et la banque comme un crédit normal. Cependant, il peut aussi cocher une case "Société générale", si le nouveau membre indique avoir ouvert un compte à la Société générale via le partenariat Société générale - BDE de l'ÉNS Paris-Saclay. Dans ce cas, tous les champs sont grisés. Une fois l'inscription validée, détail de ce qu'il se passe : -* Si crédit de la socitété générale, on mémorise que le fait que la personne ait demandé ce crédit (voir +* Si crédit de la société générale, on mémorise que le fait que la personne ait demandé ce crédit (voir `Trésorerie `_ section crédits de la société générale). Nécessairement, le club Kfet doit être rejoint. -* Sinon, on crédite la note du montant demandé par le nouveau membre (avec comme description "Crédit TYPE (Inscription)" +* Sinon, on crédite la note du montant demandé par læ nouvelleau membre (avec comme description "Crédit TYPE (Inscription)" où TYPE est le type de crédit), après avoir vérifié que le crédit est suffisant (on n'ouvre pas une note négative) -* On adhère la personne au BDE, l'adhésion commence aujourd'hui. Il dispose d'un unique rôle : "Adhérent BDE", +* On adhère la personne au BDE, l'adhésion commence aujourd'hui. Iel dispose d'un unique rôle : « Adhérent⋅e BDE », lui octroyant un faible nombre de permissions de base, telles que la visualisation de son compte. -* On adhère la personne au club Kfet si cela est demandé, l'adhésion commence aujourd'hui. Il dispose d'un unique rôle : - "Adhérent Kfet", lui octroyant un nombre un peu plus conséquent de permissions basiques, telles que la possibilité de +* On adhère la personne au club Kfet si cela est demandé, l'adhésion commence aujourd'hui. Iel dispose d'un unique rôle : + « Adhérent⋅e Kfet » , lui octroyant un nombre un peu plus conséquent de permissions basiques, telles que la possibilité de faire des transactions, d'accéder aux activités, au WEI, ... -* Si le nouveau membre a indiqué avoir ouvert un compte à la société générale, alors les transactions sont invalidées, +* Si læ nouvelleau membre a indiqué avoir ouvert un compte à la société générale, alors les transactions sont invalidées, la note n'est pas débitée (commence alors à 0 €). Par ailleurs, le BDE peut supprimer la demande d'inscription sans problème via un bouton dédié. Cette opération n'est pas réversible. -L'utilisateur a enfin accès a sa note et peut faire des bêtises :) +L'utilisateur⋅rice a enfin accès a sa note et peut faire des bêtises :) L'inscription au BDE et à la Kfet est indépendante de l'inscription au WEI. Voir `WEI `_ pour l'inscription WEI. diff --git a/docs/apps/treasury.rst b/docs/apps/treasury.rst index 7c2a8746..6da6a73a 100644 --- a/docs/apps/treasury.rst +++ b/docs/apps/treasury.rst @@ -1,7 +1,7 @@ Application Trésorerie ====================== -L'application de Trésorerie facilite la vie des trésorier, et sert d'interface de création de facture. +L'application de Trésorerie facilite la vie des trésorièr⋅es, et sert d'interface de création de facture. Elle permet également le suivi des remises de chèques reçus par le BDE et des crédits de la Société générale. Factures @@ -90,7 +90,7 @@ présent à l'adresse suivante : On le remplit avec les données de la facture et les données du BDE, hard-codées. On copie le template rempli dans un ficher tex dans un dossier temporaire. On fait ensuite 2 appels à ``pdflatex`` pour générer la facture au format PDF. -Les deux appels sont nécessaires, il y a besoin d'un double rendu. Ensuite, le PDF est envoyé à l'utilisateur et on +Les deux appels sont nécessaires, il y a besoin d'un double rendu. Ensuite, le PDF est envoyé à l'utilisateur⋅rice et on supprime les données temporaires. On remarque que les PDF sont générés à la volée et ne sont pas sauvegardés. Niveau performances, cela prend du temps @@ -155,7 +155,7 @@ Relations ~~~~~~~~~ * Toute transaction qui n'est pas attachée à une remise d'un bon type peut être attachée à une remise. Cela se passe - par le biais d'un formulaire, où le trésorier peut vérifier et corriger au besoin nom, prénom, banque émettrice et montant. + par le biais d'un formulaire, où læ trésorièr⋅e peut vérifier et corriger au besoin nom, prénom, banque émettrice et montant. * Toute transaction attachée à une remise encore ouverte peut être retirée. * Pour clore une remise, il faut au moins 1 transaction associée. @@ -174,39 +174,39 @@ Modèle Cette sous-application dispose d'un unique modèle "SogeCredit" avec les champs suivant : -* ``user`` : ``OneToOneField`` vers ``User``, utilisateur associé à ce crédit (relation ``OneToOne`` car chaque - utilisateur ne peut bénéficier qu'une seule fois d'un crédit de la Société générale) +* ``user`` : ``OneToOneField`` vers ``User``, utilisateur⋅rice associé à ce crédit (relation ``OneToOne`` car chaque + utilisateur⋅rice ne peut bénéficier qu'une seule fois d'un crédit de la Société générale) * ``transactions`` : ``ManyToManyField`` vers ``MembershipTransaction``, liste des transactions d'adhésion associées à ce crédit, généralement adhésion BDE+Kfet+WEI même si cela n'est pas restreint * ``credit_transaction`` : ``OneToOneField`` vers ``SpecialTransaction``, peut être nulle, transaction de crédit de la - Société générale vers la note de l'utilisateur si celui-ci a été validé. C'est d'ailleurs le témoin + Société générale vers la note de l'utilisateur⋅rice si celui-ci a été validé. C'est d'ailleurs le témoin de validation du crédit. -On sait qu'un utilisateur a déjà demandé un crédit de la Société générale s'il existe un crédit associé à cet -utilisateur avec une transaction associée. Par ailleurs, le modèle ``Profile`` contient une propriété ``soge`` qui +On sait qu'un⋅e utilisateur⋅rice a déjà demandé un crédit de la Société générale s'il existe un crédit associé à cet⋅te +utilisateur⋅rice avec une transaction associée. Par ailleurs, le modèle ``Profile`` contient une propriété ``soge`` qui traduit exactement ceci, et qui vaut ``False`` si jamais l'application Trésorerie n'est pas chargée. -Si jamais l'utilisateur n'a pas encore demandé de crédit de la Société générale (ou que celui-ci n'est pas encore validé), -l'utilisateur peut demander un tel crédit lors de son adhésion BDE, de sa réadhésion BDE ou de son inscription au WEI. -Dans les deux premiers cas, il est invité à jumeler avec une nouvelle adhésion Kfet (merci de d'abord se réadhérer au +Si jamais l'utilisateur⋅rice n'a pas encore demandé de crédit de la Société générale (ou que celui-ci n'est pas encore validé), +l'utilisateur⋅rice peut demander un tel crédit lors de son adhésion BDE, de sa réadhésion BDE ou de son inscription au WEI. +Dans les deux premiers cas, iel est invité⋅e à jumeler avec une nouvelle adhésion Kfet (merci de d'abord se réadhérer au BDE avant la Kfet dans ce cas). Lorsqu'une telle demande est faite, l'adhésion est créée avec une transaction d'adhésion invalide. Cela implique que la note source n'est pas débitée et la note destination n'est pas créditée. -Sur son interface, le trésorier peut récupérer les crédits de Société générale invalides. Deux options s'offrent à lui : +Sur son interface, læ trésorièr⋅e peut récupérer les crédits de Société générale invalides. Deux options s'offrent à ellui : -* Supprimer la demande. Dans ce cas, les transactions vont être validées, la note de l'utilisateur sera débité, les - clubs seront crédités. Puisque la demande sera supprimée, l'utilisateur pourra à nouveau à l'avenir déclarer avoir - ouvert un compte à la Société générale. Cette option est utile dans le cas où l'utilisateur est un boulet (ou pas, +* Supprimer la demande. Dans ce cas, les transactions vont être validées, la note de l'utilisateur⋅rice sera débité, les + clubs seront crédités. Puisque la demande sera supprimée, l'utilisateur⋅rice pourra à nouveau à l'avenir déclarer avoir + ouvert un compte à la Société générale. Cette option est utile dans le cas où l'utilisateur⋅rice est un boulet (ou pas, pour d'autres raisons) et a déclaré vouloir ouvrir un compte à la Société générale sans ne rien faire. - Cette action est irréversible, et n'est pas possible si la note de l'utilisateur n'a pas un solde suffisant. + Cette action est irréversible, et n'est pas possible si la note de l'utilisateur⋅rice n'a pas un solde suffisant. -* Valider la demande. Dans ce cas, un crédit de la note "Virements bancaires" vers la note de l'utilisateur sera créé, +* Valider la demande. Dans ce cas, un crédit de la note "Virements bancaires" vers la note de l'utilisateur⋅rice sera créé, la transaction sera liée à la demande via le champ ``credit_note`` (et donc la demande déclarée valide), et toutes les transactions d'adhésion seront déclarées valides. -* Demander à un respo info s'il y a un problème pour le régler avant de faire des bêtises. Je l'admets, ça fait trois options. +* Demander à un⋅e respo info s'il y a un problème pour le régler avant de faire des bêtises. Je l'admets, ça fait trois options. La validité d'une transaction d'adhésion n'a aucune influence sur l'adhésion elle-même. Toutefois, cela se remarque rapidement ... diff --git a/docs/apps/wei.rst b/docs/apps/wei.rst index 51d3375b..7f678f99 100644 --- a/docs/apps/wei.rst +++ b/docs/apps/wei.rst @@ -19,14 +19,14 @@ Champs hérités de ``Club`` de l'application ``member`` : * ``parent_club`` : ``ForeignKey(Club)``. Ce champ vaut toujours ``Kfet`` dans le cas d'un WEI : on doit être membre du club Kfet pour participer au WEI. -* ``email`` : ``EmailField``, adresse e-mail sur laquelle contacter les gérants du WEI. +* ``email`` : ``EmailField``, adresse e-mail sur laquelle contacter les gérant⋅es du WEI. * ``membership_start`` : ``DateField``, date à partir de laquelle il est possible de s'inscrire au WEI. * ``membership_end`` : ``DateField``, date de fin d'adhésion possible au WEI. * ``membership_duration`` : ``PositiveIntegerField``, inutilisé dans le cas d'un WEI, vaut ``None``. -* ``membership_fee_paid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un élève normalien - (donc rémunéré) puisse adhérer. -* ``membership_fee_unpaid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un étudiant - normalien (donc non rémunéré) puisse adhérer. +* ``membership_fee_paid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un⋅e élève normalien⋅ne + (donc rémunéré⋅e) puisse adhérer. +* ``membership_fee_unpaid`` : ``PositiveIntegerField``, montant de la cotisation (en centimes) pour qu'un⋅e étudiant⋅e + normalien⋅ne (donc non rémunéré⋅e) puisse adhérer. * ``name`` : ``CharField``, nom du WEI. * ``require_memberships`` : ``BooleanField``, vaut toujours ``True`` pour le WEI. @@ -65,27 +65,27 @@ que de dissocier les rôles propres au WEI des rôles s'appliquant pour n'import WEIRegistration ~~~~~~~~~~~~~~~ -Inscription au WEI, contenant les informations avant validation. Ce modèle est créé dès lors que quelqu'un se pré-inscrit au WEI. +Inscription au WEI, contenant les informations avant validation. Ce modèle est créé dès lors que quelqu'un⋅e se pré-inscrit au WEI. -* ``user`` : ``ForeignKey(User)``, utilisateur qui s'est pré-inscrit. Ce champ est unique avec ``wei``. -* ``wei`` : ``ForeignKey(WEIClub)``, le WEI auquel l'utilisateur s'est pré-inscrit. Ce champ est unique avec ``user``. -* ``soge_credit`` : ``BooleanField``, indique si l'utilisateur a déclaré vouloir ouvrir un compte à la Société générale. -* ``caution_check`` : ``BooleanField``, indique si l'utilisateur (en 2ème année ou plus) a bien remis son chèque de +* ``user`` : ``ForeignKey(User)``, utilisateur⋅rice qui s'est pré-inscrit⋅e. Ce champ est unique avec ``wei``. +* ``wei`` : ``ForeignKey(WEIClub)``, le WEI auquel l'utilisateur⋅rice s'est pré-inscrit⋅e. Ce champ est unique avec ``user``. +* ``soge_credit`` : ``BooleanField``, indique si l'utilisateur⋅rice a déclaré vouloir ouvrir un compte à la Société générale. +* ``caution_check`` : ``BooleanField``, indique si l'utilisateur⋅rice (en 2ème année ou plus) a bien remis son chèque de caution auprès de la trésorerie. -* ``birth_date`` : ``DateField``, date de naissance de l'utilisateur. +* ``birth_date`` : ``DateField``, date de naissance de l'utilisateur⋅rice. * ``gender`` : ``CharField`` parmi ``male`` (Homme), ``female`` (Femme), ``non binary`` (Non binaire), genre de la personne. -* ``health_issues`` : ``TextField``, problèmes de santé déclarés par l'utilisateur. +* ``health_issues`` : ``TextField``, problèmes de santé déclarés par l'utilisateur⋅rice. * ``emergency_contact_name`` : ``CharField``, nom du contact en cas d'urgence. * ``emergency_contact_phone`` : ``CharField``, numéro de téléphone du contact en cas d'urgence. -* ``ml_events_registration`` : ``BooleanField``, déclare si l'utilisateur veut s'inscrire à la liste de diffusion des +* ``ml_events_registration`` : ``BooleanField``, déclare si l'utilisateur⋅rice veut s'inscrire à la liste de diffusion des événements du BDE (1A uniquement) -* ``ml_art_registration`` : ``BooleanField``, déclare si l'utilisateur veut s'inscrire à la liste de diffusion des +* ``ml_art_registration`` : ``BooleanField``, déclare si l'utilisateur⋅rice veut s'inscrire à la liste de diffusion des actualités du BDA (1A uniquement) -* ``ml_sport_registration`` : ``BooleanField``, déclare si l'utilisateur veut s'inscrire à la liste de diffusion des +* ``ml_sport_registration`` : ``BooleanField``, déclare si l'utilisateur⋅rice veut s'inscrire à la liste de diffusion des actualités du BDS (1A uniquement) -* ``first_year`` : ``BooleanField``, indique si l'inscription est d'un 1A ou non. Non modifiable par n'importe qui. +* ``first_year`` : ``BooleanField``, indique si l'inscription est d'un⋅e 1A ou non. Non modifiable par n'importe qui. * ``information_json`` : ``TextField`` non modifiable manuellement par n'importe qui stockant les informations du - questionnaire d'inscription au WEI pour les 1A, et stocke les demandes faites par un 2A+ concerant bus, équipes et rôles. + questionnaire d'inscription au WEI pour les 1A, et stocke les demandes faites par un⋅e 2A+ concernant bus, équipes et rôles. On utilise un ``TextField`` contenant des données au format JSON pour permettre de la modularité au fil des années, sans avoir à tout casser à chaque fois. @@ -94,19 +94,19 @@ WEIMembership Ce modèle hérite de ``Membership`` et contient les informations d'une adhésion au WEI. -* ``bus`` : ``ForeignKey(Bus)``, bus dans lequel se trouve l'utilisateur. +* ``bus`` : ``ForeignKey(Bus)``, bus dans lequel se trouve l'utilisateur⋅rice. * ``team`` : ``ForeignKey(BusTeam)`` pouvant être nulle (pour les chefs de bus et électrons libres), équipe dans laquelle - se trouve l'utilisateur. + se trouve l'utilisateur⋅rice. * ``registration`` : ``OneToOneField(WEIRegistration)``, informations de la pré-inscription. Champs hérités du modèle ``Membership`` : * ``club`` : ``ForeignKey(Club)``, club lié à l'adhésion. Doit être un ``WEIClub``. -* ``user`` : ``ForeignKey(User)``, utilisateur adhéré. +* ``user`` : ``ForeignKey(User)``, utilisateur⋅rice qui a adhéré. * ``date_start`` : ``DateField``, date de début d'adhésion. * ``date_end`` : ``DateField``, date de fin d'adhésion. * ``fee`` : ``PositiveIntegerField``, montant de la cotisation payée. -* ``roles`` : ``ManyToManyField(Role)``, liste des rôles endossés par l'adhérent. Les rôles doivent être des ``WEIRole``. +* ``roles`` : ``ManyToManyField(Role)``, liste des rôles endossés par l'adhérent⋅e. Les rôles doivent être des ``WEIRole``. Graphe des modèles ~~~~~~~~~~~~~~~~~~ @@ -123,32 +123,32 @@ Fonctionnement Création d'un WEI ~~~~~~~~~~~~~~~~~ -Seul un respo info peut créer un WEI. Pour cela, se rendre dans l'onglet WEI, puis "Liste des WEI" et enfin -"Créer un WEI". Diverses informations sont demandées, comme le nom du WEI, l'adresse mail de contact, l'année du WEI +Seul un⋅e respo info peut créer un WEI. Pour cela, se rendre dans l'onglet WEI, puis « Liste des WEI » et enfin +« Créer un WEI ». Diverses informations sont demandées, comme le nom du WEI, l'adresse mail de contact, l'année du WEI (doit être unique), les dates de début et de fin, et les dates pendant lesquelles les utilisateurs peuvent s'inscrire. Don des droits à un GC WEI ~~~~~~~~~~~~~~~~~~~~~~~~~~ -Le GC WEI peut gérer tout ce qui a un rapport avec le WEI. Il ne peut cependant pas créer le WEI, ce privilège est -réservé au respo info. Pour avoir ses droits, le GC WEI doit s'inscrire au WEI avec le rôle GC WEI, et donc payer -en premier sa cotisation. C'est donc au respo info de créer l'adhésion du GC WEI. Voir ci-dessous pour l'inscription au WEI. +Læ GC WEI peut gérer tout ce qui a un rapport avec le WEI. Iel ne peut cependant pas créer le WEI, ce privilège est +réservé aux respos info. Pour avoir ses droits, læ GC WEI doit s'inscrire au WEI avec le rôle GC WEI, et donc payer +en premièr⋅e sa cotisation. C'est donc aux respos info de créer l'adhésion du GC WEI. Voir ci-dessous pour l'inscription au WEI. S'inscrire au WEI ~~~~~~~~~~~~~~~~~ -N'importe quel utilisateur peut s'auto-inscrire au WEI, lorsque les dates d'adhésion le permettent. Ceux qui se sont -déjà inscrits peuvent également inscrire un 1A. Seuls les GC WEI et les respo info peuvent inscrire un autre 2A+. +N'importe quel⋅le utilisateur⋅rice peut s'auto-inscrire au WEI, lorsque les dates d'adhésion le permettent. Celleux qui se sont +déjà inscrit⋅es peuvent également inscrire un⋅e 1A. Seul⋅es les GC WEI et les respos info peuvent inscrire un⋅e autre 2A+. À tout moment, tant que le WEI n'est pas passé, l'inscription peut être modifiée, même après validation. -Inscription d'un 2A+ -^^^^^^^^^^^^^^^^^^^^ +Inscription d'un⋅e 2A+ +^^^^^^^^^^^^^^^^^^^^^^ -Comme indiqué, les 2A+ sont assez autonomes dans leur inscription au WEI. Ils remplissent le questionnaire et sont -ensuite pré-inscrits. Le questionnaire se compose de plusieurs champs (voir WEIRegistration) : +Comme indiqué, les 2A+ sont assez autonomes dans leur inscription au WEI. Iels remplissent le questionnaire et sont +ensuite pré-inscrit⋅es. Le questionnaire se compose de plusieurs champs (voir WEIRegistration) : -* Est-ce que l'utilisateur a déclaré avoir ouvert un compte à la Société générale ? (Option disponible uniquemement +* Est-ce que l'utilisateur⋅rice a déclaré avoir ouvert un compte à la Société générale ? (Option disponible uniquemement si cela n'a pas été fait une année avant) * Date de naissance * Genre (Homme/Femme/Non-binaire) @@ -159,17 +159,17 @@ ensuite pré-inscrits. Le questionnaire se compose de plusieurs champs (voir WEI * Équipes préférées (choix multiple éventuellement vide, vide pour les chefs de bus/staff) * Rôles souhaités -Les trois derniers champs n'ont aucun caractère définitif et sont simplement là en suggestion pour le GC WEI qui +Les trois derniers champs n'ont aucun caractère définitif et sont simplement là en suggestion pour læ GC WEI qui validera l'inscription. C'est utile si on hésite entre plusieurs bus. L'inscription est ensuite créée, le GC WEI devra ensuite la valider (voir plus bas). -Inscription d'un 1A -^^^^^^^^^^^^^^^^^^^ +Inscription d'un⋅e 1A +^^^^^^^^^^^^^^^^^^^^^ -N'importe quelle personne déjà inscrite au WEI peut inscrire un 1A. Le formulaire 1A est assez peu différent du formulaire 2A+ : +N'importe quelle personne déjà inscrite au WEI peut inscrire un⋅e 1A. Le formulaire 1A est assez peu différent du formulaire 2A+ : -* Est-ce que l'utilisateur a déclaré avoir ouvert un compte à la Société générale ? +* Est-ce que l'utilisateur⋅rice a déclaré avoir ouvert un compte à la Société générale ? * Date de naissance * Genre (Homme/Femme/Non-binaire) * Problèmes de santé @@ -179,10 +179,10 @@ N'importe quelle personne déjà inscrite au WEI peut inscrire un 1A. Le formula * S'inscrire à la ML BDA * S'inscrire à la ML BDS -Le 1A ne peut donc pas choisir de son bus et de son équipe, et peut s'inscrire aux listes de diffusion. +Læ 1A ne peut donc pas choisir de son bus et de son équipe, et peut s'inscrire aux listes de diffusion. Il y a néanmoins une différence majeure : une fois le formulaire rempli, un questionnaire se lance. Ce questionnaire peut varier au fil des années (voir section Questionnaire), et contient divers formulaires de collecte -de données qui serviront à déterminer quel est le meilleur bus pour ce nouvel utilisateur. +de données qui serviront à déterminer quel est le meilleur bus pour ce⋅tte nouvelleau utilisateur⋅rice. Questionnaire 1A ^^^^^^^^^^^^^^^^ @@ -200,7 +200,7 @@ Je veux changer d'algorithme de répartition, que faire ? Cette section est plus technique et s'adresse surtout aux respos info en cours de mandat. -Première règle : on ne supprime rien (sauf si vraiment c'est du mauvais boulot). En prenant exemple sur des fichiers déjà existant tels que ``apps/wei/forms/surveys/wei2020.py``, créer un nouveau fichier ``apps/wei/forms/surveys/wei20XY.py``. Ce fichier doit inclure les éléments suivants : +Première règle : on ne supprime rien (sauf si vraiment c'est du mauvais boulot). En prenant exemple sur des fichiers déjà existant tels que ``apps/wei/forms/surveys/wei2021.py``, créer un nouveau fichier ``apps/wei/forms/surveys/wei20XY.py``. Ce fichier doit inclure les éléments suivants : WEISurvey """"""""" @@ -223,7 +223,7 @@ Une classe héritant de ``wei.forms.surveys.base.WEISurvey``, comportant les él Naturellement, il est implicite qu'une fonction ayant pour premier argument ``cls`` doit être annotée par ``@classmethod``. Nativement, la classe ``WEISurvey`` comprend les informations suivantes : -* ``registration``, le modèle ``WEIRegistration`` de l'utilisateur qui remplit le questionnaire +* ``registration``, le modèle ``WEIRegistration`` de l'utilisateur⋅rice qui remplit le questionnaire * ``information``, instance de ``WEISurveyInformation``, contient les données du questionnaire en cours de remplissage. * ``get_wei(cls)``, renvoie le WEI correspondant à l'année du sondage. * ``save(self)``, enregistre les informations du sondage dans l'objet ``registration`` associé, qui est ensuite @@ -291,7 +291,7 @@ pour unique effet d'appeler la fonction ``run_algorithm`` décrite plus tôt. Un n'a pas été évoqué d'adhésion. L'adhésion est ensuite manuelle, l'algorithme ne fournit qu'une suggestion. Cette structure, complexe mais raisonnable, permet de gérer plus ou moins proprement la répartition des 1A, -en limitant très fortement le hard code. Ami nouveau développeur, merci de bien penser à la propreté du code :) +en limitant très fortement le hard code. Ami⋅e nouvelleau développeur⋅se, merci de bien penser à la propreté du code :) En particulier, on évitera de mentionner dans le code le nom des bus, et profiter du champ ``information_json`` présent dans le modèle ``Bus``. @@ -300,34 +300,34 @@ Valider les inscriptions Cette partie est moins technique. -Une fois la pré-inscription faite, elle doit être validée par le BDE, afin de procéder au paiement. Le GC WEI a accès à +Une fois la pré-inscription faite, elle doit être validée par le BDE, afin de procéder au paiement. Læ GC WEI a accès à la liste des inscriptions non validées, soit sur la page de détails du WEI, soit sur un tableau plus large avec filtre. Une inscription non validée peut soit être validée, soit supprimée (la suppression est irréversible). -Lorsque le GC WEI veut valider une inscription, il a accès au récapitulatif de l'inscription ainsi qu'aux informations -personnelles de l'utilisateur. Il lui est proposé de les modifier si besoin (du moins les informations liées au WEI, -pas les informations personnelles). Il a enfin accès aux résultats du sondage et la sortie de l'algorithme s'il s'agit -d'un 1A, aux préférences d'un 2A+. Avant de valider, le GC WEI doit sélectionner un bus, éventuellement une équipe -et un rôle. Si c'est un 1A et que l'algorithme a tourné, ou si c'est un 2A+ qui n'a fait qu'un seul choix de bus, +Lorsque læ GC WEI veut valider une inscription, iel a accès au récapitulatif de l'inscription ainsi qu'aux informations +personnelles de l'utilisateur⋅rice. Il lui est proposé de les modifier si besoin (du moins les informations liées au WEI, +pas les informations personnelles). Iel a enfin accès aux résultats du sondage et la sortie de l'algorithme s'il s'agit +d'un⋅e 1A, aux préférences d'un⋅e 2A+. Avant de valider, læ GC WEI doit sélectionner un bus, éventuellement une équipe +et un rôle. Si c'est un⋅e 1A et que l'algorithme a tourné, ou si c'est un⋅e 2A+ qui n'a fait qu'un seul choix de bus, d'équipe, de rôles, les champs sont automatiquement pré-remplis. Quelques restrictions cependant : -* Si c'est un 2A+, le chèque de caution doit être déclaré déposé +* Si c'est un⋅e 2A+, le chèque de caution doit être déclaré déposé * Si l'inscription se fait via la Société générale, un message expliquant la situation apparaît : la transaction de - paiement sera créée mais invalidée, les trésoriers devront confirmer plus tard sur leur interface que le compte + paiement sera créée mais invalidée, les trésorièr⋅es devront confirmer plus tard sur leur interface que le compte à la Société générale a bien été créé avant de valider la transaction (voir `Trésorerie `_ section Crédit de la Société générale). -* Dans le cas contraire, l'utilisateur doit avoir le solde nécessaire sur sa note avant de pouvoir adhérer. -* L'utilisateur doit enfin être membre du club Kfet. Un lien est présent pour le faire adhérer ou réadhérer selon le cas. +* Dans le cas contraire, l'utilisateur⋅rice doit avoir le solde nécessaire sur sa note avant de pouvoir adhérer. +* L'utilisateur⋅rice doit enfin être membre du club Kfet. Un lien est présent pour le faire adhérer ou réadhérer selon le cas. -Si tout est bon, le GC WEI peut valider. L'utilisateur a bien payé son WEI, et son interface est un peu plus grande. -Il peut toujours changer ses paramètres au besoin. Un 1A ne voit rien de plus avant la fin du WEI. +Si tout est bon, læ GC WEI peut valider. L'utilisateur⋅rice a bien payé son WEI, et son interface est un peu plus grande. +Iel peut toujours changer ses paramètres au besoin. Un⋅e 1A ne voit rien de plus avant la fin du WEI. -Un adhérent WEI non 1A a accès à la liste des bus, des équipes et de leur descriptions. Les chefs de bus peuvent gérer -les bus et leurs équipes. Les chefs d'équipe peuvent gérer leurs équipes. Cela inclut avoir accès à la liste des membres +Un⋅e adhérent⋅e WEI non 1A a accès à la liste des bus, des équipes et de leur descriptions. Les chef⋅fes de bus peuvent gérer +les bus et leurs équipes. Les chef⋅fes d'équipe peuvent gérer leurs équipes. Cela inclut avoir accès à la liste des membres de ce bus / de cette équipe. -Un export au format PDF de la liste des membres *visibles* est disponible pour chacun. +Un export au format PDF de la liste des membres *visibles* est disponible pour chacun⋅e. -Bon WEI à tous ! +Bon WEI à toustes ! diff --git a/docs/external_services/cas.rst b/docs/external_services/cas.rst index 36dedf3e..474fb5ba 100644 --- a/docs/external_services/cas.rst +++ b/docs/external_services/cas.rst @@ -3,7 +3,7 @@ Service d'Authentification Centralisé (CAS) Un `CAS `_ est déployé sur la Note Kfet. Il est accessible à l'adresse ``_. -Il a pour but uniquement d'authentifier les utilisateurs via la note et ne communique +Il a pour but uniquement d'authentifier les utilisateur⋅rices via la note et ne communique que peu d'informations. Configuration diff --git a/docs/external_services/index.rst b/docs/external_services/index.rst index ab1539aa..05524844 100644 --- a/docs/external_services/index.rst +++ b/docs/external_services/index.rst @@ -12,10 +12,10 @@ Applications externes L'utilisation de la note par des services externes est actuellement en beta. Il est fort à parier que cette utilisation sera revue et améliorée à l'avenir. -Puisque la Note Kfet recense tous les comptes des adhérents BDE, les clubs ont alors +Puisque la Note Kfet recense tous les comptes des adhérent⋅es BDE, les clubs ont alors la possibilité de développer leurs propres applications et de les interfacer avec la -note. De cette façon, chaque application peut authentifier ses utilisateurs via la note, -et récupérer leurs adhésion, leur nom de note afin d'éventuellement faire des transferts +note. De cette façon, chaque application peut authentifier ses utilisateur⋅rices via la note, +et récupérer leurs adhésions, leur nom de note afin d'éventuellement faire des transferts via l'API. Deux protocoles d'authentification sont implémentées : @@ -25,4 +25,4 @@ Deux protocoles d'authentification sont implémentées : À ce jour, il n'y a pas encore d'exemple d'utilisation d'application qui utilise ce mécanisme, mais on peut imaginer par exemple que la Mediatek ou l'AMAP implémentent -ces protocoles pour récupérer leurs adhérents. +ces protocoles pour récupérer leurs adhérent⋅es. diff --git a/docs/external_services/oauth2.rst b/docs/external_services/oauth2.rst index 6ee89621..76dead8b 100644 --- a/docs/external_services/oauth2.rst +++ b/docs/external_services/oauth2.rst @@ -2,12 +2,12 @@ OAuth2 ====== L'authentification `OAuth2 `_ est supportée par la -Note Kfet. Elle offre l'avantage non seulement d'identifier les utilisateurs, mais aussi -de transmettre des informations à un service tiers tels que des informations personnelles, -le solde de la note ou encore les adhésions de l'utilisateur, en l'avertissant sur -quelles données sont effectivement collectées. Ainsi, il est possible de développer des -appplications tierces qui peuvent se baser sur les données de la Note Kfet ou encore -faire des transactions. +Note Kfet. Elle offre l'avantage non seulement d'identifier les utilisateur⋅rices, mais +aussi de transmettre des informations à un service tiers tels que des informations +personnelles, le solde de la note ou encore les adhésions de l'utilisateur⋅rice, en +l'avertissant sur quelles données sont effectivement collectées. Ainsi, il est possible +de développer des appplications tierces qui peuvent se baser sur les données de la Note +Kfet ou encore faire des transactions. Configuration du serveur @@ -79,7 +79,7 @@ Il vous suffit de donner à votre application : * Les scopes, qui peuvent être récupérées sur cette page : ``_ * L'URL d'autorisation : ``_ * L'URL d'obtention de jeton : ``_ -* Si besoin, l'URL de récupération des informations de l'utilisateur : ``_ +* Si besoin, l'URL de récupération des informations de l'utilisateur⋅rice : ``_ N'hésitez pas à consulter la page ``_ pour s'imprégner du format renvoyé. @@ -88,14 +88,14 @@ du format renvoyé. Un petit mot sur les scopes : tel qu'implémenté, une scope est une permission unitaire (telle que décrite dans le modèle ``Permission``) associée à un club. Ainsi, un jeton - a accès à une scope si et seulement si le/la propriétaire du jeton dispose d'une adhésion + a accès à une scope si et seulement si læ propriétaire du jeton dispose d'une adhésion courante dans le club lié à la scope qui lui octroie cette permission. Par exemple, un jeton pourra avoir accès à la permission de créer des transactions en lien - avec un club si et seulement si le propriétaire du jeton est trésorier du club. + avec un club si et seulement si læ propriétaire du jeton est trésorièr⋅e du club. - La vérification des droits du propriétaire est faite systématiquement, afin de ne pas - faire confiance au jeton en cas de droits révoqués à son propriétaire. + La vérification des droits de læ propriétaire est faite systématiquement, afin de ne pas + faire confiance au jeton en cas de droits révoqués à saon propriétaire. Vous pouvez donc contrôler le plus finement possible les permissions octroyées à vos jetons. @@ -118,8 +118,8 @@ du format renvoyé. Par exemple, vous pourriez demander la permission d'accéder aux membres d'un club ou de faire des transactions, et agir - uniquement dans le cas où l'utilisateur connecté possède la - permission problématique. + uniquement dans le cas où l'utilisateur⋅rice connecté⋅e + possède la permission problématique. Avec Django-allauth ################### @@ -152,7 +152,7 @@ Le paramètre ``DOMAIN`` permet de changer d'instance de Note Kfet. Par défaut, se connectera à ``note.crans.org`` si vous ne renseignez rien. Le paramètre ``SCOPE`` permet de définir les scopes à demander. -Dans l'exemple ci-dessous, les permissions d'accéder à l'utilisateur +Dans l'exemple ci-dessous, les permissions d'accéder à l'utilisateur⋅rice et au profil sont demandées. En créant l'application sur la note, vous pouvez renseigner @@ -200,7 +200,7 @@ cas où elle n'est pas explicitement indiquée lors de l'autorisation. Lorsqu'un client veut s'authentifier via la Note Kfet, il va devoir accéder à une page d'authentification. La page d'autorisation est ``_, -c'est sur cette page qu'il faut rediriger les utilisateurs. Il faut mettre en paramètre GET : +c'est sur cette page qu'il faut rediriger les utilisateur⋅rices. Il faut mettre en paramètre GET : * ``client_id`` : l'identifiant client de l'application (public) ; * ``response_type`` : mettre ``code`` ; @@ -211,10 +211,10 @@ c'est sur cette page qu'il faut rediriger les utilisateurs. Il faut mettre en pa * ``state`` : optionnel, peut être utilisé pour permettre au client de détecter des requêtes provenant d'autres sites. -Sur cette page, les permissions demandées seront listées, et l'utilisateur aura le choix -d'accepter ou non. Dans les deux cas, l'utilisateur sera redirigée vers ``redirect_uri``, -avec pour paramètre GET soit le message d'erreur, soit un paramètre ``code`` correspondant -au code d'autorisation. +Sur cette page, les permissions demandées seront listées, et l'utilisateur⋅rice aura le +choix d'accepter ou non. Dans les deux cas, l'utilisateur⋅rice sera redirigée vers +``redirect_uri``, avec pour paramètre GET soit le message d'erreur, soit un paramètre +``code`` correspondant au code d'autorisation. Une fois ce code d'autorisation récupéré, il faut désormais récupérer le jeton d'accès. Il faut pour cela aller sur l'URL ``_, effectuer une diff --git a/docs/faq.rst b/docs/faq.rst index 18b8a4be..bdc4028d 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -5,27 +5,26 @@ Des transactions anormales sont apparues sur mon compte. -------------------------------------------------------- .. note:: - Tu dois immédiatement contacter les trésoriers du BDE (voir ci-dessous) pour - signaler l'incident. Précise bien ton nom de note, l'heure de la transaction - ainsi que l'alias utilisé pour faire la transaction (en plaçant ta souris sur - ton pseudo sur la ligne de transaction, l'alias utilisé apparaît). La raison - la plus courante est que tu as un alias qui est trop proche d'un autre d'une - autre personne. Même si la Note Kfet 2020 essaie d'éviter ça, tu es invité⋅e - à supprimer l'alias problématique, ou tout du moins t'assurer que la confusion - ne puisse plus arriver. + Tu dois immédiatement contacter les trésorièr⋅es du BDE (voir ci-dessous) pour + signaler l'incident. Précise bien ton nom de note, l'heure de la transaction ainsi que + l'alias utilisé pour faire la transaction (en plaçant ta souris sur ton pseudo sur la + ligne de transaction, l'alias utilisé apparaît). La raison la plus courante est que tu + as un alias qui est trop proche d'une autre personne. Même si la Note Kfet 2020 essaie + d'éviter ça, tu es invité⋅e à supprimer l'alias problématique, ou tout du moins + t'assurer que la confusion ne puisse plus arriver. Je souhaite consommer mais le solde de ma note est insuffisant -------------------------------------------------------------- .. note:: - Le BDE ne fait pas crédit à ses adhérents. Il est de ton devoir de t'assurer - d'avoir en permanence un solde positif sur ta note. Les permanenciers à la - Kfet ont la possibilité de refuser une consommation qui fait passer en négatif, - et ont obligation de refuser si la consommation est alcoolisée, en accord avec - la règlementation en vigueur. + Le BDE ne fait pas crédit à ses adhérent⋅es. Il est de ton devoir de t'assurer d'avoir + en permanence un solde positif sur ta note. Les permanencièr⋅es à la Kfet ont la + possibilité de refuser une consommation qui fait passer en négatif, et ont obligation + de refuser si la consommation est alcoolisée, en accord avec la règlementation en + vigueur. - Les trésoriers connaissent la liste des personnes en situation irrégulière et + Les trésorièr⋅es connaissent la liste des personnes en situation irrégulière et n'hésiteront pas à faire des rappels pour recharger la note. @@ -33,10 +32,10 @@ Comment recharger ma note ? --------------------------- .. note:: - Le solde de la note peut être rechargé soit par espèces, par chèque à l'ordre - de l'amicale des élèves de l'ENS Cachan, par carte bancaire via un terminal - de paiement électronique ou encore par virement bancaire, dont les coordonnées - sont à demander auprès des trésoriers BDE. + Le solde de la note peut être rechargé soit par espèces, par chèque à l'ordre de + l'amicale des élèves de l'ENS Paris-Saclay, par carte bancaire via un terminal de + paiement électronique ou encore par virement bancaire, dont les coordonnées sont à + demander auprès des trésorièr⋅es BDE. Les trois premières options sont à faire directement dans la Kfet. @@ -45,9 +44,9 @@ Je pars en stage / en vacances. Puis-je bloquer ma note ? --------------------------------------------------------- .. note:: - Bien sûr : il te suffit de te rendre sur ton compte et de cliquer sur le bouton - dédié. Ta note ne sera plus affichée par les autres personnes et les transferts - seront impossibles, sauf pour les trésoriers BDE et respo info. + Bien sûr : il te suffit de te rendre sur ton compte et de cliquer sur le bouton dédié. + Ta note ne sera plus affichée par les autres personnes et les transferts seront + impossibles, sauf pour les trésorièr⋅es BDE et respo info. Il est toutefois de ton devoir de rembourser tout ce que tu dois. @@ -56,88 +55,87 @@ Quelle est la limite maximale au nombre d'alias d'une note ? ------------------------------------------------------------ .. note:: - Certains parlent d'une dizaine d'alias par note. + Certain⋅es parlent d'une dizaine d'alias par note. Sois conscient⋅e qu'ajouter des alias ne peut qu'augmenter la probabilité de - collisions avec une autre note, et peut aussi retarder la livraison de ta - commande lors d'un perm bouffe. + collisions avec une autre note, et peut aussi retarder la livraison de ta commande + lors d'une perm bouffe. -Je suis trésorier d'un club, qu'ai-je le droit de faire ? ---------------------------------------------------------- +Je suis trésorièr⋅e d'un club, qu'ai-je le droit de faire ? +----------------------------------------------------------- .. note:: - Être trésorier d'un club donne la responsabilité de gérer la trésorerie du - club, et donc de gérer sa note. Vous obtenez donc le droit d'effectuer - n'importe quelle transaction via la note en provenance ou à destination de - la note de votre club. Vous pouvez également gérer les adhésions de votre club, - en permettant à n'importe quel adhérent BDE de rejoindre votre club, en prélevant - d'éventuels frais d'adhésion. Les paramètres du club peuvent être également modifiés. + Être trésorièr⋅e d'un club donne la responsabilité de gérer la trésorerie du club, et + donc de gérer sa note. Vous obtenez donc le droit d'effectuer n'importe quelle + transaction via la note en provenance ou à destination de la note de votre club. Vous + pouvez également gérer les adhésions de votre club, en permettant à n'importe quel⋅le + adhérent⋅e BDE de rejoindre votre club, en prélevant d'éventuels frais d'adhésion. Les + paramètres du club peuvent être également modifiés. .. danger:: - Avoir des droits sur la Note Kfet ne signifie pas que vous devez les utiliser. - Chaque opération nécessitant des droits doit être fait pour une bonne raison, - et doit avoir un lien avec votre club. Vous n'avez par exemple pas le droit - d'aller récupérer des informations personnelles d'adhérents pour une raison - personnelle. En revanche, faire le lien entre nom/prénom et nom de note est - bien sûr permis pour faciliter des transferts. Tout abus de droits constaté - pourra mener à des sanctions prises par le bureau du BDE. + Avoir des droits sur la Note Kfet ne signifie pas que vous devez les utiliser. Chaque + opération nécessitant des droits doit être fait pour une bonne raison, et doit avoir + un lien avec votre club. Vous n'avez par exemple pas le droit d'aller récupérer des + informations personnelles d'adhérent⋅es pour une raison personnelle. En revanche, + faire le lien entre nom/prénom et nom de note est bien sûr permis pour faciliter des + transferts. Tout abus de droits constaté pourra mener à des sanctions prises par le + bureau du BDE. -Je suis trésorier d'un club, je n'arrive pas à voir le solde du club / faire des transactions ---------------------------------------------------------------------------------------------------- +Je suis trésorièr⋅e d'un club, je n'arrive pas à voir le solde du club / faire des transactions +----------------------------------------------------------------------------------------------- .. note:: - As-tu bien vérifié que tu t'es connecté⋅e initialement avec tous tes droits ? - Sinon, si tes droits sont tout récents, tu dois te déconnecter et te reconnecter - pour que tes droits soient bien pris en compte. + As-tu bien vérifié que tu t'es connecté⋅e initialement avec tous tes droits ? Sinon, + si tes droits sont tout récents, tu dois te déconnecter et te reconnecter pour que tes + droits soient bien pris en compte. - La Note permet de se connecter avec différents filtres de permission afin de - pouvoir prêter son ordinateur avec une session ouverte pour faire quelques - opérations en empêchant l'accès à des opérations trop sensibles. + La Note permet de se connecter avec différents filtres de permission afin de pouvoir + prêter son ordinateur avec une session ouverte pour faire quelques opérations en + empêchant l'accès à des opérations trop sensibles. -Je suis trésorier d'un club. Puis-je créer un bouton ? ------------------------------------------------------- +Je suis trésorièr⋅e d'un club. Puis-je créer un bouton ? +-------------------------------------------------------- .. note:: Oui bien sûr ! Tant qu'il redirige bien vers la note de ton club. - Pour cela, rends-toi à la page ``_ pour afficher la liste des - boutons, puis tu auras accès à l'interface pour créer un bouton. Une fois le - bouton créé, il apparaîtra dans l'onglet ``Consommations``. + Pour cela, rends-toi à la page ``_ pour afficher la liste des boutons, + puis tu auras accès à l'interface pour créer un bouton. Une fois le bouton créé, il + apparaîtra dans l'onglet ``Consommations``. - Il faut noter que tant qu'il n'y a pas de boutons visibles pour ton club, tu - n'auras pas accès à l'interface de consommations, et tu devras nécessairement - cliquer sur le lien ci-dessus pour accéder à l'interface d'édition des boutons. - Une fois qu'un bouton pour ton club est visible, l'interface consommations - devient accessible. + Il faut noter que tant qu'il n'y a pas de boutons visibles pour ton club, tu n'auras + pas accès à l'interface de consommations, et tu devras nécessairement cliquer sur le + lien ci-dessus pour accéder à l'interface d'édition des boutons. Une fois qu'un bouton + pour ton club est visible, l'interface consommations devient accessible. -Après passation, je suis trésorier d'un club. Comment récupérer mes droits note ? ---------------------------------------------------------------------------------- +Après passation, je suis trésorièr⋅e d'un club. Comment récupérer mes droits note ? +----------------------------------------------------------------------------------- .. note:: - Tu dois pour cela contacter les trésoriers BDE (voir ci-dessous). Ils vous - expliqueront en détails vos droits et vos interdits et vous donneront les - droits requis. + Tu dois pour cela contacter les trésorièr⋅es BDE (voir ci-dessous). Iels vous + expliqueront en détails vos droits et vos interdits et vous donneront les droits + requis. -Je souhaite contacter un trésorier ----------------------------------- +Je souhaite contacter un⋅e trésorièr⋅e +-------------------------------------- .. note:: - Pour contacter un trésorier, il te suffit d'envoyer un mail à l'adresse - `tresorerie.bde@lists.crans.org `_. Pense bien - à donner ton nom de note, voire à envoyer un scan de ta carte d'identité si ta - demande concerne un virement entre le compte du BDE et ton propre compte. + Pour contacter un⋅e trésorièr⋅e, il te suffit d'envoyer un mail à l'adresse + `tresorerie.bde@lists.crans.org `_. Pense bien à + donner ton nom de note, voire à envoyer un scan de ta carte d'identité si ta demande + concerne un virement entre le compte du BDE et ton propre compte. J'ai trouvé un bug, comment le signaler ? ----------------------------------------- .. note:: - La Note Kfet est développée bénévolement par des membres du BDE. Malgré tous nos + La Note Kfet est développée bénévolement par des membres du BDE. Nous mettons tous nos efforts pour fournir une plateforme sans erreur et la plus ergonomique possible. Toutefois, il n'est évidemment pas exclu que des bugs soient présents :) @@ -156,41 +154,44 @@ Je souhaite contribuer ---------------------- .. note:: - La Note Kfet est essentiellement développée par des responsables informatiques du - BDE de l'ENS Paris-Saclay. Toutefois, si vous souhaitez contribuer, vous pouvez - bien sûr le faire en accord avec la licence GPLv3 avec laquelle la Note Kfet est - distribuée. Pour cela, si vous êtes adhérent Crans, vous pouvez proposer une - demande de fusion de votre code. Si ce n'est pas le cas, vous pouvez envoyer un - mail à `notekfet2020@lists.crans.org `_. - Dans les deux cas, merci de rejoindre le canal ``#note`` sur IRC :) + La Note Kfet est essentiellement développée par des responsables informatiques du BDE + de l'ENS Paris-Saclay. Toutefois, si vous souhaitez contribuer, vous pouvez bien sûr + le faire en accord avec la licence GPLv3 avec laquelle la Note Kfet est distribuée. + Pour cela, si vous êtes adhérent⋅e Crans, vous pouvez proposer une demande de fusion + de votre code. Si ce n'est pas le cas, vous pouvez envoyer un mail à + `notekfet2020@lists.crans.org `_. Dans les deux cas, + merci de rejoindre le canal ``#note`` sur IRC :) -Contributeurs -------------- +Contributeur⋅rices +------------------ .. note:: La version 2020 de la Note Kfet a été développée sous le mandat de la - Saper[list]popette. Son développement a commencé à l'été 2019. Après un mois de beta - à l'été 2020, son déploiement en production s'est fait le samedi 5 septembre 2020. + Saper[list]popette. Son développement a commencé à l'été 2019. Après un mois de beta à + l'été 2020, son déploiement en production s'est fait le samedi 5 septembre 2020. Elle succède à presque 6 années de la `Note Kfet 2015 `_, alors en production depuis le 6 octobre 2014. - Liste des contributeurs majeurs, par ordre alphabétique : + Liste des contributeur⋅rices majeur⋅es, par ordre alphabétique : - * Pierre-André « PAC » COMBY - * Yohann « ÿnérant » D'ANELLO - * Benjamin « esum » GRAILLOT - * Alexandre « erdnaxe » IOOSS + * bleizi + * erdnaxe + * esum + * korenst1 + * nicomarg + * PAC + * ÿnérant Hébergement ----------- .. note:: - En accord entre de l'ENS Paris-Saclay et le Crans, l'instance de production présente - sur ``_ est hébergée sur l'un des serveurs du Crans. + En accord entre le BDE de l'ENS Paris-Saclay et le Crans, l'instance de production + présente sur ``_ est hébergée sur l'un des serveurs du Crans. Les données sont hébergées à l'adresse : .. code:: diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 77b83ab4..040c1fe9 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -1,12 +1,12 @@ La note, c'est quoi ? ===================== -La Note Kfet est un porte-monnaie virtuel proposé gratuitement à tous les adhérents BDE. -C'est le moyen de paiement privilégié au sein du campus, que ce soit pour payer des -activités du BDE, ou bien pour faire des remboursements entre amis. La note contient -également la base d'adhérents du BDE et contient de nombreux outils facilitant la vie -des différents bureaux de clubs, en particulier les trésoriers et surtout les trésoriers -BDE. +La Note Kfet est un porte-monnaie virtuel proposé gratuitement à toustes les +adhérent⋅es BDE. C'est le moyen de paiement privilégié au sein du campus, que ce soit +pour payer des activités du BDE, ou bien pour faire des remboursements entre ami⋅es. +La note contient également la base d'adhérent⋅es du BDE et contient de nombreux outils +facilitant la vie des différents bureaux de clubs, en particulier les trésorièr⋅es et +surtout les trésorièr⋅es BDE. La Note Kfet est accessible à l'adresse ``_. La version actuelle a été développée par le BDE 2020-2021, et est maintenue par les respos infos du BDE de @@ -23,7 +23,7 @@ Chaque adhérent⋅e BDE dispose d'un compte appelé « note », créé lors de Une note est associée à un solde en euros, et à un pseudo appelé « nom de note ». Le solde associé à la note correspond au solde de l'adhérent⋅e, avec lequel il est possible de payer tout ce qui est en lien avec le BDE. Le nom de note suffit à -identifier une personne, et il suffit par exemple de donner ce nom à un permanencier +identifier une personne, et il suffit par exemple de donner ce nom à un⋅e permanencièr⋅e à la Kfet pour acheter un produit. Faire une transaction @@ -34,10 +34,10 @@ d'un⋅e autre adhérent⋅e, pourvu que le montant de la transaction n'excède propre solde. Pour cela, il suffit d'aller sur la page de transferts, qui est la page par défaut après connexion : ``_ Le formulaire pour effectuer un transfert apparaît alors. En cliquant sur le bouton -« Je suis l'émetteur », le champ « Émetteurs » est directement complété avec votre -propre note. Il vous suffit alors de remplir le champ « Destinataires » avec le ou -les noms de note que vous voulez créditer, de spécifier le montant et la raison de -votre transfert, puis de cliquer sur le bouton « Virement ». Après quelques secondes, +« Je suis l'émetteur⋅rice », le champ « Émetteur⋅rices » est directement complété +avec votre propre note. Il vous suffit alors de remplir le champ « Destinataires » +avec le⋅s nom⋅s de note que vous voulez créditer, de spécifier le montant et la raison +de votre transfert, puis de cliquer sur le bouton « Virement ». Après quelques secondes, si votre solde est suffisant et que la transaction est acceptée, un message de confirmation apparaîtra et la transaction apparaîtra dans l'historique ci-dessous. @@ -49,7 +49,7 @@ Consulter ses données personnelles ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ La Note Kfet sert non seulement à faire des transactions d'argent virtuel au sein -du BDE, mais aussi à permettre au BDE et à ses clubs de gérer leurs adhérents. +du BDE, mais aussi à permettre au BDE et à ses clubs de gérer leurs adhérent⋅es. À cet effet, diverses informations personnelles sont collectées par la Note. Pour accéder à votre compte, une fois connecté⋅e, rendez-vous dans le menu en haut @@ -75,26 +75,26 @@ présentes sont : * Département * Promotion * Adresse - * Élève/étudiant + * Élève/étudiant⋅e * Inscription aux listes de diffusion du BDE, du BDA et du BDS Les trois premières informations sont obligatoires pour pouvoir vous contacter -et pouvoir tenir un registre d'adhérent. +et pouvoir tenir un registre d'adhérent⋅es. Le numéro de téléphone et l'adresse ne sont utilisés uniquement en cas d'urgence, notamment en cas de WEI, et ne sont pas des champs obligatoires. De plus, promotion, département et section ne sont utilisés sérieusement que en cas de WEI, et ne sont pas utilisés sinon. -La distinction élève/étudiant permet de distinguer les achats qui peuvent avoir -des prix différents pour les élèves et pour les étudiants, notamment en cas de +La distinction élève/étudiant⋅e permet de distinguer les achats qui peuvent avoir +des prix différents pour les élèves et pour les étudiant⋅es, notamment en cas de transferts d'argent, de WEI ou d'adhésion à certains clubs. Hormis le pseudo, toutes ces informations ont un caractère confidentiel qui les -rend privées et inaccessible aux utilisateurs. Les trésoriers BDE et les respos +rend privées et inaccessible aux utilisateur⋅rices. Les trésorièr⋅es BDE et les respos info ont accès à toutes les informations (en cas de besoin uniquement, le cas contraire est considéré comme de l'abus de droit qui est punissable), et les -trésoriers de club ont accès au nom et au prénom afin de faciliter l'association +trésorièr⋅es de club ont accès au nom et au prénom afin de faciliter l'association à un nom de note. Dans la partie informations personnelles, il est également possible de modifier @@ -106,7 +106,7 @@ de nom de note. Le tableau des adhésions actives sur la partie du haut contient l'ensemble des clubs auxquels vous êtes adhérent⋅es, avec les dates de début, de fin, votre rôle au sein du club (généralement simple membre, mais cela peut également -être trésorier⋅ère par exemple) ainsi que la cotisation que vous avez éventuellement +être trésorièr⋅e par exemple) ainsi que la cotisation que vous avez éventuellement payée au club. L'historique des transactions contient l'ensemble des transactions qui ont fait diff --git a/docs/index.rst b/docs/index.rst index 2e7a2e2c..899c8277 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,7 +3,7 @@ Documentation de la Note Kfet 2020 Bienvenue sur la documentation de la Note Kfet 2020. Cette documentation est à la fois -destinée aux adhérents BDE pour découvrir le fonctionnement de la note, mais aussi aux +destinée aux adhérent⋅es BDE pour découvrir le fonctionnement de la note, mais aussi aux respos info qui souhaitent découvrir comment fonctionne la note sous le capot. Des informations complémentaires sont également disponibles sur le `Wiki Crans `_. diff --git a/docs/install-dev.rst b/docs/install-dev.rst index d3e616b2..55aa048b 100644 --- a/docs/install-dev.rst +++ b/docs/install-dev.rst @@ -82,7 +82,7 @@ Pour cela, on peut simplement faire : $ source env/bin/activate (env) $ -À noter que ``source`` peut s'abbréger par ``.`` uniquement. +À noter que ``source`` peut s'abréger par ``.`` uniquement. Vous êtes donc dans un environnement virtuel Python. Pour installer les dépendances de la note : @@ -162,8 +162,8 @@ Ouvrez votre navigateur, tapez ``_, enjoy :) optimisé pour recevoir des requêtes en parallèle ou être utilisé en production. -Créer un super-utilisateur --------------------------- +Créer un⋅e super-utilisateur⋅rice +--------------------------------- -La commande ``./manage.py createsuperuser`` vous permettra de créer un super-utilisateur -initial. +La commande ``./manage.py createsuperuser`` vous permettra de créer un⋅e +super-utilisateur⋅rice initial⋅e. diff --git a/docs/install.rst b/docs/install.rst index b625bfae..4edf3bb0 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -15,7 +15,7 @@ version 1.11, qui n'est plus maintenue par Django depuis déjà quelques mois. Les versions stables de Django sont les versions 2.2 et 3.2, Debian Bullseye utilisant la version 2.2. -Afin de permettre à ses utilisateurs d'utiliser les dernières fonctionnalités de +Afin de permettre à ses utilisateur⋅rices d'utiliser les dernières fonctionnalités de certains paquets, Debian a créé une distribution particulière, appelée ``buster-backports``. Cette distribution contient des paquets de la distribution à venir « ``testing`` » (``bullseye`` à l'heure où cette documentation est écrite) @@ -187,7 +187,7 @@ clé sous forme de chaîne de caractère suffisamment longue (64 caractères par qui n'est pas à transmettre et qui évite d'autres sites malveillants de faire des requêtes directement sur la note. -Le champ ``CONTACT_EMAIL`` correspond l'adresse mail que les adhérent⋅e⋅s peuvent contacter +Le champ ``CONTACT_EMAIL`` correspond l'adresse mail que les adhérent⋅es peuvent contacter en cas de problème. C'est là où le champ ``Nous contacter`` redirigera. Le champ ``NOTE_URL`` correspond au nom de domaine autorisé à accéder au site. C'est également @@ -636,5 +636,5 @@ On peut enfin redémarrer le serveur Web. Les données ont bien été copiées. .. caution:: - On ne copiera **jamais** des données d'adhérent⋅e⋅s sur une machine personnelle. + On ne copiera **jamais** des données d'adhérent⋅es sur une machine personnelle. Ce type d'opération doit s'effectuer impérativement entre des serveurs du BDE. diff --git a/docs/scripts.rst b/docs/scripts.rst index 53545171..fc85468c 100644 --- a/docs/scripts.rst +++ b/docs/scripts.rst @@ -139,8 +139,8 @@ de diffusion utiles. Il prend 2 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érents - actuels (pour la liste ``adherents.bde@lists.crans.org), des clubs (pour + ``sport``, qui permet respectivement de sortir la liste des adresses mails des adhérent⋅es + actuel⋅les (pour la liste ``adherents.bde@lists.crans.org), des clubs (pour ``clubs@lists.crans.org``), des personnes à abonner à ``evenements@lists.crans.org``, à ``all.bda@lists.crans.org`` et enfin à ``bds@lists.crans.org``. * ``--lang``, qui prend en argument ``fr`` ou ``en``. N'est utile que pour la ML événements, @@ -157,8 +157,8 @@ malheureusement pas aussi simple que de simplement supposer que ces listes sont À terme, il pourrait être envisageable de synchroniser automatiquement les listes avec la note. -Suppression d'un utilisateur ----------------------------- +Suppression d'un⋅e utilisateur⋅rice +----------------------------------- Le script s'appelle ``force_delete_user``. @@ -167,7 +167,7 @@ Le script s'appelle ``force_delete_user``. Ce script est dangereux. À n'utiliser qu'avec de très grosses pincettes si vous savez ce que vous faites. Seul cas d'usage pour l'instant recensé : supprimer des comptes en double qui se sont malencontreusement retrouvés validés pour raison de Sogé et de mauvaise - communication au sein des trésorier⋅ère⋅s. + communication au sein des trésorièr⋅es. Il n'est certainement pas prévu de supprimer des vrais comptes existants via ce script. On ne supprime pas l'historique. Si jamais quelqu'un demanderait à supprimer son compte, @@ -175,8 +175,8 @@ Le script s'appelle ``force_delete_user``. Ce script est utile lorsqu'il faut supprimer un compte créer par erreur. Tant que la validation n'est pas faite, il suffit en général de cliquer sur le bouton « Supprimer le compte » sur -l'interface de validation. Cela supprimera l'utilisateur et le profil associé, sans toucher -à une quelconque note puisqu'elle ne sera pas créée. +l'interface de validation. Cela supprimera l'utilisateur⋅rice et le profil associé, sans +toucher à une quelconque note puisqu'elle ne sera pas créée. Ce script supprime donc un compte ainsi que toutes les données associées (note, alias, transactions). Il n'est donc pas à prendre à la légère, et vous devez savoir ce que vous @@ -192,8 +192,8 @@ pour supprimer tout ce qu'il faut, et une validation manuelle sera requise pour la suppression. L'option ``--doit`` évite cette confirmation manuelle. **Vous n'avez jamais à utiliser cette option en théorie.** -À la fin du processus, un mail est envoyé aux administrateurs pour les prévenir des -élements supprimés. +À la fin du processus, un mail est envoyé aux administrateur⋅rices pour les prévenir des +éléments supprimés. Des données réelles jamais tu ne supprimeras. @@ -210,18 +210,18 @@ la Note Kfet 2015. s'est déroulé. -Ajouter un super-utilisateur ----------------------------- +Ajouter un⋅e super-utilisateur⋅rice +----------------------------------- Le script s'appelle ``make_su``. Il prend en argument un pseudo. -Avec l'option ``--SUPER, -S``, la personne avec ce pseudo devient super-utilisateur, +Avec l'option ``--SUPER, -S``, la personne avec ce pseudo devient super-utilisateur⋅rice, et obtiens donc les pleins pouvoirs sur la note. À ne donner qu'aux respos info. Avec l'option ``--STAFF, -s``, la personne avec ce pseudo acquiert le statut équipe, -et obtiens l'accès à django-admin. À ne donner qu'aux respos info. +et obtient l'accès à django-admin. À ne donner qu'aux respos info. Rafraîchissement des activités @@ -264,20 +264,20 @@ Envoi des rappels de négatif Le script s'appelle ``send_mail_to_negative_balances``. -Il sert à rappeler aux adhérent⋅e⋅s et clubs en négatif qu'ils le sont, mais également -à envoyer aux trésorier⋅ère⋅s et respos info la liste des adhérent⋅e⋅s en négatif. +Il sert à rappeler aux adhérent⋅es et clubs en négatif qui le sont, mais également +à envoyer aux trésorièr⋅es et respos info la liste des adhérent⋅es en négatif. Il prend les options suivantes : * ``--spam, -s`` : envoie à chaque adhérent⋅e en négatif un rappel par mail pour recharger -* ``--report, -r`` : envoie le rapport aux trésorier⋅ère⋅s et respos info +* ``--report, -r`` : envoie le rapport aux trésorièr⋅es et respos info * ``--negative-amount,-n`` : définit le solde maximal en-dessous duquel les notes apparaitront sur le rapport / seront spammées -* ``--add-years, -y`` : ajoute également les adhérent⋅e⋅s des ``n`` dernières années +* ``--add-years, -y`` : ajoute également les adhérent⋅es des ``n`` dernières années -Ce script est appelé tous les mardis à 5h00 pour spammer les utilisateur⋅rice⋅s en -négatif et tous les 6 du mois pour envoyer le rapport des notes d'adhérent⋅e⋅s ou de -vieux/vieilles adhérent⋅e⋅s de moins d'un an sous -10 €. +Ce script est appelé tous les mardis à 5h00 pour spammer les utilisateur⋅ices en +négatif et tous les 6 du mois pour envoyer le rapport des notes d'adhérent⋅es ou de +vieilleux adhérent⋅es de moins d'un an sous -10 €. Envoi des rapports @@ -285,8 +285,8 @@ Envoi des rapports Le script s'appelle ``send_reports``. -Les utilisateurs ont la possibilité de recevoir sur demande un rapport à la fréquence de -leur choix (en jours) des transactions effectuées sur leur note. +Les utilisateur⋅rices ont la possibilité de recevoir sur demande un rapport à la +fréquence de leur choix (en jours) des transactions effectuées sur leur note. Le script prend 2 options : diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 9ce95379..055b0093 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-03-22 00:33+0100\n" +"POT-Creation-Date: 2024-08-17 11:57+0200\n" "PO-Revision-Date: 2020-11-16 20:02+0000\n" "Last-Translator: bleizi \n" "Language-Team: German \n" @@ -18,8 +18,8 @@ msgstr "" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.3.2\n" -#: apps/activity/apps.py:10 apps/activity/models.py:127 -#: apps/activity/models.py:167 +#: apps/activity/apps.py:10 apps/activity/models.py:129 +#: apps/activity/models.py:169 msgid "activity" msgstr "Veranstaltung" @@ -27,33 +27,33 @@ msgstr "Veranstaltung" msgid "The note of this club is inactive." msgstr "" -#: apps/activity/forms.py:41 apps/activity/models.py:140 +#: apps/activity/forms.py:41 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:269 +#: apps/activity/forms.py:82 apps/activity/models.py:271 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:272 +#: apps/activity/forms.py:85 apps/activity/models.py:274 msgid "This activity is not validated yet." msgstr "Diese Veranstaltung ist noch nicht bestätigt." -#: apps/activity/forms.py:95 apps/activity/models.py:280 +#: apps/activity/forms.py:95 apps/activity/models.py:282 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:284 +#: apps/activity/forms.py:99 apps/activity/models.py:286 msgid "This person is already invited." msgstr "Diese Person wurde schon eingeladen." -#: apps/activity/forms.py:103 apps/activity/models.py:288 +#: apps/activity/forms.py:103 apps/activity/models.py:290 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/member/models.py:203 +#: 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/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 @@ -99,121 +99,121 @@ msgstr "Vearnstaltungarte" msgid "description" msgstr "Beschreibung" -#: apps/activity/models.py:72 +#: apps/activity/models.py:74 msgid "location" msgstr "Ort" -#: apps/activity/models.py:76 +#: apps/activity/models.py:78 msgid "Place where the activity is organized, eg. Kfet." msgstr "Wo findet die Veranstaltung statt ? (z.B Kfet)." -#: apps/activity/models.py:83 +#: apps/activity/models.py:85 #: apps/activity/templates/activity/includes/activity_info.html:22 #: apps/note/models/notes.py:207 apps/note/models/transactions.py:67 #: apps/permission/models.py:163 msgid "type" msgstr "Type" -#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:313 +#: 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/wei/models.py:171 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/templates/wei/survey.html:15 msgid "user" msgstr "User" -#: apps/activity/models.py:96 +#: apps/activity/models.py:98 #: apps/activity/templates/activity/includes/activity_info.html:36 msgid "organizer" msgstr "Veranstalter" -#: apps/activity/models.py:97 +#: apps/activity/models.py:99 msgid "Club that organizes the activity. The entry fees will go to this club." msgstr "" "Die Veranstaltung wurde von einem Club organisert. Die Eintrittsbeiträge " "gehen an diesen Club." -#: apps/activity/models.py:104 +#: apps/activity/models.py:106 #: apps/activity/templates/activity/includes/activity_info.html:39 msgid "attendees club" msgstr "Teilnehmer" -#: apps/activity/models.py:105 +#: apps/activity/models.py:107 msgid "Club that is authorized to join the activity. Mostly the Kfet club." msgstr "Club die an die Veranstaltung teilnehmen können." -#: apps/activity/models.py:109 +#: apps/activity/models.py:111 #: apps/activity/templates/activity/includes/activity_info.html:25 msgid "start date" msgstr "Anfangsdatum" -#: apps/activity/models.py:113 +#: apps/activity/models.py:115 #: apps/activity/templates/activity/includes/activity_info.html:28 msgid "end date" msgstr "Abschlussdatum" -#: apps/activity/models.py:118 +#: apps/activity/models.py:120 #: apps/activity/templates/activity/includes/activity_info.html:50 #: apps/note/models/transactions.py:149 msgid "valid" msgstr "gültig" -#: apps/activity/models.py:123 +#: apps/activity/models.py:125 #: apps/activity/templates/activity/includes/activity_info.html:65 msgid "open" msgstr "geöffnet" -#: apps/activity/models.py:128 +#: apps/activity/models.py:130 msgid "activities" msgstr "Veranstaltungen" -#: apps/activity/models.py:172 +#: apps/activity/models.py:174 msgid "entry time" msgstr "Eintrittzeit" -#: apps/activity/models.py:178 apps/note/apps.py:14 +#: apps/activity/models.py:180 apps/note/apps.py:14 #: apps/note/models/notes.py:77 msgid "note" msgstr "Note" -#: apps/activity/models.py:189 +#: apps/activity/models.py:191 #: apps/activity/templates/activity/activity_entry.html:46 msgid "entry" msgstr "Eintritt" -#: apps/activity/models.py:190 +#: apps/activity/models.py:192 #: apps/activity/templates/activity/activity_entry.html:46 msgid "entries" msgstr "Eintritte" -#: apps/activity/models.py:193 +#: apps/activity/models.py:195 #, python-brace-format msgid "Entry for {guest}, invited by {note} to the activity {activity}" msgstr "Eintritt für {guest}, von {note} zur Vanstaltung {activity} eingeladen" -#: apps/activity/models.py:195 +#: apps/activity/models.py:197 #, python-brace-format msgid "Entry for {note} to the activity {activity}" msgstr "Eintritt von {note} zur Veranstaltung {activity}" -#: apps/activity/models.py:202 +#: apps/activity/models.py:204 msgid "Already entered on " msgstr "Schon eingetretten " -#: apps/activity/models.py:202 apps/activity/tables.py:56 +#: apps/activity/models.py:204 apps/activity/tables.py:56 msgid "{:%Y-%m-%d %H:%M:%S}" msgstr "{:%Y-%m-%d %H:%M:%S}" -#: apps/activity/models.py:210 +#: apps/activity/models.py:212 msgid "The balance is negative." msgstr "Kontostand ist im Rot." -#: apps/activity/models.py:240 +#: apps/activity/models.py:242 #: 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:245 +#: apps/activity/models.py:247 #: 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,19 +222,19 @@ msgstr "Nachname" msgid "first name" msgstr "Vorname" -#: apps/activity/models.py:252 +#: apps/activity/models.py:254 msgid "inviter" msgstr "Einlader" -#: apps/activity/models.py:256 +#: apps/activity/models.py:258 msgid "guest" msgstr "Gast" -#: apps/activity/models.py:257 +#: apps/activity/models.py:259 msgid "guests" msgstr "Gäste" -#: apps/activity/models.py:310 +#: apps/activity/models.py:312 msgid "Invitation" msgstr "Einladung" @@ -246,7 +246,9 @@ msgstr "Die Veranstaltung ist geöffnet." msgid "The validation of the activity is pending." msgstr "Diese Veranstaltung ist noch nicht bestätigt." -#: apps/activity/tables.py:43 apps/treasury/tables.py:107 +#: apps/activity/tables.py:43 +#: apps/member/templates/member/picture_update.html:18 +#: apps/treasury/tables.py:107 msgid "Remove" msgstr "Entfernen" @@ -262,15 +264,15 @@ msgstr "entfernen" msgid "Type" msgstr "Type" -#: apps/activity/tables.py:84 apps/member/forms.py:193 -#: apps/registration/forms.py:92 apps/treasury/forms.py:131 +#: apps/activity/tables.py:84 apps/member/forms.py:196 +#: apps/registration/forms.py:91 apps/treasury/forms.py:131 #: apps/wei/forms/registration.py:104 msgid "Last name" msgstr "Nachname" -#: apps/activity/tables.py:86 apps/member/forms.py:198 +#: apps/activity/tables.py:86 apps/member/forms.py:201 #: apps/note/templates/note/transaction_form.html:138 -#: apps/registration/forms.py:97 apps/treasury/forms.py:133 +#: apps/registration/forms.py:96 apps/treasury/forms.py:133 #: apps/wei/forms/registration.py:109 msgid "First name" msgstr "Vorname" @@ -297,7 +299,7 @@ msgstr "Gastliste" #: apps/note/models/transactions.py:261 #: apps/note/templates/note/transaction_form.html:17 #: apps/note/templates/note/transaction_form.html:152 -#: note_kfet/templates/base.html:72 +#: note_kfet/templates/base.html:78 msgid "Transfer" msgstr "Überweisen" @@ -334,6 +336,10 @@ 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/member/templates/member/add_members.html:46 #: apps/member/templates/member/club_form.html:16 #: apps/note/templates/note/transactiontemplate_form.html:18 @@ -399,39 +405,39 @@ msgstr "bearbeiten" msgid "Invite" msgstr "Einladen" -#: apps/activity/views.py:36 +#: apps/activity/views.py:37 msgid "Create new activity" msgstr "Neue Veranstaltung schaffen" -#: apps/activity/views.py:67 note_kfet/templates/base.html:90 +#: apps/activity/views.py:67 note_kfet/templates/base.html:96 msgid "Activities" msgstr "Veranstaltungen" -#: apps/activity/views.py:93 +#: apps/activity/views.py:108 msgid "Activity detail" msgstr "Veranstaltunginfo" -#: apps/activity/views.py:113 +#: apps/activity/views.py:128 msgid "Update activity" msgstr "Veranstaltung bearbeiten" -#: apps/activity/views.py:140 +#: apps/activity/views.py:155 msgid "Invite guest to the activity \"{}\"" msgstr "Gast zur Veranstaltung \"{}\" einladen" -#: apps/activity/views.py:178 +#: apps/activity/views.py:193 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:181 +#: apps/activity/views.py:196 msgid "This activity does not support activity entries." msgstr "Diese Veranstaltung braucht nicht Eintritt." -#: apps/activity/views.py:184 +#: apps/activity/views.py:199 msgid "This activity is closed." msgstr "Diese Veranstaltung ist geschlossen." -#: apps/activity/views.py:280 +#: apps/activity/views.py:295 msgid "Entry for activity \"{}\"" msgstr "Eintritt zur Veranstaltung \"{}\"" @@ -439,6 +445,298 @@ msgstr "Eintritt zur Veranstaltung \"{}\"" msgid "API" msgstr "API" +#: apps/food/apps.py:11 apps/food/models.py:105 +msgid "food" +msgstr "" + +#: apps/food/forms.py:32 +msgid "Fully used" +msgstr "" + +#: apps/food/forms.py:50 +msgid "Pasta METRO 5kg" +msgstr "" + +#: apps/food/forms.py:96 +msgid "Lasagna" +msgstr "" + +#: apps/food/models.py:18 +#, fuzzy +#| msgid "phone 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 +msgid "Allergen" +msgstr "" + +#: apps/food/models.py:48 apps/food/templates/food/basicfood_detail.html:17 +#: apps/food/templates/food/transformedfood_detail.html:20 +msgid "Allergens" +msgstr "" + +#: apps/food/models.py:64 +msgid "owner" +msgstr "" + +#: apps/food/models.py:70 +msgid "allergen" +msgstr "" + +#: apps/food/models.py:74 +#, fuzzy +#| msgid "birth date" +msgid "expiry date" +msgstr "Geburtsdatum" + +#: apps/food/models.py:80 +msgid "was eaten" +msgstr "" + +#: apps/food/models.py:89 +msgid "is ready" +msgstr "" + +#: apps/food/models.py:94 +#, fuzzy +#| msgid "active" +msgid "is active" +msgstr "Aktiv" + +#: apps/food/models.py:106 +msgid "foods" +msgstr "" + +#: apps/food/models.py:122 +#, fuzzy +#| msgid "start date" +msgid "arrival date" +msgstr "Anfangsdatum" + +#: apps/food/models.py:152 +msgid "Basic food" +msgstr "" + +#: apps/food/models.py:153 +msgid "Basic foods" +msgstr "" + +#: apps/food/models.py:161 +#, fuzzy +#| msgid "created at" +msgid "creation date" +msgstr "erschafft am" + +#: apps/food/models.py:169 +msgid "transformed ingredient" +msgstr "" + +#: apps/food/models.py:174 +msgid "shelf life" +msgstr "" + +#: apps/food/models.py:225 apps/food/views.py:365 +#, fuzzy +#| msgid "Transfer money" +msgid "Transformed food" +msgstr "Geld überweisen" + +#: apps/food/models.py:226 +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 +#, fuzzy +#| msgid "Owned" +msgid "Owner" +msgstr "Besetzt" + +#: 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" +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 +#, 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 +#, fuzzy +#| msgid "Add team" +msgid "Add to a meal" +msgstr "Neue Team" + +#: apps/food/templates/food/create_qrcode_form.html:14 +#, fuzzy +#| msgid "Transfer money" +msgid "New basic food" +msgstr "Geld überweisen" + +#: apps/food/templates/food/qrcode_detail.html:10 +#, fuzzy +#| msgid "phone number" +msgid "number" +msgstr "Telefonnummer" + +#: 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 +msgid "Meal served" +msgstr "" + +#: apps/food/templates/food/transformedfood_list.html:16 +#, fuzzy +#| msgid "New user" +msgid "New meal" +msgstr "Neue User" + +#: apps/food/templates/food/transformedfood_list.html:25 +#, 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" +msgstr "" + +#: apps/food/templates/food/transformedfood_list.html:40 +#, fuzzy +#| msgid "There is no results." +msgid "There is no free meal." +msgstr "Es gibt keine Ergebnisse." + +#: apps/food/templates/food/transformedfood_list.html:48 +msgid "All meals" +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" +msgstr "" + +#: apps/food/views.py:42 +#, fuzzy +#| msgid "This credit is already validated." +msgid "The product is already prepared" +msgstr "Dieser Kredit ist bereits validiert." + +#: apps/food/views.py:70 +#, fuzzy +#| msgid "Update an invoice" +msgid "Update an aliment" +msgstr "Rechnung bearbeiten" + +#: apps/food/views.py:97 +#, 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: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/logs/apps.py:11 msgid "Logs" msgstr "Logs" @@ -508,11 +806,11 @@ msgstr "Mitgliedschaftpreis (bezahlte Studenten)" msgid "membership fee (unpaid students)" msgstr "Mitgliedschaftpreis (unbezahlte Studenten)" -#: apps/member/admin.py:65 apps/member/models.py:325 +#: apps/member/admin.py:65 apps/member/models.py:330 msgid "roles" msgstr "Rollen" -#: apps/member/admin.py:66 apps/member/models.py:339 +#: apps/member/admin.py:66 apps/member/models.py:344 msgid "fee" msgstr "Preis" @@ -565,49 +863,49 @@ msgstr "Maximal Größe: 2MB" msgid "This image cannot be loaded." msgstr "Dieses Bild kann nicht geladen werden." -#: apps/member/forms.py:148 apps/member/views.py:102 -#: apps/registration/forms.py:34 apps/registration/views.py:266 +#: apps/member/forms.py:151 apps/member/views.py:102 +#: apps/registration/forms.py:33 apps/registration/views.py:276 msgid "An alias with a similar name already exists." msgstr "Ein ähnliches Alias ist schon benutzt." -#: apps/member/forms.py:172 +#: apps/member/forms.py:175 msgid "Inscription paid by Société Générale" msgstr "Mitgliedschaft von der Société Générale bezahlt" -#: apps/member/forms.py:174 +#: apps/member/forms.py:177 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:179 apps/registration/forms.py:79 +#: apps/member/forms.py:182 apps/registration/forms.py:78 #: apps/wei/forms/registration.py:91 msgid "Credit type" msgstr "Kredittype" -#: apps/member/forms.py:180 apps/registration/forms.py:80 +#: apps/member/forms.py:183 apps/registration/forms.py:79 #: apps/wei/forms/registration.py:92 msgid "No credit" msgstr "Kein Kredit" -#: apps/member/forms.py:182 +#: apps/member/forms.py:185 msgid "You can credit the note of the user." msgstr "Sie dûrfen diese Note kreditieren." -#: apps/member/forms.py:186 apps/registration/forms.py:85 +#: apps/member/forms.py:189 apps/registration/forms.py:84 #: apps/wei/forms/registration.py:97 msgid "Credit amount" msgstr "Kreditanzahl" -#: apps/member/forms.py:203 apps/note/templates/note/transaction_form.html:144 -#: apps/registration/forms.py:102 apps/treasury/forms.py:135 +#: apps/member/forms.py:206 apps/note/templates/note/transaction_form.html:144 +#: apps/registration/forms.py:101 apps/treasury/forms.py:135 #: apps/wei/forms/registration.py:114 msgid "Bank" msgstr "Bank" -#: apps/member/forms.py:230 +#: apps/member/forms.py:233 msgid "User" msgstr "User" -#: apps/member/forms.py:244 +#: apps/member/forms.py:247 msgid "Roles" msgstr "Rollen" @@ -857,46 +1155,52 @@ msgid "Maximal date of a membership, after which members must renew it." msgstr "" "Maximales Datum einer Mitgliedschaft, nach dem Mitglieder es erneuern müssen." -#: apps/member/models.py:263 apps/member/models.py:319 +#: apps/member/models.py:263 +#, fuzzy +#| msgid "Validate registration" +msgid "add to registration form" +msgstr "Registrierung validieren" + +#: apps/member/models.py:268 apps/member/models.py:324 #: apps/note/models/notes.py:176 msgid "club" msgstr "Club" -#: apps/member/models.py:264 +#: apps/member/models.py:269 msgid "clubs" msgstr "Clubs" -#: apps/member/models.py:330 +#: apps/member/models.py:335 msgid "membership starts on" msgstr "Mitgliedschaft fängt an" -#: apps/member/models.py:334 +#: apps/member/models.py:339 msgid "membership ends on" msgstr "Mitgliedschaft endet am" -#: apps/member/models.py:343 apps/note/models/transactions.py:385 +#: apps/member/models.py:348 apps/note/models/transactions.py:385 msgid "membership" msgstr "Mitgliedschaft" -#: apps/member/models.py:344 +#: apps/member/models.py:349 msgid "memberships" msgstr "Mitgliedschaften" -#: apps/member/models.py:348 +#: apps/member/models.py:353 #, python-brace-format msgid "Membership of {user} for the club {club}" msgstr "Mitgliedschaft von {user} für das Club {club}" -#: apps/member/models.py:367 +#: apps/member/models.py:372 #, 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:376 apps/member/views.py:712 +#: apps/member/models.py:381 apps/member/views.py:715 msgid "User is already a member of the club" msgstr "User ist schon ein Mitglied dieser club" -#: apps/member/models.py:388 apps/member/views.py:721 +#: apps/member/models.py:393 apps/member/views.py:724 msgid "User is not a member of the parent club" msgstr "User ist noch nicht Mitglied des Urclubs" @@ -1013,7 +1317,7 @@ msgstr "" #: 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:517 +#: apps/member/views.py:520 msgid "Note aliases" msgstr "Note Aliases" @@ -1169,11 +1473,11 @@ msgstr "" msgid "Show my applications" msgstr "" -#: apps/member/templates/member/picture_update.html:35 +#: apps/member/templates/member/picture_update.html:38 msgid "Nevermind" msgstr "Vergessen" -#: apps/member/templates/member/picture_update.html:36 +#: apps/member/templates/member/picture_update.html:39 msgid "Crop and upload" msgstr "Beschneiden und hochladen" @@ -1218,7 +1522,7 @@ msgstr "Speichern" msgid "Registrations" msgstr "Anmeldung" -#: apps/member/views.py:72 apps/registration/forms.py:24 +#: apps/member/views.py:72 apps/registration/forms.py:23 msgid "This address must be valid." msgstr "Diese Adresse muss gültig sein." @@ -1238,31 +1542,31 @@ msgstr "" msgid "Update note picture" msgstr "Notebild ändern" -#: apps/member/views.py:354 +#: apps/member/views.py:357 msgid "Manage auth token" msgstr "Auth token bearbeiten" -#: apps/member/views.py:381 +#: apps/member/views.py:384 msgid "Create new club" msgstr "Neue Club" -#: apps/member/views.py:400 +#: apps/member/views.py:403 msgid "Search club" msgstr "Club finden" -#: apps/member/views.py:433 +#: apps/member/views.py:436 msgid "Club detail" msgstr "Club Details" -#: apps/member/views.py:540 +#: apps/member/views.py:543 msgid "Update club" msgstr "Club bearbeiten" -#: apps/member/views.py:574 +#: apps/member/views.py:577 msgid "Add new member to the club" msgstr "Neue Mitglieder" -#: apps/member/views.py:703 apps/wei/views.py:973 +#: apps/member/views.py:706 apps/wei/views.py:973 msgid "" "This user don't have enough money to join this club, and can't have a " "negative balance." @@ -1270,19 +1574,19 @@ msgstr "" "Diese User hat nicht genug Geld um Mitglied zu werden, und darf nich im Rot " "sein." -#: apps/member/views.py:725 +#: apps/member/views.py:728 msgid "The membership must start after {:%m-%d-%Y}." msgstr "Die Mitgliedschaft muss nach {:%m-%d-Y} anfängen." -#: apps/member/views.py:730 +#: apps/member/views.py:733 msgid "The membership must begin before {:%m-%d-%Y}." msgstr "Die Mitgliedschaft muss vor {:%m-%d-Y} anfängen." -#: apps/member/views.py:880 +#: apps/member/views.py:883 msgid "Manage roles of an user in the club" msgstr "Rollen in diesen Club bearbeiten" -#: apps/member/views.py:905 +#: apps/member/views.py:908 msgid "Members of the club" msgstr "Mitlglieder dieses Club" @@ -1446,8 +1750,10 @@ msgid "trusted" msgstr "" #: apps/note/models/notes.py:243 -msgid "frienship" -msgstr "" +#, fuzzy +#| msgid "Manage aliases" +msgid "friendship" +msgstr "Aliases bearbeiten" #: apps/note/models/notes.py:248 #, python-brace-format @@ -1745,11 +2051,6 @@ msgstr "Aktion" msgid "Amount" msgstr "Anzahl" -#: apps/note/templates/note/transaction_form.html:132 -#: apps/treasury/models.py:60 -msgid "Name" -msgstr "Name" - #: apps/note/templates/note/transaction_form.html:177 msgid "Select emitter" msgstr "Sender auswählen" @@ -2027,7 +2328,7 @@ 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:108 +#: apps/permission/views.py:112 note_kfet/templates/base.html:114 msgid "Rights" msgstr "Rechten" @@ -2039,15 +2340,15 @@ msgstr "Alle Rechten" msgid "registration" msgstr "Anmeldung" -#: apps/registration/forms.py:40 +#: apps/registration/forms.py:39 msgid "This email address is already used." msgstr "Diese email adresse ist schon benutzt." -#: apps/registration/forms.py:60 +#: apps/registration/forms.py:59 msgid "Register to the WEI" msgstr "Zu WEI anmelden" -#: apps/registration/forms.py:62 +#: apps/registration/forms.py:61 msgid "" "Check this case if you want to register to the WEI. If you hesitate, you " "will be able to register later, after validating your account in the Kfet." @@ -2056,11 +2357,11 @@ msgstr "" "falls Zweifel, können Sie sich später nach Bestätigung Ihres Kontos im Kfet " "registrieren." -#: apps/registration/forms.py:107 +#: apps/registration/forms.py:106 msgid "Join BDE Club" msgstr "BDE Mitglieder werden" -#: apps/registration/forms.py:114 +#: apps/registration/forms.py:113 msgid "Join Kfet Club" msgstr "Kfet Mitglieder werden" @@ -2171,58 +2472,64 @@ msgstr "Danke" msgid "The Note Kfet team." msgstr "Die NoteKfet Team." -#: apps/registration/views.py:41 +#: apps/registration/views.py:42 msgid "Register new user" msgstr "Neuen User registrieren" -#: apps/registration/views.py:99 +#: apps/registration/views.py:100 msgid "Email validation" msgstr "Email validierung" -#: apps/registration/views.py:101 +#: apps/registration/views.py:102 msgid "Validate email" msgstr "Email validieren" -#: apps/registration/views.py:145 +#: apps/registration/views.py:146 msgid "Email validation unsuccessful" msgstr "Email validierung unerfolgreich" -#: apps/registration/views.py:156 +#: apps/registration/views.py:157 msgid "Email validation email sent" msgstr "Validierungsemail wurde gesendet" -#: apps/registration/views.py:164 +#: apps/registration/views.py:165 msgid "Resend email validation link" msgstr "E-Mail-Validierungslink erneut senden" -#: apps/registration/views.py:182 +#: apps/registration/views.py:183 msgid "Pre-registered users list" msgstr "Vorregistrierte Userliste" -#: apps/registration/views.py:206 +#: apps/registration/views.py:207 msgid "Unregistered users" msgstr "Unregistrierte Users" -#: apps/registration/views.py:219 +#: apps/registration/views.py:220 msgid "Registration detail" msgstr "Registrierung Detailen" -#: apps/registration/views.py:293 +#: apps/registration/views.py:256 +#, 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." msgstr "Sie müssen die BDE beitreten." -#: apps/registration/views.py:323 +#: apps/registration/views.py:330 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:417 +#: apps/registration/views.py:425 msgid "Invalidate pre-registration" msgstr "Ungültige Vorregistrierung" -#: apps/treasury/apps.py:12 note_kfet/templates/base.html:96 +#: apps/treasury/apps.py:12 note_kfet/templates/base.html:102 msgid "Treasury" msgstr "Quaestor" @@ -2648,7 +2955,7 @@ 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 -#: note_kfet/templates/base.html:102 +#: note_kfet/templates/base.html:108 msgid "WEI" msgstr "WEI" @@ -3282,19 +3589,19 @@ msgstr "" msgid "Attribute bus" msgstr "" -#: note_kfet/settings/base.py:172 +#: note_kfet/settings/base.py:173 msgid "German" msgstr "Deutsch" -#: note_kfet/settings/base.py:173 +#: note_kfet/settings/base.py:174 msgid "English" msgstr "English" -#: note_kfet/settings/base.py:174 +#: note_kfet/settings/base.py:175 msgid "Spanish" msgstr "Spanisch" -#: note_kfet/settings/base.py:175 +#: note_kfet/settings/base.py:176 msgid "French" msgstr "Französich" @@ -3358,34 +3665,38 @@ msgstr "Reset" msgid "The ENS Paris-Saclay BDE note." msgstr "Die BDE ENS-Paris-Saclay Note." -#: note_kfet/templates/base.html:78 +#: note_kfet/templates/base.html:72 +msgid "Food" +msgstr "" + +#: note_kfet/templates/base.html:84 msgid "Users" msgstr "Users" -#: note_kfet/templates/base.html:84 +#: note_kfet/templates/base.html:90 msgid "Clubs" msgstr "Clubs" -#: note_kfet/templates/base.html:113 +#: note_kfet/templates/base.html:119 msgid "Admin" msgstr "Admin" -#: note_kfet/templates/base.html:127 +#: note_kfet/templates/base.html:133 msgid "My account" msgstr "Mein Konto" -#: note_kfet/templates/base.html:130 +#: note_kfet/templates/base.html:136 msgid "Log out" msgstr "Abmelden" -#: note_kfet/templates/base.html:138 +#: note_kfet/templates/base.html:144 #: 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:145 +#: note_kfet/templates/base.html:151 #: note_kfet/templates/registration/login.html:6 #: note_kfet/templates/registration/login.html:15 #: note_kfet/templates/registration/login.html:38 @@ -3393,13 +3704,13 @@ msgstr "Registrieren" msgid "Log in" msgstr "Anmelden" -#: note_kfet/templates/base.html:159 +#: note_kfet/templates/base.html:165 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:165 +#: note_kfet/templates/base.html:171 msgid "" "Your e-mail address is not validated. Please check your mail inbox and click " "on the validation link." @@ -3407,7 +3718,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:171 +#: note_kfet/templates/base.html:177 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 " @@ -3416,21 +3727,25 @@ msgid "" "creation." msgstr "" -#: note_kfet/templates/base.html:194 +#: note_kfet/templates/base.html:200 msgid "Contact us" msgstr "Kontakt" -#: note_kfet/templates/base.html:196 +#: note_kfet/templates/base.html:202 msgid "Technical Support" msgstr "" -#: note_kfet/templates/base.html:198 +#: note_kfet/templates/base.html:204 +msgid "Charte Info (FR)" +msgstr "" + +#: note_kfet/templates/base.html:206 msgid "FAQ (FR)" msgstr "FAQ (FR)" #: note_kfet/templates/base_search.html:15 -msgid "Search by attribute such as name…" -msgstr "Suche nach Attributen wie Name…" +msgid "Search by attribute such as name..." +msgstr "Suche nach Attributen wie Name..." #: note_kfet/templates/base_search.html:23 msgid "There is no results." @@ -3675,6 +3990,298 @@ msgstr "" "müssen Ihre E-Mail-Adresse auch überprüfen, indem Sie dem Link folgen, den " "Sie erhalten haben." +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid color." +#~ msgstr "Email validierung" + +#, fuzzy +#~| msgid "Email validation" +#~ msgid "Enter a valid value." +#~ msgstr "Email validierung" + +#, 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 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 +#~| 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" +#~ msgstr "WEI Registrierung" + #~ msgid "Join BDA Club" #~ msgstr "BDA Mitglieder werden" diff --git a/locale/de/LC_MESSAGES/djangojs.po b/locale/de/LC_MESSAGES/djangojs.po index 35648a6e..f8e4ea95 100644 --- a/locale/de/LC_MESSAGES/djangojs.po +++ b/locale/de/LC_MESSAGES/djangojs.po @@ -9,9 +9,9 @@ msgstr "" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-10-07 09:07+0200\n" "PO-Revision-Date: 2020-11-16 20:21+0000\n" -"Last-Translator: Yohann D'ANELLO \n" -"Language-Team: German \n" +"Last-Translator: Emmy D'ANELLO \n" +"Language-Team: German " +"\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" diff --git a/locale/es/LC_MESSAGES/django.po b/locale/es/LC_MESSAGES/django.po index a5482763..6dcf73a1 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-03-22 00:33+0100\n" +"POT-Creation-Date: 2024-08-17 11:57+0200\n" "PO-Revision-Date: 2022-04-11 23:12+0200\n" "Last-Translator: bleizi \n" "Language-Team: \n" @@ -18,8 +18,8 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" "X-Generator: Poedit 3.0.1\n" -#: apps/activity/apps.py:10 apps/activity/models.py:127 -#: apps/activity/models.py:167 +#: apps/activity/apps.py:10 apps/activity/models.py:129 +#: apps/activity/models.py:169 msgid "activity" msgstr "actividad" @@ -27,32 +27,32 @@ msgstr "actividad" msgid "The note of this club is inactive." msgstr "La note del club está inactiva." -#: apps/activity/forms.py:41 apps/activity/models.py:140 +#: apps/activity/forms.py:41 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:269 +#: apps/activity/forms.py:82 apps/activity/models.py:271 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:272 +#: apps/activity/forms.py:85 apps/activity/models.py:274 msgid "This activity is not validated yet." msgstr "Esta actividad no fue validada por ahora." -#: apps/activity/forms.py:95 apps/activity/models.py:280 +#: apps/activity/forms.py:95 apps/activity/models.py:282 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:284 +#: apps/activity/forms.py:99 apps/activity/models.py:286 msgid "This person is already invited." msgstr "Esta persona ya está invitada." -#: apps/activity/forms.py:103 apps/activity/models.py:288 +#: apps/activity/forms.py:103 apps/activity/models.py:290 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/member/models.py:203 +#: 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/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 @@ -98,121 +98,121 @@ msgstr "tipos de actividad" msgid "description" msgstr "descripción" -#: apps/activity/models.py:72 +#: apps/activity/models.py:74 msgid "location" msgstr "ubicación" -#: apps/activity/models.py:76 +#: apps/activity/models.py:78 msgid "Place where the activity is organized, eg. Kfet." msgstr "Lugar donde se organiza la actividad, por ejemplo la Kfet." -#: apps/activity/models.py:83 +#: apps/activity/models.py:85 #: apps/activity/templates/activity/includes/activity_info.html:22 #: apps/note/models/notes.py:207 apps/note/models/transactions.py:67 #: apps/permission/models.py:163 msgid "type" msgstr "tipo" -#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:313 +#: 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/wei/models.py:171 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/templates/wei/survey.html:15 msgid "user" msgstr "usuario" -#: apps/activity/models.py:96 +#: apps/activity/models.py:98 #: apps/activity/templates/activity/includes/activity_info.html:36 msgid "organizer" msgstr "organizador" -#: apps/activity/models.py:97 +#: apps/activity/models.py:99 msgid "Club that organizes the activity. The entry fees will go to this club." msgstr "" "El club que organiza la actividad. Los pagos de entrada serán dados a este " "club." -#: apps/activity/models.py:104 +#: apps/activity/models.py:106 #: apps/activity/templates/activity/includes/activity_info.html:39 msgid "attendees club" msgstr "club esperado" -#: apps/activity/models.py:105 +#: apps/activity/models.py:107 msgid "Club that is authorized to join the activity. Mostly the Kfet club." msgstr "Club permitido unirse a la actividad. Generalmente el club Kfet." -#: apps/activity/models.py:109 +#: apps/activity/models.py:111 #: apps/activity/templates/activity/includes/activity_info.html:25 msgid "start date" msgstr "fecha de inicio" -#: apps/activity/models.py:113 +#: apps/activity/models.py:115 #: apps/activity/templates/activity/includes/activity_info.html:28 msgid "end date" msgstr "fecha de fin" -#: apps/activity/models.py:118 +#: apps/activity/models.py:120 #: apps/activity/templates/activity/includes/activity_info.html:50 #: apps/note/models/transactions.py:149 msgid "valid" msgstr "válido" -#: apps/activity/models.py:123 +#: apps/activity/models.py:125 #: apps/activity/templates/activity/includes/activity_info.html:65 msgid "open" msgstr "abierto" -#: apps/activity/models.py:128 +#: apps/activity/models.py:130 msgid "activities" msgstr "actividades" -#: apps/activity/models.py:172 +#: apps/activity/models.py:174 msgid "entry time" msgstr "hora de entrada" -#: apps/activity/models.py:178 apps/note/apps.py:14 +#: apps/activity/models.py:180 apps/note/apps.py:14 #: apps/note/models/notes.py:77 msgid "note" msgstr "note" -#: apps/activity/models.py:189 +#: apps/activity/models.py:191 #: apps/activity/templates/activity/activity_entry.html:46 msgid "entry" msgstr "entrada" -#: apps/activity/models.py:190 +#: apps/activity/models.py:192 #: apps/activity/templates/activity/activity_entry.html:46 msgid "entries" msgstr "entradas" -#: apps/activity/models.py:193 +#: apps/activity/models.py:195 #, python-brace-format msgid "Entry for {guest}, invited by {note} to the activity {activity}" msgstr "Entrada para {guest}, invitado por {note} en la actividad {activity}" -#: apps/activity/models.py:195 +#: apps/activity/models.py:197 #, python-brace-format msgid "Entry for {note} to the activity {activity}" msgstr "Entrada para {note} en la actividad {activity}" -#: apps/activity/models.py:202 +#: apps/activity/models.py:204 msgid "Already entered on " msgstr "Entrado ya el " -#: apps/activity/models.py:202 apps/activity/tables.py:56 +#: apps/activity/models.py:204 apps/activity/tables.py:56 msgid "{:%Y-%m-%d %H:%M:%S}" msgstr "{:%d/%m/%Y %H:%M:%S}" -#: apps/activity/models.py:210 +#: apps/activity/models.py:212 msgid "The balance is negative." msgstr "El saldo es negativo." -#: apps/activity/models.py:240 +#: apps/activity/models.py:242 #: 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:245 +#: apps/activity/models.py:247 #: 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,19 +221,19 @@ msgstr "apellido" msgid "first name" msgstr "nombre" -#: apps/activity/models.py:252 +#: apps/activity/models.py:254 msgid "inviter" msgstr "huésped" -#: apps/activity/models.py:256 +#: apps/activity/models.py:258 msgid "guest" msgstr "invitado" -#: apps/activity/models.py:257 +#: apps/activity/models.py:259 msgid "guests" msgstr "invitados" -#: apps/activity/models.py:310 +#: apps/activity/models.py:312 msgid "Invitation" msgstr "Invitación" @@ -245,7 +245,9 @@ msgstr "La actividad está actualmente abierta." msgid "The validation of the activity is pending." msgstr "La validación de esta actividad es pendiente." -#: apps/activity/tables.py:43 apps/treasury/tables.py:107 +#: apps/activity/tables.py:43 +#: apps/member/templates/member/picture_update.html:18 +#: apps/treasury/tables.py:107 msgid "Remove" msgstr "Quitar" @@ -261,15 +263,15 @@ msgstr "quitar" msgid "Type" msgstr "Tipo" -#: apps/activity/tables.py:84 apps/member/forms.py:193 -#: apps/registration/forms.py:92 apps/treasury/forms.py:131 +#: apps/activity/tables.py:84 apps/member/forms.py:196 +#: apps/registration/forms.py:91 apps/treasury/forms.py:131 #: apps/wei/forms/registration.py:104 msgid "Last name" msgstr "Apellido" -#: apps/activity/tables.py:86 apps/member/forms.py:198 +#: apps/activity/tables.py:86 apps/member/forms.py:201 #: apps/note/templates/note/transaction_form.html:138 -#: apps/registration/forms.py:97 apps/treasury/forms.py:133 +#: apps/registration/forms.py:96 apps/treasury/forms.py:133 #: apps/wei/forms/registration.py:109 msgid "First name" msgstr "Nombre" @@ -294,7 +296,7 @@ msgstr "Invitados suprimidos" #: apps/note/models/transactions.py:261 #: apps/note/templates/note/transaction_form.html:17 #: apps/note/templates/note/transaction_form.html:152 -#: note_kfet/templates/base.html:72 +#: note_kfet/templates/base.html:78 msgid "Transfer" msgstr "Transferencia" @@ -329,6 +331,10 @@ 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/member/templates/member/add_members.html:46 #: apps/member/templates/member/club_form.html:16 #: apps/note/templates/note/transactiontemplate_form.html:18 @@ -394,41 +400,41 @@ msgstr "modificar" msgid "Invite" msgstr "Invitar" -#: apps/activity/views.py:36 +#: apps/activity/views.py:37 msgid "Create new activity" msgstr "Crear una nueva actividad" -#: apps/activity/views.py:67 note_kfet/templates/base.html:90 +#: apps/activity/views.py:67 note_kfet/templates/base.html:96 msgid "Activities" msgstr "Actividades" -#: apps/activity/views.py:93 +#: apps/activity/views.py:108 msgid "Activity detail" msgstr "Detalles de la actividad" -#: apps/activity/views.py:113 +#: apps/activity/views.py:128 msgid "Update activity" msgstr "Modificar la actividad" -#: apps/activity/views.py:140 +#: apps/activity/views.py:155 msgid "Invite guest to the activity \"{}\"" msgstr "Invitar alguien para la actividad \"{}\"" -#: apps/activity/views.py:178 +#: apps/activity/views.py:193 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:181 +#: apps/activity/views.py:196 msgid "This activity does not support activity entries." msgstr "Esta actividad no necesita entradas." -#: apps/activity/views.py:184 +#: apps/activity/views.py:199 msgid "This activity is closed." msgstr "Esta actividad esta cerrada." -#: apps/activity/views.py:280 +#: apps/activity/views.py:295 msgid "Entry for activity \"{}\"" msgstr "Entradas para la actividad \"{}\"" @@ -436,6 +442,298 @@ msgstr "Entradas para la actividad \"{}\"" msgid "API" msgstr "API" +#: apps/food/apps.py:11 apps/food/models.py:105 +msgid "food" +msgstr "" + +#: apps/food/forms.py:32 +msgid "Fully used" +msgstr "" + +#: apps/food/forms.py:50 +msgid "Pasta METRO 5kg" +msgstr "" + +#: apps/food/forms.py:96 +msgid "Lasagna" +msgstr "" + +#: apps/food/models.py:18 +#, fuzzy +#| msgid "phone 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 +msgid "Allergen" +msgstr "" + +#: apps/food/models.py:48 apps/food/templates/food/basicfood_detail.html:17 +#: apps/food/templates/food/transformedfood_detail.html:20 +msgid "Allergens" +msgstr "" + +#: apps/food/models.py:64 +msgid "owner" +msgstr "" + +#: apps/food/models.py:70 +msgid "allergen" +msgstr "" + +#: apps/food/models.py:74 +#, fuzzy +#| msgid "birth date" +msgid "expiry date" +msgstr "fecha de nacimiento" + +#: apps/food/models.py:80 +msgid "was eaten" +msgstr "" + +#: apps/food/models.py:89 +msgid "is ready" +msgstr "" + +#: apps/food/models.py:94 +#, fuzzy +#| msgid "active" +msgid "is active" +msgstr "activo" + +#: apps/food/models.py:106 +msgid "foods" +msgstr "" + +#: apps/food/models.py:122 +#, fuzzy +#| msgid "invalidate" +msgid "arrival date" +msgstr "invalidar" + +#: apps/food/models.py:152 +msgid "Basic food" +msgstr "" + +#: apps/food/models.py:153 +msgid "Basic foods" +msgstr "" + +#: apps/food/models.py:161 +#, fuzzy +#| msgid "created at" +msgid "creation date" +msgstr "creada el" + +#: apps/food/models.py:169 +msgid "transformed ingredient" +msgstr "" + +#: apps/food/models.py:174 +msgid "shelf life" +msgstr "" + +#: apps/food/models.py:225 apps/food/views.py:365 +#, fuzzy +#| msgid "Transfer money" +msgid "Transformed food" +msgstr "Transferir dinero" + +#: apps/food/models.py:226 +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 +#, fuzzy +#| msgid "Owned" +msgid "Owner" +msgstr "Tenido" + +#: 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" +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 +#, 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 +#, fuzzy +#| msgid "Add team" +msgid "Add to a meal" +msgstr "Añadir un equipo" + +#: apps/food/templates/food/create_qrcode_form.html:14 +#, fuzzy +#| msgid "Transfer money" +msgid "New basic food" +msgstr "Transferir dinero" + +#: apps/food/templates/food/qrcode_detail.html:10 +#, fuzzy +#| msgid "phone number" +msgid "number" +msgstr "número de teléfono" + +#: 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 +msgid "Meal served" +msgstr "" + +#: apps/food/templates/food/transformedfood_list.html:16 +#, fuzzy +#| msgid "New user" +msgid "New meal" +msgstr "Nuevo usuario" + +#: apps/food/templates/food/transformedfood_list.html:25 +#, 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" +msgstr "" + +#: apps/food/templates/food/transformedfood_list.html:40 +#, fuzzy +#| msgid "There is no results." +msgid "There is no free meal." +msgstr "No hay resultado." + +#: apps/food/templates/food/transformedfood_list.html:48 +msgid "All meals" +msgstr "" + +#: apps/food/templates/food/transformedfood_list.html:55 +#, fuzzy +#| msgid "There is no results." +msgid "There is no meal." +msgstr "No hay resultado." + +#: apps/food/views.py:28 +msgid "Add the ingredient" +msgstr "" + +#: apps/food/views.py:42 +#, fuzzy +#| msgid "This credit is already validated." +msgid "The product is already prepared" +msgstr "Este crédito ya fue validado." + +#: apps/food/views.py:70 +#, fuzzy +#| msgid "Update an invoice" +msgid "Update an aliment" +msgstr "Modificar una factura" + +#: apps/food/views.py:97 +#, 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: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/logs/apps.py:11 msgid "Logs" msgstr "Logs" @@ -505,11 +803,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:325 +#: apps/member/admin.py:65 apps/member/models.py:330 msgid "roles" msgstr "papel" -#: apps/member/admin.py:66 apps/member/models.py:339 +#: apps/member/admin.py:66 apps/member/models.py:344 msgid "fee" msgstr "pago" @@ -561,49 +859,49 @@ msgstr "Tamaño máximo : 2Mo" msgid "This image cannot be loaded." msgstr "Esta imagen no puede ser cargada." -#: apps/member/forms.py:148 apps/member/views.py:102 -#: apps/registration/forms.py:34 apps/registration/views.py:266 +#: apps/member/forms.py:151 apps/member/views.py:102 +#: apps/registration/forms.py:33 apps/registration/views.py:276 msgid "An alias with a similar name already exists." msgstr "Un alias similar ya existe." -#: apps/member/forms.py:172 +#: apps/member/forms.py:175 msgid "Inscription paid by Société Générale" msgstr "Registración pagadas por Société Générale" -#: apps/member/forms.py:174 +#: apps/member/forms.py:177 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:179 apps/registration/forms.py:79 +#: apps/member/forms.py:182 apps/registration/forms.py:78 #: apps/wei/forms/registration.py:91 msgid "Credit type" msgstr "Tipo de crédito" -#: apps/member/forms.py:180 apps/registration/forms.py:80 +#: apps/member/forms.py:183 apps/registration/forms.py:79 #: apps/wei/forms/registration.py:92 msgid "No credit" msgstr "No crédito" -#: apps/member/forms.py:182 +#: apps/member/forms.py:185 msgid "You can credit the note of the user." msgstr "Usted puede acreditar la note del usuario." -#: apps/member/forms.py:186 apps/registration/forms.py:85 +#: apps/member/forms.py:189 apps/registration/forms.py:84 #: apps/wei/forms/registration.py:97 msgid "Credit amount" msgstr "Valor del crédito" -#: apps/member/forms.py:203 apps/note/templates/note/transaction_form.html:144 -#: apps/registration/forms.py:102 apps/treasury/forms.py:135 +#: apps/member/forms.py:206 apps/note/templates/note/transaction_form.html:144 +#: apps/registration/forms.py:101 apps/treasury/forms.py:135 #: apps/wei/forms/registration.py:114 msgid "Bank" msgstr "Banco" -#: apps/member/forms.py:230 +#: apps/member/forms.py:233 msgid "User" msgstr "Usuario" -#: apps/member/forms.py:244 +#: apps/member/forms.py:247 msgid "Roles" msgstr "Papeles" @@ -850,46 +1148,52 @@ msgstr "" "Ultima fecha de una afiliación, después de la cual los miembros tienen que " "prorrogarla." -#: apps/member/models.py:263 apps/member/models.py:319 +#: apps/member/models.py:263 +#, fuzzy +#| msgid "Validate registration" +msgid "add to registration form" +msgstr "Validar la afiliación" + +#: apps/member/models.py:268 apps/member/models.py:324 #: apps/note/models/notes.py:176 msgid "club" msgstr "club" -#: apps/member/models.py:264 +#: apps/member/models.py:269 msgid "clubs" msgstr "clubs" -#: apps/member/models.py:330 +#: apps/member/models.py:335 msgid "membership starts on" msgstr "afiliación empezá el" -#: apps/member/models.py:334 +#: apps/member/models.py:339 msgid "membership ends on" msgstr "afiliación termina el" -#: apps/member/models.py:343 apps/note/models/transactions.py:385 +#: apps/member/models.py:348 apps/note/models/transactions.py:385 msgid "membership" msgstr "afiliación" -#: apps/member/models.py:344 +#: apps/member/models.py:349 msgid "memberships" msgstr "afiliaciones" -#: apps/member/models.py:348 +#: apps/member/models.py:353 #, python-brace-format msgid "Membership of {user} for the club {club}" msgstr "Afiliación of {user} for the club {club}" -#: apps/member/models.py:367 +#: apps/member/models.py:372 #, 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:376 apps/member/views.py:712 +#: apps/member/models.py:381 apps/member/views.py:715 msgid "User is already a member of the club" msgstr "Usuario ya esta un miembro del club" -#: apps/member/models.py:388 apps/member/views.py:721 +#: apps/member/models.py:393 apps/member/views.py:724 msgid "User is not a member of the parent club" msgstr "Usuario no es un miembro del club pariente" @@ -1003,7 +1307,7 @@ msgstr "" #: 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:517 +#: apps/member/views.py:520 msgid "Note aliases" msgstr "Alias de la note" @@ -1151,11 +1455,11 @@ msgstr "Introspección :" msgid "Show my applications" msgstr "Mostrar mis aplicaciones" -#: apps/member/templates/member/picture_update.html:35 +#: apps/member/templates/member/picture_update.html:38 msgid "Nevermind" msgstr "No importa" -#: apps/member/templates/member/picture_update.html:36 +#: apps/member/templates/member/picture_update.html:39 msgid "Crop and upload" msgstr "Podar y subir" @@ -1204,7 +1508,7 @@ msgstr "Guardar cambios" msgid "Registrations" msgstr "Registraciones" -#: apps/member/views.py:72 apps/registration/forms.py:24 +#: apps/member/views.py:72 apps/registration/forms.py:23 msgid "This address must be valid." msgstr "Este correo tiene que ser valido." @@ -1224,31 +1528,31 @@ msgstr "Amistades de note" msgid "Update note picture" msgstr "Modificar la imagen de la note" -#: apps/member/views.py:354 +#: apps/member/views.py:357 msgid "Manage auth token" msgstr "Gestionar los token de autentificación" -#: apps/member/views.py:381 +#: apps/member/views.py:384 msgid "Create new club" msgstr "Crear un nuevo club" -#: apps/member/views.py:400 +#: apps/member/views.py:403 msgid "Search club" msgstr "Buscar un club" -#: apps/member/views.py:433 +#: apps/member/views.py:436 msgid "Club detail" msgstr "Detalles del club" -#: apps/member/views.py:540 +#: apps/member/views.py:543 msgid "Update club" msgstr "Modificar el club" -#: apps/member/views.py:574 +#: apps/member/views.py:577 msgid "Add new member to the club" msgstr "Añadir un nuevo miembro al club" -#: apps/member/views.py:703 apps/wei/views.py:973 +#: apps/member/views.py:706 apps/wei/views.py:973 msgid "" "This user don't have enough money to join this club, and can't have a " "negative balance." @@ -1256,19 +1560,19 @@ msgstr "" "Este usuario no tiene suficiente dinero para unirse a este club, y no puede " "tener un saldo negativo." -#: apps/member/views.py:725 +#: apps/member/views.py:728 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:730 +#: apps/member/views.py:733 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:880 +#: apps/member/views.py:883 msgid "Manage roles of an user in the club" msgstr "Gestionar los papeles de un usuario en el club" -#: apps/member/views.py:905 +#: apps/member/views.py:908 msgid "Members of the club" msgstr "Miembros del club" @@ -1433,8 +1737,10 @@ msgid "trusted" msgstr "amigo" #: apps/note/models/notes.py:243 -msgid "frienship" -msgstr "amistad" +#, fuzzy +#| msgid "friendships" +msgid "friendship" +msgstr "amistades" #: apps/note/models/notes.py:248 #, python-brace-format @@ -1729,11 +2035,6 @@ msgstr "Acción" msgid "Amount" msgstr "Monto" -#: apps/note/templates/note/transaction_form.html:132 -#: apps/treasury/models.py:60 -msgid "Name" -msgstr "Nombre" - #: apps/note/templates/note/transaction_form.html:177 msgid "Select emitter" msgstr "Elegir el remitente" @@ -2005,7 +2306,7 @@ msgid "" "with these parameters. Please correct your data and retry." msgstr "" -#: apps/permission/views.py:112 note_kfet/templates/base.html:108 +#: apps/permission/views.py:112 note_kfet/templates/base.html:114 msgid "Rights" msgstr "Permisos" @@ -2017,15 +2318,15 @@ msgstr "Todos los permisos" msgid "registration" msgstr "afiliación" -#: apps/registration/forms.py:40 +#: apps/registration/forms.py:39 msgid "This email address is already used." msgstr "Este correo electrónico ya esta utilizado." -#: apps/registration/forms.py:60 +#: apps/registration/forms.py:59 msgid "Register to the WEI" msgstr "Registrarse en el WEI" -#: apps/registration/forms.py:62 +#: apps/registration/forms.py:61 msgid "" "Check this case if you want to register to the WEI. If you hesitate, you " "will be able to register later, after validating your account in the Kfet." @@ -2033,11 +2334,11 @@ msgstr "" "Marcar esta casilla si usted quiere registrarse en el WEI. Si duda, podrá " "registrarse más tarde, después de validar su cuenta Note Kfet." -#: apps/registration/forms.py:107 +#: apps/registration/forms.py:106 msgid "Join BDE Club" msgstr "Afiliarse al club BDE" -#: apps/registration/forms.py:114 +#: apps/registration/forms.py:113 msgid "Join Kfet Club" msgstr "Afiliarse al club Kfet" @@ -2146,58 +2447,64 @@ msgstr "Gracias" msgid "The Note Kfet team." msgstr "El equipo Note Kfet." -#: apps/registration/views.py:41 +#: apps/registration/views.py:42 msgid "Register new user" msgstr "Registrar un nuevo usuario" -#: apps/registration/views.py:99 +#: apps/registration/views.py:100 msgid "Email validation" msgstr "Validación del correo electrónico" -#: apps/registration/views.py:101 +#: apps/registration/views.py:102 msgid "Validate email" msgstr "Validar el correo electrónico" -#: apps/registration/views.py:145 +#: apps/registration/views.py:146 msgid "Email validation unsuccessful" msgstr "La validación del correo electrónico fracasó" -#: apps/registration/views.py:156 +#: apps/registration/views.py:157 msgid "Email validation email sent" msgstr "Correo de validación enviado" -#: apps/registration/views.py:164 +#: apps/registration/views.py:165 msgid "Resend email validation link" msgstr "Reenviar el enlace de validación" -#: apps/registration/views.py:182 +#: apps/registration/views.py:183 msgid "Pre-registered users list" msgstr "Lista de los usuarios con afiliación pendiente" -#: apps/registration/views.py:206 +#: apps/registration/views.py:207 msgid "Unregistered users" msgstr "Usuarios con afiliación pendiente" -#: apps/registration/views.py:219 +#: apps/registration/views.py:220 msgid "Registration detail" msgstr "Detalles de la afiliación" -#: apps/registration/views.py:293 +#: apps/registration/views.py:256 +#, 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." msgstr "Usted tiene que afiliarse al BDE." -#: apps/registration/views.py:323 +#: apps/registration/views.py:330 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:417 +#: apps/registration/views.py:425 msgid "Invalidate pre-registration" msgstr "Invalidar la afiliación" -#: apps/treasury/apps.py:12 note_kfet/templates/base.html:96 +#: apps/treasury/apps.py:12 note_kfet/templates/base.html:102 msgid "Treasury" msgstr "Tesorería" @@ -2614,7 +2921,7 @@ 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 -#: note_kfet/templates/base.html:102 +#: note_kfet/templates/base.html:108 msgid "WEI" msgstr "WEI" @@ -3228,19 +3535,19 @@ msgstr "Repartir los primer años en los buses" msgid "Attribute bus" msgstr "Repartir en un bus" -#: note_kfet/settings/base.py:172 +#: note_kfet/settings/base.py:173 msgid "German" msgstr "Alemán" -#: note_kfet/settings/base.py:173 +#: note_kfet/settings/base.py:174 msgid "English" msgstr "Ingles" -#: note_kfet/settings/base.py:174 +#: note_kfet/settings/base.py:175 msgid "Spanish" msgstr "Español" -#: note_kfet/settings/base.py:175 +#: note_kfet/settings/base.py:176 msgid "French" msgstr "Francés" @@ -3302,34 +3609,38 @@ msgstr "Reiniciar" msgid "The ENS Paris-Saclay BDE note." msgstr "La note del BDE de la ENS Paris-Saclay." -#: note_kfet/templates/base.html:78 +#: note_kfet/templates/base.html:72 +msgid "Food" +msgstr "" + +#: note_kfet/templates/base.html:84 msgid "Users" msgstr "Usuarios" -#: note_kfet/templates/base.html:84 +#: note_kfet/templates/base.html:90 msgid "Clubs" msgstr "Clubs" -#: note_kfet/templates/base.html:113 +#: note_kfet/templates/base.html:119 msgid "Admin" msgstr "" -#: note_kfet/templates/base.html:127 +#: note_kfet/templates/base.html:133 msgid "My account" msgstr "Mi cuenta" -#: note_kfet/templates/base.html:130 +#: note_kfet/templates/base.html:136 msgid "Log out" msgstr "Desconectarse" -#: note_kfet/templates/base.html:138 +#: note_kfet/templates/base.html:144 #: 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:145 +#: note_kfet/templates/base.html:151 #: note_kfet/templates/registration/login.html:6 #: note_kfet/templates/registration/login.html:15 #: note_kfet/templates/registration/login.html:38 @@ -3337,7 +3648,7 @@ msgstr "Registrar" msgid "Log in" msgstr "Conectarse" -#: note_kfet/templates/base.html:159 +#: note_kfet/templates/base.html:165 msgid "" "You are not a BDE member anymore. Please renew your membership if you want " "to use the note." @@ -3345,7 +3656,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:165 +#: note_kfet/templates/base.html:171 msgid "" "Your e-mail address is not validated. Please check your mail inbox and click " "on the validation link." @@ -3353,7 +3664,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:171 +#: note_kfet/templates/base.html:177 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 " @@ -3366,21 +3677,25 @@ 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:194 +#: note_kfet/templates/base.html:200 msgid "Contact us" msgstr "Contactarnos" -#: note_kfet/templates/base.html:196 +#: note_kfet/templates/base.html:202 msgid "Technical Support" msgstr "Soporte técnico" -#: note_kfet/templates/base.html:198 +#: note_kfet/templates/base.html:204 +msgid "Charte Info (FR)" +msgstr "" + +#: note_kfet/templates/base.html:206 msgid "FAQ (FR)" msgstr "FAQ (FR)" #: note_kfet/templates/base_search.html:15 -msgid "Search by attribute such as name…" -msgstr "Buscar con atributo, como el nombre…" +msgid "Search by attribute such as name..." +msgstr "Buscar con atributo, como el nombre..." #: note_kfet/templates/base_search.html:23 msgid "There is no results." @@ -3599,6 +3914,323 @@ msgstr "" "pagar su afiliación. Tambien tiene que validar su correo electronico con el " "enlace que recibió." +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid color." +#~ msgstr "invalidar" + +#, fuzzy +#~| msgid "invalidate" +#~ msgid "Enter a valid value." +#~ msgstr "invalidar" + +#, 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 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 +#~| 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" +#~ msgstr "Apuntación al WEI" + #~ msgid "Join BDA Club" #~ msgstr "Afiliarse al club BDA" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 46e6aa0a..2af3257e 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-03-22 00:33+0100\n" +"POT-Creation-Date: 2024-08-17 11:57+0200\n" "PO-Revision-Date: 2022-04-11 22:05+0200\n" "Last-Translator: bleizi \n" "Language-Team: French \n" @@ -20,6 +20,7 @@ msgstr "" #: apps/activity/apps.py:10 apps/activity/models.py:127 #: apps/activity/models.py:167 +#: apps/activity/models.py:323 msgid "activity" msgstr "activité" @@ -27,33 +28,33 @@ msgstr "activité" msgid "The note of this club is inactive." msgstr "La note du club est inactive." -#: apps/activity/forms.py:41 apps/activity/models.py:140 +#: apps/activity/forms.py:41 apps/activity/models.py:142 msgid "The end date must be after the start date." msgstr "La date de fin doit être après celle de début." -#: apps/activity/forms.py:82 apps/activity/models.py:269 +#: apps/activity/forms.py:82 apps/activity/models.py:271 msgid "You can't invite someone once the activity is started." msgstr "" "Vous ne pouvez pas inviter quelqu'un une fois que l'activité a démarré." -#: apps/activity/forms.py:85 apps/activity/models.py:272 +#: apps/activity/forms.py:85 apps/activity/models.py:274 msgid "This activity is not validated yet." msgstr "Cette activité n'est pas encore validée." -#: apps/activity/forms.py:95 apps/activity/models.py:280 +#: apps/activity/forms.py:95 apps/activity/models.py:282 msgid "This person has been already invited 5 times this year." msgstr "Cette personne a déjà été invitée 5 fois cette année." -#: apps/activity/forms.py:99 apps/activity/models.py:284 +#: apps/activity/forms.py:99 apps/activity/models.py:286 msgid "This person is already invited." msgstr "Cette personne est déjà invitée." -#: apps/activity/forms.py:103 apps/activity/models.py:288 +#: apps/activity/forms.py:103 apps/activity/models.py:290 msgid "You can't invite more than 3 people to this activity." msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité." -#: apps/activity/models.py:28 apps/activity/models.py:63 -#: apps/member/models.py:203 +#: 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/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 @@ -82,7 +83,7 @@ msgstr "peut inviter" #: apps/activity/models.py:44 #: apps/activity/templates/activity/includes/activity_info.html:46 msgid "guest entry fee" -msgstr "cotisation de l'entrée invitée" +msgstr "cotisation de l'entrée invité⋅e" #: apps/activity/models.py:49 msgid "activity type" @@ -99,121 +100,121 @@ msgstr "types d'activité" msgid "description" msgstr "description" -#: apps/activity/models.py:72 +#: apps/activity/models.py:74 msgid "location" msgstr "lieu" -#: apps/activity/models.py:76 +#: apps/activity/models.py:78 msgid "Place where the activity is organized, eg. Kfet." msgstr "Lieu où l'activité est organisée, par exemple la Kfet." -#: apps/activity/models.py:83 +#: apps/activity/models.py:85 #: apps/activity/templates/activity/includes/activity_info.html:22 #: apps/note/models/notes.py:207 apps/note/models/transactions.py:67 #: apps/permission/models.py:163 msgid "type" msgstr "type" -#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:313 +#: 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/wei/models.py:171 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/templates/wei/survey.html:15 msgid "user" -msgstr "utilisateur·ice" +msgstr "utilisateur⋅rice" -#: apps/activity/models.py:96 +#: apps/activity/models.py:98 #: apps/activity/templates/activity/includes/activity_info.html:36 msgid "organizer" -msgstr "organisateur·ice" +msgstr "organisateur·rice" -#: apps/activity/models.py:97 +#: apps/activity/models.py:99 msgid "Club that organizes the activity. The entry fees will go to this club." msgstr "" "Le club qui organise l'activité. Les coûts d'invitation iront pour ce club." -#: apps/activity/models.py:104 +#: apps/activity/models.py:106 #: apps/activity/templates/activity/includes/activity_info.html:39 msgid "attendees club" msgstr "club attendu" -#: apps/activity/models.py:105 +#: apps/activity/models.py:107 msgid "Club that is authorized to join the activity. Mostly the Kfet club." msgstr "" "Club qui est autorisé à rejoindre l'activité. Très souvent le club Kfet." -#: apps/activity/models.py:109 +#: apps/activity/models.py:111 #: apps/activity/templates/activity/includes/activity_info.html:25 msgid "start date" msgstr "date de début" -#: apps/activity/models.py:113 +#: apps/activity/models.py:115 #: apps/activity/templates/activity/includes/activity_info.html:28 msgid "end date" msgstr "date de fin" -#: apps/activity/models.py:118 +#: apps/activity/models.py:120 #: apps/activity/templates/activity/includes/activity_info.html:50 #: apps/note/models/transactions.py:149 msgid "valid" msgstr "valide" -#: apps/activity/models.py:123 +#: apps/activity/models.py:125 #: apps/activity/templates/activity/includes/activity_info.html:65 msgid "open" msgstr "ouvrir" -#: apps/activity/models.py:128 +#: apps/activity/models.py:130 msgid "activities" msgstr "activités" -#: apps/activity/models.py:172 +#: apps/activity/models.py:174 msgid "entry time" msgstr "heure d'entrée" -#: apps/activity/models.py:178 apps/note/apps.py:14 +#: apps/activity/models.py:180 apps/note/apps.py:14 #: apps/note/models/notes.py:77 msgid "note" msgstr "note" -#: apps/activity/models.py:189 +#: apps/activity/models.py:191 #: apps/activity/templates/activity/activity_entry.html:46 msgid "entry" msgstr "entrée" -#: apps/activity/models.py:190 +#: apps/activity/models.py:192 #: apps/activity/templates/activity/activity_entry.html:46 msgid "entries" msgstr "entrées" -#: apps/activity/models.py:193 +#: apps/activity/models.py:195 #, python-brace-format msgid "Entry for {guest}, invited by {note} to the activity {activity}" msgstr "Entrée pour {guest}, invité·e par {note} à l'activité {activity}" -#: apps/activity/models.py:195 +#: apps/activity/models.py:197 #, python-brace-format msgid "Entry for {note} to the activity {activity}" msgstr "Entrée de la note {note} pour l'activité « {activity} »" -#: apps/activity/models.py:202 +#: apps/activity/models.py:204 msgid "Already entered on " msgstr "Déjà rentré·e le " -#: apps/activity/models.py:202 apps/activity/tables.py:56 +#: apps/activity/models.py:204 apps/activity/tables.py:56 msgid "{:%Y-%m-%d %H:%M:%S}" msgstr "{:%d/%m/%Y %H:%M:%S}" -#: apps/activity/models.py:210 +#: apps/activity/models.py:212 msgid "The balance is negative." msgstr "La note est en négatif." -#: apps/activity/models.py:240 +#: apps/activity/models.py:242 #: 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:245 +#: apps/activity/models.py:247 #: 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,22 +223,32 @@ msgstr "nom de famille" msgid "first name" msgstr "prénom" -#: apps/activity/models.py:252 +#: apps/activity/models.py:254 msgid "inviter" msgstr "hôte" -#: apps/activity/models.py:256 +#: apps/activity/models.py:258 msgid "guest" msgstr "invité·e" -#: apps/activity/models.py:257 +#: apps/activity/models.py:259 msgid "guests" msgstr "invité·e·s" -#: apps/activity/models.py:310 +#: apps/activity/models.py:312 msgid "Invitation" msgstr "Invitation" +#: apps/activity/models.py:330 +#: apps/activity/models.py:334 +msgid "Opener" +msgstr "Ouvreur⋅se" + +#: apps/activity/models.py:335 +#: apps/activity/templates/activity_detail.html:16 +msgid "Openers" +msgstr "Ouvreur⋅ses" + #: apps/activity/tables.py:27 msgid "The activity is currently open." msgstr "Cette activité est actuellement ouverte." @@ -246,7 +257,9 @@ msgstr "Cette activité est actuellement ouverte." msgid "The validation of the activity is pending." msgstr "La validation de cette activité est en attente." -#: apps/activity/tables.py:43 apps/treasury/tables.py:107 +#: apps/activity/tables.py:43 +#: apps/member/templates/member/picture_update.html:18 +#: apps/treasury/tables.py:107 msgid "Remove" msgstr "Supprimer" @@ -262,15 +275,15 @@ msgstr "supprimer" msgid "Type" msgstr "Type" -#: apps/activity/tables.py:84 apps/member/forms.py:193 -#: apps/registration/forms.py:92 apps/treasury/forms.py:131 +#: apps/activity/tables.py:84 apps/member/forms.py:196 +#: apps/registration/forms.py:91 apps/treasury/forms.py:131 #: apps/wei/forms/registration.py:104 msgid "Last name" msgstr "Nom de famille" -#: apps/activity/tables.py:86 apps/member/forms.py:198 +#: apps/activity/tables.py:86 apps/member/forms.py:201 #: apps/note/templates/note/transaction_form.html:138 -#: apps/registration/forms.py:97 apps/treasury/forms.py:133 +#: apps/registration/forms.py:96 apps/treasury/forms.py:133 #: apps/wei/forms/registration.py:109 msgid "First name" msgstr "Prénom" @@ -295,7 +308,7 @@ msgstr "Invité·e supprimé·e" #: apps/note/models/transactions.py:261 #: apps/note/templates/note/transaction_form.html:17 #: apps/note/templates/note/transaction_form.html:152 -#: note_kfet/templates/base.html:72 +#: note_kfet/templates/base.html:78 msgid "Transfer" msgstr "Virement" @@ -324,13 +337,18 @@ msgstr "Retour à la page de l'activité" #: apps/activity/templates/activity/activity_entry.html:129 msgid "Entry done, but caution: the user is not a Kfet member." msgstr "" -"Entrée effectuée, mais attention : la personne n'est pas un·e adhérent·e Kfet." +"Entrée effectuée, mais attention : la personne n'est pas un·e adhérent·e " +"Kfet." #: apps/activity/templates/activity/activity_entry.html:132 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:19 +#: apps/food/templates/food/transformedfood_form.html:16 #: apps/member/templates/member/add_members.html:46 #: apps/member/templates/member/club_form.html:16 #: apps/note/templates/note/transactiontemplate_form.html:18 @@ -365,7 +383,7 @@ msgstr "Toutes les activités" #: apps/activity/templates/activity/includes/activity_info.html:32 msgid "creater" -msgstr "créateur·ice" +msgstr "créateur⋅rice" #: apps/activity/templates/activity/includes/activity_info.html:53 msgid "opened" @@ -396,41 +414,41 @@ msgstr "modifier" msgid "Invite" msgstr "Inviter" -#: apps/activity/views.py:36 +#: apps/activity/views.py:37 msgid "Create new activity" msgstr "Créer une nouvelle activité" -#: apps/activity/views.py:67 note_kfet/templates/base.html:90 +#: apps/activity/views.py:67 note_kfet/templates/base.html:96 msgid "Activities" msgstr "Activités" -#: apps/activity/views.py:93 +#: apps/activity/views.py:108 msgid "Activity detail" msgstr "Détails de l'activité" -#: apps/activity/views.py:113 +#: apps/activity/views.py:128 msgid "Update activity" msgstr "Modifier l'activité" -#: apps/activity/views.py:140 +#: apps/activity/views.py:155 msgid "Invite guest to the activity \"{}\"" msgstr "Invitation pour l'activité « {} »" -#: apps/activity/views.py:178 +#: apps/activity/views.py:193 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:181 +#: apps/activity/views.py:196 msgid "This activity does not support activity entries." msgstr "Cette activité ne requiert pas d'entrées." -#: apps/activity/views.py:184 +#: apps/activity/views.py:199 msgid "This activity is closed." msgstr "Cette activité est fermée." -#: apps/activity/views.py:280 +#: apps/activity/views.py:295 msgid "Entry for activity \"{}\"" msgstr "Entrées pour l'activité « {} »" @@ -438,6 +456,258 @@ msgstr "Entrées pour l'activité « {} »" msgid "API" msgstr "API" +#: apps/food/apps.py:11 apps/food/models.py:105 +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:96 +msgid "Lasagna" +msgstr "Lasagnes" + +#: apps/food/models.py:18 +msgid "QR-code number" +msgstr "numéro de QR-code" + +#: apps/food/models.py:26 +msgid "food container" +msgstr "récipient" + +#: 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 +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 +msgid "Allergens" +msgstr "Allergènes" + +#: apps/food/models.py:64 +msgid "owner" +msgstr "propriétaire" + +#: apps/food/models.py:70 +msgid "allergen" +msgstr "allergène" + +#: apps/food/models.py:74 +msgid "expiry date" +msgstr "date de péremption" + +#: apps/food/models.py:80 +msgid "was eaten" +msgstr "a été mangé" + +#: apps/food/models.py:89 +msgid "is ready" +msgstr "est prêt" + +#: apps/food/models.py:94 +msgid "is active" +msgstr "est en cours" + +#: apps/food/models.py:106 +msgid "foods" +msgstr "bouffes" + +#: apps/food/models.py:122 +msgid "arrival date" +msgstr "date d'arrivée" + +#: apps/food/models.py:152 +msgid "Basic food" +msgstr "Aliment basique" + +#: apps/food/models.py:153 +msgid "Basic foods" +msgstr "Aliments basiques" + +#: apps/food/models.py:161 +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 +msgid "shelf life" +msgstr "durée de vie" + +#: apps/food/models.py:225 apps/food/views.py:365 +msgid "Transformed food" +msgstr "Aliment transformé" + +#: apps/food/models.py:226 +msgid "Transformed foods" +msgstr "Aliments transformés" + +#: apps/food/templates/food/create_qrcode_form.html:31 +#: apps/food/templates/food/basicfood_detail.html:14 +#: apps/food/templates/food/qrcode_detail.html:15 +#: apps/food/templates/food/transformedfood_detail.html:14 +msgid "Owner" +msgstr "Propriétaire" + +#: apps/food/templates/food/create_qrcode_form.html:34 +#: apps/food/templates/food/basicfood_detail.html:15 +msgid "Arrival date" +msgstr "Date d'arrivée" + +#: apps/food/templates/food/create_qrcode_form.html:37 +#: apps/food/templates/food/basicfood_detail.html:16 +#: apps/food/templates/food/qrcode_detail.html:16 +#: apps/food/templates/food/transformedfood_detail.html:19 +msgid "Expiry date" +msgstr "Date de péremption" + +#: apps/food/templates/food/basicfood_detail.html:24 +#: apps/food/templates/food/transformedfood_detail.html:36 +msgid "Active" +msgstr "Actif" + +#: apps/food/templates/food/basicfood_detail.html:25 +#: apps/food/templates/food/transformedfood_detail.html:37 +msgid "Eaten" +msgstr "Mangé" + +#: 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 +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 +msgid "Add to a meal" +msgstr "Ajouter à un plat" + +#: apps/food/templates/food/create_qrcode_form.html:14 +msgid "New basic food" +msgstr "Nouvel aliment basique" + +#: apps/food/templates/food/create_qrcode_form.html:23 +msgid "Copy constructor" +msgstr "Constructeur de copie" + +#: apps/food/templates/food/qrcode_detail.html:10 +msgid "number" +msgstr "numéro" + +#: apps/food/templates/food/create_qrcode_form.html:28 +#: apps/food/templates/food/qrcode_detail.html:14 +#: apps/note/templates/note/transaction_form.html:132 +#: apps/treasury/models.py:60 +msgid "Name" +msgstr "Nom" + +#: 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 +msgid "Meal served" +msgstr "Plat servis" + +#: apps/food/templates/food/transformedfood_list.html:16 +msgid "New meal" +msgstr "Nouveau plat" + +#: apps/food/templates/food/transformedfood_list.html:25 +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" +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/transformedfood_list.html:48 +msgid "All meals" +msgstr "Tout les plats" + +#: apps/food/templates/food/transformedfood_list.html:55 +msgid "There is no meal." +msgstr "Il n'y a pas de plat" + +#: apps/food/views.py:28 +msgid "Add the ingredient" +msgstr "Ajouter un ingrédient" + +#: apps/food/views.py:42 +msgid "The product is already prepared" +msgstr "Le produit est déjà prêt" + +#: apps/food/views.py:70 +msgid "Update an aliment" +msgstr "Modifier un aliment" + +#: apps/food/views.py:97 +msgid "Details of:" +msgstr "Détails de:" + +#: 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/views.py:185 +msgid "Add a new QRCode" +msgstr "Ajouter un nouveau QR-code" + +#: apps/food/views.py:235 +msgid "QRCode" +msgstr "QR-code" + +#: apps/food/views.py:271 +msgid "Add a new meal" +msgstr "Ajouter un nouveau plat" + +#: apps/food/views.py:337 +msgid "Update a meal" +msgstr "Modifier le plat" + #: apps/logs/apps.py:11 msgid "Logs" msgstr "Logs" @@ -507,11 +777,11 @@ msgstr "cotisation pour adhérer (normalien·ne élève)" msgid "membership fee (unpaid students)" msgstr "cotisation pour adhérer (normalien·ne étudiant·e)" -#: apps/member/admin.py:65 apps/member/models.py:325 +#: apps/member/admin.py:65 apps/member/models.py:330 msgid "roles" msgstr "rôles" -#: apps/member/admin.py:66 apps/member/models.py:339 +#: apps/member/admin.py:66 apps/member/models.py:344 msgid "fee" msgstr "cotisation" @@ -562,49 +832,49 @@ msgstr "Taille maximale : 2 Mo" msgid "This image cannot be loaded." msgstr "Cette image ne peut pas être chargée." -#: apps/member/forms.py:148 apps/member/views.py:102 -#: apps/registration/forms.py:34 apps/registration/views.py:266 +#: apps/member/forms.py:151 apps/member/views.py:102 +#: apps/registration/forms.py:33 apps/registration/views.py:276 msgid "An alias with a similar name already exists." msgstr "Un alias avec un nom similaire existe déjà." -#: apps/member/forms.py:172 +#: apps/member/forms.py:175 msgid "Inscription paid by Société Générale" msgstr "Inscription payée par la Société générale" -#: apps/member/forms.py:174 +#: apps/member/forms.py:177 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:179 apps/registration/forms.py:79 +#: apps/member/forms.py:182 apps/registration/forms.py:78 #: apps/wei/forms/registration.py:91 msgid "Credit type" msgstr "Type de rechargement" -#: apps/member/forms.py:180 apps/registration/forms.py:80 +#: apps/member/forms.py:183 apps/registration/forms.py:79 #: apps/wei/forms/registration.py:92 msgid "No credit" msgstr "Pas de rechargement" -#: apps/member/forms.py:182 +#: apps/member/forms.py:185 msgid "You can credit the note of the user." -msgstr "Vous pouvez créditer la note de l'utilisateur·ice avant l'adhésion." +msgstr "Vous pouvez créditer la note de l'utilisateur⋅rice avant l'adhésion." -#: apps/member/forms.py:186 apps/registration/forms.py:85 +#: apps/member/forms.py:189 apps/registration/forms.py:84 #: apps/wei/forms/registration.py:97 msgid "Credit amount" msgstr "Montant à créditer" -#: apps/member/forms.py:203 apps/note/templates/note/transaction_form.html:144 -#: apps/registration/forms.py:102 apps/treasury/forms.py:135 +#: apps/member/forms.py:206 apps/note/templates/note/transaction_form.html:144 +#: apps/registration/forms.py:101 apps/treasury/forms.py:135 #: apps/wei/forms/registration.py:114 msgid "Bank" msgstr "Banque" -#: apps/member/forms.py:230 +#: apps/member/forms.py:233 msgid "User" -msgstr "Utilisateur·ice" +msgstr "Utilisateur⋅rice" -#: apps/member/forms.py:244 +#: apps/member/forms.py:247 msgid "Roles" msgstr "Rôles" @@ -731,11 +1001,11 @@ msgstr "adresse" #: apps/registration/templates/registration/future_profile_detail.html:43 #: apps/wei/templates/wei/weimembership_form.html:47 msgid "paid" -msgstr "payé·e" +msgstr "payé⋅e" #: apps/member/models.py:90 msgid "Tells if the user receive a salary." -msgstr "Indique si l'utilisateur·ice perçoit un salaire." +msgstr "Indique si l'utilisateur⋅rice perçoit un salaire." #: apps/member/models.py:99 apps/treasury/tables.py:143 msgid "No" @@ -754,8 +1024,8 @@ msgid "" "Register on the mailing list to stay informed of the events of the campus (1 " "mail/week)" msgstr "" -"S'inscrire sur la liste de diffusion pour rester informé·e des événements sur " -"le campus (1 mail par semaine)" +"S'inscrire sur la liste de diffusion pour rester informé·e des événements " +"sur le campus (1 mail par semaine)" #: apps/member/models.py:108 msgid "" @@ -783,7 +1053,7 @@ msgstr "date de dernier rapport" #: apps/member/models.py:127 msgid "email confirmed" -msgstr "adresse email confirmée" +msgstr "adresse e-mail confirmée" #: apps/member/models.py:132 msgid "registration valid" @@ -795,7 +1065,7 @@ msgstr "Charte VSS lue" #: apps/member/models.py:142 apps/member/models.py:143 msgid "user profile" -msgstr "profil utilisateur·ice" +msgstr "profil utilisateur·rice" #: apps/member/models.py:177 msgid "Activate your Note Kfet account" @@ -849,51 +1119,55 @@ msgstr "fin de l'adhésion" #: apps/member/models.py:259 msgid "Maximal date of a membership, after which members must renew it." msgstr "" -"Date maximale d'une fin d'adhésion, après laquelle les adhérent·e·s doivent la " -"renouveler." +"Date maximale d'une fin d'adhésion, après laquelle les adhérent·e·s doivent " +"la renouveler." -#: apps/member/models.py:263 apps/member/models.py:319 +#: apps/member/models.py:263 +msgid "add to registration form" +msgstr "ajouter au formulaire d'inscription" + +#: apps/member/models.py:268 apps/member/models.py:324 #: apps/note/models/notes.py:176 msgid "club" msgstr "club" -#: apps/member/models.py:264 +#: apps/member/models.py:269 msgid "clubs" msgstr "clubs" -#: apps/member/models.py:330 +#: apps/member/models.py:335 msgid "membership starts on" msgstr "l'adhésion commence le" -#: apps/member/models.py:334 +#: apps/member/models.py:339 msgid "membership ends on" msgstr "l'adhésion finit le" -#: apps/member/models.py:343 apps/note/models/transactions.py:385 +#: apps/member/models.py:348 apps/note/models/transactions.py:385 msgid "membership" msgstr "adhésion" -#: apps/member/models.py:344 +#: apps/member/models.py:349 msgid "memberships" msgstr "adhésions" -#: apps/member/models.py:348 +#: apps/member/models.py:353 #, python-brace-format msgid "Membership of {user} for the club {club}" msgstr "Adhésion de {user} pour le club {club}" -#: apps/member/models.py:367 +#: apps/member/models.py:372 #, python-brace-format msgid "The role {role} does not apply to the club {club}." msgstr "Le rôle {role} ne s'applique pas au club {club}." -#: apps/member/models.py:376 apps/member/views.py:712 +#: apps/member/models.py:381 apps/member/views.py:715 msgid "User is already a member of the club" -msgstr "L'utilisateur·ice est déjà membre du club" +msgstr "L'utilisateur·rice est déjà membre du club" -#: apps/member/models.py:388 apps/member/views.py:721 +#: apps/member/models.py:393 apps/member/views.py:724 msgid "User is not a member of the parent club" -msgstr "L'utilisateur·ice n'est pas membre du club parent" +msgstr "L'utilisateur·rice n'est pas membre du club parent" #: apps/member/tables.py:139 msgid "Renew" @@ -906,7 +1180,7 @@ msgid "" "%(pretty_fee)s will be charged to renew automatically the membership in this/" "these club·s." msgstr "" -"Cet·te utilisateur·ice n'est pas membre du/des club·s parent·s %(clubs)s. Un " +"Cet·te utilisateur·rice n'est pas membre du/des club·s parent·s %(clubs)s. Un " "montant supplémentaire de %(pretty_fee)s sera débité afin de renouveler " "automatiquement l'adhésion dans ce·s club·s." @@ -916,8 +1190,8 @@ msgid "" "The user is not a member of the club·s %(clubs)s. Please create the required " "memberships, otherwise it will fail." msgstr "" -"Cet·te utilisateur·ice n'est pas membre du/des club·s parent·s %(clubs)s. Merci de " -"d'abord créer l'adhésion requise, sinon cette adhésion va échouer." +"Cet⋅te utilisateur⋅rice n'est pas membre du/des club·s parent·s %(clubs)s. " +"Merci de d'abord créer l'adhésion requise, sinon cette adhésion va échouer." #: apps/member/templates/member/add_members.html:29 #, python-format @@ -934,7 +1208,7 @@ msgid "" "This club has parents %(clubs)s. Please make sure that the user is a member " "of this or these club·s, otherwise the creation of this membership will fail." msgstr "" -"Ce club a pour parents %(clubs)s. Merci de vous assurer que l'utilisateur·ice " +"Ce club a pour parents %(clubs)s. Merci de vous assurer que l'utilisateur⋅rice " "est membre de ce·s club·s, sinon la création de cette adhésion va échouer." #: apps/member/templates/member/base.html:17 @@ -983,8 +1257,8 @@ msgid "" "If you use the force mode, the user won't be able to unlock the note by " "itself." msgstr "" -"Si vous verrouillez la note de force, l'utilisateur·ice ne pourra plus la " -"déverrouiller soi-même." +"Si vous verrouillez la note de force, l'utilisateur⋅rice ne pourra plus la " +"déverrouiller ellui-même." #: apps/member/templates/member/base.html:110 #: apps/member/templates/member/base.html:137 apps/treasury/forms.py:91 @@ -1005,7 +1279,7 @@ msgstr "" #: 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:517 +#: apps/member/views.py:520 msgid "Note aliases" msgstr "Alias de la note" @@ -1127,7 +1401,7 @@ msgstr "Attention" #: apps/member/templates/member/manage_auth_tokens.html:44 msgid "Regenerate token" -msgstr "Regénérer le jeton" +msgstr "Régénérer le jeton" #: apps/member/templates/member/manage_auth_tokens.html:53 msgid "OAuth2 authentication" @@ -1153,11 +1427,11 @@ msgstr "Introspection :" msgid "Show my applications" msgstr "Voir mes applications" -#: apps/member/templates/member/picture_update.html:35 +#: apps/member/templates/member/picture_update.html:38 msgid "Nevermind" msgstr "Annuler" -#: apps/member/templates/member/picture_update.html:36 +#: apps/member/templates/member/picture_update.html:39 msgid "Crop and upload" msgstr "Recadrer et envoyer" @@ -1165,7 +1439,7 @@ msgstr "Recadrer et envoyer" #: apps/registration/templates/registration/future_profile_detail.html:28 #: apps/wei/templates/wei/weimembership_form.html:26 msgid "This user doesn't have confirmed his/her e-mail address." -msgstr "Cet·te utilisateur·ice n'a pas encore confirmé son adresse e-mail." +msgstr "Cet⋅te utilisateur⋅rice n'a pas encore confirmé son adresse e-mail." #: apps/member/templates/member/profile_detail.html:13 #: apps/registration/templates/registration/future_profile_detail.html:29 @@ -1206,17 +1480,17 @@ msgstr "Sauvegarder les changements" msgid "Registrations" msgstr "Inscriptions" -#: apps/member/views.py:72 apps/registration/forms.py:24 +#: apps/member/views.py:72 apps/registration/forms.py:23 msgid "This address must be valid." msgstr "Cette adresse doit être valide." #: apps/member/views.py:139 msgid "Profile detail" -msgstr "Détails de l'utilisateur·ice" +msgstr "Détails de l'utilisateur⋅rice" #: apps/member/views.py:205 msgid "Search user" -msgstr "Chercher un·e utilisateur·ice" +msgstr "Chercher un·e utilisateur·rice" #: apps/member/views.py:253 msgid "Note friendships" @@ -1226,51 +1500,51 @@ msgstr "Amitiés note" msgid "Update note picture" msgstr "Modifier la photo de la note" -#: apps/member/views.py:354 +#: apps/member/views.py:357 msgid "Manage auth token" msgstr "Gérer les jetons d'authentification" -#: apps/member/views.py:381 +#: apps/member/views.py:384 msgid "Create new club" msgstr "Créer un nouveau club" -#: apps/member/views.py:400 +#: apps/member/views.py:403 msgid "Search club" msgstr "Chercher un club" -#: apps/member/views.py:433 +#: apps/member/views.py:436 msgid "Club detail" msgstr "Détails du club" -#: apps/member/views.py:540 +#: apps/member/views.py:543 msgid "Update club" msgstr "Modifier le club" -#: apps/member/views.py:574 +#: apps/member/views.py:577 msgid "Add new member to the club" -msgstr "Ajouter un·e nouvelle·au membre au club" +msgstr "Ajouter un·e nouvelleau membre au club" -#: apps/member/views.py:703 apps/wei/views.py:973 +#: apps/member/views.py:706 apps/wei/views.py:973 msgid "" "This user don't have enough money to join this club, and can't have a " "negative balance." msgstr "" -"Cet·te utilisateur·ice n'a pas assez d'argent pour rejoindre ce club et ne peut pas " -"avoir un solde négatif." +"Cet⋅te utilisateur⋅rice n'a pas assez d'argent pour rejoindre ce club et ne " +"peut pas avoir un solde négatif." -#: apps/member/views.py:725 +#: apps/member/views.py:728 msgid "The membership must start after {:%m-%d-%Y}." msgstr "L'adhésion doit commencer après le {:%d/%m/%Y}." -#: apps/member/views.py:730 +#: apps/member/views.py:733 msgid "The membership must begin before {:%m-%d-%Y}." msgstr "L'adhésion doit commencer avant le {:%d/%m/%Y}." -#: apps/member/views.py:880 +#: apps/member/views.py:883 msgid "Manage roles of an user in the club" -msgstr "Gérer les rôles d'un·e utilisateur·ice dans le club" +msgstr "Gérer les rôles d'un⋅e utilisateur⋅rice dans le club" -#: apps/member/views.py:905 +#: apps/member/views.py:908 msgid "Members of the club" msgstr "Membres du club" @@ -1379,8 +1653,9 @@ msgstr "" #: apps/note/models/notes.py:70 msgid "The note is blocked by the the BDE and can't be manually reactivated." msgstr "" -"La note est bloquée de force par le BDE et ne peut pas être débloquée par le·a " -"possesseur·ice de la note." +"La note est bloquée de force par le BDE et ne peut pas être débloquée par læ " +"propriétaire de la note." + #: apps/note/models/notes.py:78 msgid "notes" @@ -1392,11 +1667,11 @@ msgstr "Cet alias est déjà pris." #: apps/note/models/notes.py:152 msgid "one's note" -msgstr "note d'un·e utilisateur·ice" +msgstr "note d'un·e utilisateur·rice" #: apps/note/models/notes.py:153 msgid "users note" -msgstr "notes des utilisateur·ice·s" +msgstr "notes des utilisateur·rice·s" #: apps/note/models/notes.py:159 #, python-format @@ -1433,7 +1708,7 @@ msgid "trusted" msgstr "ami·e" #: apps/note/models/notes.py:243 -msgid "frienship" +msgid "friendship" msgstr "amitié" #: apps/note/models/notes.py:248 @@ -1538,7 +1813,7 @@ msgid "" msgstr "" "Les montants des notes doivent se trouver entre - 92 233 720 368 547 758.08 " "€ et 92 233 720 368 547 758.07 €. Ne cherchez pas à capitaliser l'argent du " -"BDE." +"BDE, il est temps de penser à partager les richesses." #: apps/note/models/transactions.py:274 msgid "recurrent transaction" @@ -1553,7 +1828,7 @@ msgid "" "The destination of this transaction must equal to the destination of the " "template." msgstr "" -"Le·a destinataire de cette transaction doit être identique à celui du bouton " +"Læ destinataire de cette transaction doit être identique à cellui du bouton " "utilisé." #: apps/note/models/transactions.py:290 @@ -1582,7 +1857,7 @@ msgid "" "payment method and a User or a Club" msgstr "" "Une transaction spéciale n'est possible que entre une note associée à un " -"mode de paiement et un·e utilisateur·ice ou un club" +"mode de paiement et un⋅e utilisateur⋅rice ou un club" #: apps/note/models/transactions.py:357 apps/note/models/transactions.py:360 #: apps/note/models/transactions.py:363 apps/wei/views.py:978 @@ -1657,7 +1932,7 @@ msgstr "Consommer" #: apps/note/templates/note/transaction_form.html:69 #: apps/note/templates/note/transaction_form.html:96 msgid "Name or alias..." -msgstr "Pseudo ou alias ..." +msgstr "Pseudo ou alias..." #: apps/note/templates/note/conso_form.html:53 msgid "Select consumptions" @@ -1706,11 +1981,11 @@ msgstr "Mail généré par la Note Kfet le" #: apps/note/templates/note/transaction_form.html:58 #: apps/note/templates/note/transaction_form.html:178 msgid "Select emitters" -msgstr "Sélection des émetteur·ice·s" +msgstr "Sélection des émetteur·rice·s" #: apps/note/templates/note/transaction_form.html:73 msgid "I am the emitter" -msgstr "Je suis l'émetteur·ice" +msgstr "Je suis l'émetteur⋅rice" #: apps/note/templates/note/transaction_form.html:85 #: apps/note/templates/note/transaction_form.html:180 @@ -1728,18 +2003,13 @@ msgstr "Action" msgid "Amount" msgstr "Montant" -#: apps/note/templates/note/transaction_form.html:132 -#: apps/treasury/models.py:60 -msgid "Name" -msgstr "Nom" - #: apps/note/templates/note/transaction_form.html:177 msgid "Select emitter" -msgstr "Sélection de l'émetteur·ice" +msgstr "Sélection de l'émetteur⋅rice" #: apps/note/templates/note/transaction_form.html:179 msgid "Select receiver" -msgstr "Sélection du destinataire" +msgstr "Sélection de læ destinataire" #: apps/note/templates/note/transaction_form.html:181 msgid "Transfer type" @@ -1763,23 +2033,23 @@ msgstr "Prix actuel" #: apps/note/templates/note/transactiontemplate_list.html:13 msgid "Name of the button..." -msgstr "Nom du bouton ..." +msgstr "Nom du bouton..." #: apps/note/templates/note/transactiontemplate_list.html:15 msgid "New button" msgstr "Nouveau bouton" #: apps/note/templates/note/transactiontemplate_list.html:22 -msgid "buttons listing " -msgstr "liste des boutons " +msgid "buttons listing" +msgstr "liste des boutons" #: apps/note/templates/note/transactiontemplate_list.html:73 -msgid "button successfully deleted " -msgstr "le bouton a bien été supprimé " +msgid "button successfully deleted" +msgstr "le bouton a bien été supprimé" #: apps/note/templates/note/transactiontemplate_list.html:77 -msgid "Unable to delete button " -msgstr "Impossible de supprimer le bouton " +msgid "Unable to delete button" +msgstr "Impossible de supprimer le bouton" #: apps/note/templates/note/transactiontemplate_list.html:95 msgid "Button hidden" @@ -1873,7 +2143,7 @@ msgid "" "is expired." msgstr "" "Indique si la permission doit être attribuée même si l'adhésion de " -"l'utilisateur·ice est expirée." +"l'utilisateur⋅rice est expirée." #: apps/permission/models.py:182 #: apps/permission/templates/permission/all_rights.html:89 @@ -1931,16 +2201,16 @@ msgstr "" #: apps/permission/templates/permission/all_rights.html:12 msgid "Users that have surnormal rights" -msgstr "Liste des utilisateur·ice·s ayant des droits surnormaux" +msgstr "Liste des utilisateur·rice·s ayant des droits surnormaux" #: apps/permission/templates/permission/all_rights.html:16 msgid "Superusers have all rights on everything, to manage the website." msgstr "" -"Les super-utilisateur·ice·s ont tous les droits sur tout, afin de gérer le site." +"Les super-utilisateur·rice·s ont tous les droits sur tout, afin de gérer le site." #: apps/permission/templates/permission/all_rights.html:21 msgid "Superusers" -msgstr "Super-utilisateur·ice·s" +msgstr "Super-utilisateur·rice·s" #: apps/permission/templates/permission/all_rights.html:45 msgid "Roles description" @@ -2007,7 +2277,7 @@ msgstr "" "Vous n'avez pas la permission d'ajouter une instance du modèle « {model} » " "avec ces paramètres. Merci de les corriger et de réessayer." -#: apps/permission/views.py:112 note_kfet/templates/base.html:108 +#: apps/permission/views.py:112 note_kfet/templates/base.html:114 msgid "Rights" msgstr "Droits" @@ -2019,15 +2289,15 @@ msgstr "Tous les droits" msgid "registration" msgstr "inscription" -#: apps/registration/forms.py:40 +#: apps/registration/forms.py:39 msgid "This email address is already used." -msgstr "Cet email est déjà pris." +msgstr "Cette adresse e-mail est déjà prise." -#: apps/registration/forms.py:60 +#: apps/registration/forms.py:59 msgid "Register to the WEI" msgstr "S'inscrire au WEI" -#: apps/registration/forms.py:62 +#: apps/registration/forms.py:61 msgid "" "Check this case if you want to register to the WEI. If you hesitate, you " "will be able to register later, after validating your account in the Kfet." @@ -2036,11 +2306,11 @@ msgstr "" "pourrez toujours vous inscrire plus tard, après avoir validé votre compte à " "la Kfet." -#: apps/registration/forms.py:107 +#: apps/registration/forms.py:106 msgid "Join BDE Club" msgstr "Adhérer au club BDE" -#: apps/registration/forms.py:114 +#: apps/registration/forms.py:113 msgid "Join Kfet Club" msgstr "Adhérer au club Kfet" @@ -2076,7 +2346,7 @@ msgstr "Activation du compte" msgid "" "An email has been sent. Please click on the link to activate your account." msgstr "" -"Un email vient de vous être envoyé. Merci de cliquer sur le lien de " +"Un e-mail vient de vous être envoyé. Merci de cliquer sur le lien de " "validation pour activer votre compte." #: apps/registration/templates/registration/email_validation_email_sent.html:17 @@ -2095,7 +2365,7 @@ msgstr "Valider le compte" #: apps/registration/templates/registration/future_profile_detail.html:63 msgid "" "The user declared that he/she opened a bank account in the Société générale." -msgstr "L'utilisateur·ice a déclaré avoir ouvert un compte à la société générale." +msgstr "L'utilisateur·rice a déclaré avoir ouvert un compte à la société générale." #: apps/registration/templates/registration/future_profile_detail.html:73 #: apps/wei/templates/wei/weimembership_form.html:127 @@ -2105,7 +2375,7 @@ msgstr "Valider l'inscription" #: apps/registration/templates/registration/future_user_list.html:9 msgid "New user" -msgstr "Nouvel·le utilisateur·ice" +msgstr "Nouvel⋅le utilisateur⋅rice" #: apps/registration/templates/registration/mails/email_validation_email.html:12 #: apps/registration/templates/registration/mails/email_validation_email.txt:3 @@ -2119,7 +2389,7 @@ msgid "" "confirm your registration." msgstr "" "Vous vous êtes inscrit·e récemment sur la Note Kfet. Merci de cliquer sur le " -"lien ci-dessous pour confirmer votre adresse email." +"lien ci-dessous pour confirmer votre adresse e-mail." #: apps/registration/templates/registration/mails/email_validation_email.html:26 #: apps/registration/templates/registration/mails/email_validation_email.txt:9 @@ -2147,58 +2417,63 @@ msgstr "Merci" msgid "The Note Kfet team." msgstr "L'équipe de la Note Kfet." -#: apps/registration/views.py:41 +#: apps/registration/views.py:42 msgid "Register new user" -msgstr "Enregistrer un·e nouvel·le utilisateur·ice" +msgstr "Enregistrer un⋅e nouvel⋅le utilisateur⋅rice" -#: apps/registration/views.py:99 +#: apps/registration/views.py:100 msgid "Email validation" -msgstr "Validation de l'adresse mail" +msgstr "Validation de l'adresse e-mail" -#: apps/registration/views.py:101 +#: apps/registration/views.py:102 msgid "Validate email" msgstr "Valider l'adresse e-mail" -#: apps/registration/views.py:145 +#: apps/registration/views.py:146 msgid "Email validation unsuccessful" -msgstr "La validation de l'adresse mail a échoué" +msgstr "La validation de l'adresse e-mail a échoué" -#: apps/registration/views.py:156 +#: apps/registration/views.py:157 msgid "Email validation email sent" -msgstr "L'email de vérification de l'adresse email a bien été envoyé" +msgstr "L'e-mail de vérification de l'adresse e-mail a bien été envoyé" -#: apps/registration/views.py:164 +#: apps/registration/views.py:165 msgid "Resend email validation link" msgstr "Renvoyer le lien de validation" -#: apps/registration/views.py:182 +#: apps/registration/views.py:183 msgid "Pre-registered users list" -msgstr "Liste des utilisateur·ice·s en attente d'inscription" +msgstr "Liste des utilisateur⋅rices en attente d'inscription" -#: apps/registration/views.py:206 +#: apps/registration/views.py:207 msgid "Unregistered users" -msgstr "Utilisateur·ice·s en attente d'inscription" +msgstr "Utilisateur·rices en attente d'inscription" -#: apps/registration/views.py:219 +#: apps/registration/views.py:220 msgid "Registration detail" msgstr "Détails de l'inscription" -#: apps/registration/views.py:293 +#: apps/registration/views.py:256 +#, python-format +msgid "Join %(club)s Club" +msgstr "Adhérer au club %(club)s" + +#: apps/registration/views.py:299 msgid "You must join the BDE." msgstr "Vous devez adhérer au BDE." -#: apps/registration/views.py:323 +#: apps/registration/views.py:330 msgid "" "The entered amount is not enough for the memberships, should be at least {}" msgstr "" "Le montant crédité est trop faible pour adhérer, il doit être au minimum de " "{}" -#: apps/registration/views.py:417 +#: apps/registration/views.py:425 msgid "Invalidate pre-registration" msgstr "Invalider l'inscription" -#: apps/treasury/apps.py:12 note_kfet/templates/base.html:96 +#: apps/treasury/apps.py:12 note_kfet/templates/base.html:102 msgid "Treasury" msgstr "Trésorerie" @@ -2360,14 +2635,14 @@ msgstr "Crédits de la Société générale" #: apps/treasury/models.py:315 #, python-brace-format msgid "Soge credit for {user}" -msgstr "Crédit de la société générale pour l'utilisateur·ice {user}" +msgstr "Crédit de la société générale pour l'utilisateur·rice {user}" #: apps/treasury/models.py:445 msgid "" "This user doesn't have enough money to pay the memberships with its note. " "Please ask her/him to credit the note before invalidating this credit." msgstr "" -"Cet·te utilisateur·ice n'a pas assez d'argent pour payer les adhésions avec sa " +"Cet·te utilisateur·rice n'a pas assez d'argent pour payer les adhésions avec sa " "note. Merci de lui demander de recharger sa note avant d'invalider ce crédit." #: apps/treasury/tables.py:20 @@ -2420,7 +2695,7 @@ msgid "" "implies regenerate it. Be careful if you manipulate old invoices." msgstr "" "Attention : le template LaTeX est enregistré avec cet objet. Modifier la " -"facture implique la regénérer. Faites attention si vous manipulez de " +"facture implique la régénérer. Faites attention si vous manipulez de " "vieilles factures." #: apps/treasury/templates/treasury/invoice_form.html:69 @@ -2519,12 +2794,13 @@ msgid "" "If this credit is validated, then the user won't be able to ask for a credit " "from the Société générale." msgstr "" -"Si ce crédit est validé, alors l'utilisateur·ice ne pourra plus demander d'être " +"Si ce crédit est validé, alors l'utilisateur·rice ne pourra plus demander d'être " "crédité·e par la Société générale à l'avenir." #: apps/treasury/templates/treasury/sogecredit_detail.html:44 msgid "If you think there is an error, please contact the \"respos info\"." -msgstr "Si vous pensez qu'il y a une erreur, merci de contacter un·e respo info." +msgstr "" +"Si vous pensez qu'il y a une erreur, merci de contacter un·e respo info." #: apps/treasury/templates/treasury/sogecredit_detail.html:50 msgid "This credit is already validated." @@ -2535,13 +2811,13 @@ msgid "" "Warning: if you don't validate this credit, the note of the user doesn't " "have enough money to pay its memberships." msgstr "" -"Attention : si vous ne validez pas ce crédit, la note de l'utilisateur·ice n'a " +"Attention : si vous ne validez pas ce crédit, la note de l'utilisateur·rice n'a " "pas assez d'argent pour payer les adhésions." #: apps/treasury/templates/treasury/sogecredit_detail.html:56 msgid "Please ask the user to credit its note before deleting this credit." msgstr "" -"Merci de demander à l'utilisateur·ice de recharger sa note avant de supprimer la " +"Merci de demander à l'utilisateur·rice de recharger sa note avant de supprimer la " "demande de crédit." #: apps/treasury/templates/treasury/sogecredit_detail.html:63 @@ -2560,7 +2836,7 @@ msgstr "Filtrer avec uniquement les crédits non valides" #: apps/treasury/templates/treasury/sogecredit_list.html:50 msgid "There is no matched user that have asked for a Société générale credit." msgstr "" -"Il n'y a pas d'utilisateur·ice trouvé·e ayant demandé un crédit de la Société " +"Il n'y a pas d'utilisateur·rice trouvé·e ayant demandé un crédit de la Société " "générale." #: apps/treasury/templates/treasury/sogecredit_list.html:63 @@ -2614,14 +2890,14 @@ 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 -#: note_kfet/templates/base.html:102 +#: note_kfet/templates/base.html:108 msgid "WEI" msgstr "WEI" #: apps/wei/forms/registration.py:35 msgid "The selected user is not validated. Please validate its account first" msgstr "" -"L'utilisateur·ice sélectionné·e n'est pas validé·e. Merci de d'abord valider son " +"L'utilisateur·rice sélectionné·e n'est pas validé·e. Merci de d'abord valider son " "compte" #: apps/wei/forms/registration.py:59 apps/wei/models.py:126 @@ -2634,7 +2910,7 @@ msgid "" "This choice is not definitive. The WEI organizers are free to attribute for " "you a bus and a team, in particular if you are a free eletron." msgstr "" -"Ce choix n'est pas définitif. Les organisateur·ice·s du WEI sont libres de vous " +"Ce choix n'est pas définitif. Les organisateur·rice·s du WEI sont libres de vous " "attribuer un bus et une équipe, en particulier si vous êtes un·e électron " "libre." @@ -2726,6 +3002,7 @@ msgid "Credit from Société générale" msgstr "Crédit de la Société générale" #: apps/wei/models.py:188 +#: apps/wei/forms/registration.py:84 msgid "Caution check given" msgstr "Chèque de caution donné" @@ -2787,7 +3064,7 @@ msgstr "première année" #: apps/wei/models.py:249 msgid "Tells if the user is new in the school." -msgstr "Indique si l'utilisateur·ice est nouveau dans l'école." +msgstr "Indique si l'utilisateur⋅rice est nouvelleeau dans l'école." #: apps/wei/models.py:254 msgid "registration information" @@ -2827,19 +3104,19 @@ msgstr "Adhésions au WEI" #: apps/wei/tables.py:105 msgid "The user does not have enough money." -msgstr "L'utilisateur·ice n'a pas assez d'argent." +msgstr "L'utilisateur⋅rice n'a pas assez d'argent." #: apps/wei/tables.py:108 msgid "" "The user is in first year. You may validate the credit, the algorithm will " "run later." msgstr "" -"L'utilisateur·ice est en première année, vous pouvez valider le crédit, " +"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 msgid "The user has enough money, you can validate the registration." -msgstr "L'utilisateur·ice a assez d'argent, l'inscription est possible." +msgstr "L'utilisateur⋅rice a assez d'argent, l'inscription est possible." #: apps/wei/tables.py:143 msgid "Year" @@ -2860,19 +3137,19 @@ msgstr "Nombre de membres" #: apps/wei/tables.py:226 apps/wei/tables.py:257 msgid "members" -msgstr "adhérent·e·s" +msgstr "adhérent·es" #: apps/wei/tables.py:287 msgid "suggested first year" -msgstr "1A suggéré·e·s" +msgstr "1A suggéré·es" #: apps/wei/tables.py:293 msgid "validated first year" -msgstr "1A validé·e·s" +msgstr "1A validé·es" #: apps/wei/tables.py:299 msgid "validated staff" -msgstr "2A+ validé·e·s" +msgstr "2A+ validé·es" #: apps/wei/tables.py:310 msgid "free seats" @@ -2912,7 +3189,7 @@ msgstr "L'adhésion au BDE est offerte avec l'inscription au WEI." #: apps/wei/templates/wei/base.html:51 msgid "WEI fee (unpaid students)" -msgstr "Prix du WEI (étudiants)" +msgstr "Prix du WEI (étudiant⋅es)" #: apps/wei/templates/wei/base.html:76 msgid "WEI list" @@ -2920,11 +3197,11 @@ msgstr "Liste des WEI" #: apps/wei/templates/wei/base.html:81 apps/wei/views.py:528 msgid "Register 1A" -msgstr "Inscrire un 1A" +msgstr "Inscrire un⋅e 1A" #: apps/wei/templates/wei/base.html:85 apps/wei/views.py:614 msgid "Register 2A+" -msgstr "Inscrire un 2A+" +msgstr "Inscrire un⋅e 2A+" #: apps/wei/templates/wei/base.html:93 msgid "Add bus" @@ -3052,7 +3329,7 @@ msgstr "L'inscription a déjà été validée et ne peut pas être dévalidée." #: apps/wei/templates/wei/weimembership_form.html:132 msgid "The user joined the bus" -msgstr "L'utilisateur·ice a rejoint le bus" +msgstr "L'utilisateur⋅rice a rejoint le bus" #: apps/wei/templates/wei/weimembership_form.html:133 msgid "in the team" @@ -3097,7 +3374,7 @@ msgstr "" #: apps/wei/templates/wei/weimembership_form.html:166 msgid "The user didn't give her/his caution check." -msgstr "L'utilisateur·ice n'a pas donné son chèque de caution." +msgstr "L'utilisateur⋅rice n'a pas donné son chèque de caution." #: apps/wei/templates/wei/weimembership_form.html:174 msgid "" @@ -3105,13 +3382,13 @@ msgid "" "membership will be processed automatically, the WEI registration includes " "the membership fee." msgstr "" -"Cet·te utilisateur·ice n'est pas membre du club Kfet pour l'année à venir. " +"Cet⋅te utilisateur⋅rice n'est pas membre du club Kfet pour l'année à venir. " "L'adhésion va être faite automatiquement, l'inscription au WEI inclut le " "coût d'adhésion." #: apps/wei/templates/wei/weimembership_list.html:27 msgid "View unvalidated registrations..." -msgstr "Voir les inscriptions non validées ..." +msgstr "Voir les inscriptions non validées..." #: apps/wei/templates/wei/weiregistration_confirm_delete.html:16 msgid "This registration is already validated and can't be deleted." @@ -3131,8 +3408,8 @@ msgid "There is no pre-registration found with this pattern." msgstr "Il n'y a pas de pré-inscription en attente avec cette entrée." #: apps/wei/templates/wei/weiregistration_list.html:27 -msgid "View validated memberships..." -msgstr "Voir les adhésions validées ..." +msgid "View validated membershipis..." +msgstr "Voir les adhésions validées..." #: apps/wei/views.py:58 msgid "Search WEI" @@ -3188,7 +3465,7 @@ msgstr "Gérer l'équipe WEI" #: apps/wei/views.py:492 msgid "Register first year student to the WEI" -msgstr "Inscrire un 1A au WEI" +msgstr "Inscrire un⋅e 1A au WEI" #: apps/wei/views.py:550 apps/wei/views.py:649 msgid "This user is already registered to this WEI." @@ -3199,12 +3476,12 @@ msgid "" "This user can't be in her/his first year since he/she has already " "participated to a WEI." msgstr "" -"Cet·te utilisateur·ice ne peut pas être en première année puisqu'iel a déjà " +"Cet⋅te utilisateur⋅rice ne peut pas être en première année puisqu'iel a déjà " "participé à un WEI." #: apps/wei/views.py:578 msgid "Register old student to the WEI" -msgstr "Inscrire un 2A+ au WEI" +msgstr "Inscrire un⋅e 2A+ au WEI" #: apps/wei/views.py:633 apps/wei/views.py:721 msgid "You already opened an account in the Société générale." @@ -3234,19 +3511,19 @@ msgstr "Répartir les 1A dans les bus" msgid "Attribute bus" msgstr "Attribuer un bus" -#: note_kfet/settings/base.py:172 +#: note_kfet/settings/base.py:173 msgid "German" msgstr "Allemand" -#: note_kfet/settings/base.py:173 +#: note_kfet/settings/base.py:174 msgid "English" msgstr "Anglais" -#: note_kfet/settings/base.py:174 +#: note_kfet/settings/base.py:175 msgid "Spanish" msgstr "Espagnol" -#: note_kfet/settings/base.py:175 +#: note_kfet/settings/base.py:176 msgid "French" msgstr "Français" @@ -3298,10 +3575,10 @@ msgid "" "sent to webmasters with the detail of the error, and this will be fixed " "soon. You can now drink a beer." msgstr "" -"Désolé, une erreur est survenue lors de l'analyse de votre requête. Un email " +"Désolé, une erreur est survenue lors de l'analyse de votre requête. Un e-mail " "a été envoyé aux responsables de la plateforme avec les détails de cette " "erreur, qui sera corrigée rapidement. Vous pouvez désormais aller boire une " -"bière." +"bière, avec modération." #: note_kfet/templates/autocomplete_model.html:15 msgid "Reset" @@ -3311,34 +3588,38 @@ msgstr "Réinitialiser" msgid "The ENS Paris-Saclay BDE note." msgstr "La note du BDE de l'ENS Paris-Saclay." -#: note_kfet/templates/base.html:78 -msgid "Users" -msgstr "Utilisateur·ice·s" +#: note_kfet/templates/base.html:72 +msgid "Food" +msgstr "Bouffe" #: note_kfet/templates/base.html:84 +msgid "Users" +msgstr "Utilisateur·rices" + +#: note_kfet/templates/base.html:90 msgid "Clubs" msgstr "Clubs" -#: note_kfet/templates/base.html:113 +#: note_kfet/templates/base.html:119 msgid "Admin" msgstr "Admin" -#: note_kfet/templates/base.html:127 +#: note_kfet/templates/base.html:133 msgid "My account" msgstr "Mon compte" -#: note_kfet/templates/base.html:130 +#: note_kfet/templates/base.html:136 msgid "Log out" msgstr "Se déconnecter" -#: note_kfet/templates/base.html:138 +#: note_kfet/templates/base.html:144 #: 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:145 +#: note_kfet/templates/base.html:151 #: note_kfet/templates/registration/login.html:6 #: note_kfet/templates/registration/login.html:15 #: note_kfet/templates/registration/login.html:38 @@ -3346,15 +3627,15 @@ msgstr "Inscription" msgid "Log in" msgstr "Se connecter" -#: note_kfet/templates/base.html:159 +#: note_kfet/templates/base.html:165 msgid "" "You are not a BDE member anymore. Please renew your membership if you want " "to use the note." msgstr "" -"Vous n'êtes plus adhérent·e BDE. Merci de réadhérer si vous voulez profiter de " -"la note." +"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:165 +#: note_kfet/templates/base.html:171 msgid "" "Your e-mail address is not validated. Please check your mail inbox and click " "on the validation link." @@ -3362,7 +3643,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:171 +#: note_kfet/templates/base.html:177 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 " @@ -3376,21 +3657,25 @@ 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:194 +#: note_kfet/templates/base.html:200 msgid "Contact us" msgstr "Nous contacter" -#: note_kfet/templates/base.html:196 +#: note_kfet/templates/base.html:202 msgid "Technical Support" msgstr "Support technique" -#: note_kfet/templates/base.html:198 +#: note_kfet/templates/base.html:204 +msgid "Charte Info (FR)" +msgstr "Charte Info (FR)" + +#: note_kfet/templates/base.html:206 msgid "FAQ (FR)" msgstr "FAQ (FR)" #: note_kfet/templates/base_search.html:15 -msgid "Search by attribute such as name…" -msgstr "Chercher par un attribut tel que le nom …" +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." @@ -3588,7 +3873,7 @@ msgid "" "If you don't receive an email, please make sure you've entered the address " "you registered with, and check your spam folder." msgstr "" -"Si vous ne recevez pas d'email, vérifiez que vous avez bien utilisé " +"Si vous ne recevez pas d'e-mail, vérifiez que vous avez bien utilisé " "l'adresse associée à votre compte, et regarder également le dossier spam." #: note_kfet/templates/registration/password_reset_form.html:13 @@ -3610,14 +3895,340 @@ msgid "" "Kfet and pay the registration fee. You must also validate your email address " "by following the link you received." msgstr "" -"Si vous vous êtes déjà inscrit·e·s, votre inscription a bien été prise en " +"Si vous vous êtes déjà inscrit·e, votre inscription a bien été prise en " "compte. Le BDE doit d'abord valider votre compte avant que vous puissiez " "vous connecter. Vous devez vous rendre à la Kfet et payer les frais " "d'adhésion. Vous devez également valider votre adresse email en suivant le " "lien que vous avez reçu." -#~ msgid "Join BDA Club" -#~ msgstr "Adhérer au club BDA" +#, fuzzy +#~| msgid "invalidate" +#~ 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 +#~| 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." +#~ 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" + +#~ msgid "In preparation" +#~ msgstr "En cours de préparation" + +#~ msgid "Free" +#~ msgstr "Open" + +#~ msgid "Add a new aliment" +#~ msgstr "Ajouter un nouvel aliment" + +#, fuzzy +#~| msgid "Transformed food" +#~ msgid "New transformed food" +#~ msgstr "Bouffe transformée" #, fuzzy #~| msgid "People having you as a friend" diff --git a/locale/fr/LC_MESSAGES/djangojs.po b/locale/fr/LC_MESSAGES/djangojs.po index 93be9947..90f85fc4 100644 --- a/locale/fr/LC_MESSAGES/djangojs.po +++ b/locale/fr/LC_MESSAGES/djangojs.po @@ -17,6 +17,14 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" +#: apps/member/static/member/js/alias.js:17 +msgid "Opener successfully added" +msgstr "Ouvreureuse ajouté avec succès" + +#: apps/member/static/member/js/alias.js:17 +msgid "Opener successfully deleted" +msgstr "Ouvreureuse supprimé avec succès" + #: apps/member/static/member/js/alias.js:17 msgid "Alias successfully added" msgstr "Alias ajouté avec succès" diff --git a/note.cron b/note.cron index a54ad031..dc1f6460 100644 --- a/note.cron +++ b/note.cron @@ -9,6 +9,7 @@ MAILTO=notekfet2020@lists.crans.org * * * * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail -c 1 -v 0 * * * * * root cd /var/www/note_kfet && env/bin/python manage.py retry_deferred -c 1 -v 0 00 0 * * * root cd /var/www/note_kfet && env/bin/python manage.py purge_mail_log 7 -v 0 + 00 0 * * * root cd /var/www/note_kfet && env/bin/python manage.py purge_mail_log -r failure 30 -v 0 # Faire une sauvegarde de la base de données 00 2 * * * root cd /var/www/note_kfet && apps/scripts/shell/backup_db # Vérifier la cohérence de la base et mailer en cas de problème diff --git a/note_kfet/admin.py b/note_kfet/admin.py index 0900c3b0..8b47de06 100644 --- a/note_kfet/admin.py +++ b/note_kfet/admin.py @@ -25,8 +25,8 @@ admin_site.register(Site, SiteAdmin) # Add external apps model if "oauth2_provider" in settings.INSTALLED_APPS: - from oauth2_provider.admin import Application, ApplicationAdmin, Grant, \ - GrantAdmin, AccessToken, AccessTokenAdmin, RefreshToken, RefreshTokenAdmin + from oauth2_provider.admin import ApplicationAdmin, GrantAdmin, AccessTokenAdmin, RefreshTokenAdmin + from oauth2_provider.models import Application, Grant, AccessToken, RefreshToken admin_site.register(Application, ApplicationAdmin) admin_site.register(Grant, GrantAdmin) admin_site.register(AccessToken, AccessTokenAdmin) diff --git a/note_kfet/fixtures/initial.json b/note_kfet/fixtures/initial.json index 5d54f3e5..f5bf4c08 100644 --- a/note_kfet/fixtures/initial.json +++ b/note_kfet/fixtures/initial.json @@ -4,7 +4,7 @@ "pk": 1, "fields": { "domain": "note.crans.org", - "name": "La Note Kfet \ud83c\udf7b" + "name": "La Note Kfet 🍪" } } ] diff --git a/note_kfet/inputs.py b/note_kfet/inputs.py index 0ad0797a..b68adf4d 100644 --- a/note_kfet/inputs.py +++ b/note_kfet/inputs.py @@ -68,264 +68,3 @@ class ColorWidget(Widget): def value_from_datadict(self, data, files, name): val = super().value_from_datadict(data, files, name) return int(val[1:], 16) - - -""" -The remaining of this file comes from the project `django-bootstrap-datepicker-plus` available on Github: -https://github.com/monim67/django-bootstrap-datepicker-plus -This is distributed under Apache License 2.0. - -This adds datetime pickers with bootstrap. -""" - -"""Contains Base Date-Picker input class for widgets of this package.""" - - -class DatePickerDictionary: - """Keeps track of all date-picker input classes.""" - - _i = 0 - items = dict() - - @classmethod - def generate_id(cls): - """Return a unique ID for each date-picker input class.""" - cls._i += 1 - return 'dp_%s' % cls._i - - -class BasePickerInput(DateTimeBaseInput): - """Base Date-Picker input class for widgets of this package.""" - - template_name = 'bootstrap_datepicker_plus/date-picker.html' - picker_type = 'DATE' - format = '%Y-%m-%d' - config = {} - _default_config = { - 'id': None, - 'picker_type': None, - 'linked_to': None, - 'options': {} # final merged options - } - options = {} # options extended by user - options_param = {} # options passed as parameter - _default_options = { - 'showClose': True, - 'showClear': True, - 'showTodayButton': True, - "locale": "fr", - } - - # source: https://github.com/tutorcruncher/django-bootstrap3-datetimepicker - # file: /blob/31fbb09/bootstrap3_datetime/widgets.py#L33 - format_map = ( - ('DDD', r'%j'), - ('DD', r'%d'), - ('MMMM', r'%B'), - ('MMM', r'%b'), - ('MM', r'%m'), - ('YYYY', r'%Y'), - ('YY', r'%y'), - ('HH', r'%H'), - ('hh', r'%I'), - ('mm', r'%M'), - ('ss', r'%S'), - ('a', r'%p'), - ('ZZ', r'%z'), - ) - - class Media: - """JS/CSS resources needed to render the date-picker calendar.""" - - js = ( - 'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.9.0/' - 'moment-with-locales.min.js', - 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/' - '4.17.47/js/bootstrap-datetimepicker.min.js', - 'bootstrap_datepicker_plus/js/datepicker-widget.js' - ) - css = {'all': ( - 'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/' - '4.17.47/css/bootstrap-datetimepicker.css', - 'bootstrap_datepicker_plus/css/datepicker-widget.css' - ), } - - @classmethod - def format_py2js(cls, datetime_format): - """Convert python datetime format to moment datetime format.""" - for js_format, py_format in cls.format_map: - datetime_format = datetime_format.replace(py_format, js_format) - return datetime_format - - @classmethod - def format_js2py(cls, datetime_format): - """Convert moment datetime format to python datetime format.""" - for js_format, py_format in cls.format_map: - datetime_format = datetime_format.replace(js_format, py_format) - return datetime_format - - def __init__(self, attrs=None, format=None, options=None): - """Initialize the Date-picker widget.""" - self.format_param = format - self.options_param = options if options else {} - self.config = self._default_config.copy() - self.config['id'] = DatePickerDictionary.generate_id() - self.config['picker_type'] = self.picker_type - self.config['options'] = self._calculate_options() - attrs = attrs if attrs else {} - if 'class' not in attrs: - attrs['class'] = 'form-control' - super().__init__(attrs, self._calculate_format()) - - def _calculate_options(self): - """Calculate and Return the options.""" - _options = self._default_options.copy() - _options.update(self.options) - if self.options_param: - _options.update(self.options_param) - return _options - - def _calculate_format(self): - """Calculate and Return the datetime format.""" - _format = self.format_param if self.format_param else self.format - if self.config['options'].get('format'): - _format = self.format_js2py(self.config['options'].get('format')) - else: - self.config['options']['format'] = self.format_py2js(_format) - return _format - - def get_context(self, name, value, attrs): - """Return widget context dictionary.""" - context = super().get_context( - name, value, attrs) - context['widget']['attrs']['dp_config'] = json_dumps(self.config) - return context - - def start_of(self, event_id): - """ - Set Date-Picker as the start-date of a date-range. - - Args: - - event_id (string): User-defined unique id for linking two fields - """ - DatePickerDictionary.items[str(event_id)] = self - return self - - def end_of(self, event_id, import_options=True): - """ - Set Date-Picker as the end-date of a date-range. - - Args: - - event_id (string): User-defined unique id for linking two fields - - import_options (bool): inherit options from start-date input, - default: TRUE - """ - event_id = str(event_id) - if event_id in DatePickerDictionary.items: - linked_picker = DatePickerDictionary.items[event_id] - self.config['linked_to'] = linked_picker.config['id'] - if import_options: - backup_moment_format = self.config['options']['format'] - self.config['options'].update(linked_picker.config['options']) - self.config['options'].update(self.options_param) - if self.format_param or 'format' in self.options_param: - self.config['options']['format'] = backup_moment_format - else: - self.format = linked_picker.format - # Setting useCurrent is necessary, see following issue - # https://github.com/Eonasdan/bootstrap-datetimepicker/issues/1075 - self.config['options']['useCurrent'] = False - self._link_to(linked_picker) - else: - raise KeyError( - 'start-date not specified for event_id "%s"' % event_id) - return self - - def _link_to(self, linked_picker): - """ - Executed when two date-inputs are linked together. - - This method for sub-classes to override to customize the linking. - """ - pass - - -class DatePickerInput(BasePickerInput): - """ - Widget to display a Date-Picker Calendar on a DateField property. - - Args: - - attrs (dict): HTML attributes of rendered HTML input - - format (string): Python DateTime format eg. "%Y-%m-%d" - - options (dict): Options to customize the widget, see README - """ - - picker_type = 'DATE' - format = '%Y-%m-%d' - format_key = 'DATE_INPUT_FORMATS' - - -class TimePickerInput(BasePickerInput): - """ - Widget to display a Time-Picker Calendar on a TimeField property. - - Args: - - attrs (dict): HTML attributes of rendered HTML input - - format (string): Python DateTime format eg. "%Y-%m-%d" - - options (dict): Options to customize the widget, see README - """ - - picker_type = 'TIME' - format = '%H:%M' - format_key = 'TIME_INPUT_FORMATS' - template_name = 'bootstrap_datepicker_plus/time_picker.html' - - -class DateTimePickerInput(BasePickerInput): - """ - Widget to display a DateTime-Picker Calendar on a DateTimeField property. - - Args: - - attrs (dict): HTML attributes of rendered HTML input - - format (string): Python DateTime format eg. "%Y-%m-%d" - - options (dict): Options to customize the widget, see README - """ - - picker_type = 'DATETIME' - format = '%Y-%m-%d %H:%M' - format_key = 'DATETIME_INPUT_FORMATS' - - -class MonthPickerInput(BasePickerInput): - """ - Widget to display a Month-Picker Calendar on a DateField property. - - Args: - - attrs (dict): HTML attributes of rendered HTML input - - format (string): Python DateTime format eg. "%Y-%m-%d" - - options (dict): Options to customize the widget, see README - """ - - picker_type = 'MONTH' - format = '01/%m/%Y' - format_key = 'DATE_INPUT_FORMATS' - - -class YearPickerInput(BasePickerInput): - """ - Widget to display a Year-Picker Calendar on a DateField property. - - Args: - - attrs (dict): HTML attributes of rendered HTML input - - format (string): Python DateTime format eg. "%Y-%m-%d" - - options (dict): Options to customize the widget, see README - """ - - picker_type = 'YEAR' - format = '01/01/%Y' - format_key = 'DATE_INPUT_FORMATS' - - def _link_to(self, linked_picker): - """Customize the options when linked with other date-time input""" - yformat = self.config['options']['format'].replace('-01-01', '-12-31') - self.config['options']['format'] = yformat diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index 74fed818..8378448d 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -40,8 +40,9 @@ INSTALLED_APPS = [ # External apps 'bootstrap_datepicker_plus', 'colorfield', + 'crispy_bootstrap4', 'crispy_forms', - 'django_htcpcp_tea', +# 'django_htcpcp_tea', 'django_tables2', 'mailer', 'phonenumber_field', @@ -69,6 +70,7 @@ INSTALLED_APPS = [ # Note apps 'api', 'activity', + 'food', 'logs', 'member', 'note', @@ -90,12 +92,14 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.contrib.sites.middleware.CurrentSiteMiddleware', - 'django_htcpcp_tea.middleware.HTCPCPTeaMiddleware', 'note_kfet.middlewares.SessionMiddleware', 'note_kfet.middlewares.LoginByIPMiddleware', 'note_kfet.middlewares.TurbolinksMiddleware', 'note_kfet.middlewares.ClacksMiddleware', ] +if "django_htcpcp_tea" in INSTALLED_APPS: + MIDDLEWARE.append('django_htcpcp_tea.middleware.HTCPCPTeaMiddleware') + ROOT_URLCONF = 'note_kfet.urls' @@ -221,6 +225,7 @@ MEDIA_URL = '/media/' # Use mailer in production to place emails in a queue before sending them to avoid spam EMAIL_BACKEND = 'mailer.backend.DbBackend' MAILER_EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' +MAILER_EMAIL_MAX_BATCH = 10 EMAIL_USE_SSL = os.getenv('EMAIL_USE_SSL', False) EMAIL_HOST = os.getenv('EMAIL_HOST', 'smtp.example.org') EMAIL_PORT = os.getenv('EMAIL_PORT', 25) @@ -236,7 +241,7 @@ DEFAULT_FROM_EMAIL = "NoteKfet2020 <" + SERVER_EMAIL + ">" cache_address = os.getenv("CACHE_ADDRESS", "127.0.0.1:11211") CACHES = { 'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', + 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache', 'LOCATION': cache_address, } } @@ -261,6 +266,7 @@ OAUTH2_PROVIDER = { 'SCOPES_BACKEND_CLASS': 'permission.scopes.PermissionScopes', 'OAUTH2_VALIDATOR_CLASS': "permission.scopes.PermissionOAuth2Validator", 'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=14), + 'PKCE_REQUIRED': False, # PKCE (fix a breaking change of django-oauth-toolkit 2.0.0) } # Take control on how widget templates are sourced @@ -274,6 +280,7 @@ LOGIN_REDIRECT_URL = '/' SESSION_COOKIE_AGE = 60 * 60 * 3 # Use Crispy Bootstrap4 theme +CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap4' CRISPY_TEMPLATE_PACK = 'bootstrap4' # Use Django Table2 Bootstrap4 theme @@ -295,3 +302,6 @@ PHONENUMBER_DEFAULT_REGION = 'FR' # We add custom information to CAS, in order to give a normalized name to other services CAS_AUTH_CLASS = 'member.auth.CustomAuthUser' + +# Default field for primary key +DEFAULT_AUTO_FIELD = "django.db.models.AutoField" diff --git a/note_kfet/templates/autocomplete_model.html b/note_kfet/templates/autocomplete_model.html index fa24213f..5ffe971d 100644 --- a/note_kfet/templates/autocomplete_model.html +++ b/note_kfet/templates/autocomplete_model.html @@ -8,7 +8,7 @@ SPDX-License-Identifier: GPL-3.0-or-later {% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %} name="{{ widget.name }}_name" autocomplete="off" {% for name, value in widget.attrs.items %} - {% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %} + {% if value is not False %}{{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %} {% endfor %} aria-describedby="{{widget.attrs.id}}_tooltip"> {% if widget.resetable %} diff --git a/note_kfet/templates/base.html b/note_kfet/templates/base.html index 979d6e2c..68cbf542 100644 --- a/note_kfet/templates/base.html +++ b/note_kfet/templates/base.html @@ -66,10 +66,16 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans 'Consumptions' %} {% endif %} - {% if user.is_authenticated and user|is_member:"Kfet" %} + {% if request.user.is_authenticated %} + + {% endif %} + {% if user.is_authenticated %} {% endif %} {% if "auth.user"|model_list_length >= 2 %} @@ -194,6 +200,8 @@ SPDX-License-Identifier: GPL-3.0-or-later class="text-muted">{% trans "Contact us" %} — {% trans "Technical Support" %} — + {% trans "Charte Info (FR)" %}{% trans "FAQ (FR)" %} — diff --git a/note_kfet/templates/base_search.html b/note_kfet/templates/base_search.html index f526c0e1..cb5b04b1 100644 --- a/note_kfet/templates/base_search.html +++ b/note_kfet/templates/base_search.html @@ -12,7 +12,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
+ placeholder="{% trans "Search by attribute such as name..." %}">
{% if table.data %} @@ -75,4 +75,4 @@ SPDX-License-Identifier: GPL-3.0-or-later init_table(); }); -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/note_kfet/urls.py b/note_kfet/urls.py index d222c239..b2b64dcf 100644 --- a/note_kfet/urls.py +++ b/note_kfet/urls.py @@ -21,6 +21,7 @@ urlpatterns = [ path('activity/', include('activity.urls')), path('treasury/', include('treasury.urls')), path('wei/', include('wei.urls')), + path('food/',include('food.urls')), # Include Django Contrib and Core routers path('i18n/', include('django.conf.urls.i18n')), @@ -30,9 +31,6 @@ urlpatterns = [ path('accounts/', include('django.contrib.auth.urls')), path('api/', include('api.urls')), path('permission/', include('permission.urls')), - - # Make coffee - path('coffee/', include('django_htcpcp_tea.urls')), ] # During development, serve static and media files @@ -57,6 +55,11 @@ if "debug_toolbar" in settings.INSTALLED_APPS: path('__debug__/', include(debug_toolbar.urls)), ] + urlpatterns +if "django_htcpcp_tea" in settings.INSTALLED_APPS: + # Make coffee + urlpatterns.append( + path('coffee/', include('django_htcpcp_tea.urls')) + ) handler400 = bad_request handler403 = permission_denied diff --git a/requirements.txt b/requirements.txt index f4ece220..f4a32c97 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1,20 @@ -beautifulsoup4~=4.7.1 -Django~=2.2.15 -django-bootstrap-datepicker-plus~=3.0.5 -django-cas-server~=1.2.0 -django-colorfield~=0.3.2 -django-crispy-forms~=1.7.2 -django-extensions>=2.1.4 -django-filter~=2.1 -django-htcpcp-tea~=0.3.1 -django-mailer~=2.0.1 -django-oauth-toolkit~=1.3.3 -django-phonenumber-field~=5.0.0 -django-polymorphic>=2.0.3,<3.0.0 -djangorestframework>=3.9.0,<3.13.0 -django-rest-polymorphic~=0.1.9 -django-tables2~=2.3.1 -python-memcached~=1.59 -phonenumbers~=8.9.10 -Pillow>=5.4.1 +beautifulsoup4~=4.12.3 +crispy-bootstrap4~=2023.1 +Django~=4.2.9 +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-htcpcp-tea~=0.8.1 +django-mailer~=2.3.1 +django-oauth-toolkit~=2.3.0 +django-phonenumber-field~=7.3.0 +django-polymorphic~=3.1.0 +djangorestframework~=3.14.0 +django-rest-polymorphic~=0.1.10 +django-tables2~=2.7.0 +python-memcached~=1.62 +phonenumbers~=8.13.28 +Pillow>=10.2.0 diff --git a/tox.ini b/tox.ini index ad3c6798..924ea514 100644 --- a/tox.ini +++ b/tox.ini @@ -1,13 +1,13 @@ [tox] envlist = - # Debian Buster Python - py37-django22 + # Ubuntu 22.04 Python + py310-django42 - # Ubuntu 20.04 Python - py38-django22 + # Debian Bookworm Python + py311-django42 - # Debian Bullseye Python - py39-django22 + # Ubuntu 24.04 Python + py312-django42 linters skipsdist = True @@ -51,4 +51,4 @@ max-complexity = 15 max-line-length = 160 import-order-style = google application-import-names = flake8 -format = ${cyan}%(path)s${reset}:${yellow_bold}%(row)d${reset}:${green_bold}%(col)d${reset}: ${red_bold}%(code)s${reset} %(text)s +format = %(cyan)s%(path)s%(reset)s:%(yellow)s%(bold)s%(row)d%(reset)s:%(green)s%(bold)s%(col)d%(reset)s: %(red)s%(bold)s%(code)s%(reset)s %(text)s