Restructurate memberships, closes #16

This commit is contained in:
Yohann D'ANELLO 2020-03-31 23:54:14 +02:00
parent e98693b214
commit bf9789bd9e
10 changed files with 231 additions and 224 deletions

View File

@ -7,9 +7,9 @@
"email": "tresorerie.bde@example.com", "email": "tresorerie.bde@example.com",
"require_memberships": true, "require_memberships": true,
"membership_fee": 500, "membership_fee": 500,
"membership_duration": "396 00:00:00", "membership_duration": 396,
"membership_start": "213 00:00:00", "membership_start": "2019-08-31",
"membership_end": "273 00:00:00" "membership_end": "2020-09-30"
} }
}, },
{ {
@ -20,9 +20,9 @@
"email": "tresorerie.bde@example.com", "email": "tresorerie.bde@example.com",
"require_memberships": true, "require_memberships": true,
"membership_fee": 3500, "membership_fee": 3500,
"membership_duration": "396 00:00:00", "membership_duration": 396,
"membership_start": "213 00:00:00", "membership_start": "2019-08-31",
"membership_end": "273 00:00:00" "membership_end": "2020-09-30"
} }
} }
] ]

View File

@ -1,13 +1,10 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from crispy_forms.bootstrap import Div
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout
from django import forms from django import forms
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from note_kfet.inputs import Autocomplete, AmountInput from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput
from permission.models import PermissionMask from permission.models import PermissionMask
from .models import Profile, Club, Membership from .models import Profile, Club, Membership
@ -55,6 +52,8 @@ class ClubForm(forms.ModelForm):
'api_url': '/api/members/club/', 'api_url': '/api/members/club/',
} }
), ),
"membership_start": DatePickerInput(),
"membership_end": DatePickerInput(),
} }
@ -80,28 +79,5 @@ class MembershipForm(forms.ModelForm):
'placeholder': 'Nom ...', 'placeholder': 'Nom ...',
}, },
), ),
'date_start': DatePickerInput(),
} }
MemberFormSet = forms.modelformset_factory(
Membership,
form=MembershipForm,
extra=2,
can_delete=True,
)
class FormSetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.form_tag = False
self.form_method = 'POST'
self.form_class = 'form-inline'
# self.template = 'bootstrap/table_inline_formset.html'
self.layout = Layout(
Div(
Div('user', css_class='col-sm-2'),
Div('roles', css_class='col-sm-2'),
Div('date_start', css_class='col-sm-2'),
css_class="row formset-row",
))

View File

@ -83,27 +83,31 @@ class Club(models.Model):
require_memberships = models.BooleanField( require_memberships = models.BooleanField(
default=True, default=True,
verbose_name=_("require memberships"), verbose_name=_("require memberships"),
help_text=_("Uncheck if this club don't require memberships."),
) )
membership_fee = models.PositiveIntegerField( membership_fee = models.PositiveIntegerField(
default=0, default=0,
verbose_name=_('membership fee'), verbose_name=_('membership fee'),
) )
membership_duration = models.DurationField(
membership_duration = models.IntegerField(
blank=True, blank=True,
null=True, null=True,
verbose_name=_('membership duration'), verbose_name=_('membership duration'),
help_text=_('The longest time a membership can last ' help_text=_('The longest time (in days) a membership can last '
'(NULL = infinite).'), '(NULL = infinite).'),
) )
membership_start = models.DurationField(
membership_start = models.DateField(
blank=True, blank=True,
null=True, null=True,
verbose_name=_('membership start'), verbose_name=_('membership start'),
help_text=_('How long after January 1st the members can renew ' help_text=_('How long after January 1st the members can renew '
'their membership.'), 'their membership.'),
) )
membership_end = models.DurationField(
membership_end = models.DateField(
blank=True, blank=True,
null=True, null=True,
verbose_name=_('membership end'), verbose_name=_('membership end'),
@ -112,6 +116,20 @@ class Club(models.Model):
'membership.'), 'membership.'),
) )
def update_membership_dates(self):
"""
This function is called each time the club detail view is displayed.
Update the year of the membership dates.
"""
today = datetime.date.today()
if (today - self.membership_start).days >= 365:
self.membership_start = datetime.date(self.membership_start.year + 1,
self.membership_start.month, self.membership_start.day)
self.membership_end = datetime.date(self.membership_end.year + 1,
self.membership_end.month, self.membership_end.day)
self.save(force_update=True)
def save(self, force_insert=False, force_update=False, using=None, def save(self, force_insert=False, force_update=False, using=None,
update_fields=None): update_fields=None):
if not self.require_memberships: if not self.require_memberships:
@ -135,9 +153,6 @@ class Club(models.Model):
class Role(models.Model): class Role(models.Model):
""" """
Role that an :model:`auth.User` can have in a :model:`member.Club` Role that an :model:`auth.User` can have in a :model:`member.Club`
TODO: Integrate the right management, and create some standard Roles at the
creation of the club.
""" """
name = models.CharField( name = models.CharField(
verbose_name=_('name'), verbose_name=_('name'),
@ -162,21 +177,25 @@ class Membership(models.Model):
settings.AUTH_USER_MODEL, settings.AUTH_USER_MODEL,
on_delete=models.PROTECT, on_delete=models.PROTECT,
) )
club = models.ForeignKey( club = models.ForeignKey(
Club, Club,
on_delete=models.PROTECT, on_delete=models.PROTECT,
) )
roles = models.ForeignKey(
roles = models.ManyToManyField(
Role, Role,
on_delete=models.PROTECT,
) )
date_start = models.DateField( date_start = models.DateField(
verbose_name=_('membership starts on'), verbose_name=_('membership starts on'),
) )
date_end = models.DateField( date_end = models.DateField(
verbose_name=_('membership ends on'), verbose_name=_('membership ends on'),
null=True, null=True,
) )
fee = models.PositiveIntegerField( fee = models.PositiveIntegerField(
verbose_name=_('fee'), verbose_name=_('fee'),
) )
@ -189,8 +208,16 @@ class Membership(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.club.parent_club is not None: if self.club.parent_club is not None:
if not Membership.objects.filter(user=self.user, club=self.club.parent_club): 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')) raise ValidationError(_('User is not a member of the parent club'))
created = not self.pk
if created:
self.fee = self.club.membership_fee
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration)
if self.date_end > self.club.membership_end:
self.date_end = self.club.membership_end
super().save(*args, **kwargs) super().save(*args, **kwargs)
class Meta: class Meta:

