Merge branch 'beta' into 'master'

Corrections diverses

See merge request bde/nk20!123
This commit is contained in:
ynerant 2020-09-14 10:16:13 +02:00
commit 15139ec89b
38 changed files with 3542 additions and 232 deletions

View File

@ -7,7 +7,7 @@ from threading import Thread
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models, transaction
from django.db.models import Q from django.db.models import Q
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -123,6 +123,7 @@ class Activity(models.Model):
verbose_name=_('open'), verbose_name=_('open'),
) )
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" """
Update the activity wiki page each time the activity is updated (validation, change description, ...) Update the activity wiki page each time the activity is updated (validation, change description, ...)
@ -194,8 +195,8 @@ class Entry(models.Model):
else _("Entry for {note} to the activity {activity}").format( else _("Entry for {note} to the activity {activity}").format(
guest=str(self.guest), note=str(self.note), activity=str(self.activity)) guest=str(self.guest), note=str(self.note), activity=str(self.activity))
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
qs = Entry.objects.filter(~Q(pk=self.pk), activity=self.activity, note=self.note, guest=self.guest) qs = Entry.objects.filter(~Q(pk=self.pk), activity=self.activity, note=self.note, guest=self.guest)
if qs.exists(): if qs.exists():
raise ValidationError(_("Already entered on ") + _("{:%Y-%m-%d %H:%M:%S}").format(qs.get().time, )) raise ValidationError(_("Already entered on ") + _("{:%Y-%m-%d %H:%M:%S}").format(qs.get().time, ))
@ -260,6 +261,7 @@ class Guest(models.Model):
except AttributeError: except AttributeError:
return False return False
@transaction.atomic
def save(self, force_insert=False, force_update=False, using=None, update_fields=None): def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
one_year = timedelta(days=365) one_year = timedelta(days=365)

View File

@ -7,6 +7,7 @@ from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import F, Q from django.db.models import F, Q
from django.http import HttpResponse from django.http import HttpResponse
from django.urls import reverse_lazy from django.urls import reverse_lazy
@ -44,6 +45,7 @@ class ActivityCreateView(ProtectQuerysetMixin, ProtectedCreateView):
date_end=timezone.now(), date_end=timezone.now(),
) )
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
form.instance.creater = self.request.user form.instance.creater = self.request.user
return super().form_valid(form) return super().form_valid(form)
@ -145,6 +147,7 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
form.fields["inviter"].initial = self.request.user.note form.fields["inviter"].initial = self.request.user.note
return form return form
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
form.instance.activity = Activity.objects\ form.instance.activity = Activity.objects\
.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")).get(pk=self.kwargs["pk"]) .filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view")).get(pk=self.kwargs["pk"])

View File

@ -8,6 +8,7 @@ from django import forms
from django.conf import settings from django.conf import settings
from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import transaction
from django.forms import CheckboxSelectMultiple from django.forms import CheckboxSelectMultiple
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -57,6 +58,7 @@ class ProfileForm(forms.ModelForm):
self.fields['address'].widget.attrs.update({"placeholder": "4 avenue des Sciences, 91190 GIF-SUR-YVETTE"}) self.fields['address'].widget.attrs.update({"placeholder": "4 avenue des Sciences, 91190 GIF-SUR-YVETTE"})
self.fields['promotion'].widget.attrs.update({"max": timezone.now().year}) self.fields['promotion'].widget.attrs.update({"max": timezone.now().year})
@transaction.atomic
def save(self, commit=True): def save(self, commit=True):
if not self.instance.section or (("department" in self.changed_data if not self.instance.section or (("department" in self.changed_data
or "promotion" in self.changed_data) and "section" not in self.changed_data): or "promotion" in self.changed_data) and "section" not in self.changed_data):
@ -161,7 +163,7 @@ class MembershipForm(forms.ModelForm):
soge = forms.BooleanField( soge = forms.BooleanField(
label=_("Inscription paid by Société Générale"), label=_("Inscription paid by Société Générale"),
required=False, required=False,
help_text=_("Check this case is the Société Générale paid the inscription."), help_text=_("Check this case if the Société Générale paid the inscription."),
) )
credit_type = forms.ModelChoiceField( credit_type = forms.ModelChoiceField(

View File

@ -7,7 +7,7 @@ import os
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models, transaction
from django.db.models import Q from django.db.models import Q
from django.template import loader from django.template import loader
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
@ -271,6 +271,7 @@ class Club(models.Model):
self._force_save = True self._force_save = True
self.save(force_update=True) self.save(force_update=True)
@transaction.atomic
def save(self, force_insert=False, force_update=False, using=None, def save(self, force_insert=False, force_update=False, using=None,
update_fields=None): update_fields=None):
if not self.require_memberships: if not self.require_memberships:
@ -406,6 +407,7 @@ class Membership(models.Model):
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all()) parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
parent_membership.save() parent_membership.save()
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" """
Calculate fee and end date before saving the membership and creating the transaction if needed. Calculate fee and end date before saving the membership and creating the transaction if needed.
@ -475,8 +477,13 @@ class Membership(models.Model):
# to treasurers. # to treasurers.
transaction.valid = False transaction.valid = False
from treasury.models import SogeCredit from treasury.models import SogeCredit
soge_credit = SogeCredit.objects.get_or_create(user=self.user)[0] if SogeCredit.objects.filter(user=self.user).exists():
soge_credit.refresh_from_db() soge_credit = SogeCredit.objects.get(user=self.user)
else:
soge_credit = SogeCredit(user=self.user)
soge_credit._force_save = True
soge_credit.save(force_insert=True)
soge_credit.refresh_from_db()
transaction.save(force_insert=True) transaction.save(force_insert=True)
transaction.refresh_from_db() transaction.refresh_from_db()
soge_credit.transactions.add(transaction) soge_credit.transactions.add(transaction)

View File

@ -38,7 +38,7 @@
<dt class="col-xl-6">{% trans 'address'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
<dd class="col-xl-6">{{ user_object.profile.address }}</dd> <dd class="col-xl-6">{{ user_object.profile.address }}</dd>
{% if "note.view_note"|has_perm:user_object.note %} {% if user_object.note and "note.view_note"|has_perm:user_object.note %}
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
<dd class="col-xl-6">{{ user_object.note.balance | pretty_money }}</dd> <dd class="col-xl-6">{{ user_object.note.balance | pretty_money }}</dd>
@ -47,7 +47,7 @@
{% endif %} {% endif %}
</dl> </dl>
{% if user_object.pk == user_object.pk %} {% if user_object.pk == user.pk %}
<div class="text-center"> <div class="text-center">
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}"> <a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
<i class="fa fa-cogs"></i>{% trans 'API token' %} <i class="fa fa-cogs"></i>{% trans 'API token' %}

View File

@ -38,6 +38,7 @@ class CustomLoginView(LoginView):
""" """
form_class = CustomAuthenticationForm form_class = CustomAuthenticationForm
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
logout(self.request) logout(self.request)
_set_current_user_and_ip(form.get_user(), self.request.session, None) _set_current_user_and_ip(form.get_user(), self.request.session, None)
@ -76,6 +77,7 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
return context return context
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
""" """
Check if ProfileForm is correct Check if ProfileForm is correct
@ -269,6 +271,7 @@ class PictureUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin, Det
self.object = self.get_object() self.object = self.get_object()
return self.form_valid(form) if form.is_valid() else self.form_invalid(form) return self.form_valid(form) if form.is_valid() else self.form_invalid(form)
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
"""Save image to note""" """Save image to note"""
image = form.cleaned_data['image'] image = form.cleaned_data['image']
@ -650,6 +653,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
return not error return not error
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
""" """
Create membership, check that all is good, make transactions Create membership, check that all is good, make transactions

View File

@ -1,5 +1,6 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay # Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings from django.conf import settings
from django.db.models import Q from django.db.models import Q
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -56,8 +57,9 @@ class AliasViewSet(ReadProtectedModelViewSet):
""" """
queryset = Alias.objects.all() queryset = Alias.objects.all()
serializer_class = AliasSerializer serializer_class = AliasSerializer
filter_backends = [SearchFilter, OrderingFilter] filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ] search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
filterset_fields = ['note']
ordering_fields = ['name', 'normalized_name'] ordering_fields = ['name', 'normalized_name']
def get_serializer_class(self): def get_serializer_class(self):
@ -106,8 +108,9 @@ class AliasViewSet(ReadProtectedModelViewSet):
class ConsumerViewSet(ReadOnlyProtectedModelViewSet): class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
queryset = Alias.objects.all() queryset = Alias.objects.all()
serializer_class = ConsumerSerializer serializer_class = ConsumerSerializer
filter_backends = [SearchFilter, OrderingFilter] filter_backends = [SearchFilter, OrderingFilter, DjangoFilterBackend]
search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ] search_fields = ['$normalized_name', '$name', '$note__polymorphic_ctype__model', ]
filterset_fields = ['note']
ordering_fields = ['name', 'normalized_name'] ordering_fields = ['name', 'normalized_name']
def get_queryset(self): def get_queryset(self):
@ -116,29 +119,31 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
:return: The filtered set of requested aliases :return: The filtered set of requested aliases
""" """
queryset = super().get_queryset() queryset = super().get_queryset().distinct()
# Sqlite doesn't support ORDER BY in subqueries # Sqlite doesn't support ORDER BY in subqueries
queryset = queryset.order_by("name") \ queryset = queryset.order_by("name") \
if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' else queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' else queryset
alias = self.request.query_params.get("alias", ".*") alias = self.request.query_params.get("alias", None)
queryset = queryset.prefetch_related('note') queryset = queryset.prefetch_related('note')
# We match first an alias if it is matched without normalization,
# then if the normalized pattern matches a normalized alias. if alias:
queryset = queryset.filter( # We match first an alias if it is matched without normalization,
name__iregex="^" + alias # then if the normalized pattern matches a normalized alias.
).union( queryset = queryset.filter(
queryset.filter( name__iregex="^" + alias
Q(normalized_name__iregex="^" + Alias.normalize(alias)) ).union(
& ~Q(name__iregex="^" + alias) queryset.filter(
), Q(normalized_name__iregex="^" + Alias.normalize(alias))
all=True).union( & ~Q(name__iregex="^" + alias)
queryset.filter( ),
Q(normalized_name__iregex="^" + alias.lower()) all=True).union(
& ~Q(normalized_name__iregex="^" + Alias.normalize(alias)) queryset.filter(
& ~Q(name__iregex="^" + alias) Q(normalized_name__iregex="^" + alias.lower())
), & ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
all=True) & ~Q(name__iregex="^" + alias)
),
all=True)
queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \ queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \
else queryset.order_by("name") else queryset.order_by("name")
@ -179,8 +184,11 @@ class TransactionViewSet(ReadProtectedModelViewSet):
""" """
queryset = Transaction.objects.order_by("-created_at").all() queryset = Transaction.objects.order_by("-created_at").all()
serializer_class = TransactionPolymorphicSerializer serializer_class = TransactionPolymorphicSerializer
filter_backends = [SearchFilter] filter_backends = [SearchFilter, DjangoFilterBackend, OrderingFilter]
filterset_fields = ["source", "source_alias", "destination", "destination_alias", "quantity",
"polymorphic_ctype", "amount", "created_at", ]
search_fields = ['$reason', ] search_fields = ['$reason', ]
ordering_fields = ['created_at', 'amount']
def get_queryset(self): def get_queryset(self):
user = self.request.user user = self.request.user

