1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2024-11-26 18:37:12 +00:00

Merge branch 'activity' into 'master'

Activity management

See merge request bde/nk20!32
This commit is contained in:
ynerant 2020-03-30 22:43:12 +02:00
commit c8854cf45d
48 changed files with 2477 additions and 626 deletions

View File

@ -3,7 +3,7 @@
from rest_framework import serializers from rest_framework import serializers
from ..models import ActivityType, Activity, Guest from ..models import ActivityType, Activity, Guest, Entry, GuestTransaction
class ActivityTypeSerializer(serializers.ModelSerializer): class ActivityTypeSerializer(serializers.ModelSerializer):
@ -37,3 +37,25 @@ class GuestSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Guest model = Guest
fields = '__all__' fields = '__all__'
class EntrySerializer(serializers.ModelSerializer):
"""
REST API Serializer for Entries.
The djangorestframework plugin will analyse the model `Entry` and parse all fields in the API.
"""
class Meta:
model = Entry
fields = '__all__'
class GuestTransactionSerializer(serializers.ModelSerializer):
"""
REST API Serializer for Special transactions.
The djangorestframework plugin will analyse the model `GuestTransaction` and parse all fields in the API.
"""
class Meta:
model = GuestTransaction
fields = '__all__'

View File

@ -1,7 +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
from .views import ActivityTypeViewSet, ActivityViewSet, GuestViewSet from .views import ActivityTypeViewSet, ActivityViewSet, GuestViewSet, EntryViewSet
def register_activity_urls(router, path): def register_activity_urls(router, path):
@ -11,3 +11,4 @@ def register_activity_urls(router, path):
router.register(path + '/activity', ActivityViewSet) router.register(path + '/activity', ActivityViewSet)
router.register(path + '/type', ActivityTypeViewSet) router.register(path + '/type', ActivityTypeViewSet)
router.register(path + '/guest', GuestViewSet) router.register(path + '/guest', GuestViewSet)
router.register(path + '/entry', EntryViewSet)

View File

@ -5,8 +5,8 @@ from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter from rest_framework.filters import SearchFilter
from api.viewsets import ReadProtectedModelViewSet from api.viewsets import ReadProtectedModelViewSet
from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer, EntrySerializer
from ..models import ActivityType, Activity, Guest from ..models import ActivityType, Activity, Guest, Entry
class ActivityTypeViewSet(ReadProtectedModelViewSet): class ActivityTypeViewSet(ReadProtectedModelViewSet):
@ -42,4 +42,16 @@ class GuestViewSet(ReadProtectedModelViewSet):
queryset = Guest.objects.all() queryset = Guest.objects.all()
serializer_class = GuestSerializer serializer_class = GuestSerializer
filter_backends = [SearchFilter] filter_backends = [SearchFilter]
search_fields = ['$name', ] search_fields = ['$last_name', '$first_name', '$inviter__alias__name', '$inviter__alias__normalized_name', ]
class EntryViewSet(ReadProtectedModelViewSet):
"""
REST API View set.
The djangorestframework plugin will get all `Entry` objects, serialize it to JSON with the given serializer,
then render it on /api/activity/entry/
"""
queryset = Entry.objects.all()
serializer_class = EntrySerializer
filter_backends = [SearchFilter]
search_fields = ['$last_name', '$first_name', '$inviter__alias__name', '$inviter__alias__normalized_name', ]

84
apps/activity/forms.py Normal file
View File

@ -0,0 +1,84 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import timedelta, datetime
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext as _
from member.models import Club
from note.models import NoteUser, Note
from note_kfet.inputs import DateTimePickerInput, Autocomplete
from .models import Activity, Guest
class ActivityForm(forms.ModelForm):
class Meta:
model = Activity
exclude = ('creater', 'valid', 'open', )
widgets = {
"organizer": Autocomplete(
model=Club,
attrs={"api_url": "/api/members/club/"},
),
"note": Autocomplete(
model=Note,
attrs={
"api_url": "/api/note/note/",
'placeholder': 'Note de l\'événement sur laquelle envoyer les crédits d\'invitation ...'
},
),
"attendees_club": Autocomplete(
model=Club,
attrs={"api_url": "/api/members/club/"},
),
"date_start": DateTimePickerInput(),
"date_end": DateTimePickerInput(),
}
class GuestForm(forms.ModelForm):
def clean(self):
cleaned_data = super().clean()
if self.activity.date_start > datetime.now():
self.add_error("inviter", _("You can't invite someone once the activity is started."))
if not self.activity.valid:
self.add_error("inviter", _("This activity is not validated yet."))
one_year = timedelta(days=365)
qs = Guest.objects.filter(
first_name=cleaned_data["first_name"],
last_name=cleaned_data["last_name"],
activity__date_start__gte=self.activity.date_start - one_year,
)
if len(qs) >= 5:
self.add_error("last_name", _("This person has been already invited 5 times this year."))
qs = qs.filter(activity=self.activity)
if qs.exists():
self.add_error("last_name", _("This person is already invited."))
qs = Guest.objects.filter(inviter=cleaned_data["inviter"], activity=self.activity)
if len(qs) >= 3:
self.add_error("inviter", _("You can't invite more than 3 people to this activity."))
return cleaned_data
class Meta:
model = Guest
fields = ('last_name', 'first_name', 'inviter', )
widgets = {
"inviter": Autocomplete(
NoteUser,
attrs={
'api_url': '/api/note/note/',
# We don't evaluate the content type at launch because the DB might be not initialized
'api_url_suffix':
lambda: '&polymorphic_ctype=' + str(ContentType.objects.get_for_model(NoteUser).pk),
'placeholder': 'Note ...',
},
),
}

View File

@ -1,9 +1,13 @@
# 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 datetime import timedelta, datetime
from django.conf import settings from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from rest_framework.exceptions import ValidationError
from note.models import NoteUser, Transaction
class ActivityType(models.Model): class ActivityType(models.Model):
@ -44,39 +48,131 @@ class Activity(models.Model):
verbose_name=_('name'), verbose_name=_('name'),
max_length=255, max_length=255,
) )
description = models.TextField( description = models.TextField(
verbose_name=_('description'), verbose_name=_('description'),
) )
activity_type = models.ForeignKey( activity_type = models.ForeignKey(
ActivityType, ActivityType,
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='+', related_name='+',
verbose_name=_('type'), verbose_name=_('type'),
) )
creater = models.ForeignKey(
User,
on_delete=models.PROTECT,
verbose_name=_("user"),
)
organizer = models.ForeignKey( organizer = models.ForeignKey(
'member.Club', 'member.Club',
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='+', related_name='+',
verbose_name=_('organizer'), verbose_name=_('organizer'),
) )
note = models.ForeignKey(
'note.Note',
on_delete=models.PROTECT,
blank=True,
null=True,
related_name='+',
verbose_name=_('note'),
)
attendees_club = models.ForeignKey( attendees_club = models.ForeignKey(
'member.Club', 'member.Club',
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='+', related_name='+',
verbose_name=_('attendees club'), verbose_name=_('attendees club'),
) )
date_start = models.DateTimeField( date_start = models.DateTimeField(
verbose_name=_('start date'), verbose_name=_('start date'),
) )
date_end = models.DateTimeField( date_end = models.DateTimeField(
verbose_name=_('end date'), verbose_name=_('end date'),
) )
valid = models.BooleanField(
default=False,
verbose_name=_('valid'),
)
open = models.BooleanField(
default=False,
verbose_name=_('open'),
)
class Meta: class Meta:
verbose_name = _("activity") verbose_name = _("activity")
verbose_name_plural = _("activities") verbose_name_plural = _("activities")
class Entry(models.Model):
activity = models.ForeignKey(
Activity,
on_delete=models.PROTECT,
related_name="entries",
verbose_name=_("activity"),
)
time = models.DateTimeField(
auto_now_add=True,
verbose_name=_("entry time"),
)
note = models.ForeignKey(
NoteUser,
on_delete=models.PROTECT,
verbose_name=_("note"),
)
guest = models.OneToOneField(
'activity.Guest',
on_delete=models.PROTECT,
null=True,
)
class Meta:
unique_together = (('activity', 'note', 'guest', ), )
def save(self, force_insert=False, force_update=False, using=None,
update_fields=None):
qs = Entry.objects.filter(~Q(pk=self.pk), activity=self.activity, note=self.note, guest=self.guest)
if qs.exists():
raise ValidationError(_("Already entered on ") + _("{:%Y-%m-%d %H:%M:%S}").format(qs.get().time, ))
if self.guest:
self.note = self.guest.inviter
insert = not self.pk
if insert:
if self.note.balance < 0:
raise ValidationError(_("The balance is negative."))
ret = super().save(force_insert, force_update, using, update_fields)
if insert and self.guest:
GuestTransaction.objects.create(
source=self.note,
source_alias=self.note.user.username,
destination=self.note,
destination_alias=self.activity.organizer.name,
quantity=1,
amount=self.activity.activity_type.guest_entry_fee,
reason="Invitation " + self.activity.name + " " + self.guest.first_name + " " + self.guest.last_name,
valid=True,
guest=self.guest,
).save()
return ret
class Guest(models.Model): class Guest(models.Model):
""" """
People who are not current members of any clubs, and are invited by someone who is a current member. People who are not current members of any clubs, and are invited by someone who is a current member.
@ -86,24 +182,73 @@ class Guest(models.Model):
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='+', related_name='+',
) )
name = models.CharField(
last_name = models.CharField(
max_length=255, max_length=255,
verbose_name=_("last name"),
) )
first_name = models.CharField(
max_length=255,
verbose_name=_("first name"),
)
inviter = models.ForeignKey( inviter = models.ForeignKey(
settings.AUTH_USER_MODEL, NoteUser,
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name='+', related_name='guests',
verbose_name=_("inviter"),
) )
entry = models.DateTimeField(
null=True, @property
) def has_entry(self):
entry_transaction = models.ForeignKey( try:
'note.Transaction', if self.entry:
on_delete=models.PROTECT, return True
blank=True, return False
null=True, except AttributeError:
return False
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
one_year = timedelta(days=365)
if not force_insert:
if self.activity.date_start > datetime.now():
raise ValidationError(_("You can't invite someone once the activity is started."))
if not self.activity.valid:
raise ValidationError(_("This activity is not validated yet."))
qs = Guest.objects.filter(
first_name=self.first_name,
last_name=self.last_name,
activity__date_start__gte=self.activity.date_start - one_year,
) )
if len(qs) >= 5:
raise ValidationError(_("This person has been already invited 5 times this year."))
qs = qs.filter(activity=self.activity)
if qs.exists():
raise ValidationError(_("This person is already invited."))
qs = Guest.objects.filter(inviter=self.inviter, activity=self.activity)
if len(qs) >= 3:
raise ValidationError(_("You can't invite more than 3 people to this activity."))
return super().save(force_insert, force_update, using, update_fields)
class Meta: class Meta:
verbose_name = _("guest") verbose_name = _("guest")
verbose_name_plural = _("guests") verbose_name_plural = _("guests")
unique_together = ("activity", "last_name", "first_name", )
class GuestTransaction(Transaction):
guest = models.OneToOneField(
Guest,
on_delete=models.PROTECT,
)
@property
def type(self):
return _('Invitation')

108
apps/activity/tables.py Normal file
View File

@ -0,0 +1,108 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
import django_tables2 as tables
from django_tables2 import A
from note.templatetags.pretty_money import pretty_money
from .models import Activity, Guest, Entry
class ActivityTable(tables.Table):
name = tables.LinkColumn(
'activity:activity_detail',
args=[A('pk'), ],
)
class Meta:
attrs = {
'class': 'table table-condensed table-striped table-hover'
}
model = Activity
template_name = 'django_tables2/bootstrap4.html'
fields = ('name', 'activity_type', 'organizer', 'attendees_club', 'date_start', 'date_end', )
class GuestTable(tables.Table):
inviter = tables.LinkColumn(
'member:user_detail',
args=[A('inviter.user.pk'), ],
)
entry = tables.Column(
empty_values=(),
attrs={
"td": {
"class": lambda record: "" if record.has_entry else "validate btn btn-danger",
"onclick": lambda record: "" if record.has_entry else "remove_guest(" + str(record.pk) + ")"
}
}
)
class Meta:
attrs = {
'class': 'table table-condensed table-striped table-hover'
}
model = Guest
template_name = 'django_tables2/bootstrap4.html'
fields = ("last_name", "first_name", "inviter", )
def render_entry(self, record):
if record.has_entry:
return str(_("Entered on ") + str(_("{:%Y-%m-%d %H:%M:%S}").format(record.entry.time, )))
return _("remove").capitalize()
def get_row_class(record):
c = "table-row"
if isinstance(record, Guest):
if record.has_entry:
c += " table-success"
else:
c += " table-warning"
else:
qs = Entry.objects.filter(note=record.note, activity=record.activity, guest=None)
if qs.exists():
c += " table-success"
elif record.note.balance < 0:
c += " table-danger"
return c
class EntryTable(tables.Table):
type = tables.Column(verbose_name=_("Type"))
last_name = tables.Column(verbose_name=_("Last name"))
first_name = tables.Column(verbose_name=_("First name"))
note_name = tables.Column(verbose_name=_("Note"))
balance = tables.Column(verbose_name=_("Balance"))
def render_note_name(self, value, record):
if hasattr(record, 'username'):
username = record.username
if username != value:
return format_html(value + " <em>aka.</em> " + username)
return value
def render_balance(self, value):
return pretty_money(value)
class Meta:
attrs = {
'class': 'table table-condensed table-striped table-hover'
}
template_name = 'django_tables2/bootstrap4.html'
row_attrs = {
'class': lambda record: get_row_class(record),
'id': lambda record: "row-" + ("guest-" if isinstance(record, Guest) else "membership-") + str(record.pk),
'data-type': lambda record: "guest" if isinstance(record, Guest) else "membership",
'data-id': lambda record: record.pk if isinstance(record, Guest) else record.note.pk,
'data-inviter': lambda record: record.inviter.pk if isinstance(record, Guest) else "",
'data-last-name': lambda record: record.last_name,
'data-first-name': lambda record: record.first_name,
}

17
apps/activity/urls.py Normal file
View File

@ -0,0 +1,17 @@
# 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 = 'activity'
urlpatterns = [
path('', views.ActivityListView.as_view(), name='activity_list'),
path('<int:pk>/', views.ActivityDetailView.as_view(), name='activity_detail'),
path('<int:pk>/invite/', views.ActivityInviteView.as_view(), name='activity_invite'),
path('<int:pk>/entry/', views.ActivityEntryView.as_view(), name='activity_entry'),
path('<int:pk>/update/', views.ActivityUpdateView.as_view(), name='activity_update'),
path('new/', views.ActivityCreateView.as_view(), name='activity_create'),
]

153
apps/activity/views.py Normal file
View File

@ -0,0 +1,153 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime, timezone
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType
from django.db.models import F, Q
from django.urls import reverse_lazy
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView
from django.utils.translation import gettext_lazy as _
from django_tables2.views import SingleTableView
from note.models import NoteUser, Alias, NoteSpecial
from permission.backends import PermissionBackend
from .forms import ActivityForm, GuestForm
from .models import Activity, Guest, Entry
from .tables import ActivityTable, GuestTable, EntryTable
class ActivityCreateView(LoginRequiredMixin, CreateView):
model = Activity
form_class = ActivityForm
def form_valid(self, form):
form.instance.creater = self.request.user
return super().form_valid(form)
def get_success_url(self, **kwargs):
self.object.refresh_from_db()
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk})
class ActivityListView(LoginRequiredMixin, SingleTableView):
model = Activity
table_class = ActivityTable
def get_queryset(self):
return super().get_queryset()\
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")).reverse()
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['title'] = _("Activities")
upcoming_activities = Activity.objects.filter(date_end__gt=datetime.now())
ctx['upcoming'] = ActivityTable(data=upcoming_activities
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")))
return ctx
class ActivityDetailView(LoginRequiredMixin, DetailView):
model = Activity
context_object_name = "activity"
def get_context_data(self, **kwargs):
ctx = super().get_context_data()
table = GuestTable(data=Guest.objects.filter(activity=self.object)
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view")))
ctx["guests"] = table
ctx["activity_started"] = datetime.now(timezone.utc) > self.object.date_start
return ctx
class ActivityUpdateView(LoginRequiredMixin, UpdateView):
model = Activity
form_class = ActivityForm
def get_success_url(self, **kwargs):
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]})
class ActivityInviteView(LoginRequiredMixin, CreateView):
model = Guest
form_class = GuestForm
template_name = "activity/activity_invite.html"
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.activity = Activity.objects.get(pk=self.kwargs["pk"])
return form
def form_valid(self, form):
form.instance.activity = Activity.objects.get(pk=self.kwargs["pk"])
return super().form_valid(form)
def get_success_url(self, **kwargs):
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]})
class ActivityEntryView(LoginRequiredMixin, TemplateView):
template_name = "activity/activity_entry.html"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
activity = Activity.objects.get(pk=self.kwargs["pk"])
ctx["activity"] = activity
matched = []
pattern = "^$"
if "search" in self.request.GET:
pattern = self.request.GET["search"]
if not pattern:
pattern = "^$"
if pattern[0] != "^":
pattern = "^" + pattern
guest_qs = Guest.objects\
.annotate(balance=F("inviter__balance"), note_name=F("inviter__user__username"))\
.filter(Q(first_name__regex=pattern) | Q(last_name__regex=pattern)
| Q(inviter__alias__name__regex=pattern)
| Q(inviter__alias__normalized_name__regex=Alias.normalize(pattern))) \
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view"))\
.distinct()[:20]
for guest in guest_qs:
guest.type = "Invité"
matched.append(guest)
note_qs = Alias.objects.annotate(last_name=F("note__noteuser__user__last_name"),
first_name=F("note__noteuser__user__first_name"),
username=F("note__noteuser__user__username"),
note_name=F("name"),
balance=F("note__balance"))\
.filter(Q(note__polymorphic_ctype__model="noteuser")
& (Q(note__noteuser__user__first_name__regex=pattern)
| Q(note__noteuser__user__last_name__regex=pattern)
| Q(name__regex=pattern)
| Q(normalized_name__regex=Alias.normalize(pattern)))) \
.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view"))\
.distinct("username")[:20]
for note in note_qs:
note.type = "Adhérent"
note.activity = activity
matched.append(note)
table = EntryTable(data=matched)
ctx["table"] = table
ctx["entries"] = Entry.objects.filter(activity=activity)
ctx["title"] = _('Entry for activity "{}"').format(activity.name)
ctx["noteuser_ctype"] = ContentType.objects.get_for_model(NoteUser).pk
ctx["notespecial_ctype"] = ContentType.objects.get_for_model(NoteSpecial).pk
return ctx