View File

@ -2,6 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import io import io
from datetime import datetime
from PIL import Image from PIL import Image
from django.conf import settings from django.conf import settings
@ -24,8 +25,7 @@ from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin from permission.views import ProtectQuerysetMixin
from .filters import UserFilter, UserFilterFormHelper from .filters import UserFilter, UserFilterFormHelper
from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, MemberFormSet, FormSetHelper, \ from .forms import SignUpForm, ProfileForm, ClubForm, MembershipForm, CustomAuthenticationForm
CustomAuthenticationForm
from .models import Club, Membership from .models import Club, Membership
from .tables import ClubTable, UserTable from .tables import ClubTable, UserTable
@ -281,13 +281,19 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
club = context["club"] club = context["club"]
if PermissionBackend().has_perm(self.request.user, "member.change_club_membership_start", club):
club.update_membership_dates()
club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\ 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') .filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view")).order_by('-id')
context['history_list'] = HistoryTable(club_transactions) context['history_list'] = HistoryTable(club_transactions)
club_member = Membership.objects.filter(club=club)\ club_member = Membership.objects.filter(
.filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view")).all() club=club,
# TODO: consider only valid Membership date_start__lte=datetime.now().date(),
date_end__gte=datetime.now().date(),
).filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view")).all()
context['member_list'] = club_member context['member_list'] = club_member
return context return context
@ -328,31 +334,20 @@ class ClubAddMemberView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
form_class = MembershipForm form_class = MembershipForm
template_name = 'member/add_members.html' template_name = 'member/add_members.html'
def get_queryset(self, **kwargs):
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view")
| PermissionBackend.filter_queryset(self.request.user, Membership,
"change"))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\ club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\
.get(pk=self.kwargs["pk"]) .get(pk=self.kwargs["pk"])
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['formset'] = MemberFormSet()
context['helper'] = FormSetHelper()
context['club'] = club context['club'] = club
context['no_cache'] = True context['no_cache'] = True
return context return context
def post(self, request, *args, **kwargs): def form_valid(self, form):
return club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\
# TODO: Implement POST .get(pk=self.kwargs["pk"])
# formset = MembershipFormset(request.POST) form.instance.club = club
# if formset.is_valid(): return super().form_valid(form)
# return self.form_valid(formset)
# else:
# return self.form_invalid(formset)
def form_valid(self, formset): def get_success_url(self):
formset.save() return reverse_lazy('member:club_detail', kwargs={'pk': self.object.club.id})
return super().form_valid(formset)

View File