View File

@ -8,7 +8,7 @@ from django.conf.global_settings import DEFAULT_FROM_EMAIL
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.core.mail import send_mail from django.core.mail import send_mail
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from django.db import models from django.db import models, transaction
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -93,6 +93,7 @@ class Note(PolymorphicModel):
delta = timezone.now() - self.last_negative delta = timezone.now() - self.last_negative
return "{:d} jours".format(delta.days) return "{:d} jours".format(delta.days)
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" """
Save note with it's alias (called in polymorphic children) Save note with it's alias (called in polymorphic children)
@ -108,12 +109,16 @@ class Note(PolymorphicModel):
# Save alias # Save alias
a.note = self a.note = self
# Consider that if the name of the note could be changed, then the alias can be created.
# It does not mean that any alias can be created.
a._force_save = True
a.save(force_insert=True) a.save(force_insert=True)
else: else:
# Check if the name of the note changed without changing the normalized form of the alias # Check if the name of the note changed without changing the normalized form of the alias
alias = Alias.objects.get(normalized_name=Alias.normalize(str(self))) alias = Alias.objects.get(normalized_name=Alias.normalize(str(self)))
if alias.name != str(self): if alias.name != str(self):
alias.name = str(self) alias.name = str(self)
alias._force_save = True
alias.save() alias.save()
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):
@ -154,6 +159,7 @@ class NoteUser(Note):
def pretty(self): def pretty(self):
return _("%(user)s's note") % {'user': str(self.user)} return _("%(user)s's note") % {'user': str(self.user)}
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.pk and self.balance < 0: if self.pk and self.balance < 0:
old_note = NoteUser.objects.get(pk=self.pk) old_note = NoteUser.objects.get(pk=self.pk)
@ -195,6 +201,7 @@ class NoteClub(Note):
def pretty(self): def pretty(self):
return _("Note of %(club)s club") % {'club': str(self.club)} return _("Note of %(club)s club") % {'club': str(self.club)}
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if self.pk and self.balance < 0: if self.pk and self.balance < 0:
old_note = NoteClub.objects.get(pk=self.pk) old_note = NoteClub.objects.get(pk=self.pk)
@ -310,6 +317,7 @@ class Alias(models.Model):
pass pass
self.normalized_name = normalized_name self.normalized_name = normalized_name
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.clean() self.clean()
super().save(*args, **kwargs) super().save(*args, **kwargs)

View File

@ -170,19 +170,21 @@ class Transaction(PolymorphicModel):
previous_source_balance = self.source.balance previous_source_balance = self.source.balance
previous_dest_balance = self.destination.balance previous_dest_balance = self.destination.balance
source_balance = self.source.balance source_balance = previous_source_balance
dest_balance = self.destination.balance dest_balance = previous_dest_balance
created = self.pk is None created = self.pk is None
to_transfer = self.amount * self.quantity to_transfer = self.total
if not created and not self.valid and not hasattr(self, "_force_save"): if not created:
# Revert old transaction # Revert old transaction
old_transaction = Transaction.objects.get(pk=self.pk) # We make a select for update to avoid concurrency issues
old_transaction = Transaction.objects.select_for_update().get(pk=self.pk)
# Check that nothing important changed # Check that nothing important changed
for field_name in ["source_id", "destination_id", "quantity", "amount"]: if not hasattr(self, "_force_save"):
if getattr(self, field_name) != getattr(old_transaction, field_name): for field_name in ["source_id", "destination_id", "quantity", "amount"]:
raise ValidationError(_("You can't update the {field} on a Transaction. " if getattr(self, field_name) != getattr(old_transaction, field_name):
"Please invalidate it and create one other.").format(field=field_name)) raise ValidationError(_("You can't update the {field} on a Transaction. "
"Please invalidate it and create one other.").format(field=field_name))
if old_transaction.valid == self.valid: if old_transaction.valid == self.valid:
# Don't change anything # Don't change anything
@ -215,10 +217,6 @@ class Transaction(PolymorphicModel):
# When source == destination, no money is transferred and no transaction is created # When source == destination, no money is transferred and no transaction is created
return return
# We refresh the notes with the "select for update" tag to avoid concurrency issues
self.source = Note.objects.filter(pk=self.source_id).select_for_update().get()
self.destination = Note.objects.filter(pk=self.destination_id).select_for_update().get()
# Check that the amounts stay between big integer bounds # Check that the amounts stay between big integer bounds
diff_source, diff_dest = self.validate() diff_source, diff_dest = self.validate()
@ -237,9 +235,11 @@ class Transaction(PolymorphicModel):
super().save(*args, **kwargs) super().save(*args, **kwargs)
# Save notes # Save notes
self.source.refresh_from_db()
self.source.balance += diff_source self.source.balance += diff_source
self.source._force_save = True self.source._force_save = True
self.source.save() self.source.save()
self.destination.refresh_from_db()
self.destination.balance += diff_dest self.destination.balance += diff_dest
self.destination._force_save = True self.destination._force_save = True
self.destination.save() self.destination.save()
@ -273,6 +273,7 @@ class RecurrentTransaction(Transaction):
_("The destination of this transaction must equal to the destination of the template.")) _("The destination of this transaction must equal to the destination of the template."))
return super().clean() return super().clean()
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.clean() self.clean()
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
@ -323,6 +324,7 @@ class SpecialTransaction(Transaction):
raise(ValidationError(_("A special transaction is only possible between a" raise(ValidationError(_("A special transaction is only possible between a"
" Note associated to a payment method and a User or a Club"))) " Note associated to a payment method and a User or a Club")))
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.clean() self.clean()
super().save(*args, **kwargs) super().save(*args, **kwargs)

View File