View File

@ -4,10 +4,10 @@
from crispy_forms.bootstrap import Div from crispy_forms.bootstrap import Div
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout from crispy_forms.layout import Layout
from dal import autocomplete
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
from permission.models import PermissionMask from permission.models import PermissionMask
from .models import Profile, Club, Membership from .models import Profile, Club, Membership
@ -63,11 +63,12 @@ class MembershipForm(forms.ModelForm):
# et récupère les noms d'utilisateur valides # et récupère les noms d'utilisateur valides
widgets = { widgets = {
'user': 'user':
autocomplete.ModelSelect2( Autocomplete(
url='member:user_autocomplete', User,
attrs={ attrs={
'data-placeholder': 'Nom ...', 'api_url': '/api/user/',
'data-minimum-input-length': 1, 'name_field': 'username',
'placeholder': 'Nom ...',
}, },
), ),
} }

View File

@ -21,6 +21,4 @@ urlpatterns = [
path('user/<int:pk>/update_pic', views.ProfilePictureUpdateView.as_view(), name="user_update_pic"), path('user/<int:pk>/update_pic', views.ProfilePictureUpdateView.as_view(), name="user_update_pic"),
path('user/<int:pk>/aliases', views.ProfileAliasView.as_view(), name="user_alias"), path('user/<int:pk>/aliases', views.ProfileAliasView.as_view(), name="user_alias"),
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'), path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
# API for the user autocompleter
path('user/user-autocomplete', views.UserAutocomplete.as_view(), name="user_autocomplete"),
] ]

View File

@ -4,24 +4,19 @@
import io import io
from PIL import Image from PIL import Image
from dal import autocomplete
from django.conf import settings from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.views import LoginView from django.contrib.auth.views import LoginView
from django.core.exceptions import ValidationError
from django.db.models import Q from django.db.models import Q
from django.http import HttpResponseRedirect
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView, DeleteView from django.views.generic import CreateView, DetailView, UpdateView, TemplateView
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from django_tables2.views import SingleTableView from django_tables2.views import SingleTableView
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from note.forms import ImageForm from note.forms import ImageForm
#from note.forms import AliasForm, ImageForm
from note.models import Alias, NoteUser from note.models import Alias, NoteUser
from note.models.transactions import Transaction from note.models.transactions import Transaction
from note.tables import HistoryTable, AliasTable from note.tables import HistoryTable, AliasTable
@ -257,28 +252,6 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
return context return context
class UserAutocomplete(autocomplete.Select2QuerySetView):
"""
Auto complete users by usernames
"""
def get_queryset(self):
"""
Quand une personne cherche un utilisateur par pseudo, une requête est envoyée sur l'API dédiée à l'auto-complétion.
Cette fonction récupère la requête, et renvoie la liste filtrée des utilisateurs par pseudos.
"""
# Un utilisateur non connecté n'a accès à aucune information
if not self.request.user.is_authenticated:
return User.objects.none()
qs = User.objects.filter(PermissionBackend.filter_queryset(self.request.user, User, "view")).all()
if self.q:
qs = qs.filter(username__regex="^" + self.q)
return qs
# ******************************* # # ******************************* #
# CLUB # # CLUB #
# ******************************* # # ******************************* #
@ -326,6 +299,7 @@ class ClubDetailView(LoginRequiredMixin, DetailView):
context['member_list'] = club_member context['member_list'] = club_member
return context return context
class ClubAliasView(LoginRequiredMixin, DetailView): class ClubAliasView(LoginRequiredMixin, DetailView):
model = Club model = Club
template_name = 'member/club_alias.html' template_name = 'member/club_alias.html'
@ -364,6 +338,7 @@ class ClubAddMemberView(LoginRequiredMixin, CreateView):
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view") return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view")
| PermissionBackend.filter_queryset(self.request.user, Membership, | PermissionBackend.filter_queryset(self.request.user, Membership,
"change")) "change"))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
club = Club.objects.get(pk=self.kwargs["pk"]) club = Club.objects.get(pk=self.kwargs["pk"])
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)

View File

@ -163,6 +163,7 @@ class SpecialTransactionSerializer(serializers.ModelSerializer):
fields = '__all__' fields = '__all__'
# noinspection PyUnresolvedReferences
class TransactionPolymorphicSerializer(PolymorphicSerializer): class TransactionPolymorphicSerializer(PolymorphicSerializer):
model_serializer_mapping = { model_serializer_mapping = {
Transaction: TransactionSerializer, Transaction: TransactionSerializer,
@ -171,5 +172,12 @@ class TransactionPolymorphicSerializer(PolymorphicSerializer):
SpecialTransaction: SpecialTransactionSerializer, SpecialTransaction: SpecialTransactionSerializer,
} }
try:
from activity.models import GuestTransaction
from activity.api.serializers import GuestTransactionSerializer
model_serializer_mapping[GuestTransaction] = GuestTransactionSerializer
except ImportError: # Activity app is not loaded
pass
class Meta: class Meta:
model = Transaction model = Transaction

View File