@ -32,6 +32,7 @@ class PermissionBackend(ModelBackend):
for permission in Permission.objects.annotate(club=F("rolepermissions__role__membership__club")) \ for permission in Permission.objects.annotate(club=F("rolepermissions__role__membership__club")) \
.filter( .filter(
rolepermissions__role__membership__user=user, rolepermissions__role__membership__user=user,
rolepermissions__role__membership__valid=True,
model__app_label=model.app_label, # For polymorphic models, we don't filter on model type model__app_label=model.app_label, # For polymorphic models, we don't filter on model type
type=type, type=type,
).all(): ).all():

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-03-31 04:16+0200\n" "POT-Creation-Date: 2020-03-31 23:49+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -44,11 +44,10 @@ msgid "You can't invite more than 3 people to this activity."
msgstr "" msgstr ""
#: apps/activity/models.py:23 apps/activity/models.py:48 #: apps/activity/models.py:23 apps/activity/models.py:48
#: apps/member/models.py:64 apps/member/models.py:122 #: apps/member/models.py:64 apps/member/models.py:158
#: apps/note/models/notes.py:186 apps/note/models/notes.py:224 #: apps/note/models/notes.py:188 apps/note/models/transactions.py:24
#: apps/note/models/transactions.py:24 apps/note/models/transactions.py:44 #: apps/note/models/transactions.py:44 apps/note/models/transactions.py:231
#: apps/note/models/transactions.py:231 templates/member/club_info.html:13 #: templates/member/club_info.html:13 templates/member/profile_info.html:14
#: templates/member/profile_info.html:14
msgid "name" msgid "name"
msgstr "" msgstr ""
@ -73,14 +72,14 @@ msgstr ""
msgid "description" msgid "description"
msgstr "" msgstr ""
#: apps/activity/models.py:60 apps/note/models/notes.py:166 #: apps/activity/models.py:60 apps/note/models/notes.py:164
#: apps/note/models/transactions.py:62 #: apps/note/models/transactions.py:62
#: templates/activity/activity_detail.html:19 #: templates/activity/activity_detail.html:19
msgid "type" msgid "type"
msgstr "" msgstr ""
#: apps/activity/models.py:66 apps/logs/models.py:21 #: apps/activity/models.py:66 apps/logs/models.py:21
#: apps/note/models/notes.py:119 #: apps/note/models/notes.py:117
msgid "user" msgid "user"
msgstr "" msgstr ""
@ -89,7 +88,7 @@ msgid "organizer"
msgstr "" msgstr ""
#: apps/activity/models.py:82 apps/activity/models.py:131 apps/note/apps.py:14 #: apps/activity/models.py:82 apps/activity/models.py:131 apps/note/apps.py:14
#: apps/note/models/notes.py:60 #: apps/note/models/notes.py:58
msgid "note" msgid "note"
msgstr "" msgstr ""
@ -179,7 +178,7 @@ msgstr ""
msgid "First name" msgid "First name"
msgstr "" msgstr ""
#: apps/activity/tables.py:81 apps/note/models/notes.py:69 #: apps/activity/tables.py:81 apps/note/models/notes.py:67
msgid "Note" msgid "Note"
msgstr "" msgstr ""
@ -227,12 +226,12 @@ msgstr ""
msgid "create" msgid "create"
msgstr "" msgstr ""
#: apps/logs/models.py:61 apps/note/tables.py:160 #: apps/logs/models.py:61 apps/note/tables.py:142
#: templates/activity/activity_detail.html:67 #: templates/activity/activity_detail.html:67
msgid "edit" msgid "edit"
msgstr "" msgstr ""
#: apps/logs/models.py:62 apps/note/tables.py:120 apps/note/tables.py:164 #: apps/logs/models.py:62 apps/note/tables.py:120 apps/note/tables.py:146
msgid "delete" msgid "delete"
msgstr "" msgstr ""
@ -276,7 +275,7 @@ msgstr ""
msgid "user profile" msgid "user profile"
msgstr "" msgstr ""
#: apps/member/models.py:69 templates/member/club_info.html:36 #: apps/member/models.py:69 templates/member/club_info.html:38
msgid "email" msgid "email"
msgstr "" msgstr ""
@ -284,90 +283,97 @@ msgstr ""
msgid "parent club" msgid "parent club"
msgstr "" msgstr ""
#: apps/member/models.py:81 templates/member/club_info.html:30 #: apps/member/models.py:85
msgid "membership fee" msgid "require memberships"
msgstr ""
#: apps/member/models.py:85 templates/member/club_info.html:27
msgid "membership duration"
msgstr "" msgstr ""
#: apps/member/models.py:86 #: apps/member/models.py:86
msgid "The longest time a membership can last (NULL = infinite)." msgid "Uncheck if this club don't require memberships."
msgstr "" msgstr ""
#: apps/member/models.py:91 templates/member/club_info.html:21 #: apps/member/models.py:91 templates/member/club_info.html:31
msgid "membership start" msgid "membership fee"
msgstr "" msgstr ""
#: apps/member/models.py:92 #: apps/member/models.py:97 templates/member/club_info.html:28
msgid "How long after January 1st the members can renew their membership." msgid "membership duration"
msgstr ""
#: apps/member/models.py:97 templates/member/club_info.html:24
msgid "membership end"
msgstr "" msgstr ""
#: apps/member/models.py:98 #: apps/member/models.py:98
msgid "The longest time (in days) a membership can last (NULL = infinite)."
msgstr ""
#: apps/member/models.py:105 templates/member/club_info.html:22
msgid "membership start"
msgstr ""
#: apps/member/models.py:106
msgid "How long after January 1st the members can renew their membership."
msgstr ""
#: apps/member/models.py:113 templates/member/club_info.html:25
msgid "membership end"
msgstr ""
#: apps/member/models.py:114
msgid "" msgid ""
"How long the membership can last after January 1st of the next year after " "How long the membership can last after January 1st of the next year after "
"members can renew their membership." "members can renew their membership."
msgstr "" msgstr ""
#: apps/member/models.py:104 apps/note/models/notes.py:141 #: apps/member/models.py:143 apps/note/models/notes.py:139
#: apps/note/models/notes.py:195
msgid "club" msgid "club"
msgstr "" msgstr ""
#: apps/member/models.py:105 #: apps/member/models.py:144
msgid "clubs" msgid "clubs"
msgstr "" msgstr ""
#: apps/member/models.py:128 apps/permission/models.py:284 #: apps/member/models.py:164 apps/permission/models.py:284
msgid "role" msgid "role"
msgstr "" msgstr ""
#: apps/member/models.py:129 #: apps/member/models.py:165
msgid "roles" msgid "roles"
msgstr "" msgstr ""
#: apps/member/models.py:153 #: apps/member/models.py:191
msgid "membership starts on" msgid "membership starts on"
msgstr "" msgstr ""
#: apps/member/models.py:156 #: apps/member/models.py:195
msgid "membership ends on" msgid "membership ends on"
msgstr "" msgstr ""
#: apps/member/models.py:160 #: apps/member/models.py:200
msgid "fee" msgid "fee"
msgstr "" msgstr ""
#: apps/member/models.py:172 #: apps/member/models.py:212
msgid "User is not a member of the parent club" msgid "User is not a member of the parent club"
msgstr "" msgstr ""
#: apps/member/models.py:176 #: apps/member/models.py:224
msgid "membership" msgid "membership"
msgstr "" msgstr ""
#: apps/member/models.py:177 #: apps/member/models.py:225
msgid "memberships" msgid "memberships"
msgstr "" msgstr ""
#: apps/member/views.py:78 templates/member/profile_info.html:45 #: apps/member/views.py:77 templates/member/profile_info.html:45
msgid "Update Profile" msgid "Update Profile"
msgstr "" msgstr ""
#: apps/member/views.py:91 #: apps/member/views.py:90
msgid "An alias with a similar name already exists." msgid "An alias with a similar name already exists."
msgstr "" msgstr ""
#: apps/note/admin.py:128 apps/note/models/transactions.py:94 #: apps/note/admin.py:120 apps/note/models/transactions.py:94
msgid "source" msgid "source"
msgstr "" msgstr ""
#: apps/note/admin.py:136 apps/note/admin.py:164 #: apps/note/admin.py:128 apps/note/admin.py:156
#: apps/note/models/transactions.py:53 apps/note/models/transactions.py:107 #: apps/note/models/transactions.py:53 apps/note/models/transactions.py:107
msgid "destination" msgid "destination"
msgstr "" msgstr ""
@ -380,104 +386,104 @@ msgstr ""
msgid "Maximal size: 2MB" msgid "Maximal size: 2MB"
msgstr "" msgstr ""
#: apps/note/models/notes.py:29 #: apps/note/models/notes.py:27
msgid "account balance" msgid "account balance"
msgstr "" msgstr ""
#: apps/note/models/notes.py:30 #: apps/note/models/notes.py:28
msgid "in centimes, money credited for this instance" msgid "in centimes, money credited for this instance"
msgstr "" msgstr ""
#: apps/note/models/notes.py:34 #: apps/note/models/notes.py:32
msgid "last negative date" msgid "last negative date"
msgstr "" msgstr ""
#: apps/note/models/notes.py:35 #: apps/note/models/notes.py:33
msgid "last time the balance was negative" msgid "last time the balance was negative"
msgstr "" msgstr ""
#: apps/note/models/notes.py:40 #: apps/note/models/notes.py:38
msgid "active" msgid "active"
msgstr "" msgstr ""
#: apps/note/models/notes.py:43 #: apps/note/models/notes.py:41
msgid "" msgid ""
"Designates whether this note should be treated as active. Unselect this " "Designates whether this note should be treated as active. Unselect this "
"instead of deleting notes." "instead of deleting notes."
msgstr "" msgstr ""
#: apps/note/models/notes.py:47 #: apps/note/models/notes.py:45
msgid "display image" msgid "display image"
msgstr "" msgstr ""
#: apps/note/models/notes.py:55 apps/note/models/transactions.py:117 #: apps/note/models/notes.py:53 apps/note/models/transactions.py:117
msgid "created at" msgid "created at"
msgstr "" msgstr ""
#: apps/note/models/notes.py:61 #: apps/note/models/notes.py:59
msgid "notes" msgid "notes"
msgstr "" msgstr ""
#: apps/note/models/notes.py:79 apps/note/models/notes.py:103 #: apps/note/models/notes.py:77 apps/note/models/notes.py:101
msgid "This alias is already taken." msgid "This alias is already taken."
msgstr "" msgstr ""
#: apps/note/models/notes.py:123 #: apps/note/models/notes.py:121
msgid "one's note" msgid "one's note"
msgstr "" msgstr ""
#: apps/note/models/notes.py:124 #: apps/note/models/notes.py:122
msgid "users note" msgid "users note"
msgstr "" msgstr ""
#: apps/note/models/notes.py:130 #: apps/note/models/notes.py:128
#, python-format #, python-format
msgid "%(user)s's note" msgid "%(user)s's note"
msgstr "" msgstr ""
#: apps/note/models/notes.py:145 #: apps/note/models/notes.py:143
msgid "club note" msgid "club note"
msgstr "" msgstr ""
#: apps/note/models/notes.py:146 #: apps/note/models/notes.py:144
msgid "clubs notes" msgid "clubs notes"
msgstr "" msgstr ""
#: apps/note/models/notes.py:152 #: apps/note/models/notes.py:150
#, python-format #, python-format
msgid "Note of %(club)s club" msgid "Note of %(club)s club"
msgstr "" msgstr ""
#: apps/note/models/notes.py:172 #: apps/note/models/notes.py:170
msgid "special note" msgid "special note"
msgstr "" msgstr ""
#: apps/note/models/notes.py:173 #: apps/note/models/notes.py:171
msgid "special notes" msgid "special notes"
msgstr "" msgstr ""
#: apps/note/models/notes.py:230 #: apps/note/models/notes.py:194
msgid "Invalid alias" msgid "Invalid alias"
msgstr "" msgstr ""
#: apps/note/models/notes.py:246 #: apps/note/models/notes.py:210
msgid "alias" msgid "alias"
msgstr "" msgstr ""
#: apps/note/models/notes.py:247 templates/member/club_info.html:33 #: apps/note/models/notes.py:211 templates/member/club_info.html:35
#: templates/member/profile_info.html:36 #: templates/member/profile_info.html:36
msgid "aliases" msgid "aliases"
msgstr "" msgstr ""
#: apps/note/models/notes.py:269 #: apps/note/models/notes.py:233
msgid "Alias is too long." msgid "Alias is too long."
msgstr "" msgstr ""
#: apps/note/models/notes.py:274 #: apps/note/models/notes.py:238
msgid "An alias with a similar name already exists: {} " msgid "An alias with a similar name already exists: {} "
msgstr "" msgstr ""
#: apps/note/models/notes.py:287 #: apps/note/models/notes.py:251
msgid "You can't delete your main alias." msgid "You can't delete your main alias."
msgstr "" msgstr ""
@ -614,7 +620,7 @@ msgstr ""
#: templates/activity/activity_form.html:9 #: templates/activity/activity_form.html:9
#: templates/activity/activity_invite.html:8 #: templates/activity/activity_invite.html:8
#: templates/django_filters/rest_framework/form.html:5 #: templates/django_filters/rest_framework/form.html:5
#: templates/member/club_form.html:9 #: templates/member/add_members.html:14 templates/member/club_form.html:9
#: templates/treasury/invoice_form.html:46 #: templates/treasury/invoice_form.html:46
msgid "Submit" msgid "Submit"
msgstr "" msgstr ""
@ -882,23 +888,23 @@ msgstr ""
msgid "Club Parent" msgid "Club Parent"
msgstr "" msgstr ""
#: templates/member/club_info.html:39 #: templates/member/club_info.html:29
msgid "linked notes" msgid "days"
msgstr "" msgstr ""
#: templates/member/club_info.html:44 #: templates/member/club_info.html:43
msgid "Add member" msgid "Add member"
msgstr "" msgstr ""
#: templates/member/club_info.html:45 templates/note/conso_form.html:121 #: templates/member/club_info.html:44 templates/note/conso_form.html:121
msgid "Edit" msgid "Edit"
msgstr "" msgstr ""
#: templates/member/club_info.html:46 #: templates/member/club_info.html:45
msgid "Add roles" msgid "Add roles"
msgstr "" msgstr ""
#: templates/member/club_info.html:49 templates/member/profile_info.html:48 #: templates/member/club_info.html:48 templates/member/profile_info.html:48
msgid "View Profile" msgid "View Profile"
msgstr "" msgstr ""

