diff --git a/apps/activity/fixtures/initial.json b/apps/activity/fixtures/initial.json new file mode 100644 index 00000000..1856bce4 --- /dev/null +++ b/apps/activity/fixtures/initial.json @@ -0,0 +1,20 @@ +[ + { + "model": "activity.activitytype", + "pk": 1, + "fields": { + "name": "Pot", + "can_invite": true, + "guest_entry_fee": 500 + } + }, + { + "model": "activity.activitytype", + "pk": 2, + "fields": { + "name": "Soir\u00e9e de club", + "can_invite": false, + "guest_entry_fee": 0 + } + } +] \ No newline at end of file diff --git a/apps/activity/models.py b/apps/activity/models.py index ed2d94c9..215aa0cc 100644 --- a/apps/activity/models.py +++ b/apps/activity/models.py @@ -130,6 +130,8 @@ class Entry(models.Model): class Meta: unique_together = (('activity', 'note', 'guest', ), ) + verbose_name = _("entry") + verbose_name_plural = _("entries") def save(self, force_insert=False, force_update=False, using=None, update_fields=None): diff --git a/apps/activity/views.py b/apps/activity/views.py index 34670d36..1226610f 100644 --- a/apps/activity/views.py +++ b/apps/activity/views.py @@ -154,4 +154,8 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView): ctx["noteuser_ctype"] = ContentType.objects.get_for_model(NoteUser).pk ctx["notespecial_ctype"] = ContentType.objects.get_for_model(NoteSpecial).pk + ctx["activities_open"] = Activity.objects.filter(open=True).filter( + PermissionBackend.filter_queryset(self.request.user, Activity, "view")).filter( + PermissionBackend.filter_queryset(self.request.user, Activity, "change")).all() + return ctx diff --git a/apps/logs/models.py b/apps/logs/models.py index 10e2651f..94e2b4ce 100644 --- a/apps/logs/models.py +++ b/apps/logs/models.py @@ -75,3 +75,7 @@ class Changelog(models.Model): def delete(self, using=None, keep_parents=False): raise ValidationError(_("Logs cannot be destroyed.")) + + class Meta: + verbose_name = _("changelog") + verbose_name_plural = _("changelogs") diff --git a/apps/member/forms.py b/apps/member/forms.py index a37d143e..6fe95f5a 100644 --- a/apps/member/forms.py +++ b/apps/member/forms.py @@ -2,8 +2,10 @@ # SPDX-License-Identifier: GPL-3.0-or-later from django import forms -from django.contrib.auth.forms import UserCreationForm, AuthenticationForm +from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.models import User +from django.utils.translation import gettext_lazy as _ +from note.models import NoteSpecial from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput from permission.models import PermissionMask @@ -18,17 +20,6 @@ class CustomAuthenticationForm(AuthenticationForm): ) -class SignUpForm(UserCreationForm): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.fields['username'].widget.attrs.pop("autofocus", None) - self.fields['first_name'].widget.attrs.update({"autofocus": "autofocus"}) - - class Meta: - model = User - fields = ['first_name', 'last_name', 'username', 'email'] - - class ProfileForm(forms.ModelForm): """ A form for the extras field provided by the :model:`member.Profile` model. @@ -37,7 +28,7 @@ class ProfileForm(forms.ModelForm): class Meta: model = Profile fields = '__all__' - exclude = ['user'] + exclude = ('user', 'email_confirmed', 'registration_valid', 'soge', ) class ClubForm(forms.ModelForm): @@ -59,6 +50,42 @@ class ClubForm(forms.ModelForm): class MembershipForm(forms.ModelForm): + soge = forms.BooleanField( + label=_("Inscription paid by Société Générale"), + required=False, + help_text=_("Check this case is the Société Générale paid the inscription."), + ) + + credit_type = forms.ModelChoiceField( + queryset=NoteSpecial.objects, + label=_("Credit type"), + empty_label=_("No credit"), + required=False, + help_text=_("You can credit the note of the user."), + ) + + credit_amount = forms.IntegerField( + label=_("Credit amount"), + required=False, + initial=0, + widget=AmountInput(), + ) + + last_name = forms.CharField( + label=_("Last name"), + required=False, + ) + + first_name = forms.CharField( + label=_("First name"), + required=False, + ) + + bank = forms.CharField( + label=_("Bank"), + required=False, + ) + class Meta: model = Membership fields = ('user', 'roles', 'date_start') diff --git a/apps/member/models.py b/apps/member/models.py index 693854af..3a022434 100644 --- a/apps/member/models.py +++ b/apps/member/models.py @@ -2,13 +2,18 @@ # SPDX-License-Identifier: GPL-3.0-or-later import datetime +import os from django.conf import settings from django.contrib.auth.models import User from django.core.exceptions import ValidationError from django.db import models +from django.template import loader from django.urls import reverse, reverse_lazy +from django.utils.encoding import force_bytes +from django.utils.http import urlsafe_base64_encode from django.utils.translation import gettext_lazy as _ +from registration.tokens import email_validation_token from note.models import MembershipTransaction @@ -45,6 +50,23 @@ class Profile(models.Model): ) paid = models.BooleanField( verbose_name=_("paid"), + help_text=_("Tells if the user receive a salary."), + default=False, + ) + + email_confirmed = models.BooleanField( + verbose_name=_("email confirmed"), + default=False, + ) + + registration_valid = models.BooleanField( + verbose_name=_("registration valid"), + default=False, + ) + + soge = models.BooleanField( + verbose_name=_("Société générale"), + help_text=_("Has the user ever be paid by the Société générale?"), default=False, ) @@ -56,6 +78,17 @@ class Profile(models.Model): def get_absolute_url(self): return reverse('user_detail', args=(self.pk,)) + def send_email_validation_link(self): + subject = "Activate your Note Kfet account" + message = loader.render_to_string('registration/mails/email_validation_email.html', + { + 'user': self.user, + 'domain': os.getenv("NOTE_URL", "note.example.com"), + 'token': email_validation_token.make_token(self.user), + 'uid': urlsafe_base64_encode(force_bytes(self.user.pk)).decode('UTF-8'), + }) + self.user.email_user(subject, message) + class Club(models.Model): """ @@ -202,6 +235,7 @@ class Membership(models.Model): ) date_start = models.DateField( + default=datetime.date.today, verbose_name=_('membership starts on'), ) @@ -215,12 +249,18 @@ class Membership(models.Model): ) def valid(self): + """ + A membership is valid if today is between the start and the end date. + """ if self.date_end is not None: return self.date_start.toordinal() <= datetime.datetime.now().toordinal() < self.date_end.toordinal() else: return self.date_start.toordinal() <= datetime.datetime.now().toordinal() def save(self, *args, **kwargs): + """ + Calculate fee and end date before saving the membership and creating the transaction if needed. + """ if self.club.parent_club is not None: if not Membership.objects.filter(user=self.user, club=self.club.parent_club).exists(): raise ValidationError(_('User is not a member of the parent club') + ' ' + self.club.parent_club.name) @@ -252,6 +292,9 @@ class Membership(models.Model): self.make_transaction() def make_transaction(self): + """ + Create Membership transaction associated to this membership. + """ if not self.fee or MembershipTransaction.objects.filter(membership=self).exists(): return diff --git a/apps/member/signals.py b/apps/member/signals.py index 2b03e3ce..fbb66c1f 100644 --- a/apps/member/signals.py +++ b/apps/member/signals.py @@ -10,7 +10,7 @@ def save_user_profile(instance, created, raw, **_kwargs): # When provisionning data, do not try to autocreate return - if created: + if created and instance.is_active: from .models import Profile Profile.objects.get_or_create(user=instance) - instance.profile.save() + instance.profile.save() diff --git a/apps/member/tables.py b/apps/member/tables.py index c8a510ff..515d7836 100644 --- a/apps/member/tables.py +++ b/apps/member/tables.py @@ -15,6 +15,9 @@ from .models import Club, Membership class ClubTable(tables.Table): + """ + List all clubs. + """ class Meta: attrs = { 'class': 'table table-condensed table-striped table-hover' @@ -30,6 +33,9 @@ class ClubTable(tables.Table): class UserTable(tables.Table): + """ + List all users. + """ section = tables.Column(accessor='profile.section') balance = tables.Column(accessor='note.balance', verbose_name=_("Balance")) @@ -51,6 +57,9 @@ class UserTable(tables.Table): class MembershipTable(tables.Table): + """ + List all memberships. + """ roles = tables.Column( attrs={ "td": { @@ -59,7 +68,17 @@ class MembershipTable(tables.Table): } ) + def render_user(self, value): + # If the user has the right, link the displayed user with the page of its detail. + s = value.username + if PermissionBackend.check_perm(get_current_authenticated_user(), "auth.view_user", value): + s = format_html("{name}", + url=reverse_lazy('member:user_detail', kwargs={"pk": value.pk}), name=s) + + return s + def render_club(self, value): + # If the user has the right, link the displayed club with the page of its detail. s = value.name if PermissionBackend.check_perm(get_current_authenticated_user(), "member.view_club", value): s = format_html("{name}", @@ -94,6 +113,7 @@ class MembershipTable(tables.Table): return t def render_roles(self, record): + # If the user has the right to manage the roles, display the link to manage them roles = record.roles.all() s = ", ".join(str(role) for role in roles) if PermissionBackend.check_perm(get_current_authenticated_user(), "member.change_membership_roles", record): diff --git a/apps/member/urls.py b/apps/member/urls.py index 1214f024..4be4ceae 100644 --- a/apps/member/urls.py +++ b/apps/member/urls.py @@ -7,14 +7,12 @@ from . import views app_name = 'member' urlpatterns = [ - path('signup/', views.UserCreateView.as_view(), name="signup"), - path('club/', views.ClubListView.as_view(), name="club_list"), path('club/create/', views.ClubCreateView.as_view(), name="club_create"), path('club//', views.ClubDetailView.as_view(), name="club_detail"), - path('club//add_member/', views.ClubAddMemberView.as_view(), name="club_add_member"), + path('club//add_member/', views.ClubAddMemberView.as_view(), name="club_add_member"), path('club/manage_roles//', views.ClubManageRolesView.as_view(), name="club_manage_roles"), - path('club/renew_membership//', views.ClubRenewMembershipView.as_view(), name="club_renew_membership"), + path('club/renew_membership//', views.ClubAddMemberView.as_view(), name="club_renew_membership"), path('club//update/', views.ClubUpdateView.as_view(), name="club_update"), path('club//update_pic/', views.ClubPictureUpdateView.as_view(), name="club_update_pic"), path('club//aliases/', views.ClubAliasView.as_view(), name="club_alias"), diff --git a/apps/member/views.py b/apps/member/views.py index f695002f..381314b2 100644 --- a/apps/member/views.py +++ b/apps/member/views.py @@ -9,30 +9,30 @@ from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.models import User from django.contrib.auth.views import LoginView -from django.core.exceptions import ValidationError from django.db.models import Q -from django.forms import HiddenInput 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, DetailView, UpdateView, TemplateView -from django.views.generic.base import View from django.views.generic.edit import FormMixin from django_tables2.views import SingleTableView from rest_framework.authtoken.models import Token from note.forms import ImageForm -from note.models import Alias, NoteUser -from note.models.transactions import Transaction +from note.models import Alias, NoteUser, NoteSpecial +from note.models.transactions import Transaction, SpecialTransaction from note.tables import HistoryTable, AliasTable from permission.backends import PermissionBackend from permission.views import ProtectQuerysetMixin -from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, CustomAuthenticationForm -from .models import Club, Membership +from .forms import ProfileForm, ClubForm, MembershipForm, CustomAuthenticationForm +from .models import Club, Membership, Role from .tables import ClubTable, UserTable, MembershipTable class CustomLoginView(LoginView): + """ + Login view, where the user can select its permission mask. + """ form_class = CustomAuthenticationForm def form_valid(self, form): @@ -40,33 +40,10 @@ class CustomLoginView(LoginView): return super().form_valid(form) -class UserCreateView(CreateView): - """ - Une vue pour inscrire un utilisateur et lui créer un profile - """ - - form_class = SignUpForm - success_url = reverse_lazy('login') - template_name = 'member/signup.html' - second_form = ProfileForm - - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context["profile_form"] = self.second_form() - - return context - - def form_valid(self, form): - profile_form = ProfileForm(self.request.POST) - if form.is_valid() and profile_form.is_valid(): - user = form.save(commit=False) - user.profile = profile_form.save(commit=False) - user.save() - user.profile.save() - return super().form_valid(form) - - class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): + """ + Update the user information. + """ model = User fields = ['first_name', 'last_name', 'username', 'email'] template_name = 'member/profile_update.html' @@ -75,14 +52,20 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) + + form = context['form'] + form.fields['username'].widget.attrs.pop("autofocus", None) + form.fields['first_name'].widget.attrs.update({"autofocus": "autofocus"}) + form.fields['first_name'].required = True + form.fields['last_name'].required = True + form.fields['email'].required = True + form.fields['email'].help_text = _("This address must be valid.") + context['profile_form'] = self.profile_form(instance=context['user_object'].profile) context['title'] = _("Update Profile") return context - def get_form(self, form_class=None): - form = super().get_form(form_class) - if 'username' not in form.data: - return form + def form_valid(self, form): new_username = form.data['username'] # Si l'utilisateur cherche à modifier son pseudo, le nouveau pseudo ne doit pas être proche d'un alias existant note = NoteUser.objects.filter( @@ -90,9 +73,8 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): if note.exists() and note.get().user != self.object: form.add_error('username', _("An alias with a similar name already exists.")) - return form + return super().form_invalid(form) - def form_valid(self, form): profile_form = ProfileForm( data=self.request.POST, instance=self.object.profile, @@ -108,19 +90,24 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): if similar.exists(): similar.delete() + olduser = User.objects.get(pk=form.instance.pk) + user = form.save(commit=False) profile = profile_form.save(commit=False) profile.user = user profile.save() user.save() + + if olduser.email != user.email: + # If the user changed her/his email, then it is unvalidated and a confirmation link is sent. + user.profile.email_confirmed = False + user.profile.send_email_validation_link() + return super().form_valid(form) def get_success_url(self, **kwargs): - if kwargs: - return reverse_lazy('member:user_detail', - kwargs={'pk': kwargs['id']}) - else: - return reverse_lazy('member:user_detail', args=(self.object.id,)) + url = 'member:user_detail' if self.object.profile.registration_valid else 'registration:future_user_detail' + return reverse_lazy(url, args=(self.object.id,)) class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): @@ -131,29 +118,43 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): context_object_name = "user_object" template_name = "member/profile_detail.html" + def get_queryset(self, **kwargs): + """ + We can't display information of a not registered user. + """ + return super().get_queryset().filter(profile__registration_valid=True) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) user = context['user_object'] history_list = \ Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note)).order_by("-id")\ .filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view")) - context['history_list'] = HistoryTable(history_list) + history_table = HistoryTable(history_list, prefix='transaction-') + history_table.paginate(per_page=20, page=self.request.GET.get("transaction-page", 1)) + context['history_list'] = history_table + club_list = Membership.objects.filter(user=user, date_end__gte=datetime.today())\ .filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view")) - context['club_list'] = MembershipTable(data=club_list) + membership_table = MembershipTable(data=club_list, prefix='membership-') + membership_table.paginate(per_page=10, page=self.request.GET.get("membership-page", 1)) + context['club_list'] = membership_table return context class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): """ - Affiche la liste des utilisateurs, avec une fonction de recherche statique + Display user list, with a search bar """ model = User table_class = UserTable template_name = 'member/user_list.html' def get_queryset(self, **kwargs): - qs = super().get_queryset() + """ + Filter the user list with the given pattern. + """ + qs = super().get_queryset().filter(profile__registration_valid=True) if "search" in self.request.GET: pattern = self.request.GET["search"] @@ -164,6 +165,7 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): Q(first_name__iregex=pattern) | Q(last_name__iregex=pattern) | Q(profile__section__iregex=pattern) + | Q(profile__username__iregex="^" + pattern) | Q(note__alias__name__iregex="^" + pattern) | Q(note__alias__normalized_name__iregex=Alias.normalize("^" + pattern)) ) @@ -181,6 +183,9 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): + """ + View and manage user aliases. + """ model = User template_name = 'member/profile_alias.html' context_object_name = 'user_object' @@ -193,6 +198,9 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, DetailView): + """ + Update profile picture of the user note. + """ form_class = ImageForm def get_context_data(self, **kwargs): @@ -292,6 +300,9 @@ class ClubListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): + """ + Display details of a club + """ model = Club context_object_name = "club" @@ -304,14 +315,19 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\ .filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view")).order_by('-id') - context['history_list'] = HistoryTable(club_transactions) + 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 = Membership.objects.filter( club=club, date_end__gte=datetime.today(), ).filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view")) - context['member_list'] = MembershipTable(data=club_member) + membership_table = MembershipTable(data=club_member, prefix="membership-") + membership_table.paginate(per_page=20, page=self.request.GET.get('membership-page', 1)) + context['member_list'] = membership_table + # Check if the user has the right to create a membership, to display the button. empty_membership = Membership( club=club, user=User.objects.first(), @@ -326,6 +342,9 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): + """ + Manage aliases of a club. + """ model = Club template_name = 'member/club_alias.html' context_object_name = 'club' @@ -338,6 +357,9 @@ class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView): class ClubUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): + """ + Update the information of a club. + """ model = Club context_object_name = "club" form_class = ClubForm @@ -348,6 +370,9 @@ class ClubUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): class ClubPictureUpdateView(PictureUpdateView): + """ + Update the profile picture of a club. + """ model = Club template_name = 'member/club_picture_update.html' context_object_name = 'club' @@ -357,29 +382,107 @@ class ClubPictureUpdateView(PictureUpdateView): class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): + """ + Add a membership to a club. + """ model = Membership form_class = MembershipForm template_name = 'member/add_members.html' def get_context_data(self, **kwargs): - club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\ - .get(pk=self.kwargs["pk"]) context = super().get_context_data(**kwargs) + form = context['form'] + + if "club_pk" in self.kwargs: + # We create a new membership. + club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\ + .get(pk=self.kwargs["club_pk"]) + form.fields['credit_amount'].initial = club.membership_fee_paid + form.fields['roles'].initial = Role.objects.filter(name="Membre de club").all() + + # If the concerned club is the BDE, then we add the option that Société générale pays the membership. + if club.name != "BDE": + del form.fields['soge'] + else: + fee = 0 + bde = Club.objects.get(name="BDE") + fee += bde.membership_fee_paid + kfet = Club.objects.get(name="Kfet") + fee += kfet.membership_fee_paid + context["total_fee"] = "{:.02f}".format(fee / 100, ) + else: + # This is a renewal. Fields can be pre-completed. + old_membership = self.get_queryset().get(pk=self.kwargs["pk"]) + club = old_membership.club + user = old_membership.user + form.fields['user'].initial = user + form.fields['user'].disabled = True + form.fields['roles'].initial = old_membership.roles.all() + form.fields['date_start'].initial = old_membership.date_end + timedelta(days=1) + form.fields['credit_amount'].initial = club.membership_fee_paid if user.profile.paid \ + else club.membership_fee_unpaid + form.fields['last_name'].initial = user.last_name + form.fields['first_name'].initial = user.first_name + + # If this is a renewal of a BDE membership, Société générale can pays, if it is not yet done + if club.name != "BDE" or user.profile.soge: + del form.fields['soge'] + else: + fee = 0 + bde = Club.objects.get(name="BDE") + 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 + context["total_fee"] = "{:.02f}".format(fee / 100, ) + context['club'] = club return context def form_valid(self, form): - club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\ - .get(pk=self.kwargs["pk"]) - user = self.request.user + """ + Create membership, check that all is good, make transactions + """ + # Get the club that is concerned by the membership + if "club_pk" in self.kwargs: + club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view")) \ + .get(pk=self.kwargs["club_pk"]) + user = form.instance.user + else: + old_membership = self.get_queryset().get(pk=self.kwargs["pk"]) + club = old_membership.club + user = old_membership.user + form.instance.club = club + # Get form data + credit_type = form.cleaned_data["credit_type"] + credit_amount = form.cleaned_data["credit_amount"] + last_name = form.cleaned_data["last_name"] + first_name = form.cleaned_data["first_name"] + bank = form.cleaned_data["bank"] + soge = form.cleaned_data["soge"] and not user.profile.soge and club.name == "BDE" + + # If Société générale pays, then we auto-fill some data + if soge: + credit_type = NoteSpecial.objects.get(special_type="Virement bancaire") + bde = club + kfet = Club.objects.get(name="Kfet") + if user.profile.paid: + fee = bde.membership_fee_paid + kfet.membership_fee_paid + else: + fee = bde.membership_fee_unpaid + kfet.membership_fee_unpaid + credit_amount = fee + bank = "Société générale" + + if credit_type is None: + credit_amount = 0 + if user.profile.paid: fee = club.membership_fee_paid else: fee = club.membership_fee_unpaid - if user.note.balance < fee and not Membership.objects.filter( + if user.note.balance + credit_amount < fee and not Membership.objects.filter( club__name="Kfet", user=user, date_start__lte=datetime.now().date(), @@ -390,6 +493,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): # TODO Send a notification to the user (with a mail?) to tell her/him to credit her/his note form.add_error('user', _("This user don't have enough money to join this club, and can't have a negative balance.")) + return super().form_invalid(form) if club.parent_club is not None: if not Membership.objects.filter(user=form.instance.user, club=club.parent_club).exists(): @@ -405,16 +509,70 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): form.add_error('user', _('User is already a member of the club')) return super().form_invalid(form) - if form.instance.club.membership_start and form.instance.date_start < form.instance.club.membership_start: + if club.membership_start and form.instance.date_start < club.membership_start: form.add_error('user', _("The membership must start after {:%m-%d-%Y}.") .format(form.instance.club.membership_start)) return super().form_invalid(form) - if form.instance.club.membership_end and form.instance.date_start > form.instance.club.membership_end: + if club.membership_end and form.instance.date_start > club.membership_end: form.add_error('user', _("The membership must begin before {:%m-%d-%Y}.") .format(form.instance.club.membership_start)) return super().form_invalid(form) + # Now, all is fine, the membership can be created. + + # Credit note before the membership is created. + if credit_amount > 0: + if not last_name or not first_name or (not bank and credit_type.special_type == "Chèque"): + if not last_name: + form.add_error('last_name', _("This field is required.")) + if not first_name: + form.add_error('first_name', _("This field is required.")) + if not bank and credit_type.special_type == "Chèque": + form.add_error('bank', _("This field is required.")) + return self.form_invalid(form) + + SpecialTransaction.objects.create( + source=credit_type, + destination=user.note, + quantity=1, + amount=credit_amount, + reason="Crédit " + credit_type.special_type + " (Adhésion " + club.name + ")", + last_name=last_name, + first_name=first_name, + bank=bank, + valid=True, + ) + + # If Société générale pays, then we store the information: the bank can't pay twice to a same person. + if soge: + user.profile.soge = True + user.profile.save() + + kfet = Club.objects.get(name="Kfet") + kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid + + # Get current membership, to get the end date + old_membership = Membership.objects.filter( + club__name="Kfet", + user=user, + date_start__lte=datetime.today(), + date_end__gte=datetime.today(), + ) + + membership = Membership.objects.create( + club=kfet, + user=user, + fee=kfet_fee, + date_start=old_membership.get().date_end + timedelta(days=1) + if old_membership.exists() else form.instance.date_start, + ) + if old_membership.exists(): + membership.roles.set(old_membership.get().roles.all()) + else: + membership.roles.add(Role.objects.get(name="Adhérent Kfet")) + membership.save() + return super().form_valid(form) def get_success_url(self): @@ -422,6 +580,9 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): class ClubManageRolesView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): + """ + Manage the roles of a user in a club + """ model = Membership form_class = MembershipForm template_name = 'member/add_members.html' @@ -430,49 +591,19 @@ class ClubManageRolesView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): context = super().get_context_data(**kwargs) club = self.object.club context['club'] = club - form = context['form'] - form.fields['user'].disabled = True - form.fields['date_start'].widget = HiddenInput() - return context - def form_valid(self, form): - if form.instance.club.membership_start and form.instance.date_start < form.instance.club.membership_start: - form.add_error('user', _("The membership must start after {:%m-%d-%Y}.") - .format(form.instance.club.membership_start)) - return super().form_invalid(form) - - if form.instance.club.membership_end and form.instance.date_start > form.instance.club.membership_end: - form.add_error('user', _("The membership must begin before {:%m-%d-%Y}.") - .format(form.instance.club.membership_start)) - return super().form_invalid(form) - - return super().form_valid(form) + def get_form(self, form_class=None): + form = super().get_form(form_class) + # We don't create a full membership, we only update one field + form.fields['user'].disabled = True + del form.fields['date_start'] + del form.fields['credit_type'] + del form.fields['credit_amount'] + del form.fields['last_name'] + del form.fields['first_name'] + del form.fields['bank'] + return form def get_success_url(self): return reverse_lazy('member:club_detail', kwargs={'pk': self.object.club.id}) - - -class ClubRenewMembershipView(ProtectQuerysetMixin, LoginRequiredMixin, View): - def get(self, *args, **kwargs): - user = self.request.user - membership = Membership.objects.filter(PermissionBackend.filter_queryset(user, Membership, "change"))\ - .filter(pk=self.kwargs["pk"]).get() - - if Membership.objects.filter( - club=membership.club, - user=membership.user, - date_start__gte=membership.club.membership_start, - date_end__lte=membership.club.membership_end, - ).exists(): - raise ValidationError(_("This membership is already renewed")) - - new_membership = Membership.objects.create( - user=user, - club=membership.club, - date_start=membership.date_end + timedelta(days=1), - ) - new_membership.roles.set(membership.roles.all()) - new_membership.save() - - return redirect(reverse_lazy('member:club_detail', kwargs={'pk': membership.club.pk})) diff --git a/apps/note/admin.py b/apps/note/admin.py index 7b4ba870..dc6470d2 100644 --- a/apps/note/admin.py +++ b/apps/note/admin.py @@ -8,7 +8,7 @@ from polymorphic.admin import PolymorphicChildModelAdmin, \ from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \ - RecurrentTransaction, MembershipTransaction + RecurrentTransaction, MembershipTransaction, SpecialTransaction class AliasInlines(admin.TabularInline): @@ -102,7 +102,7 @@ class TransactionAdmin(PolymorphicParentModelAdmin): """ Admin customisation for Transaction """ - child_models = (RecurrentTransaction, MembershipTransaction) + child_models = (RecurrentTransaction, MembershipTransaction, SpecialTransaction) list_display = ('created_at', 'poly_source', 'poly_destination', 'quantity', 'amount', 'valid') list_filter = ('valid',) @@ -141,7 +141,14 @@ class TransactionAdmin(PolymorphicParentModelAdmin): @admin.register(MembershipTransaction) class MembershipTransactionAdmin(PolymorphicChildModelAdmin): """ - Admin customisation for Transaction + Admin customisation for MembershipTransaction + """ + + +@admin.register(SpecialTransaction) +class SpecialTransactionAdmin(PolymorphicChildModelAdmin): + """ + Admin customisation for SpecialTransaction """ diff --git a/apps/note/signals.py b/apps/note/signals.py index e62115b3..37737a45 100644 --- a/apps/note/signals.py +++ b/apps/note/signals.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: GPL-3.0-or-later -def save_user_note(instance, created, raw, **_kwargs): +def save_user_note(instance, raw, **_kwargs): """ Hook to create and save a note when an user is updated """ @@ -10,10 +10,11 @@ def save_user_note(instance, created, raw, **_kwargs): # When provisionning data, do not try to autocreate return - if created: - from .models import NoteUser - NoteUser.objects.create(user=instance) - instance.note.save() + if (instance.is_superuser or instance.profile.registration_valid) and instance.is_active: + # Create note only when the registration is validated + from note.models import NoteUser + NoteUser.objects.get_or_create(user=instance) + instance.note.save() def save_club_note(instance, created, raw, **_kwargs): diff --git a/apps/note/views.py b/apps/note/views.py index ac9b3e40..88d47847 100644 --- a/apps/note/views.py +++ b/apps/note/views.py @@ -1,6 +1,7 @@ # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # SPDX-License-Identifier: GPL-3.0-or-later +from django.conf import settings from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.contenttypes.models import ContentType from django.utils.translation import gettext_lazy as _ @@ -29,7 +30,7 @@ class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTabl table_class = HistoryTable def get_queryset(self, **kwargs): - return super().get_queryset(**kwargs).order_by("-id").all()[:50] + return super().get_queryset(**kwargs).order_by("-id").all()[:20] def get_context_data(self, **kwargs): """ @@ -44,12 +45,19 @@ class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTabl .filter(PermissionBackend.filter_queryset(self.request.user, NoteSpecial, "view"))\ .order_by("special_type").all() + # Add a shortcut for entry page for open activities + if "activity" in settings.INSTALLED_APPS: + from activity.models import Activity + context["activities_open"] = Activity.objects.filter(open=True).filter( + PermissionBackend.filter_queryset(self.request.user, Activity, "view")).filter( + PermissionBackend.filter_queryset(self.request.user, Activity, "change")).all() + return context class TransactionTemplateCreateView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView): """ - Create TransactionTemplate + Create Transaction template """ model = TransactionTemplate form_class = TransactionTemplateForm @@ -58,7 +66,7 @@ class TransactionTemplateCreateView(ProtectQuerysetMixin, LoginRequiredMixin, Cr class TransactionTemplateListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): """ - List TransactionsTemplates + List Transaction templates """ model = TransactionTemplate table_class = ButtonTable @@ -66,6 +74,7 @@ class TransactionTemplateListView(ProtectQuerysetMixin, LoginRequiredMixin, Sing class TransactionTemplateUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView): """ + Update Transaction template """ model = TransactionTemplate form_class = TransactionTemplateForm @@ -84,7 +93,7 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): table_class = HistoryTable def get_queryset(self, **kwargs): - return super().get_queryset(**kwargs).order_by("-id").all()[:50] + return super().get_queryset(**kwargs).order_by("-id").all()[:20] def get_context_data(self, **kwargs): """ diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json index 4cf3ecfa..d7eca508 100644 --- a/apps/permission/fixtures/initial.json +++ b/apps/permission/fixtures/initial.json @@ -17,54 +17,61 @@ "model": "member.role", "pk": 3, "fields": { - "name": "Pr\u00e9sident\u00b7e BDE" + "name": "Membre de club" } }, { "model": "member.role", "pk": 4, "fields": { - "name": "Tr\u00e9sorier\u00b7\u00e8re BDE" + "name": "Bureau de club" } }, { "model": "member.role", "pk": 5, "fields": { - "name": "Respo info" + "name": "Pr\u00e9sident\u00b7e de club" } }, { "model": "member.role", "pk": 6, "fields": { - "name": "GC Kfet" + "name": "Tr\u00e9sorier\u00b7\u00e8re de club" } }, { "model": "member.role", "pk": 7, "fields": { - "name": "Pr\u00e9sident\u00b7e de club" + "name": "Pr\u00e9sident\u00b7e BDE" } }, { "model": "member.role", "pk": 8, "fields": { - "name": "Tr\u00e9sorier\u00b7\u00e8re de club" - } - }, - { - "model": "member.role", - "pk": 8, - "fields": { - "name": "Tr\u00e9sorier\u00b7\u00e8re de club" + "name": "Tr\u00e9sorier\u00b7\u00e8re BDE" } }, { "model": "member.role", "pk": 9, + "fields": { + "name": "Respo info" + } + }, + { + "model": "member.role", + "pk": 10, + "fields": { + "name": "GC Kfet" + } + }, + { + "model": "member.role", + "pk": 11, "fields": { "name": "Res[pot]" } @@ -97,10 +104,7 @@ "model": "permission.permission", "pk": 1, "fields": { - "model": [ - "auth", - "user" - ], + "model": 4, "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "view", "mask": 1, @@ -112,10 +116,7 @@ "model": "permission.permission", "pk": 2, "fields": { - "model": [ - "member", - "profile" - ], + "model": 17, "query": "{\"user\": [\"user\"]}", "type": "view", "mask": 1, @@ -127,10 +128,7 @@ "model": "permission.permission", "pk": 3, "fields": { - "model": [ - "note", - "noteuser" - ], + "model": 27, "query": "{\"pk\": [\"user\", \"note\", \"pk\"]}", "type": "view", "mask": 1, @@ -142,10 +140,7 @@ "model": "permission.permission", "pk": 4, "fields": { - "model": [ - "authtoken", - "token" - ], + "model": 8, "query": "{\"user\": [\"user\"]}", "type": "view", "mask": 1, @@ -157,10 +152,7 @@ "model": "permission.permission", "pk": 5, "fields": { - "model": [ - "note", - "transaction" - ], + "model": 22, "query": "[\"OR\", {\"source\": [\"user\", \"note\"]}, {\"destination\": [\"user\", \"note\"]}]", "type": "view", "mask": 1, @@ -172,10 +164,7 @@ "model": "permission.permission", "pk": 6, "fields": { - "model": [ - "note", - "alias" - ], + "model": 19, "query": "[\"OR\", {\"note__in\": [\"NoteUser\", \"objects\", [\"filter\", {\"user__membership__club__name\": \"Kfet\"}], [\"all\"]]}, {\"note__in\": [\"NoteClub\", \"objects\", [\"all\"]]}]", "type": "view", "mask": 1, @@ -187,10 +176,7 @@ "model": "permission.permission", "pk": 7, "fields": { - "model": [ - "auth", - "user" - ], + "model": 4, "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -202,10 +188,7 @@ "model": "permission.permission", "pk": 8, "fields": { - "model": [ - "auth", - "user" - ], + "model": 4, "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -217,10 +200,7 @@ "model": "permission.permission", "pk": 9, "fields": { - "model": [ - "auth", - "user" - ], + "model": 4, "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -232,10 +212,7 @@ "model": "permission.permission", "pk": 10, "fields": { - "model": [ - "auth", - "user" - ], + "model": 4, "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -247,10 +224,7 @@ "model": "permission.permission", "pk": 11, "fields": { - "model": [ - "auth", - "user" - ], + "model": 4, "query": "{\"pk\": [\"user\", \"pk\"]}", "type": "change", "mask": 1, @@ -262,10 +236,7 @@ "model": "permission.permission", "pk": 12, "fields": { - "model": [ - "authtoken", - "token" - ], + "model": 8, "query": "{\"user\": [\"user\"]}", "type": "delete", "mask": 1, @@ -277,10 +248,7 @@ "model": "permission.permission", "pk": 13, "fields": { - "model": [ - "authtoken", - "token" - ], + "model": 8, "query": "{\"user\": [\"user\"]}", "type": "add", "mask": 1, @@ -292,10 +260,7 @@ "model": "permission.permission", "pk": 14, "fields": { - "model": [ - "note", - "alias" - ], + "model": 19, "query": "{\"note\": [\"user\", \"note\"]}", "type": "delete", "mask": 1, @@ -307,10 +272,7 @@ "model": "permission.permission", "pk": 15, "fields": { - "model": [ - "note", - "alias" - ], + "model": 19, "query": "{\"note\": [\"user\", \"note\"]}", "type": "add", "mask": 1, @@ -322,10 +284,7 @@ "model": "permission.permission", "pk": 16, "fields": { - "model": [ - "note", - "noteuser" - ], + "model": 27, "query": "{\"pk\": [\"user\", \"note\", \"pk\"]}", "type": "change", "mask": 1, @@ -337,10 +296,7 @@ "model": "permission.permission", "pk": 17, "fields": { - "model": [ - "note", - "transaction" - ], + "model": 22, "query": "[\"AND\", {\"source\": [\"user\", \"note\"]}, [\"OR\", {\"amount__lte\": [\"user\", \"note\", \"balance\"]}, {\"valid\": false}]]", "type": "add", "mask": 1, @@ -352,10 +308,7 @@ "model": "permission.permission", "pk": 18, "fields": { - "model": [ - "note", - "note" - ], + "model": 20, "query": "{}", "type": "change", "mask": 1, @@ -367,10 +320,7 @@ "model": "permission.permission", "pk": 19, "fields": { - "model": [ - "note", - "note" - ], + "model": 20, "query": "[\"OR\", {\"pk\": [\"club\", \"note\", \"pk\"]}, {\"pk__in\": [\"NoteUser\", \"objects\", [\"filter\", {\"user__membership__club\": [\"club\"]}], [\"all\"]]}]", "type": "view", "mask": 2, @@ -382,10 +332,7 @@ "model": "permission.permission", "pk": 20, "fields": { - "model": [ - "note", - "transaction" - ], + "model": 22, "query": "[\"AND\", [\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}], [\"OR\", {\"amount__lte\": {\"F\": [\"ADD\", [\"F\", \"source__balance\"], 5000]}}, {\"valid\": false}]]", "type": "add", "mask": 2, @@ -397,10 +344,7 @@ "model": "permission.permission", "pk": 21, "fields": { - "model": [ - "note", - "recurrenttransaction" - ], + "model": 28, "query": "[\"AND\", {\"destination\": [\"club\", \"note\"]}, [\"OR\", {\"amount__lte\": {\"F\": [\"ADD\", [\"F\", \"source__balance\"], 5000]}}, {\"valid\": false}]]", "type": "add", "mask": 2, @@ -412,10 +356,7 @@ "model": "permission.permission", "pk": 22, "fields": { - "model": [ - "member", - "club" - ], + "model": 15, "query": "{\"pk\": [\"club\", \"pk\"]}", "type": "view", "mask": 1, @@ -427,10 +368,7 @@ "model": "permission.permission", "pk": 23, "fields": { - "model": [ - "note", - "transaction" - ], + "model": 22, "query": "{}", "type": "change", "mask": 1, @@ -442,10 +380,7 @@ "model": "permission.permission", "pk": 24, "fields": { - "model": [ - "note", - "transaction" - ], + "model": 22, "query": "{}", "type": "view", "mask": 2, @@ -457,10 +392,7 @@ "model": "permission.permission", "pk": 25, "fields": { - "model": [ - "note", - "notespecial" - ], + "model": 26, "query": "{}", "type": "view", "mask": 2, @@ -472,10 +404,7 @@ "model": "permission.permission", "pk": 26, "fields": { - "model": [ - "note", - "specialtransaction" - ], + "model": 29, "query": "{}", "type": "add", "mask": 2, @@ -487,10 +416,7 @@ "model": "permission.permission", "pk": 27, "fields": { - "model": [ - "note", - "templatecategory" - ], + "model": 21, "query": "{}", "type": "view", "mask": 2, @@ -502,10 +428,7 @@ "model": "permission.permission", "pk": 28, "fields": { - "model": [ - "note", - "templatecategory" - ], + "model": 21, "query": "{}", "type": "change", "mask": 3, @@ -517,10 +440,7 @@ "model": "permission.permission", "pk": 29, "fields": { - "model": [ - "note", - "templatecategory" - ], + "model": 21, "query": "{}", "type": "add", "mask": 3, @@ -532,10 +452,7 @@ "model": "permission.permission", "pk": 30, "fields": { - "model": [ - "note", - "transactiontemplate" - ], + "model": 23, "query": "{}", "type": "view", "mask": 2, @@ -547,10 +464,7 @@ "model": "permission.permission", "pk": 31, "fields": { - "model": [ - "note", - "transactiontemplate" - ], + "model": 23, "query": "{}", "type": "add", "mask": 3, @@ -562,10 +476,7 @@ "model": "permission.permission", "pk": 32, "fields": { - "model": [ - "note", - "transactiontemplate" - ], + "model": 23, "query": "{}", "type": "change", "mask": 3, @@ -577,10 +488,7 @@ "model": "permission.permission", "pk": 33, "fields": { - "model": [ - "note", - "transaction" - ], + "model": 22, "query": "{}", "type": "add", "mask": 2, @@ -592,10 +500,7 @@ "model": "permission.permission", "pk": 34, "fields": { - "model": [ - "activity", - "activity" - ], + "model": 9, "query": "[\"OR\", {\"valid\": true}, {\"creater\": [\"user\"]}]", "type": "view", "mask": 1, @@ -607,10 +512,7 @@ "model": "permission.permission", "pk": 35, "fields": { - "model": [ - "activity", - "activity" - ], + "model": 9, "query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]", "type": "change", "mask": 1, @@ -622,10 +524,7 @@ "model": "permission.permission", "pk": 36, "fields": { - "model": [ - "activity", - "activity" - ], + "model": 9, "query": "{\"creater\": [\"user\"], \"valid\": false}", "type": "add", "mask": 1, @@ -637,10 +536,7 @@ "model": "permission.permission", "pk": 37, "fields": { - "model": [ - "activity", - "activity" - ], + "model": 9, "query": "{}", "type": "change", "mask": 2, @@ -652,10 +548,7 @@ "model": "permission.permission", "pk": 38, "fields": { - "model": [ - "activity", - "activity" - ], + "model": 9, "query": "{}", "type": "change", "mask": 2, @@ -667,10 +560,7 @@ "model": "permission.permission", "pk": 39, "fields": { - "model": [ - "activity", - "guest" - ], + "model": 12, "query": "{\"inviter\": [\"user\", \"note\"], \"activity__activity_type__can_invite\": true}", "type": "add", "mask": 1, @@ -682,10 +572,7 @@ "model": "permission.permission", "pk": 40, "fields": { - "model": [ - "activity", - "guest" - ], + "model": 12, "query": "{\"inviter\": [\"user\", \"note\"]}", "type": "view", "mask": 1, @@ -697,10 +584,7 @@ "model": "permission.permission", "pk": 41, "fields": { - "model": [ - "activity", - "activity" - ], + "model": 9, "query": "{}", "type": "view", "mask": 2, @@ -712,10 +596,7 @@ "model": "permission.permission", "pk": 42, "fields": { - "model": [ - "activity", - "guest" - ], + "model": 12, "query": "{}", "type": "view", "mask": 2, @@ -727,10 +608,7 @@ "model": "permission.permission", "pk": 43, "fields": { - "model": [ - "activity", - "entry" - ], + "model": 11, "query": "{}", "type": "add", "mask": 2, @@ -742,10 +620,7 @@ "model": "permission.permission", "pk": 44, "fields": { - "model": [ - "activity", - "guesttransaction" - ], + "model": 13, "query": "{}", "type": "add", "mask": 2, @@ -757,10 +632,7 @@ "model": "permission.permission", "pk": 45, "fields": { - "model": [ - "activity", - "guesttransaction" - ], + "model": 13, "query": "{}", "type": "view", "mask": 1, @@ -772,10 +644,7 @@ "model": "permission.permission", "pk": 46, "fields": { - "model": [ - "activity", - "guesttransaction" - ], + "model": 13, "query": "{}", "type": "change", "mask": 2, @@ -787,10 +656,7 @@ "model": "permission.permission", "pk": 47, "fields": { - "model": [ - "member", - "club" - ], + "model": 15, "query": "{\"pk\": [\"club\", \"pk\"]}", "type": "change", "mask": 1, @@ -802,10 +668,7 @@ "model": "permission.permission", "pk": 48, "fields": { - "model": [ - "member", - "membership" - ], + "model": 16, "query": "{\"user\": [\"user\"]}", "type": "view", "mask": 1, @@ -817,10 +680,7 @@ "model": "permission.permission", "pk": 49, "fields": { - "model": [ - "member", - "membership" - ], + "model": 16, "query": "{\"club\": [\"club\"]}", "type": "view", "mask": 1, @@ -832,10 +692,7 @@ "model": "permission.permission", "pk": 50, "fields": { - "model": [ - "member", - "membership" - ], + "model": 16, "query": "{\"club\": [\"club\"]}", "type": "add", "mask": 2, @@ -843,6 +700,234 @@ "description": "Add a membership to a club" } }, + { + "model": "permission.permission", + "pk": 51, + "fields": { + "model": 16, + "query": "{\"club\": [\"club\"]}", + "type": "change", + "mask": 2, + "field": "roles", + "description": "Update user roles" + } + }, + { + "model": "permission.permission", + "pk": 52, + "fields": { + "model": 17, + "query": "{\"user\": [\"user\"]}", + "type": "change", + "mask": 1, + "field": "", + "description": "Change own profile" + } + }, + { + "model": "permission.permission", + "pk": 53, + "fields": { + "model": 17, + "query": "{}", + "type": "change", + "mask": 2, + "field": "", + "description": "Change any profile" + } + }, + { + "model": "permission.permission", + "pk": 54, + "fields": { + "model": 4, + "query": "{}", + "type": "change", + "mask": 2, + "field": "", + "description": "Change any user" + } + }, + { + "model": "permission.permission", + "pk": 55, + "fields": { + "model": 4, + "query": "{}", + "type": "add", + "mask": 1, + "field": "", + "description": "Add user" + } + }, + { + "model": "permission.permission", + "pk": 56, + "fields": { + "model": 17, + "query": "{\"email_confirmed\": false, \"registration_valid\": false}", + "type": "add", + "mask": 1, + "field": "", + "description": "Add profile" + } + }, + { + "model": "permission.permission", + "pk": 57, + "fields": { + "model": 4, + "query": "{\"profile__registration_valid\": false}", + "type": "delete", + "mask": 2, + "field": "", + "description": "Delete pre-registered user" + } + }, + { + "model": "permission.permission", + "pk": 58, + "fields": { + "model": 17, + "query": "{\"registration_valid\": false}", + "type": "delete", + "mask": 2, + "field": "", + "description": "Delete pre-registered user profile" + } + }, + { + "model": "permission.permission", + "pk": 59, + "fields": { + "model": 23, + "query": "{\"destination\": [\"club\", \"note\"]}", + "type": "view", + "mask": 2, + "field": "", + "description": "New club button" + } + }, + { + "model": "permission.permission", + "pk": 60, + "fields": { + "model": 23, + "query": "{\"destination\": [\"club\", \"note\"]}", + "type": "add", + "mask": 2, + "field": "", + "description": "Create club button" + } + }, + { + "model": "permission.permission", + "pk": 61, + "fields": { + "model": 23, + "query": "{\"destination\": [\"club\", \"note\"]}", + "type": "change", + "mask": 2, + "field": "", + "description": "Update club button" + } + }, + { + "model": "permission.permission", + "pk": 62, + "fields": { + "model": 22, + "query": "[\"OR\", {\"source\": [\"club\", \"note\"]}, {\"destination\": [\"club\", \"note\"]}]", + "type": "view", + "mask": 1, + "field": "", + "description": "View transactions of a club" + } + }, + { + "model": "permission.permission", + "pk": 63, + "fields": { + "model": 33, + "query": "{}", + "type": "view", + "mask": 3, + "field": "", + "description": "View invoices" + } + }, + { + "model": "permission.permission", + "pk": 64, + "fields": { + "model": 33, + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "description": "Add invoice" + } + }, + { + "model": "permission.permission", + "pk": 65, + "fields": { + "model": 33, + "query": "{}", + "type": "change", + "mask": 3, + "field": "", + "description": "Change invoice" + } + }, + { + "model": "permission.permission", + "pk": 66, + "fields": { + "model": 34, + "query": "{}", + "type": "view", + "mask": 3, + "field": "", + "description": "View products" + } + }, + { + "model": "permission.permission", + "pk": 67, + "fields": { + "model": 34, + "query": "{}", + "type": "add", + "mask": 3, + "field": "", + "description": "Add products" + } + }, + { + "model": "permission.permission", + "pk": 68, + "fields": { + "model": 34, + "query": "{}", + "type": "change", + "mask": 3, + "field": "", + "description": "Change product" + } + }, + { + "model": "permission.permission", + "pk": 69, + "fields": { + "model": 34, + "query": "{}", + "type": "delete", + "mask": 3, + "field": "", + "description": "Delete product" + } + }, { "model": "permission.rolepermissions", "pk": 1, @@ -851,12 +936,18 @@ "permissions": [ 1, 2, + 3, + 4, + 5, 7, 8, 9, 10, 11, - 48 + 12, + 13, + 48, + 52 ] } }, @@ -866,19 +957,7 @@ "fields": { "role": 2, "permissions": [ - 1, - 2, - 3, - 4, - 5, 6, - 7, - 8, - 9, - 10, - 11, - 12, - 13, 14, 15, 16, @@ -894,23 +973,73 @@ }, { "model": "permission.rolepermissions", - "pk": 3, + "pk": 4, "fields": { - "role": 8, + "role": 4, "permissions": [ - 19, - 20, - 21, - 22 + 22, + 47, + 49 ] } }, { "model": "permission.rolepermissions", - "pk": 4, + "pk": 5, "fields": { - "role": 4, + "role": 5, "permissions": [ + 50, + 51, + 62 + ] + } + }, + { + "model": "permission.rolepermissions", + "pk": 6, + "fields": { + "role": 6, + "permissions": [ + 19, + 21, + 27, + 59, + 60, + 61, + 20, + 62 + ] + } + }, + { + "model": "permission.rolepermissions", + "pk": 7, + "fields": { + "role": 7, + "permissions": [ + 33, + 24, + 25, + 26, + 27 + ] + } + }, + { + "model": "permission.rolepermissions", + "pk": 8, + "fields": { + "role": 8, + "permissions": [ + 32, + 33, + 56, + 58, + 55, + 57, + 53, + 54, 23, 24, 25, @@ -920,44 +1049,21 @@ 29, 30, 31, - 32, - 33 + 64, + 65, + 66, + 67, + 68, + 69, + 63 ] } }, { "model": "permission.rolepermissions", - "pk": 5, + "pk": 9, "fields": { "role": 9, - "permissions": [ - 37, - 38, - 41, - 42, - 43, - 44, - 45, - 46 - ] - } - }, - { - "model": "permission.rolepermissions", - "pk": 6, - "fields": { - "role": 7, - "permissions": [ - 22, - 47 - ] - } - }, - { - "model": "permission.rolepermissions", - "pk": 7, - "fields": { - "role": 5, "permissions": [ 1, 2, @@ -978,7 +1084,6 @@ 17, 18, 19, - 20, 21, 22, 23, @@ -1008,7 +1113,71 @@ 47, 48, 49, - 50 + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59, + 60, + 61, + 20, + 62, + 63, + 64, + 65, + 66, + 67, + 68, + 69 + ] + } + }, + { + "model": "permission.rolepermissions", + "pk": 10, + "fields": { + "role": 10, + "permissions": [ + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, + 32, + 33, + 52, + 53, + 54, + 55, + 56, + 57, + 58 + ] + } + }, + { + "model": "permission.rolepermissions", + "pk": 11, + "fields": { + "role": 11, + "permissions": [ + 37, + 38, + 41, + 42, + 43, + 44, + 45, + 46 ] } } diff --git a/apps/permission/models.py b/apps/permission/models.py index 8aaf416c..81174389 100644 --- a/apps/permission/models.py +++ b/apps/permission/models.py @@ -106,6 +106,10 @@ class PermissionMask(models.Model): def __str__(self): return self.description + class Meta: + verbose_name = _("permission mask") + verbose_name_plural = _("permission masks") + class Permission(models.Model): @@ -153,6 +157,8 @@ class Permission(models.Model): class Meta: unique_together = ('model', 'query', 'type', 'field') + verbose_name = _("permission") + verbose_name_plural = _("permissions") def clean(self): self.query = json.dumps(json.loads(self.query)) @@ -293,3 +299,7 @@ class RolePermissions(models.Model): def __str__(self): return str(self.role) + + class Meta: + verbose_name = _("role permissions") + verbose_name_plural = _("role permissions") diff --git a/apps/registration/__init__.py b/apps/registration/__init__.py new file mode 100644 index 00000000..700d9f00 --- /dev/null +++ b/apps/registration/__init__.py @@ -0,0 +1,4 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +default_app_config = 'registration.apps.RegistrationConfig' diff --git a/apps/registration/apps.py b/apps/registration/apps.py new file mode 100644 index 00000000..dec89274 --- /dev/null +++ b/apps/registration/apps.py @@ -0,0 +1,10 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.apps import AppConfig +from django.utils.translation import gettext_lazy as _ + + +class RegistrationConfig(AppConfig): + name = 'registration' + verbose_name = _('registration') diff --git a/apps/registration/forms.py b/apps/registration/forms.py new file mode 100644 index 00000000..cba5c2ae --- /dev/null +++ b/apps/registration/forms.py @@ -0,0 +1,80 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +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 note.models import NoteSpecial +from note_kfet.inputs import AmountInput + + +class SignUpForm(UserCreationForm): + """ + Pre-register users with all information + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['username'].widget.attrs.pop("autofocus", None) + self.fields['first_name'].widget.attrs.update({"autofocus": "autofocus"}) + self.fields['first_name'].required = True + self.fields['last_name'].required = True + self.fields['email'].required = True + self.fields['email'].help_text = _("This address must be valid.") + + class Meta: + model = User + fields = ('first_name', 'last_name', 'username', 'email', ) + + +class ValidationForm(forms.Form): + """ + Validate the inscription of the new users and pay memberships. + """ + soge = forms.BooleanField( + label=_("Inscription paid by Société Générale"), + required=False, + help_text=_("Check this case is the Société Générale paid the inscription."), + ) + + credit_type = forms.ModelChoiceField( + queryset=NoteSpecial.objects, + label=_("Credit type"), + empty_label=_("No credit"), + required=False, + ) + + credit_amount = forms.IntegerField( + label=_("Credit amount"), + required=False, + initial=0, + widget=AmountInput(), + ) + + last_name = forms.CharField( + label=_("Last name"), + required=False, + ) + + first_name = forms.CharField( + label=_("First name"), + required=False, + ) + + bank = forms.CharField( + label=_("Bank"), + required=False, + ) + + join_BDE = forms.BooleanField( + label=_("Join BDE Club"), + required=False, + initial=True, + ) + + # The user can join the Kfet club at the inscription + join_Kfet = forms.BooleanField( + label=_("Join Kfet Club"), + required=False, + initial=True, + ) diff --git a/apps/registration/migrations/__init__.py b/apps/registration/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/registration/tables.py b/apps/registration/tables.py new file mode 100644 index 00000000..7068f6ca --- /dev/null +++ b/apps/registration/tables.py @@ -0,0 +1,26 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +import django_tables2 as tables +from django.contrib.auth.models import User + + +class FutureUserTable(tables.Table): + """ + Display the list of pre-registered users + """ + phone_number = tables.Column(accessor='profile.phone_number') + + section = tables.Column(accessor='profile.section') + + class Meta: + attrs = { + 'class': 'table table-condensed table-striped table-hover' + } + template_name = 'django_tables2/bootstrap4.html' + fields = ('last_name', 'first_name', 'username', 'email', ) + model = User + row_attrs = { + 'class': 'table-row', + 'data-href': lambda record: record.pk + } diff --git a/apps/registration/tokens.py b/apps/registration/tokens.py new file mode 100644 index 00000000..c5ddc82b --- /dev/null +++ b/apps/registration/tokens.py @@ -0,0 +1,30 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later +# Copied from https://gitlab.crans.org/bombar/codeflix/-/blob/master/codeflix/codeflix/tokens.py + +from django.contrib.auth.tokens import PasswordResetTokenGenerator + + +class AccountActivationTokenGenerator(PasswordResetTokenGenerator): + """ + Create a unique token generator to confirm email addresses. + """ + def _make_hash_value(self, user, timestamp): + """ + Hash the user's primary key and some user state that's sure to change + after an account validation to produce a token that invalidated when + it's used: + 1. The user.profile.email_confirmed field will change upon an account + validation. + 2. The last_login field will usually be updated very shortly after + an account validation. + Failing those things, settings.PASSWORD_RESET_TIMEOUT_DAYS eventually + invalidates the token. + """ + # Truncate microseconds so that tokens are consistent even if the + # database doesn't support microseconds. + login_timestamp = '' if user.last_login is None else user.last_login.replace(microsecond=0, tzinfo=None) + return str(user.pk) + str(user.profile.email_confirmed) + str(login_timestamp) + str(timestamp) + + +email_validation_token = AccountActivationTokenGenerator() diff --git a/apps/registration/urls.py b/apps/registration/urls.py new file mode 100644 index 00000000..14678cbb --- /dev/null +++ b/apps/registration/urls.py @@ -0,0 +1,18 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.urls import path + +from . import views + +app_name = 'registration' +urlpatterns = [ + path('signup/', views.UserCreateView.as_view(), name="signup"), + path('validate_email/sent/', views.UserValidationEmailSentView.as_view(), name='email_validation_sent'), + path('validate_email/resend//', views.UserResendValidationEmailView.as_view(), + name='email_validation_resend'), + path('validate_email///', views.UserValidateView.as_view(), name='email_validation'), + path('validate_user/', views.FutureUserListView.as_view(), name="future_user_list"), + path('validate_user//', views.FutureUserDetailView.as_view(), name="future_user_detail"), + path('validate_user//invalidate/', views.FutureUserInvalidateView.as_view(), name="future_user_invalidate"), +] diff --git a/apps/registration/views.py b/apps/registration/views.py new file mode 100644 index 00000000..35391b05 --- /dev/null +++ b/apps/registration/views.py @@ -0,0 +1,358 @@ +# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +from django.conf import settings +from django.contrib.auth.mixins import LoginRequiredMixin +from django.contrib.auth.models import User +from django.core.exceptions import ValidationError +from django.db.models import Q +from django.shortcuts import resolve_url, redirect +from django.urls import reverse_lazy +from django.utils.http import urlsafe_base64_decode +from django.utils.translation import gettext_lazy as _ +from django.views import View +from django.views.generic import CreateView, TemplateView, DetailView, FormView +from django.views.generic.edit import FormMixin +from django_tables2 import SingleTableView +from member.forms import ProfileForm +from member.models import Membership, Club, Role +from note.models import SpecialTransaction, NoteSpecial +from note.templatetags.pretty_money import pretty_money +from permission.backends import PermissionBackend +from permission.views import ProtectQuerysetMixin + +from .forms import SignUpForm, ValidationForm +from .tables import FutureUserTable +from .tokens import email_validation_token + + +class UserCreateView(CreateView): + """ + Une vue pour inscrire un utilisateur et lui créer un profil + """ + + form_class = SignUpForm + success_url = reverse_lazy('registration:email_validation_sent') + template_name = 'registration/signup.html' + second_form = ProfileForm + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context["profile_form"] = self.second_form() + + return context + + def form_valid(self, form): + """ + If the form is valid, then the user is created with is_active set to False + so that the user cannot log in until the email has been validated. + The user must also wait that someone validate her/his account. + """ + profile_form = ProfileForm(data=self.request.POST) + if not profile_form.is_valid(): + return self.form_invalid(form) + + # Save the user and the profile + user = form.save(commit=False) + user.is_active = False + profile_form.instance.user = user + profile = profile_form.save(commit=False) + user.profile = profile + user.save() + user.refresh_from_db() + profile.user = user + profile.save() + + user.profile.send_email_validation_link() + + return super().form_valid(form) + + +class UserValidateView(TemplateView): + """ + A view to validate the email address. + """ + title = _("Email validation") + template_name = 'registration/email_validation_complete.html' + + def get(self, *args, **kwargs): + """ + With a given token and user id (in params), validate the email address. + """ + assert 'uidb64' in kwargs and 'token' in kwargs + + self.validlink = False + user = self.get_user(kwargs['uidb64']) + token = kwargs['token'] + + # Validate the token + if user is not None and email_validation_token.check_token(user, token): + self.validlink = True + # The user must wait that someone validates the account before the user can be active and login. + user.is_active = user.profile.registration_valid or user.is_superuser + user.profile.email_confirmed = True + user.save() + user.profile.save() + return super().dispatch(*args, **kwargs) + else: + # Display the "Email validation unsuccessful" page. + return self.render_to_response(self.get_context_data()) + + def get_user(self, uidb64): + """ + Get user from the base64-encoded string. + """ + try: + # urlsafe_base64_decode() decodes to bytestring + uid = urlsafe_base64_decode(uidb64).decode() + user = User.objects.get(pk=uid) + except (TypeError, ValueError, OverflowError, User.DoesNotExist, ValidationError): + user = None + return user + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['user'] = self.get_user(self.kwargs["uidb64"]) + context['login_url'] = resolve_url(settings.LOGIN_URL) + if self.validlink: + context['validlink'] = True + else: + context.update({ + 'title': _('Email validation unsuccessful'), + 'validlink': False, + }) + return context + + +class UserValidationEmailSentView(TemplateView): + """ + Display the information that the validation link has been sent. + """ + template_name = 'registration/email_validation_email_sent.html' + title = _('Email validation email sent') + + +class UserResendValidationEmailView(LoginRequiredMixin, ProtectQuerysetMixin, DetailView): + """ + Rensend the email validation link. + """ + model = User + + def get(self, request, *args, **kwargs): + user = self.get_object() + + user.profile.send_email_validation_link() + + url = 'member:user_detail' if user.profile.registration_valid else 'registration:future_user_detail' + return redirect(url, user.id) + + +class FutureUserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): + """ + Display pre-registered users, with a search bar + """ + model = User + table_class = FutureUserTable + template_name = 'registration/future_user_list.html' + + def get_queryset(self, **kwargs): + """ + Filter the table with the given parameter. + :param kwargs: + :return: + """ + qs = super().get_queryset().filter(profile__registration_valid=False) + if "search" in self.request.GET: + pattern = self.request.GET["search"] + + if not pattern: + return qs.none() + + qs = qs.filter( + Q(first_name__iregex=pattern) + | Q(last_name__iregex=pattern) + | Q(profile__section__iregex=pattern) + | Q(username__iregex="^" + pattern) + ) + else: + qs = qs.none() + + return qs[:20] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + context["title"] = _("Unregistered users") + + return context + + +class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, DetailView): + """ + Display information about a pre-registered user, in order to complete the registration. + """ + model = User + form_class = ValidationForm + context_object_name = "user_object" + template_name = "registration/future_profile_detail.html" + + def post(self, request, *args, **kwargs): + form = self.get_form() + self.object = self.get_object() + if form.is_valid(): + return self.form_valid(form) + else: + return self.form_invalid(form) + + def get_queryset(self, **kwargs): + """ + We only display information of a not registered user. + """ + return super().get_queryset().filter(profile__registration_valid=False) + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + + user = self.get_object() + fee = 0 + bde = Club.objects.get(name="BDE") + 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 + ctx["total_fee"] = "{:.02f}".format(fee / 100, ) + + return ctx + + def get_form(self, form_class=None): + form = super().get_form(form_class) + user = self.get_object() + form.fields["last_name"].initial = user.last_name + form.fields["first_name"].initial = user.first_name + return form + + def form_valid(self, form): + user = self.get_object() + + # Get form data + soge = form.cleaned_data["soge"] + credit_type = form.cleaned_data["credit_type"] + credit_amount = form.cleaned_data["credit_amount"] + last_name = form.cleaned_data["last_name"] + first_name = form.cleaned_data["first_name"] + bank = form.cleaned_data["bank"] + join_BDE = form.cleaned_data["join_BDE"] + join_Kfet = form.cleaned_data["join_Kfet"] + + if soge: + # If Société Générale pays the inscription, the user joins the two clubs + join_BDE = True + join_Kfet = True + + if not join_BDE: + form.add_error('join_BDE', _("You must join the BDE.")) + return super().form_invalid(form) + + fee = 0 + bde = Club.objects.get(name="BDE") + bde_fee = bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid + if join_BDE: + fee += bde_fee + kfet = Club.objects.get(name="Kfet") + kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid + if join_Kfet: + fee += kfet_fee + + if soge: + # Fill payment information if Société Générale pays the inscription + credit_type = NoteSpecial.objects.get(special_type="Virement bancaire") + credit_amount = fee + bank = "Société générale" + + print("OK") + + if join_Kfet and not join_BDE: + form.add_error('join_Kfet', _("You must join BDE club before joining Kfet club.")) + + if fee > credit_amount: + # Check if the user credits enough money + form.add_error('credit_type', + _("The entered amount is not enough for the memberships, should be at least {}") + .format(pretty_money(fee))) + return self.form_invalid(form) + + if credit_type is not None and credit_amount > 0: + if not last_name or not first_name or (not bank and credit_type.special_type == "Chèque"): + if not last_name: + form.add_error('last_name', _("This field is required.")) + if not first_name: + form.add_error('first_name', _("This field is required.")) + if not bank and credit_type.special_type == "Chèque": + form.add_error('bank', _("This field is required.")) + return self.form_invalid(form) + + # Save the user and finally validate the registration + # Saving the user creates the associated note + ret = super().form_valid(form) + user.is_active = user.profile.email_confirmed or user.is_superuser + user.profile.registration_valid = True + # Store if Société générale paid for next years + user.profile.soge = soge + user.save() + user.profile.save() + + if credit_type is not None and credit_amount > 0: + # Credit the note + SpecialTransaction.objects.create( + source=credit_type, + destination=user.note, + quantity=1, + amount=credit_amount, + reason="Crédit " + ("Société générale" if soge else credit_type.special_type) + " (Inscription)", + last_name=last_name, + first_name=first_name, + bank=bank, + valid=True, + ) + + if join_BDE: + # Create membership for the user to the BDE starting today + membership = Membership.objects.create( + club=bde, + user=user, + fee=bde_fee, + ) + membership.roles.add(Role.objects.get(name="Adhérent BDE")) + membership.save() + + if join_Kfet: + # Create membership for the user to the Kfet starting today + membership = Membership.objects.create( + club=kfet, + user=user, + fee=kfet_fee, + ) + membership.roles.add(Role.objects.get(name="Adhérent Kfet")) + membership.save() + + return ret + + def get_success_url(self): + return reverse_lazy('member:user_detail', args=(self.get_object().pk, )) + + +class FutureUserInvalidateView(ProtectQuerysetMixin, LoginRequiredMixin, View): + """ + Delete a pre-registered user. + """ + + def get(self, request, *args, **kwargs): + """ + Delete the pre-registered user which id is given in the URL. + """ + user = User.objects.filter(profile__registration_valid=False)\ + .filter(PermissionBackend.filter_queryset(request.user, User, "change", "is_valid"))\ + .get(pk=self.kwargs["pk"]) + + user.delete() + + return redirect('registration:future_user_list') diff --git a/apps/treasury/forms.py b/apps/treasury/forms.py index ad479e14..b09a46c7 100644 --- a/apps/treasury/forms.py +++ b/apps/treasury/forms.py @@ -53,7 +53,7 @@ ProductFormSet = forms.inlineformset_factory( class ProductFormSetHelper(FormHelper): """ - Specify some template informations for the product form. + Specify some template information for the product form. """ def __init__(self, form=None): diff --git a/apps/treasury/models.py b/apps/treasury/models.py index bcd89db9..ca1da3a4 100644 --- a/apps/treasury/models.py +++ b/apps/treasury/models.py @@ -59,6 +59,10 @@ class Invoice(models.Model): verbose_name=_("Acquitted"), ) + class Meta: + verbose_name = _("invoice") + verbose_name_plural = _("invoices") + class Product(models.Model): """ @@ -95,6 +99,10 @@ class Product(models.Model): def total_euros(self): return self.total / 100 + class Meta: + verbose_name = _("product") + verbose_name_plural = _("products") + class RemittanceType(models.Model): """ @@ -109,6 +117,10 @@ class RemittanceType(models.Model): def __str__(self): return str(self.note) + class Meta: + verbose_name = _("remittance type") + verbose_name_plural = _("remittance types") + class Remittance(models.Model): """ @@ -136,6 +148,10 @@ class Remittance(models.Model): verbose_name=_("Closed"), ) + class Meta: + verbose_name = _("remittance") + verbose_name_plural = _("remittances") + @property def transactions(self): """ @@ -187,3 +203,7 @@ class SpecialTransactionProxy(models.Model): null=True, verbose_name=_("Remittance"), ) + + class Meta: + verbose_name = _("special transaction proxy") + verbose_name_plural = _("special transaction proxies") diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po index 16c73f35..f616ffd6 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-04-01 18:39+0200\n" +"POT-Creation-Date: 2020-04-09 21:59+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,36 +18,37 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: apps/activity/apps.py:10 apps/activity/models.py:111 -#: apps/activity/models.py:120 +#: apps/activity/apps.py:10 apps/activity/models.py:102 +#: apps/activity/models.py:111 msgid "activity" msgstr "" -#: apps/activity/forms.py:45 apps/activity/models.py:217 +#: apps/activity/forms.py:45 apps/activity/models.py:208 msgid "You can't invite someone once the activity is started." msgstr "" -#: apps/activity/forms.py:48 apps/activity/models.py:220 +#: apps/activity/forms.py:48 apps/activity/models.py:211 msgid "This activity is not validated yet." msgstr "" -#: apps/activity/forms.py:58 apps/activity/models.py:228 +#: apps/activity/forms.py:58 apps/activity/models.py:219 msgid "This person has been already invited 5 times this year." msgstr "" -#: apps/activity/forms.py:62 apps/activity/models.py:232 +#: apps/activity/forms.py:62 apps/activity/models.py:223 msgid "This person is already invited." msgstr "" -#: apps/activity/forms.py:66 apps/activity/models.py:236 +#: apps/activity/forms.py:66 apps/activity/models.py:227 msgid "You can't invite more than 3 people to this activity." msgstr "" #: apps/activity/models.py:23 apps/activity/models.py:48 -#: apps/member/models.py:66 apps/member/models.py:169 +#: apps/member/models.py:99 apps/member/models.py:202 #: apps/note/models/notes.py:188 apps/note/models/transactions.py:24 -#: apps/note/models/transactions.py:44 apps/note/models/transactions.py:232 +#: apps/note/models/transactions.py:44 apps/note/models/transactions.py:237 #: templates/member/club_info.html:13 templates/member/profile_info.html:14 +#: templates/registration/future_profile_detail.html:16 msgid "name" msgstr "" @@ -67,18 +68,18 @@ msgstr "" msgid "activity types" msgstr "" -#: apps/activity/models.py:53 apps/note/models/transactions.py:69 +#: apps/activity/models.py:53 apps/note/models/transactions.py:74 #: apps/permission/models.py:103 templates/activity/activity_detail.html:16 msgid "description" msgstr "" #: apps/activity/models.py:60 apps/note/models/notes.py:164 -#: apps/note/models/transactions.py:62 +#: apps/note/models/transactions.py:64 #: templates/activity/activity_detail.html:19 msgid "type" msgstr "" -#: apps/activity/models.py:66 apps/logs/models.py:21 apps/member/models.py:190 +#: apps/activity/models.py:66 apps/logs/models.py:21 apps/member/models.py:223 #: apps/note/models/notes.py:117 msgid "user" msgstr "" @@ -87,73 +88,82 @@ msgstr "" msgid "organizer" msgstr "" -#: apps/activity/models.py:82 apps/activity/models.py:131 apps/note/apps.py:14 -#: apps/note/models/notes.py:58 -msgid "note" -msgstr "" - -#: apps/activity/models.py:89 templates/activity/activity_detail.html:36 +#: apps/activity/models.py:80 templates/activity/activity_detail.html:36 msgid "attendees club" msgstr "" -#: apps/activity/models.py:93 templates/activity/activity_detail.html:22 +#: apps/activity/models.py:84 templates/activity/activity_detail.html:22 msgid "start date" msgstr "" -#: apps/activity/models.py:97 templates/activity/activity_detail.html:25 +#: apps/activity/models.py:88 templates/activity/activity_detail.html:25 msgid "end date" msgstr "" -#: apps/activity/models.py:102 apps/note/models/transactions.py:134 +#: apps/activity/models.py:93 apps/note/models/transactions.py:139 #: templates/activity/activity_detail.html:47 msgid "valid" msgstr "" -#: apps/activity/models.py:107 templates/activity/activity_detail.html:61 +#: apps/activity/models.py:98 templates/activity/activity_detail.html:61 msgid "open" msgstr "" -#: apps/activity/models.py:112 +#: apps/activity/models.py:103 msgid "activities" msgstr "" -#: apps/activity/models.py:125 +#: apps/activity/models.py:116 msgid "entry time" msgstr "" -#: apps/activity/models.py:148 +#: apps/activity/models.py:122 apps/note/apps.py:14 +#: apps/note/models/notes.py:58 +msgid "note" +msgstr "" + +#: apps/activity/models.py:133 templates/activity/activity_entry.html:38 +msgid "entry" +msgstr "" + +#: apps/activity/models.py:134 templates/activity/activity_entry.html:38 +msgid "entries" +msgstr "" + +#: apps/activity/models.py:141 msgid "Already entered on " msgstr "" -#: apps/activity/models.py:148 apps/activity/tables.py:54 +#: apps/activity/models.py:141 apps/activity/tables.py:54 msgid "{:%Y-%m-%d %H:%M:%S}" msgstr "" -#: apps/activity/models.py:156 +#: apps/activity/models.py:149 msgid "The balance is negative." msgstr "" -#: apps/activity/models.py:188 +#: apps/activity/models.py:179 msgid "last name" msgstr "" -#: apps/activity/models.py:193 templates/member/profile_info.html:14 +#: apps/activity/models.py:184 templates/member/profile_info.html:14 +#: templates/registration/future_profile_detail.html:16 msgid "first name" msgstr "" -#: apps/activity/models.py:200 +#: apps/activity/models.py:191 msgid "inviter" msgstr "" -#: apps/activity/models.py:241 +#: apps/activity/models.py:232 msgid "guest" msgstr "" -#: apps/activity/models.py:242 +#: apps/activity/models.py:233 msgid "guests" msgstr "" -#: apps/activity/models.py:254 +#: apps/activity/models.py:245 msgid "Invitation" msgstr "" @@ -165,16 +175,18 @@ msgstr "" msgid "remove" msgstr "" -#: apps/activity/tables.py:75 apps/treasury/models.py:126 +#: apps/activity/tables.py:75 apps/treasury/models.py:138 msgid "Type" msgstr "" -#: apps/activity/tables.py:77 apps/treasury/forms.py:121 +#: apps/activity/tables.py:77 apps/member/forms.py:75 +#: apps/registration/forms.py:55 apps/treasury/forms.py:121 msgid "Last name" msgstr "" -#: apps/activity/tables.py:79 apps/treasury/forms.py:123 -#: templates/note/transaction_form.html:92 +#: apps/activity/tables.py:79 apps/member/forms.py:80 +#: apps/registration/forms.py:60 apps/treasury/forms.py:123 +#: templates/note/transaction_form.html:97 msgid "First name" msgstr "" @@ -182,15 +194,15 @@ msgstr "" msgid "Note" msgstr "" -#: apps/activity/tables.py:83 +#: apps/activity/tables.py:83 apps/member/tables.py:41 msgid "Balance" msgstr "" -#: apps/activity/views.py:45 templates/base.html:94 +#: apps/activity/views.py:45 templates/base.html:106 msgid "Activities" msgstr "" -#: apps/activity/views.py:153 +#: apps/activity/views.py:154 msgid "Entry for activity \"{}\"" msgstr "" @@ -226,12 +238,12 @@ msgstr "" msgid "create" msgstr "" -#: apps/logs/models.py:61 apps/note/tables.py:142 +#: apps/logs/models.py:61 apps/note/tables.py:144 #: templates/activity/activity_detail.html:67 msgid "edit" msgstr "" -#: apps/logs/models.py:62 apps/note/tables.py:120 apps/note/tables.py:146 +#: apps/logs/models.py:62 apps/note/tables.py:120 apps/note/tables.py:149 msgid "delete" msgstr "" @@ -247,170 +259,243 @@ msgstr "" msgid "Logs cannot be destroyed." msgstr "" +#: apps/logs/models.py:80 +msgid "changelog" +msgstr "" + +#: apps/logs/models.py:81 +msgid "changelogs" +msgstr "" + #: apps/member/apps.py:14 msgid "member" msgstr "" -#: apps/member/models.py:28 +#: apps/member/forms.py:54 apps/registration/forms.py:35 +msgid "Inscription paid by Société Générale" +msgstr "" + +#: apps/member/forms.py:56 apps/registration/forms.py:37 +msgid "Check this case is the Société Générale paid the inscription." +msgstr "" + +#: apps/member/forms.py:61 apps/registration/forms.py:42 +msgid "Credit type" +msgstr "" + +#: apps/member/forms.py:62 apps/registration/forms.py:43 +msgid "No credit" +msgstr "" + +#: apps/member/forms.py:64 +msgid "You can credit the note of the user." +msgstr "" + +#: apps/member/forms.py:68 apps/registration/forms.py:48 +msgid "Credit amount" +msgstr "" + +#: apps/member/forms.py:85 apps/registration/forms.py:65 +#: apps/treasury/forms.py:125 templates/note/transaction_form.html:103 +msgid "Bank" +msgstr "" + +#: apps/member/models.py:33 +#: templates/registration/future_profile_detail.html:47 msgid "phone number" msgstr "" -#: apps/member/models.py:34 templates/member/profile_info.html:27 +#: apps/member/models.py:39 templates/member/profile_info.html:27 +#: templates/registration/future_profile_detail.html:41 msgid "section" msgstr "" -#: apps/member/models.py:35 +#: apps/member/models.py:40 msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" msgstr "" -#: apps/member/models.py:41 templates/member/profile_info.html:30 +#: apps/member/models.py:46 templates/member/profile_info.html:30 +#: templates/registration/future_profile_detail.html:44 msgid "address" msgstr "" -#: apps/member/models.py:47 +#: apps/member/models.py:52 +#: templates/registration/future_profile_detail.html:50 msgid "paid" msgstr "" -#: apps/member/models.py:52 apps/member/models.py:53 +#: apps/member/models.py:53 +msgid "Tells if the user receive a salary." +msgstr "" + +#: apps/member/models.py:58 +msgid "email confirmed" +msgstr "" + +#: apps/member/models.py:63 +msgid "registration valid" +msgstr "" + +#: apps/member/models.py:68 +msgid "Société générale" +msgstr "" + +#: apps/member/models.py:69 +msgid "Has the user ever be paid by the Société générale?" +msgstr "" + +#: apps/member/models.py:74 apps/member/models.py:75 msgid "user profile" msgstr "" -#: apps/member/models.py:71 templates/member/club_info.html:46 +#: apps/member/models.py:104 templates/member/club_info.html:46 +#: templates/registration/future_profile_detail.html:22 msgid "email" msgstr "" -#: apps/member/models.py:78 +#: apps/member/models.py:111 msgid "parent club" msgstr "" -#: apps/member/models.py:87 +#: apps/member/models.py:120 msgid "require memberships" msgstr "" -#: apps/member/models.py:88 +#: apps/member/models.py:121 msgid "Uncheck if this club don't require memberships." msgstr "" -#: apps/member/models.py:93 templates/member/club_info.html:35 +#: apps/member/models.py:126 templates/member/club_info.html:35 msgid "membership fee (paid students)" msgstr "" -#: apps/member/models.py:98 templates/member/club_info.html:38 +#: apps/member/models.py:131 templates/member/club_info.html:38 msgid "membership fee (unpaid students)" msgstr "" -#: apps/member/models.py:104 templates/member/club_info.html:28 +#: apps/member/models.py:137 templates/member/club_info.html:28 msgid "membership duration" msgstr "" -#: apps/member/models.py:105 +#: apps/member/models.py:138 msgid "The longest time (in days) a membership can last (NULL = infinite)." msgstr "" -#: apps/member/models.py:112 templates/member/club_info.html:22 +#: apps/member/models.py:145 templates/member/club_info.html:22 msgid "membership start" msgstr "" -#: apps/member/models.py:113 +#: apps/member/models.py:146 msgid "How long after January 1st the members can renew their membership." msgstr "" -#: apps/member/models.py:120 templates/member/club_info.html:25 +#: apps/member/models.py:153 templates/member/club_info.html:25 msgid "membership end" msgstr "" -#: apps/member/models.py:121 +#: apps/member/models.py:154 msgid "" "How long the membership can last after January 1st of the next year after " "members can renew their membership." msgstr "" -#: apps/member/models.py:154 apps/member/models.py:196 +#: apps/member/models.py:187 apps/member/models.py:229 #: apps/note/models/notes.py:139 msgid "club" msgstr "" -#: apps/member/models.py:155 +#: apps/member/models.py:188 msgid "clubs" msgstr "" -#: apps/member/models.py:175 apps/permission/models.py:288 +#: apps/member/models.py:208 apps/permission/models.py:294 msgid "role" msgstr "" -#: apps/member/models.py:176 apps/member/models.py:201 +#: apps/member/models.py:209 apps/member/models.py:234 msgid "roles" msgstr "" -#: apps/member/models.py:205 +#: apps/member/models.py:239 msgid "membership starts on" msgstr "" -#: apps/member/models.py:209 +#: apps/member/models.py:243 msgid "membership ends on" msgstr "" -#: apps/member/models.py:214 +#: apps/member/models.py:248 msgid "fee" msgstr "" -#: apps/member/models.py:226 apps/member/views.py:383 +#: apps/member/models.py:266 apps/member/views.py:500 msgid "User is not a member of the parent club" msgstr "" -#: apps/member/models.py:236 apps/member/views.py:392 +#: apps/member/models.py:276 apps/member/views.py:509 msgid "User is already a member of the club" msgstr "" -#: apps/member/models.py:271 +#: apps/member/models.py:314 #, python-brace-format msgid "Membership of {user} for the club {club}" msgstr "" -#: apps/member/models.py:274 +#: apps/member/models.py:317 msgid "membership" msgstr "" -#: apps/member/models.py:275 +#: apps/member/models.py:318 msgid "memberships" msgstr "" -#: apps/member/tables.py:73 +#: apps/member/tables.py:112 msgid "Renew" msgstr "" -#: apps/member/views.py:80 templates/member/profile_info.html:45 +#: apps/member/views.py:62 apps/registration/forms.py:23 +msgid "This address must be valid." +msgstr "" + +#: apps/member/views.py:65 templates/member/profile_info.html:45 +#: templates/registration/future_profile_detail.html:55 msgid "Update Profile" msgstr "" -#: apps/member/views.py:93 +#: apps/member/views.py:75 msgid "An alias with a similar name already exists." msgstr "" -#: apps/member/views.py:379 +#: apps/member/views.py:180 +msgid "Search user" +msgstr "" + +#: apps/member/views.py:495 msgid "" "This user don't have enough money to join this club, and can't have a " "negative balance." msgstr "" -#: apps/member/views.py:396 apps/member/views.py:428 +#: apps/member/views.py:513 msgid "The membership must start after {:%m-%d-%Y}." msgstr "" -#: apps/member/views.py:401 apps/member/views.py:433 +#: apps/member/views.py:518 msgid "The membership must begin before {:%m-%d-%Y}." msgstr "" -#: apps/member/views.py:455 -msgid "This membership is already renewed" +#: apps/member/views.py:528 apps/member/views.py:530 apps/member/views.py:532 +#: apps/registration/views.py:286 apps/registration/views.py:288 +#: apps/registration/views.py:290 +msgid "This field is required." msgstr "" -#: apps/note/admin.py:120 apps/note/models/transactions.py:94 +#: apps/note/admin.py:120 apps/note/models/transactions.py:99 msgid "source" msgstr "" -#: apps/note/admin.py:128 apps/note/admin.py:163 -#: apps/note/models/transactions.py:53 apps/note/models/transactions.py:107 +#: apps/note/admin.py:128 apps/note/admin.py:170 +#: apps/note/models/transactions.py:54 apps/note/models/transactions.py:112 msgid "destination" msgstr "" @@ -452,7 +537,7 @@ msgstr "" msgid "display image" msgstr "" -#: apps/note/models/notes.py:53 apps/note/models/transactions.py:117 +#: apps/note/models/notes.py:53 apps/note/models/transactions.py:122 msgid "created at" msgstr "" @@ -535,78 +620,85 @@ msgstr "" msgid "A template with this name already exist" msgstr "" -#: apps/note/models/transactions.py:56 apps/note/models/transactions.py:125 +#: apps/note/models/transactions.py:58 apps/note/models/transactions.py:130 msgid "amount" msgstr "" -#: apps/note/models/transactions.py:57 +#: apps/note/models/transactions.py:59 msgid "in centimes" msgstr "" -#: apps/note/models/transactions.py:75 +#: apps/note/models/transactions.py:70 +msgid "display" +msgstr "" + +#: apps/note/models/transactions.py:80 msgid "transaction template" msgstr "" -#: apps/note/models/transactions.py:76 +#: apps/note/models/transactions.py:81 msgid "transaction templates" msgstr "" -#: apps/note/models/transactions.py:100 apps/note/models/transactions.py:113 +#: apps/note/models/transactions.py:105 apps/note/models/transactions.py:118 #: apps/note/tables.py:33 apps/note/tables.py:42 msgid "used alias" msgstr "" -#: apps/note/models/transactions.py:121 +#: apps/note/models/transactions.py:126 msgid "quantity" msgstr "" -#: apps/note/models/transactions.py:129 +#: apps/note/models/transactions.py:134 msgid "reason" msgstr "" -#: apps/note/models/transactions.py:139 apps/note/tables.py:95 +#: apps/note/models/transactions.py:144 apps/note/tables.py:95 msgid "invalidity reason" msgstr "" -#: apps/note/models/transactions.py:147 +#: apps/note/models/transactions.py:152 msgid "transaction" msgstr "" -#: apps/note/models/transactions.py:148 +#: apps/note/models/transactions.py:153 msgid "transactions" msgstr "" -#: apps/note/models/transactions.py:202 templates/base.html:84 +#: apps/note/models/transactions.py:207 +#: templates/activity/activity_entry.html:13 templates/base.html:84 #: templates/note/transaction_form.html:19 -#: templates/note/transaction_form.html:140 +#: templates/note/transaction_form.html:145 msgid "Transfer" msgstr "" -#: apps/note/models/transactions.py:222 +#: apps/note/models/transactions.py:227 msgid "Template" msgstr "" -#: apps/note/models/transactions.py:237 +#: apps/note/models/transactions.py:242 msgid "first_name" msgstr "" -#: apps/note/models/transactions.py:242 +#: apps/note/models/transactions.py:247 msgid "bank" msgstr "" -#: apps/note/models/transactions.py:248 templates/note/transaction_form.html:24 +#: apps/note/models/transactions.py:253 +#: templates/activity/activity_entry.html:17 +#: templates/note/transaction_form.html:24 msgid "Credit" msgstr "" -#: apps/note/models/transactions.py:248 templates/note/transaction_form.html:28 +#: apps/note/models/transactions.py:253 templates/note/transaction_form.html:28 msgid "Debit" msgstr "" -#: apps/note/models/transactions.py:264 apps/note/models/transactions.py:269 +#: apps/note/models/transactions.py:269 apps/note/models/transactions.py:274 msgid "membership transaction" msgstr "" -#: apps/note/models/transactions.py:265 +#: apps/note/models/transactions.py:270 msgid "membership transactions" msgstr "" @@ -622,20 +714,29 @@ msgstr "" msgid "No reason specified" msgstr "" -#: apps/note/views.py:39 +#: apps/note/tables.py:122 apps/note/tables.py:151 +msgid "Delete" +msgstr "" + +#: apps/note/tables.py:146 templates/member/club_info.html:55 +#: templates/note/conso_form.html:121 +msgid "Edit" +msgstr "" + +#: apps/note/views.py:40 msgid "Transfer money" msgstr "" -#: apps/note/views.py:100 templates/base.html:79 +#: apps/note/views.py:109 templates/base.html:79 msgid "Consumptions" msgstr "" -#: apps/permission/models.py:82 apps/permission/models.py:275 +#: apps/permission/models.py:82 apps/permission/models.py:281 #, python-brace-format msgid "Can {type} {model}.{field} in {query}" msgstr "" -#: apps/permission/models.py:84 apps/permission/models.py:277 +#: apps/permission/models.py:84 apps/permission/models.py:283 #, python-brace-format msgid "Can {type} {model} in {query}" msgstr "" @@ -644,11 +745,72 @@ msgstr "" msgid "rank" msgstr "" +#: apps/permission/models.py:110 +msgid "permission mask" +msgstr "" + +#: apps/permission/models.py:111 +msgid "permission masks" +msgstr "" + #: apps/permission/models.py:160 +msgid "permission" +msgstr "" + +#: apps/permission/models.py:161 +msgid "permissions" +msgstr "" + +#: apps/permission/models.py:166 msgid "Specifying field applies only to view and change permission types." msgstr "" -#: apps/treasury/apps.py:12 templates/base.html:99 +#: apps/permission/models.py:304 apps/permission/models.py:305 +msgid "role permissions" +msgstr "" + +#: apps/registration/apps.py:10 +msgid "registration" +msgstr "" + +#: apps/registration/forms.py:70 +msgid "Join BDE Club" +msgstr "" + +#: apps/registration/forms.py:77 +msgid "Join Kfet Club" +msgstr "" + +#: apps/registration/views.py:75 +msgid "Email validation" +msgstr "" + +#: apps/registration/views.py:121 +msgid "Email validation unsuccessful" +msgstr "" + +#: apps/registration/views.py:132 +msgid "Email validation email sent" +msgstr "" + +#: apps/registration/views.py:185 +msgid "Unregistered users" +msgstr "" + +#: apps/registration/views.py:252 +msgid "You must join the BDE." +msgstr "" + +#: apps/registration/views.py:274 +msgid "You must join BDE club before joining Kfet club." +msgstr "" + +#: apps/registration/views.py:279 +msgid "" +"The entered amount is not enough for the memberships, should be at least {}" +msgstr "" + +#: apps/treasury/apps.py:12 templates/base.html:111 msgid "Treasury" msgstr "" @@ -673,12 +835,8 @@ msgstr "" msgid "You can't change the type of the remittance." msgstr "" -#: apps/treasury/forms.py:125 templates/note/transaction_form.html:98 -msgid "Bank" -msgstr "" - #: apps/treasury/forms.py:127 apps/treasury/tables.py:47 -#: templates/note/transaction_form.html:128 +#: templates/note/transaction_form.html:133 #: templates/treasury/remittance_form.html:18 msgid "Amount" msgstr "" @@ -699,7 +857,7 @@ msgstr "" msgid "Description" msgstr "" -#: apps/treasury/models.py:46 templates/note/transaction_form.html:86 +#: apps/treasury/models.py:46 templates/note/transaction_form.html:91 msgid "Name" msgstr "" @@ -715,40 +873,80 @@ msgstr "" msgid "Acquitted" msgstr "" -#: apps/treasury/models.py:75 -msgid "Designation" +#: apps/treasury/models.py:63 +msgid "invoice" +msgstr "" + +#: apps/treasury/models.py:64 +msgid "invoices" msgstr "" #: apps/treasury/models.py:79 -msgid "Quantity" +msgid "Designation" msgstr "" #: apps/treasury/models.py:83 +msgid "Quantity" +msgstr "" + +#: apps/treasury/models.py:87 msgid "Unit price" msgstr "" -#: apps/treasury/models.py:120 +#: apps/treasury/models.py:103 +msgid "product" +msgstr "" + +#: apps/treasury/models.py:104 +msgid "products" +msgstr "" + +#: apps/treasury/models.py:121 +msgid "remittance type" +msgstr "" + +#: apps/treasury/models.py:122 +msgid "remittance types" +msgstr "" + +#: apps/treasury/models.py:132 msgid "Date" msgstr "" -#: apps/treasury/models.py:131 +#: apps/treasury/models.py:143 msgid "Comment" msgstr "" -#: apps/treasury/models.py:136 +#: apps/treasury/models.py:148 msgid "Closed" msgstr "" -#: apps/treasury/models.py:169 +#: apps/treasury/models.py:152 +msgid "remittance" +msgstr "" + +#: apps/treasury/models.py:153 +msgid "remittances" +msgstr "" + +#: apps/treasury/models.py:185 msgid "Remittance #{:d}: {}" msgstr "" -#: apps/treasury/models.py:188 apps/treasury/tables.py:76 +#: apps/treasury/models.py:204 apps/treasury/tables.py:76 #: apps/treasury/tables.py:84 templates/treasury/invoice_list.html:13 #: templates/treasury/remittance_list.html:13 msgid "Remittance" msgstr "" +#: apps/treasury/models.py:208 +msgid "special transaction proxy" +msgstr "" + +#: apps/treasury/models.py:209 +msgid "special transaction proxies" +msgstr "" + #: apps/treasury/tables.py:19 msgid "Invoice #{:d}" msgstr "" @@ -781,15 +979,15 @@ msgid "" "again unless your session expires or you logout." msgstr "" -#: note_kfet/settings/base.py:152 +#: note_kfet/settings/base.py:153 msgid "German" msgstr "" -#: note_kfet/settings/base.py:153 +#: note_kfet/settings/base.py:154 msgid "English" msgstr "" -#: note_kfet/settings/base.py:154 +#: note_kfet/settings/base.py:155 msgid "French" msgstr "" @@ -825,18 +1023,15 @@ msgstr "" msgid "Guests list" msgstr "" -#: templates/activity/activity_entry.html:10 +#: templates/activity/activity_entry.html:22 +#: templates/note/transaction_form.html:33 +msgid "Entries" +msgstr "" + +#: templates/activity/activity_entry.html:30 msgid "Return to activity page" msgstr "" -#: templates/activity/activity_entry.html:18 -msgid "entries" -msgstr "" - -#: templates/activity/activity_entry.html:18 -msgid "entry" -msgstr "" - #: templates/activity/activity_list.html:5 msgid "Upcoming activities" msgstr "" @@ -858,9 +1053,23 @@ msgid "The ENS Paris-Saclay BDE note." msgstr "" #: templates/base.html:89 +msgid "Users" +msgstr "" + +#: templates/base.html:94 msgid "Clubs" msgstr "" +#: templates/base.html:100 +msgid "Registrations" +msgstr "" + +#: templates/base.html:150 +msgid "" +"Your e-mail address is not validated. Please check your mail inbox and click " +"on the validation link." +msgstr "" + #: templates/cas_server/base.html:7 msgid "Central Authentication Service" msgstr "" @@ -936,10 +1145,6 @@ msgstr "" msgid "Add member" msgstr "" -#: templates/member/club_info.html:55 templates/note/conso_form.html:121 -msgid "Edit" -msgstr "" - #: templates/member/club_info.html:59 templates/member/profile_info.html:48 msgid "View Profile" msgstr "" @@ -956,11 +1161,11 @@ msgstr "" msgid "club listing " msgstr "" -#: templates/member/club_tables.html:9 +#: templates/member/club_tables.html:6 msgid "Member of the Club" msgstr "" -#: templates/member/club_tables.html:22 templates/member/profile_tables.html:22 +#: templates/member/club_tables.html:17 templates/member/profile_tables.html:28 msgid "Transaction history" msgstr "" @@ -977,18 +1182,22 @@ msgid "Regenerate token" msgstr "" #: templates/member/profile_info.html:5 +#: templates/registration/future_profile_detail.html:12 msgid "Account #" msgstr "" #: templates/member/profile_info.html:17 +#: templates/registration/future_profile_detail.html:19 msgid "username" msgstr "" #: templates/member/profile_info.html:20 +#: templates/registration/future_profile_detail.html:34 msgid "password" msgstr "" #: templates/member/profile_info.html:23 +#: templates/registration/future_profile_detail.html:37 msgid "Change password" msgstr "" @@ -1000,7 +1209,17 @@ msgstr "" msgid "Manage auth token" msgstr "" -#: templates/member/profile_tables.html:9 +#: templates/member/profile_tables.html:7 +#: templates/registration/future_profile_detail.html:28 +msgid "This user doesn't have confirmed his/her e-mail address." +msgstr "" + +#: templates/member/profile_tables.html:8 +#: templates/registration/future_profile_detail.html:29 +msgid "Click here to resend a validation link." +msgstr "" + +#: templates/member/profile_tables.html:15 msgid "View my memberships" msgstr "" @@ -1008,12 +1227,12 @@ msgstr "" msgid "Save Changes" msgstr "" -#: templates/member/signup.html:5 templates/member/signup.html:8 -#: templates/member/signup.html:14 -msgid "Sign up" +#: templates/member/user_list.html:14 +#: templates/registration/future_user_list.html:17 +msgid "There is no pending user with this pattern." msgstr "" -#: templates/note/conso_form.html:28 templates/note/transaction_form.html:50 +#: templates/note/conso_form.html:28 templates/note/transaction_form.html:55 msgid "Select emitters" msgstr "" @@ -1037,7 +1256,7 @@ msgstr "" msgid "Double consumptions" msgstr "" -#: templates/note/conso_form.html:141 templates/note/transaction_form.html:147 +#: templates/note/conso_form.html:141 templates/note/transaction_form.html:152 msgid "Recent transactions history" msgstr "" @@ -1045,29 +1264,29 @@ msgstr "" msgid "Gift" msgstr "" -#: templates/note/transaction_form.html:68 +#: templates/note/transaction_form.html:73 msgid "External payment" msgstr "" -#: templates/note/transaction_form.html:76 +#: templates/note/transaction_form.html:81 msgid "Transfer type" msgstr "" -#: templates/note/transaction_form.html:111 -#: templates/note/transaction_form.html:164 -#: templates/note/transaction_form.html:171 +#: templates/note/transaction_form.html:116 +#: templates/note/transaction_form.html:169 +#: templates/note/transaction_form.html:176 msgid "Select receivers" msgstr "" -#: templates/note/transaction_form.html:133 +#: templates/note/transaction_form.html:138 msgid "Reason" msgstr "" -#: templates/note/transaction_form.html:178 +#: templates/note/transaction_form.html:183 msgid "Credit note" msgstr "" -#: templates/note/transaction_form.html:185 +#: templates/note/transaction_form.html:190 msgid "Debit note" msgstr "" @@ -1087,14 +1306,50 @@ msgstr "" msgid "buttons listing " msgstr "" -#: templates/note/transactiontemplate_list.html:71 +#: templates/note/transactiontemplate_list.html:70 msgid "button successfully deleted " msgstr "" -#: templates/note/transactiontemplate_list.html:75 +#: templates/note/transactiontemplate_list.html:74 msgid "Unable to delete button " msgstr "" +#: templates/registration/email_validation_complete.html:6 +msgid "Your email have successfully been validated." +msgstr "" + +#: templates/registration/email_validation_complete.html:8 +#, python-format +msgid "You can now log in." +msgstr "" + +#: templates/registration/email_validation_complete.html:10 +msgid "" +"You must pay now your membership in the Kfet to complete your registration." +msgstr "" + +#: templates/registration/email_validation_complete.html:13 +msgid "" +"The link was invalid. The token may have expired. Please send us an email to " +"activate your account." +msgstr "" + +#: templates/registration/future_profile_detail.html:56 +msgid "Delete registration" +msgstr "" + +#: templates/registration/future_profile_detail.html:64 +msgid "Validate account" +msgstr "" + +#: templates/registration/future_profile_detail.html:71 +msgid "Validate registration" +msgstr "" + +#: templates/registration/future_user_list.html:7 +msgid "New user" +msgstr "" + #: templates/registration/logged_out.html:8 msgid "Thanks for spending some quality time with the Web site today." msgstr "" @@ -1128,6 +1383,36 @@ msgstr "" msgid "Forgotten your password or username?" msgstr "" +#: templates/registration/mails/email_validation_email.html:3 +msgid "Hi" +msgstr "" + +#: templates/registration/mails/email_validation_email.html:5 +msgid "" +"You recently registered on the Note Kfet. Please click on the link below to " +"confirm your registration." +msgstr "" + +#: templates/registration/mails/email_validation_email.html:9 +msgid "" +"This link is only valid for a couple of days, after that you will need to " +"contact us to validate your email." +msgstr "" + +#: templates/registration/mails/email_validation_email.html:11 +msgid "" +"After that, you'll have to wait that someone validates your account before " +"you can log in. You will need to pay your membership in the Kfet." +msgstr "" + +#: templates/registration/mails/email_validation_email.html:13 +msgid "Thanks" +msgstr "" + +#: templates/registration/mails/email_validation_email.html:15 +msgid "The Note Kfet team." +msgstr "" + #: templates/registration/password_change_done.html:8 msgid "Your password was changed." msgstr "" @@ -1181,6 +1466,11 @@ msgstr "" msgid "Reset my password" msgstr "" +#: templates/registration/signup.html:5 templates/registration/signup.html:8 +#: templates/registration/signup.html:14 +msgid "Sign up" +msgstr "" + #: templates/treasury/invoice_form.html:6 msgid "Invoices list" msgstr "" diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index 9754fa9c..27636a65 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-01 18:39+0200\n" +"POT-Creation-Date: 2020-04-09 21:59+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -13,37 +13,38 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: apps/activity/apps.py:10 apps/activity/models.py:111 -#: apps/activity/models.py:120 +#: apps/activity/apps.py:10 apps/activity/models.py:102 +#: apps/activity/models.py:111 msgid "activity" msgstr "activité" -#: apps/activity/forms.py:45 apps/activity/models.py:217 +#: apps/activity/forms.py:45 apps/activity/models.py:208 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:48 apps/activity/models.py:220 +#: apps/activity/forms.py:48 apps/activity/models.py:211 msgid "This activity is not validated yet." msgstr "Cette activité n'est pas encore validée." -#: apps/activity/forms.py:58 apps/activity/models.py:228 +#: apps/activity/forms.py:58 apps/activity/models.py:219 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:62 apps/activity/models.py:232 +#: apps/activity/forms.py:62 apps/activity/models.py:223 msgid "This person is already invited." msgstr "Cette personne est déjà invitée." -#: apps/activity/forms.py:66 apps/activity/models.py:236 +#: apps/activity/forms.py:66 apps/activity/models.py:227 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:23 apps/activity/models.py:48 -#: apps/member/models.py:66 apps/member/models.py:169 +#: apps/member/models.py:99 apps/member/models.py:202 #: apps/note/models/notes.py:188 apps/note/models/transactions.py:24 -#: apps/note/models/transactions.py:44 apps/note/models/transactions.py:232 +#: apps/note/models/transactions.py:44 apps/note/models/transactions.py:237 #: templates/member/club_info.html:13 templates/member/profile_info.html:14 +#: templates/registration/future_profile_detail.html:16 msgid "name" msgstr "nom" @@ -63,18 +64,18 @@ msgstr "type d'activité" msgid "activity types" msgstr "types d'activité" -#: apps/activity/models.py:53 apps/note/models/transactions.py:69 +#: apps/activity/models.py:53 apps/note/models/transactions.py:74 #: apps/permission/models.py:103 templates/activity/activity_detail.html:16 msgid "description" msgstr "description" #: apps/activity/models.py:60 apps/note/models/notes.py:164 -#: apps/note/models/transactions.py:62 +#: apps/note/models/transactions.py:64 #: templates/activity/activity_detail.html:19 msgid "type" msgstr "type" -#: apps/activity/models.py:66 apps/logs/models.py:21 apps/member/models.py:190 +#: apps/activity/models.py:66 apps/logs/models.py:21 apps/member/models.py:223 #: apps/note/models/notes.py:117 msgid "user" msgstr "utilisateur" @@ -83,73 +84,82 @@ msgstr "utilisateur" msgid "organizer" msgstr "organisateur" -#: apps/activity/models.py:82 apps/activity/models.py:131 apps/note/apps.py:14 -#: apps/note/models/notes.py:58 -msgid "note" -msgstr "note" - -#: apps/activity/models.py:89 templates/activity/activity_detail.html:36 +#: apps/activity/models.py:80 templates/activity/activity_detail.html:36 msgid "attendees club" msgstr "club attendu" -#: apps/activity/models.py:93 templates/activity/activity_detail.html:22 +#: apps/activity/models.py:84 templates/activity/activity_detail.html:22 msgid "start date" msgstr "date de début" -#: apps/activity/models.py:97 templates/activity/activity_detail.html:25 +#: apps/activity/models.py:88 templates/activity/activity_detail.html:25 msgid "end date" msgstr "date de fin" -#: apps/activity/models.py:102 apps/note/models/transactions.py:134 +#: apps/activity/models.py:93 apps/note/models/transactions.py:139 #: templates/activity/activity_detail.html:47 msgid "valid" msgstr "valide" -#: apps/activity/models.py:107 templates/activity/activity_detail.html:61 +#: apps/activity/models.py:98 templates/activity/activity_detail.html:61 msgid "open" msgstr "ouvrir" -#: apps/activity/models.py:112 +#: apps/activity/models.py:103 msgid "activities" msgstr "activités" -#: apps/activity/models.py:125 +#: apps/activity/models.py:116 msgid "entry time" msgstr "heure d'entrée" -#: apps/activity/models.py:148 +#: apps/activity/models.py:122 apps/note/apps.py:14 +#: apps/note/models/notes.py:58 +msgid "note" +msgstr "note" + +#: apps/activity/models.py:133 templates/activity/activity_entry.html:38 +msgid "entry" +msgstr "entrée" + +#: apps/activity/models.py:134 templates/activity/activity_entry.html:38 +msgid "entries" +msgstr "entrées" + +#: apps/activity/models.py:141 msgid "Already entered on " msgstr "Déjà rentré le " -#: apps/activity/models.py:148 apps/activity/tables.py:54 +#: apps/activity/models.py:141 apps/activity/tables.py:54 msgid "{:%Y-%m-%d %H:%M:%S}" msgstr "{:%d/%m/%Y %H:%M:%S}" -#: apps/activity/models.py:156 +#: apps/activity/models.py:149 msgid "The balance is negative." msgstr "La note est en négatif." -#: apps/activity/models.py:188 +#: apps/activity/models.py:179 msgid "last name" msgstr "nom de famille" -#: apps/activity/models.py:193 templates/member/profile_info.html:14 +#: apps/activity/models.py:184 templates/member/profile_info.html:14 +#: templates/registration/future_profile_detail.html:16 msgid "first name" msgstr "prénom" -#: apps/activity/models.py:200 +#: apps/activity/models.py:191 msgid "inviter" msgstr "hôte" -#: apps/activity/models.py:241 +#: apps/activity/models.py:232 msgid "guest" msgstr "invité" -#: apps/activity/models.py:242 +#: apps/activity/models.py:233 msgid "guests" msgstr "invités" -#: apps/activity/models.py:254 +#: apps/activity/models.py:245 msgid "Invitation" msgstr "Invitation" @@ -161,16 +171,18 @@ msgstr "Entré le " msgid "remove" msgstr "supprimer" -#: apps/activity/tables.py:75 apps/treasury/models.py:126 +#: apps/activity/tables.py:75 apps/treasury/models.py:138 msgid "Type" msgstr "Type" -#: apps/activity/tables.py:77 apps/treasury/forms.py:121 +#: apps/activity/tables.py:77 apps/member/forms.py:75 +#: apps/registration/forms.py:55 apps/treasury/forms.py:121 msgid "Last name" msgstr "Nom de famille" -#: apps/activity/tables.py:79 apps/treasury/forms.py:123 -#: templates/note/transaction_form.html:92 +#: apps/activity/tables.py:79 apps/member/forms.py:80 +#: apps/registration/forms.py:60 apps/treasury/forms.py:123 +#: templates/note/transaction_form.html:97 msgid "First name" msgstr "Prénom" @@ -178,15 +190,15 @@ msgstr "Prénom" msgid "Note" msgstr "Note" -#: apps/activity/tables.py:83 +#: apps/activity/tables.py:83 apps/member/tables.py:41 msgid "Balance" msgstr "Solde du compte" -#: apps/activity/views.py:45 templates/base.html:94 +#: apps/activity/views.py:45 templates/base.html:106 msgid "Activities" msgstr "Activités" -#: apps/activity/views.py:153 +#: apps/activity/views.py:154 msgid "Entry for activity \"{}\"" msgstr "Entrées pour l'activité « {} »" @@ -222,12 +234,12 @@ msgstr "Nouvelles données" msgid "create" msgstr "Créer" -#: apps/logs/models.py:61 apps/note/tables.py:142 +#: apps/logs/models.py:61 apps/note/tables.py:144 #: templates/activity/activity_detail.html:67 msgid "edit" msgstr "Modifier" -#: apps/logs/models.py:62 apps/note/tables.py:120 apps/note/tables.py:146 +#: apps/logs/models.py:62 apps/note/tables.py:120 apps/note/tables.py:149 msgid "delete" msgstr "Supprimer" @@ -243,81 +255,143 @@ msgstr "Date" msgid "Logs cannot be destroyed." msgstr "Les logs ne peuvent pas être détruits." +#: apps/logs/models.py:80 +msgid "changelog" +msgstr "" + +#: apps/logs/models.py:81 +msgid "changelogs" +msgstr "" + #: apps/member/apps.py:14 msgid "member" msgstr "adhérent" -#: apps/member/models.py:28 +#: apps/member/forms.py:54 apps/registration/forms.py:35 +msgid "Inscription paid by Société Générale" +msgstr "Inscription payée par la Société générale" + +#: apps/member/forms.py:56 apps/registration/forms.py:37 +msgid "Check this case is 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:61 apps/registration/forms.py:42 +msgid "Credit type" +msgstr "Type de rechargement" + +#: apps/member/forms.py:62 apps/registration/forms.py:43 +msgid "No credit" +msgstr "Pas de rechargement" + +#: apps/member/forms.py:64 +msgid "You can credit the note of the user." +msgstr "Vous pouvez créditer la note de l'utisateur avant l'adhésion." + +#: apps/member/forms.py:68 apps/registration/forms.py:48 +msgid "Credit amount" +msgstr "Montant à créditer" + +#: apps/member/forms.py:85 apps/registration/forms.py:65 +#: apps/treasury/forms.py:125 templates/note/transaction_form.html:103 +msgid "Bank" +msgstr "Banque" + +#: apps/member/models.py:33 +#: templates/registration/future_profile_detail.html:47 msgid "phone number" msgstr "numéro de téléphone" -#: apps/member/models.py:34 templates/member/profile_info.html:27 +#: apps/member/models.py:39 templates/member/profile_info.html:27 +#: templates/registration/future_profile_detail.html:41 msgid "section" msgstr "section" -#: apps/member/models.py:35 +#: apps/member/models.py:40 msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" -#: apps/member/models.py:41 templates/member/profile_info.html:30 +#: apps/member/models.py:46 templates/member/profile_info.html:30 +#: templates/registration/future_profile_detail.html:44 msgid "address" msgstr "adresse" -#: apps/member/models.py:47 +#: apps/member/models.py:52 +#: templates/registration/future_profile_detail.html:50 msgid "paid" msgstr "payé" -#: apps/member/models.py:52 apps/member/models.py:53 +#: apps/member/models.py:53 +msgid "Tells if the user receive a salary." +msgstr "Indique si l'utilisateur perçoit un salaire." + +#: apps/member/models.py:58 +msgid "email confirmed" +msgstr "adresse email confirmée" + +#: apps/member/models.py:63 +msgid "registration valid" +msgstr "inscription valid" + +#: apps/member/models.py:68 +msgid "Société générale" +msgstr "Société générale" + +#: apps/member/models.py:69 +msgid "Has the user ever be paid by the Société générale?" +msgstr "Est-ce que l'utilisateur a déjà été payé par la Société Générale ?" + +#: apps/member/models.py:74 apps/member/models.py:75 msgid "user profile" msgstr "profil utilisateur" -#: apps/member/models.py:71 templates/member/club_info.html:46 +#: apps/member/models.py:104 templates/member/club_info.html:46 +#: templates/registration/future_profile_detail.html:22 msgid "email" msgstr "courriel" -#: apps/member/models.py:78 +#: apps/member/models.py:111 msgid "parent club" msgstr "club parent" -#: apps/member/models.py:87 +#: apps/member/models.py:120 msgid "require memberships" msgstr "nécessite des adhésions" -#: apps/member/models.py:88 +#: apps/member/models.py:121 msgid "Uncheck if this club don't require memberships." msgstr "Décochez si ce club n'utilise pas d'adhésions." -#: apps/member/models.py:93 templates/member/club_info.html:35 +#: apps/member/models.py:126 templates/member/club_info.html:35 msgid "membership fee (paid students)" msgstr "cotisation pour adhérer (normalien élève)" -#: apps/member/models.py:98 templates/member/club_info.html:38 +#: apps/member/models.py:131 templates/member/club_info.html:38 msgid "membership fee (unpaid students)" msgstr "cotisation pour adhérer (normalien étudiant)" -#: apps/member/models.py:104 templates/member/club_info.html:28 +#: apps/member/models.py:137 templates/member/club_info.html:28 msgid "membership duration" msgstr "durée de l'adhésion" -#: apps/member/models.py:105 +#: apps/member/models.py:138 msgid "The longest time (in days) a membership can last (NULL = infinite)." msgstr "La durée maximale (en jours) d'une adhésion (NULL = infinie)." -#: apps/member/models.py:112 templates/member/club_info.html:22 +#: apps/member/models.py:145 templates/member/club_info.html:22 msgid "membership start" msgstr "début de l'adhésion" -#: apps/member/models.py:113 +#: apps/member/models.py:146 msgid "How long after January 1st the members can renew their membership." msgstr "" "Combien de temps après le 1er Janvier les adhérents peuvent renouveler leur " "adhésion." -#: apps/member/models.py:120 templates/member/club_info.html:25 +#: apps/member/models.py:153 templates/member/club_info.html:25 msgid "membership end" msgstr "fin de l'adhésion" -#: apps/member/models.py:121 +#: apps/member/models.py:154 msgid "" "How long the membership can last after January 1st of the next year after " "members can renew their membership." @@ -325,92 +399,105 @@ msgstr "" "Combien de temps l'adhésion peut durer après le 1er Janvier de l'année " "suivante avant que les adhérents peuvent renouveler leur adhésion." -#: apps/member/models.py:154 apps/member/models.py:196 +#: apps/member/models.py:187 apps/member/models.py:229 #: apps/note/models/notes.py:139 msgid "club" msgstr "club" -#: apps/member/models.py:155 +#: apps/member/models.py:188 msgid "clubs" msgstr "clubs" -#: apps/member/models.py:175 apps/permission/models.py:288 +#: apps/member/models.py:208 apps/permission/models.py:294 msgid "role" msgstr "rôle" -#: apps/member/models.py:176 apps/member/models.py:201 +#: apps/member/models.py:209 apps/member/models.py:234 msgid "roles" msgstr "rôles" -#: apps/member/models.py:205 +#: apps/member/models.py:239 msgid "membership starts on" msgstr "l'adhésion commence le" -#: apps/member/models.py:209 +#: apps/member/models.py:243 msgid "membership ends on" msgstr "l'adhésion finit le" -#: apps/member/models.py:214 +#: apps/member/models.py:248 msgid "fee" msgstr "cotisation" -#: apps/member/models.py:226 apps/member/views.py:383 +#: apps/member/models.py:266 apps/member/views.py:500 msgid "User is not a member of the parent club" msgstr "L'utilisateur n'est pas membre du club parent" -#: apps/member/models.py:236 apps/member/views.py:392 +#: apps/member/models.py:276 apps/member/views.py:509 msgid "User is already a member of the club" msgstr "L'utilisateur est déjà membre du club" -#: apps/member/models.py:271 +#: apps/member/models.py:314 #, python-brace-format msgid "Membership of {user} for the club {club}" msgstr "Adhésion de {user} pour le club {club}" -#: apps/member/models.py:274 +#: apps/member/models.py:317 msgid "membership" msgstr "adhésion" -#: apps/member/models.py:275 +#: apps/member/models.py:318 msgid "memberships" msgstr "adhésions" -#: apps/member/tables.py:73 +#: apps/member/tables.py:112 msgid "Renew" -msgstr "" +msgstr "Renouveler" -#: apps/member/views.py:80 templates/member/profile_info.html:45 +#: apps/member/views.py:62 apps/registration/forms.py:23 +msgid "This address must be valid." +msgstr "Cette adresse doit être valide." + +#: apps/member/views.py:65 templates/member/profile_info.html:45 +#: templates/registration/future_profile_detail.html:55 msgid "Update Profile" msgstr "Modifier le profil" -#: apps/member/views.py:93 +#: apps/member/views.py:75 msgid "An alias with a similar name already exists." msgstr "Un alias avec un nom similaire existe déjà." -#: apps/member/views.py:379 +#: apps/member/views.py:180 +msgid "Search user" +msgstr "Chercher un utilisateur" + +#: apps/member/views.py:495 msgid "" "This user don't have enough money to join this club, and can't have a " "negative balance." 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:396 apps/member/views.py:428 +#: apps/member/views.py:513 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:401 apps/member/views.py:433 +#: apps/member/views.py:518 msgid "The membership must begin before {:%m-%d-%Y}." msgstr "L'adhésion doit commencer avant le {:%d/%m/%Y}." -#: apps/member/views.py:455 -msgid "This membership is already renewed" -msgstr "Cette adhésion est déjà renouvelée" +#: apps/member/views.py:528 apps/member/views.py:530 apps/member/views.py:532 +#: apps/registration/views.py:286 apps/registration/views.py:288 +#: apps/registration/views.py:290 +msgid "This field is required." +msgstr "Ce champ est requis." -#: apps/note/admin.py:120 apps/note/models/transactions.py:94 +#: apps/note/admin.py:120 apps/note/models/transactions.py:99 msgid "source" msgstr "source" -#: apps/note/admin.py:128 apps/note/admin.py:163 -#: apps/note/models/transactions.py:53 apps/note/models/transactions.py:107 +#: apps/note/admin.py:128 apps/note/admin.py:170 +#: apps/note/models/transactions.py:54 apps/note/models/transactions.py:112 msgid "destination" msgstr "destination" @@ -453,7 +540,7 @@ msgstr "" msgid "display image" msgstr "image affichée" -#: apps/note/models/notes.py:53 apps/note/models/transactions.py:117 +#: apps/note/models/notes.py:53 apps/note/models/transactions.py:122 msgid "created at" msgstr "créée le" @@ -536,80 +623,87 @@ msgstr "catégories de transaction" msgid "A template with this name already exist" msgstr "Un modèle de transaction avec un nom similaire existe déjà." -#: apps/note/models/transactions.py:56 apps/note/models/transactions.py:125 +#: apps/note/models/transactions.py:58 apps/note/models/transactions.py:130 msgid "amount" msgstr "montant" -#: apps/note/models/transactions.py:57 +#: apps/note/models/transactions.py:59 msgid "in centimes" msgstr "en centimes" -#: apps/note/models/transactions.py:75 +#: apps/note/models/transactions.py:70 +msgid "display" +msgstr "afficher" + +#: apps/note/models/transactions.py:80 msgid "transaction template" msgstr "modèle de transaction" -#: apps/note/models/transactions.py:76 +#: apps/note/models/transactions.py:81 msgid "transaction templates" msgstr "modèles de transaction" -#: apps/note/models/transactions.py:100 apps/note/models/transactions.py:113 +#: apps/note/models/transactions.py:105 apps/note/models/transactions.py:118 #: apps/note/tables.py:33 apps/note/tables.py:42 msgid "used alias" msgstr "alias utilisé" -#: apps/note/models/transactions.py:121 +#: apps/note/models/transactions.py:126 msgid "quantity" msgstr "quantité" -#: apps/note/models/transactions.py:129 +#: apps/note/models/transactions.py:134 msgid "reason" msgstr "raison" -#: apps/note/models/transactions.py:139 apps/note/tables.py:95 +#: apps/note/models/transactions.py:144 apps/note/tables.py:95 msgid "invalidity reason" msgstr "Motif d'invalidité" -#: apps/note/models/transactions.py:147 +#: apps/note/models/transactions.py:152 msgid "transaction" msgstr "transaction" -#: apps/note/models/transactions.py:148 +#: apps/note/models/transactions.py:153 msgid "transactions" msgstr "transactions" -#: apps/note/models/transactions.py:202 templates/base.html:84 +#: apps/note/models/transactions.py:207 +#: templates/activity/activity_entry.html:13 templates/base.html:84 #: templates/note/transaction_form.html:19 -#: templates/note/transaction_form.html:140 +#: templates/note/transaction_form.html:145 msgid "Transfer" msgstr "Virement" -#: apps/note/models/transactions.py:222 +#: apps/note/models/transactions.py:227 msgid "Template" msgstr "Bouton" -#: apps/note/models/transactions.py:237 +#: apps/note/models/transactions.py:242 msgid "first_name" msgstr "prénom" -#: apps/note/models/transactions.py:242 +#: apps/note/models/transactions.py:247 msgid "bank" msgstr "banque" -#: apps/note/models/transactions.py:248 templates/note/transaction_form.html:24 +#: apps/note/models/transactions.py:253 +#: templates/activity/activity_entry.html:17 +#: templates/note/transaction_form.html:24 msgid "Credit" msgstr "Crédit" -#: apps/note/models/transactions.py:248 templates/note/transaction_form.html:28 +#: apps/note/models/transactions.py:253 templates/note/transaction_form.html:28 msgid "Debit" msgstr "Débit" -#: apps/note/models/transactions.py:264 apps/note/models/transactions.py:269 +#: apps/note/models/transactions.py:269 apps/note/models/transactions.py:274 msgid "membership transaction" -msgstr "transaction d'adhésion" +msgstr "Transaction d'adhésion" -#: apps/note/models/transactions.py:265 +#: apps/note/models/transactions.py:270 msgid "membership transactions" -msgstr "transactions d'adhésion" +msgstr "Transactions d'adhésion" #: apps/note/tables.py:57 msgid "Click to invalidate" @@ -623,20 +717,29 @@ msgstr "Cliquez pour valider" msgid "No reason specified" msgstr "Pas de motif spécifié" -#: apps/note/views.py:39 +#: apps/note/tables.py:122 apps/note/tables.py:151 +msgid "Delete" +msgstr "Supprimer" + +#: apps/note/tables.py:146 templates/member/club_info.html:55 +#: templates/note/conso_form.html:121 +msgid "Edit" +msgstr "Éditer" + +#: apps/note/views.py:40 msgid "Transfer money" msgstr "Transférer de l'argent" -#: apps/note/views.py:100 templates/base.html:79 +#: apps/note/views.py:109 templates/base.html:79 msgid "Consumptions" msgstr "Consommations" -#: apps/permission/models.py:82 apps/permission/models.py:275 +#: apps/permission/models.py:82 apps/permission/models.py:281 #, python-brace-format msgid "Can {type} {model}.{field} in {query}" msgstr "" -#: apps/permission/models.py:84 apps/permission/models.py:277 +#: apps/permission/models.py:84 apps/permission/models.py:283 #, python-brace-format msgid "Can {type} {model} in {query}" msgstr "" @@ -645,11 +748,74 @@ msgstr "" msgid "rank" msgstr "Rang" +#: apps/permission/models.py:110 +msgid "permission mask" +msgstr "masque de permissions" + +#: apps/permission/models.py:111 +msgid "permission masks" +msgstr "masques de permissions" + #: apps/permission/models.py:160 +msgid "permission" +msgstr "permission" + +#: apps/permission/models.py:161 +msgid "permissions" +msgstr "permissions" + +#: apps/permission/models.py:166 msgid "Specifying field applies only to view and change permission types." msgstr "" -#: apps/treasury/apps.py:12 templates/base.html:99 +#: apps/permission/models.py:304 apps/permission/models.py:305 +msgid "role permissions" +msgstr "Permissions par rôles" + +#: apps/registration/apps.py:10 +msgid "registration" +msgstr "inscription" + +#: apps/registration/forms.py:70 +msgid "Join BDE Club" +msgstr "Adhérer au club BDE" + +#: apps/registration/forms.py:77 +msgid "Join Kfet Club" +msgstr "Adhérer au club Kfet" + +#: apps/registration/views.py:75 +msgid "Email validation" +msgstr "Validation de l'adresse mail" + +#: apps/registration/views.py:121 +msgid "Email validation unsuccessful" +msgstr " La validation de l'adresse mail a échoué" + +#: apps/registration/views.py:132 +msgid "Email validation email sent" +msgstr "L'email de vérification de l'adresse email a bien été envoyé." + +#: apps/registration/views.py:185 +msgid "Unregistered users" +msgstr "Utilisateurs en attente d'inscription" + +#: apps/registration/views.py:252 +msgid "You must join the BDE." +msgstr "Vous devez adhérer au BDE." + +#: apps/registration/views.py:274 +msgid "You must join BDE club before joining Kfet club." +msgstr "Vous devez adhérer au club BDE avant d'adhérer au club Kfet." + +#: apps/registration/views.py:279 +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/treasury/apps.py:12 templates/base.html:111 msgid "Treasury" msgstr "Trésorerie" @@ -674,12 +840,8 @@ msgstr "La remise est déjà fermée." msgid "You can't change the type of the remittance." msgstr "Vous ne pouvez pas changer le type de la remise." -#: apps/treasury/forms.py:125 templates/note/transaction_form.html:98 -msgid "Bank" -msgstr "Banque" - #: apps/treasury/forms.py:127 apps/treasury/tables.py:47 -#: templates/note/transaction_form.html:128 +#: templates/note/transaction_form.html:133 #: templates/treasury/remittance_form.html:18 msgid "Amount" msgstr "Montant" @@ -700,7 +862,7 @@ msgstr "Objet" msgid "Description" msgstr "Description" -#: apps/treasury/models.py:46 templates/note/transaction_form.html:86 +#: apps/treasury/models.py:46 templates/note/transaction_form.html:91 msgid "Name" msgstr "Nom" @@ -716,40 +878,80 @@ msgstr "Lieu" msgid "Acquitted" msgstr "Acquittée" -#: apps/treasury/models.py:75 +#: apps/treasury/models.py:63 +msgid "invoice" +msgstr "facture" + +#: apps/treasury/models.py:64 +msgid "invoices" +msgstr "factures" + +#: apps/treasury/models.py:79 msgid "Designation" msgstr "Désignation" -#: apps/treasury/models.py:79 +#: apps/treasury/models.py:83 msgid "Quantity" msgstr "Quantité" -#: apps/treasury/models.py:83 +#: apps/treasury/models.py:87 msgid "Unit price" msgstr "Prix unitaire" -#: apps/treasury/models.py:120 +#: apps/treasury/models.py:103 +msgid "product" +msgstr "produit" + +#: apps/treasury/models.py:104 +msgid "products" +msgstr "produits" + +#: apps/treasury/models.py:121 +msgid "remittance type" +msgstr "type de remise" + +#: apps/treasury/models.py:122 +msgid "remittance types" +msgstr "types de remises" + +#: apps/treasury/models.py:132 msgid "Date" msgstr "Date" -#: apps/treasury/models.py:131 +#: apps/treasury/models.py:143 msgid "Comment" msgstr "Commentaire" -#: apps/treasury/models.py:136 +#: apps/treasury/models.py:148 msgid "Closed" msgstr "Fermée" -#: apps/treasury/models.py:169 +#: apps/treasury/models.py:152 +msgid "remittance" +msgstr "remise" + +#: apps/treasury/models.py:153 +msgid "remittances" +msgstr "remises" + +#: apps/treasury/models.py:185 msgid "Remittance #{:d}: {}" msgstr "Remise n°{:d} : {}" -#: apps/treasury/models.py:188 apps/treasury/tables.py:76 +#: apps/treasury/models.py:204 apps/treasury/tables.py:76 #: apps/treasury/tables.py:84 templates/treasury/invoice_list.html:13 #: templates/treasury/remittance_list.html:13 msgid "Remittance" msgstr "Remise" +#: apps/treasury/models.py:208 +msgid "special transaction proxy" +msgstr "Proxy de transaction spéciale" + +#: apps/treasury/models.py:209 +msgid "special transaction proxies" +msgstr "Proxys de transactions spéciales" + #: apps/treasury/tables.py:19 msgid "Invoice #{:d}" msgstr "Facture n°{:d}" @@ -782,15 +984,15 @@ msgid "" "again unless your session expires or you logout." msgstr "" -#: note_kfet/settings/base.py:152 +#: note_kfet/settings/base.py:153 msgid "German" msgstr "" -#: note_kfet/settings/base.py:153 +#: note_kfet/settings/base.py:154 msgid "English" msgstr "" -#: note_kfet/settings/base.py:154 +#: note_kfet/settings/base.py:155 msgid "French" msgstr "" @@ -826,18 +1028,15 @@ msgstr "Inviter" msgid "Guests list" msgstr "Liste des invités" -#: templates/activity/activity_entry.html:10 +#: templates/activity/activity_entry.html:22 +#: templates/note/transaction_form.html:33 +msgid "Entries" +msgstr "Entrées" + +#: templates/activity/activity_entry.html:30 msgid "Return to activity page" msgstr "Retour à la page de l'activité" -#: templates/activity/activity_entry.html:18 -msgid "entries" -msgstr "entrées" - -#: templates/activity/activity_entry.html:18 -msgid "entry" -msgstr "entrée" - #: templates/activity/activity_list.html:5 msgid "Upcoming activities" msgstr "Activités à venir" @@ -859,9 +1058,23 @@ msgid "The ENS Paris-Saclay BDE note." msgstr "La note du BDE de l'ENS Paris-Saclay." #: templates/base.html:89 +msgid "Users" +msgstr "Utilisateurs" + +#: templates/base.html:94 msgid "Clubs" msgstr "Clubs" +#: templates/base.html:100 +msgid "Registrations" +msgstr "Inscriptions" + +#: templates/base.html:150 +msgid "" +"Your e-mail address is not validated. Please check your mail inbox and click " +"on the validation link." +msgstr "" + #: templates/cas_server/base.html:7 msgid "Central Authentication Service" msgstr "" @@ -939,10 +1152,6 @@ msgstr "cotisation pour adhérer" msgid "Add member" msgstr "Ajouter un membre" -#: templates/member/club_info.html:55 templates/note/conso_form.html:121 -msgid "Edit" -msgstr "Éditer" - #: templates/member/club_info.html:59 templates/member/profile_info.html:48 msgid "View Profile" msgstr "Voir le profil" @@ -959,11 +1168,11 @@ msgstr "Créer un club" msgid "club listing " msgstr "Liste des clubs" -#: templates/member/club_tables.html:9 +#: templates/member/club_tables.html:6 msgid "Member of the Club" msgstr "Membre du club" -#: templates/member/club_tables.html:22 templates/member/profile_tables.html:22 +#: templates/member/club_tables.html:17 templates/member/profile_tables.html:28 msgid "Transaction history" msgstr "Historique des transactions" @@ -980,18 +1189,22 @@ msgid "Regenerate token" msgstr "Regénérer le jeton" #: templates/member/profile_info.html:5 +#: templates/registration/future_profile_detail.html:12 msgid "Account #" msgstr "Compte n°" #: templates/member/profile_info.html:17 +#: templates/registration/future_profile_detail.html:19 msgid "username" msgstr "pseudo" #: templates/member/profile_info.html:20 +#: templates/registration/future_profile_detail.html:34 msgid "password" msgstr "mot de passe" #: templates/member/profile_info.html:23 +#: templates/registration/future_profile_detail.html:37 msgid "Change password" msgstr "Changer le mot de passe" @@ -1003,7 +1216,17 @@ msgstr "solde du compte" msgid "Manage auth token" msgstr "Gérer les jetons d'authentification" -#: templates/member/profile_tables.html:9 +#: templates/member/profile_tables.html:7 +#: templates/registration/future_profile_detail.html:28 +msgid "This user doesn't have confirmed his/her e-mail address." +msgstr "Cet utilisateur n'a pas encore confirmé son adresse e-mail." + +#: templates/member/profile_tables.html:8 +#: templates/registration/future_profile_detail.html:29 +msgid "Click here to resend a validation link." +msgstr "Cliquez ici pour renvoyer un lien de validation." + +#: templates/member/profile_tables.html:15 msgid "View my memberships" msgstr "Voir mes adhésions" @@ -1011,12 +1234,12 @@ msgstr "Voir mes adhésions" msgid "Save Changes" msgstr "Sauvegarder les changements" -#: templates/member/signup.html:5 templates/member/signup.html:8 -#: templates/member/signup.html:14 -msgid "Sign up" -msgstr "Inscription" +#: templates/member/user_list.html:14 +#: templates/registration/future_user_list.html:17 +msgid "There is no pending user with this pattern." +msgstr "Il n'y a pas d'inscription en attente avec cette entrée." -#: templates/note/conso_form.html:28 templates/note/transaction_form.html:50 +#: templates/note/conso_form.html:28 templates/note/transaction_form.html:55 msgid "Select emitters" msgstr "Sélection des émetteurs" @@ -1040,7 +1263,7 @@ msgstr "Consommations simples" msgid "Double consumptions" msgstr "Consommations doubles" -#: templates/note/conso_form.html:141 templates/note/transaction_form.html:147 +#: templates/note/conso_form.html:141 templates/note/transaction_form.html:152 msgid "Recent transactions history" msgstr "Historique des transactions récentes" @@ -1048,29 +1271,29 @@ msgstr "Historique des transactions récentes" msgid "Gift" msgstr "Don" -#: templates/note/transaction_form.html:68 +#: templates/note/transaction_form.html:73 msgid "External payment" msgstr "Paiement externe" -#: templates/note/transaction_form.html:76 +#: templates/note/transaction_form.html:81 msgid "Transfer type" msgstr "Type de transfert" -#: templates/note/transaction_form.html:111 -#: templates/note/transaction_form.html:164 -#: templates/note/transaction_form.html:171 +#: templates/note/transaction_form.html:116 +#: templates/note/transaction_form.html:169 +#: templates/note/transaction_form.html:176 msgid "Select receivers" msgstr "Sélection des destinataires" -#: templates/note/transaction_form.html:133 +#: templates/note/transaction_form.html:138 msgid "Reason" msgstr "Raison" -#: templates/note/transaction_form.html:178 +#: templates/note/transaction_form.html:183 msgid "Credit note" msgstr "Note à recharger" -#: templates/note/transaction_form.html:185 +#: templates/note/transaction_form.html:190 msgid "Debit note" msgstr "Note à débiter" @@ -1090,14 +1313,53 @@ msgstr "Nouveau bouton" msgid "buttons listing " msgstr "Liste des boutons" -#: templates/note/transactiontemplate_list.html:71 +#: templates/note/transactiontemplate_list.html:70 msgid "button successfully deleted " msgstr "Le bouton a bien été supprimé" -#: templates/note/transactiontemplate_list.html:75 +#: templates/note/transactiontemplate_list.html:74 msgid "Unable to delete button " msgstr "Impossible de supprimer le bouton " +#: templates/registration/email_validation_complete.html:6 +msgid "Your email have successfully been validated." +msgstr "Votre adresse e-mail a bien été validée." + +#: templates/registration/email_validation_complete.html:8 +#, python-format +msgid "You can now log in." +msgstr "Vous pouvez désormais vous connecter." + +#: templates/registration/email_validation_complete.html:10 +msgid "" +"You must pay now your membership in the Kfet to complete your registration." +msgstr "" +"Vous devez désormais payer votre adhésion à la Kfet pour compléter votre inscription." + +#: templates/registration/email_validation_complete.html:13 +msgid "" +"The link was invalid. The token may have expired. Please send us an email to " +"activate your account." +msgstr "" +"Le lien est invalide. Le jeton a sans doute expiré. Merci de nous contacter pour " +"activer votre compte." + +#: templates/registration/future_profile_detail.html:56 +msgid "Delete registration" +msgstr "Supprimer l'inscription" + +#: templates/registration/future_profile_detail.html:64 +msgid "Validate account" +msgstr "Valider le compte" + +#: templates/registration/future_profile_detail.html:71 +msgid "Validate registration" +msgstr "Valider l'inscription" + +#: templates/registration/future_user_list.html:7 +msgid "New user" +msgstr "Nouvel utilisateur" + #: templates/registration/logged_out.html:8 msgid "Thanks for spending some quality time with the Web site today." msgstr "" @@ -1131,9 +1393,41 @@ msgstr "" msgid "Forgotten your password or username?" msgstr "" +#: templates/registration/mails/email_validation_email.html:3 +msgid "Hi" +msgstr "" + +#: templates/registration/mails/email_validation_email.html:5 +msgid "" +"You recently registered on the Note Kfet. Please click on the link below to " +"confirm your registration." +msgstr "" + +#: templates/registration/mails/email_validation_email.html:9 +msgid "" +"This link is only valid for a couple of days, after that you will need to " +"contact us to validate your email." +msgstr "" + +#: templates/registration/mails/email_validation_email.html:11 +msgid "" +"After that, you'll have to wait that someone validates your account before " +"you can log in. You will need to pay your membership in the Kfet." +msgstr "" +"Après cela, vous devrez attendre que quelqu'un valide votre compte avant " +"de pouvoir vous connecter. Vous devrez payer votre adhésion à la Kfet." + +#: templates/registration/mails/email_validation_email.html:13 +msgid "Thanks" +msgstr "Merci" + +#: templates/registration/mails/email_validation_email.html:15 +msgid "The Note Kfet team." +msgstr "L'équipe de la Note Kfet." + #: templates/registration/password_change_done.html:8 msgid "Your password was changed." -msgstr "" +msgstr "Votre mot de passe a bien été changé." #: templates/registration/password_change_form.html:9 msgid "" @@ -1184,6 +1478,11 @@ msgstr "" msgid "Reset my password" msgstr "" +#: templates/registration/signup.html:5 templates/registration/signup.html:8 +#: templates/registration/signup.html:14 +msgid "Sign up" +msgstr "Inscription" + #: templates/treasury/invoice_form.html:6 msgid "Invoices list" msgstr "Liste des factures" @@ -1252,3 +1551,6 @@ msgstr "Il n'y a pas de transaction associée à une remise ouverte." #: templates/treasury/remittance_list.html:54 msgid "Closed remittances" msgstr "Remises fermées" + +#~ msgid "This membership is already renewed" +#~ msgstr "Cette adhésion est déjà renouvelée" diff --git a/note_kfet/inputs.py b/note_kfet/inputs.py index ecd758e0..b3cccbce 100644 --- a/note_kfet/inputs.py +++ b/note_kfet/inputs.py @@ -13,7 +13,7 @@ class AmountInput(NumberInput): template_name = "note/amount_input.html" def format_value(self, value): - return None if value is None or value == "" else "{:.02f}".format(value / 100, ) + return None if value is None or value == "" else "{:.02f}".format(int(value) / 100, ) def value_from_datadict(self, data, files, name): val = super().value_from_datadict(data, files, name) diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py index 61e5ea51..283f8e56 100644 --- a/note_kfet/settings/base.py +++ b/note_kfet/settings/base.py @@ -54,13 +54,14 @@ INSTALLED_APPS = [ 'rest_framework.authtoken', # Note apps + 'api', 'activity', + 'logs', 'member', 'note', - 'treasury', 'permission', - 'api', - 'logs', + 'registration', + 'treasury', ] LOGIN_REDIRECT_URL = '/note/transfer/' diff --git a/note_kfet/urls.py b/note_kfet/urls.py index a7afab29..90d44a07 100644 --- a/note_kfet/urls.py +++ b/note_kfet/urls.py @@ -16,6 +16,7 @@ urlpatterns = [ # Include project routers path('note/', include('note.urls')), path('accounts/', include('member.urls')), + path('registration/', include('registration.urls')), path('activity/', include('activity.urls')), path('treasury/', include('treasury.urls')), @@ -37,14 +38,7 @@ if "cas_server" in settings.INSTALLED_APPS: # Include CAS Server routers path('cas/', include('cas_server.urls', namespace="cas_server")), ] -if "cas" in settings.INSTALLED_APPS: - from cas import views as cas_views - urlpatterns += [ - # Include CAS Client routers - path('accounts/login/cas/', cas_views.login, name='cas_login'), - path('accounts/logout/cas/', cas_views.logout, name='cas_logout'), - ] if "debug_toolbar" in settings.INSTALLED_APPS: import debug_toolbar urlpatterns = [ diff --git a/static/js/autocomplete_model.js b/static/js/autocomplete_model.js index e2a3f0cb..8c3f6f09 100644 --- a/static/js/autocomplete_model.js +++ b/static/js/autocomplete_model.js @@ -24,6 +24,9 @@ $(document).ready(function () { $("#" + prefix + "_" + obj.id).click(function() { target.val(obj[name_field]); $("#" + prefix + "_pk").val(obj.id); + + if (typeof autocompleted != 'undefined') + autocompleted(obj, prefix) }); if (input === obj[name_field]) diff --git a/static/js/base.js b/static/js/base.js index 7febd3d6..bb73b328 100644 --- a/static/js/base.js +++ b/static/js/base.js @@ -19,23 +19,32 @@ function pretty_money(value) { * Add a message on the top of the page. * @param msg The message to display * @param alert_type The type of the alert. Choices: info, success, warning, danger + * @param timeout The delay (in millis) after that the message is auto-closed. If negative, then it is ignored. */ -function addMsg(msg, alert_type) { +function addMsg(msg, alert_type, timeout=-1) { let msgDiv = $("#messages"); let html = msgDiv.html(); + let id = Math.floor(10000 * Math.random() + 1); html += "
" + - "" + "" + msg + "
\n"; msgDiv.html(html); + + if (timeout > 0) { + setTimeout(function () { + $("#close-message-" + id).click(); + }, timeout); + } } /** * add Muliple error message from err_obj * @param errs_obj [{error_code:erro_message}] + * @param timeout The delay (in millis) after that the message is auto-closed. If negative, then it is ignored. */ -function errMsg(errs_obj){ +function errMsg(errs_obj, timeout=-1) { for (const err_msg of Object.values(errs_obj)) { - addMsg(err_msg,'danger'); + addMsg(err_msg,'danger', timeout); } } diff --git a/static/js/transfer.js b/static/js/transfer.js index cf62e453..e5aafc39 100644 --- a/static/js/transfer.js +++ b/static/js/transfer.js @@ -61,16 +61,24 @@ $(document).ready(function() { // Ensure we begin in gift mode. Removing these lines may cause problems when reloading. - $("#type_gift").prop('checked', 'true'); + let type_gift = $("#type_gift"); // Default mode + type_gift.removeAttr('checked'); $("#type_transfer").removeAttr('checked'); $("#type_credit").removeAttr('checked'); $("#type_debit").removeAttr('checked'); + $("label[for='type_gift']").attr('class', 'btn btn-sm btn-outline-primary'); $("label[for='type_transfer']").attr('class', 'btn btn-sm btn-outline-primary'); $("label[for='type_credit']").attr('class', 'btn btn-sm btn-outline-primary'); $("label[for='type_debit']").attr('class', 'btn btn-sm btn-outline-primary'); + + if (location.hash) + $("#type_" + location.hash.substr(1)).click(); + else + type_gift.click(); + location.hash = ""; }); -$("#transfer").click(function() { +$("#btn_transfer").click(function() { if ($("#type_gift").is(':checked')) { dests_notes_display.forEach(function (dest) { $.post("/api/note/transaction/transaction/", diff --git a/templates/activity/activity_detail.html b/templates/activity/activity_detail.html index 84182065..7ee9f7c0 100644 --- a/templates/activity/activity_detail.html +++ b/templates/activity/activity_detail.html @@ -118,7 +118,6 @@ }); $("#validate_activity").click(function () { - console.log(42); $.ajax({ url: "/api/activity/activity/{{ activity.pk }}/", type: "PATCH", diff --git a/templates/activity/activity_entry.html b/templates/activity/activity_entry.html index c06a5188..a712228d 100644 --- a/templates/activity/activity_entry.html +++ b/templates/activity/activity_entry.html @@ -6,6 +6,26 @@ {% load perms %} {% block content %} +
+
+
+ + {% trans "Transfer" %} + + {% if "note.notespecial"|not_empty_model_list %} + + {% trans "Credit" %} + + {% endif %} + {% for a in activities_open %} + + {% trans "Entries" %} {{ a.name }} + + {% endfor %} +
+
+
+ @@ -56,10 +76,10 @@ note: id, guest: null }).done(function () { - addMsg("Entrée effectuée !", "success"); + addMsg("Entrée effectuée !", "success", 4000); reloadTable(true); }).fail(function(xhr) { - errMsg(xhr.responseJSON); + errMsg(xhr.responseJSON, 4000); }); } else { @@ -84,10 +104,10 @@ note: target.attr("data-inviter"), guest: id }).done(function () { - addMsg("Entrée effectuée !", "success"); + addMsg("Entrée effectuée !", "success", 4000); reloadTable(true); }).fail(function (xhr) { - errMsg(xhr.responseJSON); + errMsg(xhr.responseJSON, 4000); }); }; @@ -111,7 +131,7 @@ makeTransaction(); reset(); }).fail(function (xhr) { - errMsg(xhr.responseJSON); + errMsg(xhr.responseJSON, 4000); }); }; }; diff --git a/templates/base.html b/templates/base.html index c44e2467..3c2c637f 100644 --- a/templates/base.html +++ b/templates/base.html @@ -94,6 +94,13 @@ SPDX-License-Identifier: GPL-3.0-or-later {% trans 'Clubs' %} {% endif %} + {% if "member.change_profile_registration_valid"|has_perm:user %} + + {% endif %} {% if "activity.activity"|not_empty_model_list %} {% else %} @@ -138,6 +145,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
+ {% if user.is_authenticated and not user.profile.email_confirmed %} +
+ {% trans "Your e-mail address is not validated. Please check your mail inbox and click on the validation link." %} +
+ {% endif %} {% block contenttitle %}