@ -8,7 +8,6 @@ from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework import viewsets from rest_framework import viewsets
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import status from rest_framework import status
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
from .serializers import NotePolymorphicSerializer, AliasSerializer, TemplateCategorySerializer, \ from .serializers import NotePolymorphicSerializer, AliasSerializer, TemplateCategorySerializer, \
@ -25,7 +24,8 @@ class NotePolymorphicViewSet(ReadOnlyProtectedModelViewSet):
""" """
queryset = Note.objects.all() queryset = Note.objects.all()
serializer_class = NotePolymorphicSerializer serializer_class = NotePolymorphicSerializer
filter_backends = [SearchFilter, OrderingFilter] filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = ['polymorphic_ctype', 'is_active', ]
search_fields = ['$alias__normalized_name', '$alias__name', '$polymorphic_ctype__model', ] search_fields = ['$alias__normalized_name', '$alias__name', '$polymorphic_ctype__model', ]
ordering_fields = ['alias__name', 'alias__normalized_name'] ordering_fields = ['alias__name', 'alias__normalized_name']
@ -60,7 +60,7 @@ class AliasViewSet(ReadProtectedModelViewSet):
def get_serializer_class(self): def get_serializer_class(self):
serializer_class = self.serializer_class serializer_class = self.serializer_class
if self.request.method in ['PUT', 'PATCH']: if self.request.method in ['PUT', 'PATCH']:
#alias owner cannot be change once establish # alias owner cannot be change once establish
setattr(serializer_class.Meta, 'read_only_fields', ('note',)) setattr(serializer_class.Meta, 'read_only_fields', ('note',))
return serializer_class return serializer_class
@ -70,7 +70,7 @@ class AliasViewSet(ReadProtectedModelViewSet):
self.perform_destroy(instance) self.perform_destroy(instance)
except ValidationError as e: except ValidationError as e:
print(e) print(e)
return Response({e.code:e.message},status.HTTP_400_BAD_REQUEST) return Response({e.code: e.message}, status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
def get_queryset(self): def get_queryset(self):

View File

@ -1,12 +1,12 @@
# 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 dal import autocomplete
from django import forms from django import forms
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note_kfet.inputs import Autocomplete
from .models import Alias from .models import TransactionTemplate, NoteClub
from .models import TransactionTemplate
class ImageForm(forms.Form): class ImageForm(forms.Form):
@ -31,11 +31,14 @@ class TransactionTemplateForm(forms.ModelForm):
# forward=(forward.Const('TYPE', 'note_type') où TYPE est dans {user, club, special} # forward=(forward.Const('TYPE', 'note_type') où TYPE est dans {user, club, special}
widgets = { widgets = {
'destination': 'destination':
autocomplete.ModelSelect2( Autocomplete(
url='note:note_autocomplete', NoteClub,
attrs={ attrs={
'data-placeholder': 'Note ...', 'api_url': '/api/note/note/',
'data-minimum-input-length': 1, # We don't evaluate the content type at launch because the DB might be not initialized
'api_url_suffix':
lambda: '&polymorphic_ctype=' + str(ContentType.objects.get_for_model(NoteClub).pk),
'placeholder': 'Note ...',
}, },
), ),
} }

View File

@ -242,9 +242,9 @@ class Alias(models.Model):
pass pass
self.normalized_name = normalized_name self.normalized_name = normalized_name
def save(self,*args,**kwargs): def save(self, *args, **kwargs):
self.normalized_name = self.normalize(self.name) self.normalized_name = self.normalize(self.name)
super().save(*args,**kwargs) super().save(*args, **kwargs)
def delete(self, using=None, keep_parents=False): def delete(self, using=None, keep_parents=False):
if self.name == str(self.note): if self.name == str(self.note):

View File

@ -2,7 +2,6 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.db import models from django.db import models
from django.db.models import F
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _

View File

@ -106,9 +106,8 @@ DELETE_TEMPLATE = """
class AliasTable(tables.Table): class AliasTable(tables.Table):
class Meta: class Meta:
attrs = { attrs = {
'class': 'class': 'table table condensed table-striped table-hover',
'table table condensed table-striped table-hover', 'id': "alias_table"
'id':"alias_table"
} }
model = Alias model = Alias
fields = ('name',) fields = ('name',)
@ -122,7 +121,6 @@ class AliasTable(tables.Table):
attrs={'td': {'class': 'col-sm-1'}}) attrs={'td': {'class': 'col-sm-1'}})
class ButtonTable(tables.Table): class ButtonTable(tables.Table):
class Meta: class Meta:
attrs = { attrs = {

View File

@ -18,10 +18,5 @@ def pretty_money(value):
) )
def cents_to_euros(value):
return "{:.02f}".format(value / 100) if value else ""
register = template.Library() register = template.Library()
register.filter('pretty_money', pretty_money) register.filter('pretty_money', pretty_money)
register.filter('cents_to_euros', cents_to_euros)

View File

@ -4,7 +4,6 @@
from django.urls import path from django.urls import path
from . import views from . import views
from .models import Note
app_name = 'note' app_name = 'note'
urlpatterns = [ urlpatterns = [
@ -13,7 +12,4 @@ urlpatterns = [
path('buttons/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'), path('buttons/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'),
path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'), path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'),
path('consos/', views.ConsoView.as_view(), name='consos'), path('consos/', views.ConsoView.as_view(), name='consos'),
# API for the note autocompleter
path('note-autocomplete/', views.NoteAutocomplete.as_view(model=Note), name='note_autocomplete'),
] ]

View File

@ -1,18 +1,17 @@
# 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 dal import autocomplete
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views.generic import CreateView, UpdateView from django.views.generic import CreateView, UpdateView
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
from django.urls import reverse_lazy from django.urls import reverse_lazy
from note_kfet.inputs import AmountInput
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from .forms import TransactionTemplateForm from .forms import TransactionTemplateForm
from .models import Transaction, TransactionTemplate, Alias, RecurrentTransaction, NoteSpecial from .models import Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial
from .models.transactions import SpecialTransaction from .models.transactions import SpecialTransaction
from .tables import HistoryTable, ButtonTable from .tables import HistoryTable, ButtonTable
@ -40,6 +39,7 @@ class TransactionCreateView(LoginRequiredMixin, SingleTableView):
""" """
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['title'] = _('Transfer money') context['title'] = _('Transfer money')
context['amount_widget'] = AmountInput(attrs={"id": "amount"})
context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk
context['special_polymorphic_ctype'] = ContentType.objects.get_for_model(SpecialTransaction).pk context['special_polymorphic_ctype'] = ContentType.objects.get_for_model(SpecialTransaction).pk
context['special_types'] = NoteSpecial.objects.order_by("special_type").all() context['special_types'] = NoteSpecial.objects.order_by("special_type").all()
@ -47,62 +47,6 @@ class TransactionCreateView(LoginRequiredMixin, SingleTableView):
return context return context
class NoteAutocomplete(autocomplete.Select2QuerySetView):
"""
Auto complete note by aliases. Used in every search field for note
ex: :view:`ConsoView`, :view:`TransactionCreateView`
"""
def get_queryset(self):
"""
When someone look for an :models:`note.Alias`, a query is sent to the dedicated API.
This function handles the result and return a filtered list of aliases.
"""
# Un utilisateur non connecté n'a accès à aucune information
if not self.request.user.is_authenticated:
return Alias.objects.none()
qs = Alias.objects.all()
# self.q est le paramètre de la recherche
if self.q:
qs = qs.filter(Q(name__regex="^" + self.q) | Q(normalized_name__regex="^" + Alias.normalize(self.q))) \
.order_by('normalized_name').distinct()
# Filtrage par type de note (user, club, special)
note_type = self.forwarded.get("note_type", None)
if note_type:
types = str(note_type).lower()
if "user" in types:
qs = qs.filter(note__polymorphic_ctype__model="noteuser")
elif "club" in types:
qs = qs.filter(note__polymorphic_ctype__model="noteclub")
elif "special" in types:
qs = qs.filter(note__polymorphic_ctype__model="notespecial")
else:
qs = qs.none()
return qs
def get_result_label(self, result):
"""
Show the selected alias and the username associated
<Alias> (aka. <Username> )
"""
# Gère l'affichage de l'alias dans la recherche
res = result.name
note_name = str(result.note)
if res != note_name:
res += " (aka. " + note_name + ")"
return res
def get_result_value(self, result):
"""
The value used for the transactions will be the id of the Note.
"""
return str(result.note.pk)
class TransactionTemplateCreateView(LoginRequiredMixin, CreateView): class TransactionTemplateCreateView(LoginRequiredMixin, CreateView):
""" """
Create TransactionTemplate Create TransactionTemplate

View File

@ -55,6 +55,20 @@
"name": "Tr\u00e9sorier\u00b7\u00e8re de club" "name": "Tr\u00e9sorier\u00b7\u00e8re de club"
} }
}, },
{
"model": "member.role",
"pk": 8,
"fields": {
"name": "Tr\u00e9sorier\u00b7\u00e8re de club"
}
},
{
"model": "member.role",
"pk": 9,
"fields": {
"name": "Res[pot]"
}
},
{ {
"model": "permission.permissionmask", "model": "permission.permissionmask",
"pk": 1, "pk": 1,
@ -574,6 +588,201 @@
"description": "Create any transaction" "description": "Create any transaction"
} }
}, },
{
"model": "permission.permission",
"pk": 34,
"fields": {
"model": [
"activity",
"activity"
],
"query": "[\"OR\", {\"valid\": true}, {\"creater\": [\"user\"]}]",
"type": "view",
"mask": 1,
"field": "",
"description": "View valid activites"
}
},
{
"model": "permission.permission",
"pk": 35,
"fields": {
"model": [
"activity",
"activity"
],
"query": "[\"AND\", {\"valid\": false}, {\"creater\": [\"user\"]}]",
"type": "change",
"mask": 1,
"field": "",
"description": "Change our activities"
}
},
{
"model": "permission.permission",
"pk": 36,
"fields": {
"model": [
"activity",
"activity"
],
"query": "{\"creater\": [\"user\"], \"valid\": false}",
"type": "add",
"mask": 1,
"field": "",
"description": "Add activities"
}
},
{
"model": "permission.permission",
"pk": 37,
"fields": {
"model": [
"activity",
"activity"
],
"query": "{}",
"type": "change",
"mask": 2,
"field": "valid",
"description": "Validate activities"
}
},
{
"model": "permission.permission",
"pk": 38,
"fields": {
"model": [
"activity",
"activity"
],
"query": "{}",
"type": "change",
"mask": 2,
"field": "open",
"description": "Open activities"
}
},
{
"model": "permission.permission",
"pk": 39,
"fields": {
"model": [
"activity",
"guest"
],
"query": "{\"inviter\": [\"user\", \"note\"], \"activity__activity_type__can_invite\": true}",
"type": "add",
"mask": 1,
"field": "",
"description": "Invite people to activities"
}
},
{
"model": "permission.permission",
"pk": 40,
"fields": {
"model": [
"activity",
"guest"
],
"query": "{\"inviter\": [\"user\", \"note\"]}",
"type": "view",
"mask": 1,
"field": "",
"description": "View invited people"
}
},
{
"model": "permission.permission",
"pk": 41,
"fields": {
"model": [
"activity",
"activity"
],
"query": "{}",
"type": "view",
"mask": 2,
"field": "",
"description": "View all activities"
}
},
{
"model": "permission.permission",
"pk": 42,
"fields": {
"model": [
"activity",
"guest"
],
"query": "{}",
"type": "view",
"mask": 2,
"field": "",
"description": "View all invited people"
}
},
{
"model": "permission.permission",
"pk": 43,
"fields": {
"model": [
"activity",
"entry"
],
"query": "{}",
"type": "add",
"mask": 2,
"field": "",
"description": "Manage entries"
}
},
{
"model": "permission.permission",
"pk": 44,
"fields": {
"model": [
"activity",
"guesttransaction"
],
"query": "{}",
"type": "add",
"mask": 2,
"field": "",
"description": "Add invitation transactions"
}
},
{
"model": "permission.permission",
"pk": 45,
"fields": {
"model": [
"activity",
"guesttransaction"
],
"query": "{}",
"type": "view",
"mask": 1,
"field": "",
"description": "View invitation transactions"
}
},
{
"model": "permission.permission",
"pk": 46,
"fields": {
"model": [
"activity",
"guesttransaction"
],
"query": "{}",
"type": "change",
"mask": 2,
"field": "valid",
"description": "Validate invitation transactions"
}
},
{ {
"model": "permission.rolepermissions", "model": "permission.rolepermissions",
"pk": 1, "pk": 1,
@ -613,7 +822,12 @@
15, 15,
16, 16,
17, 17,
18 18,
34,
35,
36,
39,
40
] ]
} }
}, },
@ -649,5 +863,22 @@
33 33
] ]
} }
},
{
"model": "permission.rolepermissions",
"pk": 5,
"fields": {
"role": 9,
"permissions": [
37,
38,
41,
42,
43,
44,
45,
46
]
}
} }
] ]

View File

@ -48,6 +48,11 @@ def not_empty_model_change_list(model_name):
return session.get("not_empty_model_change_list_" + model_name) == 1 return session.get("not_empty_model_change_list_" + model_name) == 1
def has_perm(perm, obj):
return PermissionBackend().has_perm(get_current_authenticated_user(), perm, obj)
register = template.Library() register = template.Library()
register.filter('not_empty_model_list', not_empty_model_list) register.filter('not_empty_model_list', not_empty_model_list)
register.filter('not_empty_model_change_list', not_empty_model_change_list) register.filter('not_empty_model_change_list', not_empty_model_change_list)
register.filter('has_perm', has_perm)

View File

@ -7,6 +7,7 @@ from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit from crispy_forms.layout import Submit
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note_kfet.inputs import DatePickerInput, AmountInput
from .models import Invoice, Product, Remittance, SpecialTransactionProxy from .models import Invoice, Product, Remittance, SpecialTransactionProxy
@ -19,7 +20,7 @@ class InvoiceForm(forms.ModelForm):
# Django forms don't support date fields. We have to add it manually # Django forms don't support date fields. We have to add it manually
date = forms.DateField( date = forms.DateField(
initial=datetime.date.today, initial=datetime.date.today,
widget=forms.TextInput(attrs={'type': 'date'}) widget=DatePickerInput()
) )
def clean_date(self): def clean_date(self):
@ -30,12 +31,21 @@ class InvoiceForm(forms.ModelForm):
exclude = ('bde', ) exclude = ('bde', )
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = '__all__'
widgets = {
"amount": AmountInput()
}
# Add a subform per product in the invoice form, and manage correctly the link between the invoice and # Add a subform per product in the invoice form, and manage correctly the link between the invoice and
# its products. The FormSet will search automatically the ForeignKey in the Product model. # its products. The FormSet will search automatically the ForeignKey in the Product model.
ProductFormSet = forms.inlineformset_factory( ProductFormSet = forms.inlineformset_factory(
Invoice, Invoice,
Product, Product,
fields='__all__', form=ProductForm,
extra=1, extra=1,
) )

View File

@ -50,18 +50,8 @@ class InvoiceCreateView(LoginRequiredMixin, CreateView):
def form_valid(self, form): def form_valid(self, form):
ret = super().form_valid(form) ret = super().form_valid(form)
kwargs = {}
# The user type amounts in cents. We convert it in euros.
for key in self.request.POST:
value = self.request.POST[key]
if key.endswith("amount") and value:
kwargs[key] = str(int(100 * float(value)))
elif value:
kwargs[key] = value
# For each product, we save it # For each product, we save it
formset = ProductFormSet(kwargs, instance=form.instance) formset = ProductFormSet(self.request.POST, instance=form.instance)
if formset.is_valid(): if formset.is_valid():
for f in formset: for f in formset:
# We don't save the product if the designation is not entered, ie. if the line is empty # We don't save the product if the designation is not entered, ie. if the line is empty
@ -112,16 +102,7 @@ class InvoiceUpdateView(LoginRequiredMixin, UpdateView):
def form_valid(self, form): def form_valid(self, form):
ret = super().form_valid(form) ret = super().form_valid(form)
kwargs = {} formset = ProductFormSet(self.request.POST, instance=form.instance)
# The user type amounts in cents. We convert it in euros.
for key in self.request.POST:
value = self.request.POST[key]
if key.endswith("amount") and value:
kwargs[key] = str(int(100 * float(value)))
elif value:
kwargs[key] = value
formset = ProductFormSet(kwargs, instance=form.instance)
saved = [] saved = []
# For each product, we save it # For each product, we save it
if formset.is_valid(): if formset.is_valid():

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-26 14:40+0100\n" "POT-Creation-Date: 2020-03-30 17:31+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"
@ -18,72 +18,182 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: apps/activity/apps.py:10 apps/activity/models.py:76 #: apps/activity/apps.py:10 apps/activity/models.py:111
#: apps/activity/models.py:120
msgid "activity" msgid "activity"
msgstr "" msgstr ""
#: apps/activity/models.py:19 apps/activity/models.py:44 #: apps/activity/forms.py:45 apps/activity/models.py:217
#: apps/member/models.py:63 apps/member/models.py:114 msgid "You can't invite someone once the activity is started."
#: apps/note/models/notes.py:188 apps/note/models/transactions.py:25 msgstr ""
#: apps/note/models/transactions.py:45 apps/note/models/transactions.py:232
#: templates/member/profile_detail.html:15 #: apps/activity/forms.py:48 apps/activity/models.py:220
msgid "This activity is not validated yet."
msgstr ""
#: apps/activity/forms.py:58 apps/activity/models.py:228
msgid "This person has been already invited 5 times this year."
msgstr ""
#: apps/activity/forms.py:62 apps/activity/models.py:232
msgid "This person is already invited."
msgstr ""
#: apps/activity/forms.py:66 apps/activity/models.py:236
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:64 apps/member/models.py:122
#: apps/note/models/notes.py:188 apps/note/models/transactions.py:24
#: apps/note/models/transactions.py:44 apps/note/models/transactions.py:231
#: templates/member/club_info.html:13 templates/member/profile_info.html:14
msgid "name" msgid "name"
msgstr "" msgstr ""
#: apps/activity/models.py:23 #: apps/activity/models.py:27 templates/activity/activity_detail.html:39
msgid "can invite" msgid "can invite"
msgstr "" msgstr ""
#: apps/activity/models.py:26 #: apps/activity/models.py:30 templates/activity/activity_detail.html:43
msgid "guest entry fee" msgid "guest entry fee"
msgstr "" msgstr ""
#: apps/activity/models.py:30 #: apps/activity/models.py:34
msgid "activity type" msgid "activity type"
msgstr "" msgstr ""
#: apps/activity/models.py:31 #: apps/activity/models.py:35
msgid "activity types" msgid "activity types"
msgstr "" msgstr ""
#: apps/activity/models.py:48 apps/note/models/transactions.py:70 #: apps/activity/models.py:53 apps/note/models/transactions.py:69
#: apps/permission/models.py:91 #: apps/permission/models.py:90 templates/activity/activity_detail.html:16
msgid "description" msgid "description"
msgstr "" msgstr ""
#: apps/activity/models.py:54 apps/note/models/notes.py:164 #: apps/activity/models.py:60 apps/note/models/notes.py:164
#: apps/note/models/transactions.py:63 #: apps/note/models/transactions.py:62
#: templates/activity/activity_detail.html:19
msgid "type" msgid "type"
msgstr "" msgstr ""
#: apps/activity/models.py:60 #: apps/activity/models.py:66 apps/logs/models.py:21
#: apps/note/models/notes.py:117
msgid "user"
msgstr ""
#: apps/activity/models.py:73 templates/activity/activity_detail.html:33
msgid "organizer" msgid "organizer"
msgstr "" msgstr ""
#: apps/activity/models.py:66 #: 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
msgid "attendees club" msgid "attendees club"
msgstr "" msgstr ""
#: apps/activity/models.py:69 #: apps/activity/models.py:93 templates/activity/activity_detail.html:22
msgid "start date" msgid "start date"
msgstr "" msgstr ""
#: apps/activity/models.py:72 #: apps/activity/models.py:97 templates/activity/activity_detail.html:25
msgid "end date" msgid "end date"
msgstr "" msgstr ""
#: apps/activity/models.py:77 #: apps/activity/models.py:102 apps/note/models/transactions.py:134
#: templates/activity/activity_detail.html:47
msgid "valid"
msgstr ""
#: apps/activity/models.py:107 templates/activity/activity_detail.html:61
msgid "open"
msgstr ""
#: apps/activity/models.py:112
msgid "activities" msgid "activities"
msgstr "" msgstr ""
#: apps/activity/models.py:108 #: apps/activity/models.py:125
msgid "entry time"
msgstr ""
#: apps/activity/models.py:148
msgid "Already entered on "
msgstr ""
#: apps/activity/models.py:148 apps/activity/tables.py:54
msgid "{:%Y-%m-%d %H:%M:%S}"
msgstr ""
#: apps/activity/models.py:156
msgid "The balance is negative."
msgstr ""
#: apps/activity/models.py:188
msgid "last name"
msgstr ""
#: apps/activity/models.py:193 templates/member/profile_info.html:14
msgid "first name"
msgstr ""
#: apps/activity/models.py:200
msgid "inviter"
msgstr ""
#: apps/activity/models.py:241
msgid "guest" msgid "guest"
msgstr "" msgstr ""
#: apps/activity/models.py:109 #: apps/activity/models.py:242
msgid "guests" msgid "guests"
msgstr "" msgstr ""
#: apps/activity/models.py:254
msgid "Invitation"
msgstr ""
#: apps/activity/tables.py:54
msgid "Entered on "
msgstr ""
#: apps/activity/tables.py:55
msgid "remove"
msgstr ""
#: apps/activity/tables.py:75 apps/treasury/models.py:126
msgid "Type"
msgstr ""
#: apps/activity/tables.py:77 apps/treasury/forms.py:120
msgid "Last name"
msgstr ""
#: apps/activity/tables.py:79 apps/treasury/forms.py:122
#: templates/note/transaction_form.html:92
msgid "First name"
msgstr ""
#: apps/activity/tables.py:81 apps/note/models/notes.py:67
msgid "Note"
msgstr ""
#: apps/activity/tables.py:83
msgid "Balance"
msgstr ""
#: apps/activity/views.py:44 templates/base.html:94
msgid "Activities"
msgstr ""
#: apps/activity/views.py:149
msgid "Entry for activity \"{}\""
msgstr ""
#: apps/api/apps.py:10 #: apps/api/apps.py:10
msgid "API" msgid "API"
msgstr "" msgstr ""
@ -92,10 +202,6 @@ msgstr ""
msgid "Logs" msgid "Logs"
msgstr "" msgstr ""
#: apps/logs/models.py:21 apps/note/models/notes.py:117
msgid "user"
msgstr ""
#: apps/logs/models.py:27 #: apps/logs/models.py:27
msgid "IP Address" msgid "IP Address"
msgstr "" msgstr ""
@ -120,11 +226,12 @@ msgstr ""
msgid "create" msgid "create"
msgstr "" msgstr ""
#: apps/logs/models.py:61 apps/note/tables.py:147 #: apps/logs/models.py:61 apps/note/tables.py:142
#: templates/activity/activity_detail.html:67
msgid "edit" msgid "edit"
msgstr "" msgstr ""
#: apps/logs/models.py:62 apps/note/tables.py:151 #: apps/logs/models.py:62 apps/note/tables.py:120 apps/note/tables.py:146
msgid "delete" msgid "delete"
msgstr "" msgstr ""
@ -144,139 +251,130 @@ msgstr ""
msgid "member" msgid "member"
msgstr "" msgstr ""
#: apps/member/models.py:25 #: apps/member/models.py:26
msgid "phone number" msgid "phone number"
msgstr "" msgstr ""
#: apps/member/models.py:31 templates/member/profile_detail.html:28 #: apps/member/models.py:32 templates/member/profile_info.html:27
msgid "section" msgid "section"
msgstr "" msgstr ""
#: apps/member/models.py:32 #: apps/member/models.py:33
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
msgstr "" msgstr ""
#: apps/member/models.py:38 templates/member/profile_detail.html:31 #: apps/member/models.py:39 templates/member/profile_info.html:30
msgid "address" msgid "address"
msgstr "" msgstr ""
#: apps/member/models.py:44 #: apps/member/models.py:45
msgid "paid" msgid "paid"
msgstr "" msgstr ""
#: apps/member/models.py:49 apps/member/models.py:50 #: apps/member/models.py:50 apps/member/models.py:51
msgid "user profile" msgid "user profile"
msgstr "" msgstr ""
#: apps/member/models.py:68 #: apps/member/models.py:69 templates/member/club_info.html:36
msgid "email" msgid "email"
msgstr "" msgstr ""
#: apps/member/models.py:73 #: apps/member/models.py:76
msgid "parent club"
msgstr ""
#: apps/member/models.py:81 templates/member/club_info.html:30
msgid "membership fee" msgid "membership fee"
msgstr "" msgstr ""
#: apps/member/models.py:77 #: apps/member/models.py:85 templates/member/club_info.html:27
msgid "membership duration" msgid "membership duration"
msgstr "" msgstr ""
#: apps/member/models.py:78 #: apps/member/models.py:86
msgid "The longest time a membership can last (NULL = infinite)." msgid "The longest time a membership can last (NULL = infinite)."
msgstr "" msgstr ""
#: apps/member/models.py:83 #: apps/member/models.py:91 templates/member/club_info.html:21
msgid "membership start" msgid "membership start"
msgstr "" msgstr ""
#: apps/member/models.py:84 #: apps/member/models.py:92
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 ""
#: apps/member/models.py:89 #: apps/member/models.py:97 templates/member/club_info.html:24
msgid "membership end" msgid "membership end"
msgstr "" msgstr ""
#: apps/member/models.py:90 #: apps/member/models.py:98
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:96 apps/note/models/notes.py:139 #: apps/member/models.py:104 apps/note/models/notes.py:139
msgid "club" msgid "club"
msgstr "" msgstr ""
#: apps/member/models.py:97 #: apps/member/models.py:105
msgid "clubs" msgid "clubs"
msgstr "" msgstr ""
#: apps/member/models.py:120 apps/permission/models.py:276 #: apps/member/models.py:128 apps/permission/models.py:275
msgid "role" msgid "role"
msgstr "" msgstr ""
#: apps/member/models.py:121 #: apps/member/models.py:129
msgid "roles" msgid "roles"
msgstr "" msgstr ""
#: apps/member/models.py:145 #: apps/member/models.py:153
msgid "membership starts on" msgid "membership starts on"
msgstr "" msgstr ""
#: apps/member/models.py:148 #: apps/member/models.py:156
msgid "membership ends on" msgid "membership ends on"
msgstr "" msgstr ""
#: apps/member/models.py:152 #: apps/member/models.py:160
msgid "fee" msgid "fee"
msgstr "" msgstr ""
#: apps/member/models.py:162 #: apps/member/models.py:172
msgid "User is not a member of the parent club"
msgstr ""
#: apps/member/models.py:176
msgid "membership" msgid "membership"
msgstr "" msgstr ""
#: apps/member/models.py:163 #: apps/member/models.py:177
msgid "memberships" msgid "memberships"
msgstr "" msgstr ""
#: apps/member/views.py:80 templates/member/profile_detail.html:46 #: apps/member/views.py:76 templates/member/profile_info.html:45
msgid "Update Profile" msgid "Update Profile"
msgstr "" msgstr ""
#: apps/member/views.py:93 #: apps/member/views.py:89
msgid "An alias with a similar name already exists." msgid "An alias with a similar name already exists."
msgstr "" msgstr ""
#: apps/member/views.py:146 #: apps/note/admin.py:120 apps/note/models/transactions.py:94
#, python-format
msgid "Account #%(id)s: %(username)s"
msgstr ""
#: apps/member/views.py:216
msgid "Alias successfully deleted"
msgstr ""
#: apps/note/admin.py:120 apps/note/models/transactions.py:95
msgid "source" msgid "source"
msgstr "" msgstr ""
#: apps/note/admin.py:128 apps/note/admin.py:156 #: apps/note/admin.py:128 apps/note/admin.py:156
#: apps/note/models/transactions.py:54 apps/note/models/transactions.py:108 #: apps/note/models/transactions.py:53 apps/note/models/transactions.py:107
msgid "destination" msgid "destination"
msgstr "" msgstr ""
#: apps/note/apps.py:14 apps/note/models/notes.py:58 #: apps/note/forms.py:14
msgid "note"
msgstr ""
#: apps/note/forms.py:20
msgid "New Alias"
msgstr ""
#: apps/note/forms.py:25
msgid "select an image" msgid "select an image"
msgstr "" msgstr ""
#: apps/note/forms.py:26 #: apps/note/forms.py:15
msgid "Maximal size: 2MB" msgid "Maximal size: 2MB"
msgstr "" msgstr ""
@ -310,7 +408,7 @@ msgstr ""
msgid "display image" msgid "display image"
msgstr "" msgstr ""
#: apps/note/models/notes.py:53 apps/note/models/transactions.py:118 #: apps/note/models/notes.py:53 apps/note/models/transactions.py:117
msgid "created at" msgid "created at"
msgstr "" msgstr ""
@ -318,10 +416,6 @@ msgstr ""
msgid "notes" msgid "notes"
msgstr "" msgstr ""
#: apps/note/models/notes.py:67
msgid "Note"
msgstr ""
#: apps/note/models/notes.py:77 apps/note/models/notes.py:101 #: 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 ""
@ -368,7 +462,8 @@ msgstr ""
msgid "alias" msgid "alias"
msgstr "" msgstr ""
#: apps/note/models/notes.py:211 templates/member/profile_detail.html:37 #: apps/note/models/notes.py:211 templates/member/club_info.html:33
#: templates/member/profile_info.html:36
msgid "aliases" msgid "aliases"
msgstr "" msgstr ""
@ -380,106 +475,114 @@ msgstr ""
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:247 #: apps/note/models/notes.py:251
msgid "You can't delete your main alias." msgid "You can't delete your main alias."
msgstr "" msgstr ""
#: apps/note/models/transactions.py:31 #: apps/note/models/transactions.py:30
msgid "transaction category" msgid "transaction category"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:32 #: apps/note/models/transactions.py:31
msgid "transaction categories" msgid "transaction categories"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:48 #: apps/note/models/transactions.py:47
msgid "A template with this name already exist" msgid "A template with this name already exist"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:57 apps/note/models/transactions.py:126 #: apps/note/models/transactions.py:56 apps/note/models/transactions.py:125
msgid "amount" msgid "amount"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:58 #: apps/note/models/transactions.py:57
msgid "in centimes" msgid "in centimes"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:76 #: apps/note/models/transactions.py:75
msgid "transaction template" msgid "transaction template"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:77 #: apps/note/models/transactions.py:76
msgid "transaction templates" msgid "transaction templates"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:101 apps/note/models/transactions.py:114 #: apps/note/models/transactions.py:100 apps/note/models/transactions.py:113
#: apps/note/tables.py:33 apps/note/tables.py:42 #: apps/note/tables.py:33 apps/note/tables.py:42
msgid "used alias" msgid "used alias"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:122 #: apps/note/models/transactions.py:121
msgid "quantity" msgid "quantity"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:130 #: apps/note/models/transactions.py:129
msgid "reason" msgid "reason"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:135 #: apps/note/models/transactions.py:139 apps/note/tables.py:95
msgid "valid"
msgstr ""
#: apps/note/models/transactions.py:140 apps/note/tables.py:95
msgid "invalidity reason" msgid "invalidity reason"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:147 #: apps/note/models/transactions.py:146
msgid "transaction" msgid "transaction"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:148 #: apps/note/models/transactions.py:147
msgid "transactions" msgid "transactions"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:202 templates/base.html:83 #: apps/note/models/transactions.py:201 templates/base.html:84
#: templates/note/transaction_form.html:19 #: templates/note/transaction_form.html:19
#: templates/note/transaction_form.html:145 #: templates/note/transaction_form.html:140
msgid "Transfer" msgid "Transfer"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:188 #: apps/note/models/transactions.py:221
msgid "Template" msgid "Template"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:203 #: apps/note/models/transactions.py:236
msgid "first_name" msgid "first_name"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:208 #: apps/note/models/transactions.py:241
msgid "bank" msgid "bank"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:214 templates/note/transaction_form.html:24 #: apps/note/models/transactions.py:247 templates/note/transaction_form.html:24
msgid "Credit" msgid "Credit"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:214 templates/note/transaction_form.html:28 #: apps/note/models/transactions.py:247 templates/note/transaction_form.html:28
msgid "Debit" msgid "Debit"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:230 apps/note/models/transactions.py:235 #: apps/note/models/transactions.py:263 apps/note/models/transactions.py:268
msgid "membership transaction" msgid "membership transaction"
msgstr "" msgstr ""
#: apps/note/models/transactions.py:231 #: apps/note/models/transactions.py:264
msgid "membership transactions" msgid "membership transactions"
msgstr "" msgstr ""
#: apps/note/views.py:39 #: apps/note/tables.py:57
msgid "Click to invalidate"
msgstr ""
#: apps/note/tables.py:57
msgid "Click to validate"
msgstr ""
#: apps/note/tables.py:93
msgid "No reason specified"
msgstr ""
#: apps/note/views.py:41
msgid "Transfer money" msgid "Transfer money"
msgstr "" msgstr ""
#: apps/note/views.py:145 templates/base.html:79 #: apps/note/views.py:102 templates/base.html:79
msgid "Consumptions" msgid "Consumptions"
msgstr "" msgstr ""
@ -501,41 +604,35 @@ msgstr ""
msgid "Specifying field applies only to view and change permission types." msgid "Specifying field applies only to view and change permission types."
msgstr "" msgstr ""
#: apps/treasury/apps.py:11 templates/base.html:102 #: apps/treasury/apps.py:12 templates/base.html:99
msgid "Treasury" msgid "Treasury"
msgstr "" msgstr ""
#: apps/treasury/forms.py:56 apps/treasury/forms.py:95 #: apps/treasury/forms.py:84 apps/treasury/forms.py:132
#: templates/activity/activity_form.html:9
#: 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:10 templates/treasury/invoice_form.html:47 #: templates/member/club_form.html:9 templates/treasury/invoice_form.html:46
msgid "Submit" msgid "Submit"
msgstr "" msgstr ""
#: apps/treasury/forms.py:58 #: apps/treasury/forms.py:86
msgid "Close" msgid "Close"
msgstr "" msgstr ""
#: apps/treasury/forms.py:65 #: apps/treasury/forms.py:95
msgid "Remittance is already closed." msgid "Remittance is already closed."
msgstr "" msgstr ""
#: apps/treasury/forms.py:70 #: apps/treasury/forms.py:100
msgid "You can't change the type of the remittance." msgid "You can't change the type of the remittance."
msgstr "" msgstr ""
#: apps/treasury/forms.py:84 #: apps/treasury/forms.py:124 templates/note/transaction_form.html:98
msgid "Last name"
msgstr ""
#: apps/treasury/forms.py:86 templates/note/transaction_form.html:92
msgid "First name"
msgstr ""
#: apps/treasury/forms.py:88 templates/note/transaction_form.html:98
msgid "Bank" msgid "Bank"
msgstr "" msgstr ""
#: apps/treasury/forms.py:90 apps/treasury/tables.py:40 #: apps/treasury/forms.py:126 apps/treasury/tables.py:47
#: templates/note/transaction_form.html:128 #: templates/note/transaction_form.html:128
#: templates/treasury/remittance_form.html:18 #: templates/treasury/remittance_form.html:18
msgid "Amount" msgid "Amount"
@ -589,10 +686,6 @@ msgstr ""
msgid "Date" msgid "Date"
msgstr "" msgstr ""
#: apps/treasury/models.py:126
msgid "Type"
msgstr ""
#: apps/treasury/models.py:131 #: apps/treasury/models.py:131
msgid "Comment" msgid "Comment"
msgstr "" msgstr ""
@ -601,38 +694,38 @@ msgstr ""
msgid "Closed" msgid "Closed"
msgstr "" msgstr ""
#: apps/treasury/models.py:159 #: apps/treasury/models.py:169
msgid "Remittance #{:d}: {}" msgid "Remittance #{:d}: {}"
msgstr "" msgstr ""
#: apps/treasury/models.py:178 apps/treasury/tables.py:64 #: apps/treasury/models.py:188 apps/treasury/tables.py:76
#: apps/treasury/tables.py:72 templates/treasury/invoice_list.html:13 #: apps/treasury/tables.py:84 templates/treasury/invoice_list.html:13
#: templates/treasury/remittance_list.html:13 #: templates/treasury/remittance_list.html:13
msgid "Remittance" msgid "Remittance"
msgstr "" msgstr ""
#: apps/treasury/tables.py:16 #: apps/treasury/tables.py:19
msgid "Invoice #{:d}" msgid "Invoice #{:d}"
msgstr "" msgstr ""
#: apps/treasury/tables.py:19 templates/treasury/invoice_list.html:10 #: apps/treasury/tables.py:22 templates/treasury/invoice_list.html:10
#: templates/treasury/remittance_list.html:10 #: templates/treasury/remittance_list.html:10
msgid "Invoice" msgid "Invoice"
msgstr "" msgstr ""
#: apps/treasury/tables.py:38 #: apps/treasury/tables.py:45
msgid "Transaction count" msgid "Transaction count"
msgstr "" msgstr ""
#: apps/treasury/tables.py:43 apps/treasury/tables.py:45 #: apps/treasury/tables.py:50 apps/treasury/tables.py:52
msgid "View" msgid "View"
msgstr "" msgstr ""
#: apps/treasury/tables.py:66 #: apps/treasury/tables.py:78
msgid "Add" msgid "Add"
msgstr "" msgstr ""
#: apps/treasury/tables.py:74 #: apps/treasury/tables.py:86
msgid "Remove" msgid "Remove"
msgstr "" msgstr ""
@ -643,30 +736,86 @@ msgid ""
"again unless your session expires or you logout." "again unless your session expires or you logout."
msgstr "" msgstr ""
#: note_kfet/settings/base.py:151 #: note_kfet/settings/base.py:152
msgid "German" msgid "German"
msgstr "" msgstr ""
#: note_kfet/settings/base.py:152 #: note_kfet/settings/base.py:153
msgid "English" msgid "English"
msgstr "" msgstr ""
#: note_kfet/settings/base.py:153 #: note_kfet/settings/base.py:154
msgid "French" msgid "French"
msgstr "" msgstr ""
#: templates/activity/activity_detail.html:29
msgid "creater"
msgstr ""
#: templates/activity/activity_detail.html:50
msgid "opened"
msgstr ""
#: templates/activity/activity_detail.html:57
msgid "Entry page"
msgstr ""
#: templates/activity/activity_detail.html:61
msgid "close"
msgstr ""
#: templates/activity/activity_detail.html:64
msgid "invalidate"
msgstr ""
#: templates/activity/activity_detail.html:64
msgid "validate"
msgstr ""
#: templates/activity/activity_detail.html:70
msgid "Invite"
msgstr ""
#: templates/activity/activity_detail.html:77
msgid "Guests list"
msgstr ""
#: templates/activity/activity_entry.html:10
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 ""
#: templates/activity/activity_list.html:10
msgid "There is no planned activity."
msgstr ""
#: templates/activity/activity_list.html:14
msgid "New activity"
msgstr ""
#: templates/activity/activity_list.html:18
msgid "All activities"
msgstr ""
#: templates/base.html:13 #: templates/base.html:13
msgid "The ENS Paris-Saclay BDE note." msgid "The ENS Paris-Saclay BDE note."
msgstr "" msgstr ""
#: templates/base.html:87 #: templates/base.html:89
msgid "Clubs" msgid "Clubs"
msgstr "" msgstr ""
#: templates/base.html:92
msgid "Activities"
msgstr ""
#: templates/cas_server/base.html:7 #: templates/cas_server/base.html:7
msgid "Central Authentication Service" msgid "Central Authentication Service"
msgstr "" msgstr ""
@ -722,32 +871,48 @@ msgstr ""
msgid "Field filters" msgid "Field filters"
msgstr "" msgstr ""
#: templates/member/club_detail.html:10 #: templates/member/alias_update.html:5
msgid "Membership starts on" msgid "Add alias"
msgstr "" msgstr ""
#: templates/member/club_detail.html:12 #: templates/member/club_info.html:17
msgid "Membership ends on" msgid "Club Parent"
msgstr "" msgstr ""
#: templates/member/club_detail.html:14 #: templates/member/club_info.html:41
msgid "Membership duration" msgid "Add member"
msgstr "" msgstr ""
#: templates/member/club_detail.html:18 templates/member/profile_detail.html:34 #: templates/member/club_info.html:42 templates/note/conso_form.html:121
msgid "balance" msgid "Edit"
msgstr "" msgstr ""
#: templates/member/club_detail.html:51 templates/member/profile_detail.html:75 #: templates/member/club_info.html:43
msgid "Transaction history" msgid "Add roles"
msgstr "" msgstr ""
#: templates/member/club_form.html:6 #: templates/member/club_info.html:46 templates/member/profile_info.html:48
msgid "Clubs list" msgid "View Profile"
msgstr "" msgstr ""
#: templates/member/club_list.html:8 #: templates/member/club_list.html:8
msgid "New club" msgid "search clubs"
msgstr ""
#: templates/member/club_list.html:12
msgid "Créer un club"
msgstr ""
#: templates/member/club_list.html:19
msgid "club listing "
msgstr ""
#: templates/member/club_tables.html:9
msgid "Member of the Club"
msgstr ""
#: templates/member/club_tables.html:22 templates/member/profile_tables.html:22
msgid "Transaction history"
msgstr "" msgstr ""
#: templates/member/manage_auth_tokens.html:16 #: templates/member/manage_auth_tokens.html:16
@ -762,35 +927,31 @@ msgstr ""
msgid "Regenerate token" msgid "Regenerate token"
msgstr "" msgstr ""
#: templates/member/profile_alias.html:10 #: templates/member/profile_info.html:5
msgid "Add alias" msgid "Account #"
msgstr "" msgstr ""
#: templates/member/profile_detail.html:15 #: templates/member/profile_info.html:17
msgid "first name"
msgstr ""
#: templates/member/profile_detail.html:18
msgid "username" msgid "username"
msgstr "" msgstr ""
#: templates/member/profile_detail.html:21 #: templates/member/profile_info.html:20
msgid "password" msgid "password"
msgstr "" msgstr ""
#: templates/member/profile_detail.html:24 #: templates/member/profile_info.html:23
msgid "Change password" msgid "Change password"
msgstr "" msgstr ""
#: templates/member/profile_detail.html:42 #: templates/member/profile_info.html:33
msgid "balance"
msgstr ""
#: templates/member/profile_info.html:41
msgid "Manage auth token" msgid "Manage auth token"
msgstr "" msgstr ""
#: templates/member/profile_detail.html:49 #: templates/member/profile_tables.html:9
msgid "View Profile"
msgstr ""
#: templates/member/profile_detail.html:62
msgid "View my memberships" msgid "View my memberships"
msgstr "" msgstr ""
@ -819,10 +980,6 @@ msgstr ""
msgid "Most used buttons" msgid "Most used buttons"
msgstr "" msgstr ""
#: templates/note/conso_form.html:121
msgid "Edit"
msgstr ""
#: templates/note/conso_form.html:126 #: templates/note/conso_form.html:126
msgid "Single consumptions" msgid "Single consumptions"
msgstr "" msgstr ""
@ -831,7 +988,7 @@ msgstr ""
msgid "Double consumptions" msgid "Double consumptions"
msgstr "" msgstr ""
#: templates/note/conso_form.html:141 templates/note/transaction_form.html:152 #: templates/note/conso_form.html:141 templates/note/transaction_form.html:147
msgid "Recent transactions history" msgid "Recent transactions history"
msgstr "" msgstr ""
@ -847,37 +1004,21 @@ msgstr ""
msgid "Transfer type" msgid "Transfer type"
msgstr "" msgstr ""
#: templates/note/transaction_form.html:86
msgid "Name"
msgstr ""
#: templates/note/transaction_form.html:92
msgid "First name"
msgstr ""
#: templates/note/transaction_form.html:98
msgid "Bank"
msgstr ""
#: templates/note/transaction_form.html:111 #: templates/note/transaction_form.html:111
#: templates/note/transaction_form.html:169 #: templates/note/transaction_form.html:164
#: templates/note/transaction_form.html:176 #: templates/note/transaction_form.html:171
msgid "Select receivers" msgid "Select receivers"
msgstr "" msgstr ""
#: templates/note/transaction_form.html:128 #: templates/note/transaction_form.html:133
msgid "Amount"
msgstr ""
#: templates/note/transaction_form.html:138
msgid "Reason" msgid "Reason"
msgstr "" msgstr ""
#: templates/note/transaction_form.html:183 #: templates/note/transaction_form.html:178
msgid "Credit note" msgid "Credit note"
msgstr "" msgstr ""
#: templates/note/transaction_form.html:190 #: templates/note/transaction_form.html:185
msgid "Debit note" msgid "Debit note"
msgstr "" msgstr ""
@ -889,6 +1030,10 @@ msgstr ""
msgid "search button" msgid "search button"
msgstr "" msgstr ""
#: templates/note/transactiontemplate_list.html:13
msgid "New button"
msgstr ""
#: templates/note/transactiontemplate_list.html:20 #: templates/note/transactiontemplate_list.html:20
msgid "buttons listing " msgid "buttons listing "
msgstr "" msgstr ""
@ -991,11 +1136,11 @@ msgstr ""
msgid "Invoices list" msgid "Invoices list"
msgstr "" msgstr ""
#: templates/treasury/invoice_form.html:42 #: templates/treasury/invoice_form.html:41
msgid "Add product" msgid "Add product"
msgstr "" msgstr ""
#: templates/treasury/invoice_form.html:43 #: templates/treasury/invoice_form.html:42
msgid "Remove product" msgid "Remove product"
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-26 14:40+0100\n" "POT-Creation-Date: 2020-03-30 17:31+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"
@ -13,83 +13,189 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: apps/activity/apps.py:10 apps/activity/models.py:76 #: apps/activity/apps.py:10 apps/activity/models.py:111
#: apps/activity/models.py:120
msgid "activity" msgid "activity"
msgstr "activité" msgstr "activité"
#: apps/activity/models.py:19 apps/activity/models.py:44 #: apps/activity/forms.py:45 apps/activity/models.py:217
#: apps/member/models.py:63 apps/member/models.py:114 msgid "You can't invite someone once the activity is started."
#: apps/note/models/notes.py:188 apps/note/models/transactions.py:25 msgstr "Vous ne pouvez pas inviter quelqu'un une fois que l'activité a démarré."
#: apps/note/models/transactions.py:45 apps/note/models/transactions.py:232
#: templates/member/profile_detail.html:15 #: apps/activity/forms.py:48 apps/activity/models.py:220
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
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
msgid "This person is already invited."
msgstr "Cette personne est déjà invitée."
#: apps/activity/forms.py:66 apps/activity/models.py:236
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:64 apps/member/models.py:122
#: apps/note/models/notes.py:188 apps/note/models/transactions.py:24
#: apps/note/models/transactions.py:44 apps/note/models/transactions.py:231
#: templates/member/club_info.html:13 templates/member/profile_info.html:14
msgid "name" msgid "name"
msgstr "nom" msgstr "nom"
#: apps/activity/models.py:23 #: apps/activity/models.py:27 templates/activity/activity_detail.html:39
msgid "can invite" msgid "can invite"
msgstr "peut inviter" msgstr "peut inviter"
#: apps/activity/models.py:26 #: apps/activity/models.py:30 templates/activity/activity_detail.html:43
msgid "guest entry fee" msgid "guest entry fee"
msgstr "cotisation de l'entrée invité" msgstr "cotisation de l'entrée invité"
#: apps/activity/models.py:30 #: apps/activity/models.py:34
msgid "activity type" msgid "activity type"
msgstr "type d'activité" msgstr "type d'activité"
#: apps/activity/models.py:31 #: apps/activity/models.py:35
msgid "activity types" msgid "activity types"
msgstr "types d'activité" msgstr "types d'activité"
#: apps/activity/models.py:48 apps/note/models/transactions.py:70 #: apps/activity/models.py:53 apps/note/models/transactions.py:69
#: apps/permission/models.py:91 #: apps/permission/models.py:90 templates/activity/activity_detail.html:16
msgid "description" msgid "description"
msgstr "description" msgstr "description"
#: apps/activity/models.py:54 apps/note/models/notes.py:164 #: apps/activity/models.py:60 apps/note/models/notes.py:164
#: apps/note/models/transactions.py:63 #: apps/note/models/transactions.py:62
#: templates/activity/activity_detail.html:19
msgid "type" msgid "type"
msgstr "type" msgstr "type"
#: apps/activity/models.py:60 #: apps/activity/models.py:66 apps/logs/models.py:21
#: apps/note/models/notes.py:117
msgid "user"
msgstr "utilisateur"
#: apps/activity/models.py:73 templates/activity/activity_detail.html:33
msgid "organizer" msgid "organizer"
msgstr "organisateur" msgstr "organisateur"
#: apps/activity/models.py:66 #: apps/activity/models.py:82 apps/activity/models.py:131 apps/note/apps.py:14
msgid "attendees club" #: apps/note/models/notes.py:58
msgstr "" msgid "note"
msgstr "note"
#: apps/activity/models.py:69 #: apps/activity/models.py:89 templates/activity/activity_detail.html:36
msgid "attendees club"
msgstr "club attendu"
#: apps/activity/models.py:93 templates/activity/activity_detail.html:22
msgid "start date" msgid "start date"
msgstr "date de début" msgstr "date de début"
#: apps/activity/models.py:72 #: apps/activity/models.py:97 templates/activity/activity_detail.html:25
msgid "end date" msgid "end date"
msgstr "date de fin" msgstr "date de fin"
#: apps/activity/models.py:77 #: apps/activity/models.py:102 apps/note/models/transactions.py:134
#: templates/activity/activity_detail.html:47
msgid "valid"
msgstr "valide"
#: apps/activity/models.py:107 templates/activity/activity_detail.html:61
msgid "open"
msgstr "ouvrir"
#: apps/activity/models.py:112
msgid "activities" msgid "activities"
msgstr "activités" msgstr "activités"
#: apps/activity/models.py:108 #: apps/activity/models.py:125
msgid "entry time"
msgstr "heure d'entrée"
#: apps/activity/models.py:148
msgid "Already entered on "
msgstr "Déjà rentré le "
#: apps/activity/models.py:148 apps/activity/tables.py:54
msgid "{:%Y-%m-%d %H:%M:%S}"
msgstr "{:%d/%m/%Y %H:%M:%S}"
#: apps/activity/models.py:156
msgid "The balance is negative."
msgstr "La note est en négatif."
#: apps/activity/models.py:188
msgid "last name"
msgstr "nom de famille"
#: apps/activity/models.py:193 templates/member/profile_info.html:14
msgid "first name"
msgstr "prénom"
#: apps/activity/models.py:200
msgid "inviter"
msgstr "hôte"
#: apps/activity/models.py:241
msgid "guest" msgid "guest"
msgstr "invité" msgstr "invité"
#: apps/activity/models.py:109 #: apps/activity/models.py:242
msgid "guests" msgid "guests"
msgstr "invités" msgstr "invités"
#: apps/activity/models.py:254
msgid "Invitation"
msgstr "Invitation"
#: apps/activity/tables.py:54
msgid "Entered on "
msgstr "Entré le "
#: apps/activity/tables.py:55
msgid "remove"
msgstr "supprimer"
#: apps/activity/tables.py:75 apps/treasury/models.py:126
msgid "Type"
msgstr "Type"
#: apps/activity/tables.py:77 apps/treasury/forms.py:120
msgid "Last name"
msgstr "Nom de famille"
#: apps/activity/tables.py:79 apps/treasury/forms.py:122
#: templates/note/transaction_form.html:92
msgid "First name"
msgstr "Prénom"
#: apps/activity/tables.py:81 apps/note/models/notes.py:67
msgid "Note"
msgstr "Note"
#: apps/activity/tables.py:83
msgid "Balance"
msgstr "Solde du compte"
#: apps/activity/views.py:44 templates/base.html:94
msgid "Activities"
msgstr "Activités"
#: apps/activity/views.py:149
msgid "Entry for activity \"{}\""
msgstr "Entrées pour l'activité « {} »"
#: apps/api/apps.py:10 #: apps/api/apps.py:10
msgid "API" msgid "API"
msgstr "" msgstr "API"
#: apps/logs/apps.py:11 #: apps/logs/apps.py:11
msgid "Logs" msgid "Logs"
msgstr "" msgstr "Logs"
#: apps/logs/models.py:21 apps/note/models/notes.py:117
msgid "user"
msgstr "utilisateur"
#: apps/logs/models.py:27 #: apps/logs/models.py:27
msgid "IP Address" msgid "IP Address"
@ -115,11 +221,12 @@ msgstr "Nouvelles données"
msgid "create" msgid "create"
msgstr "Créer" msgstr "Créer"
#: apps/logs/models.py:61 apps/note/tables.py:147 #: apps/logs/models.py:61 apps/note/tables.py:142
#: templates/activity/activity_detail.html:67
msgid "edit" msgid "edit"
msgstr "Modifier" msgstr "Modifier"
#: apps/logs/models.py:62 apps/note/tables.py:151 #: apps/logs/models.py:62 apps/note/tables.py:120 apps/note/tables.py:146
msgid "delete" msgid "delete"
msgstr "Supprimer" msgstr "Supprimer"
@ -139,61 +246,65 @@ msgstr "Les logs ne peuvent pas être détruits."
msgid "member" msgid "member"
msgstr "adhérent" msgstr "adhérent"
#: apps/member/models.py:25 #: apps/member/models.py:26
msgid "phone number" msgid "phone number"
msgstr "numéro de téléphone" msgstr "numéro de téléphone"
#: apps/member/models.py:31 templates/member/profile_detail.html:28 #: apps/member/models.py:32 templates/member/profile_info.html:27
msgid "section" msgid "section"
msgstr "section" msgstr "section"
#: apps/member/models.py:32 #: apps/member/models.py:33
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\"" msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
#: apps/member/models.py:38 templates/member/profile_detail.html:31 #: apps/member/models.py:39 templates/member/profile_info.html:30
msgid "address" msgid "address"
msgstr "adresse" msgstr "adresse"
#: apps/member/models.py:44 #: apps/member/models.py:45
msgid "paid" msgid "paid"
msgstr "payé" msgstr "payé"
#: apps/member/models.py:49 apps/member/models.py:50 #: apps/member/models.py:50 apps/member/models.py:51
msgid "user profile" msgid "user profile"
msgstr "profil utilisateur" msgstr "profil utilisateur"
#: apps/member/models.py:68 #: apps/member/models.py:69 templates/member/club_info.html:36
msgid "email" msgid "email"
msgstr "courriel" msgstr "courriel"
#: apps/member/models.py:73 #: apps/member/models.py:76
msgid "parent club"
msgstr "club parent"
#: apps/member/models.py:81 templates/member/club_info.html:30
msgid "membership fee" msgid "membership fee"
msgstr "cotisation pour adhérer" msgstr "cotisation pour adhérer"
#: apps/member/models.py:77 #: apps/member/models.py:85 templates/member/club_info.html:27
msgid "membership duration" msgid "membership duration"
msgstr "durée de l'adhésion" msgstr "durée de l'adhésion"
#: apps/member/models.py:78 #: apps/member/models.py:86
msgid "The longest time a membership can last (NULL = infinite)." msgid "The longest time a membership can last (NULL = infinite)."
msgstr "La durée maximale d'une adhésion (NULL = infinie)." msgstr "La durée maximale d'une adhésion (NULL = infinie)."
#: apps/member/models.py:83 #: apps/member/models.py:91 templates/member/club_info.html:21
msgid "membership start" msgid "membership start"
msgstr "début de l'adhésion" msgstr "début de l'adhésion"
#: apps/member/models.py:84 #: apps/member/models.py:92
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:89 #: apps/member/models.py:97 templates/member/club_info.html:24
msgid "membership end" msgid "membership end"
msgstr "fin de l'adhésion" msgstr "fin de l'adhésion"
#: apps/member/models.py:90 #: apps/member/models.py:98
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."
@ -201,81 +312,68 @@ 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:96 apps/note/models/notes.py:139 #: apps/member/models.py:104 apps/note/models/notes.py:139
msgid "club" msgid "club"
msgstr "club" msgstr "club"
#: apps/member/models.py:97 #: apps/member/models.py:105
msgid "clubs" msgid "clubs"
msgstr "clubs" msgstr "clubs"
#: apps/member/models.py:120 apps/permission/models.py:276 #: apps/member/models.py:128 apps/permission/models.py:275
msgid "role" msgid "role"
msgstr "rôle" msgstr "rôle"
#: apps/member/models.py:121 #: apps/member/models.py:129
msgid "roles" msgid "roles"
msgstr "rôles" msgstr "rôles"
#: apps/member/models.py:145 #: apps/member/models.py:153
msgid "membership starts on" msgid "membership starts on"
msgstr "l'adhésion commence le" msgstr "l'adhésion commence le"
#: apps/member/models.py:148 #: apps/member/models.py:156
msgid "membership ends on" msgid "membership ends on"
msgstr "l'adhésion finie le" msgstr "l'adhésion finie le"
#: apps/member/models.py:152 #: apps/member/models.py:160
msgid "fee" msgid "fee"
msgstr "cotisation" msgstr "cotisation"
#: apps/member/models.py:162 #: apps/member/models.py:172
msgid "User is not a member of the parent club"
msgstr "L'utilisateur n'est pas membre du club parent"
#: apps/member/models.py:176
msgid "membership" msgid "membership"
msgstr "adhésion" msgstr "adhésion"
#: apps/member/models.py:163 #: apps/member/models.py:177
msgid "memberships" msgid "memberships"
msgstr "adhésions" msgstr "adhésions"
#: apps/member/views.py:80 templates/member/profile_detail.html:46 #: apps/member/views.py:76 templates/member/profile_info.html:45
msgid "Update Profile" msgid "Update Profile"
msgstr "Modifier le profil" msgstr "Modifier le profil"
#: apps/member/views.py:93 #: apps/member/views.py:89
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/member/views.py:146 #: apps/note/admin.py:120 apps/note/models/transactions.py:94
#, python-format
msgid "Account #%(id)s: %(username)s"
msgstr "Compte n°%(id)s : %(username)s"
#: apps/member/views.py:216
msgid "Alias successfully deleted"
msgstr "L'alias a bien été supprimé"
#: apps/note/admin.py:120 apps/note/models/transactions.py:95
msgid "source" msgid "source"
msgstr "source" msgstr "source"
#: apps/note/admin.py:128 apps/note/admin.py:156 #: apps/note/admin.py:128 apps/note/admin.py:156
#: apps/note/models/transactions.py:54 apps/note/models/transactions.py:108 #: apps/note/models/transactions.py:53 apps/note/models/transactions.py:107
msgid "destination" msgid "destination"
msgstr "destination" msgstr "destination"
#: apps/note/apps.py:14 apps/note/models/notes.py:58 #: apps/note/forms.py:14
msgid "note"
msgstr "note"
#: apps/note/forms.py:20
msgid "New Alias"
msgstr "Nouvel alias"
#: apps/note/forms.py:25
msgid "select an image" msgid "select an image"
msgstr "Choisissez une image" msgstr "Choisissez une image"
#: apps/note/forms.py:26 #: apps/note/forms.py:15
msgid "Maximal size: 2MB" msgid "Maximal size: 2MB"
msgstr "Taille maximale : 2 Mo" msgstr "Taille maximale : 2 Mo"
@ -310,7 +408,7 @@ msgstr ""
msgid "display image" msgid "display image"
msgstr "image affichée" msgstr "image affichée"
#: apps/note/models/notes.py:53 apps/note/models/transactions.py:118 #: 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"
@ -318,10 +416,6 @@ msgstr "créée le"
msgid "notes" msgid "notes"
msgstr "notes" msgstr "notes"
#: apps/note/models/notes.py:67
msgid "Note"
msgstr "Note"
#: apps/note/models/notes.py:77 apps/note/models/notes.py:101 #: 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."
@ -368,7 +462,8 @@ msgstr "Alias invalide"
msgid "alias" msgid "alias"
msgstr "alias" msgstr "alias"
#: apps/note/models/notes.py:211 templates/member/profile_detail.html:37 #: apps/note/models/notes.py:211 templates/member/club_info.html:33
#: templates/member/profile_info.html:36
msgid "aliases" msgid "aliases"
msgstr "alias" msgstr "alias"
@ -380,102 +475,114 @@ msgstr "L'alias est trop long."
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:247 #: 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."
#: apps/note/models/transactions.py:31 #: apps/note/models/transactions.py:30
msgid "transaction category" msgid "transaction category"
msgstr "catégorie de transaction" msgstr "catégorie de transaction"
#: apps/note/models/transactions.py:32 #: apps/note/models/transactions.py:31
msgid "transaction categories" msgid "transaction categories"
msgstr "catégories de transaction" msgstr "catégories de transaction"
#: apps/note/models/transactions.py:48 #: apps/note/models/transactions.py:47
msgid "A template with this name already exist" msgid "A template with this name already exist"
msgstr "Un modèle de transaction avec un nom similaire existe déjà." msgstr "Un modèle de transaction avec un nom similaire existe déjà."
#: apps/note/models/transactions.py:57 apps/note/models/transactions.py:126 #: apps/note/models/transactions.py:56 apps/note/models/transactions.py:125
msgid "amount" msgid "amount"
msgstr "montant" msgstr "montant"
#: apps/note/models/transactions.py:58 #: apps/note/models/transactions.py:57
msgid "in centimes" msgid "in centimes"
msgstr "en centimes" msgstr "en centimes"
#: apps/note/models/transactions.py:76 #: apps/note/models/transactions.py:75
msgid "transaction template" msgid "transaction template"
msgstr "modèle de transaction" msgstr "modèle de transaction"
#: apps/note/models/transactions.py:77 #: apps/note/models/transactions.py:76
msgid "transaction templates" msgid "transaction templates"
msgstr "modèles de transaction" msgstr "modèles de transaction"
#: apps/note/models/transactions.py:101 apps/note/models/transactions.py:114 #: apps/note/models/transactions.py:100 apps/note/models/transactions.py:113
#: apps/note/tables.py:33 apps/note/tables.py:42 #: apps/note/tables.py:33 apps/note/tables.py:42
msgid "used alias" msgid "used alias"
msgstr "alias utilisé" msgstr "alias utilisé"
#: apps/note/models/transactions.py:122 #: apps/note/models/transactions.py:121
msgid "quantity" msgid "quantity"
msgstr "quantité" msgstr "quantité"
#: apps/note/models/transactions.py:115 #: apps/note/models/transactions.py:129
msgid "reason" msgid "reason"
msgstr "raison" msgstr "raison"
#: apps/note/models/transactions.py:119 #: apps/note/models/transactions.py:139 apps/note/tables.py:95
msgid "valid" msgid "invalidity reason"
msgstr "valide" msgstr "Motif d'invalidité"
#: apps/note/models/transactions.py:124 #: apps/note/models/transactions.py:146
msgid "transaction" msgid "transaction"
msgstr "transaction" msgstr "transaction"
#: apps/note/models/transactions.py:125 #: apps/note/models/transactions.py:147
msgid "transactions" msgid "transactions"
msgstr "transactions" msgstr "transactions"
#: apps/note/models/transactions.py:168 templates/base.html:98 #: apps/note/models/transactions.py:201 templates/base.html:84
#: templates/note/transaction_form.html:19 #: templates/note/transaction_form.html:19
#: templates/note/transaction_form.html:145 #: templates/note/transaction_form.html:140
msgid "Transfer" msgid "Transfer"
msgstr "Virement" msgstr "Virement"
#: apps/note/models/transactions.py:188 #: apps/note/models/transactions.py:221
msgid "Template" msgid "Template"
msgstr "Bouton" msgstr "Bouton"
#: apps/note/models/transactions.py:203 #: apps/note/models/transactions.py:236
msgid "first_name" msgid "first_name"
msgstr "prénom" msgstr "prénom"
#: apps/note/models/transactions.py:208 #: apps/note/models/transactions.py:241
msgid "bank" msgid "bank"
msgstr "banque" msgstr "banque"
#: apps/note/models/transactions.py:214 templates/note/transaction_form.html:24 #: apps/note/models/transactions.py:247 templates/note/transaction_form.html:24
msgid "Credit" msgid "Credit"
msgstr "Crédit" msgstr "Crédit"
#: apps/note/models/transactions.py:214 templates/note/transaction_form.html:28 #: apps/note/models/transactions.py:247 templates/note/transaction_form.html:28
msgid "Debit" msgid "Debit"
msgstr "Débit" msgstr "Débit"
#: apps/note/models/transactions.py:230 apps/note/models/transactions.py:235 #: apps/note/models/transactions.py:263 apps/note/models/transactions.py:268
msgid "membership transaction" msgid "membership transaction"
msgstr "transaction d'adhésion" msgstr "transaction d'adhésion"
#: apps/note/models/transactions.py:231 #: apps/note/models/transactions.py:264
msgid "membership transactions" msgid "membership transactions"
msgstr "transactions d'adhésion" msgstr "transactions d'adhésion"
#: apps/note/views.py:39 #: apps/note/tables.py:57
msgid "Click to invalidate"
msgstr "Cliquez pour dévalider"
#: apps/note/tables.py:57
msgid "Click to validate"
msgstr "Cliquez pour valider"
#: apps/note/tables.py:93
msgid "No reason specified"
msgstr "Pas de motif spécifié"
#: apps/note/views.py:41
msgid "Transfer money" msgid "Transfer money"
msgstr "Transférer de l'argent" msgstr "Transférer de l'argent"
#: apps/note/views.py:145 templates/base.html:79 #: apps/note/views.py:102 templates/base.html:79
msgid "Consumptions" msgid "Consumptions"
msgstr "Consommations" msgstr "Consommations"
@ -497,41 +604,35 @@ msgstr "Rang"
msgid "Specifying field applies only to view and change permission types." msgid "Specifying field applies only to view and change permission types."
msgstr "" msgstr ""
#: apps/treasury/apps.py:11 templates/base.html:102 #: apps/treasury/apps.py:12 templates/base.html:99
msgid "Treasury" msgid "Treasury"
msgstr "Trésorerie" msgstr "Trésorerie"
#: apps/treasury/forms.py:56 apps/treasury/forms.py:95 #: apps/treasury/forms.py:84 apps/treasury/forms.py:132
#: templates/activity/activity_form.html:9
#: 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:10 templates/treasury/invoice_form.html:47 #: templates/member/club_form.html:9 templates/treasury/invoice_form.html:46
msgid "Submit" msgid "Submit"
msgstr "Envoyer" msgstr "Envoyer"
#: apps/treasury/forms.py:58 #: apps/treasury/forms.py:86
msgid "Close" msgid "Close"
msgstr "Fermer" msgstr "Fermer"
#: apps/treasury/forms.py:65 #: apps/treasury/forms.py:95
msgid "Remittance is already closed." msgid "Remittance is already closed."
msgstr "La remise est déjà fermée." msgstr "La remise est déjà fermée."
#: apps/treasury/forms.py:70 #: apps/treasury/forms.py:100
msgid "You can't change the type of the remittance." msgid "You can't change the type of the remittance."
msgstr "Vous ne pouvez pas changer le type de la remise." msgstr "Vous ne pouvez pas changer le type de la remise."
#: apps/treasury/forms.py:84 #: apps/treasury/forms.py:124 templates/note/transaction_form.html:98
msgid "Last name"
msgstr "Nom de famille"
#: apps/treasury/forms.py:86 templates/note/transaction_form.html:92
msgid "First name"
msgstr "Prénom"
#: apps/treasury/forms.py:88 templates/note/transaction_form.html:98
msgid "Bank" msgid "Bank"
msgstr "Banque" msgstr "Banque"
#: apps/treasury/forms.py:90 apps/treasury/tables.py:40 #: apps/treasury/forms.py:126 apps/treasury/tables.py:47
#: templates/note/transaction_form.html:128 #: templates/note/transaction_form.html:128
#: templates/treasury/remittance_form.html:18 #: templates/treasury/remittance_form.html:18
msgid "Amount" msgid "Amount"
@ -585,10 +686,6 @@ msgstr "Prix unitaire"
msgid "Date" msgid "Date"
msgstr "Date" msgstr "Date"
#: apps/treasury/models.py:126
msgid "Type"
msgstr "Type"
#: apps/treasury/models.py:131 #: apps/treasury/models.py:131
msgid "Comment" msgid "Comment"
msgstr "Commentaire" msgstr "Commentaire"
@ -597,38 +694,38 @@ msgstr "Commentaire"
msgid "Closed" msgid "Closed"
msgstr "Fermée" msgstr "Fermée"
#: apps/treasury/models.py:159 #: apps/treasury/models.py:169
msgid "Remittance #{:d}: {}" msgid "Remittance #{:d}: {}"
msgstr "Remise n°{:d} : {}" msgstr "Remise n°{:d} : {}"
#: apps/treasury/models.py:178 apps/treasury/tables.py:64 #: apps/treasury/models.py:188 apps/treasury/tables.py:76
#: apps/treasury/tables.py:72 templates/treasury/invoice_list.html:13 #: apps/treasury/tables.py:84 templates/treasury/invoice_list.html:13
#: templates/treasury/remittance_list.html:13 #: templates/treasury/remittance_list.html:13
msgid "Remittance" msgid "Remittance"
msgstr "Remise" msgstr "Remise"
#: apps/treasury/tables.py:16 #: apps/treasury/tables.py:19
msgid "Invoice #{:d}" msgid "Invoice #{:d}"
msgstr "Facture n°{:d}" msgstr "Facture n°{:d}"
#: apps/treasury/tables.py:19 templates/treasury/invoice_list.html:10 #: apps/treasury/tables.py:22 templates/treasury/invoice_list.html:10
#: templates/treasury/remittance_list.html:10 #: templates/treasury/remittance_list.html:10
msgid "Invoice" msgid "Invoice"
msgstr "Facture" msgstr "Facture"
#: apps/treasury/tables.py:38 #: apps/treasury/tables.py:45
msgid "Transaction count" msgid "Transaction count"
msgstr "Nombre de transactions" msgstr "Nombre de transactions"
#: apps/treasury/tables.py:43 apps/treasury/tables.py:45 #: apps/treasury/tables.py:50 apps/treasury/tables.py:52
msgid "View" msgid "View"
msgstr "Voir" msgstr "Voir"
#: apps/treasury/tables.py:66 #: apps/treasury/tables.py:78
msgid "Add" msgid "Add"
msgstr "Ajouter" msgstr "Ajouter"
#: apps/treasury/tables.py:74 #: apps/treasury/tables.py:86
msgid "Remove" msgid "Remove"
msgstr "supprimer" msgstr "supprimer"
@ -639,30 +736,86 @@ msgid ""
"again unless your session expires or you logout." "again unless your session expires or you logout."
msgstr "" msgstr ""
#: note_kfet/settings/base.py:151 #: note_kfet/settings/base.py:152
msgid "German" msgid "German"
msgstr "" msgstr ""
#: note_kfet/settings/base.py:152 #: note_kfet/settings/base.py:153
msgid "English" msgid "English"
msgstr "" msgstr ""
#: note_kfet/settings/base.py:153 #: note_kfet/settings/base.py:154
msgid "French" msgid "French"
msgstr "" msgstr ""
#: templates/activity/activity_detail.html:29
msgid "creater"
msgstr "Créateur"
#: templates/activity/activity_detail.html:50
msgid "opened"
msgstr "ouvert"
#: templates/activity/activity_detail.html:57
msgid "Entry page"
msgstr "Page des entrées"
#: templates/activity/activity_detail.html:61
msgid "close"
msgstr "fermer"
#: templates/activity/activity_detail.html:64
msgid "invalidate"
msgstr "dévalider"
#: templates/activity/activity_detail.html:64
msgid "validate"
msgstr "valider"
#: templates/activity/activity_detail.html:70
msgid "Invite"
msgstr "Inviter"
#: templates/activity/activity_detail.html:77
msgid "Guests list"
msgstr "Liste des invités"
#: templates/activity/activity_entry.html:10
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"
#: templates/activity/activity_list.html:10
msgid "There is no planned activity."
msgstr "Il n'y a pas d'activité prévue."
#: templates/activity/activity_list.html:14
msgid "New activity"
msgstr "Nouvelle activité"
#: templates/activity/activity_list.html:18
msgid "All activities"
msgstr "Toutes les activités"
#: templates/base.html:13 #: templates/base.html:13
msgid "The ENS Paris-Saclay BDE note." msgid "The ENS Paris-Saclay BDE note."
msgstr "La note du BDE de l'ENS Paris-Saclay." msgstr "La note du BDE de l'ENS Paris-Saclay."
#: templates/base.html:87 #: templates/base.html:89
msgid "Clubs" msgid "Clubs"
msgstr "Clubs" msgstr "Clubs"
#: templates/base.html:92
msgid "Activities"
msgstr "Activités"
#: templates/cas_server/base.html:7 #: templates/cas_server/base.html:7
msgid "Central Authentication Service" msgid "Central Authentication Service"
msgstr "" msgstr ""
@ -720,33 +873,49 @@ msgstr ""
msgid "Field filters" msgid "Field filters"
msgstr "" msgstr ""
#: templates/member/club_detail.html:10 #: templates/member/alias_update.html:5
msgid "Membership starts on" msgid "Add alias"
msgstr "L'adhésion commence le" msgstr "Ajouter un alias"
#: templates/member/club_detail.html:12 #: templates/member/club_info.html:17
msgid "Membership ends on" msgid "Club Parent"
msgstr "L'adhésion finie le" msgstr "Club parent"
#: templates/member/club_detail.html:14 #: templates/member/club_info.html:41
msgid "Membership duration" msgid "Add member"
msgstr "Durée de l'adhésion" msgstr "Ajouter un membre"
#: templates/member/club_detail.html:18 templates/member/profile_detail.html:34 #: templates/member/club_info.html:42 templates/note/conso_form.html:121
msgid "balance" msgid "Edit"
msgstr "solde du compte" msgstr "Éditer"
#: templates/member/club_detail.html:51 templates/member/profile_detail.html:75 #: templates/member/club_info.html:43
msgid "Transaction history" msgid "Add roles"
msgstr "Historique des transactions" msgstr "Ajouter des rôles"
#: templates/member/club_form.html:6 #: templates/member/club_info.html:46 templates/member/profile_info.html:48
msgid "Clubs list" msgid "View Profile"
msgstr "Liste des clubs" msgstr "Voir le profil"
#: templates/member/club_list.html:8 #: templates/member/club_list.html:8
msgid "New club" msgid "search clubs"
msgstr "Nouveau club" msgstr "Chercher un club"
#: templates/member/club_list.html:12
msgid "Créer un club"
msgstr ""
#: templates/member/club_list.html:19
msgid "club listing "
msgstr "Liste des clubs"
#: templates/member/club_tables.html:9
msgid "Member of the Club"
msgstr "Membre du club"
#: templates/member/club_tables.html:22 templates/member/profile_tables.html:22
msgid "Transaction history"
msgstr "Historique des transactions"
#: templates/member/manage_auth_tokens.html:16 #: templates/member/manage_auth_tokens.html:16
msgid "Token" msgid "Token"
@ -760,35 +929,31 @@ msgstr "Créé le"
msgid "Regenerate token" msgid "Regenerate token"
msgstr "Regénérer le jeton" msgstr "Regénérer le jeton"
#: templates/member/profile_alias.html:10 #: templates/member/profile_info.html:5
msgid "Add alias" msgid "Account #"
msgstr "Ajouter un alias" msgstr "Compte n°"
#: templates/member/profile_detail.html:15 #: templates/member/profile_info.html:17
msgid "first name"
msgstr "prénom"
#: templates/member/profile_detail.html:18
msgid "username" msgid "username"
msgstr "pseudo" msgstr "pseudo"
#: templates/member/profile_detail.html:21 #: templates/member/profile_info.html:20
msgid "password" msgid "password"
msgstr "mot de passe" msgstr "mot de passe"
#: templates/member/profile_detail.html:24 #: templates/member/profile_info.html:23
msgid "Change password" msgid "Change password"
msgstr "Changer le mot de passe" msgstr "Changer le mot de passe"
#: templates/member/profile_detail.html:42 #: templates/member/profile_info.html:33
msgid "balance"
msgstr "solde du compte"
#: templates/member/profile_info.html:41
msgid "Manage auth token" msgid "Manage auth token"
msgstr "Gérer les jetons d'authentification" msgstr "Gérer les jetons d'authentification"
#: templates/member/profile_detail.html:49 #: templates/member/profile_tables.html:9
msgid "View Profile"
msgstr "Voir le profil"
#: templates/member/profile_detail.html:62
msgid "View my memberships" msgid "View my memberships"
msgstr "Voir mes adhésions" msgstr "Voir mes adhésions"
@ -817,10 +982,6 @@ msgstr "Consommer !"
msgid "Most used buttons" msgid "Most used buttons"
msgstr "Boutons les plus utilisés" msgstr "Boutons les plus utilisés"
#: templates/note/conso_form.html:121
msgid "Edit"
msgstr "Éditer"
#: templates/note/conso_form.html:126 #: templates/note/conso_form.html:126
msgid "Single consumptions" msgid "Single consumptions"
msgstr "Consommations simples" msgstr "Consommations simples"
@ -829,7 +990,7 @@ msgstr "Consommations simples"
msgid "Double consumptions" msgid "Double consumptions"
msgstr "Consommations doubles" msgstr "Consommations doubles"
#: templates/note/conso_form.html:141 templates/note/transaction_form.html:152 #: templates/note/conso_form.html:141 templates/note/transaction_form.html:147
msgid "Recent transactions history" msgid "Recent transactions history"
msgstr "Historique des transactions récentes" msgstr "Historique des transactions récentes"
@ -845,37 +1006,21 @@ msgstr "Paiement externe"
msgid "Transfer type" msgid "Transfer type"
msgstr "Type de transfert" msgstr "Type de transfert"
#: templates/note/transaction_form.html:86
msgid "Name"
msgstr "Nom"
#: templates/note/transaction_form.html:92
msgid "First name"
msgstr "Prénom"
#: templates/note/transaction_form.html:98
msgid "Bank"
msgstr "Banque"
#: templates/note/transaction_form.html:111 #: templates/note/transaction_form.html:111
#: templates/note/transaction_form.html:169 #: templates/note/transaction_form.html:164
#: templates/note/transaction_form.html:176 #: templates/note/transaction_form.html:171
msgid "Select receivers" msgid "Select receivers"
msgstr "Sélection des destinataires" msgstr "Sélection des destinataires"
#: templates/note/transaction_form.html:128 #: templates/note/transaction_form.html:133
msgid "Amount"
msgstr "Montant"
#: templates/note/transaction_form.html:138
msgid "Reason" msgid "Reason"
msgstr "Raison" msgstr "Raison"
#: templates/note/transaction_form.html:183 #: templates/note/transaction_form.html:178
msgid "Credit note" msgid "Credit note"
msgstr "Note à recharger" msgstr "Note à recharger"
#: templates/note/transaction_form.html:190 #: templates/note/transaction_form.html:185
msgid "Debit note" msgid "Debit note"
msgstr "Note à débiter" msgstr "Note à débiter"
@ -887,6 +1032,10 @@ msgstr "Liste des boutons"
msgid "search button" msgid "search button"
msgstr "Chercher un bouton" msgstr "Chercher un bouton"
#: templates/note/transactiontemplate_list.html:13
msgid "New button"
msgstr "Nouveau bouton"
#: templates/note/transactiontemplate_list.html:20 #: templates/note/transactiontemplate_list.html:20
msgid "buttons listing " msgid "buttons listing "
msgstr "Liste des boutons" msgstr "Liste des boutons"
@ -989,11 +1138,11 @@ msgstr ""
msgid "Invoices list" msgid "Invoices list"
msgstr "Liste des factures" msgstr "Liste des factures"
#: templates/treasury/invoice_form.html:42 #: templates/treasury/invoice_form.html:41
msgid "Add product" msgid "Add product"
msgstr "Ajouter produit" msgstr "Ajouter produit"
#: templates/treasury/invoice_form.html:43 #: templates/treasury/invoice_form.html:42
msgid "Remove product" msgid "Remove product"
msgstr "Retirer produit" msgstr "Retirer produit"

302
note_kfet/inputs.py Normal file
View File

@ -0,0 +1,302 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from json import dumps as json_dumps
from django.forms.widgets import DateTimeBaseInput, NumberInput, TextInput
class AmountInput(NumberInput):
"""
This input type lets the user type amounts in euros, but forms receive data in cents
"""
template_name = "note/amount_input.html"
def format_value(self, value):
return None if value is None or value == "" else "{:.02f}".format(value / 100, )
def value_from_datadict(self, data, files, name):
val = super().value_from_datadict(data, files, name)
return str(int(100 * float(val))) if val else val
class Autocomplete(TextInput):
template_name = "member/autocomplete_model.html"
def __init__(self, model, attrs=None):
super().__init__(attrs)
self.model = model
self.model_pk = None
class Media:
"""JS/CSS resources needed to render the date-picker calendar."""
js = ('js/autocomplete_model.js', )
def format_value(self, value):
if value:
self.attrs["model_pk"] = int(value)
return str(self.model.objects.get(pk=int(value)))
return ""
"""
The remaining of this file comes from the project `django-bootstrap-datepicker-plus` available on Github:
https://github.com/monim67/django-bootstrap-datepicker-plus
This is distributed under Apache License 2.0.
This adds datetime pickers with bootstrap.
"""
"""Contains Base Date-Picker input class for widgets of this package."""
class DatePickerDictionary:
"""Keeps track of all date-picker input classes."""
_i = 0
items = dict()
@classmethod
def generate_id(cls):
"""Return a unique ID for each date-picker input class."""
cls._i += 1
return 'dp_%s' % cls._i
class BasePickerInput(DateTimeBaseInput):
"""Base Date-Picker input class for widgets of this package."""
template_name = 'bootstrap_datepicker_plus/date_picker.html'
picker_type = 'DATE'
format = '%Y-%m-%d'
config = {}
_default_config = {
'id': None,
'picker_type': None,
'linked_to': None,
'options': {} # final merged options
}
options = {} # options extended by user
options_param = {} # options passed as parameter
_default_options = {
'showClose': True,
'showClear': True,
'showTodayButton': True,
"locale": "fr",
}
# source: https://github.com/tutorcruncher/django-bootstrap3-datetimepicker
# file: /blob/31fbb09/bootstrap3_datetime/widgets.py#L33
format_map = (
('DDD', r'%j'),
('DD', r'%d'),
('MMMM', r'%B'),
('MMM', r'%b'),
('MM', r'%m'),
('YYYY', r'%Y'),
('YY', r'%y'),
('HH', r'%H'),
('hh', r'%I'),
('mm', r'%M'),
('ss', r'%S'),
('a', r'%p'),
('ZZ', r'%z'),
)
class Media:
"""JS/CSS resources needed to render the date-picker calendar."""
js = (
'https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.9.0/'
'moment-with-locales.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/'
'4.17.47/js/bootstrap-datetimepicker.min.js',
'bootstrap_datepicker_plus/js/datepicker-widget.js'
)
css = {'all': (
'https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/'
'4.17.47/css/bootstrap-datetimepicker.css',
'bootstrap_datepicker_plus/css/datepicker-widget.css'
), }
@classmethod
def format_py2js(cls, datetime_format):
"""Convert python datetime format to moment datetime format."""
for js_format, py_format in cls.format_map:
datetime_format = datetime_format.replace(py_format, js_format)
return datetime_format
@classmethod
def format_js2py(cls, datetime_format):
"""Convert moment datetime format to python datetime format."""
for js_format, py_format in cls.format_map:
datetime_format = datetime_format.replace(js_format, py_format)
return datetime_format
def __init__(self, attrs=None, format=None, options=None):
"""Initialize the Date-picker widget."""
self.format_param = format
self.options_param = options if options else {}
self.config = self._default_config.copy()
self.config['id'] = DatePickerDictionary.generate_id()
self.config['picker_type'] = self.picker_type
self.config['options'] = self._calculate_options()
attrs = attrs if attrs else {}
if 'class' not in attrs:
attrs['class'] = 'form-control'
super().__init__(attrs, self._calculate_format())
def _calculate_options(self):
"""Calculate and Return the options."""
_options = self._default_options.copy()
_options.update(self.options)
if self.options_param:
_options.update(self.options_param)
return _options
def _calculate_format(self):
"""Calculate and Return the datetime format."""
_format = self.format_param if self.format_param else self.format
if self.config['options'].get('format'):
_format = self.format_js2py(self.config['options'].get('format'))
else:
self.config['options']['format'] = self.format_py2js(_format)
return _format
def get_context(self, name, value, attrs):
"""Return widget context dictionary."""
context = super().get_context(
name, value, attrs)
context['widget']['attrs']['dp_config'] = json_dumps(self.config)
return context
def start_of(self, event_id):
"""
Set Date-Picker as the start-date of a date-range.
Args:
- event_id (string): User-defined unique id for linking two fields
"""
DatePickerDictionary.items[str(event_id)] = self
return self
def end_of(self, event_id, import_options=True):
"""
Set Date-Picker as the end-date of a date-range.
Args:
- event_id (string): User-defined unique id for linking two fields
- import_options (bool): inherit options from start-date input,
default: TRUE
"""
event_id = str(event_id)
if event_id in DatePickerDictionary.items:
linked_picker = DatePickerDictionary.items[event_id]
self.config['linked_to'] = linked_picker.config['id']
if import_options:
backup_moment_format = self.config['options']['format']
self.config['options'].update(linked_picker.config['options'])
self.config['options'].update(self.options_param)
if self.format_param or 'format' in self.options_param:
self.config['options']['format'] = backup_moment_format
else:
self.format = linked_picker.format
# Setting useCurrent is necessary, see following issue
# https://github.com/Eonasdan/bootstrap-datetimepicker/issues/1075
self.config['options']['useCurrent'] = False
self._link_to(linked_picker)
else:
raise KeyError(
'start-date not specified for event_id "%s"' % event_id)
return self
def _link_to(self, linked_picker):
"""
Executed when two date-inputs are linked together.
This method for sub-classes to override to customize the linking.
"""
pass
class DatePickerInput(BasePickerInput):
"""
Widget to display a Date-Picker Calendar on a DateField property.
Args:
- attrs (dict): HTML attributes of rendered HTML input
- format (string): Python DateTime format eg. "%Y-%m-%d"
- options (dict): Options to customize the widget, see README
"""
picker_type = 'DATE'
format = '%Y-%m-%d'
format_key = 'DATE_INPUT_FORMATS'
class TimePickerInput(BasePickerInput):
"""
Widget to display a Time-Picker Calendar on a TimeField property.
Args:
- attrs (dict): HTML attributes of rendered HTML input
- format (string): Python DateTime format eg. "%Y-%m-%d"
- options (dict): Options to customize the widget, see README
"""
picker_type = 'TIME'
format = '%H:%M'
format_key = 'TIME_INPUT_FORMATS'
template_name = 'bootstrap_datepicker_plus/time_picker.html'
class DateTimePickerInput(BasePickerInput):
"""
Widget to display a DateTime-Picker Calendar on a DateTimeField property.
Args:
- attrs (dict): HTML attributes of rendered HTML input
- format (string): Python DateTime format eg. "%Y-%m-%d"
- options (dict): Options to customize the widget, see README
"""
picker_type = 'DATETIME'
format = '%Y-%m-%d %H:%M'
format_key = 'DATETIME_INPUT_FORMATS'
class MonthPickerInput(BasePickerInput):
"""
Widget to display a Month-Picker Calendar on a DateField property.
Args:
- attrs (dict): HTML attributes of rendered HTML input
- format (string): Python DateTime format eg. "%Y-%m-%d"
- options (dict): Options to customize the widget, see README
"""
picker_type = 'MONTH'
format = '01/%m/%Y'
format_key = 'DATE_INPUT_FORMATS'
class YearPickerInput(BasePickerInput):
"""
Widget to display a Year-Picker Calendar on a DateField property.
Args:
- attrs (dict): HTML attributes of rendered HTML input
- format (string): Python DateTime format eg. "%Y-%m-%d"
- options (dict): Options to customize the widget, see README
"""
picker_type = 'YEAR'
format = '01/01/%Y'
format_key = 'DATE_INPUT_FORMATS'
def _link_to(self, linked_picker):
"""Customize the options when linked with other date-time input"""
yformat = self.config['options']['format'].replace('-01-01', '-12-31')
self.config['options']['format'] = yformat

View File

@ -48,12 +48,10 @@ INSTALLED_APPS = [
'django.contrib.sites', 'django.contrib.sites',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.forms',
# API # API
'rest_framework', 'rest_framework',
'rest_framework.authtoken', 'rest_framework.authtoken',
# Autocomplete
'dal',
'dal_select2',
# Note apps # Note apps
'activity', 'activity',
@ -100,6 +98,8 @@ TEMPLATES = [
}, },
] ]
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
WSGI_APPLICATION = 'note_kfet.wsgi.application' WSGI_APPLICATION = 'note_kfet.wsgi.application'
# Password validation # Password validation

View File

@ -15,13 +15,14 @@ urlpatterns = [
# Include project routers # Include project routers
path('note/', include('note.urls')), path('note/', include('note.urls')),
path('accounts/', include('member.urls')),
path('activity/', include('activity.urls')),
path('treasury/', include('treasury.urls')), path('treasury/', include('treasury.urls')),
# Include Django Contrib and Core routers # Include Django Contrib and Core routers
path('i18n/', include('django.conf.urls.i18n')), path('i18n/', include('django.conf.urls.i18n')),
path('admin/doc/', include('django.contrib.admindocs.urls')), path('admin/doc/', include('django.contrib.admindocs.urls')),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('accounts/', include('member.urls')),
path('accounts/login/', CustomLoginView.as_view()), path('accounts/login/', CustomLoginView.as_view()),
path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('django.contrib.auth.urls')),
path('api/', include('api.urls')), path('api/', include('api.urls')),

View File

@ -3,7 +3,6 @@ chardet==3.0.4
defusedxml==0.6.0 defusedxml==0.6.0
Django~=2.2 Django~=2.2
django-allauth==0.39.1 django-allauth==0.39.1
django-autocomplete-light==3.5.1
django-crispy-forms==1.7.2 django-crispy-forms==1.7.2
django-extensions==2.1.9 django-extensions==2.1.9
django-filter==2.2.0 django-filter==2.2.0

View File

@ -0,0 +1,121 @@
@font-face {
font-family: 'Glyphicons Halflings';
src: url('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.eot');
src: url('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'),
url('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.woff2') format('woff2'),
url('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.woff') format('woff'),
url('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.ttf') format('truetype'),
url('//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
}
.glyphicon {
position: relative;
top: 1px;
display: inline-block;
font-family: 'Glyphicons Halflings';
font-style: normal;
font-weight: normal;
line-height: 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.glyphicon-time:before {
content: "\e023";
}
.glyphicon-chevron-left:before {
content: "\e079";
}
.glyphicon-chevron-right:before {
content: "\e080";
}
.glyphicon-chevron-up:before {
content: "\e113";
}
.glyphicon-chevron-down:before {
content: "\e114";
}
.glyphicon-calendar:before {
content: "\e109";
}
.glyphicon-screenshot:before {
content: "\e087";
}
.glyphicon-trash:before {
content: "\e020";
}
.glyphicon-remove:before {
content: "\e014";
}
.bootstrap-datetimepicker-widget .btn {
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-size: 14px;
font-weight: normal;
line-height: 1.42857143;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-image: none;
border: 1px solid transparent;
border-radius: 4px;
}
.bootstrap-datetimepicker-widget.dropdown-menu {
position: absolute;
left: 0;
z-index: 1000;
display: none;
float: left;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
font-size: 14px;
text-align: left;
list-style: none;
background-color: #fff;
-webkit-background-clip: padding-box;
background-clip: padding-box;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, .15);
border-radius: 4px;
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
box-shadow: 0 6px 12px rgba(0, 0, 0, .175);
}
.bootstrap-datetimepicker-widget .list-unstyled {
padding-left: 0;
list-style: none;
}
.bootstrap-datetimepicker-widget .collapse {
display: none;
}
.bootstrap-datetimepicker-widget .collapse.in {
display: block;
}
/* fix for bootstrap4 */
.bootstrap-datetimepicker-widget .table-condensed > thead > tr > th,
.bootstrap-datetimepicker-widget .table-condensed > tbody > tr > td,
.bootstrap-datetimepicker-widget .table-condensed > tfoot > tr > td {
padding: 5px;
}

View File

@ -0,0 +1,55 @@
jQuery(function ($) {
var datepickerDict = {};
var isBootstrap4 = $.fn.collapse.Constructor.VERSION.split('.').shift() == "4";
function fixMonthEndDate(e, picker) {
e.date && picker.val().length && picker.val(e.date.endOf('month').format('YYYY-MM-DD'));
}
$("[dp_config]:not([disabled])").each(function (i, element) {
var $element = $(element), data = {};
try {
data = JSON.parse($element.attr('dp_config'));
}
catch (x) { }
if (data.id && data.options) {
data.$element = $element.datetimepicker(data.options);
data.datepickerdata = $element.data("DateTimePicker");
datepickerDict[data.id] = data;
data.$element.next('.input-group-addon').on('click', function(){
data.datepickerdata.show();
});
if(isBootstrap4){
data.$element.on("dp.show", function (e) {
$('.collapse.in').addClass('show');
});
}
}
});
$.each(datepickerDict, function (id, to_picker) {
if (to_picker.linked_to) {
var from_picker = datepickerDict[to_picker.linked_to];
from_picker.datepickerdata.maxDate(to_picker.datepickerdata.date() || false);
to_picker.datepickerdata.minDate(from_picker.datepickerdata.date() || false);
from_picker.$element.on("dp.change", function (e) {
to_picker.datepickerdata.minDate(e.date || false);
});
to_picker.$element.on("dp.change", function (e) {
if (to_picker.picker_type == 'MONTH') fixMonthEndDate(e, to_picker.$element);
from_picker.datepickerdata.maxDate(e.date || false);
});
if (to_picker.picker_type == 'MONTH') {
to_picker.$element.on("dp.hide", function (e) {
fixMonthEndDate(e, to_picker.$element);
});
fixMonthEndDate({ date: to_picker.datepickerdata.date() }, to_picker.$element);
}
}
});
if(isBootstrap4) {
$('body').on('show.bs.collapse','.bootstrap-datetimepicker-widget .collapse',function(e){
$(e.target).addClass('in');
});
$('body').on('hidden.bs.collapse','.bootstrap-datetimepicker-widget .collapse',function(e){
$(e.target).removeClass('in');
});
}
});

View File

@ -0,0 +1,34 @@
$(document).ready(function () {
$(".autocomplete").keyup(function(e) {
let target = $("#" + e.target.id);
let prefix = target.attr("id");
let api_url = target.attr("api_url");
let api_url_suffix = target.attr("api_url_suffix");
if (!api_url_suffix)
api_url_suffix = "";
let name_field = target.attr("name_field");
if (!name_field)
name_field = "name";
let input = target.val();
$.getJSON(api_url + "?format=json&search=^" + input + api_url_suffix, function(objects) {
let html = "";
objects.results.forEach(function (obj) {
html += li(prefix + "_" + obj.id, obj[name_field]);
});
$("#" + prefix + "_list").html(html);
objects.results.forEach(function (obj) {
$("#" + prefix + "_" + obj.id).click(function() {
target.val(obj[name_field]);
$("#" + prefix + "_pk").val(obj.id);
});
if (input === obj[name_field])
$("#" + prefix + "_pk").val(obj.id);
});
});
});
});

View File

@ -28,15 +28,35 @@ function addMsg(msg, alert_type) {
+ msg + "</div>\n"; + msg + "</div>\n";
msgDiv.html(html); msgDiv.html(html);
} }
/** /**
* add Muliple error message from err_obj * add Muliple error message from err_obj
* @param err_obj {error_code:erro_message} * @param errs_obj [{error_code:erro_message}]
*/ */
function errMsg(errs_obj){ function errMsg(errs_obj){
for (const err_msg of Object.values(errs_obj)) { for (const err_msg of Object.values(errs_obj)) {
addMsg(err_msg,'danger'); addMsg(err_msg,'danger');
} }
} }
var reloadWithTurbolinks = (function () {
var scrollPosition;
function reload () {
scrollPosition = [window.scrollX, window.scrollY];
Turbolinks.visit(window.location.toString(), { action: 'replace' })
}
document.addEventListener('turbolinks:load', function () {
if (scrollPosition) {
window.scrollTo.apply(window, scrollPosition);
scrollPosition = null
}
});
return reload;
})();
/** /**
* Reload the balance of the user on the right top corner * Reload the balance of the user on the right top corner
*/ */

View File

@ -0,0 +1,139 @@
{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% load pretty_money %}
{% load perms %}
{% block content %}
<div id="activity_info" class="card bg-light shadow">
<div class="card-header text-center">
<h4>{{ activity.name }}</h4>
</div>
<div class="card-body" id="profile_infos">
<dl class="row">
<dt class="col-xl-6">{% trans 'description'|capfirst %}</dt>
<dd class="col-xl-6"> {{ activity.description }}</dd>
<dt class="col-xl-6">{% trans 'type'|capfirst %}</dt>
<dd class="col-xl-6"> {{ activity.activity_type }}</dd>
<dt class="col-xl-6">{% trans 'start date'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.date_start }}</dd>
<dt class="col-xl-6">{% trans 'end date'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.date_end }}</dd>
{% if "view_"|has_perm:activity.creater %}
<dt class="col-xl-6">{% trans 'creater'|capfirst %}</dt>
<dd class="col-xl-6"><a href="{% url "member:user_detail" pk=activity.creater.pk %}">{{ activity.creater }}</a></dd>
{% endif %}
<dt class="col-xl-6">{% trans 'organizer'|capfirst %}</dt>
<dd class="col-xl-6"><a href="{% url "member:club_detail" pk=activity.organizer.pk %}">{{ activity.organizer }}</a></dd>
<dt class="col-xl-6">{% trans 'attendees club'|capfirst %}</dt>
<dd class="col-xl-6"><a href="{% url "member:club_detail" pk=activity.attendees_club.pk %}">{{ activity.attendees_club }}</a></dd>
<dt class="col-xl-6">{% trans 'can invite'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.activity_type.can_invite|yesno }}</dd>
{% if activity.activity_type.can_invite %}
<dt class="col-xl-6">{% trans 'guest entry fee'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.activity_type.guest_entry_fee|pretty_money }}</dd>
{% endif %}
<dt class="col-xl-6">{% trans 'valid'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.valid|yesno }}</dd>
<dt class="col-xl-6">{% trans 'opened'|capfirst %}</dt>
<dd class="col-xl-6">{{ activity.open|yesno }}</dd>
</dl>
</div>
<div class="card-footer text-center">
{% if activity.open and "change__open"|has_perm:activity %}
<a class="btn btn-warning btn-sm my-1" href="{% url 'activity:activity_entry' pk=activity.pk %}"> {% trans "Entry page" %}</a>
{% endif %}
{% if activity.valid and "change__open"|has_perm:activity %}
<a class="btn btn-warning btn-sm my-1" id="open_activity"> {% if activity.open %}{% trans "close"|capfirst %}{% else %}{% trans "open"|capfirst %}{% endif %}</a>
{% endif %}
{% if not activity.open and "change__valid"|has_perm:activity %}
<a class="btn btn-success btn-sm my-1" id="validate_activity"> {% if activity.valid %}{% trans "invalidate"|capfirst %}{% else %}{% trans "validate"|capfirst %}{% endif %}</a>
{% endif %}
{% if "view_"|has_perm:activity %}
<a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_update' pk=activity.pk %}"> {% trans "edit"|capfirst %}</a>
{% endif %}
{% if activity.activity_type.can_invite and not activity_started %}
<a class="btn btn-primary btn-sm my-1" href="{% url 'activity:activity_invite' pk=activity.pk %}"> {% trans "Invite" %}</a>
{% endif %}
</div>
</div>
{% if guests.data %}
<hr>
<h2>{% trans "Guests list" %}</h2>
<div id="guests_table">
{% render_table guests %}
</div>
{% endif %}
{% endblock %}
{% block extrajavascript %}
<script>
function remove_guest(guest_id) {
$.ajax({
url:"/api/activity/guest/" + guest_id + "/",
method:"DELETE",
headers: {"X-CSRFTOKEN": CSRF_TOKEN}
})
.done(function() {
addMsg('Invité supprimé','success');
$("#guests_table").load(location.href + " #guests_table");
})
.fail(function(xhr, textStatus, error) {
errMsg(xhr.responseJSON);
});
}
$("#open_activity").click(function() {
$.ajax({
url: "/api/activity/activity/{{ activity.pk }}/",
type: "PATCH",
dataType: "json",
headers: {
"X-CSRFTOKEN": CSRF_TOKEN
},
data: {
open: {{ activity.open|yesno:'false,true' }}
}
}).done(function () {
reloadWithTurbolinks();
}).fail(function (xhr) {
errMsg(xhr.responseJSON);
});
});
$("#validate_activity").click(function () {
console.log(42);
$.ajax({
url: "/api/activity/activity/{{ activity.pk }}/",
type: "PATCH",
dataType: "json",
headers: {
"X-CSRFTOKEN": CSRF_TOKEN
},
data: {
valid: {{ activity.valid|yesno:'false,true' }}
}
}).done(function () {
reloadWithTurbolinks();
}).fail(function (xhr) {
errMsg(xhr.responseJSON);
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,126 @@
{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% load render_table from django_tables2 %}
{% load pretty_money %}
{% load perms %}
{% block content %}
<a href="{% url "activity:activity_detail" pk=activity.pk %}">
<button class="btn btn-light">{% trans "Return to activity page" %}</button>
</a>
<input id="alias" type="text" class="form-control" placeholder="Nom/note ...">
<hr>
<div id="entry_table">
<h2 class="text-center">{{ entries.count }} {% if entries.count >= 2 %}{% trans "entries" %}{% else %}{% trans "entry" %}{% endif %}</h2>
{% render_table table %}
</div>
{% endblock %}
{% block extrajavascript %}
<script>
old_pattern = null;
alias_obj = $("#alias");
function reloadTable(force=false) {
let pattern = alias_obj.val();
if ((pattern === old_pattern || pattern === "") && !force)
return;
$("#entry_table").load(location.href + "?search=" + pattern.replace(" ", "%20") + " #entry_table", init);
refreshBalance();
}
alias_obj.keyup(reloadTable);
$(document).ready(init);
function init() {
$(".table-row").click(function(e) {
let target = e.target.parentElement;
target = $("#" + target.id);
let type = target.attr("data-type");
let id = target.attr("data-id");
let last_name = target.attr("data-last-name");
let first_name = target.attr("data-first-name");
if (type === "membership") {
$.post("/api/activity/entry/?format=json", {
csrfmiddlewaretoken: CSRF_TOKEN,
activity: {{ activity.id }},
note: id,
guest: null
}).done(function () {
addMsg("Entrée effectuée !", "success");
reloadTable(true);
}).fail(function(xhr) {
errMsg(xhr.responseJSON);
});
}
else {
let line_obj = $("#buttons_guest_" + id);
if (line_obj.length || target.attr('class').includes("table-success")) {
line_obj.remove();
return;
}
let tr = "<tr class='text-center'>" +
"<td id='buttons_guest_" + id + "' style='table-danger center' colspan='5'>" +
"<button id='transaction_guest_" + id + "' class='btn btn-secondary'>Payer avec la note de l'hôte</button> " +
"<button id='transaction_guest_" + id + "_especes' class='btn btn-secondary'>Payer en espèces</button> " +
"<button id='transaction_guest_" + id + "_cb' class='btn btn-secondary'>Payer en CB</button></td>" +
"<tr>";
$(tr).insertAfter(target);
let makeTransaction = function() {
$.post("/api/activity/entry/?format=json", {
csrfmiddlewaretoken: CSRF_TOKEN,
activity: {{ activity.id }},
note: target.attr("data-inviter"),
guest: id
}).done(function () {
addMsg("Entrée effectuée !", "success");
reloadTable(true);
}).fail(function (xhr) {
errMsg(xhr.responseJSON);
});
};
let credit = function(credit_id, credit_name) {
return function() {
$.post("/api/note/transaction/transaction/",
{
"csrfmiddlewaretoken": CSRF_TOKEN,
"quantity": 1,
"amount": {{ activity.activity_type.guest_entry_fee }},
"reason": "Crédit " + credit_name + " (invitation {{ activity.name }})",
"valid": true,
"polymorphic_ctype": {{ notespecial_ctype }},
"resourcetype": "SpecialTransaction",
"source": credit_id,
"destination": target.attr('data-inviter'),
"last_name": last_name,
"first_name": first_name,
"bank": ""
}).done(function () {
makeTransaction();
reset();
}).fail(function (xhr) {
errMsg(xhr.responseJSON);
});
};
};
$("#transaction_guest_" + id).click(makeTransaction);
$("#transaction_guest_" + id + "_especes").click(credit(1, "espèces"));
$("#transaction_guest_" + id + "_cb").click(credit(2, "carte bancaire"));
}
});
}
</script>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends "base.html" %}
{% load static %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block content %}
<form method="post">
{% csrf_token %}
{{form|crispy}}
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
</form>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends "base.html" %}
{% load render_table from django_tables2 %}
{% load i18n crispy_forms_tags %}
{% block content %}
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
</form>
{% endblock %}
{% block extrajavascript %}
<script type="text/javascript">
</script>
{% endblock %}

View File

@ -0,0 +1,33 @@
{% extends "base.html" %}
{% load render_table from django_tables2 %}
{% load i18n crispy_forms_tags%}
{% block content %}
<h2>{% trans "Upcoming activities" %}</h2>
{% if upcoming.data %}
{% render_table upcoming %}
{% else %}
<div class="alert alert-warning">
{% trans "There is no planned activity." %}
</div>
{% endif %}
<a class="btn btn-primary" href="{% url 'activity:activity_create' %}">{% trans 'New activity' %}</a>
<hr>
<h2>{% trans "All activities" %}</h2>
{% render_table table %}
{% endblock %}
{% block extrajavascript %}
<script type="text/javascript">
$(document).ready(function($) {
$(".table-row").click(function() {
window.document.location = $(this).data("href");
});
});
</script>
{% endblock %}

View File

@ -91,7 +91,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endif %} {% endif %}
{% if "activity.activity"|not_empty_model_list %} {% if "activity.activity"|not_empty_model_list %}
<li class="nav-item active"> <li class="nav-item active">
<a class="nav-link" href="#"><i class="fa fa-calendar"></i> {% trans 'Activities' %}</a> <a class="nav-link" href="{% url 'activity:activity_list' %}"><i class="fa fa-calendar"></i> {% trans 'Activities' %}</a>
</li> </li>
{% endif %} {% endif %}
{% if "treasury.invoice"|not_empty_model_change_list %} {% if "treasury.invoice"|not_empty_model_change_list %}

View File

@ -0,0 +1,6 @@
<div class="input-group date">
{% include "bootstrap_datepicker_plus/input.html" %}
<div class="input-group-addon input-group-append" data-target="#datetimepicker1" data-toggle="datetimepickerv">
<div class="input-group-text"><i class="glyphicon glyphicon-calendar"></i></div>
</div>
</div>

View File

@ -0,0 +1,4 @@
<input type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None and widget.value != "" %}
value="{{ widget.value }}"{% endif %}{% for name, value in widget.attrs.items %}{% ifnotequal value False %}
{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}
{% endifnotequal %}{% endfor %}/>

View File

@ -0,0 +1,6 @@
<div class="input-group date">
{% include "bootstrap_datepicker_plus/input.html" %}
<div class="input-group-addon input-group-append" data-target="#datetimepicker1" data-toggle="datetimepickerv">
<div class="input-group-text"><i class="glyphicon glyphicon-time"></i></div>
</div>
</div>

View File

@ -0,0 +1,9 @@
<input type="hidden" name="{{ widget.name }}" {% if widget.attrs.model_pk %}value="{{ widget.attrs.model_pk }}"{% endif %} id="{{ widget.attrs.id }}_pk">
<input type="text"
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
name="{{ widget.name }}_name" autocomplete="off"
{% for name, value in widget.attrs.items %}
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
{% endfor %}>
<ul class="list-group list-group-flush" id="{{ widget.attrs.id }}_list">
</ul>

View File

@ -13,8 +13,10 @@
<dt class="col-xl-6">{% trans 'name'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'name'|capfirst %}</dt>
<dd class="col-xl-6">{{ club.name}}</dd> <dd class="col-xl-6">{{ club.name}}</dd>
{% if club.parent_club %}
<dt class="col-xl-6"><a href="{% url 'member:club_detail' club.parent_club.pk %}">{% trans 'Club Parent'|capfirst %}</a></dt> <dt class="col-xl-6"><a href="{% url 'member:club_detail' club.parent_club.pk %}">{% trans 'Club Parent'|capfirst %}</a></dt>
<dd class="col-xl-6"> {{ club.parent_club.name}}</dd> <dd class="col-xl-6"> {{ club.parent_club.name}}</dd>
{% endif %}
<dt class="col-xl-6">{% trans 'membership start'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'membership start'|capfirst %}</dt>
<dd class="col-xl-6">{{ club.membership_start }}</dd> <dd class="col-xl-6">{{ club.membership_start }}</dd>

View File

@ -0,0 +1,11 @@
<div class="input-group">
<input class="form-control mx-auto d-block" type="number" min="0" step="0.01"
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
name="{{ widget.name }}"
{% for name, value in widget.attrs.items %}
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
{% endfor %}>
<div class="input-group-append">
<span class="input-group-text"></span>
</div>
</div>

View File

@ -126,12 +126,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
<div class="form-row"> <div class="form-row">
<div class="form-group col-md-6"> <div class="form-group col-md-6">
<label for="amount">{% trans "Amount" %} :</label> <label for="amount">{% trans "Amount" %} :</label>
<div class="input-group"> {% include "note/amount_input.html" with widget=amount_widget %}
<input class="form-control mx-auto d-block" type="number" min="0" step="0.01" id="amount" />
<div class="input-group-append">
<span class="input-group-text"></span>
</div>
</div>
</div> </div>
<div class="form-group col-md-6"> <div class="form-group col-md-6">

View File

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags pretty_money %} {% load crispy_forms_tags %}
{% block content %} {% block content %}
<p><a class="btn btn-default" href="{% url 'treasury:invoice_list' %}">{% trans "Invoices list" %}</a></p> <p><a class="btn btn-default" href="{% url 'treasury:invoice_list' %}">{% trans "Invoices list" %}</a></p>
<form method="post" action=""> <form method="post" action="">
@ -26,18 +26,8 @@
{% endif %} {% endif %}
<tr class="row-formset"> <tr class="row-formset">
<td>{{ form.designation }}</td> <td>{{ form.designation }}</td>
<td>{{ form.quantity }} </td> <td>{{ form.quantity }}</td>
<td> <td>{{ form.amount }}</td>
{# Use custom input for amount, with the € symbol #}
<div class="input-group">
<input type="number" name="product_set-{{ forloop.counter0 }}-amount" step="0.01"
id="id_product_set-{{ forloop.counter0 }}-amount"
value="{{ form.instance.amount|cents_to_euros }}">
<div class="input-group-append">
<span class="input-group-text"></span>
</div>
</div>
</td>
{# These fields are hidden but handled by the formset to link the id and the invoice id #} {# These fields are hidden but handled by the formset to link the id and the invoice id #}
{{ form.invoice }} {{ form.invoice }}
{{ form.id }} {{ form.id }}
@ -64,15 +54,7 @@
<tr class="row-formset"> <tr class="row-formset">
<td>{{ formset.empty_form.designation }}</td> <td>{{ formset.empty_form.designation }}</td>
<td>{{ formset.empty_form.quantity }} </td> <td>{{ formset.empty_form.quantity }} </td>
<td> <td>{{ formset.empty_form.amount }}</td>
<div class="input-group">
<input type="number" name="product_set-__prefix__-amount" step="0.01"
id="id_product_set-__prefix__-amount">
<div class="input-group-append">
<span class="input-group-text"></span>
</div>
</div>
</td>
{{ formset.empty_form.invoice }} {{ formset.empty_form.invoice }}
{{ formset.empty_form.id }} {{ formset.empty_form.id }}
</tr> </tr>