All transactions are now atomic

This commit is contained in:
Yohann D'ANELLO 2020-09-11 22:52:16 +02:00
parent 860c7b50e5
commit 9b090a145c
15 changed files with 61 additions and 20 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):

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.

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

@ -5,10 +5,10 @@ import unicodedata
from django.conf import settings from django.conf import settings
from django.conf.global_settings import DEFAULT_FROM_EMAIL from django.conf.global_settings import DEFAULT_FROM_EMAIL
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError, PermissionDenied
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)
@ -154,6 +155,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 +197,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 +313,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

@ -3,6 +3,7 @@
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models, transaction from django.db import models, transaction
from django.db.models import F
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -170,19 +171,20 @@ 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 and not self.valid:
# Revert old transaction # Revert old transaction
old_transaction = Transaction.objects.get(pk=self.pk) old_transaction = Transaction.objects.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
@ -237,12 +239,8 @@ class Transaction(PolymorphicModel):
super().save(*args, **kwargs) super().save(*args, **kwargs)
# Save notes # Save notes
self.source.balance += diff_source Note.objects.filter(pk=self.source_id).update(balance=F("balance") + diff_source)
self.source._force_save = True Note.objects.filter(pk=self.destination_id).update(balance=F("balance") + diff_dest)
self.source.save()
self.destination.balance += diff_dest
self.destination._force_save = True
self.destination.save()
@property @property
def total(self): def total(self):
@ -273,6 +271,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 +322,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

@ -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

@ -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

@ -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()

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():
@ -329,6 +331,7 @@ class SogeCredit(models.Model):
transaction.created_at = timezone.now() transaction.created_at = timezone.now()
transaction.save() transaction.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( self.credit_transaction = SpecialTransaction.objects.create(

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

@ -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.