@ -29,7 +29,6 @@ $(document).ready(function () {
// Switching in double consumptions mode should update the layout // Switching in double consumptions mode should update the layout
$('#double_conso').change(function () { $('#double_conso').change(function () {
$('#consos_list_div').removeClass('d-none') $('#consos_list_div').removeClass('d-none')
$('#user_select_div').attr('class', 'col-xl-4')
$('#infos_div').attr('class', 'col-sm-5 col-xl-6') $('#infos_div').attr('class', 'col-sm-5 col-xl-6')
const note_list_obj = $('#note_list') const note_list_obj = $('#note_list')
@ -48,7 +47,6 @@ $(document).ready(function () {
$('#single_conso').change(function () { $('#single_conso').change(function () {
$('#consos_list_div').addClass('d-none') $('#consos_list_div').addClass('d-none')
$('#user_select_div').attr('class', 'col-xl-7')
$('#infos_div').attr('class', 'col-sm-5 col-md-4') $('#infos_div').attr('class', 'col-sm-5 col-md-4')
const consos_list_obj = $('#consos_list') const consos_list_obj = $('#consos_list')

View File

@ -10,22 +10,22 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% block content %} {% block content %}
<div class="row mt-4"> <div class="row mt-4">
<div class="col-sm-5 col-md-4" id="infos_div"> <div class="col-sm-5 col-md-4" id="infos_div">
<div class="row"> <div class="row justify-content-center justify-content-md-end">
{# User details column #} {# User details column #}
<div class="col"> <div class="col picture-col">
<div class="card bg-light border-success mb-4 text-center"> <div class="card bg-light mb-4 text-center">
<a id="profile_pic_link" href="#"> <a id="profile_pic_link" href="#">
<img src="{% static "member/img/default_picture.png" %}" <img src="{% static "member/img/default_picture.png" %}"
id="profile_pic" alt="" class="card-img-top"> id="profile_pic" alt="" class="card-img-top d-none d-sm-block">
</a> </a>
<div class="card-body text-center text-break"> <div class="card-body text-center text-break p-2">
<span id="user_note"></span> <span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span>
</div> </div>
</div> </div>
</div> </div>
{# User selection column #} {# User selection column #}
<div class="col-xl-7" id="user_select_div"> <div class="col-xl" id="user_select_div">
<div class="card bg-light border-success mb-4"> <div class="card bg-light border-success mb-4">
<div class="card-header"> <div class="card-header">
<p class="card-text font-weight-bold"> <p class="card-text font-weight-bold">
@ -44,6 +44,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div> </div>
</div> </div>
</div> </div>
{# Summary of consumption and consume button #} {# Summary of consumption and consume button #}
<div class="col-xl-5 d-none" id="consos_list_div"> <div class="col-xl-5 d-none" id="consos_list_div">
<div class="card bg-light border-info mb-4"> <div class="card bg-light border-info mb-4">
@ -65,7 +66,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div> </div>
</div> </div>
{# Show last used buttons #} {# Show last used buttons #}
<div class="card bg-light mb-4"> <div class="card bg-light mb-4">
<div class="card-header"> <div class="card-header">
@ -159,7 +159,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endblock %} {% endblock %}
{% block extrajavascript %} {% block extrajavascript %}
<script type="text/javascript" src="{% static "js/consos.js" %}"></script> <script type="text/javascript" src="{% static "note/js/consos.js" %}"></script>
<script type="text/javascript"> <script type="text/javascript">
{% for button in highlighted %} {% for button in highlighted %}
{% if button.display %} {% if button.display %}

View File

@ -34,21 +34,21 @@ SPDX-License-Identifier: GPL-2.0-or-later
</div> </div>
</div> </div>
<hr> <hr>
<div class="row"> <div class="row justify-content-center">
{# Preview note profile (picture, username and balance) #} {# Preview note profile (picture, username and balance) #}
<div class="col-md-3" id="note_infos_div"> <div class="col-md picture-col" id="note_infos_div">
<div class="card bg-light border-success shadow mb-4 pt-4 text-center"> <div class="card bg-light mb-4 text-center">
<a id="profile_pic_link" href="#"><img src="{% static "member/img/default_picture.png" %}" <a id="profile_pic_link" href="#"><img src="{% static "member/img/default_picture.png" %}"
id="profile_pic" alt="" class="img-fluid rounded mx-auto"></a> id="profile_pic" alt="" class="img-fluid rounded mx-auto"></a>
<div class="card-body text-center"> <div class="card-body text-center p-2">
<span id="user_note"></span> <span id="user_note"><i class="small">{% trans "Please select a note" %}</i></span>
</div> </div>
</div> </div>
</div> </div>
{# list of emitters #} {# list of emitters #}
<div class="col-md-3" id="emitters_div"> <div class="col-md-3" id="emitters_div">
<div class="card bg-light border-success shadow mb-4"> <div class="card bg-light mb-4">
<div class="card-header"> <div class="card-header">
<p class="card-text font-weight-bold"> <p class="card-text font-weight-bold">
<label for="source_note" id="source_note_label">{% trans "Select emitters" %}</label> <label for="source_note" id="source_note_label">{% trans "Select emitters" %}</label>
@ -75,7 +75,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
{# list of receiver #} {# list of receiver #}
<div class="col-md-3" id="dests_div"> <div class="col-md-3" id="dests_div">
<div class="card bg-light border-info shadow mb-4"> <div class="card bg-light mb-4">
<div class="card-header"> <div class="card-header">
<p class="card-text font-weight-bold" id="dest_title"> <p class="card-text font-weight-bold" id="dest_title">
<label for="dest_note" id="dest_note_label">{% trans "Select receivers" %}</label> <label for="dest_note" id="dest_note_label">{% trans "Select receivers" %}</label>
@ -97,8 +97,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
</div> </div>
{# Information on transaction (amount, reason, name,...) #} {# Information on transaction (amount, reason, name,...) #}
<div class="col-md-3" id="external_div"> <div class="col-md" id="external_div">
<div class="card bg-light border-warning shadow mb-4"> <div class="card bg-light mb-4">
<div class="card-header"> <div class="card-header">
<p class="card-text font-weight-bold"> <p class="card-text font-weight-bold">
{% trans "Action" %} {% trans "Action" %}
@ -153,7 +153,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
</div> </div>
</div> </div>
{# transaction history #} {# transaction history #}
<div class="card shadow mb-4" id="history"> <div class="card mb-4" id="history">
<div class="card-header"> <div class="card-header">
<p class="card-text font-weight-bold"> <p class="card-text font-weight-bold">
{% trans "Recent transactions history" %} {% trans "Recent transactions history" %}
@ -176,5 +176,5 @@ SPDX-License-Identifier: GPL-2.0-or-later
select_receveirs_label = "{% trans "Select receivers" %}"; select_receveirs_label = "{% trans "Select receivers" %}";
transfer_type_label = "{% trans "Transfer type" %}"; transfer_type_label = "{% trans "Transfer type" %}";
</script> </script>
<script src="/static/js/transfer.js"></script> <script src="{% static "note/js/transfer.js" %}"></script>
{% endblock %} {% endblock %}

View File

@ -144,7 +144,7 @@ class TransactionTemplateUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, Up
class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView): class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
""" """
The Magic View that make people pay their beer and burgers. The Magic View that make people pay their beer and burgers.
(Most of the magic happens in the dark world of Javascript see `note_kfet/static/js/consos.js`) (Most of the magic happens in the dark world of Javascript see `static/note/js/consos.js`)
""" """
model = Transaction model = Transaction
template_name = "note/conso_form.html" template_name = "note/conso_form.html"

View File

@ -4,7 +4,6 @@
from functools import lru_cache from functools import lru_cache
from time import time from time import time
from django.conf import settings
from django.contrib.sessions.models import Session from django.contrib.sessions.models import Session
from note_kfet.middlewares import get_current_session from note_kfet.middlewares import get_current_session
@ -33,9 +32,9 @@ def memoize(f):
sess_funs = new_sess_funs sess_funs = new_sess_funs
def func(*args, **kwargs): def func(*args, **kwargs):
if settings.DEBUG: # if settings.DEBUG:
# Don't memoize in DEBUG mode # # Don't memoize in DEBUG mode
return f(*args, **kwargs) # return f(*args, **kwargs)
nonlocal last_collect nonlocal last_collect

View File

@ -2679,6 +2679,102 @@
"description": "Supprimer n'importe quel alias à une note non bloquée" "description": "Supprimer n'importe quel alias à une note non bloquée"
} }
}, },
{
"model": "permission.permission",
"pk": 172,
"fields": {
"model": [
"treasury",
"remittance"
],
"query": "{}",
"type": "view",
"mask": 3,
"field": "",
"permanent": false,
"description": "Voir toutes les remises"
}
},
{
"model": "permission.permission",
"pk": 173,
"fields": {
"model": [
"treasury",
"remittance"
],
"query": "{}",
"type": "add",
"mask": 3,
"field": "",
"permanent": false,
"description": "Ajouter une remise"
}
},
{
"model": "permission.permission",
"pk": 174,
"fields": {
"model": [
"treasury",
"remittance"
],
"query": "{}",
"type": "change",
"mask": 3,
"field": "",
"permanent": false,
"description": "Modifier une remise"
}
},
{
"model": "permission.permission",
"pk": 175,
"fields": {
"model": [
"treasury",
"remittance"
],
"query": "{}",
"type": "delete",
"mask": 3,
"field": "",
"permanent": false,
"description": "Supprimer une remise"
}
},
{
"model": "permission.permission",
"pk": 176,
"fields": {
"model": [
"auth",
"user"
],
"query": "{\"profile__registration_valid\": false}",
"type": "change",
"mask": 1,
"field": "",
"permanent": false,
"description": "Modifier n'importe quel utilisateur non encore inscrit"
}
},
{
"model": "permission.permission",
"pk": 177,
"fields": {
"model": [
"member",
"profile"
],
"query": "{\"registration_valid\": false}",
"type": "change",
"mask": 1,
"field": "",
"permanent": false,
"description": "Modifier n'importe quel profil non encore inscrit"
}
},
{ {
"model": "permission.role", "model": "permission.role",
"pk": 1, "pk": 1,
@ -2884,7 +2980,13 @@
163, 163,
164, 164,
170, 170,
171 171,
172,
173,
174,
175,
176,
177
] ]
} }
}, },
@ -3060,7 +3162,13 @@
168, 168,
169, 169,
170, 170,
171 171,
172,
173,
174,
175,
176,
177
] ]
} }
}, },
@ -3092,7 +3200,9 @@
167, 167,
168, 168,
170, 170,
171 171,
176,
177
] ]
} }
}, },
@ -3258,10 +3368,13 @@
138, 138,
139, 139,
140, 140,
143,
145, 145,
146, 146,
147, 147,
150 150,
176,
177
] ]
} }
}, },

