mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-11-04 01:12:08 +01:00 
			
		
		
		
	🐛 Fix transaction update concurency
This commit is contained in:
		@@ -4,7 +4,7 @@
 | 
			
		||||
from django.contrib.contenttypes.models import ContentType
 | 
			
		||||
from permission.backends import PermissionBackend
 | 
			
		||||
from rest_framework import viewsets
 | 
			
		||||
from note_kfet.middlewares import get_current_authenticated_user
 | 
			
		||||
from note_kfet.middlewares import get_current_session
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ReadProtectedModelViewSet(viewsets.ModelViewSet):
 | 
			
		||||
@@ -17,7 +17,8 @@ class ReadProtectedModelViewSet(viewsets.ModelViewSet):
 | 
			
		||||
        self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        user = get_current_authenticated_user()
 | 
			
		||||
        user = self.request.user
 | 
			
		||||
        get_current_session().setdefault("permission_mask", 42)
 | 
			
		||||
        return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -31,5 +32,6 @@ class ReadOnlyProtectedModelViewSet(viewsets.ReadOnlyModelViewSet):
 | 
			
		||||
        self.model = ContentType.objects.get_for_model(self.serializer_class.Meta.model).model_class()
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        user = get_current_authenticated_user()
 | 
			
		||||
        user = self.request.user
 | 
			
		||||
        get_current_session().setdefault("permission_mask", 42)
 | 
			
		||||
        return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct()
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ from rest_framework import viewsets
 | 
			
		||||
from rest_framework.response import Response
 | 
			
		||||
from rest_framework import status
 | 
			
		||||
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
 | 
			
		||||
from note_kfet.middlewares import get_current_authenticated_user
 | 
			
		||||
from note_kfet.middlewares import  get_current_session
 | 
			
		||||
from permission.backends import PermissionBackend
 | 
			
		||||
 | 
			
		||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
 | 
			
		||||
@@ -154,5 +154,7 @@ class TransactionViewSet(ReadProtectedModelViewSet):
 | 
			
		||||
    search_fields = ['$reason', ]
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
        user = get_current_authenticated_user()
 | 
			
		||||
        return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view"))
 | 
			
		||||
        user = self.request.user
 | 
			
		||||
        get_current_session().setdefault("permission_mask", 42)
 | 
			
		||||
        return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view"))\
 | 
			
		||||
            .order_by("created_at", "id")
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.db import models, transaction
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
@@ -196,38 +196,51 @@ class Transaction(PolymorphicModel):
 | 
			
		||||
                or dest_balance > 2147483647 or dest_balance < -2147483648:
 | 
			
		||||
            raise ValidationError(_("The note balances must be between - 21 474 836.47 € and 21 474 836.47 €."))
 | 
			
		||||
 | 
			
		||||
    @transaction.atomic
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
        When saving, also transfer money between two notes
 | 
			
		||||
        """
 | 
			
		||||
        self.validate(False)
 | 
			
		||||
        with transaction.atomic():
 | 
			
		||||
            self.refresh_from_db()
 | 
			
		||||
            self.source.refresh_from_db()
 | 
			
		||||
            self.destination.refresh_from_db()
 | 
			
		||||
            self.validate(False)
 | 
			
		||||
 | 
			
		||||
        if not self.source.is_active or not self.destination.is_active:
 | 
			
		||||
            if 'force_insert' not in kwargs or not kwargs['force_insert']:
 | 
			
		||||
                if 'force_update' not in kwargs or not kwargs['force_update']:
 | 
			
		||||
                    raise ValidationError(_("The transaction can't be saved since the source note "
 | 
			
		||||
                                            "or the destination note is not active."))
 | 
			
		||||
            if not self.source.is_active or not self.destination.is_active:
 | 
			
		||||
                if 'force_insert' not in kwargs or not kwargs['force_insert']:
 | 
			
		||||
                    if 'force_update' not in kwargs or not kwargs['force_update']:
 | 
			
		||||
                        raise ValidationError(_("The transaction can't be saved since the source note "
 | 
			
		||||
                                                "or the destination note is not active."))
 | 
			
		||||
 | 
			
		||||
        # If the aliases are not entered, we assume that the used alias is the name of the note
 | 
			
		||||
        if not self.source_alias:
 | 
			
		||||
            self.source_alias = str(self.source)
 | 
			
		||||
            # If the aliases are not entered, we assume that the used alias is the name of the note
 | 
			
		||||
            if not self.source_alias:
 | 
			
		||||
                self.source_alias = str(self.source)
 | 
			
		||||
 | 
			
		||||
        if not self.destination_alias:
 | 
			
		||||
            self.destination_alias = str(self.destination)
 | 
			
		||||
            if not self.destination_alias:
 | 
			
		||||
                self.destination_alias = str(self.destination)
 | 
			
		||||
 | 
			
		||||
        if self.source.pk == self.destination.pk:
 | 
			
		||||
            # When source == destination, no money is transferred
 | 
			
		||||
            if self.source.pk == self.destination.pk:
 | 
			
		||||
                # When source == destination, no money is transferred
 | 
			
		||||
                super().save(*args, **kwargs)
 | 
			
		||||
                return
 | 
			
		||||
 | 
			
		||||
            self.log("Saving")
 | 
			
		||||
            # We save first the transaction, in case of the user has no right to transfer money
 | 
			
		||||
            super().save(*args, **kwargs)
 | 
			
		||||
            return
 | 
			
		||||
            self.log("Saved")
 | 
			
		||||
 | 
			
		||||
        # We save first the transaction, in case of the user has no right to transfer money
 | 
			
		||||
        super().save(*args, **kwargs)
 | 
			
		||||
            # Save notes
 | 
			
		||||
            self.source._force_save = True
 | 
			
		||||
            self.source.save()
 | 
			
		||||
            self.log("Source saved")
 | 
			
		||||
            self.destination._force_save = True
 | 
			
		||||
            self.destination.save()
 | 
			
		||||
            self.log("Destination saved")
 | 
			
		||||
 | 
			
		||||
        # Save notes
 | 
			
		||||
        self.source._force_save = True
 | 
			
		||||
        self.source.save()
 | 
			
		||||
        self.destination._force_save = True
 | 
			
		||||
        self.destination.save()
 | 
			
		||||
    def log(self, msg):
 | 
			
		||||
        with open("/tmp/log", "a") as f:
 | 
			
		||||
            f.write(msg + "\n")
 | 
			
		||||
 | 
			
		||||
    def delete(self, **kwargs):
 | 
			
		||||
        """
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ from time import sleep
 | 
			
		||||