View File

@ -3,7 +3,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-03-31 04:16+0200\n" "POT-Creation-Date: 2020-03-31 23:49+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -40,11 +40,10 @@ msgid "You can't invite more than 3 people to this activity."
msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité." msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité."
#: apps/activity/models.py:23 apps/activity/models.py:48 #: apps/activity/models.py:23 apps/activity/models.py:48
#: apps/member/models.py:64 apps/member/models.py:122 #: apps/member/models.py:64 apps/member/models.py:158
#: apps/note/models/notes.py:186 apps/note/models/notes.py:224 #: apps/note/models/notes.py:188 apps/note/models/transactions.py:24
#: apps/note/models/transactions.py:24 apps/note/models/transactions.py:44 #: apps/note/models/transactions.py:44 apps/note/models/transactions.py:231
#: apps/note/models/transactions.py:231 templates/member/club_info.html:13 #: templates/member/club_info.html:13 templates/member/profile_info.html:14
#: templates/member/profile_info.html:14
msgid "name" msgid "name"
msgstr "nom" msgstr "nom"
@ -69,14 +68,14 @@ msgstr "types d'activité"
msgid "description" msgid "description"
msgstr "description" msgstr "description"
#: apps/activity/models.py:60 apps/note/models/notes.py:166 #: apps/activity/models.py:60 apps/note/models/notes.py:164
#: apps/note/models/transactions.py:62 #: apps/note/models/transactions.py:62
#: templates/activity/activity_detail.html:19 #: templates/activity/activity_detail.html:19
msgid "type" msgid "type"
msgstr "type" msgstr "type"
#: apps/activity/models.py:66 apps/logs/models.py:21 #: apps/activity/models.py:66 apps/logs/models.py:21
#: apps/note/models/notes.py:119 #: apps/note/models/notes.py:117
msgid "user" msgid "user"
msgstr "utilisateur" msgstr "utilisateur"
@ -85,7 +84,7 @@ msgid "organizer"
msgstr "organisateur" msgstr "organisateur"
#: apps/activity/models.py:82 apps/activity/models.py:131 apps/note/apps.py:14 #: apps/activity/models.py:82 apps/activity/models.py:131 apps/note/apps.py:14
#: apps/note/models/notes.py:60 #: apps/note/models/notes.py:58
msgid "note" msgid "note"
msgstr "note" msgstr "note"
@ -175,7 +174,7 @@ msgstr "Nom de famille"
msgid "First name" msgid "First name"
msgstr "Prénom" msgstr "Prénom"
#: apps/activity/tables.py:81 apps/note/models/notes.py:69 #: apps/activity/tables.py:81 apps/note/models/notes.py:67
msgid "Note" msgid "Note"
msgstr "Note" msgstr "Note"
@ -223,12 +222,12 @@ msgstr "Nouvelles données"
msgid "create" msgid "create"
msgstr "Créer" msgstr "Créer"
#: apps/logs/models.py:61 apps/note/tables.py:160 #: apps/logs/models.py:61 apps/note/tables.py:142
#: templates/activity/activity_detail.html:67 #: templates/activity/activity_detail.html:67
msgid "edit" msgid "edit"
msgstr "Modifier" msgstr "Modifier"
#: apps/logs/models.py:62 apps/note/tables.py:120 apps/note/tables.py:164 #: apps/logs/models.py:62 apps/note/tables.py:120 apps/note/tables.py:146
msgid "delete" msgid "delete"
msgstr "Supprimer" msgstr "Supprimer"
@ -272,7 +271,7 @@ msgstr "payé"
msgid "user profile" msgid "user profile"
msgstr "profil utilisateur" msgstr "profil utilisateur"
#: apps/member/models.py:69 templates/member/club_info.html:36 #: apps/member/models.py:69 templates/member/club_info.html:38
msgid "email" msgid "email"
msgstr "courriel" msgstr "courriel"
@ -280,33 +279,41 @@ msgstr "courriel"
msgid "parent club" msgid "parent club"
msgstr "club parent" msgstr "club parent"
#: apps/member/models.py:81 templates/member/club_info.html:30 #: apps/member/models.py:85
msgid "require memberships"
msgstr "nécessite des adhésions"
#: apps/member/models.py:86
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:91 templates/member/club_info.html:31
msgid "membership fee" msgid "membership fee"
msgstr "cotisation pour adhérer" msgstr "cotisation pour adhérer"
#: apps/member/models.py:85 templates/member/club_info.html:27 #: apps/member/models.py:97 templates/member/club_info.html:28
msgid "membership duration" msgid "membership duration"
msgstr "durée de l'adhésion" msgstr "durée de l'adhésion"
#: apps/member/models.py:86 #: apps/member/models.py:98
msgid "The longest time a membership can last (NULL = infinite)." msgid "The longest time (in days) a membership can last (NULL = infinite)."
msgstr "La durée maximale d'une adhésion (NULL = infinie)." msgstr "La durée maximale (en jours) d'une adhésion (NULL = infinie)."
#: apps/member/models.py:91 templates/member/club_info.html:21 #: apps/member/models.py:105 templates/member/club_info.html:22
msgid "membership start" msgid "membership start"
msgstr "début de l'adhésion" msgstr "début de l'adhésion"
#: apps/member/models.py:92 #: apps/member/models.py:106
msgid "How long after January 1st the members can renew their membership." msgid "How long after January 1st the members can renew their membership."
msgstr "" msgstr ""
"Combien de temps après le 1er Janvier les adhérents peuvent renouveler leur " "Combien de temps après le 1er Janvier les adhérents peuvent renouveler leur "
"adhésion." "adhésion."
#: apps/member/models.py:97 templates/member/club_info.html:24 #: apps/member/models.py:113 templates/member/club_info.html:25
msgid "membership end" msgid "membership end"
msgstr "fin de l'adhésion" msgstr "fin de l'adhésion"
#: apps/member/models.py:98 #: apps/member/models.py:114
msgid "" msgid ""
"How long the membership can last after January 1st of the next year after " "How long the membership can last after January 1st of the next year after "
"members can renew their membership." "members can renew their membership."
@ -314,60 +321,59 @@ msgstr ""
"Combien de temps l'adhésion peut durer après le 1er Janvier de l'année " "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." "suivante avant que les adhérents peuvent renouveler leur adhésion."
#: apps/member/models.py:104 apps/note/models/notes.py:141 #: apps/member/models.py:143 apps/note/models/notes.py:139
#: apps/note/models/notes.py:195
msgid "club" msgid "club"
msgstr "club" msgstr "club"
#: apps/member/models.py:105 #: apps/member/models.py:144
msgid "clubs" msgid "clubs"
msgstr "clubs" msgstr "clubs"
#: apps/member/models.py:128 apps/permission/models.py:284 #: apps/member/models.py:164 apps/permission/models.py:284
msgid "role" msgid "role"
msgstr "rôle" msgstr "rôle"
#: apps/member/models.py:129 #: apps/member/models.py:165
msgid "roles" msgid "roles"
msgstr "rôles" msgstr "rôles"
#: apps/member/models.py:153 #: apps/member/models.py:191
msgid "membership starts on" msgid "membership starts on"
msgstr "l'adhésion commence le" msgstr "l'adhésion commence le"
#: apps/member/models.py:156 #: apps/member/models.py:195
msgid "membership ends on" msgid "membership ends on"
msgstr "l'adhésion finie le" msgstr "l'adhésion finie le"
#: apps/member/models.py:160 #: apps/member/models.py:200
msgid "fee" msgid "fee"
msgstr "cotisation" msgstr "cotisation"
#: apps/member/models.py:172 #: apps/member/models.py:212
msgid "User is not a member of the parent club" msgid "User is not a member of the parent club"
msgstr "L'utilisateur n'est pas membre du club parent" msgstr "L'utilisateur n'est pas membre du club parent"
#: apps/member/models.py:176 #: apps/member/models.py:224
msgid "membership" msgid "membership"
msgstr "adhésion" msgstr "adhésion"
#: apps/member/models.py:177 #: apps/member/models.py:225
msgid "memberships" msgid "memberships"
msgstr "adhésions" msgstr "adhésions"
#: apps/member/views.py:78 templates/member/profile_info.html:45 #: apps/member/views.py:77 templates/member/profile_info.html:45
msgid "Update Profile" msgid "Update Profile"
msgstr "Modifier le profil" msgstr "Modifier le profil"
#: apps/member/views.py:91 #: apps/member/views.py:90
msgid "An alias with a similar name already exists." msgid "An alias with a similar name already exists."
msgstr "Un alias avec un nom similaire existe déjà." msgstr "Un alias avec un nom similaire existe déjà."
#: apps/note/admin.py:128 apps/note/models/transactions.py:94 #: apps/note/admin.py:120 apps/note/models/transactions.py:94
msgid "source" msgid "source"
msgstr "source" msgstr "source"
#: apps/note/admin.py:136 apps/note/admin.py:164 #: apps/note/admin.py:128 apps/note/admin.py:156
#: apps/note/models/transactions.py:53 apps/note/models/transactions.py:107 #: apps/note/models/transactions.py:53 apps/note/models/transactions.py:107
msgid "destination" msgid "destination"
msgstr "destination" msgstr "destination"
@ -380,105 +386,105 @@ msgstr "Choisissez une image"
msgid "Maximal size: 2MB" msgid "Maximal size: 2MB"
msgstr "Taille maximale : 2 Mo" msgstr "Taille maximale : 2 Mo"
#: apps/note/models/notes.py:29 #: apps/note/models/notes.py:27
msgid "account balance" msgid "account balance"
msgstr "solde du compte" msgstr "solde du compte"
#: apps/note/models/notes.py:30 #: apps/note/models/notes.py:28
msgid "in centimes, money credited for this instance" msgid "in centimes, money credited for this instance"
msgstr "en centimes, argent crédité pour cette instance" msgstr "en centimes, argent crédité pour cette instance"
#: apps/note/models/notes.py:34 #: apps/note/models/notes.py:32
msgid "last negative date" msgid "last negative date"
msgstr "dernier date de négatif" msgstr "dernier date de négatif"
#: apps/note/models/notes.py:35 #: apps/note/models/notes.py:33
msgid "last time the balance was negative" msgid "last time the balance was negative"
msgstr "dernier instant où la note était en négatif" msgstr "dernier instant où la note était en négatif"
#: apps/note/models/notes.py:40 #: apps/note/models/notes.py:38
msgid "active" msgid "active"
msgstr "actif" msgstr "actif"
#: apps/note/models/notes.py:43 #: apps/note/models/notes.py:41
msgid "" msgid ""
"Designates whether this note should be treated as active. Unselect this " "Designates whether this note should be treated as active. Unselect this "
"instead of deleting notes." "instead of deleting notes."
msgstr "" msgstr ""
"Indique si la note est active. Désactiver cela plutôt que supprimer la note." "Indique si la note est active. Désactiver cela plutôt que supprimer la note."
#: apps/note/models/notes.py:47 #: apps/note/models/notes.py:45
msgid "display image" msgid "display image"
msgstr "image affichée" msgstr "image affichée"
#: apps/note/models/notes.py:55 apps/note/models/transactions.py:117 #: apps/note/models/notes.py:53 apps/note/models/transactions.py:117
msgid "created at" msgid "created at"
msgstr "créée le" msgstr "créée le"
#: apps/note/models/notes.py:61 #: apps/note/models/notes.py:59
msgid "notes" msgid "notes"
msgstr "notes" msgstr "notes"
#: apps/note/models/notes.py:79 apps/note/models/notes.py:103 #: apps/note/models/notes.py:77 apps/note/models/notes.py:101
msgid "This alias is already taken." msgid "This alias is already taken."
msgstr "Cet alias est déjà pris." msgstr "Cet alias est déjà pris."
#: apps/note/models/notes.py:123 #: apps/note/models/notes.py:121
msgid "one's note" msgid "one's note"
msgstr "note d'un utilisateur" msgstr "note d'un utilisateur"
#: apps/note/models/notes.py:124 #: apps/note/models/notes.py:122
msgid "users note" msgid "users note"
msgstr "notes des utilisateurs" msgstr "notes des utilisateurs"
#: apps/note/models/notes.py:130 #: apps/note/models/notes.py:128
#, python-format #, python-format
msgid "%(user)s's note" msgid "%(user)s's note"
msgstr "Note de %(user)s" msgstr "Note de %(user)s"
#: apps/note/models/notes.py:145 #: apps/note/models/notes.py:143
msgid "club note" msgid "club note"
msgstr "note d'un club" msgstr "note d'un club"
#: apps/note/models/notes.py:146 #: apps/note/models/notes.py:144
msgid "clubs notes" msgid "clubs notes"
msgstr "notes des clubs" msgstr "notes des clubs"
#: apps/note/models/notes.py:152 #: apps/note/models/notes.py:150
#, python-format #, python-format
msgid "Note of %(club)s club" msgid "Note of %(club)s club"
msgstr "Note du club %(club)s" msgstr "Note du club %(club)s"
#: apps/note/models/notes.py:172 #: apps/note/models/notes.py:170
msgid "special note" msgid "special note"
msgstr "note spéciale" msgstr "note spéciale"
#: apps/note/models/notes.py:173 #: apps/note/models/notes.py:171
msgid "special notes" msgid "special notes"
msgstr "notes spéciales" msgstr "notes spéciales"
#: apps/note/models/notes.py:230 #: apps/note/models/notes.py:194
msgid "Invalid alias" msgid "Invalid alias"
msgstr "Alias invalide" msgstr "Alias invalide"
#: apps/note/models/notes.py:246 #: apps/note/models/notes.py:210
msgid "alias" msgid "alias"
msgstr "alias" msgstr "alias"
#: apps/note/models/notes.py:247 templates/member/club_info.html:33 #: apps/note/models/notes.py:211 templates/member/club_info.html:35
#: templates/member/profile_info.html:36 #: templates/member/profile_info.html:36
msgid "aliases" msgid "aliases"
msgstr "alias" msgstr "alias"
#: apps/note/models/notes.py:269 #: apps/note/models/notes.py:233
msgid "Alias is too long." msgid "Alias is too long."
msgstr "L'alias est trop long." msgstr "L'alias est trop long."
#: apps/note/models/notes.py:274 #: apps/note/models/notes.py:238
msgid "An alias with a similar name already exists: {} " msgid "An alias with a similar name already exists: {} "
msgstr "Un alias avec un nom similaire existe déjà : {}" msgstr "Un alias avec un nom similaire existe déjà : {}"
#: apps/note/models/notes.py:287 #: apps/note/models/notes.py:251
msgid "You can't delete your main alias." msgid "You can't delete your main alias."
msgstr "Vous ne pouvez pas supprimer votre alias principal." msgstr "Vous ne pouvez pas supprimer votre alias principal."
@ -615,7 +621,7 @@ msgstr "Trésorerie"
#: templates/activity/activity_form.html:9 #: templates/activity/activity_form.html:9
#: templates/activity/activity_invite.html:8 #: templates/activity/activity_invite.html:8
#: templates/django_filters/rest_framework/form.html:5 #: templates/django_filters/rest_framework/form.html:5
#: templates/member/club_form.html:9 #: templates/member/add_members.html:14 templates/member/club_form.html:9
#: templates/treasury/invoice_form.html:46 #: templates/treasury/invoice_form.html:46
msgid "Submit" msgid "Submit"
msgstr "Envoyer" msgstr "Envoyer"
@ -885,23 +891,23 @@ msgstr "Ajouter un alias"
msgid "Club Parent" msgid "Club Parent"
msgstr "Club parent" msgstr "Club parent"
#: templates/member/club_info.html:39 #: templates/member/club_info.html:29
msgid "linked notes" msgid "days"
msgstr "notes liées" msgstr "jours"
#: templates/member/club_info.html:44 #: templates/member/club_info.html:43
msgid "Add member" msgid "Add member"
msgstr "Ajouter un membre" msgstr "Ajouter un membre"
#: templates/member/club_info.html:45 templates/note/conso_form.html:121 #: templates/member/club_info.html:44 templates/note/conso_form.html:121
msgid "Edit" msgid "Edit"
msgstr "Éditer" msgstr "Éditer"
#: templates/member/club_info.html:46 #: templates/member/club_info.html:45
msgid "Add roles" msgid "Add roles"
msgstr "Ajouter des rôles" msgstr "Ajouter des rôles"
#: templates/member/club_info.html:49 templates/member/profile_info.html:48 #: templates/member/club_info.html:48 templates/member/profile_info.html:48
msgid "View Profile" msgid "View Profile"
msgstr "Voir le profil" msgstr "Voir le profil"
@ -1210,3 +1216,6 @@ msgstr "Il n'y a pas de transaction associée à une remise ouverte."
#: templates/treasury/remittance_list.html:54 #: templates/treasury/remittance_list.html:54
msgid "Closed remittances" msgid "Closed remittances"
msgstr "Remises fermées" msgstr "Remises fermées"
#~ msgid "linked notes"
#~ msgstr "notes liées"