View File

@ -199,6 +199,7 @@ class Permission(models.Model):
if self.field and self.type not in {'view', 'change'}: if self.field and self.type not in {'view', 'change'}:
raise ValidationError(_("Specifying field applies only to view and change permission types.")) raise ValidationError(_("Specifying field applies only to view and change permission types."))
@transaction.atomic
def save(self, **kwargs): def save(self, **kwargs):
self.full_clean() self.full_clean()
super().save() super().save()

View File

@ -14,6 +14,7 @@ class StrongDjangoObjectPermissions(DjangoObjectPermissions):
This is a simple patch of this class that controls view access. This is a simple patch of this class that controls view access.
""" """
# The queryset is filtered, and permissions are more powerful than a simple check than just "can view this model"
perms_map = { perms_map = {
'GET': ['%(app_label)s.view_%(model_name)s'], 'GET': ['%(app_label)s.view_%(model_name)s'],
'OPTIONS': [], 'OPTIONS': [],

View File

@ -6,6 +6,7 @@ from datetime import date
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.forms import HiddenInput from django.forms import HiddenInput
from django.http import Http404 from django.http import Http404
@ -56,6 +57,7 @@ class ProtectQuerysetMixin:
return form return form
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
""" """
Submit the form, if the page is a FormView. Submit the form, if the page is a FormView.

View File

@ -60,7 +60,7 @@ class ValidationForm(forms.Form):
soge = forms.BooleanField( soge = forms.BooleanField(
label=_("Inscription paid by Société Générale"), label=_("Inscription paid by Société Générale"),
required=False, required=False,
help_text=_("Check this case is the Société Générale paid the inscription."), help_text=_("Check this case if the Société Générale paid the inscription."),
) )
credit_type = forms.ModelChoiceField( credit_type = forms.ModelChoiceField(

View File

@ -6,7 +6,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% block content %} {% block content %}
<div class="row mt-4"> <div class="row mt-4">
<div class="col-md-3 mb-4"> <div class="col-xl-5 mb-4">
<div class="card bg-light shadow"> <div class="card bg-light shadow">
<div class="card-header text-center" > <div class="card-header text-center" >
<h4> {% trans "Account #" %} {{ object.pk }}</h4> <h4> {% trans "Account #" %} {{ object.pk }}</h4>
@ -50,7 +50,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-9"> <div class="col-md-7">
<div class="card bg-light shadow"> <div class="card bg-light shadow">
<form method="post"> <form method="post">
<div class="card-header text-center" > <div class="card-header text-center" >

View File

@ -5,6 +5,7 @@ from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.shortcuts import resolve_url, redirect from django.shortcuts import resolve_url, redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
@ -47,6 +48,7 @@ class UserCreateView(CreateView):
return context return context
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
""" """
If the form is valid, then the user is created with is_active set to False If the form is valid, then the user is created with is_active set to False
@ -234,6 +236,7 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
form.fields["first_name"].initial = user.first_name form.fields["first_name"].initial = user.first_name
return form return form
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
user = self.get_object() user = self.get_object()

@ -1 +1 @@
Subproject commit e5b76b7c35592aba4225115f933f2a7ed3a66df3 Subproject commit 7e27c3b71b04af0867d5fbe4916e2d1278637599

View File

@ -4,6 +4,7 @@
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit from crispy_forms.layout import Submit
from django import forms from django import forms
from django.db import transaction
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note_kfet.inputs import AmountInput from note_kfet.inputs import AmountInput
@ -149,6 +150,7 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
self.instance.transaction.bank = cleaned_data["bank"] self.instance.transaction.bank = cleaned_data["bank"]
return cleaned_data return cleaned_data
@transaction.atomic
def save(self, commit=True): def save(self, commit=True):
""" """
Save the transaction and the remittance. Save the transaction and the remittance.

View File

@ -5,7 +5,7 @@ from datetime import date
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models, transaction
from django.db.models import Q from django.db.models import Q
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone from django.utils import timezone
@ -76,6 +76,7 @@ class Invoice(models.Model):
verbose_name=_("tex source"), verbose_name=_("tex source"),
) )
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" """
When an invoice is generated, we store the tex source. When an invoice is generated, we store the tex source.
@ -228,6 +229,7 @@ class Remittance(models.Model):
""" """
return sum(transaction.total for transaction in self.transactions.all()) return sum(transaction.total for transaction in self.transactions.all())
@transaction.atomic
def save(self, force_insert=False, force_update=False, using=None, update_fields=None): def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
# Check if all transactions have the right type. # Check if all transactions have the right type.
if self.transactions.exists() and self.transactions.filter(~Q(source=self.remittance_type.note)).exists(): if self.transactions.exists() and self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
@ -291,11 +293,12 @@ class SogeCredit(models.Model):
@property @property
def valid(self): def valid(self):
return self.credit_transaction.valid return self.credit_transaction and self.credit_transaction.valid
@property @property
def amount(self): def amount(self):
return sum(transaction.total for transaction in self.transactions.all()) + 8000 return self.credit_transaction.total if self.valid \
else sum(transaction.total for transaction in self.transactions.all()) + 8000
def invalidate(self): def invalidate(self):
""" """
@ -305,10 +308,10 @@ class SogeCredit(models.Model):
if self.valid: if self.valid:
self.credit_transaction.valid = False self.credit_transaction.valid = False
self.credit_transaction.save() self.credit_transaction.save()
for transaction in self.transactions.all(): for tr in self.transactions.all():
transaction.valid = False tr.valid = False
transaction._force_save = True tr._force_save = True
transaction.save() tr.save()
def validate(self, force=False): def validate(self, force=False):
if self.valid and not force: if self.valid and not force:
@ -320,18 +323,20 @@ class SogeCredit(models.Model):
# Refresh credit amount # Refresh credit amount
self.save() self.save()
self.credit_transaction.valid = True self.credit_transaction.valid = True
self.credit_transaction._force_save = True
self.credit_transaction.save() self.credit_transaction.save()
self.save() self.save()
for transaction in self.transactions.all(): for tr in self.transactions.all():
transaction.valid = True tr.valid = True
transaction._force_save = True tr._force_save = True
transaction.created_at = timezone.now() tr.created_at = timezone.now()
transaction.save() tr.save()
@transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self.credit_transaction: if not self.credit_transaction:
self.credit_transaction = SpecialTransaction.objects.create( credit_transaction = SpecialTransaction(
source=NoteSpecial.objects.get(special_type="Virement bancaire"), source=NoteSpecial.objects.get(special_type="Virement bancaire"),
destination=self.user.note, destination=self.user.note,
quantity=1, quantity=1,
@ -342,6 +347,10 @@ class SogeCredit(models.Model):
bank="Société générale", bank="Société générale",
valid=False, valid=False,
) )
credit_transaction._force_save = True
credit_transaction.save()
credit_transaction.refresh_from_db()
self.credit_transaction = credit_transaction
elif not self.valid: elif not self.valid:
self.credit_transaction.amount = self.amount self.credit_transaction.amount = self.amount
self.credit_transaction._force_save = True self.credit_transaction._force_save = True
@ -361,11 +370,11 @@ class SogeCredit(models.Model):
"Please ask her/him to credit the note before invalidating this credit.")) "Please ask her/him to credit the note before invalidating this credit."))
self.invalidate() self.invalidate()
for transaction in self.transactions.all(): for tr in self.transactions.all():
transaction._force_save = True tr._force_save = True
transaction.valid = True tr.valid = True
transaction.created_at = timezone.now() tr.created_at = timezone.now()
transaction.save() tr.save()
self.credit_transaction.valid = False self.credit_transaction.valid = False
self.credit_transaction.reason += " (invalide)" self.credit_transaction.reason += " (invalide)"
self.credit_transaction.save() self.credit_transaction.save()

View File

