Skip to content

Commit ae8a43e

Browse files
committed
Fix date validator
1 parent 204abe3 commit ae8a43e

File tree

3 files changed

+24
-61
lines changed

3 files changed

+24
-61
lines changed

safe_transaction_service/history/serializers.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,6 +1343,11 @@ class SafeDeploymentSerializer(serializers.Serializer):
13431343
contracts = SafeDeploymentContractSerializer(many=True)
13441344

13451345

1346+
class SafeExportTransactionRequestParams(serializers.Serializer):
1347+
execution_date__gte = serializers.DateTimeField(required=False, allow_null=True)
1348+
execution_date__lte = serializers.DateTimeField(required=False, allow_null=True)
1349+
1350+
13461351
class SafeExportTransactionSerializer(serializers.Serializer):
13471352
"""
13481353
Serializer for the export endpoint that returns transaction data optimized for CSV export

safe_transaction_service/history/tests/test_views_v2.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import json
33
from unittest import mock
44
from unittest.mock import MagicMock
5+
from urllib.parse import urlencode
56

67
from django.contrib.auth import get_user_model
78
from django.contrib.auth.models import Permission
@@ -2684,9 +2685,9 @@ def test_safe_export_view(self):
26842685

26852686
# Test date filtering
26862687
future_date = timezone.now() + datetime.timedelta(days=1)
2688+
params = urlencode({"execution_date__gte": future_date.isoformat()})
26872689
response = self.client.get(
2688-
reverse("v2:history:safe-export", args=(safe_address,))
2689-
+ f"?execution_date__gte={future_date.isoformat()}",
2690+
reverse("v2:history:safe-export", args=(safe_address,)) + f"?{params}",
26902691
format="json",
26912692
)
26922693
self.assertEqual(response.status_code, status.HTTP_200_OK)

safe_transaction_service/history/views_v2.py

Lines changed: 16 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import hashlib
22
import logging
3-
from datetime import datetime
43
from typing import Optional
54

65
from django.db.models import Q
7-
from django.utils import timezone
86

97
import django_filters
108
from drf_spectacular.types import OpenApiTypes
@@ -28,6 +26,7 @@
2826
from .cache import CacheSafeTxsView, cache_txs_view_for_address
2927
from .models import MultisigTransaction, SafeContract, SafeContractDelegate
3028
from .pagination import DummyPagination
29+
from .serializers import SafeExportTransactionRequestParams
3130
from .services import BalanceServiceProvider, TransactionServiceProvider
3231
from .services.balance_service import Balance
3332
from .services.collectibles_service import CollectiblesServiceProvider
@@ -682,6 +681,17 @@ class SafeExportView(GenericAPIView):
682681
serializer_class = serializers.SafeExportTransactionSerializer
683682
pagination_class = pagination.ListPagination
684683

684+
def get_query_params(self):
685+
serializer = SafeExportTransactionRequestParams(
686+
data=self.request.query_params.dict()
687+
)
688+
serializer.is_valid(raise_exception=True)
689+
690+
validated_data = serializer.validated_data
691+
parsed_execution_date_gte = validated_data.get("execution_date__gte")
692+
parsed_execution_date_lte = validated_data.get("execution_date__lte")
693+
return parsed_execution_date_gte, parsed_execution_date_lte
694+
685695
def get(self, request, address):
686696
"""
687697
Get transactions optimized for CSV export with transfer information.
@@ -703,67 +713,14 @@ def get(self, request, address):
703713
except SafeContract.DoesNotExist:
704714
return Response(status=status.HTTP_404_NOT_FOUND)
705715

706-
# Parse query parameters
707-
execution_date_gte = request.query_params.get("execution_date__gte")
708-
execution_date_lte = request.query_params.get("execution_date__lte")
716+
# Get query params
717+
parsed_execution_date_gte, parsed_execution_date_lte = self.get_query_params()
718+
719+
# Get pagination
709720
paginator = pagination.ListPagination(self.request, max_limit=1000)
710721
limit = paginator.get_limit(request)
711722
offset = paginator.get_offset(request)
712723

713-
# Parse dates if provided
714-
parsed_execution_date_gte = None
715-
parsed_execution_date_lte = None
716-
717-
if execution_date_gte:
718-
try:
719-
# Handle different ISO format variations
720-
date_str = execution_date_gte.replace("Z", "+00:00")
721-
# Fix format issue where timezone offset has space instead of +
722-
if " " in date_str: # e.g., '2025-07-08T19:22:21.558887 00:00'
723-
parts = date_str.rsplit(" ", 1)
724-
if (
725-
len(parts) == 2 and ":" in parts[1] and len(parts[1]) == 5
726-
): # timezone format like '00:00'
727-
date_str = parts[0] + "+" + parts[1]
728-
parsed_execution_date_gte = datetime.fromisoformat(date_str)
729-
# Ensure timezone-aware
730-
if parsed_execution_date_gte.tzinfo is None:
731-
parsed_execution_date_gte = timezone.make_aware(
732-
parsed_execution_date_gte
733-
)
734-
except ValueError:
735-
return Response(
736-
status=status.HTTP_400_BAD_REQUEST,
737-
data={
738-
"error": "Invalid execution_date__gte format. Use ISO format."
739-
},
740-
)
741-
742-
if execution_date_lte:
743-
try:
744-
# Handle different ISO format variations
745-
date_str = execution_date_lte.replace("Z", "+00:00")
746-
# Fix format issue where timezone offset has space instead of +
747-
if " " in date_str: # e.g., '2025-07-08T19:22:21.558887 00:00'
748-
parts = date_str.rsplit(" ", 1)
749-
if (
750-
len(parts) == 2 and ":" in parts[1] and len(parts[1]) == 5
751-
): # timezone format like '00:00'
752-
date_str = parts[0] + "+" + parts[1]
753-
parsed_execution_date_lte = datetime.fromisoformat(date_str)
754-
# Ensure timezone-aware
755-
if parsed_execution_date_lte.tzinfo is None:
756-
parsed_execution_date_lte = timezone.make_aware(
757-
parsed_execution_date_lte
758-
)
759-
except ValueError:
760-
return Response(
761-
status=status.HTTP_400_BAD_REQUEST,
762-
data={
763-
"error": "Invalid execution_date__lte format. Use ISO format."
764-
},
765-
)
766-
767724
# Get transactions from service
768725
transaction_service = TransactionServiceProvider()
769726
transactions, total_count = transaction_service.get_export_transactions(

0 commit comments

Comments
 (0)