View File

@ -1,6 +1,7 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import datetime
from json import dumps as json_dumps from json import dumps as json_dumps
from django.forms.widgets import DateTimeBaseInput, NumberInput, TextInput from django.forms.widgets import DateTimeBaseInput, NumberInput, TextInput
@ -299,4 +300,4 @@ class YearPickerInput(BasePickerInput):
def _link_to(self, linked_picker): def _link_to(self, linked_picker):
"""Customize the options when linked with other date-time input""" """Customize the options when linked with other date-time input"""
yformat = self.config['options']['format'].replace('-01-01', '-12-31') yformat = self.config['options']['format'].replace('-01-01', '-12-31')
self.config['options']['format'] = yformat self.config['options']['format'] = yformat

View File

@ -1,29 +1,21 @@
{% extends "member/noteowner_detail.html" %} {% extends "member/noteowner_detail.html" %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% load static %} {% load static %}
{% load i18n %}
{% block profile_info %} {% block profile_info %}
{% include "member/club_info.html" %} {% include "member/club_info.html" %}
{% endblock %} {% endblock %}
{% block profile_content %}
{% block profile_content %}
<form method="post" action=""> <form method="post" action="">
{% csrf_token %} {% csrf_token %}
{% crispy formset helper %} {{ form|crispy }}
<div class="form-actions"> <button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
<input type="submit" name="submit" value="Add Members" class="btn btn-primary" id="submit-save">
</div>
</form> </form>
{% endblock %} {% endblock %}
{% block extrajavascript %} {% block extrajavascript %}
<script src="{% static 'js/dynamic-formset.js' %}"></script>
<script> <script>
$('.formset-row').formset({
addText: 'add another', // Text for the add link
deleteText: 'remove', // Text for the delete link
addCssClass: 'btn btn-primary', // CSS class applied to the add link
deleteCssClass: 'btn btn-danger h-50 my-auto',
});
</script> </script>
{% endblock %} {% endblock %}

View File

@ -26,7 +26,7 @@
<dd class="col-xl-6">{{ club.membership_end }}</dd> <dd class="col-xl-6">{{ club.membership_end }}</dd>
<dt class="col-xl-6">{% trans 'membership duration'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'membership duration'|capfirst %}</dt>
<dd class="col-xl-6">{{ club.membership_duration }}</dd> <dd class="col-xl-6">{{ club.membership_duration }} {% trans "days" %}</dd>
<dt class="col-xl-6">{% trans 'membership fee'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'membership fee'|capfirst %}</dt>
<dd class="col-xl-6">{{ club.membership_fee|pretty_money }}</dd> <dd class="col-xl-6">{{ club.membership_fee|pretty_money }}</dd>