From 3f92da5ec18272d2c737621f459883e74f451201 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Fri, 26 Dec 2025 20:20:39 +0000 Subject: [PATCH 01/10] Allow marketplace exchange overwrite --- .../routes/MarketplaceExchangeView.py | 71 ++++++++++++------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/django/university/routes/MarketplaceExchangeView.py b/django/university/routes/MarketplaceExchangeView.py index a3ee4634..0b1b52d5 100644 --- a/django/university/routes/MarketplaceExchangeView.py +++ b/django/university/routes/MarketplaceExchangeView.py @@ -147,23 +147,32 @@ def submit_marketplace_exchange_request(self, request): if ExchangeController.exchange_overlap(student_schedules, curr_student): return JsonResponse({"error": "classes-overlap"}, status=400, safe=False) - exchange_hash = ExchangeHasher.hash(exchanges, username=curr_student) + # By specifying replace=true, the user can replace a request with the same hash with a new one + replace = request.POST.get('replace', 'false') == 'true' + if not replace: + exchange_hash = ExchangeHasher.hash(exchanges, username=curr_student) - if MarketplaceExchange.objects.filter(hash=exchange_hash, canceled=False).exists(): - return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) + if MarketplaceExchange.objects.filter(hash=exchange_hash, canceled=False).exists(): + return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) - if ExchangeUrgentRequests.objects.filter(hash=exchange_hash).exists(): - return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) + if ExchangeUrgentRequests.objects.filter(hash=exchange_hash).exists(): + return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) if urgentMessage: - return self.add_urgent_exchange(request, exchanges, urgentMessage, exchange_hash) + return self.add_urgent_exchange(request, exchanges, urgentMessage, exchange_hash, replace) else: return self.add_normal_marketplace_exchange(request, exchanges, exchange_hash) - def add_urgent_exchange(self, request, exchanges, message: str, exchange_hash): - + def add_urgent_exchange(self, request, exchanges, message: str, exchange_hash, replace_existing=False): with transaction.atomic(): + if replace_existing: + ExchangeUrgentRequests.objects.filter( + issuer_nmec=request.user.username, + hash=exchange_hash, + admin_state="untreated" + ).delete() + urgent_request = ExchangeUrgentRequests.objects.create( issuer_name=request.user.first_name + " " + request.user.last_name, issuer_nmec=request.user.username, @@ -192,25 +201,33 @@ def add_normal_marketplace_exchange(self, request, exchanges, exchange_hash): return JsonResponse({"success": True}, safe=False) - def insert_marketplace_exchange(self, exchanges, user, exchange_hash): + def insert_marketplace_exchange(self, exchanges, user, exchange_hash, replace_existing=False): issuer_name = f"{user.first_name} {user.last_name.split(' ')[-1]}" - marketplace_exchange = MarketplaceExchange.objects.create( - issuer_name=issuer_name, - issuer_nmec=user.username, - accepted=False, - hash=exchange_hash, - canceled=False, - date=timezone.now() - ) - for exchange in exchanges: - course_unit_id = int(exchange["courseUnitId"]) - course_unit = CourseUnit.objects.get(pk=course_unit_id) - MarketplaceExchangeClass.objects.create( - marketplace_exchange=marketplace_exchange, - course_unit_acronym=course_unit.acronym, - course_unit_id=course_unit_id, - course_unit_name=course_unit.name, - class_issuer_goes_from=exchange["classNameRequesterGoesFrom"], - class_issuer_goes_to=exchange["classNameRequesterGoesTo"] + with transaction.atomic(): + if replace_existing: + MarketplaceExchange.objects.filter( + issuer_nmec=user.username, + hash=exchange_hash, + canceled=False + ).delete() + + marketplace_exchange = MarketplaceExchange.objects.create( + issuer_name=issuer_name, + issuer_nmec=user.username, + accepted=False, + hash=exchange_hash, + canceled=False, + date=timezone.now() ) + for exchange in exchanges: + course_unit_id = int(exchange["courseUnitId"]) + course_unit = CourseUnit.objects.get(pk=course_unit_id) + MarketplaceExchangeClass.objects.create( + marketplace_exchange=marketplace_exchange, + course_unit_acronym=course_unit.acronym, + course_unit_id=course_unit_id, + course_unit_name=course_unit.name, + class_issuer_goes_from=exchange["classNameRequesterGoesFrom"], + class_issuer_goes_to=exchange["classNameRequesterGoesTo"] + ) From 7d5b9fd9eed813d0d325c94f4fb48c18865b65c2 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Fri, 26 Dec 2025 22:39:39 +0000 Subject: [PATCH 02/10] Fix unbound variable --- django/university/routes/MarketplaceExchangeView.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/django/university/routes/MarketplaceExchangeView.py b/django/university/routes/MarketplaceExchangeView.py index 0b1b52d5..9256e2c5 100644 --- a/django/university/routes/MarketplaceExchangeView.py +++ b/django/university/routes/MarketplaceExchangeView.py @@ -147,11 +147,11 @@ def submit_marketplace_exchange_request(self, request): if ExchangeController.exchange_overlap(student_schedules, curr_student): return JsonResponse({"error": "classes-overlap"}, status=400, safe=False) - # By specifying replace=true, the user can replace a request with the same hash with a new one replace = request.POST.get('replace', 'false') == 'true' - if not replace: - exchange_hash = ExchangeHasher.hash(exchanges, username=curr_student) + exchange_hash = ExchangeHasher.hash(exchanges, username=curr_student) + # Unless replace=true, we want to avoid creating duplicate requests + if not replace: if MarketplaceExchange.objects.filter(hash=exchange_hash, canceled=False).exists(): return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) From 6a2c066d928e7a1cd4950d3fdcba426fdd505587 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Fri, 26 Dec 2025 23:26:16 +0000 Subject: [PATCH 03/10] Fix request replace logic --- .../routes/MarketplaceExchangeView.py | 41 ++++++++++++------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/django/university/routes/MarketplaceExchangeView.py b/django/university/routes/MarketplaceExchangeView.py index 9256e2c5..22d369e8 100644 --- a/django/university/routes/MarketplaceExchangeView.py +++ b/django/university/routes/MarketplaceExchangeView.py @@ -161,17 +161,12 @@ def submit_marketplace_exchange_request(self, request): if urgentMessage: return self.add_urgent_exchange(request, exchanges, urgentMessage, exchange_hash, replace) else: - return self.add_normal_marketplace_exchange(request, exchanges, exchange_hash) - + return self.add_normal_marketplace_exchange(request, exchanges, exchange_hash, replace) def add_urgent_exchange(self, request, exchanges, message: str, exchange_hash, replace_existing=False): with transaction.atomic(): if replace_existing: - ExchangeUrgentRequests.objects.filter( - issuer_nmec=request.user.username, - hash=exchange_hash, - admin_state="untreated" - ).delete() + self.reject_old_urgent_requests(request.user, exchange_hash) urgent_request = ExchangeUrgentRequests.objects.create( issuer_name=request.user.first_name + " " + request.user.last_name, @@ -196,8 +191,19 @@ def add_urgent_exchange(self, request, exchanges, message: str, exchange_hash, r return JsonResponse({"success": True}, safe=False) - def add_normal_marketplace_exchange(self, request, exchanges, exchange_hash): - self.insert_marketplace_exchange(exchanges, request.user, exchange_hash) + def reject_old_urgent_requestss(self, user, exchange_hash): + with transaction.atomic(): + old_requests = ExchangeUrgentRequests.objects.filter( + issuer_nmec=user.username, + hash=exchange_hash, + admin_state="untreated" + ) + for old_request in old_requests: + old_request.admin_state = "rejected" + old_request.save() + + def add_normal_marketplace_exchange(self, request, exchanges, exchange_hash, replace_existing=False): + self.insert_marketplace_exchange(exchanges, request.user, exchange_hash, replace_existing) return JsonResponse({"success": True}, safe=False) @@ -206,11 +212,7 @@ def insert_marketplace_exchange(self, exchanges, user, exchange_hash, replace_ex with transaction.atomic(): if replace_existing: - MarketplaceExchange.objects.filter( - issuer_nmec=user.username, - hash=exchange_hash, - canceled=False - ).delete() + self.cancel_old_marketplace_exchanges(user, exchange_hash) marketplace_exchange = MarketplaceExchange.objects.create( issuer_name=issuer_name, @@ -231,3 +233,14 @@ def insert_marketplace_exchange(self, exchanges, user, exchange_hash, replace_ex class_issuer_goes_from=exchange["classNameRequesterGoesFrom"], class_issuer_goes_to=exchange["classNameRequesterGoesTo"] ) + + def cancel_old_marketplace_exchanges(self, user, exchange_hash): + with transaction.atomic(): + old_exchanges = MarketplaceExchange.objects.filter( + issuer_nmec=user.username, + hash=exchange_hash, + canceled=False + ) + for old_exchange in old_exchanges: + old_exchange.canceled = True + old_exchange.save() From 810cdce9ca00cc1c8b5d769230201429dadb4b59 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Fri, 26 Dec 2025 23:47:45 +0000 Subject: [PATCH 04/10] Allow marketplace requests to allow urgent requests with the same hash --- .../routes/MarketplaceExchangeView.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/django/university/routes/MarketplaceExchangeView.py b/django/university/routes/MarketplaceExchangeView.py index 22d369e8..0c68818b 100644 --- a/django/university/routes/MarketplaceExchangeView.py +++ b/django/university/routes/MarketplaceExchangeView.py @@ -122,9 +122,10 @@ def submit_marketplace_exchange_request(self, request): exchanges = request.POST.getlist('exchangeChoices[]') exchanges = list(map(lambda exchange : json.loads(exchange), exchanges)) - # If user sent a message explaining why their request should be directly handled by the comission instead of having to be + # If user sent a message explaining why their request should be directly handled by the commission instead of having to be # accepted by students in the marketplace - urgentMessage = request.POST.get('urgentMessage') + urgent_message = request.POST.get('urgentMessage') + is_urgent = urgent_message is not None and urgent_message != "" curr_student = request.user.username sigarra_res = SigarraController().get_student_schedule(curr_student) @@ -143,7 +144,7 @@ def submit_marketplace_exchange_request(self, request): if status == ExchangeStatus.STUDENTS_NOT_ENROLLED: return JsonResponse({"error": incorrect_class_error()}, status=400, safe=False) - if not urgentMessage or urgentMessage == "": + if not is_urgent: if ExchangeController.exchange_overlap(student_schedules, curr_student): return JsonResponse({"error": "classes-overlap"}, status=400, safe=False) @@ -152,14 +153,14 @@ def submit_marketplace_exchange_request(self, request): # Unless replace=true, we want to avoid creating duplicate requests if not replace: - if MarketplaceExchange.objects.filter(hash=exchange_hash, canceled=False).exists(): + if not is_urgent and MarketplaceExchange.objects.filter(hash=exchange_hash, canceled=False).exists(): return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) - if ExchangeUrgentRequests.objects.filter(hash=exchange_hash).exists(): + if is_urgent and ExchangeUrgentRequests.objects.filter(hash=exchange_hash).exists(): return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) - if urgentMessage: - return self.add_urgent_exchange(request, exchanges, urgentMessage, exchange_hash, replace) + if is_urgent: + return self.add_urgent_exchange(request, exchanges, urgent_message, exchange_hash, replace) else: return self.add_normal_marketplace_exchange(request, exchanges, exchange_hash, replace) @@ -191,7 +192,7 @@ def add_urgent_exchange(self, request, exchanges, message: str, exchange_hash, r return JsonResponse({"success": True}, safe=False) - def reject_old_urgent_requestss(self, user, exchange_hash): + def reject_old_urgent_requests(self, user, exchange_hash): with transaction.atomic(): old_requests = ExchangeUrgentRequests.objects.filter( issuer_nmec=user.username, From 6e8fc7961493cb57d9ffd495152022a761804715 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Sun, 28 Dec 2025 17:18:27 +0000 Subject: [PATCH 05/10] Implement direct exchange replacement --- .../routes/exchange/DirectExchangeView.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/django/university/routes/exchange/DirectExchangeView.py b/django/university/routes/exchange/DirectExchangeView.py index bf464ea6..f85566a0 100644 --- a/django/university/routes/exchange/DirectExchangeView.py +++ b/django/university/routes/exchange/DirectExchangeView.py @@ -124,11 +124,20 @@ def post(self, request): # Restricts repeated exchange requests exchange_hash = ExchangeHasher.hash(exchanges, username) + # Allow exchange overwrite with replace=true + replace = request.POST.get('replace', 'false') == 'true' - if DirectExchange.objects.filter(hash=exchange_hash, canceled=False).exists(): - return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) + if not replace: + if DirectExchange.objects.filter(hash=exchange_hash, canceled=False).exists(): + return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) with transaction.atomic(): + if replace: + # Cancel previous exchanges with same hash + previous_exchanges = DirectExchange.objects.filter(hash=exchange_hash, accepted=False, canceled=False) + for previous_exchange in previous_exchanges: + ExchangeValidationController().cancel_exchange(previous_exchange) + exchange_model = DirectExchange( accepted=False, issuer_name=f"{request.user.first_name} {request.user.last_name}", From 0fb761dec725126dd2640fae99702d79602a3a8a Mon Sep 17 00:00:00 2001 From: Process-ing Date: Sun, 28 Dec 2025 18:07:39 +0000 Subject: [PATCH 06/10] Fix hash collisions with processed urgent requests --- django/university/routes/MarketplaceExchangeView.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/university/routes/MarketplaceExchangeView.py b/django/university/routes/MarketplaceExchangeView.py index 0c68818b..d9795d4e 100644 --- a/django/university/routes/MarketplaceExchangeView.py +++ b/django/university/routes/MarketplaceExchangeView.py @@ -156,7 +156,7 @@ def submit_marketplace_exchange_request(self, request): if not is_urgent and MarketplaceExchange.objects.filter(hash=exchange_hash, canceled=False).exists(): return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) - if is_urgent and ExchangeUrgentRequests.objects.filter(hash=exchange_hash).exists(): + if is_urgent and ExchangeUrgentRequests.objects.filter(hash=exchange_hash, admin_state="untreated").exists(): return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) if is_urgent: From f4338f77d5486d0455caa7f35b9684d7ca83c4b6 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Sun, 28 Dec 2025 18:13:43 +0000 Subject: [PATCH 07/10] Fix insertion with other accepted direct exchanges --- django/university/routes/exchange/DirectExchangeView.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/university/routes/exchange/DirectExchangeView.py b/django/university/routes/exchange/DirectExchangeView.py index f85566a0..f971bdcc 100644 --- a/django/university/routes/exchange/DirectExchangeView.py +++ b/django/university/routes/exchange/DirectExchangeView.py @@ -134,7 +134,7 @@ def post(self, request): with transaction.atomic(): if replace: # Cancel previous exchanges with same hash - previous_exchanges = DirectExchange.objects.filter(hash=exchange_hash, accepted=False, canceled=False) + previous_exchanges = DirectExchange.objects.filter(hash=exchange_hash, canceled=False) for previous_exchange in previous_exchanges: ExchangeValidationController().cancel_exchange(previous_exchange) From 741c67778fc08489ea07912ad4d9f46c2b0a9cfa Mon Sep 17 00:00:00 2001 From: Process-ing Date: Sun, 28 Dec 2025 18:23:08 +0000 Subject: [PATCH 08/10] Move hash checking to inside transaction, to avoid potential race condition --- .../university/routes/MarketplaceExchangeView.py | 14 ++++++-------- .../routes/exchange/DirectExchangeView.py | 8 ++++---- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/django/university/routes/MarketplaceExchangeView.py b/django/university/routes/MarketplaceExchangeView.py index d9795d4e..5b922b36 100644 --- a/django/university/routes/MarketplaceExchangeView.py +++ b/django/university/routes/MarketplaceExchangeView.py @@ -151,14 +151,6 @@ def submit_marketplace_exchange_request(self, request): replace = request.POST.get('replace', 'false') == 'true' exchange_hash = ExchangeHasher.hash(exchanges, username=curr_student) - # Unless replace=true, we want to avoid creating duplicate requests - if not replace: - if not is_urgent and MarketplaceExchange.objects.filter(hash=exchange_hash, canceled=False).exists(): - return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) - - if is_urgent and ExchangeUrgentRequests.objects.filter(hash=exchange_hash, admin_state="untreated").exists(): - return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) - if is_urgent: return self.add_urgent_exchange(request, exchanges, urgent_message, exchange_hash, replace) else: @@ -168,6 +160,9 @@ def add_urgent_exchange(self, request, exchanges, message: str, exchange_hash, r with transaction.atomic(): if replace_existing: self.reject_old_urgent_requests(request.user, exchange_hash) + elif ExchangeUrgentRequests.objects.filter(hash=exchange_hash, admin_state="untreated").exists(): + # Replace not set to true and an untreated exchange with the same hash already exists => return error + return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) urgent_request = ExchangeUrgentRequests.objects.create( issuer_name=request.user.first_name + " " + request.user.last_name, @@ -214,6 +209,9 @@ def insert_marketplace_exchange(self, exchanges, user, exchange_hash, replace_ex with transaction.atomic(): if replace_existing: self.cancel_old_marketplace_exchanges(user, exchange_hash) + elif MarketplaceExchange.objects.filter(hash=exchange_hash, canceled=False).exists(): + # Replace not set to true and a non-canceled exchange with the same hash already exists => return error + return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) marketplace_exchange = MarketplaceExchange.objects.create( issuer_name=issuer_name, diff --git a/django/university/routes/exchange/DirectExchangeView.py b/django/university/routes/exchange/DirectExchangeView.py index f971bdcc..c9ed3881 100644 --- a/django/university/routes/exchange/DirectExchangeView.py +++ b/django/university/routes/exchange/DirectExchangeView.py @@ -127,10 +127,6 @@ def post(self, request): # Allow exchange overwrite with replace=true replace = request.POST.get('replace', 'false') == 'true' - if not replace: - if DirectExchange.objects.filter(hash=exchange_hash, canceled=False).exists(): - return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) - with transaction.atomic(): if replace: # Cancel previous exchanges with same hash @@ -138,6 +134,10 @@ def post(self, request): for previous_exchange in previous_exchanges: ExchangeValidationController().cancel_exchange(previous_exchange) + elif DirectExchange.objects.filter(hash=exchange_hash, canceled=False).exists(): + # Replace not set to true and a non-canceled exchange with the same hash already exists => return error + return JsonResponse({"error": "duplicate-request"}, status=400, safe=False) + exchange_model = DirectExchange( accepted=False, issuer_name=f"{request.user.first_name} {request.user.last_name}", From 87e7b6872dd518623323d1e926ec01f9825e0cf4 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Sun, 28 Dec 2025 18:31:58 +0000 Subject: [PATCH 09/10] Fix duplicate marketplace exchange not being reported --- django/university/routes/MarketplaceExchangeView.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/django/university/routes/MarketplaceExchangeView.py b/django/university/routes/MarketplaceExchangeView.py index 5b922b36..9b91ba18 100644 --- a/django/university/routes/MarketplaceExchangeView.py +++ b/django/university/routes/MarketplaceExchangeView.py @@ -199,11 +199,7 @@ def reject_old_urgent_requests(self, user, exchange_hash): old_request.save() def add_normal_marketplace_exchange(self, request, exchanges, exchange_hash, replace_existing=False): - self.insert_marketplace_exchange(exchanges, request.user, exchange_hash, replace_existing) - - return JsonResponse({"success": True}, safe=False) - - def insert_marketplace_exchange(self, exchanges, user, exchange_hash, replace_existing=False): + user = request.user issuer_name = f"{user.first_name} {user.last_name.split(' ')[-1]}" with transaction.atomic(): From 372767d08e38116b43bb57f6aa994a203a9a2dc4 Mon Sep 17 00:00:00 2001 From: Process-ing Date: Sun, 28 Dec 2025 18:33:14 +0000 Subject: [PATCH 10/10] Fix null response error --- django/university/routes/MarketplaceExchangeView.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/django/university/routes/MarketplaceExchangeView.py b/django/university/routes/MarketplaceExchangeView.py index 9b91ba18..0879fdee 100644 --- a/django/university/routes/MarketplaceExchangeView.py +++ b/django/university/routes/MarketplaceExchangeView.py @@ -229,6 +229,8 @@ def add_normal_marketplace_exchange(self, request, exchanges, exchange_hash, rep class_issuer_goes_to=exchange["classNameRequesterGoesTo"] ) + return JsonResponse({"success": True}, safe=False) + def cancel_old_marketplace_exchanges(self, user, exchange_hash): with transaction.atomic(): old_exchanges = MarketplaceExchange.objects.filter(