@ -9,6 +9,7 @@ from tempfile import mkdtemp
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ValidationError, PermissionDenied from django.core.exceptions import ValidationError, PermissionDenied
from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.forms import Form from django.forms import Form
from django.http import HttpResponse from django.http import HttpResponse
@ -65,6 +66,7 @@ class InvoiceCreateView(ProtectQuerysetMixin, ProtectedCreateView):
del form.fields["locked"] del form.fields["locked"]
return form return form
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
ret = super().form_valid(form) ret = super().form_valid(form)
@ -144,6 +146,7 @@ class InvoiceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
del form.fields["id"] del form.fields["id"]
return form return form
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
ret = super().form_valid(form) ret = super().form_valid(form)
@ -439,6 +442,7 @@ class SogeCreditManageView(LoginRequiredMixin, ProtectQuerysetMixin, BaseFormVie
form_class = Form form_class = Form
extra_context = {"title": _("Manage credits from the Société générale")} extra_context = {"title": _("Manage credits from the Société générale")}
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
if "validate" in form.data: if "validate" in form.data:
self.get_object().validate(True) self.get_object().validate(True)

View File

@ -4,6 +4,7 @@
from random import choice from random import choice
from django import forms from django import forms
from django.db import transaction
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation
@ -88,6 +89,7 @@ class WEISurvey2020(WEISurvey):
""" """
form.set_registration(self.registration) form.set_registration(self.registration)
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
word = form.cleaned_data["word"] word = form.cleaned_data["word"]
self.information.step += 1 self.information.step += 1

View File

@ -1,59 +0,0 @@
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date
from django.core.management import BaseCommand
from django.db.models import Q
from member.models import Membership, Club
from wei.models import WEIClub
class Command(BaseCommand):
help = "Get mailing list registrations from the last wei. " \
"Usage: manage.py extract_ml_registrations -t {events,art,sport}. " \
"You can write this into a file with a pipe, then paste the document into your mail manager."
def add_arguments(self, parser):
parser.add_argument('--type', '-t', choices=["members", "clubs", "events", "art", "sport"], default="members",
help='Select the type of the mailing list (default members)')
parser.add_argument('--year', '-y', type=int, default=None,
help='Select the year of the concerned WEI. Default: last year')
def handle(self, *args, **options):
###########################################################
# WARNING #
###########################################################
#
# This code is obsolete.
# TODO: Improve the mailing list extraction system, and link it automatically with Mailman.
if options["type"] == "members":
for membership in Membership.objects.filter(
club__name="BDE",
date_start__lte=date.today(),
date_end__gte=date.today(),
).all():
self.stdout.write(membership.user.email)
return
if options["type"] == "clubs":
for club in Club.objects.all():
self.stdout.write(club.email)
return
if options["year"] is None:
wei = WEIClub.objects.order_by('-year').first()
else:
wei = WEIClub.objects.filter(year=options["year"])
if wei.exists():
wei = wei.get()
else:
wei = WEIClub.objects.order_by('-year').first()
self.stderr.write(self.style.WARNING("Warning: there was no WEI in year " + str(options["year"]) + ". "
+ "Assuming the last WEI (year " + str(wei.year) + ")"))
q = Q(ml_events_registration=True) if options["type"] == "events" else Q(ml_art_registration=True)\
if options["type"] == "art" else Q(ml_sport_registration=True)
registrations = wei.users.filter(q)
for registration in registrations.all():
self.stdout.write(registration.user.email)

View File

@ -238,7 +238,7 @@ class WEIRegistration(models.Model):
information_json = models.TextField( information_json = models.TextField(
default="{}", default="{}",
verbose_name=_("registration information"), verbose_name=_("registration information"),
help_text=_("Information about the registration (buses for old members, survey fot the new members), " help_text=_("Information about the registration (buses for old members, survey for the new members), "
"encoded in JSON"), "encoded in JSON"),
) )

View File

@ -10,6 +10,7 @@ from tempfile import mkdtemp
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import Q, Count from django.db.models import Q, Count
from django.db.models.functions.text import Lower from django.db.models.functions.text import Lower
from django.forms import HiddenInput from django.forms import HiddenInput
@ -84,6 +85,7 @@ class WEICreateView(ProtectQuerysetMixin, ProtectedCreateView):
date_end=date.today(), date_end=date.today(),
) )
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
form.instance.requires_membership = True form.instance.requires_membership = True
form.instance.parent_club = Club.objects.get(name="Kfet") form.instance.parent_club = Club.objects.get(name="Kfet")
@ -517,6 +519,7 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView):
del form.fields["information_json"] del form.fields["information_json"]
return form return form
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"]) form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
form.instance.first_year = True form.instance.first_year = True
@ -597,6 +600,7 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView):
return form return form
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"]) form.instance.wei = WEIClub.objects.get(pk=self.kwargs["wei_pk"])
form.instance.first_year = False form.instance.first_year = False
@ -688,6 +692,7 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
del form.fields["information_json"] del form.fields["information_json"]
return form return form
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
# If the membership is already validated, then we update the bus and the team (and the roles) # If the membership is already validated, then we update the bus and the team (and the roles)
if form.instance.is_validated: if form.instance.is_validated:
@ -866,6 +871,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
).all() ).all()
return form return form
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
""" """
Create membership, check that all is good, make transactions Create membership, check that all is good, make transactions
@ -1016,6 +1022,7 @@ class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView):
context["club"] = self.object.wei context["club"] = self.object.wei
return context return context
@transaction.atomic
def form_valid(self, form): def form_valid(self, form):
""" """
Update the survey with the data of the form. Update the survey with the data of the form.

View File

