mirror of
https://gitlab.crans.org/bde/nk20
synced 2024-12-23 07:52:23 +00:00
Merge branch 'activity' into 'master'
Activity management See merge request bde/nk20!32
This commit is contained in:
commit
c8854cf45d
@ -3,7 +3,7 @@
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from ..models import ActivityType, Activity, Guest
|
||||
from ..models import ActivityType, Activity, Guest, Entry, GuestTransaction
|
||||
|
||||
|
||||
class ActivityTypeSerializer(serializers.ModelSerializer):
|
||||
@ -37,3 +37,25 @@ class GuestSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Guest
|
||||
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__'
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# 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):
|
||||
@ -11,3 +11,4 @@ def register_activity_urls(router, path):
|
||||
router.register(path + '/activity', ActivityViewSet)
|
||||
router.register(path + '/type', ActivityTypeViewSet)
|
||||
router.register(path + '/guest', GuestViewSet)
|
||||
router.register(path + '/entry', EntryViewSet)
|
||||
|
@ -5,8 +5,8 @@ from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import SearchFilter
|
||||
from api.viewsets import ReadProtectedModelViewSet
|
||||
|
||||
from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer
|
||||
from ..models import ActivityType, Activity, Guest
|
||||
from .serializers import ActivityTypeSerializer, ActivitySerializer, GuestSerializer, EntrySerializer
|
||||
from ..models import ActivityType, Activity, Guest, Entry
|
||||
|
||||
|
||||
class ActivityTypeViewSet(ReadProtectedModelViewSet):
|
||||
@ -42,4 +42,16 @@ class GuestViewSet(ReadProtectedModelViewSet):
|
||||
queryset = Guest.objects.all()
|
||||
serializer_class = GuestSerializer
|
||||
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
84
apps/activity/forms.py
Normal 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 ...',
|
||||
},
|
||||
),
|
||||
}
|
@ -1,9 +1,13 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# 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.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from note.models import NoteUser, Transaction
|
||||
|
||||
|
||||
class ActivityType(models.Model):
|
||||
@ -44,39 +48,131 @@ class Activity(models.Model):
|
||||
verbose_name=_('name'),
|
||||
max_length=255,
|
||||
)
|
||||
|
||||
description = models.TextField(
|
||||
verbose_name=_('description'),
|
||||
)
|
||||
|
||||
activity_type = models.ForeignKey(
|
||||
ActivityType,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
verbose_name=_('type'),
|
||||
)
|
||||
|
||||
creater = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_("user"),
|
||||
)
|
||||
|
||||
organizer = models.ForeignKey(
|
||||
'member.Club',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
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(
|
||||
'member.Club',
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
verbose_name=_('attendees club'),
|
||||
)
|
||||
|
||||
date_start = models.DateTimeField(
|
||||
verbose_name=_('start date'),
|
||||
)
|
||||
|
||||
date_end = models.DateTimeField(
|
||||
verbose_name=_('end date'),
|
||||
)
|
||||
|
||||
valid = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_('valid'),
|
||||
)
|
||||
|
||||
open = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_('open'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("activity")
|
||||
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):
|
||||
"""
|
||||
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,
|
||||
related_name='+',
|
||||
)
|
||||
name = models.CharField(
|
||||
|
||||
last_name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("last name"),
|
||||
)
|
||||
|
||||
first_name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("first name"),
|
||||
)
|
||||
|
||||
inviter = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL,
|
||||
NoteUser,
|
||||
on_delete=models.PROTECT,
|
||||
related_name='+',
|
||||
)
|
||||
entry = models.DateTimeField(
|
||||
null=True,
|
||||
)
|
||||
entry_transaction = models.ForeignKey(
|
||||
'note.Transaction',
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name='guests',
|
||||
verbose_name=_("inviter"),
|
||||
)
|
||||
|
||||
@property
|
||||
def has_entry(self):
|
||||
try:
|
||||
if self.entry:
|
||||
return True
|
||||
return False
|
||||
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:
|
||||
verbose_name = _("guest")
|
||||
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
108
apps/activity/tables.py
Normal 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
17
apps/activity/urls.py
Normal 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
153
apps/activity/views.py
Normal 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
|
@ -4,10 +4,10 @@
|
||||
from crispy_forms.bootstrap import Div
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout
|
||||
from dal import autocomplete
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
|
||||
from django.contrib.auth.models import User
|
||||
from note_kfet.inputs import Autocomplete
|
||||
from permission.models import PermissionMask
|
||||
|
||||
from .models import Profile, Club, Membership
|
||||
@ -63,11 +63,12 @@ class MembershipForm(forms.ModelForm):
|
||||
# et récupère les noms d'utilisateur valides
|
||||
widgets = {
|
||||
'user':
|
||||
autocomplete.ModelSelect2(
|
||||
url='member:user_autocomplete',
|
||||
Autocomplete(
|
||||
User,
|
||||
attrs={
|
||||
'data-placeholder': 'Nom ...',
|
||||
'data-minimum-input-length': 1,
|
||||
'api_url': '/api/user/',
|
||||
'name_field': 'username',
|
||||
'placeholder': 'Nom ...',
|
||||
},
|
||||
),
|
||||
}
|
||||
|
@ -21,6 +21,4 @@ urlpatterns = [
|
||||
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('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"),
|
||||
]
|
||||
|
@ -4,24 +4,19 @@
|
||||
import io
|
||||
|
||||
from PIL import Image
|
||||
from dal import autocomplete
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.views import LoginView
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import redirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView, DeleteView
|
||||
from django.views.generic import CreateView, DetailView, UpdateView, TemplateView
|
||||
from django.views.generic.edit import FormMixin
|
||||
from django_tables2.views import SingleTableView
|
||||
from rest_framework.authtoken.models import Token
|
||||
from note.forms import ImageForm
|
||||
#from note.forms import AliasForm, ImageForm
|
||||
from note.models import Alias, NoteUser
|
||||
from note.models.transactions import Transaction
|
||||
from note.tables import HistoryTable, AliasTable
|
||||
@ -168,12 +163,12 @@ class UserListView(LoginRequiredMixin, SingleTableView):
|
||||
context["filter"] = self.filter
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class ProfileAliasView(LoginRequiredMixin, DetailView):
|
||||
model = User
|
||||
template_name = 'member/profile_alias.html'
|
||||
context_object_name = 'user_object'
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
note = context['object'].note
|
||||
@ -257,28 +252,6 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
|
||||
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 #
|
||||
# ******************************* #
|
||||
@ -294,7 +267,7 @@ class ClubCreateView(LoginRequiredMixin, CreateView):
|
||||
|
||||
def form_valid(self, form):
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
|
||||
class ClubListView(LoginRequiredMixin, SingleTableView):
|
||||
"""
|
||||
@ -326,11 +299,12 @@ class ClubDetailView(LoginRequiredMixin, DetailView):
|
||||
context['member_list'] = club_member
|
||||
return context
|
||||
|
||||
|
||||
class ClubAliasView(LoginRequiredMixin, DetailView):
|
||||
model = Club
|
||||
template_name = 'member/club_alias.html'
|
||||
context_object_name = 'club'
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
note = context['object'].note
|
||||
@ -364,6 +338,7 @@ class ClubAddMemberView(LoginRequiredMixin, CreateView):
|
||||
return super().get_queryset().filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view")
|
||||
| PermissionBackend.filter_queryset(self.request.user, Membership,
|
||||
"change"))
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
club = Club.objects.get(pk=self.kwargs["pk"])
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
@ -163,6 +163,7 @@ class SpecialTransactionSerializer(serializers.ModelSerializer):
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
class TransactionPolymorphicSerializer(PolymorphicSerializer):
|
||||
model_serializer_mapping = {
|
||||
Transaction: TransactionSerializer,
|
||||
@ -171,5 +172,12 @@ class TransactionPolymorphicSerializer(PolymorphicSerializer):
|
||||
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:
|
||||
model = Transaction
|
||||
|
@ -8,7 +8,6 @@ from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
|
||||
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
||||
|
||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, TemplateCategorySerializer, \
|
||||
@ -25,7 +24,8 @@ class NotePolymorphicViewSet(ReadOnlyProtectedModelViewSet):
|
||||
"""
|
||||
queryset = Note.objects.all()
|
||||
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', ]
|
||||
ordering_fields = ['alias__name', 'alias__normalized_name']
|
||||
|
||||
@ -60,19 +60,19 @@ class AliasViewSet(ReadProtectedModelViewSet):
|
||||
def get_serializer_class(self):
|
||||
serializer_class = self.serializer_class
|
||||
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',))
|
||||
return serializer_class
|
||||
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
instance = self.get_object()
|
||||
try:
|
||||
self.perform_destroy(instance)
|
||||
except ValidationError as 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)
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Parse query and apply filters.
|
||||
|
@ -1,12 +1,12 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from dal import autocomplete
|
||||
from django import forms
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from note_kfet.inputs import Autocomplete
|
||||
|
||||
from .models import Alias
|
||||
from .models import TransactionTemplate
|
||||
from .models import TransactionTemplate, NoteClub
|
||||
|
||||
|
||||
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}
|
||||
widgets = {
|
||||
'destination':
|
||||
autocomplete.ModelSelect2(
|
||||
url='note:note_autocomplete',
|
||||
Autocomplete(
|
||||
NoteClub,
|
||||
attrs={
|
||||
'data-placeholder': 'Note ...',
|
||||
'data-minimum-input-length': 1,
|
||||
'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(NoteClub).pk),
|
||||
'placeholder': 'Note ...',
|
||||
},
|
||||
),
|
||||
}
|
||||
|
@ -242,10 +242,10 @@ class Alias(models.Model):
|
||||
pass
|
||||
self.normalized_name = normalized_name
|
||||
|
||||
def save(self,*args,**kwargs):
|
||||
def save(self, *args, **kwargs):
|
||||
self.normalized_name = self.normalize(self.name)
|
||||
super().save(*args,**kwargs)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.name == str(self.note):
|
||||
raise ValidationError(_("You can't delete your main alias."),
|
||||
|
@ -2,7 +2,6 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import F
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
@ -106,9 +106,8 @@ DELETE_TEMPLATE = """
|
||||
class AliasTable(tables.Table):
|
||||
class Meta:
|
||||
attrs = {
|
||||
'class':
|
||||
'table table condensed table-striped table-hover',
|
||||
'id':"alias_table"
|
||||
'class': 'table table condensed table-striped table-hover',
|
||||
'id': "alias_table"
|
||||
}
|
||||
model = Alias
|
||||
fields = ('name',)
|
||||
@ -118,9 +117,8 @@ class AliasTable(tables.Table):
|
||||
name = tables.Column(attrs={'td': {'class': 'text-center'}})
|
||||
|
||||
delete_col = tables.TemplateColumn(template_code=DELETE_TEMPLATE,
|
||||
extra_context={"delete_trans": _('delete')},
|
||||
attrs={'td': {'class': 'col-sm-1'}})
|
||||
|
||||
extra_context={"delete_trans": _('delete')},
|
||||
attrs={'td': {'class': 'col-sm-1'}})
|
||||
|
||||
|
||||
class ButtonTable(tables.Table):
|
||||
@ -145,8 +143,8 @@ class ButtonTable(tables.Table):
|
||||
accessor='pk')
|
||||
|
||||
delete_col = tables.TemplateColumn(template_code=DELETE_TEMPLATE,
|
||||
extra_context={"delete_trans": _('delete')},
|
||||
attrs={'td': {'class': 'col-sm-1'}})
|
||||
extra_context={"delete_trans": _('delete')},
|
||||
attrs={'td': {'class': 'col-sm-1'}})
|
||||
|
||||
def render_amount(self, value):
|
||||
return pretty_money(value)
|
||||
|
@ -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.filter('pretty_money', pretty_money)
|
||||
register.filter('cents_to_euros', cents_to_euros)
|
||||
|
@ -4,7 +4,6 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
from .models import Note
|
||||
|
||||
app_name = 'note'
|
||||
urlpatterns = [
|
||||
@ -13,7 +12,4 @@ urlpatterns = [
|
||||
path('buttons/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'),
|
||||
path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'),
|
||||
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'),
|
||||
]
|
||||
|
@ -1,18 +1,17 @@
|
||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from dal import autocomplete
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import CreateView, UpdateView
|
||||
from django_tables2 import SingleTableView
|
||||
from django.urls import reverse_lazy
|
||||
from note_kfet.inputs import AmountInput
|
||||
from permission.backends import PermissionBackend
|
||||
|
||||
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 .tables import HistoryTable, ButtonTable
|
||||
|
||||
@ -40,6 +39,7 @@ class TransactionCreateView(LoginRequiredMixin, SingleTableView):
|
||||
"""
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['title'] = _('Transfer money')
|
||||
context['amount_widget'] = AmountInput(attrs={"id": "amount"})
|
||||
context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk
|
||||
context['special_polymorphic_ctype'] = ContentType.objects.get_for_model(SpecialTransaction).pk
|
||||
context['special_types'] = NoteSpecial.objects.order_by("special_type").all()
|
||||
@ -47,62 +47,6 @@ class TransactionCreateView(LoginRequiredMixin, SingleTableView):
|
||||
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):
|
||||
"""
|
||||
Create TransactionTemplate
|
||||
|
@ -55,6 +55,20 @@
|
||||
"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",
|
||||
"pk": 1,
|
||||
@ -574,6 +588,201 @@
|
||||
"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",
|
||||
"pk": 1,
|
||||
@ -613,7 +822,12 @@
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18
|
||||
18,
|
||||
34,
|
||||
35,
|
||||
36,
|
||||
39,
|
||||
40
|
||||
]
|
||||
}
|
||||
},
|
||||
@ -649,5 +863,22 @@
|
||||
33
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.rolepermissions",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"role": 9,
|
||||
"permissions": [
|
||||
37,
|
||||
38,
|
||||
41,
|
||||
42,
|
||||
43,
|
||||
44,
|
||||
45,
|
||||
46
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -48,6 +48,11 @@ def not_empty_model_change_list(model_name):
|
||||
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.filter('not_empty_model_list', not_empty_model_list)
|
||||
register.filter('not_empty_model_change_list', not_empty_model_change_list)
|
||||
register.filter('has_perm', has_perm)
|
||||
|
@ -7,6 +7,7 @@ from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Submit
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from note_kfet.inputs import DatePickerInput, AmountInput
|
||||
|
||||
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
|
||||
date = forms.DateField(
|
||||
initial=datetime.date.today,
|
||||
widget=forms.TextInput(attrs={'type': 'date'})
|
||||
widget=DatePickerInput()
|
||||
)
|
||||
|
||||
def clean_date(self):
|
||||
@ -30,12 +31,21 @@ class InvoiceForm(forms.ModelForm):
|
||||
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
|
||||
# its products. The FormSet will search automatically the ForeignKey in the Product model.
|
||||
ProductFormSet = forms.inlineformset_factory(
|
||||
Invoice,
|
||||
Product,
|
||||
fields='__all__',
|
||||
form=ProductForm,
|
||||
extra=1,
|
||||
)
|
||||
|
||||
|
@ -50,18 +50,8 @@ class InvoiceCreateView(LoginRequiredMixin, CreateView):
|
||||
def form_valid(self, 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
|
||||
formset = ProductFormSet(kwargs, instance=form.instance)
|
||||
formset = ProductFormSet(self.request.POST, instance=form.instance)
|
||||
if formset.is_valid():
|
||||
for f in formset:
|
||||
# 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):
|
||||
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
|
||||
|
||||
formset = ProductFormSet(kwargs, instance=form.instance)
|
||||
formset = ProductFormSet(self.request.POST, instance=form.instance)
|
||||
saved = []
|
||||
# For each product, we save it
|
||||
if formset.is_valid():
|
||||
|
@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -18,72 +18,182 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/activity/models.py:19 apps/activity/models.py:44
|
||||
#: apps/member/models.py:63 apps/member/models.py:114
|
||||
#: apps/note/models/notes.py:188 apps/note/models/transactions.py:25
|
||||
#: apps/note/models/transactions.py:45 apps/note/models/transactions.py:232
|
||||
#: templates/member/profile_detail.html:15
|
||||
#: apps/activity/forms.py:45 apps/activity/models.py:217
|
||||
msgid "You can't invite someone once the activity is started."
|
||||
msgstr ""
|
||||
|
||||
#: 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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/activity/models.py:23
|
||||
#: apps/activity/models.py:27 templates/activity/activity_detail.html:39
|
||||
msgid "can invite"
|
||||
msgstr ""
|
||||
|
||||
#: apps/activity/models.py:26
|
||||
#: apps/activity/models.py:30 templates/activity/activity_detail.html:43
|
||||
msgid "guest entry fee"
|
||||
msgstr ""
|
||||
|
||||
#: apps/activity/models.py:30
|
||||
#: apps/activity/models.py:34
|
||||
msgid "activity type"
|
||||
msgstr ""
|
||||
|
||||
#: apps/activity/models.py:31
|
||||
#: apps/activity/models.py:35
|
||||
msgid "activity types"
|
||||
msgstr ""
|
||||
|
||||
#: apps/activity/models.py:48 apps/note/models/transactions.py:70
|
||||
#: apps/permission/models.py:91
|
||||
#: apps/activity/models.py:53 apps/note/models/transactions.py:69
|
||||
#: apps/permission/models.py:90 templates/activity/activity_detail.html:16
|
||||
msgid "description"
|
||||
msgstr ""
|
||||
|
||||
#: apps/activity/models.py:54 apps/note/models/notes.py:164
|
||||
#: apps/note/models/transactions.py:63
|
||||
#: apps/activity/models.py:60 apps/note/models/notes.py:164
|
||||
#: apps/note/models/transactions.py:62
|
||||
#: templates/activity/activity_detail.html:19
|
||||
msgid "type"
|
||||
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"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/activity/models.py:69
|
||||
#: apps/activity/models.py:93 templates/activity/activity_detail.html:22
|
||||
msgid "start date"
|
||||
msgstr ""
|
||||
|
||||
#: apps/activity/models.py:72
|
||||
#: apps/activity/models.py:97 templates/activity/activity_detail.html:25
|
||||
msgid "end date"
|
||||
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"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/activity/models.py:109
|
||||
#: apps/activity/models.py:242
|
||||
msgid "guests"
|
||||
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
|
||||
msgid "API"
|
||||
msgstr ""
|
||||
@ -92,10 +202,6 @@ msgstr ""
|
||||
msgid "Logs"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/models.py:21 apps/note/models/notes.py:117
|
||||
msgid "user"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/models.py:27
|
||||
msgid "IP Address"
|
||||
msgstr ""
|
||||
@ -120,11 +226,12 @@ msgstr ""
|
||||
msgid "create"
|
||||
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"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
@ -144,139 +251,130 @@ msgstr ""
|
||||
msgid "member"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:25
|
||||
#: apps/member/models.py:26
|
||||
msgid "phone number"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:32
|
||||
#: apps/member/models.py:33
|
||||
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:44
|
||||
#: apps/member/models.py:45
|
||||
msgid "paid"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:49 apps/member/models.py:50
|
||||
#: apps/member/models.py:50 apps/member/models.py:51
|
||||
msgid "user profile"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:68
|
||||
#: apps/member/models.py:69 templates/member/club_info.html:36
|
||||
msgid "email"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:77
|
||||
#: apps/member/models.py:85 templates/member/club_info.html:27
|
||||
msgid "membership duration"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:78
|
||||
#: apps/member/models.py:86
|
||||
msgid "The longest time a membership can last (NULL = infinite)."
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:83
|
||||
#: apps/member/models.py:91 templates/member/club_info.html:21
|
||||
msgid "membership start"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:84
|
||||
#: apps/member/models.py:92
|
||||
msgid "How long after January 1st the members can renew their membership."
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:89
|
||||
#: apps/member/models.py:97 templates/member/club_info.html:24
|
||||
msgid "membership end"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:90
|
||||
#: apps/member/models.py:98
|
||||
msgid ""
|
||||
"How long the membership can last after January 1st of the next year after "
|
||||
"members can renew their membership."
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:96 apps/note/models/notes.py:139
|
||||
#: apps/member/models.py:104 apps/note/models/notes.py:139
|
||||
msgid "club"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:97
|
||||
#: apps/member/models.py:105
|
||||
msgid "clubs"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:120 apps/permission/models.py:276
|
||||
#: apps/member/models.py:128 apps/permission/models.py:275
|
||||
msgid "role"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:121
|
||||
#: apps/member/models.py:129
|
||||
msgid "roles"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:145
|
||||
#: apps/member/models.py:153
|
||||
msgid "membership starts on"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:148
|
||||
#: apps/member/models.py:156
|
||||
msgid "membership ends on"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:152
|
||||
#: apps/member/models.py:160
|
||||
msgid "fee"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/models.py:163
|
||||
#: apps/member/models.py:177
|
||||
msgid "memberships"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/views.py:93
|
||||
#: apps/member/views.py:89
|
||||
msgid "An alias with a similar name already exists."
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/views.py:146
|
||||
#, 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
|
||||
#: apps/note/admin.py:120 apps/note/models/transactions.py:94
|
||||
msgid "source"
|
||||
msgstr ""
|
||||
|
||||
#: 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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/apps.py:14 apps/note/models/notes.py:58
|
||||
msgid "note"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/forms.py:20
|
||||
msgid "New Alias"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/forms.py:25
|
||||
#: apps/note/forms.py:14
|
||||
msgid "select an image"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/forms.py:26
|
||||
#: apps/note/forms.py:15
|
||||
msgid "Maximal size: 2MB"
|
||||
msgstr ""
|
||||
|
||||
@ -310,7 +408,7 @@ msgstr ""
|
||||
msgid "display image"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
@ -318,10 +416,6 @@ msgstr ""
|
||||
msgid "notes"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/notes.py:67
|
||||
msgid "Note"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/notes.py:77 apps/note/models/notes.py:101
|
||||
msgid "This alias is already taken."
|
||||
msgstr ""
|
||||
@ -368,7 +462,8 @@ msgstr ""
|
||||
msgid "alias"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
@ -380,106 +475,114 @@ msgstr ""
|
||||
msgid "An alias with a similar name already exists: {} "
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/notes.py:247
|
||||
#: apps/note/models/notes.py:251
|
||||
msgid "You can't delete your main alias."
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:31
|
||||
#: apps/note/models/transactions.py:30
|
||||
msgid "transaction category"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:32
|
||||
#: apps/note/models/transactions.py:31
|
||||
msgid "transaction categories"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:48
|
||||
#: apps/note/models/transactions.py:47
|
||||
msgid "A template with this name already exist"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:58
|
||||
#: apps/note/models/transactions.py:57
|
||||
msgid "in centimes"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:76
|
||||
#: apps/note/models/transactions.py:75
|
||||
msgid "transaction template"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:77
|
||||
#: apps/note/models/transactions.py:76
|
||||
msgid "transaction templates"
|
||||
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
|
||||
msgid "used alias"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:122
|
||||
#: apps/note/models/transactions.py:121
|
||||
msgid "quantity"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:130
|
||||
#: apps/note/models/transactions.py:129
|
||||
msgid "reason"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:135
|
||||
msgid "valid"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:140 apps/note/tables.py:95
|
||||
#: apps/note/models/transactions.py:139 apps/note/tables.py:95
|
||||
msgid "invalidity reason"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:147
|
||||
#: apps/note/models/transactions.py:146
|
||||
msgid "transaction"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:148
|
||||
#: apps/note/models/transactions.py:147
|
||||
msgid "transactions"
|
||||
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:145
|
||||
#: templates/note/transaction_form.html:140
|
||||
msgid "Transfer"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:188
|
||||
#: apps/note/models/transactions.py:221
|
||||
msgid "Template"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:203
|
||||
#: apps/note/models/transactions.py:236
|
||||
msgid "first_name"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:208
|
||||
#: apps/note/models/transactions.py:241
|
||||
msgid "bank"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/models/transactions.py:231
|
||||
#: apps/note/models/transactions.py:264
|
||||
msgid "membership transactions"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/views.py:145 templates/base.html:79
|
||||
#: apps/note/views.py:102 templates/base.html:79
|
||||
msgid "Consumptions"
|
||||
msgstr ""
|
||||
|
||||
@ -501,41 +604,35 @@ msgstr ""
|
||||
msgid "Specifying field applies only to view and change permission types."
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/apps.py:11 templates/base.html:102
|
||||
#: apps/treasury/apps.py:12 templates/base.html:99
|
||||
msgid "Treasury"
|
||||
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/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"
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/forms.py:58
|
||||
#: apps/treasury/forms.py:86
|
||||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/forms.py:65
|
||||
#: apps/treasury/forms.py:95
|
||||
msgid "Remittance is already closed."
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/forms.py:70
|
||||
#: apps/treasury/forms.py:100
|
||||
msgid "You can't change the type of the remittance."
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/forms.py:84
|
||||
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
|
||||
#: apps/treasury/forms.py:124 templates/note/transaction_form.html:98
|
||||
msgid "Bank"
|
||||
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/treasury/remittance_form.html:18
|
||||
msgid "Amount"
|
||||
@ -589,10 +686,6 @@ msgstr ""
|
||||
msgid "Date"
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/models.py:126
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/models.py:131
|
||||
msgid "Comment"
|
||||
msgstr ""
|
||||
@ -601,38 +694,38 @@ msgstr ""
|
||||
msgid "Closed"
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/models.py:159
|
||||
#: apps/treasury/models.py:169
|
||||
msgid "Remittance #{:d}: {}"
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/models.py:178 apps/treasury/tables.py:64
|
||||
#: apps/treasury/tables.py:72 templates/treasury/invoice_list.html:13
|
||||
#: apps/treasury/models.py:188 apps/treasury/tables.py:76
|
||||
#: apps/treasury/tables.py:84 templates/treasury/invoice_list.html:13
|
||||
#: templates/treasury/remittance_list.html:13
|
||||
msgid "Remittance"
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/tables.py:16
|
||||
#: apps/treasury/tables.py:19
|
||||
msgid "Invoice #{:d}"
|
||||
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
|
||||
msgid "Invoice"
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/tables.py:38
|
||||
#: apps/treasury/tables.py:45
|
||||
msgid "Transaction count"
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/tables.py:43 apps/treasury/tables.py:45
|
||||
#: apps/treasury/tables.py:50 apps/treasury/tables.py:52
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/tables.py:66
|
||||
#: apps/treasury/tables.py:78
|
||||
msgid "Add"
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/tables.py:74
|
||||
#: apps/treasury/tables.py:86
|
||||
msgid "Remove"
|
||||
msgstr ""
|
||||
|
||||
@ -643,30 +736,86 @@ msgid ""
|
||||
"again unless your session expires or you logout."
|
||||
msgstr ""
|
||||
|
||||
#: note_kfet/settings/base.py:151
|
||||
#: note_kfet/settings/base.py:152
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: note_kfet/settings/base.py:152
|
||||
#: note_kfet/settings/base.py:153
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: note_kfet/settings/base.py:153
|
||||
#: note_kfet/settings/base.py:154
|
||||
msgid "French"
|
||||
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
|
||||
msgid "The ENS Paris-Saclay BDE note."
|
||||
msgstr ""
|
||||
|
||||
#: templates/base.html:87
|
||||
#: templates/base.html:89
|
||||
msgid "Clubs"
|
||||
msgstr ""
|
||||
|
||||
#: templates/base.html:92
|
||||
msgid "Activities"
|
||||
msgstr ""
|
||||
|
||||
#: templates/cas_server/base.html:7
|
||||
msgid "Central Authentication Service"
|
||||
msgstr ""
|
||||
@ -722,32 +871,48 @@ msgstr ""
|
||||
msgid "Field filters"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/club_detail.html:10
|
||||
msgid "Membership starts on"
|
||||
#: templates/member/alias_update.html:5
|
||||
msgid "Add alias"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/club_detail.html:12
|
||||
msgid "Membership ends on"
|
||||
#: templates/member/club_info.html:17
|
||||
msgid "Club Parent"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/club_detail.html:14
|
||||
msgid "Membership duration"
|
||||
#: templates/member/club_info.html:41
|
||||
msgid "Add member"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/club_detail.html:18 templates/member/profile_detail.html:34
|
||||
msgid "balance"
|
||||
#: templates/member/club_info.html:42 templates/note/conso_form.html:121
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/club_detail.html:51 templates/member/profile_detail.html:75
|
||||
msgid "Transaction history"
|
||||
#: templates/member/club_info.html:43
|
||||
msgid "Add roles"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/club_form.html:6
|
||||
msgid "Clubs list"
|
||||
#: templates/member/club_info.html:46 templates/member/profile_info.html:48
|
||||
msgid "View Profile"
|
||||
msgstr ""
|
||||
|
||||
#: 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 ""
|
||||
|
||||
#: templates/member/manage_auth_tokens.html:16
|
||||
@ -762,35 +927,31 @@ msgstr ""
|
||||
msgid "Regenerate token"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/profile_alias.html:10
|
||||
msgid "Add alias"
|
||||
#: templates/member/profile_info.html:5
|
||||
msgid "Account #"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/profile_detail.html:15
|
||||
msgid "first name"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/profile_detail.html:18
|
||||
#: templates/member/profile_info.html:17
|
||||
msgid "username"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/profile_detail.html:21
|
||||
#: templates/member/profile_info.html:20
|
||||
msgid "password"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/profile_detail.html:24
|
||||
#: templates/member/profile_info.html:23
|
||||
msgid "Change password"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/profile_detail.html:49
|
||||
msgid "View Profile"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/profile_detail.html:62
|
||||
#: templates/member/profile_tables.html:9
|
||||
msgid "View my memberships"
|
||||
msgstr ""
|
||||
|
||||
@ -819,10 +980,6 @@ msgstr ""
|
||||
msgid "Most used buttons"
|
||||
msgstr ""
|
||||
|
||||
#: templates/note/conso_form.html:121
|
||||
msgid "Edit"
|
||||
msgstr ""
|
||||
|
||||
#: templates/note/conso_form.html:126
|
||||
msgid "Single consumptions"
|
||||
msgstr ""
|
||||
@ -831,7 +988,7 @@ msgstr ""
|
||||
msgid "Double consumptions"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
@ -847,37 +1004,21 @@ msgstr ""
|
||||
msgid "Transfer type"
|
||||
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:169
|
||||
#: templates/note/transaction_form.html:176
|
||||
#: templates/note/transaction_form.html:164
|
||||
#: templates/note/transaction_form.html:171
|
||||
msgid "Select receivers"
|
||||
msgstr ""
|
||||
|
||||
#: templates/note/transaction_form.html:128
|
||||
msgid "Amount"
|
||||
msgstr ""
|
||||
|
||||
#: templates/note/transaction_form.html:138
|
||||
#: templates/note/transaction_form.html:133
|
||||
msgid "Reason"
|
||||
msgstr ""
|
||||
|
||||
#: templates/note/transaction_form.html:183
|
||||
#: templates/note/transaction_form.html:178
|
||||
msgid "Credit note"
|
||||
msgstr ""
|
||||
|
||||
#: templates/note/transaction_form.html:190
|
||||
#: templates/note/transaction_form.html:185
|
||||
msgid "Debit note"
|
||||
msgstr ""
|
||||
|
||||
@ -889,6 +1030,10 @@ msgstr ""
|
||||
msgid "search button"
|
||||
msgstr ""
|
||||
|
||||
#: templates/note/transactiontemplate_list.html:13
|
||||
msgid "New button"
|
||||
msgstr ""
|
||||
|
||||
#: templates/note/transactiontemplate_list.html:20
|
||||
msgid "buttons listing "
|
||||
msgstr ""
|
||||
@ -991,11 +1136,11 @@ msgstr ""
|
||||
msgid "Invoices list"
|
||||
msgstr ""
|
||||
|
||||
#: templates/treasury/invoice_form.html:42
|
||||
#: templates/treasury/invoice_form.html:41
|
||||
msgid "Add product"
|
||||
msgstr ""
|
||||
|
||||
#: templates/treasury/invoice_form.html:43
|
||||
#: templates/treasury/invoice_form.html:42
|
||||
msgid "Remove product"
|
||||
msgstr ""
|
||||
|
||||
|
@ -3,7 +3,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -13,83 +13,189 @@ msgstr ""
|
||||
"Content-Transfer-Encoding: 8bit\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"
|
||||
msgstr "activité"
|
||||
|
||||
#: apps/activity/models.py:19 apps/activity/models.py:44
|
||||
#: apps/member/models.py:63 apps/member/models.py:114
|
||||
#: apps/note/models/notes.py:188 apps/note/models/transactions.py:25
|
||||
#: apps/note/models/transactions.py:45 apps/note/models/transactions.py:232
|
||||
#: templates/member/profile_detail.html:15
|
||||
#: apps/activity/forms.py:45 apps/activity/models.py:217
|
||||
msgid "You can't invite someone once the activity is started."
|
||||
msgstr "Vous ne pouvez pas inviter quelqu'un une fois que l'activité a démarré."
|
||||
|
||||
#: apps/activity/forms.py:48 apps/activity/models.py:220
|
||||
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"
|
||||
msgstr "nom"
|
||||
|
||||
#: apps/activity/models.py:23
|
||||
#: apps/activity/models.py:27 templates/activity/activity_detail.html:39
|
||||
msgid "can invite"
|
||||
msgstr "peut inviter"
|
||||
|
||||
#: apps/activity/models.py:26
|
||||
#: apps/activity/models.py:30 templates/activity/activity_detail.html:43
|
||||
msgid "guest entry fee"
|
||||
msgstr "cotisation de l'entrée invité"
|
||||
|
||||
#: apps/activity/models.py:30
|
||||
#: apps/activity/models.py:34
|
||||
msgid "activity type"
|
||||
msgstr "type d'activité"
|
||||
|
||||
#: apps/activity/models.py:31
|
||||
#: apps/activity/models.py:35
|
||||
msgid "activity types"
|
||||
msgstr "types d'activité"
|
||||
|
||||
#: apps/activity/models.py:48 apps/note/models/transactions.py:70
|
||||
#: apps/permission/models.py:91
|
||||
#: apps/activity/models.py:53 apps/note/models/transactions.py:69
|
||||
#: apps/permission/models.py:90 templates/activity/activity_detail.html:16
|
||||
msgid "description"
|
||||
msgstr "description"
|
||||
|
||||
#: apps/activity/models.py:54 apps/note/models/notes.py:164
|
||||
#: apps/note/models/transactions.py:63
|
||||
#: apps/activity/models.py:60 apps/note/models/notes.py:164
|
||||
#: apps/note/models/transactions.py:62
|
||||
#: templates/activity/activity_detail.html:19
|
||||
msgid "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"
|
||||
msgstr "organisateur"
|
||||
|
||||
#: apps/activity/models.py:66
|
||||
msgid "attendees club"
|
||||
msgstr ""
|
||||
#: apps/activity/models.py:82 apps/activity/models.py:131 apps/note/apps.py:14
|
||||
#: apps/note/models/notes.py:58
|
||||
msgid "note"
|
||||
msgstr "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"
|
||||
msgstr "date de début"
|
||||
|
||||
#: apps/activity/models.py:72
|
||||
#: apps/activity/models.py:97 templates/activity/activity_detail.html:25
|
||||
msgid "end date"
|
||||
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"
|
||||
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"
|
||||
msgstr "invité"
|
||||
|
||||
#: apps/activity/models.py:109
|
||||
#: apps/activity/models.py:242
|
||||
msgid "guests"
|
||||
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
|
||||
msgid "API"
|
||||
msgstr ""
|
||||
msgstr "API"
|
||||
|
||||
#: apps/logs/apps.py:11
|
||||
msgid "Logs"
|
||||
msgstr ""
|
||||
|
||||
#: apps/logs/models.py:21 apps/note/models/notes.py:117
|
||||
msgid "user"
|
||||
msgstr "utilisateur"
|
||||
msgstr "Logs"
|
||||
|
||||
#: apps/logs/models.py:27
|
||||
msgid "IP Address"
|
||||
@ -115,11 +221,12 @@ msgstr "Nouvelles données"
|
||||
msgid "create"
|
||||
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"
|
||||
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"
|
||||
msgstr "Supprimer"
|
||||
|
||||
@ -139,61 +246,65 @@ msgstr "Les logs ne peuvent pas être détruits."
|
||||
msgid "member"
|
||||
msgstr "adhérent"
|
||||
|
||||
#: apps/member/models.py:25
|
||||
#: apps/member/models.py:26
|
||||
msgid "phone number"
|
||||
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"
|
||||
msgstr "section"
|
||||
|
||||
#: apps/member/models.py:32
|
||||
#: apps/member/models.py:33
|
||||
msgid "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"
|
||||
msgstr "adresse"
|
||||
|
||||
#: apps/member/models.py:44
|
||||
#: apps/member/models.py:45
|
||||
msgid "paid"
|
||||
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"
|
||||
msgstr "profil utilisateur"
|
||||
|
||||
#: apps/member/models.py:68
|
||||
#: apps/member/models.py:69 templates/member/club_info.html:36
|
||||
msgid "email"
|
||||
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"
|
||||
msgstr "cotisation pour adhérer"
|
||||
|
||||
#: apps/member/models.py:77
|
||||
#: apps/member/models.py:85 templates/member/club_info.html:27
|
||||
msgid "membership duration"
|
||||
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)."
|
||||
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"
|
||||
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."
|
||||
msgstr ""
|
||||
"Combien de temps après le 1er Janvier les adhérents peuvent renouveler leur "
|
||||
"adhésion."
|
||||
|
||||
#: apps/member/models.py:89
|
||||
#: apps/member/models.py:97 templates/member/club_info.html:24
|
||||
msgid "membership end"
|
||||
msgstr "fin de l'adhésion"
|
||||
|
||||
#: apps/member/models.py:90
|
||||
#: apps/member/models.py:98
|
||||
msgid ""
|
||||
"How long the membership can last after January 1st of the next year after "
|
||||
"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 "
|
||||
"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"
|
||||
msgstr "club"
|
||||
|
||||
#: apps/member/models.py:97
|
||||
#: apps/member/models.py:105
|
||||
msgid "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"
|
||||
msgstr "rôle"
|
||||
|
||||
#: apps/member/models.py:121
|
||||
#: apps/member/models.py:129
|
||||
msgid "roles"
|
||||
msgstr "rôles"
|
||||
|
||||
#: apps/member/models.py:145
|
||||
#: apps/member/models.py:153
|
||||
msgid "membership starts on"
|
||||
msgstr "l'adhésion commence le"
|
||||
|
||||
#: apps/member/models.py:148
|
||||
#: apps/member/models.py:156
|
||||
msgid "membership ends on"
|
||||
msgstr "l'adhésion finie le"
|
||||
|
||||
#: apps/member/models.py:152
|
||||
#: apps/member/models.py:160
|
||||
msgid "fee"
|
||||
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"
|
||||
msgstr "adhésion"
|
||||
|
||||
#: apps/member/models.py:163
|
||||
#: apps/member/models.py:177
|
||||
msgid "memberships"
|
||||
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"
|
||||
msgstr "Modifier le profil"
|
||||
|
||||
#: apps/member/views.py:93
|
||||
#: apps/member/views.py:89
|
||||
msgid "An alias with a similar name already exists."
|
||||
msgstr "Un alias avec un nom similaire existe déjà."
|
||||
|
||||
#: apps/member/views.py:146
|
||||
#, 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
|
||||
#: apps/note/admin.py:120 apps/note/models/transactions.py:94
|
||||
msgid "source"
|
||||
msgstr "source"
|
||||
|
||||
#: 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"
|
||||
msgstr "destination"
|
||||
|
||||
#: apps/note/apps.py:14 apps/note/models/notes.py:58
|
||||
msgid "note"
|
||||
msgstr "note"
|
||||
|
||||
#: apps/note/forms.py:20
|
||||
msgid "New Alias"
|
||||
msgstr "Nouvel alias"
|
||||
|
||||
#: apps/note/forms.py:25
|
||||
#: apps/note/forms.py:14
|
||||
msgid "select an image"
|
||||
msgstr "Choisissez une image"
|
||||
|
||||
#: apps/note/forms.py:26
|
||||
#: apps/note/forms.py:15
|
||||
msgid "Maximal size: 2MB"
|
||||
msgstr "Taille maximale : 2 Mo"
|
||||
|
||||
@ -310,7 +408,7 @@ msgstr ""
|
||||
msgid "display image"
|
||||
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"
|
||||
msgstr "créée le"
|
||||
|
||||
@ -318,10 +416,6 @@ msgstr "créée le"
|
||||
msgid "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
|
||||
msgid "This alias is already taken."
|
||||
msgstr "Cet alias est déjà pris."
|
||||
@ -368,7 +462,8 @@ msgstr "Alias invalide"
|
||||
msgid "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"
|
||||
msgstr "alias"
|
||||
|
||||
@ -380,102 +475,114 @@ msgstr "L'alias est trop long."
|
||||
msgid "An alias with a similar name already exists: {} "
|
||||
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."
|
||||
msgstr "Vous ne pouvez pas supprimer votre alias principal."
|
||||
|
||||
#: apps/note/models/transactions.py:31
|
||||
#: apps/note/models/transactions.py:30
|
||||
msgid "transaction category"
|
||||
msgstr "catégorie de transaction"
|
||||
|
||||
#: apps/note/models/transactions.py:32
|
||||
#: apps/note/models/transactions.py:31
|
||||
msgid "transaction categories"
|
||||
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"
|
||||
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"
|
||||
msgstr "montant"
|
||||
|
||||
#: apps/note/models/transactions.py:58
|
||||
#: apps/note/models/transactions.py:57
|
||||
msgid "in centimes"
|
||||
msgstr "en centimes"
|
||||
|
||||
#: apps/note/models/transactions.py:76
|
||||
#: apps/note/models/transactions.py:75
|
||||
msgid "transaction template"
|
||||
msgstr "modèle de transaction"
|
||||
|
||||
#: apps/note/models/transactions.py:77
|
||||
#: apps/note/models/transactions.py:76
|
||||
msgid "transaction templates"
|
||||
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
|
||||
msgid "used alias"
|
||||
msgstr "alias utilisé"
|
||||
|
||||
#: apps/note/models/transactions.py:122
|
||||
#: apps/note/models/transactions.py:121
|
||||
msgid "quantity"
|
||||
msgstr "quantité"
|
||||
|
||||
#: apps/note/models/transactions.py:115
|
||||
#: apps/note/models/transactions.py:129
|
||||
msgid "reason"
|
||||
msgstr "raison"
|
||||
|
||||
#: apps/note/models/transactions.py:119
|
||||
msgid "valid"
|
||||
msgstr "valide"
|
||||
#: apps/note/models/transactions.py:139 apps/note/tables.py:95
|
||||
msgid "invalidity reason"
|
||||
msgstr "Motif d'invalidité"
|
||||
|
||||
#: apps/note/models/transactions.py:124
|
||||
#: apps/note/models/transactions.py:146
|
||||
msgid "transaction"
|
||||
msgstr "transaction"
|
||||
|
||||
#: apps/note/models/transactions.py:125
|
||||
#: apps/note/models/transactions.py:147
|
||||
msgid "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:145
|
||||
#: templates/note/transaction_form.html:140
|
||||
msgid "Transfer"
|
||||
msgstr "Virement"
|
||||
|
||||
#: apps/note/models/transactions.py:188
|
||||
#: apps/note/models/transactions.py:221
|
||||
msgid "Template"
|
||||
msgstr "Bouton"
|
||||
|
||||
#: apps/note/models/transactions.py:203
|
||||
#: apps/note/models/transactions.py:236
|
||||
msgid "first_name"
|
||||
msgstr "prénom"
|
||||
|
||||
#: apps/note/models/transactions.py:208
|
||||
#: apps/note/models/transactions.py:241
|
||||
msgid "bank"
|
||||
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"
|
||||
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"
|
||||
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"
|
||||
msgstr "transaction d'adhésion"
|
||||
|
||||
#: apps/note/models/transactions.py:231
|
||||
#: apps/note/models/transactions.py:264
|
||||
msgid "membership transactions"
|
||||
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"
|
||||
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"
|
||||
msgstr "Consommations"
|
||||
|
||||
@ -497,41 +604,35 @@ msgstr "Rang"
|
||||
msgid "Specifying field applies only to view and change permission types."
|
||||
msgstr ""
|
||||
|
||||
#: apps/treasury/apps.py:11 templates/base.html:102
|
||||
#: apps/treasury/apps.py:12 templates/base.html:99
|
||||
msgid "Treasury"
|
||||
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/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"
|
||||
msgstr "Envoyer"
|
||||
|
||||
#: apps/treasury/forms.py:58
|
||||
#: apps/treasury/forms.py:86
|
||||
msgid "Close"
|
||||
msgstr "Fermer"
|
||||
|
||||
#: apps/treasury/forms.py:65
|
||||
#: apps/treasury/forms.py:95
|
||||
msgid "Remittance is already closed."
|
||||
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."
|
||||
msgstr "Vous ne pouvez pas changer le type de la remise."
|
||||
|
||||
#: apps/treasury/forms.py:84
|
||||
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
|
||||
#: apps/treasury/forms.py:124 templates/note/transaction_form.html:98
|
||||
msgid "Bank"
|
||||
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/treasury/remittance_form.html:18
|
||||
msgid "Amount"
|
||||
@ -585,10 +686,6 @@ msgstr "Prix unitaire"
|
||||
msgid "Date"
|
||||
msgstr "Date"
|
||||
|
||||
#: apps/treasury/models.py:126
|
||||
msgid "Type"
|
||||
msgstr "Type"
|
||||
|
||||
#: apps/treasury/models.py:131
|
||||
msgid "Comment"
|
||||
msgstr "Commentaire"
|
||||
@ -597,38 +694,38 @@ msgstr "Commentaire"
|
||||
msgid "Closed"
|
||||
msgstr "Fermée"
|
||||
|
||||
#: apps/treasury/models.py:159
|
||||
#: apps/treasury/models.py:169
|
||||
msgid "Remittance #{:d}: {}"
|
||||
msgstr "Remise n°{:d} : {}"
|
||||
|
||||
#: apps/treasury/models.py:178 apps/treasury/tables.py:64
|
||||
#: apps/treasury/tables.py:72 templates/treasury/invoice_list.html:13
|
||||
#: apps/treasury/models.py:188 apps/treasury/tables.py:76
|
||||
#: apps/treasury/tables.py:84 templates/treasury/invoice_list.html:13
|
||||
#: templates/treasury/remittance_list.html:13
|
||||
msgid "Remittance"
|
||||
msgstr "Remise"
|
||||
|
||||
#: apps/treasury/tables.py:16
|
||||
#: apps/treasury/tables.py:19
|
||||
msgid "Invoice #{: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
|
||||
msgid "Invoice"
|
||||
msgstr "Facture"
|
||||
|
||||
#: apps/treasury/tables.py:38
|
||||
#: apps/treasury/tables.py:45
|
||||
msgid "Transaction count"
|
||||
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"
|
||||
msgstr "Voir"
|
||||
|
||||
#: apps/treasury/tables.py:66
|
||||
#: apps/treasury/tables.py:78
|
||||
msgid "Add"
|
||||
msgstr "Ajouter"
|
||||
|
||||
#: apps/treasury/tables.py:74
|
||||
#: apps/treasury/tables.py:86
|
||||
msgid "Remove"
|
||||
msgstr "supprimer"
|
||||
|
||||
@ -639,30 +736,86 @@ msgid ""
|
||||
"again unless your session expires or you logout."
|
||||
msgstr ""
|
||||
|
||||
#: note_kfet/settings/base.py:151
|
||||
#: note_kfet/settings/base.py:152
|
||||
msgid "German"
|
||||
msgstr ""
|
||||
|
||||
#: note_kfet/settings/base.py:152
|
||||
#: note_kfet/settings/base.py:153
|
||||
msgid "English"
|
||||
msgstr ""
|
||||
|
||||
#: note_kfet/settings/base.py:153
|
||||
#: note_kfet/settings/base.py:154
|
||||
msgid "French"
|
||||
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
|
||||
msgid "The ENS Paris-Saclay BDE note."
|
||||
msgstr "La note du BDE de l'ENS Paris-Saclay."
|
||||
|
||||
#: templates/base.html:87
|
||||
#: templates/base.html:89
|
||||
msgid "Clubs"
|
||||
msgstr "Clubs"
|
||||
|
||||
#: templates/base.html:92
|
||||
msgid "Activities"
|
||||
msgstr "Activités"
|
||||
|
||||
#: templates/cas_server/base.html:7
|
||||
msgid "Central Authentication Service"
|
||||
msgstr ""
|
||||
@ -720,33 +873,49 @@ msgstr ""
|
||||
msgid "Field filters"
|
||||
msgstr ""
|
||||
|
||||
#: templates/member/club_detail.html:10
|
||||
msgid "Membership starts on"
|
||||
msgstr "L'adhésion commence le"
|
||||
#: templates/member/alias_update.html:5
|
||||
msgid "Add alias"
|
||||
msgstr "Ajouter un alias"
|
||||
|
||||
#: templates/member/club_detail.html:12
|
||||
msgid "Membership ends on"
|
||||
msgstr "L'adhésion finie le"
|
||||
#: templates/member/club_info.html:17
|
||||
msgid "Club Parent"
|
||||
msgstr "Club parent"
|
||||
|
||||
#: templates/member/club_detail.html:14
|
||||
msgid "Membership duration"
|
||||
msgstr "Durée de l'adhésion"
|
||||
#: templates/member/club_info.html:41
|
||||
msgid "Add member"
|
||||
msgstr "Ajouter un membre"
|
||||
|
||||
#: templates/member/club_detail.html:18 templates/member/profile_detail.html:34
|
||||
msgid "balance"
|
||||
msgstr "solde du compte"
|
||||
#: templates/member/club_info.html:42 templates/note/conso_form.html:121
|
||||
msgid "Edit"
|
||||
msgstr "Éditer"
|
||||
|
||||
#: templates/member/club_detail.html:51 templates/member/profile_detail.html:75
|
||||
msgid "Transaction history"
|
||||
msgstr "Historique des transactions"
|
||||
#: templates/member/club_info.html:43
|
||||
msgid "Add roles"
|
||||
msgstr "Ajouter des rôles"
|
||||
|
||||
#: templates/member/club_form.html:6
|
||||
msgid "Clubs list"
|
||||
msgstr "Liste des clubs"
|
||||
#: templates/member/club_info.html:46 templates/member/profile_info.html:48
|
||||
msgid "View Profile"
|
||||
msgstr "Voir le profil"
|
||||
|
||||
#: templates/member/club_list.html:8
|
||||
msgid "New club"
|
||||
msgstr "Nouveau club"
|
||||
msgid "search clubs"
|
||||
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
|
||||
msgid "Token"
|
||||
@ -760,35 +929,31 @@ msgstr "Créé le"
|
||||
msgid "Regenerate token"
|
||||
msgstr "Regénérer le jeton"
|
||||
|
||||
#: templates/member/profile_alias.html:10
|
||||
msgid "Add alias"
|
||||
msgstr "Ajouter un alias"
|
||||
#: templates/member/profile_info.html:5
|
||||
msgid "Account #"
|
||||
msgstr "Compte n°"
|
||||
|
||||
#: templates/member/profile_detail.html:15
|
||||
msgid "first name"
|
||||
msgstr "prénom"
|
||||
|
||||
#: templates/member/profile_detail.html:18
|
||||
#: templates/member/profile_info.html:17
|
||||
msgid "username"
|
||||
msgstr "pseudo"
|
||||
|
||||
#: templates/member/profile_detail.html:21
|
||||
#: templates/member/profile_info.html:20
|
||||
msgid "password"
|
||||
msgstr "mot de passe"
|
||||
|
||||
#: templates/member/profile_detail.html:24
|
||||
#: templates/member/profile_info.html:23
|
||||
msgid "Change password"
|
||||
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"
|
||||
msgstr "Gérer les jetons d'authentification"
|
||||
|
||||
#: templates/member/profile_detail.html:49
|
||||
msgid "View Profile"
|
||||
msgstr "Voir le profil"
|
||||
|
||||
#: templates/member/profile_detail.html:62
|
||||
#: templates/member/profile_tables.html:9
|
||||
msgid "View my memberships"
|
||||
msgstr "Voir mes adhésions"
|
||||
|
||||
@ -817,10 +982,6 @@ msgstr "Consommer !"
|
||||
msgid "Most used buttons"
|
||||
msgstr "Boutons les plus utilisés"
|
||||
|
||||
#: templates/note/conso_form.html:121
|
||||
msgid "Edit"
|
||||
msgstr "Éditer"
|
||||
|
||||
#: templates/note/conso_form.html:126
|
||||
msgid "Single consumptions"
|
||||
msgstr "Consommations simples"
|
||||
@ -829,7 +990,7 @@ msgstr "Consommations simples"
|
||||
msgid "Double consumptions"
|
||||
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"
|
||||
msgstr "Historique des transactions récentes"
|
||||
|
||||
@ -845,37 +1006,21 @@ msgstr "Paiement externe"
|
||||
msgid "Transfer type"
|
||||
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:169
|
||||
#: templates/note/transaction_form.html:176
|
||||
#: templates/note/transaction_form.html:164
|
||||
#: templates/note/transaction_form.html:171
|
||||
msgid "Select receivers"
|
||||
msgstr "Sélection des destinataires"
|
||||
|
||||
#: templates/note/transaction_form.html:128
|
||||
msgid "Amount"
|
||||
msgstr "Montant"
|
||||
|
||||
#: templates/note/transaction_form.html:138
|
||||
#: templates/note/transaction_form.html:133
|
||||
msgid "Reason"
|
||||
msgstr "Raison"
|
||||
|
||||
#: templates/note/transaction_form.html:183
|
||||
#: templates/note/transaction_form.html:178
|
||||
msgid "Credit note"
|
||||
msgstr "Note à recharger"
|
||||
|
||||
#: templates/note/transaction_form.html:190
|
||||
#: templates/note/transaction_form.html:185
|
||||
msgid "Debit note"
|
||||
msgstr "Note à débiter"
|
||||
|
||||
@ -887,6 +1032,10 @@ msgstr "Liste des boutons"
|
||||
msgid "search button"
|
||||
msgstr "Chercher un bouton"
|
||||
|
||||
#: templates/note/transactiontemplate_list.html:13
|
||||
msgid "New button"
|
||||
msgstr "Nouveau bouton"
|
||||
|
||||
#: templates/note/transactiontemplate_list.html:20
|
||||
msgid "buttons listing "
|
||||
msgstr "Liste des boutons"
|
||||
@ -989,11 +1138,11 @@ msgstr ""
|
||||
msgid "Invoices list"
|
||||
msgstr "Liste des factures"
|
||||
|
||||
#: templates/treasury/invoice_form.html:42
|
||||
#: templates/treasury/invoice_form.html:41
|
||||
msgid "Add product"
|
||||
msgstr "Ajouter produit"
|
||||
|
||||
#: templates/treasury/invoice_form.html:43
|
||||
#: templates/treasury/invoice_form.html:42
|
||||
msgid "Remove product"
|
||||
msgstr "Retirer produit"
|
||||
|
||||
|
302
note_kfet/inputs.py
Normal file
302
note_kfet/inputs.py
Normal 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
|
@ -48,12 +48,10 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sites',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.forms',
|
||||
# API
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
# Autocomplete
|
||||
'dal',
|
||||
'dal_select2',
|
||||
|
||||
# Note apps
|
||||
'activity',
|
||||
@ -100,6 +98,8 @@ TEMPLATES = [
|
||||
},
|
||||
]
|
||||
|
||||
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
|
||||
|
||||
WSGI_APPLICATION = 'note_kfet.wsgi.application'
|
||||
|
||||
# Password validation
|
||||
|
@ -15,13 +15,14 @@ urlpatterns = [
|
||||
|
||||
# Include project routers
|
||||
path('note/', include('note.urls')),
|
||||
path('accounts/', include('member.urls')),
|
||||
path('activity/', include('activity.urls')),
|
||||
path('treasury/', include('treasury.urls')),
|
||||
|
||||
# Include Django Contrib and Core routers
|
||||
path('i18n/', include('django.conf.urls.i18n')),
|
||||
path('admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
path('admin/', admin.site.urls),
|
||||
path('accounts/', include('member.urls')),
|
||||
path('accounts/login/', CustomLoginView.as_view()),
|
||||
path('accounts/', include('django.contrib.auth.urls')),
|
||||
path('api/', include('api.urls')),
|
||||
@ -42,7 +43,7 @@ if "cas" in settings.INSTALLED_APPS:
|
||||
# Include CAS Client routers
|
||||
path('accounts/login/cas/', cas_views.login, name='cas_login'),
|
||||
path('accounts/logout/cas/', cas_views.logout, name='cas_logout'),
|
||||
|
||||
|
||||
]
|
||||
if "debug_toolbar" in settings.INSTALLED_APPS:
|
||||
import debug_toolbar
|
||||
|
@ -3,7 +3,6 @@ chardet==3.0.4
|
||||
defusedxml==0.6.0
|
||||
Django~=2.2
|
||||
django-allauth==0.39.1
|
||||
django-autocomplete-light==3.5.1
|
||||
django-crispy-forms==1.7.2
|
||||
django-extensions==2.1.9
|
||||
django-filter==2.2.0
|
||||
|
121
static/bootstrap_datepicker_plus/css/datepicker-widget.css
Normal file
121
static/bootstrap_datepicker_plus/css/datepicker-widget.css
Normal 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;
|
||||
}
|
55
static/bootstrap_datepicker_plus/js/datepicker-widget.js
Normal file
55
static/bootstrap_datepicker_plus/js/datepicker-widget.js
Normal 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');
|
||||
});
|
||||
}
|
||||
});
|
34
static/js/autocomplete_model.js
Normal file
34
static/js/autocomplete_model.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -28,15 +28,35 @@ function addMsg(msg, alert_type) {
|
||||
+ msg + "</div>\n";
|
||||
msgDiv.html(html);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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){
|
||||
for (const err_msg of Object.values(errs_obj)) {
|
||||
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
|
||||
*/
|
||||
|
139
templates/activity/activity_detail.html
Normal file
139
templates/activity/activity_detail.html
Normal 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 %}
|
126
templates/activity/activity_entry.html
Normal file
126
templates/activity/activity_entry.html
Normal 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 %}
|
11
templates/activity/activity_form.html
Normal file
11
templates/activity/activity_form.html
Normal 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 %}
|
15
templates/activity/activity_invite.html
Normal file
15
templates/activity/activity_invite.html
Normal 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 %}
|
33
templates/activity/activity_list.html
Normal file
33
templates/activity/activity_list.html
Normal 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 %}
|
@ -91,7 +91,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endif %}
|
||||
{% if "activity.activity"|not_empty_model_list %}
|
||||
<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>
|
||||
{% endif %}
|
||||
{% if "treasury.invoice"|not_empty_model_change_list %}
|
||||
|
6
templates/bootstrap_datepicker_plus/date_picker.html
Normal file
6
templates/bootstrap_datepicker_plus/date_picker.html
Normal 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>
|
4
templates/bootstrap_datepicker_plus/input.html
Normal file
4
templates/bootstrap_datepicker_plus/input.html
Normal 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 %}/>
|
6
templates/bootstrap_datepicker_plus/time_picker.html
Normal file
6
templates/bootstrap_datepicker_plus/time_picker.html
Normal 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>
|
9
templates/member/autocomplete_model.html
Normal file
9
templates/member/autocomplete_model.html
Normal 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>
|
@ -13,8 +13,10 @@
|
||||
<dt class="col-xl-6">{% trans 'name'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ club.name}}</dd>
|
||||
|
||||
<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>
|
||||
{% 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>
|
||||
<dd class="col-xl-6"> {{ club.parent_club.name}}</dd>
|
||||
{% endif %}
|
||||
|
||||
<dt class="col-xl-6">{% trans 'membership start'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ club.membership_start }}</dd>
|
||||
|
11
templates/note/amount_input.html
Normal file
11
templates/note/amount_input.html
Normal 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>
|
@ -126,12 +126,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="amount">{% trans "Amount" %} :</label>
|
||||
<div class="input-group">
|
||||
<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>
|
||||
{% include "note/amount_input.html" with widget=amount_widget %}
|
||||
</div>
|
||||
|
||||
<div class="form-group col-md-6">
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_tags pretty_money %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% block content %}
|
||||
<p><a class="btn btn-default" href="{% url 'treasury:invoice_list' %}">{% trans "Invoices list" %}</a></p>
|
||||
<form method="post" action="">
|
||||
@ -26,18 +26,8 @@
|
||||
{% endif %}
|
||||
<tr class="row-formset">
|
||||
<td>{{ form.designation }}</td>
|
||||
<td>{{ form.quantity }} </td>
|
||||
<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>
|
||||
<td>{{ form.quantity }}</td>
|
||||
<td>{{ form.amount }}</td>
|
||||
{# These fields are hidden but handled by the formset to link the id and the invoice id #}
|
||||
{{ form.invoice }}
|
||||
{{ form.id }}
|
||||
@ -64,15 +54,7 @@
|
||||
<tr class="row-formset">
|
||||
<td>{{ formset.empty_form.designation }}</td>
|
||||
<td>{{ formset.empty_form.quantity }} </td>
|
||||
<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>
|
||||
<td>{{ formset.empty_form.amount }}</td>
|
||||
{{ formset.empty_form.invoice }}
|
||||
{{ formset.empty_form.id }}
|
||||
</tr>
|
||||
|
Loading…
Reference in New Issue
Block a user