from django.contrib.contenttypes.models import ContentType
 | 
			
		||||
from django.core.exceptions import ValidationError
 | 
			
		||||
from django.core.mail import mail_admins
 | 
			
		||||
from django.db import models
 | 
			
		||||
from django.db import models, transaction
 | 
			
		||||
from django.db.models import F, Q, Model
 | 
			
		||||
from django.forms import model_to_dict
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
@@ -43,35 +43,28 @@ class InstancedPermission:
 | 
			
		||||
 | 
			
		||||
                obj = copy(obj)
 | 
			
		||||
                obj.pk = 0
 | 
			
		||||
                # Ensure previous models are deleted
 | 
			
		||||
                for ignored in range(1000):
 | 
			
		||||
                    if self.model.model_class().objects.filter(pk=0).exists():
 | 
			
		||||
                        # If the object exists, that means that one permission is currently checked.
 | 
			
		||||
                        # We wait before the other permission, at most 1 second.
 | 
			
		||||
                        sleep(0.001)
 | 
			
		||||
                        continue
 | 
			
		||||
                    break
 | 
			
		||||
                for o in self.model.model_class().objects.filter(pk=0).all():
 | 
			
		||||
                    o._force_delete = True
 | 
			
		||||
                    Model.delete(o)
 | 
			
		||||
                    # An object with pk 0 wouldn't deleted. That's not normal, we alert admins.
 | 
			
		||||
                    msg = "Lors de la vérification d'une permission d'ajout, un objet de clé primaire nulle était "\
 | 
			
		||||
                          "encore présent.\n"\
 | 
			
		||||
                          "Type de permission : " + self.type + "\n"\
 | 
			
		||||
                          "Modèle : " + str(self.model) + "\n"\
 | 
			
		||||
                          "Objet trouvé : " + str(model_to_dict(o)) + "\n\n"\
 | 
			
		||||
                          "--\nLe BDE"
 | 
			
		||||
                    mail_admins("[Note Kfet] Un objet a été supprimé de force", msg)
 | 
			
		||||
                with transaction.atomic():
 | 
			
		||||
                    for o in self.model.model_class().objects.filter(pk=0).all():
 | 
			
		||||
                        o._force_delete = True
 | 
			
		||||
                        Model.delete(o)
 | 
			
		||||
                        # An object with pk 0 wouldn't deleted. That's not normal, we alert admins.
 | 
			
		||||
                        msg = "Lors de la vérification d'une permission d'ajout, un objet de clé primaire nulle était "\
 | 
			
		||||
                              "encore présent.\n"\
 | 
			
		||||
                              "Type de permission : " + self.type + "\n"\
 | 
			
		||||
                              "Modèle : " + str(self.model) + "\n"\
 | 
			
		||||
                              "Objet trouvé : " + str(model_to_dict(o)) + "\n\n"\
 | 
			
		||||
                              "--\nLe BDE"
 | 
			
		||||
                        mail_admins("[Note Kfet] Un objet a été supprimé de force", msg)
 | 
			
		||||
 | 
			
		||||
                # Force insertion, no data verification, no trigger
 | 
			
		||||
                obj._force_save = True
 | 
			
		||||
                Model.save(obj, force_insert=True)
 | 
			
		||||
                # We don't want log anything
 | 
			
		||||
                obj._no_log = True
 | 
			
		||||
                ret = self.model.model_class().objects.filter(self.query & Q(pk=0)).exists()
 | 
			
		||||
                # Delete testing object
 | 
			
		||||
                obj._force_delete = True
 | 
			
		||||
                Model.delete(obj)
 | 
			
		||||
                    # Force insertion, no data verification, no trigger
 | 
			
		||||
                    obj._force_save = True
 | 
			
		||||
                    Model.save(obj, force_insert=True)
 | 
			
		||||
                    # We don't want log anything
 | 
			
		||||
                    obj._no_log = True
 | 
			
		||||
                    ret = self.model.model_class().objects.filter(self.query & Q(pk=0)).exists()
 | 
			
		||||
                    # Delete testing object
 | 
			
		||||
                    obj._force_delete = True
 | 
			
		||||
                    Model.delete(obj)
 | 
			
		||||
 | 
			
		||||
                with open("/tmp/log", "w") as f:
 | 
			
		||||
                    f.write(str(obj) + ", " + str(obj.pk) + ", " + str(self.model.model_class().objects.filter(pk=0).exists()))
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user