@ -7,9 +7,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-07 19:43+0200\n" "POT-Creation-Date: 2020-09-13 12:35+0200\n"
"PO-Revision-Date: 2020-09-03 23:47+0200\n" "PO-Revision-Date: 2020-09-13 12:39+0200\n"
"Last-Translator: \n" "Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
"Language-Team: \n" "Language-Team: \n"
"Language: de\n" "Language: de\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -524,7 +524,7 @@ msgid "This image cannot be loaded."
msgstr "Dieses Bild kann nicht geladen werden." msgstr "Dieses Bild kann nicht geladen werden."
#: apps/member/forms.py:139 apps/member/views.py:98 #: apps/member/forms.py:139 apps/member/views.py:98
#: apps/registration/forms.py:33 apps/registration/views.py:237 #: apps/registration/forms.py:33 apps/registration/views.py:241
msgid "An alias with a similar name already exists." msgid "An alias with a similar name already exists."
msgstr "Ein ähnliches Alias ist schon benutzt." msgstr "Ein ähnliches Alias ist schon benutzt."
@ -533,7 +533,7 @@ msgid "Inscription paid by Société Générale"
msgstr "Mitgliedschaft von der Société Générale bezahlt" msgstr "Mitgliedschaft von der Société Générale bezahlt"
#: apps/member/forms.py:164 apps/registration/forms.py:63 #: apps/member/forms.py:164 apps/registration/forms.py:63
msgid "Check this case is the Société Générale paid the inscription." msgid "Check this case if the Société Générale paid the inscription."
msgstr "Die Société Générale die Mitgliedschaft bezahlt." msgstr "Die Société Générale die Mitgliedschaft bezahlt."
#: apps/member/forms.py:169 apps/registration/forms.py:68 #: apps/member/forms.py:169 apps/registration/forms.py:68
@ -1118,8 +1118,8 @@ msgid "The membership must begin before {:%m-%d-%Y}."
msgstr "Die Mitgliedschaft muss vor {:%m-%d-Y} anfängen." msgstr "Die Mitgliedschaft muss vor {:%m-%d-Y} anfängen."
#: apps/member/views.py:644 apps/member/views.py:646 apps/member/views.py:648 #: apps/member/views.py:644 apps/member/views.py:646 apps/member/views.py:648
#: apps/registration/views.py:287 apps/registration/views.py:289 #: apps/registration/views.py:291 apps/registration/views.py:293
#: apps/registration/views.py:291 apps/wei/views.py:927 apps/wei/views.py:931 #: apps/registration/views.py:295 apps/wei/views.py:927 apps/wei/views.py:931
msgid "This field is required." msgid "This field is required."
msgstr "Dies ist ein Pflichtfeld." msgstr "Dies ist ein Pflichtfeld."
@ -1454,6 +1454,11 @@ msgstr "Löschen"
msgid "Edit" msgid "Edit"
msgstr "Bearbeiten" msgstr "Bearbeiten"
#: apps/note/templates/note/conso_form.html:22
#: apps/note/templates/note/transaction_form.html:44
msgid "Please select a note"
msgstr "Wählen Sie eine Note"
#: apps/note/templates/note/conso_form.html:32 #: apps/note/templates/note/conso_form.html:32
msgid "Consum" msgid "Consum"
msgstr "Konsumieren" msgstr "Konsumieren"
@ -1464,11 +1469,11 @@ msgstr "Konsumieren"
msgid "Name or alias..." msgid "Name or alias..."
msgstr "Name oder Alias..." msgstr "Name oder Alias..."
#: apps/note/templates/note/conso_form.html:52 #: apps/note/templates/note/conso_form.html:53
msgid "Select consumptions" msgid "Select consumptions"
msgstr "Verbrauchswerte auswählen" msgstr "Verbrauchswerte auswählen"
#: apps/note/templates/note/conso_form.html:61 #: apps/note/templates/note/conso_form.html:62
msgid "Consume!" msgid "Consume!"
msgstr "Konsumieren!" msgstr "Konsumieren!"
@ -1694,7 +1699,7 @@ msgstr ""
"Sie haben nicht die Berechtigung, das Feld {field} in dieser Instanz von " "Sie haben nicht die Berechtigung, das Feld {field} in dieser Instanz von "
"Modell {app_label} zu ändern. {model_name}" "Modell {app_label} zu ändern. {model_name}"
#: apps/permission/signals.py:73 apps/permission/views.py:89 #: apps/permission/signals.py:73 apps/permission/views.py:101
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"You don't have the permission to add an instance of model {app_label}." "You don't have the permission to add an instance of model {app_label}."
@ -1752,7 +1757,7 @@ msgstr "Abfrage:"
msgid "No associated permission" msgid "No associated permission"
msgstr "Keine zugehörige Berechtigung" msgstr "Keine zugehörige Berechtigung"
#: apps/permission/views.py:56 #: apps/permission/views.py:68
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"You don't have the permission to update this instance of the model " "You don't have the permission to update this instance of the model "
@ -1762,7 +1767,7 @@ msgstr ""
"diesen Parametern zu aktualisieren. Bitte korrigieren Sie Ihre Daten und " "diesen Parametern zu aktualisieren. Bitte korrigieren Sie Ihre Daten und "
"versuchen Sie es erneut." "versuchen Sie es erneut."
#: apps/permission/views.py:60 #: apps/permission/views.py:72
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"You don't have the permission to create an instance of the model \"{model}\" " "You don't have the permission to create an instance of the model \"{model}\" "
@ -1772,11 +1777,11 @@ msgstr ""
"diesen Parametern zu erstellen. Bitte korrigieren Sie Ihre Daten und " "diesen Parametern zu erstellen. Bitte korrigieren Sie Ihre Daten und "
"versuchen Sie es erneut." "versuchen Sie es erneut."
#: apps/permission/views.py:96 note_kfet/templates/base.html:106 #: apps/permission/views.py:108 note_kfet/templates/base.html:106
msgid "Rights" msgid "Rights"
msgstr "Rechten" msgstr "Rechten"
#: apps/permission/views.py:101 #: apps/permission/views.py:113
msgid "All rights" msgid "All rights"
msgstr "Alle Rechten" msgstr "Alle Rechten"
@ -1909,54 +1914,54 @@ msgstr "Danke"
msgid "The Note Kfet team." msgid "The Note Kfet team."
msgstr "Die NoteKfet Team." msgstr "Die NoteKfet Team."
#: apps/registration/views.py:38 #: apps/registration/views.py:39
msgid "Register new user" msgid "Register new user"
msgstr "Neuen User registrieren" msgstr "Neuen User registrieren"
#: apps/registration/views.py:82 #: apps/registration/views.py:83
msgid "Email validation" msgid "Email validation"
msgstr "Email validierung" msgstr "Email validierung"
#: apps/registration/views.py:84 #: apps/registration/views.py:85
msgid "Validate email" msgid "Validate email"
msgstr "Email validieren" msgstr "Email validieren"
#: apps/registration/views.py:126 #: apps/registration/views.py:127
msgid "Email validation unsuccessful" msgid "Email validation unsuccessful"
msgstr "Email validierung unerfolgreich" msgstr "Email validierung unerfolgreich"
#: apps/registration/views.py:137 #: apps/registration/views.py:138
msgid "Email validation email sent" msgid "Email validation email sent"
msgstr "Validierungsemail wurde gesendet" msgstr "Validierungsemail wurde gesendet"
#: apps/registration/views.py:145 #: apps/registration/views.py:146
msgid "Resend email validation link" msgid "Resend email validation link"
msgstr "E-Mail-Validierungslink erneut senden" msgstr "E-Mail-Validierungslink erneut senden"
#: apps/registration/views.py:163 #: apps/registration/views.py:164
msgid "Pre-registered users list" msgid "Pre-registered users list"
msgstr "Vorregistrierte Userliste" msgstr "Vorregistrierte Userliste"
#: apps/registration/views.py:187 #: apps/registration/views.py:188
msgid "Unregistered users" msgid "Unregistered users"
msgstr "Unregistrierte Users" msgstr "Unregistrierte Users"
#: apps/registration/views.py:200 #: apps/registration/views.py:201
msgid "Registration detail" msgid "Registration detail"
msgstr "Registrierung Detailen" msgstr "Registrierung Detailen"
#: apps/registration/views.py:256 #: apps/registration/views.py:260
msgid "You must join the BDE." msgid "You must join the BDE."
msgstr "Sie müssen die BDE beitreten." msgstr "Sie müssen die BDE beitreten."
#: apps/registration/views.py:280 #: apps/registration/views.py:284
msgid "" msgid ""
"The entered amount is not enough for the memberships, should be at least {}" "The entered amount is not enough for the memberships, should be at least {}"
msgstr "" msgstr ""
"Der eingegebene Betrag reicht für die Mitgliedschaft nicht aus, sollte " "Der eingegebene Betrag reicht für die Mitgliedschaft nicht aus, sollte "
"mindestens {} betragen" "mindestens {} betragen"
#: apps/registration/views.py:355 #: apps/registration/views.py:364
msgid "Invalidate pre-registration" msgid "Invalidate pre-registration"
msgstr "Ungültige Vorregistrierung" msgstr "Ungültige Vorregistrierung"
@ -2102,7 +2107,7 @@ msgstr "spezielle Transaktion Proxies"
msgid "credit transaction" msgid "credit transaction"
msgstr "Kredit Transaktion" msgstr "Kredit Transaktion"
#: apps/treasury/models.py:352 #: apps/treasury/models.py:361
msgid "" msgid ""
"This user doesn't have enough money to pay the memberships with its note. " "This user doesn't have enough money to pay the memberships with its note. "
"Please ask her/him to credit the note before invalidating this credit." "Please ask her/him to credit the note before invalidating this credit."
@ -2110,16 +2115,16 @@ msgstr ""
"Dieser Benutzer hat nicht genug Geld, um die Mitgliedschaften mit seiner " "Dieser Benutzer hat nicht genug Geld, um die Mitgliedschaften mit seiner "
"Note zu bezahlen." "Note zu bezahlen."
#: apps/treasury/models.py:364 #: apps/treasury/models.py:376
#: apps/treasury/templates/treasury/sogecredit_detail.html:10 #: apps/treasury/templates/treasury/sogecredit_detail.html:10
msgid "Credit from the Société générale" msgid "Credit from the Société générale"
msgstr "Kredit von der Société générale" msgstr "Kredit von der Société générale"
#: apps/treasury/models.py:365 #: apps/treasury/models.py:377
msgid "Credits from the Société générale" msgid "Credits from the Société générale"
msgstr "Krediten von der Société générale" msgstr "Krediten von der Société générale"
#: apps/treasury/models.py:368 #: apps/treasury/models.py:380
#, python-brace-format #, python-brace-format
msgid "Soge credit for {user}" msgid "Soge credit for {user}"
msgstr "Kredit von der Société générale für {user}" msgstr "Kredit von der Société générale für {user}"
@ -2356,7 +2361,7 @@ msgstr "Fügen Sie einer Überweisung eine Transaktion hinzu"
msgid "List of credits from the Société générale" msgid "List of credits from the Société générale"
msgstr "Kreditliste von Société générale" msgstr "Kreditliste von Société générale"
#: apps/treasury/views.py:443 #: apps/treasury/views.py:440
msgid "Manage credits from the Société générale" msgid "Manage credits from the Société générale"
msgstr "Krediten von der Société générale handeln" msgstr "Krediten von der Société générale handeln"
@ -2519,7 +2524,7 @@ msgstr "Registrierung Detailen"
#: apps/wei/models.py:241 #: apps/wei/models.py:241
msgid "" msgid ""
"Information about the registration (buses for old members, survey fot the " "Information about the registration (buses for old members, survey for the "
"new members), encoded in JSON" "new members), encoded in JSON"
msgstr "" msgstr ""
"Informationen zur Registrierung (Busse für alte Mitglieder, Umfrage für neue " "Informationen zur Registrierung (Busse für alte Mitglieder, Umfrage für neue "
@ -2907,6 +2912,10 @@ msgid "English"
msgstr "English" msgstr "English"
#: note_kfet/settings/base.py:157 #: note_kfet/settings/base.py:157
msgid "Spanish"
msgstr "Spanisch"
#: note_kfet/settings/base.py:158
msgid "French" msgid "French"
msgstr "Französich" msgstr "Französich"
@ -3152,3 +3161,6 @@ msgstr ""
"können. Sie müssen zum Kfet gehen und die Registrierungbeitrag bezahlen. Sie " "können. Sie müssen zum Kfet gehen und die Registrierungbeitrag bezahlen. Sie "
"müssen Ihre E-Mail-Adresse auch überprüfen, indem Sie dem Link folgen, den " "müssen Ihre E-Mail-Adresse auch überprüfen, indem Sie dem Link folgen, den "
"Sie erhalten haben." "Sie erhalten haben."
#~ msgid "Check this case is the Société Générale paid the inscription."
#~ msgstr "Die Société Générale die Mitgliedschaft bezahlt."

File diff suppressed because it is too large Load Diff

View File

