Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions backend/leaderboard/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ def get_referral_points(self, obj):

def get_active_validators_count(self, obj):
"""
Get count of active validator wallets for this user's validator.
Returns null if user has no validator wallets linked, otherwise returns the active count.
Uses annotated values from queryset to avoid N+1 queries.
Get count of active validator wallets for validator-related leaderboards.
Returns null for other leaderboard types.
"""
if obj.type not in {'validator', 'validator-waitlist', 'validator-waitlist-graduation'}:
return None

# Use annotated values if available (from optimized queryset)
if hasattr(obj, '_total_validators_count') and hasattr(obj, '_active_validators_count'):
# If no validator wallets at all, return null
Expand Down
37 changes: 22 additions & 15 deletions backend/leaderboard/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,35 +72,42 @@ def get_queryset(self):
'user__creator'
)

# Annotate with validator wallet counts to avoid N+1 queries
queryset = queryset.annotate(
_active_validators_count=Count(
'user__validator__validator_wallets',
filter=Q(user__validator__validator_wallets__status='active')
),
_total_validators_count=Count(
'user__validator__validator_wallets'
)
)

# Filter by user address if provided
user_address = self.request.query_params.get('user_address')
leaderboard_type = self.request.query_params.get('type')

if user_address:
queryset = queryset.filter(user__address__iexact=user_address)
# When filtering by user, don't apply type filter unless explicitly provided
leaderboard_type = self.request.query_params.get('type')
if leaderboard_type:
queryset = queryset.filter(type=leaderboard_type)
else:
# Get type from query params
leaderboard_type = self.request.query_params.get('type')

if leaderboard_type:
queryset = queryset.filter(type=leaderboard_type)
else:
# Default to validator leaderboard only when not filtering by user
leaderboard_type = 'validator'
queryset = queryset.filter(type='validator')

# Validator wallet count annotations are expensive and only needed
# for validator-oriented leaderboards.
needs_validator_counts = leaderboard_type in {
'validator',
'validator-waitlist',
'validator-waitlist-graduation',
}
if needs_validator_counts:
queryset = queryset.annotate(
_active_validators_count=Count(
'user__validator__validator_wallets',
filter=Q(user__validator__validator_wallets__status='active')
),
_total_validators_count=Count(
'user__validator__validator_wallets'
)
)

# Handle rank ordering
order = self.request.query_params.get('order', 'asc')
if order == 'desc':
Expand All @@ -109,7 +116,6 @@ def get_queryset(self):
queryset = queryset.order_by('rank')

return queryset

def list(self, request, *args, **kwargs):
"""
Override to apply limit after filtering/ordering.
Expand Down Expand Up @@ -521,3 +527,4 @@ def trending(self, request):

return Response(results)