From c466715e8ab8ea4921b7347c8a2478cdeb82d554 Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 13 Aug 2020 15:20:15 +0200 Subject: [PATCH] Raise permission denied on CreateView if you don't have the permission to create a sample instance, see #53 --- apps/activity/models.py | 2 +- apps/activity/views.py | 48 ++++- apps/member/templates/member/club_list.html | 5 +- apps/member/tests/test_login.py | 10 ++ apps/member/views.py | 35 +++- apps/note/views.py | 10 ++ apps/permission/signals.py | 2 +- apps/permission/tests/__init__.py | 0 .../tests/test_permission_denied.py | 150 ++++++++++++++++ .../test_permission_queries.py} | 2 +- apps/permission/views.py | 28 ++- apps/treasury/views.py | 53 +++++- apps/wei/views.py | 80 ++++++++- locale/de/LC_MESSAGES/django.po | 165 +++++++++-------- locale/fr/LC_MESSAGES/django.po | 167 ++++++++++-------- 15 files changed, 584 insertions(+), 173 deletions(-) create mode 100644 apps/permission/tests/__init__.py create mode 100644 apps/permission/tests/test_permission_denied.py rename apps/permission/{test.py => tests/test_permission_queries.py} (98%) diff --git a/apps/activity/models.py b/apps/activity/models.py index 86c30656..89a51d73 100644 --- a/apps/activity/models.py +++ b/apps/activity/models.py @@ -124,7 +124,7 @@ class Activity(models.Model): Update the activity wiki page each time the activity is updated (validation, change description, ...) """ ret = super().save(*args, **kwargs) - if self.pk and "scripts" in settings.INSTALLED_APPS: + if settings.DEBUG and self.pk and "scripts" in settings.INSTALLED_APPS: def refresh_activities(): from scripts.management.commands.refresh_activities import Command as RefreshActivitiesCommand RefreshActivitiesCommand.refresh_human_readable_wiki_page("Modification de l'activité " + self.name) diff --git a/apps/activity/views.py b/apps/activity/views.py index 170f8003..3d596a3f 100644 --- a/apps/activity/views.py +++ b/apps/activity/views.py @@ -4,26 +4,39 @@ from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import PermissionDenied from django.db.models import F, Q from django.urls import reverse_lazy from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, DetailView, TemplateView, UpdateView +from django.views.generic import DetailView, TemplateView, UpdateView from django_tables2.views import SingleTableView from note.models import Alias, NoteSpecial, NoteUser from permission.backends import PermissionBackend -from permission.views import ProtectQuerysetMixin +from permission.views import ProtectQuerysetMixin, ProtectedCreateView from .forms import ActivityForm, GuestForm from .models import Activity, Entry, Guest from .tables import ActivityTable, EntryTable, GuestTable -class ActivityCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): +class ActivityCreateView(LoginRequiredMixin, ProtectedCreateView): model = Activity form_class = ActivityForm extra_context = {"title": _("Create new activity")} + def get_sample_object(self): + return Activity( + name="", + description="", + creater=self.request.user, + activity_type_id=1, + organizer_id=1, + attendees_club_id=1, + date_start=timezone.now(), + date_end=timezone.now(), + ) + def form_valid(self, form): form.instance.creater = self.request.user return super().form_valid(form) @@ -85,11 +98,20 @@ class ActivityUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]}) -class ActivityInviteView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): +class ActivityInviteView(ProtectQuerysetMixin, LoginRequiredMixin, ProtectedCreateView): model = Guest form_class = GuestForm template_name = "activity/activity_invite.html" + def get_sample_object(self): + activity = Activity.objects.get(pk=self.kwargs["pk"]) + return Guest( + activity=activity, + first_name="", + last_name="", + inviter=self.request.user.note, + ) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) activity = context["form"].activity @@ -114,6 +136,24 @@ class ActivityInviteView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): class ActivityEntryView(LoginRequiredMixin, TemplateView): template_name = "activity/activity_entry.html" + def dispatch(self, request, *args, **kwargs): + """ + Don't display the entry interface if the user has no right to see it (no right to add an entry for itself), + it is closed or doesn't manage entries. + """ + activity = Activity.objects.get(pk=self.kwargs["pk"]) + + sample_entry = Entry(activity=activity, note=self.request.user.note) + if not PermissionBackend.check_perm(self.request.user, "activity.add_entry", sample_entry): + raise PermissionDenied(_("You are not allowed to display the entry interface for this activity.")) + + if not activity.activity_type.manage_entries: + raise PermissionDenied(_("This activity does not support activity entries.")) + + if not activity.open: + raise PermissionDenied(_("This activity is closed.")) + return super().dispatch(request, *args, **kwargs) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) diff --git a/apps/member/templates/member/club_list.html b/apps/member/templates/member/club_list.html index 9b06e924..0ae4d5b8 100644 --- a/apps/member/templates/member/club_list.html +++ b/apps/member/templates/member/club_list.html @@ -1,12 +1,15 @@ {% extends "base.html" %} {% load render_table from django_tables2 %} {% load i18n %} + {% block content %}

- {% trans "Create club" %} + {% if can_add_club %} + {% trans "Create club" %} + {% endif %}
diff --git a/apps/member/tests/test_login.py b/apps/member/tests/test_login.py index 6b712687..51a4ab94 100644 --- a/apps/member/tests/test_login.py +++ b/apps/member/tests/test_login.py @@ -3,6 +3,7 @@ from django.contrib.auth.models import User from django.test import TestCase +from note.models import TransactionTemplate, TemplateCategory """ Test that login page still works @@ -16,6 +17,8 @@ class TemplateLoggedOutTests(TestCase): class TemplateLoggedInTests(TestCase): + fixtures = ('initial', ) + def setUp(self): self.user = User.objects.create_superuser( username="admin", @@ -48,5 +51,12 @@ class TemplateLoggedInTests(TestCase): self.assertEqual(response.status_code, 200) def test_consos_page(self): + # Create one button and ensure that it is visible + cat = TemplateCategory.objects.create() + TransactionTemplate.objects.create( + destination_id=5, + category=cat, + amount=0, + ) response = self.client.get('/note/consos/') self.assertEqual(response.status_code, 200) diff --git a/apps/member/views.py b/apps/member/views.py index ebcd9d7b..332f0047 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -15,7 +15,7 @@ from django.shortcuts import redirect from django.urls import reverse_lazy from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, DetailView, UpdateView, TemplateView +from django.views.generic import DetailView, UpdateView, TemplateView from django.views.generic.edit import FormMixin from django_tables2.views import SingleTableView from rest_framework.authtoken.models import Token @@ -26,7 +26,7 @@ from note.tables import HistoryTable, AliasTable from note_kfet.middlewares import _set_current_user_and_ip from permission.backends import PermissionBackend from permission.models import Role -from permission.views import ProtectQuerysetMixin +from permission.views import ProtectQuerysetMixin, ProtectedCreateView from .forms import ProfileForm, ClubForm, MembershipForm, CustomAuthenticationForm, UserForm, MembershipRolesForm from .models import Club, Membership @@ -295,7 +295,7 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView): # ******************************* # -class ClubCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): +class ClubCreateView(ProtectQuerysetMixin, LoginRequiredMixin, ProtectedCreateView): """ Create Club """ @@ -304,6 +304,12 @@ class ClubCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): success_url = reverse_lazy('member:club_list') extra_context = {"title": _("Create new club")} + def get_sample_object(self): + return Club( + name="", + email="", + ) + def form_valid(self, form): return super().form_valid(form) @@ -332,6 +338,14 @@ class ClubListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): return qs + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["can_add_club"] = PermissionBackend.check_perm(self.request.user, "member.add_club", Club( + name="", + email="club@example.com", + )) + return context + class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): """ @@ -432,7 +446,7 @@ class ClubPictureUpdateView(PictureUpdateView): return reverse_lazy('member:club_detail', kwargs={'pk': self.object.id}) -class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): +class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, ProtectedCreateView): """ Add a membership to a club. """ @@ -441,6 +455,19 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): template_name = 'member/add_members.html' extra_context = {"title": _("Add new member to the club")} + def get_sample_object(self): + if "club_pk" in self.kwargs: + club = Club.objects.get(pk=self.kwargs["club_pk"]) + else: + club = Membership.objects.get(pk=self.kwargs["pk"]).club + return Membership( + user=self.request.user, + club=club, + fee=0, + date_start=timezone.now(), + date_end=timezone.now() + timedelta(days=1), + ) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) form = context['form'] diff --git a/apps/note/views.py b/apps/note/views.py index 169920e9..01fa9048 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -1,10 +1,12 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later + import json from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import PermissionDenied from django.db.models import Q, F from django.utils.translation import gettext_lazy as _ from django.views.generic import CreateView, UpdateView, DetailView @@ -145,6 +147,14 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): # Transaction history table table_class = HistoryTable + def dispatch(self, request, *args, **kwargs): + templates = TransactionTemplate.objects.filter( + PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view") + ) + if not templates.exists(): + raise PermissionDenied(_("You can't see any button.")) + return super().dispatch(request, *args, **kwargs) + def get_queryset(self, **kwargs): return Transaction.objects.filter( PermissionBackend.filter_queryset(self.request.user, Transaction, "view") diff --git a/apps/permission/signals.py b/apps/permission/signals.py index ef21c36e..112247eb 100644 --- a/apps/permission/signals.py +++ b/apps/permission/signals.py @@ -70,7 +70,7 @@ def pre_save_object(sender, instance, **kwargs): if not has_perm: raise PermissionDenied( - _("You don't have the permission to add this instance of model {app_label}.{model_name}.") + _("You don't have the permission to add an instance of model {app_label}.{model_name}.") .format(app_label=app_label, model_name=model_name, )) diff --git a/apps/permission/tests/__init__.py b/apps/permission/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/permission/tests/test_permission_denied.py b/apps/permission/tests/test_permission_denied.py new file mode 100644 index 00000000..80bf4129 --- /dev/null +++ b/apps/permission/tests/test_permission_denied.py @@ -0,0 +1,150 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from datetime import timedelta + +from django.contrib.auth.models import User +from django.test import TestCase +from django.urls import reverse +from django.utils import timezone +from activity.models import Activity +from member.models import Club, Membership +from note.models import NoteUser +from wei.models import WEIClub, Bus, WEIRegistration + + +class TestPermissionDenied(TestCase): + """ + Load some protected pages and check that we have 403 errors. + """ + fixtures = ('initial',) + + def setUp(self) -> None: + # Create sample user with no rights + self.user = User.objects.create( + username="toto", + ) + NoteUser.objects.create(user=self.user) + self.client.force_login(self.user) + + def test_consos(self): + response = self.client.get(reverse("note:consos")) + self.assertEqual(response.status_code, 403) + + def test_create_activity(self): + response = self.client.get(reverse("activity:activity_create")) + self.assertEqual(response.status_code, 403) + + def test_activity_entries(self): + activity = Activity.objects.create( + name="", + description="", + creater=self.user, + activity_type_id=1, + organizer_id=1, + attendees_club_id=1, + date_start=timezone.now(), + date_end=timezone.now(), + ) + response = self.client.get(reverse("activity:activity_entry", kwargs=dict(pk=activity.pk))) + self.assertEqual(response.status_code, 403) + + def test_invite_activity(self): + activity = Activity.objects.create( + name="", + description="", + creater=self.user, + activity_type_id=1, + organizer_id=1, + attendees_club_id=1, + date_start=timezone.now(), + date_end=timezone.now(), + ) + response = self.client.get(reverse("activity:activity_invite", kwargs=dict(pk=activity.pk))) + self.assertEqual(response.status_code, 403) + + def test_create_club(self): + response = self.client.get(reverse("member:club_create")) + self.assertEqual(response.status_code, 403) + + def test_add_member_club(self): + club = Club.objects.create() + response = self.client.get(reverse("member:club_add_member", kwargs=dict(club_pk=club.pk))) + self.assertEqual(response.status_code, 403) + + def test_renew_membership(self): + club = Club.objects.create() + membership = Membership.objects.create(user=self.user, club=club) + response = self.client.get(reverse("member:club_renew_membership", kwargs=dict(pk=membership.pk))) + self.assertEqual(response.status_code, 403) + + def test_create_weiclub(self): + response = self.client.get(reverse("wei:wei_create")) + self.assertEqual(response.status_code, 403) + + def test_create_wei_bus(self): + wei = WEIClub.objects.create( + membership_start=timezone.now().date(), + date_start=timezone.now().date() + timedelta(days=1), + date_end=timezone.now().date() + timedelta(days=1), + ) + response = self.client.get(reverse("wei:add_bus", kwargs=dict(pk=wei.pk))) + self.assertEqual(response.status_code, 403) + + def test_create_wei_team(self): + wei = WEIClub.objects.create( + membership_start=timezone.now().date(), + date_start=timezone.now().date() + timedelta(days=1), + date_end=timezone.now().date() + timedelta(days=1), + ) + bus = Bus.objects.create(wei=wei) + response = self.client.get(reverse("wei:add_team", kwargs=dict(pk=bus.pk))) + self.assertEqual(response.status_code, 403) + + def test_create_1a_weiregistration(self): + wei = WEIClub.objects.create( + membership_start=timezone.now().date(), + date_start=timezone.now().date() + timedelta(days=1), + date_end=timezone.now().date() + timedelta(days=1), + ) + response = self.client.get(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=wei.pk))) + self.assertEqual(response.status_code, 403) + + def test_create_old_weiregistration(self): + wei = WEIClub.objects.create( + membership_start=timezone.now().date(), + date_start=timezone.now().date() + timedelta(days=1), + date_end=timezone.now().date() + timedelta(days=1), + ) + response = self.client.get(reverse("wei:wei_register_2A", kwargs=dict(wei_pk=wei.pk))) + self.assertEqual(response.status_code, 403) + + def test_validate_weiregistration(self): + wei = WEIClub.objects.create( + membership_start=timezone.now().date(), + date_start=timezone.now().date() + timedelta(days=1), + date_end=timezone.now().date() + timedelta(days=1), + ) + registration = WEIRegistration.objects.create(wei=wei, user=self.user, birth_date="2000-01-01") + response = self.client.get(reverse("wei:validate_registration", kwargs=dict(pk=registration.pk))) + self.assertEqual(response.status_code, 403) + + def test_create_invoice(self): + response = self.client.get(reverse("treasury:invoice_create")) + self.assertEqual(response.status_code, 403) + + def test_list_invoices(self): + response = self.client.get(reverse("treasury:invoice_list")) + self.assertEqual(response.status_code, 403) + + def test_create_remittance(self): + response = self.client.get(reverse("treasury:remittance_create")) + self.assertEqual(response.status_code, 403) + + def test_list_remittance(self): + response = self.client.get(reverse("treasury:remittance_list")) + self.assertEqual(response.status_code, 403) + + def test_list_soge_credits(self): + response = self.client.get(reverse("treasury:soge_credits")) + self.assertEqual(response.status_code, 403) diff --git a/apps/permission/test.py b/apps/permission/tests/test_permission_queries.py similarity index 98% rename from apps/permission/test.py rename to apps/permission/tests/test_permission_queries.py index e8452e8a..913893e7 100644 --- a/apps/permission/test.py +++ b/apps/permission/tests/test_permission_queries.py @@ -10,7 +10,7 @@ from member.models import Club, Membership from note.models import NoteUser, Note, NoteClub, NoteSpecial from wei.models import WEIMembership, WEIRegistration, WEIClub, Bus, BusTeam -from .models import Permission +from ..models import Permission class PermissionQueryTestCase(TestCase): diff --git a/apps/permission/views.py b/apps/permission/views.py index 113f0443..4b59204b 100644 --- a/apps/permission/views.py +++ b/apps/permission/views.py @@ -1,12 +1,14 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later + from datetime import date +from django.core.exceptions import PermissionDenied from django.db.models import Q from django.forms import HiddenInput from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from django.views.generic import UpdateView, TemplateView +from django.views.generic import UpdateView, TemplateView, CreateView from member.models import Membership from .backends import PermissionBackend @@ -42,6 +44,30 @@ class ProtectQuerysetMixin: return form +class ProtectedCreateView(CreateView): + """ + Extends a CreateView to check is the user has the right to create a sample instance of the given Model. + If not, a 403 error is displayed. + """ + + def get_sample_object(self): + """ + return a sample instance of the Model. + It should be valid (can be stored properly in database), but must not collide with existing data. + """ + raise NotImplementedError + + def dispatch(self, request, *args, **kwargs): + model_class = self.model + # noinspection PyProtectedMember + app_label, model_name = model_class._meta.app_label, model_class._meta.model_name.lower() + perm = app_label + ".add_" + model_name + if not PermissionBackend.check_perm(request.user, perm, self.get_sample_object()): + raise PermissionDenied(_("You don't have the permission to add an instance of model " + "{app_label}.{model_name}.").format(app_label=app_label, model_name=model_name)) + return super().dispatch(request, *args, **kwargs) + + class RightsView(TemplateView): template_name = "permission/all_rights.html" extra_context = {"title": _("Rights")} diff --git a/apps/treasury/views.py b/apps/treasury/views.py index b95ce074..351253bf 100644 --- a/apps/treasury/views.py +++ b/apps/treasury/views.py @@ -8,28 +8,28 @@ from tempfile import mkdtemp from crispy_forms.helper import FormHelper from django.contrib.auth.mixins import LoginRequiredMixin -from django.core.exceptions import ValidationError +from django.core.exceptions import ValidationError, PermissionDenied from django.db.models import Q from django.forms import Form from django.http import HttpResponse from django.shortcuts import redirect from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ -from django.views.generic import CreateView, UpdateView, DetailView +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 note.models import SpecialTransaction, NoteSpecial, Alias from note_kfet.settings.base import BASE_DIR from permission.backends import PermissionBackend -from permission.views import ProtectQuerysetMixin +from permission.views import ProtectQuerysetMixin, ProtectedCreateView from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, LinkTransactionToRemittanceForm from .models import Invoice, Product, Remittance, SpecialTransactionProxy, SogeCredit from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable, SogeCreditTable -class InvoiceCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): +class InvoiceCreateView(ProtectQuerysetMixin, LoginRequiredMixin, ProtectedCreateView): """ Create Invoice """ @@ -37,6 +37,15 @@ class InvoiceCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): form_class = InvoiceForm extra_context = {"title": _("Create new invoice")} + def get_sample_object(self): + return Invoice( + id=0, + object="", + description="", + name="", + address="", + ) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -72,7 +81,7 @@ class InvoiceCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): return reverse_lazy('treasury:invoice_list') -class InvoiceListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): +class InvoiceListView(LoginRequiredMixin, SingleTableView): """ List existing Invoices """ @@ -80,6 +89,18 @@ class InvoiceListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView) table_class = InvoiceTable extra_context = {"title": _("Invoices list")} + def dispatch(self, request, *args, **kwargs): + sample_invoice = Invoice( + id=0, + object="", + description="", + name="", + address="", + ) + if not PermissionBackend.check_perm(self.request.user, "treasury.add_invoice", sample_invoice): + raise PermissionDenied(_("You are not able to see the treasury interface.")) + return super().dispatch(request, *args, **kwargs) + class InvoiceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): """ @@ -194,7 +215,7 @@ class InvoiceRenderView(LoginRequiredMixin, View): return response -class RemittanceCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): +class RemittanceCreateView(ProtectQuerysetMixin, LoginRequiredMixin, ProtectedCreateView): """ Create Remittance """ @@ -202,6 +223,12 @@ class RemittanceCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView) form_class = RemittanceForm extra_context = {"title": _("Create a new remittance")} + def get_sample_object(self): + return Remittance( + remittance_type_id=1, + comment="", + ) + def get_success_url(self): return reverse_lazy('treasury:remittance_list') @@ -223,6 +250,15 @@ class RemittanceListView(LoginRequiredMixin, TemplateView): template_name = "treasury/remittance_list.html" extra_context = {"title": _("Remittances list")} + def dispatch(self, request, *args, **kwargs): + sample_remittance = Remittance( + remittance_type_id=1, + comment="", + ) + if not PermissionBackend.check_perm(self.request.user, "treasury.add_remittance", sample_remittance): + raise PermissionDenied(_("You are not able to see the treasury interface.")) + return super().dispatch(request, *args, **kwargs) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -340,6 +376,11 @@ class SogeCreditListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableVi table_class = SogeCreditTable extra_context = {"title": _("List of credits from the Société générale")} + def dispatch(self, request, *args, **kwargs): + if not self.get_queryset().exists(): + raise PermissionDenied(_("You are not able to see the treasury interface.")) + return super().dispatch(request, *args, **kwargs) + def get_queryset(self, **kwargs): """ Filter the table with the given parameter. diff --git a/apps/wei/views.py b/apps/wei/views.py index 696fece9..5b16fe3a 100644 --- a/apps/wei/views.py +++ b/apps/wei/views.py @@ -4,7 +4,7 @@ import os import shutil import subprocess -from datetime import datetime, date +from datetime import datetime, date, timedelta from tempfile import mkdtemp from django.contrib.auth.mixins import LoginRequiredMixin @@ -19,7 +19,7 @@ from django.template.loader import render_to_string from django.urls import reverse_lazy from django.utils import timezone from django.views import View -from django.views.generic import DetailView, UpdateView, CreateView, RedirectView, TemplateView +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 @@ -28,7 +28,7 @@ from note.models import Transaction, NoteClub, Alias, SpecialTransaction, NoteSp from note.tables import HistoryTable from note_kfet.settings import BASE_DIR from permission.backends import PermissionBackend -from permission.views import ProtectQuerysetMixin +from permission.views import ProtectQuerysetMixin, ProtectedCreateView from .forms.registration import WEIChooseBusForm from .models import WEIClub, WEIRegistration, WEIMembership, Bus, BusTeam, WEIRole @@ -58,6 +58,8 @@ class WEIListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["can_create_wei"] = PermissionBackend.check_perm(self.request.user, "wei.add_weiclub", WEIClub( + name="", + email="weiclub@example.com", year=0, date_start=timezone.now().date(), date_end=timezone.now().date(), @@ -65,14 +67,24 @@ class WEIListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): return context -class WEICreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): +class WEICreateView(ProtectQuerysetMixin, LoginRequiredMixin, ProtectedCreateView): """ Create WEI """ + model = WEIClub form_class = WEIForm extra_context = {"title": _("Create WEI")} + def get_sample_object(self): + return WEIClub( + name="", + email="weiclub@example.com", + year=0, + date_start=timezone.now().date(), + date_end=timezone.now().date(), + ) + def form_valid(self, form): form.instance.requires_membership = True form.instance.parent_club = Club.objects.get(name="Kfet") @@ -274,7 +286,7 @@ class WEIUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.pk}) -class BusCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): +class BusCreateView(ProtectQuerysetMixin, LoginRequiredMixin, ProtectedCreateView): """ Create Bus """ @@ -282,6 +294,13 @@ class BusCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): form_class = BusForm extra_context = {"title": _("Create new bus")} + def get_sample_object(self): + wei = WEIClub.objects.get(pk=self.kwargs["pk"]) + return Bus( + wei=wei, + name="", + ) + def dispatch(self, request, *args, **kwargs): wei = WEIClub.objects.get(pk=self.kwargs["pk"]) today = date.today() @@ -362,7 +381,7 @@ class BusManageView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): return context -class BusTeamCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): +class BusTeamCreateView(ProtectQuerysetMixin, LoginRequiredMixin, ProtectedCreateView): """ Create BusTeam """ @@ -370,6 +389,14 @@ class BusTeamCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): form_class = BusTeamForm extra_context = {"title": _("Create new team")} + def get_sample_object(self): + bus = Bus.objects.get(pk=self.kwargs["pk"]) + return BusTeam( + name="", + bus=bus, + color=0, + ) + def dispatch(self, request, *args, **kwargs): wei = WEIClub.objects.get(buses__pk=self.kwargs["pk"]) today = date.today() @@ -447,7 +474,7 @@ class BusTeamManageView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): return context -class WEIRegister1AView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): +class WEIRegister1AView(ProtectQuerysetMixin, LoginRequiredMixin, ProtectedCreateView): """ Register a new user to the WEI """ @@ -455,6 +482,18 @@ class WEIRegister1AView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): form_class = WEIRegistrationForm extra_context = {"title": _("Register first year student to the WEI")} + def get_sample_object(self): + wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"]) + return WEIRegistration( + wei=wei, + user=self.request.user, + first_year=True, + birth_date="1970-01-01", + gender="No", + emergency_contact_name="No", + emergency_contact_phone="No", + ) + def dispatch(self, request, *args, **kwargs): wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"]) today = date.today() @@ -502,7 +541,7 @@ class WEIRegister1AView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): return reverse_lazy("wei:wei_survey", kwargs={"pk": self.object.pk}) -class WEIRegister2AView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): +class WEIRegister2AView(ProtectQuerysetMixin, LoginRequiredMixin, ProtectedCreateView): """ Register an old user to the WEI """ @@ -510,6 +549,18 @@ class WEIRegister2AView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): form_class = WEIRegistrationForm extra_context = {"title": _("Register old student to the WEI")} + def get_sample_object(self): + wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"]) + return WEIRegistration( + wei=wei, + user=self.request.user, + first_year=True, + birth_date="1970-01-01", + gender="No", + emergency_contact_name="No", + emergency_contact_phone="No", + ) + def dispatch(self, request, *args, **kwargs): wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"]) today = date.today() @@ -713,7 +764,7 @@ class WEIDeleteRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Delete return reverse_lazy('wei:wei_detail', args=(self.object.wei.pk,)) -class WEIValidateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): +class WEIValidateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, ProtectedCreateView): """ Validate WEI Registration """ @@ -721,6 +772,17 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Crea form_class = WEIMembershipForm extra_context = {"title": _("Validate WEI registration")} + def get_sample_object(self): + registration = WEIRegistration.objects.get(pk=self.kwargs["pk"]) + return WEIMembership( + club=registration.wei, + user=registration.user, + date_start=timezone.now().date(), + date_end=timezone.now().date() + timedelta(days=1), + fee=0, + registration=registration, + ) + def dispatch(self, request, *args, **kwargs): wei = WEIRegistration.objects.get(pk=self.kwargs["pk"]).wei today = date.today() diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 4486c987..59a41d73 100644 --- a/locale/de/LC_MESSAGES/django.po +++ b/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-08-10 19:56+0200\n" +"POT-Creation-Date: 2020-08-13 15:15+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -327,27 +327,39 @@ msgstr "" msgid "All activities" msgstr "" -#: apps/activity/views.py:25 +#: apps/activity/views.py:26 msgid "Create new activity" msgstr "" -#: apps/activity/views.py:40 note_kfet/templates/base.html:128 +#: apps/activity/views.py:53 note_kfet/templates/base.html:128 msgid "Activities" msgstr "" -#: apps/activity/views.py:65 +#: apps/activity/views.py:78 msgid "Activity detail" msgstr "" -#: apps/activity/views.py:82 +#: apps/activity/views.py:95 msgid "Update activity" msgstr "" -#: apps/activity/views.py:96 +#: apps/activity/views.py:118 msgid "Invite guest to the activity \"{}\"" msgstr "" -#: apps/activity/views.py:197 +#: apps/activity/views.py:148 +msgid "You are not allowed to display the entry interface for this activity." +msgstr "" + +#: apps/activity/views.py:151 +msgid "This activity does not support activity entries." +msgstr "" + +#: apps/activity/views.py:154 +msgid "This activity is closed." +msgstr "" + +#: apps/activity/views.py:237 msgid "Entry for activity \"{}\"" msgstr "" @@ -682,7 +694,7 @@ msgstr "" msgid "The role {role} does not apply to the club {club}." msgstr "" -#: apps/member/models.py:362 apps/member/views.py:592 +#: apps/member/models.py:362 apps/member/views.py:619 msgid "User is already a member of the club" msgstr "" @@ -774,11 +786,11 @@ msgstr "" msgid "View Profile" msgstr "" -#: apps/member/templates/member/club_list.html:9 +#: apps/member/templates/member/club_list.html:11 msgid "Create club" msgstr "" -#: apps/member/templates/member/club_list.html:16 +#: apps/member/templates/member/club_list.html:19 msgid "Club listing" msgstr "" @@ -890,7 +902,7 @@ msgstr "" msgid "Search user" msgstr "" -#: apps/member/views.py:205 apps/member/views.py:391 +#: apps/member/views.py:205 apps/member/views.py:405 msgid "Note aliases" msgstr "" @@ -902,47 +914,47 @@ msgstr "" msgid "Create new club" msgstr "" -#: apps/member/views.py:317 +#: apps/member/views.py:323 msgid "Search club" msgstr "" -#: apps/member/views.py:342 +#: apps/member/views.py:356 msgid "Club detail" msgstr "" -#: apps/member/views.py:408 +#: apps/member/views.py:422 msgid "Update club" msgstr "" -#: apps/member/views.py:442 +#: apps/member/views.py:456 msgid "Add new member to the club" msgstr "" -#: apps/member/views.py:583 apps/wei/views.py:861 +#: apps/member/views.py:610 apps/wei/views.py:926 msgid "" "This user don't have enough money to join this club, and can't have a " "negative balance." msgstr "" -#: apps/member/views.py:596 +#: apps/member/views.py:623 msgid "The membership must start after {:%m-%d-%Y}." msgstr "" -#: apps/member/views.py:601 +#: apps/member/views.py:628 msgid "The membership must begin before {:%m-%d-%Y}." msgstr "" -#: apps/member/views.py:618 apps/member/views.py:620 apps/member/views.py:622 +#: apps/member/views.py:645 apps/member/views.py:647 apps/member/views.py:649 #: apps/registration/views.py:292 apps/registration/views.py:294 -#: apps/registration/views.py:296 apps/wei/views.py:866 apps/wei/views.py:870 +#: apps/registration/views.py:296 apps/wei/views.py:931 apps/wei/views.py:935 msgid "This field is required." msgstr "" -#: apps/member/views.py:706 +#: apps/member/views.py:733 msgid "Manage roles of an user in the club" msgstr "" -#: apps/member/views.py:731 +#: apps/member/views.py:758 msgid "Members of the club" msgstr "" @@ -1223,7 +1235,7 @@ msgstr "" #: apps/note/tables.py:138 apps/note/tables.py:172 apps/treasury/tables.py:39 #: apps/treasury/templates/treasury/invoice_confirm_delete.html:28 #: apps/treasury/templates/treasury/sogecredit_detail.html:59 -#: apps/wei/tables.py:75 apps/wei/tables.py:101 +#: apps/wei/tables.py:75 apps/wei/tables.py:102 #: apps/wei/templates/wei/weiregistration_confirm_delete.html:32 msgid "Delete" msgstr "" @@ -1340,27 +1352,31 @@ msgstr "" msgid "Unable to delete button " msgstr "" -#: apps/note/views.py:34 +#: apps/note/views.py:36 msgid "Transfer money" msgstr "" -#: apps/note/views.py:74 +#: apps/note/views.py:76 msgid "Create new button" msgstr "" -#: apps/note/views.py:83 +#: apps/note/views.py:85 msgid "Search button" msgstr "" -#: apps/note/views.py:106 +#: apps/note/views.py:108 msgid "Update button" msgstr "" -#: apps/note/views.py:143 note_kfet/templates/base.html:108 +#: apps/note/views.py:145 note_kfet/templates/base.html:108 msgid "Consumptions" msgstr "" -#: apps/note/views.py:179 +#: apps/note/views.py:155 +msgid "You can't see any button." +msgstr "" + +#: apps/note/views.py:189 msgid "Search transactions" msgstr "" @@ -1448,10 +1464,10 @@ msgid "" "of model {app_label}.{model_name}." msgstr "" -#: apps/permission/signals.py:73 +#: apps/permission/signals.py:73 apps/permission/views.py:66 #, python-brace-format msgid "" -"You don't have the permission to add this instance of model {app_label}." +"You don't have the permission to add an instance of model {app_label}." "{model_name}." msgstr "" @@ -1490,11 +1506,11 @@ msgstr "" msgid "No associated permission" msgstr "" -#: apps/permission/views.py:47 note_kfet/templates/base.html:143 +#: apps/permission/views.py:73 note_kfet/templates/base.html:143 msgid "Rights" msgstr "" -#: apps/permission/views.py:52 +#: apps/permission/views.py:78 msgid "All rights" msgstr "" @@ -1864,7 +1880,7 @@ msgid "No" msgstr "" #: apps/treasury/templates/treasury/invoice_confirm_delete.html:8 -#: apps/treasury/views.py:143 +#: apps/treasury/views.py:164 msgid "Delete invoice" msgstr "" @@ -1882,7 +1898,7 @@ msgid "Return to invoices list" msgstr "" #: apps/treasury/templates/treasury/invoice_form.html:6 -#: apps/treasury/views.py:81 +#: apps/treasury/views.py:90 msgid "Invoices list" msgstr "" @@ -1916,7 +1932,7 @@ msgstr "" #: apps/treasury/templates/treasury/remittance_form.html:9 #: apps/treasury/templates/treasury/specialtransactionproxy_form.html:7 -#: apps/treasury/views.py:224 +#: apps/treasury/views.py:251 msgid "Remittances list" msgstr "" @@ -2009,7 +2025,7 @@ msgid "Please ask the user to credit its note before deleting this credit." msgstr "" #: apps/treasury/templates/treasury/sogecredit_detail.html:57 -#: apps/wei/tables.py:58 apps/wei/tables.py:59 apps/wei/tables.py:96 +#: apps/wei/tables.py:58 apps/wei/tables.py:59 apps/wei/tables.py:98 msgid "Validate" msgstr "" @@ -2029,27 +2045,32 @@ msgstr "" msgid "Create new invoice" msgstr "" -#: apps/treasury/views.py:90 +#: apps/treasury/views.py:101 apps/treasury/views.py:259 +#: apps/treasury/views.py:381 +msgid "You are not able to see the treasury interface." +msgstr "" + +#: apps/treasury/views.py:111 msgid "Update an invoice" msgstr "" -#: apps/treasury/views.py:203 +#: apps/treasury/views.py:224 msgid "Create a new remittance" msgstr "" -#: apps/treasury/views.py:274 +#: apps/treasury/views.py:310 msgid "Update a remittance" msgstr "" -#: apps/treasury/views.py:297 +#: apps/treasury/views.py:333 msgid "Attach a transaction to a remittance" msgstr "" -#: apps/treasury/views.py:341 +#: apps/treasury/views.py:377 msgid "List of credits from the Société générale" msgstr "" -#: apps/treasury/views.py:375 +#: apps/treasury/views.py:416 msgid "Manage credits from the Société générale" msgstr "" @@ -2287,8 +2308,8 @@ msgstr "" #: apps/wei/templates/wei/survey.html:12 #: apps/wei/templates/wei/survey_closed.html:12 -#: apps/wei/templates/wei/survey_end.html:12 apps/wei/views.py:917 -#: apps/wei/views.py:971 apps/wei/views.py:981 +#: apps/wei/templates/wei/survey_end.html:12 apps/wei/views.py:982 +#: apps/wei/views.py:1036 apps/wei/views.py:1046 msgid "Survey WEI" msgstr "" @@ -2320,11 +2341,11 @@ msgstr "" msgid "WEI list" msgstr "" -#: apps/wei/templates/wei/weiclub_info.html:62 apps/wei/views.py:468 +#: apps/wei/templates/wei/weiclub_info.html:62 apps/wei/views.py:507 msgid "Register 1A" msgstr "" -#: apps/wei/templates/wei/weiclub_info.html:65 apps/wei/views.py:523 +#: apps/wei/templates/wei/weiclub_info.html:65 apps/wei/views.py:574 msgid "Register 2A+" msgstr "" @@ -2336,7 +2357,7 @@ msgstr "" msgid "View WEI" msgstr "" -#: apps/wei/templates/wei/weiclub_list.html:10 apps/wei/views.py:74 +#: apps/wei/templates/wei/weiclub_list.html:10 apps/wei/views.py:77 msgid "Create WEI" msgstr "" @@ -2489,93 +2510,93 @@ msgstr "" msgid "Search WEI" msgstr "" -#: apps/wei/views.py:94 +#: apps/wei/views.py:106 msgid "WEI Detail" msgstr "" -#: apps/wei/views.py:189 +#: apps/wei/views.py:201 msgid "View members of the WEI" msgstr "" -#: apps/wei/views.py:217 +#: apps/wei/views.py:229 msgid "Find WEI Membership" msgstr "" -#: apps/wei/views.py:227 +#: apps/wei/views.py:239 msgid "View registrations to the WEI" msgstr "" -#: apps/wei/views.py:251 +#: apps/wei/views.py:263 msgid "Find WEI Registration" msgstr "" -#: apps/wei/views.py:262 +#: apps/wei/views.py:274 msgid "Update the WEI" msgstr "" -#: apps/wei/views.py:283 +#: apps/wei/views.py:295 msgid "Create new bus" msgstr "" -#: apps/wei/views.py:314 +#: apps/wei/views.py:333 msgid "Update bus" msgstr "" -#: apps/wei/views.py:344 +#: apps/wei/views.py:363 msgid "Manage bus" msgstr "" -#: apps/wei/views.py:371 +#: apps/wei/views.py:390 msgid "Create new team" msgstr "" -#: apps/wei/views.py:403 +#: apps/wei/views.py:430 msgid "Update team" msgstr "" -#: apps/wei/views.py:434 +#: apps/wei/views.py:461 msgid "Manage WEI team" msgstr "" -#: apps/wei/views.py:456 +#: apps/wei/views.py:483 msgid "Register first year student to the WEI" msgstr "" -#: apps/wei/views.py:489 apps/wei/views.py:560 +#: apps/wei/views.py:528 apps/wei/views.py:611 msgid "This user is already registered to this WEI." msgstr "" -#: apps/wei/views.py:494 +#: apps/wei/views.py:533 msgid "" -"This user can't be in her/his first year since he/she has already participated " -"to a WEI." +"This user can't be in her/his first year since he/she has already " +"participated to a WEI." msgstr "" -#: apps/wei/views.py:511 +#: apps/wei/views.py:550 msgid "Register old student to the WEI" msgstr "" -#: apps/wei/views.py:542 apps/wei/views.py:627 +#: apps/wei/views.py:593 apps/wei/views.py:684 msgid "You already opened an account in the Société générale." msgstr "" -#: apps/wei/views.py:590 +#: apps/wei/views.py:641 msgid "Update WEI Registration" msgstr "" -#: apps/wei/views.py:686 +#: apps/wei/views.py:743 msgid "Delete WEI registration" msgstr "" -#: apps/wei/views.py:697 +#: apps/wei/views.py:754 msgid "You don't have the right to delete this WEI registration." msgstr "" -#: apps/wei/views.py:716 +#: apps/wei/views.py:773 msgid "Validate WEI registration" msgstr "" -#: apps/wei/views.py:855 +#: apps/wei/views.py:920 msgid "This user didn't give her/his caution check." msgstr "" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 527f3c37..9556dd6e 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-08-10 19:56+0200\n" +"POT-Creation-Date: 2020-08-13 15:15+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -328,27 +328,39 @@ msgstr "Nouvelle activité" msgid "All activities" msgstr "Toutes les activités" -#: apps/activity/views.py:25 +#: apps/activity/views.py:26 msgid "Create new activity" msgstr "Créer une nouvelle activité" -#: apps/activity/views.py:40 note_kfet/templates/base.html:128 +#: apps/activity/views.py:53 note_kfet/templates/base.html:128 msgid "Activities" msgstr "Activités" -#: apps/activity/views.py:65 +#: apps/activity/views.py:78 msgid "Activity detail" msgstr "Détails de l'activité" -#: apps/activity/views.py:82 +#: apps/activity/views.py:95 msgid "Update activity" msgstr "Modifier l'activité" -#: apps/activity/views.py:96 +#: apps/activity/views.py:118 msgid "Invite guest to the activity \"{}\"" msgstr "Invitation pour l'activité « {} »" -#: apps/activity/views.py:197 +#: apps/activity/views.py:148 +msgid "You are not allowed to display the entry interface for this activity." +msgstr "Vous n'êtes pas autorisé à afficher l'interface des entrées pour cette activité." + +#: apps/activity/views.py:151 +msgid "This activity does not support activity entries." +msgstr "Cette activité ne requiert pas d'entrées." + +#: apps/activity/views.py:154 +msgid "This activity is closed." +msgstr "Cette activité est fermée." + +#: apps/activity/views.py:237 msgid "Entry for activity \"{}\"" msgstr "Entrées pour l'activité « {} »" @@ -686,7 +698,7 @@ msgstr "l'adhésion finit le" 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:362 apps/member/views.py:592 +#: apps/member/models.py:362 apps/member/views.py:619 msgid "User is already a member of the club" msgstr "L'utilisateur est déjà membre du club" @@ -783,11 +795,11 @@ msgstr "Éditer" msgid "View Profile" msgstr "Voir le profil" -#: apps/member/templates/member/club_list.html:9 +#: apps/member/templates/member/club_list.html:11 msgid "Create club" msgstr "Créer un club" -#: apps/member/templates/member/club_list.html:16 +#: apps/member/templates/member/club_list.html:19 msgid "Club listing" msgstr "Liste des clubs" @@ -899,7 +911,7 @@ msgstr "Détails de l'utilisateur" msgid "Search user" msgstr "Chercher un utilisateur" -#: apps/member/views.py:205 apps/member/views.py:391 +#: apps/member/views.py:205 apps/member/views.py:405 msgid "Note aliases" msgstr "Alias de la note" @@ -911,23 +923,23 @@ msgstr "Modifier la photo de la note" msgid "Create new club" msgstr "Créer un nouveau club" -#: apps/member/views.py:317 +#: apps/member/views.py:323 msgid "Search club" msgstr "Chercher un club" -#: apps/member/views.py:342 +#: apps/member/views.py:356 msgid "Club detail" msgstr "Détails du club" -#: apps/member/views.py:408 +#: apps/member/views.py:422 msgid "Update club" msgstr "Modifier le club" -#: apps/member/views.py:442 +#: apps/member/views.py:456 msgid "Add new member to the club" msgstr "Ajouter un nouveau membre au club" -#: apps/member/views.py:583 apps/wei/views.py:861 +#: apps/member/views.py:610 apps/wei/views.py:926 msgid "" "This user don't have enough money to join this club, and can't have a " "negative balance." @@ -935,25 +947,25 @@ msgstr "" "Cet utilisateur n'a pas assez d'argent pour rejoindre ce club et ne peut pas " "avoir un solde négatif." -#: apps/member/views.py:596 +#: apps/member/views.py:623 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:601 +#: apps/member/views.py:628 msgid "The membership must begin before {:%m-%d-%Y}." msgstr "L'adhésion doit commencer avant le {:%d/%m/%Y}." -#: apps/member/views.py:618 apps/member/views.py:620 apps/member/views.py:622 +#: apps/member/views.py:645 apps/member/views.py:647 apps/member/views.py:649 #: apps/registration/views.py:292 apps/registration/views.py:294 -#: apps/registration/views.py:296 apps/wei/views.py:866 apps/wei/views.py:870 +#: apps/registration/views.py:296 apps/wei/views.py:931 apps/wei/views.py:935 msgid "This field is required." msgstr "Ce champ est requis." -#: apps/member/views.py:706 +#: apps/member/views.py:733 msgid "Manage roles of an user in the club" msgstr "Gérer les rôles d'un utilisateur dans le club" -#: apps/member/views.py:731 +#: apps/member/views.py:758 msgid "Members of the club" msgstr "Membres du club" @@ -1241,7 +1253,7 @@ msgstr "Pas de motif spécifié" #: apps/note/tables.py:138 apps/note/tables.py:172 apps/treasury/tables.py:39 #: apps/treasury/templates/treasury/invoice_confirm_delete.html:28 #: apps/treasury/templates/treasury/sogecredit_detail.html:59 -#: apps/wei/tables.py:75 apps/wei/tables.py:101 +#: apps/wei/tables.py:75 apps/wei/tables.py:102 #: apps/wei/templates/wei/weiregistration_confirm_delete.html:32 msgid "Delete" msgstr "Supprimer" @@ -1358,27 +1370,31 @@ msgstr "Le bouton a bien été supprimé" msgid "Unable to delete button " msgstr "Impossible de supprimer le bouton " -#: apps/note/views.py:34 +#: apps/note/views.py:36 msgid "Transfer money" msgstr "Transférer de l'argent" -#: apps/note/views.py:74 +#: apps/note/views.py:76 msgid "Create new button" msgstr "Créer un nouveau bouton" -#: apps/note/views.py:83 +#: apps/note/views.py:85 msgid "Search button" msgstr "Chercher un bouton" -#: apps/note/views.py:106 +#: apps/note/views.py:108 msgid "Update button" msgstr "Modifier le bouton" -#: apps/note/views.py:143 note_kfet/templates/base.html:108 +#: apps/note/views.py:145 note_kfet/templates/base.html:108 msgid "Consumptions" msgstr "Consommations" -#: apps/note/views.py:179 +#: apps/note/views.py:155 +msgid "You can't see any button." +msgstr "Vous ne pouvez pas voir le moindre bouton." + +#: apps/note/views.py:189 msgid "Search transactions" msgstr "Rechercher des transactions" @@ -1472,13 +1488,13 @@ msgstr "" "Vous n'avez pas la permission de modifier le champ {field} sur l'instance du " "modèle {app_label}.{model_name}." -#: apps/permission/signals.py:73 +#: apps/permission/signals.py:73 apps/permission/views.py:66 #, python-brace-format msgid "" -"You don't have the permission to add this instance of model {app_label}." +"You don't have the permission to add an instance of model {app_label}." "{model_name}." msgstr "" -"Vous n'avez pas la permission d'ajouter cette instance du modèle {app_label}." +"Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}." "{model_name}." #: apps/permission/signals.py:101 @@ -1518,11 +1534,11 @@ msgstr "Requête :" msgid "No associated permission" msgstr "Pas de permission associée" -#: apps/permission/views.py:47 note_kfet/templates/base.html:143 +#: apps/permission/views.py:73 note_kfet/templates/base.html:143 msgid "Rights" msgstr "Droits" -#: apps/permission/views.py:52 +#: apps/permission/views.py:78 msgid "All rights" msgstr "Tous les droits" @@ -1912,7 +1928,7 @@ msgid "No" msgstr "Non" #: apps/treasury/templates/treasury/invoice_confirm_delete.html:8 -#: apps/treasury/views.py:143 +#: apps/treasury/views.py:164 msgid "Delete invoice" msgstr "Supprimer la facture" @@ -1932,7 +1948,7 @@ msgid "Return to invoices list" msgstr "Retour à la liste des factures" #: apps/treasury/templates/treasury/invoice_form.html:6 -#: apps/treasury/views.py:81 +#: apps/treasury/views.py:90 msgid "Invoices list" msgstr "Liste des factures" @@ -1969,7 +1985,7 @@ msgstr "Remise n°" #: apps/treasury/templates/treasury/remittance_form.html:9 #: apps/treasury/templates/treasury/specialtransactionproxy_form.html:7 -#: apps/treasury/views.py:224 +#: apps/treasury/views.py:251 msgid "Remittances list" msgstr "Liste des remises" @@ -2073,7 +2089,7 @@ msgstr "" "demande de crédit." #: apps/treasury/templates/treasury/sogecredit_detail.html:57 -#: apps/wei/tables.py:58 apps/wei/tables.py:59 apps/wei/tables.py:96 +#: apps/wei/tables.py:58 apps/wei/tables.py:59 apps/wei/tables.py:98 msgid "Validate" msgstr "Valider" @@ -2095,27 +2111,32 @@ msgstr "" msgid "Create new invoice" msgstr "Créer une nouvelle facture" -#: apps/treasury/views.py:90 +#: apps/treasury/views.py:101 apps/treasury/views.py:259 +#: apps/treasury/views.py:381 +msgid "You are not able to see the treasury interface." +msgstr "Vous n'êtes pas autorisé à voir l'interface de trésorerie." + +#: apps/treasury/views.py:111 msgid "Update an invoice" msgstr "Modifier la facture" -#: apps/treasury/views.py:203 +#: apps/treasury/views.py:224 msgid "Create a new remittance" msgstr "Créer une nouvelle remise" -#: apps/treasury/views.py:274 +#: apps/treasury/views.py:310 msgid "Update a remittance" msgstr "Modifier la remise" -#: apps/treasury/views.py:297 +#: apps/treasury/views.py:333 msgid "Attach a transaction to a remittance" msgstr "Joindre une transaction à une remise" -#: apps/treasury/views.py:341 +#: apps/treasury/views.py:377 msgid "List of credits from the Société générale" msgstr "Liste des crédits de la Société générale" -#: apps/treasury/views.py:375 +#: apps/treasury/views.py:416 msgid "Manage credits from the Société générale" msgstr "Gérer les crédits de la Société générale" @@ -2368,8 +2389,8 @@ msgstr "Télécharger au format PDF" #: apps/wei/templates/wei/survey.html:12 #: apps/wei/templates/wei/survey_closed.html:12 -#: apps/wei/templates/wei/survey_end.html:12 apps/wei/views.py:917 -#: apps/wei/views.py:971 apps/wei/views.py:981 +#: apps/wei/templates/wei/survey_end.html:12 apps/wei/views.py:982 +#: apps/wei/views.py:1036 apps/wei/views.py:1046 msgid "Survey WEI" msgstr "Questionnaire WEI" @@ -2402,11 +2423,11 @@ msgstr "Prix du WEI / incluant l'adhésion BDE/Kfet (étudiants)" msgid "WEI list" msgstr "Liste des WEI" -#: apps/wei/templates/wei/weiclub_info.html:62 apps/wei/views.py:468 +#: apps/wei/templates/wei/weiclub_info.html:62 apps/wei/views.py:507 msgid "Register 1A" msgstr "Inscrire un 1A" -#: apps/wei/templates/wei/weiclub_info.html:65 apps/wei/views.py:523 +#: apps/wei/templates/wei/weiclub_info.html:65 apps/wei/views.py:574 msgid "Register 2A+" msgstr "Inscrire un 2A+" @@ -2418,7 +2439,7 @@ msgstr "Ajouter un bus" msgid "View WEI" msgstr "Voir le WEI" -#: apps/wei/templates/wei/weiclub_list.html:10 apps/wei/views.py:74 +#: apps/wei/templates/wei/weiclub_list.html:10 apps/wei/views.py:77 msgid "Create WEI" msgstr "Créer un WEI" @@ -2584,95 +2605,95 @@ msgstr "Voir les adhésions validées ..." msgid "Search WEI" msgstr "Chercher un WEI" -#: apps/wei/views.py:94 +#: apps/wei/views.py:106 msgid "WEI Detail" msgstr "Détails du WEI" -#: apps/wei/views.py:189 +#: apps/wei/views.py:201 msgid "View members of the WEI" msgstr "Voir les membres du WEI" -#: apps/wei/views.py:217 +#: apps/wei/views.py:229 msgid "Find WEI Membership" msgstr "Trouver une adhésion au WEI" -#: apps/wei/views.py:227 +#: apps/wei/views.py:239 msgid "View registrations to the WEI" msgstr "Voir les inscriptions au WEI" -#: apps/wei/views.py:251 +#: apps/wei/views.py:263 msgid "Find WEI Registration" msgstr "Trouver une inscription au WEI" -#: apps/wei/views.py:262 +#: apps/wei/views.py:274 msgid "Update the WEI" msgstr "Modifier le WEI" -#: apps/wei/views.py:283 +#: apps/wei/views.py:295 msgid "Create new bus" msgstr "Ajouter un nouveau bus" -#: apps/wei/views.py:314 +#: apps/wei/views.py:333 msgid "Update bus" msgstr "Modifier le bus" -#: apps/wei/views.py:344 +#: apps/wei/views.py:363 msgid "Manage bus" msgstr "Gérer le bus" -#: apps/wei/views.py:371 +#: apps/wei/views.py:390 msgid "Create new team" msgstr "Créer une nouvelle équipe" -#: apps/wei/views.py:403 +#: apps/wei/views.py:430 msgid "Update team" msgstr "Modifier l'équipe" -#: apps/wei/views.py:434 +#: apps/wei/views.py:461 msgid "Manage WEI team" msgstr "Gérer l'équipe WEI" -#: apps/wei/views.py:456 +#: apps/wei/views.py:483 msgid "Register first year student to the WEI" msgstr "Inscrire un 1A au WEI" -#: apps/wei/views.py:489 apps/wei/views.py:560 +#: apps/wei/views.py:528 apps/wei/views.py:611 msgid "This user is already registered to this WEI." msgstr "Cette personne est déjà inscrite au WEI." -#: apps/wei/views.py:494 +#: apps/wei/views.py:533 msgid "" -"This user can't be in her/his first year since he/she has already participated " -"to a WEI." +"This user can't be in her/his first year since he/she has already " +"participated to a WEI." msgstr "" "Cet utilisateur ne peut pas être en première année puisqu'iel a déjà " "participé à un WEI." -#: apps/wei/views.py:511 +#: apps/wei/views.py:550 msgid "Register old student to the WEI" msgstr "Inscrire un 2A+ au WEI" -#: apps/wei/views.py:542 apps/wei/views.py:627 +#: apps/wei/views.py:593 apps/wei/views.py:684 msgid "You already opened an account in the Société générale." msgstr "Vous avez déjà ouvert un compte auprès de la société générale." -#: apps/wei/views.py:590 +#: apps/wei/views.py:641 msgid "Update WEI Registration" msgstr "Modifier l'inscription WEI" -#: apps/wei/views.py:686 +#: apps/wei/views.py:743 msgid "Delete WEI registration" msgstr "Supprimer l'inscription WEI" -#: apps/wei/views.py:697 +#: apps/wei/views.py:754 msgid "You don't have the right to delete this WEI registration." msgstr "Vous n'avez pas la permission de supprimer cette inscription au WEI." -#: apps/wei/views.py:716 +#: apps/wei/views.py:773 msgid "Validate WEI registration" msgstr "Valider l'inscription WEI" -#: apps/wei/views.py:855 +#: apps/wei/views.py:920 msgid "This user didn't give her/his caution check." msgstr "Cet utilisateur n'a pas donné son chèque de caution."