@ -7,9 +7,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-07 19:43+0200\n" "POT-Creation-Date: 2020-09-13 12:35+0200\n"
"PO-Revision-Date: 2020-09-02 23:18+0200\n" "PO-Revision-Date: 2020-09-13 12:36+0200\n"
"Last-Translator: \n" "Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
"Language-Team: \n" "Language-Team: \n"
"Language: fr\n" "Language: fr\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
@ -438,7 +438,7 @@ msgstr "données précédentes"
#: apps/logs/models.py:55 #: apps/logs/models.py:55
msgid "new data" msgid "new data"
msgstr "ouvelles données" msgstr "nouvelles données"
#: apps/logs/models.py:63 #: apps/logs/models.py:63
msgid "create" msgid "create"
@ -526,7 +526,7 @@ msgid "This image cannot be loaded."
msgstr "Cette image ne peut pas être chargée." msgstr "Cette image ne peut pas être chargée."
#: apps/member/forms.py:139 apps/member/views.py:98 #: apps/member/forms.py:139 apps/member/views.py:98
#: apps/registration/forms.py:33 apps/registration/views.py:237 #: apps/registration/forms.py:33 apps/registration/views.py:241
msgid "An alias with a similar name already exists." msgid "An alias with a similar name already exists."
msgstr "Un alias avec un nom similaire existe déjà." msgstr "Un alias avec un nom similaire existe déjà."
@ -535,7 +535,7 @@ msgid "Inscription paid by Société Générale"
msgstr "Inscription payée par la Société générale" msgstr "Inscription payée par la Société générale"
#: apps/member/forms.py:164 apps/registration/forms.py:63 #: apps/member/forms.py:164 apps/registration/forms.py:63
msgid "Check this case is the Société Générale paid the inscription." msgid "Check this case if the Société Générale paid the inscription."
msgstr "Cochez cette case si la Société Générale a payé l'inscription." msgstr "Cochez cette case si la Société Générale a payé l'inscription."
#: apps/member/forms.py:169 apps/registration/forms.py:68 #: apps/member/forms.py:169 apps/registration/forms.py:68
@ -550,7 +550,7 @@ msgstr "Pas de rechargement"
#: apps/member/forms.py:172 #: apps/member/forms.py:172
msgid "You can credit the note of the user." msgid "You can credit the note of the user."
msgstr "Vous pouvez créditer la note de l'utisateur avant l'adhésion." msgstr "Vous pouvez créditer la note de l'utilisateur avant l'adhésion."
#: apps/member/forms.py:176 apps/registration/forms.py:74 #: apps/member/forms.py:176 apps/registration/forms.py:74
#: apps/wei/forms/registration.py:89 #: apps/wei/forms/registration.py:89
@ -1017,7 +1017,7 @@ msgstr "Changer le mot de passe"
#: apps/member/templates/member/includes/profile_info.html:53 #: apps/member/templates/member/includes/profile_info.html:53
msgid "API token" msgid "API token"
msgstr "Acces API" msgstr "Accès API"
#: apps/member/templates/member/manage_auth_tokens.html:19 #: apps/member/templates/member/manage_auth_tokens.html:19
msgid "Token" msgid "Token"
@ -1120,8 +1120,8 @@ msgid "The membership must begin before {:%m-%d-%Y}."
msgstr "L'adhésion doit commencer avant le {:%d/%m/%Y}." msgstr "L'adhésion doit commencer avant le {:%d/%m/%Y}."
#: apps/member/views.py:644 apps/member/views.py:646 apps/member/views.py:648 #: apps/member/views.py:644 apps/member/views.py:646 apps/member/views.py:648
#: apps/registration/views.py:287 apps/registration/views.py:289 #: apps/registration/views.py:291 apps/registration/views.py:293
#: apps/registration/views.py:291 apps/wei/views.py:927 apps/wei/views.py:931 #: apps/registration/views.py:295 apps/wei/views.py:927 apps/wei/views.py:931
msgid "This field is required." msgid "This field is required."
msgstr "Ce champ est requis." msgstr "Ce champ est requis."
@ -1459,6 +1459,11 @@ msgstr "Supprimer"
msgid "Edit" msgid "Edit"
msgstr "Éditer" msgstr "Éditer"
#: apps/note/templates/note/conso_form.html:22
#: apps/note/templates/note/transaction_form.html:44
msgid "Please select a note"
msgstr "Sélectionnez une note"
#: apps/note/templates/note/conso_form.html:32 #: apps/note/templates/note/conso_form.html:32
msgid "Consum" msgid "Consum"
msgstr "Consommer" msgstr "Consommer"
@ -1469,11 +1474,11 @@ msgstr "Consommer"
msgid "Name or alias..." msgid "Name or alias..."
msgstr "Pseudo ou alias ..." msgstr "Pseudo ou alias ..."
#: apps/note/templates/note/conso_form.html:52 #: apps/note/templates/note/conso_form.html:53
msgid "Select consumptions" msgid "Select consumptions"
msgstr "Sélection des consommations" msgstr "Sélection des consommations"
#: apps/note/templates/note/conso_form.html:61 #: apps/note/templates/note/conso_form.html:62
msgid "Consume!" msgid "Consume!"
msgstr "Consommer !" msgstr "Consommer !"
@ -1701,7 +1706,7 @@ msgstr ""
"Vous n'avez pas la permission de modifier le champ {field} sur l'instance du " "Vous n'avez pas la permission de modifier le champ {field} sur l'instance du "
"modèle {app_label}.{model_name}." "modèle {app_label}.{model_name}."
#: apps/permission/signals.py:73 apps/permission/views.py:89 #: apps/permission/signals.py:73 apps/permission/views.py:101
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"You don't have the permission to add an instance of model {app_label}." "You don't have the permission to add an instance of model {app_label}."
@ -1760,7 +1765,7 @@ msgstr "Requête :"
msgid "No associated permission" msgid "No associated permission"
msgstr "Pas de permission associée" msgstr "Pas de permission associée"
#: apps/permission/views.py:56 #: apps/permission/views.py:68
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"You don't have the permission to update this instance of the model " "You don't have the permission to update this instance of the model "
@ -1769,7 +1774,7 @@ msgstr ""
"Vous n'avez pas la permission de modifier cette instance du modèle « {model} " "Vous n'avez pas la permission de modifier cette instance du modèle « {model} "
"» avec ces paramètres. Merci de les corriger et de réessayer." "» avec ces paramètres. Merci de les corriger et de réessayer."
#: apps/permission/views.py:60 #: apps/permission/views.py:72
#, python-brace-format #, python-brace-format
msgid "" msgid ""
"You don't have the permission to create an instance of the model \"{model}\" " "You don't have the permission to create an instance of the model \"{model}\" "
@ -1778,11 +1783,11 @@ msgstr ""
"Vous n'avez pas la permission d'ajouter une instance du modèle « {model} » " "Vous n'avez pas la permission d'ajouter une instance du modèle « {model} » "
"avec ces paramètres. Merci de les corriger et de réessayer." "avec ces paramètres. Merci de les corriger et de réessayer."
#: apps/permission/views.py:96 note_kfet/templates/base.html:106 #: apps/permission/views.py:108 note_kfet/templates/base.html:106
msgid "Rights" msgid "Rights"
msgstr "Droits" msgstr "Droits"
#: apps/permission/views.py:101 #: apps/permission/views.py:113
msgid "All rights" msgid "All rights"
msgstr "Tous les droits" msgstr "Tous les droits"
@ -1913,54 +1918,54 @@ msgstr "Merci"
msgid "The Note Kfet team." msgid "The Note Kfet team."
msgstr "L'équipe de la Note Kfet." msgstr "L'équipe de la Note Kfet."
#: apps/registration/views.py:38 #: apps/registration/views.py:39
msgid "Register new user" msgid "Register new user"
msgstr "Enregistrer un nouvel utilisateur" msgstr "Enregistrer un nouvel utilisateur"
#: apps/registration/views.py:82 #: apps/registration/views.py:83
msgid "Email validation" msgid "Email validation"
msgstr "Validation de l'adresse mail" msgstr "Validation de l'adresse mail"
#: apps/registration/views.py:84 #: apps/registration/views.py:85
msgid "Validate email" msgid "Validate email"
msgstr "Valider l'adresse e-mail" msgstr "Valider l'adresse e-mail"
#: apps/registration/views.py:126 #: apps/registration/views.py:127
msgid "Email validation unsuccessful" msgid "Email validation unsuccessful"
msgstr "La validation de l'adresse mail a échoué" msgstr "La validation de l'adresse mail a échoué"
#: apps/registration/views.py:137 #: apps/registration/views.py:138
msgid "Email validation email sent" msgid "Email validation email sent"
msgstr "L'email de vérification de l'adresse email a bien été envoyé" msgstr "L'email de vérification de l'adresse email a bien été envoyé"
#: apps/registration/views.py:145 #: apps/registration/views.py:146
msgid "Resend email validation link" msgid "Resend email validation link"
msgstr "Renvoyer le lien de validation" msgstr "Renvoyer le lien de validation"
#: apps/registration/views.py:163 #: apps/registration/views.py:164
msgid "Pre-registered users list" msgid "Pre-registered users list"
msgstr "Liste des utilisateurs en attente d'inscription" msgstr "Liste des utilisateurs en attente d'inscription"
#: apps/registration/views.py:187 #: apps/registration/views.py:188
msgid "Unregistered users" msgid "Unregistered users"
msgstr "Utilisateurs en attente d'inscription" msgstr "Utilisateurs en attente d'inscription"
#: apps/registration/views.py:200 #: apps/registration/views.py:201
msgid "Registration detail" msgid "Registration detail"
msgstr "Détails de l'inscription" msgstr "Détails de l'inscription"
#: apps/registration/views.py:256 #: apps/registration/views.py:260
msgid "You must join the BDE." msgid "You must join the BDE."
msgstr "Vous devez adhérer au BDE." msgstr "Vous devez adhérer au BDE."
#: apps/registration/views.py:280 #: apps/registration/views.py:284
msgid "" msgid ""
"The entered amount is not enough for the memberships, should be at least {}" "The entered amount is not enough for the memberships, should be at least {}"
msgstr "" msgstr ""
"Le montant crédité est trop faible pour adhérer, il doit être au minimum de " "Le montant crédité est trop faible pour adhérer, il doit être au minimum de "
"{}" "{}"
#: apps/registration/views.py:355 #: apps/registration/views.py:364
msgid "Invalidate pre-registration" msgid "Invalidate pre-registration"
msgstr "Invalider l'inscription" msgstr "Invalider l'inscription"
@ -2106,7 +2111,7 @@ msgstr "proxys de transactions spéciales"
msgid "credit transaction" msgid "credit transaction"
msgstr "transaction de crédit" msgstr "transaction de crédit"
#: apps/treasury/models.py:352 #: apps/treasury/models.py:361
msgid "" msgid ""
"This user doesn't have enough money to pay the memberships with its note. " "This user doesn't have enough money to pay the memberships with its note. "
"Please ask her/him to credit the note before invalidating this credit." "Please ask her/him to credit the note before invalidating this credit."
@ -2114,16 +2119,16 @@ msgstr ""
"Cet utilisateur n'a pas assez d'argent pour payer les adhésions avec sa " "Cet utilisateur n'a pas assez d'argent pour payer les adhésions avec sa "
"note. Merci de lui demander de recharger sa note avant d'invalider ce crédit." "note. Merci de lui demander de recharger sa note avant d'invalider ce crédit."
#: apps/treasury/models.py:364 #: apps/treasury/models.py:376
#: apps/treasury/templates/treasury/sogecredit_detail.html:10 #: apps/treasury/templates/treasury/sogecredit_detail.html:10
msgid "Credit from the Société générale" msgid "Credit from the Société générale"
msgstr "Crédit de la Société générale" msgstr "Crédit de la Société générale"
#: apps/treasury/models.py:365 #: apps/treasury/models.py:377
msgid "Credits from the Société générale" msgid "Credits from the Société générale"
msgstr "Crédits de la Société générale" msgstr "Crédits de la Société générale"
#: apps/treasury/models.py:368 #: apps/treasury/models.py:380
#, python-brace-format #, python-brace-format
msgid "Soge credit for {user}" msgid "Soge credit for {user}"
msgstr "Crédit de la société générale pour l'utilisateur {user}" msgstr "Crédit de la société générale pour l'utilisateur {user}"
@ -2358,7 +2363,7 @@ msgstr "Joindre une transaction à une remise"
msgid "List of credits from the Société générale" msgid "List of credits from the Société générale"
msgstr "Liste des crédits de la Société générale" msgstr "Liste des crédits de la Société générale"
#: apps/treasury/views.py:443 #: apps/treasury/views.py:440
msgid "Manage credits from the Société générale" msgid "Manage credits from the Société générale"
msgstr "Gérer les crédits de la Société générale" msgstr "Gérer les crédits de la Société générale"
@ -2523,7 +2528,7 @@ msgstr "informations sur l'inscription"
#: apps/wei/models.py:241 #: apps/wei/models.py:241
msgid "" msgid ""
"Information about the registration (buses for old members, survey fot the " "Information about the registration (buses for old members, survey for the "
"new members), encoded in JSON" "new members), encoded in JSON"
msgstr "" msgstr ""
"Informations sur l'inscription (bus pour les 2A+, questionnaire pour les " "Informations sur l'inscription (bus pour les 2A+, questionnaire pour les "
@ -2868,7 +2873,7 @@ msgid ""
"This user can't be in her/his first year since he/she has already " "This user can't be in her/his first year since he/she has already "
"participated to a WEI." "participated to a WEI."
msgstr "" msgstr ""
"Cet utilisateur ne peut pas être en première année puisqu'iel a déjà " "Cet utilisateur ne peut pas être en première année puisqu'il a déjà "
"participé à un WEI." "participé à un WEI."
#: apps/wei/views.py:549 #: apps/wei/views.py:549
@ -2908,6 +2913,10 @@ msgid "English"
msgstr "Anglais" msgstr "Anglais"
#: note_kfet/settings/base.py:157 #: note_kfet/settings/base.py:157
msgid "Spanish"
msgstr "Espagnol"
#: note_kfet/settings/base.py:158
msgid "French" msgid "French"
msgstr "Français" msgstr "Français"
@ -3083,7 +3092,7 @@ msgid ""
"password twice so we can verify you typed it in correctly." "password twice so we can verify you typed it in correctly."
msgstr "" msgstr ""
"Veuillez entrer votre ancien mot de passe pour des raisons de sécurité, puis " "Veuillez entrer votre ancien mot de passe pour des raisons de sécurité, puis "
"renseigné votre nouveau mot de passe à deux reprises, pour être sur de " "renseigner votre nouveau mot de passe à deux reprises, pour être sûr de "
"l'avoir tapé correctement." "l'avoir tapé correctement."
#: note_kfet/templates/registration/password_change_form.html:16 #: note_kfet/templates/registration/password_change_form.html:16
@ -3153,3 +3162,6 @@ msgstr ""
"vous connecter. Vous devez vous rendre à la Kfet et payer les frais " "vous connecter. Vous devez vous rendre à la Kfet et payer les frais "
"d'adhésion. Vous devez également valider votre adresse email en suivant le " "d'adhésion. Vous devez également valider votre adresse email en suivant le "
"lien que vous avez reçu." "lien que vous avez reçu."
#~ msgid "Check this case is the Société Générale paid the inscription."
#~ msgstr "Cochez cette case si la Société Générale a payé l'inscription."