{{ title }}

{% endblock %}
{% block content %} diff --git a/templates/member/add_members.html b/templates/member/add_members.html index c44440bf..ad6f1f29 100644 --- a/templates/member/add_members.html +++ b/templates/member/add_members.html @@ -16,6 +16,40 @@ {% endblock %} {% block extrajavascript %} - + {% endblock %} diff --git a/templates/member/club_detail.html b/templates/member/club_detail.html index 3ad29901..fedd43fa 100644 --- a/templates/member/club_detail.html +++ b/templates/member/club_detail.html @@ -10,9 +10,11 @@ {% block extrajavascript %} {% endblock %} diff --git a/templates/member/club_info.html b/templates/member/club_info.html index a781bea8..93c76d59 100644 --- a/templates/member/club_info.html +++ b/templates/member/club_info.html @@ -49,13 +49,13 @@
diff --git a/templates/member/club_list.html b/templates/member/club_list.html index 2653ace8..4682164c 100644 --- a/templates/member/club_list.html +++ b/templates/member/club_list.html @@ -36,7 +36,6 @@ function getInfo() { if (asked.length >= 1) { $.getJSON("/api/members/club/?format=json&search="+asked, function(buttons){ let selected_id = buttons.results.map((a => "#row-"+a.id)); - console.log(selected_id.join()); $(".table-row,"+selected_id.join()).show(); $(".table-row").not(selected_id.join()).hide(); diff --git a/templates/member/club_tables.html b/templates/member/club_tables.html index fbded9c3..32be9bd4 100644 --- a/templates/member/club_tables.html +++ b/templates/member/club_tables.html @@ -1,31 +1,23 @@ {% load render_table from django_tables2 %} {% load i18n %} -
-
- -
- {% render_table member_list %} -
-
- -
- -
-
- {% render_table history_list %} -
-
+
+ + {% render_table member_list %} +
+ +
+ +
+ +
+ {% render_table history_list %} +
diff --git a/templates/member/profile_detail.html b/templates/member/profile_detail.html index 42d03d8b..04b15d3b 100644 --- a/templates/member/profile_detail.html +++ b/templates/member/profile_detail.html @@ -7,3 +7,14 @@ {% block profile_content %} {% include "member/profile_tables.html" %} {% endblock %} + +{% block extrajavascript %} + +{% endblock %} diff --git a/templates/member/profile_info.html b/templates/member/profile_info.html index 9ff20385..74856355 100644 --- a/templates/member/profile_info.html +++ b/templates/member/profile_info.html @@ -44,7 +44,7 @@ diff --git a/templates/member/profile_tables.html b/templates/member/profile_tables.html index 9d2c687f..9629ff14 100644 --- a/templates/member/profile_tables.html +++ b/templates/member/profile_tables.html @@ -1,31 +1,34 @@ {% load render_table from django_tables2 %} {% load i18n %} -
-
- -
- {% render_table club_list %} -
-
+{% load perms %} -
- -
-
- {% render_table history_list %} -
-
+{% if not object.profile.email_confirmed and "member.change_profile_email_confirmed"|has_perm:object.profile %} +
+ {% trans "This user doesn't have confirmed his/her e-mail address." %} + {% trans "Click here to resend a validation link." %} +
+{% endif %} + +
+ + {% render_table club_list %} +
+ +
+ +
+ +
+ {% render_table history_list %}
diff --git a/templates/member/user_list.html b/templates/member/user_list.html index d0eaaedb..0bcd7e89 100644 --- a/templates/member/user_list.html +++ b/templates/member/user_list.html @@ -7,7 +7,13 @@
- {% render_table table %} + {% if table.data %} + {% render_table table %} + {% else %} +
+ {% trans "There is no pending user with this pattern." %} +
+ {% endif %}
{% endblock %} diff --git a/templates/note/transaction_form.html b/templates/note/transaction_form.html index 65aaa635..0b53df61 100644 --- a/templates/note/transaction_form.html +++ b/templates/note/transaction_form.html @@ -28,6 +28,11 @@ SPDX-License-Identifier: GPL-2.0-or-later {% trans "Debit" %} {% endif %} + {% for activity in activities_open %} + + {% trans "Entries" %} {{ activity.name }} + + {% endfor %}
@@ -137,7 +142,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
- +
diff --git a/templates/note/transactiontemplate_list.html b/templates/note/transactiontemplate_list.html index af0a02b4..cf9bc5ed 100644 --- a/templates/note/transactiontemplate_list.html +++ b/templates/note/transactiontemplate_list.html @@ -37,7 +37,6 @@ function getInfo() { if (asked.length >= 1) { $.getJSON("/api/note/transaction/template/?format=json&search="+asked, function(buttons){ let selected_id = buttons.results.map((a => "#row-"+a.id)); - console.log(selected_id.join()); $(".table-row,"+selected_id.join()).show(); $(".table-row").not(selected_id.join()).hide(); diff --git a/templates/registration/email_validation_complete.html b/templates/registration/email_validation_complete.html new file mode 100644 index 00000000..4835cfa1 --- /dev/null +++ b/templates/registration/email_validation_complete.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block content %} + {% if validlink %} + {% trans "Your email have successfully been validated." %} + {% if user.profile.registration_valid %} + {% blocktrans %}You can now log in.{% endblocktrans %} + {% else %} + {% trans "You must pay now your membership in the Kfet to complete your registration." %} + {% endif %} + {% else %} + {% trans "The link was invalid. The token may have expired. Please send us an email to activate your account." %} + {% endif %} +{% endblock %} diff --git a/templates/registration/email_validation_email_sent.html b/templates/registration/email_validation_email_sent.html new file mode 100644 index 00000000..bd4cf8d8 --- /dev/null +++ b/templates/registration/email_validation_email_sent.html @@ -0,0 +1,7 @@ +{% extends "base.html" %} + +{% block content %} +

Account Activation

+ +An email has been sent. Please click on the link to activate your account. +{% endblock %} \ No newline at end of file diff --git a/templates/registration/future_profile_detail.html b/templates/registration/future_profile_detail.html new file mode 100644 index 00000000..8c78fb8d --- /dev/null +++ b/templates/registration/future_profile_detail.html @@ -0,0 +1,119 @@ +{% extends "base.html" %} +{% load static %} +{% load i18n %} +{% load crispy_forms_tags %} +{% load perms %} + +{% block content %} +
+
+
+
+

{% trans "Account #" %} {{ object.pk }}

+
+
+
+
{% trans 'name'|capfirst %}, {% trans 'first name' %}
+
{{ object.last_name }} {{ object.first_name }}
+ +
{% trans 'username'|capfirst %}
+
{{ object.username }}
+ +
{% trans 'email'|capfirst %}
+
{{ object.email }}
+ + {% if not object.profile.email_confirmed and "member.change_profile_email_confirmed"|has_perm:object.profile %} +
+
+ {% trans "This user doesn't have confirmed his/her e-mail address." %} + {% trans "Click here to resend a validation link." %} +
+
+ {% endif %} + +
{% trans 'password'|capfirst %}
+
+ + {% trans 'Change password' %} + +
+ +
{% trans 'section'|capfirst %}
+
{{ object.profile.section }}
+ +
{% trans 'address'|capfirst %}
+
{{ object.profile.address }}
+ +
{% trans 'phone number'|capfirst %}
+
{{ object.profile.phone_number }}
+ +
{% trans 'paid'|capfirst %}
+
{{ object.profile.paid|yesno }}
+
+
+ +
+
+
+
+
+
+

{% trans "Validate account" %}

+
+
+ {% csrf_token %} + {{ form|crispy }} +
+ +
+
+
+
+{% endblock %} + +{% block extrajavascript %} + +{% endblock %} \ No newline at end of file diff --git a/templates/registration/future_user_list.html b/templates/registration/future_user_list.html new file mode 100644 index 00000000..1e10dcbb --- /dev/null +++ b/templates/registration/future_user_list.html @@ -0,0 +1,53 @@ +{% extends "base.html" %} +{% load render_table from django_tables2 %} +{% load crispy_forms_tags %} +{% load i18n %} + +{% block content %} + +
+ +
+ +
+ {% if table.data %} + {% render_table table %} + {% else %} +
+ {% trans "There is no pending user with this pattern." %} +
+ {% endif %} +
+{% endblock %} + +{% block extrajavascript %} + +{% endblock %} diff --git a/templates/registration/mails/email_validation_email.html b/templates/registration/mails/email_validation_email.html new file mode 100644 index 00000000..577c1220 --- /dev/null +++ b/templates/registration/mails/email_validation_email.html @@ -0,0 +1,15 @@ +{% load i18n %} + +{% trans "Hi" %} {{ user.username }}, + +{% trans "You recently registered on the Note Kfet. Please click on the link below to confirm your registration." %} + +https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=token %} + +{% trans "This link is only valid for a couple of days, after that you will need to contact us to validate your email." %} + +{% trans "After that, you'll have to wait that someone validates your account before you can log in. You will need to pay your membership in the Kfet." %} + +{% trans "Thanks" %}, + +{% trans "The Note Kfet team." %} diff --git a/templates/member/signup.html b/templates/registration/signup.html similarity index 100% rename from templates/member/signup.html rename to templates/registration/signup.html diff --git a/tox.ini b/tox.ini index 01bf4edb..73cf0525 100644 --- a/tox.ini +++ b/tox.ini @@ -34,7 +34,7 @@ commands = [flake8] # Ignore too many errors, should be reduced in the future -ignore = D203, W503, E203, I100, I101 +ignore = D203, W503, E203, I100, I101, C901 exclude = .tox, .git,