From bb2704323a8bae2ce68a12c154de4917e0c33ef9 Mon Sep 17 00:00:00 2001
From: Yohann D'ANELLO <yohann.danello@gmail.com>
Date: Thu, 13 Aug 2020 17:04:10 +0200
Subject: [PATCH] Spam click on invalidity button is no longer possible

---
 apps/logs/api/views.py                |  4 ++--
 apps/logs/signals.py                  | 20 ++++++++++++++------
 apps/note/models/transactions.py      | 23 ++++++++++++++++-------
 apps/permission/fixtures/initial.json |  2 +-
 note_kfet/settings/base.py            |  1 +
 note_kfet/static/js/base.js           |  9 ++++++++-
 6 files changed, 42 insertions(+), 17 deletions(-)

diff --git a/apps/logs/api/views.py b/apps/logs/api/views.py
index b3b9b166..4160d609 100644
--- a/apps/logs/api/views.py
+++ b/apps/logs/api/views.py
@@ -19,5 +19,5 @@ class ChangelogViewSet(ReadOnlyProtectedModelViewSet):
     serializer_class = ChangelogSerializer
     filter_backends = [DjangoFilterBackend, OrderingFilter]
     filterset_fields = ['model', 'action', "instance_pk", 'user', 'ip', ]
-    ordering_fields = ['timestamp', ]
-    ordering = ['-timestamp', ]
+    ordering_fields = ['timestamp', 'id', ]
+    ordering = ['-id', ]
diff --git a/apps/logs/signals.py b/apps/logs/signals.py
index 12d22155..bc8eec44 100644
--- a/apps/logs/signals.py
+++ b/apps/logs/signals.py
@@ -81,19 +81,27 @@ def save_object(sender, instance, **kwargs):
         if instance.last_login != previous.last_login:
             return
 
-    # On crée notre propre sérialiseur JSON pour pouvoir sauvegarder les modèles
+    fields = '__all__'
+    if previous:
+        # On ne garde que les champs modifiés
+        changed_fields = []
+        for field in instance._meta.fields:
+            if getattr(instance, field.name) != getattr(previous, field.name):
+                changed_fields.append(field.name)
+
+    if len(changed_fields) == 0:
+        # Pas de log s'il n'y a pas de modification
+        return
+
+    # On crée notre propre sérialiseur JSON pour pouvoir sauvegarder les modèles avec uniquement les champs modifiés
     class CustomSerializer(ModelSerializer):
         class Meta:
             model = instance.__class__
-            fields = '__all__'
+            fields = changed_fields
 
     previous_json = JSONRenderer().render(CustomSerializer(previous).data).decode("UTF-8") if previous else None
     instance_json = JSONRenderer().render(CustomSerializer(instance).data).decode("UTF-8")
 
-    if previous_json == instance_json:
-        # Pas de log s'il n'y a pas de modification
-        return
-
     Changelog.objects.create(user=user,
                              ip=ip,
                              model=ContentType.objects.get_for_model(instance),
diff --git a/apps/note/models/transactions.py b/apps/note/models/transactions.py
index b3a541a1..f57cc9b5 100644
--- a/apps/note/models/transactions.py
+++ b/apps/note/models/transactions.py
@@ -167,26 +167,35 @@ class Transaction(PolymorphicModel):
         previous_source_balance = self.source.balance
         previous_dest_balance = self.destination.balance
 
+        source_balance = self.source.balance
+        dest_balance = self.destination.balance
+
         created = self.pk is None
         to_transfer = self.amount * self.quantity
         if not created:
             # Revert old transaction
             old_transaction = Transaction.objects.get(pk=self.pk)
+            # Check that nothing important changed
+            for field_name in ["source_id", "destination_id", "quantity", "amount"]:
+                if getattr(self, field_name) != getattr(old_transaction, 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:
+                # Don't change anything
+                return 0, 0
             if old_transaction.valid:
-                self.source.balance += to_transfer
-                self.destination.balance -= to_transfer
+                source_balance += to_transfer
+                dest_balance -= to_transfer
 
         if self.valid:
-            self.source.balance -= to_transfer
-            self.destination.balance += to_transfer
+            source_balance -= to_transfer
+            dest_balance += to_transfer
 
             # When a transaction is declared valid, we ensure that the invalidity reason is null, if it was
             # previously invalid
             self.invalidity_reason = None
 
-        source_balance = self.source.balance
-        dest_balance = self.destination.balance
-
         if source_balance > 2147483647 or source_balance < -2147483648\
                 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 €."))
diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json
index 80e38f6a..192b9391 100644
--- a/apps/permission/fixtures/initial.json
+++ b/apps/permission/fixtures/initial.json
@@ -2456,7 +2456,7 @@
 				47,
 				49,
 				50,
-				140
+				141
 			]
 		}
 	},
diff --git a/note_kfet/settings/base.py b/note_kfet/settings/base.py
index 2e0ff8f5..2aa303ed 100644
--- a/note_kfet/settings/base.py
+++ b/note_kfet/settings/base.py
@@ -46,6 +46,7 @@ INSTALLED_APPS = [
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'django.forms',
+    'django_filters',
 
     # API
     'rest_framework',
diff --git a/note_kfet/static/js/base.js b/note_kfet/static/js/base.js
index 9db1d958..36e8ec84 100644
--- a/note_kfet/static/js/base.js
+++ b/note_kfet/static/js/base.js
@@ -349,8 +349,15 @@ function autoCompleteNote(field_id, note_list_id, notes, notes_display, alias_pr
 
 // When a validate button is clicked, we switch the validation status
 function de_validate(id, validated) {
+    let validate_obj = $("#validate_" + id);
+
+    if (validate_obj.data("pending"))
+        // The button is already clicked
+        return;
+
     let invalidity_reason = $("#invalidity_reason_" + id).val();
-    $("#validate_" + id).html("<strong style=\"font-size: 16pt;\">⟳</strong>");
+    validate_obj.html("<strong style=\"font-size: 16pt;\">⟳</strong>");
+    validate_obj.data("pending", true);
 
     // Perform a PATCH request to the API in order to update the transaction
     // If the user has insufficient rights, an error message will appear
-- 
GitLab