View File

@ -50,6 +50,20 @@ class SessionMiddleware(object):
def __call__(self, request): def __call__(self, request):
user = request.user user = request.user
# If we authenticate through a token to connect to the API, then we query the good user
if 'HTTP_AUTHORIZATION' in request.META and request.path.startswith("/api"):
token = request.META.get('HTTP_AUTHORIZATION')
if token.startswith("Token "):
token = token[6:]
from rest_framework.authtoken.models import Token
if Token.objects.filter(key=token).exists():
token_obj = Token.objects.get(key=token)
user = token_obj.user
session = request.session
session["permission_mask"] = 42
session.save()
if 'HTTP_X_REAL_IP' in request.META: if 'HTTP_X_REAL_IP' in request.META:
ip = request.META.get('HTTP_X_REAL_IP') ip = request.META.get('HTTP_X_REAL_IP')
elif 'HTTP_X_FORWARDED_FOR' in request.META: elif 'HTTP_X_FORWARDED_FOR' in request.META:

View File

@ -154,6 +154,7 @@ from django.utils.translation import gettext_lazy as _
LANGUAGES = [ LANGUAGES = [
('de', _('German')), ('de', _('German')),
('en', _('English')), ('en', _('English')),
('es', _('Spanish')),
('fr', _('French')), ('fr', _('French')),
] ]

View File

@ -22,6 +22,11 @@
border-bottom-color: rgba(0, 0, 0, .250); border-bottom-color: rgba(0, 0, 0, .250);
} }
/* Fixed width picture column */
.picture-col {
max-width: 202px;
}
/* Limit fluid container to a max size */ /* Limit fluid container to a max size */
.container-fluid { .container-fluid {
max-width: 1600px; max-width: 1600px;

View File

@ -177,12 +177,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
onchange="this.form.submit()"> onchange="this.form.submit()">
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %} {% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %} {% for lang_code, lang_name in LANGUAGES %}
{% for language in languages %} <option value="{{ lang_code }}"
<option value="{{ language.code }}" {% if lang_code == LANGUAGE_CODE %}
{% if language.code == LANGUAGE_CODE %}
selected{% endif %}> selected{% endif %}>
{{ language.name_local }} ({{ language.code }}) {{ lang_name }} ({{ lang_code }})
</option> </option>
{% endfor %} {% endfor %}
</select> </select>

View File

@ -36,8 +36,9 @@ urlpatterns = [
path('coffee/', include('django_htcpcp_tea.urls')), path('coffee/', include('django_htcpcp_tea.urls')),
] ]
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # During development, serve media files
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if "cas_server" in settings.INSTALLED_APPS: if "cas_server" in settings.INSTALLED_APPS: