mirror of https://gitlab.crans.org/bde/nk20
Merge branch 'beta' into 'master'
Ensure the integrity of all note balances in multiple transactions See merge request bde/nk20!131
This commit is contained in:
commit
536f0ec226
|
@ -205,7 +205,7 @@ class TestMemberships(TestCase):
|
||||||
first_name="Toto",
|
first_name="Toto",
|
||||||
bank="Le matelas",
|
bank="Le matelas",
|
||||||
))
|
))
|
||||||
self.assertRedirects(response, club.get_absolute_url(), 302, 200)
|
self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)
|
||||||
|
|
||||||
self.assertTrue(Membership.objects.filter(user=user, club=club).exists())
|
self.assertTrue(Membership.objects.filter(user=user, club=club).exists())
|
||||||
|
|
||||||
|
@ -244,9 +244,9 @@ class TestMemberships(TestCase):
|
||||||
first_name="Toto",
|
first_name="Toto",
|
||||||
bank="Bank",
|
bank="Bank",
|
||||||
))
|
))
|
||||||
self.assertRedirects(response, club.get_absolute_url(), 302, 200)
|
self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)
|
||||||
|
|
||||||
response = self.client.get(user.profile.get_absolute_url())
|
response = self.client.get(club.get_absolute_url())
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
def test_auto_join_kfet_when_join_bde_with_soge(self):
|
def test_auto_join_kfet_when_join_bde_with_soge(self):
|
||||||
|
@ -273,7 +273,7 @@ class TestMemberships(TestCase):
|
||||||
first_name="Toto",
|
first_name="Toto",
|
||||||
bank="Société générale",
|
bank="Société générale",
|
||||||
))
|
))
|
||||||
self.assertRedirects(response, bde.get_absolute_url(), 302, 200)
|
self.assertRedirects(response, user.profile.get_absolute_url(), 302, 200)
|
||||||
|
|
||||||
self.assertTrue(Membership.objects.filter(user=user, club=bde).exists())
|
self.assertTrue(Membership.objects.filter(user=user, club=bde).exists())
|
||||||
self.assertTrue(Membership.objects.filter(user=user, club=kfet).exists())
|
self.assertTrue(Membership.objects.filter(user=user, club=kfet).exists())
|
||||||
|
|
|
@ -157,7 +157,7 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||||
history_table.paginate(per_page=20, page=self.request.GET.get("transaction-page", 1))
|
history_table.paginate(per_page=20, page=self.request.GET.get("transaction-page", 1))
|
||||||
context['history_list'] = history_table
|
context['history_list'] = history_table
|
||||||
|
|
||||||
club_list = Membership.objects.filter(user=user, date_end__gte=date.today())\
|
club_list = Membership.objects.filter(user=user, date_end__gte=date.today() - timedelta(days=15))\
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))
|
.filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))
|
||||||
membership_table = MembershipTable(data=club_list, prefix='membership-')
|
membership_table = MembershipTable(data=club_list, prefix='membership-')
|
||||||
membership_table.paginate(per_page=10, page=self.request.GET.get("membership-page", 1))
|
membership_table.paginate(per_page=10, page=self.request.GET.get("membership-page", 1))
|
||||||
|
@ -409,7 +409,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||||
# member list
|
# member list
|
||||||
club_member = Membership.objects.filter(
|
club_member = Membership.objects.filter(
|
||||||
club=club,
|
club=club,
|
||||||
date_end__gte=date.today(),
|
date_end__gte=date.today() - timedelta(days=15),
|
||||||
).filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))
|
).filter(PermissionBackend.filter_queryset(self.request.user, Membership, "view"))
|
||||||
|
|
||||||
membership_table = MembershipTable(data=club_member, prefix="membership-")
|
membership_table = MembershipTable(data=club_member, prefix="membership-")
|
||||||
|
@ -680,6 +680,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view")) \
|
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view")) \
|
||||||
.get(pk=self.kwargs["club_pk"])
|
.get(pk=self.kwargs["club_pk"])
|
||||||
user = form.instance.user
|
user = form.instance.user
|
||||||
|
old_membership = None
|
||||||
else: # get from url for renewal
|
else: # get from url for renewal
|
||||||
old_membership = self.get_queryset().get(pk=self.kwargs["pk"])
|
old_membership = self.get_queryset().get(pk=self.kwargs["pk"])
|
||||||
club = old_membership.club
|
club = old_membership.club
|
||||||
|
@ -754,6 +755,9 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
member_role = Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all() \
|
member_role = Role.objects.filter(Q(name="Adhérent BDE") | Q(name="Membre de club")).all() \
|
||||||
if club.name == "BDE" else Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all() \
|
if club.name == "BDE" else Role.objects.filter(Q(name="Adhérent Kfet") | Q(name="Membre de club")).all() \
|
||||||
if club.name == "Kfet"else Role.objects.filter(name="Membre de club").all()
|
if club.name == "Kfet"else Role.objects.filter(name="Membre de club").all()
|
||||||
|
# Set the same roles as before
|
||||||
|
if old_membership:
|
||||||
|
member_role = member_role.union(old_membership.roles.all())
|
||||||
form.instance.roles.set(member_role)
|
form.instance.roles.set(member_role)
|
||||||
form.instance._force_save = True
|
form.instance._force_save = True
|
||||||
form.instance.save()
|
form.instance.save()
|
||||||
|
@ -791,7 +795,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse_lazy('member:club_detail', kwargs={'pk': self.object.club.id})
|
return reverse_lazy('member:user_detail', kwargs={'pk': self.object.user.id})
|
||||||
|
|
||||||
|
|
||||||
class ClubManageRolesView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
class ClubManageRolesView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models.signals import post_save, pre_delete
|
from django.db.models.signals import pre_delete, pre_save, post_save
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from . import signals
|
from . import signals
|
||||||
|
@ -17,6 +17,15 @@ class NoteConfig(AppConfig):
|
||||||
"""
|
"""
|
||||||
Define app internal signals to interact with other apps
|
Define app internal signals to interact with other apps
|
||||||
"""
|
"""
|
||||||
|
pre_save.connect(
|
||||||
|
signals.pre_save_note,
|
||||||
|
sender="note.noteuser",
|
||||||
|
)
|
||||||
|
pre_save.connect(
|
||||||
|
signals.pre_save_note,
|
||||||
|
sender="note.noteclub",
|
||||||
|
)
|
||||||
|
|
||||||
post_save.connect(
|
post_save.connect(
|
||||||
signals.save_user_note,
|
signals.save_user_note,
|
||||||
sender=settings.AUTH_USER_MODEL,
|
sender=settings.AUTH_USER_MODEL,
|
||||||
|
|
|
@ -159,20 +159,6 @@ 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):
|
|
||||||
if self.pk and self.balance < 0:
|
|
||||||
old_note = NoteUser.objects.get(pk=self.pk)
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
if old_note.balance >= 0:
|
|
||||||
# Passage en négatif
|
|
||||||
self.last_negative = timezone.now()
|
|
||||||
self._force_save = True
|
|
||||||
self.save(*args, **kwargs)
|
|
||||||
self.send_mail_negative_balance()
|
|
||||||
else:
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
def send_mail_negative_balance(self):
|
def send_mail_negative_balance(self):
|
||||||
plain_text = render_to_string("note/mails/negative_balance.txt", dict(note=self))
|
plain_text = render_to_string("note/mails/negative_balance.txt", dict(note=self))
|
||||||
html = render_to_string("note/mails/negative_balance.html", dict(note=self))
|
html = render_to_string("note/mails/negative_balance.html", dict(note=self))
|
||||||
|
@ -201,20 +187,6 @@ 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):
|
|
||||||
if self.pk and self.balance < 0:
|
|
||||||
old_note = NoteClub.objects.get(pk=self.pk)
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
if old_note.balance >= 0:
|
|
||||||
# Passage en négatif
|
|
||||||
self.last_negative = timezone.now()
|
|
||||||
self._force_save = True
|
|
||||||
self.save(*args, **kwargs)
|
|
||||||
self.send_mail_negative_balance()
|
|
||||||
else:
|
|
||||||
super().save(*args, **kwargs)
|
|
||||||
|
|
||||||
def send_mail_negative_balance(self):
|
def send_mail_negative_balance(self):
|
||||||
plain_text = render_to_string("note/mails/negative_balance.txt", dict(note=self))
|
plain_text = render_to_string("note/mails/negative_balance.txt", dict(note=self))
|
||||||
html = render_to_string("note/mails/negative_balance.html", dict(note=self))
|
html = render_to_string("note/mails/negative_balance.html", dict(note=self))
|
||||||
|
|
|
@ -217,6 +217,9 @@ 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
|
||||||
|
|
||||||
|
self.source = Note.objects.select_for_update().get(pk=self.source_id)
|
||||||
|
self.destination = Note.objects.select_for_update().get(pk=self.destination_id)
|
||||||
|
|
||||||
# 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()
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# 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.utils import timezone
|
||||||
|
|
||||||
|
|
||||||
def save_user_note(instance, raw, **_kwargs):
|
def save_user_note(instance, raw, **_kwargs):
|
||||||
"""
|
"""
|
||||||
|
@ -25,6 +27,16 @@ def save_club_note(instance, raw, **_kwargs):
|
||||||
instance.note.save()
|
instance.note.save()
|
||||||
|
|
||||||
|
|
||||||
|
def pre_save_note(instance, raw, **_kwargs):
|
||||||
|
if not raw and instance.pk and not hasattr(instance, "_no_signal") and instance.balance < 0:
|
||||||
|
from note.models import Note
|
||||||
|
old_note = Note.objects.get(pk=instance.pk)
|
||||||
|
if old_note.balance >= 0:
|
||||||
|
# Passage en négatif
|
||||||
|
instance.last_negative = timezone.now()
|
||||||
|
instance.send_mail_negative_balance()
|
||||||
|
|
||||||
|
|
||||||
def delete_transaction(instance, **_kwargs):
|
def delete_transaction(instance, **_kwargs):
|
||||||
"""
|
"""
|
||||||
Whenever we want to delete a transaction (caution with this), we ensure the transaction is invalid first.
|
Whenever we want to delete a transaction (caution with this), we ensure the transaction is invalid first.
|
||||||
|
|
Loading…
Reference in New Issue