44from copy import copy
55
66from django .core .cache import cache
7+ from django .db import models
78from django .db .models import QuerySet
89from django .shortcuts import get_object_or_404
910from django .utils .translation import gettext as _
@@ -63,9 +64,26 @@ class ApplicationSerializer(ModelSerializer):
6364 def get_launch_url (self , app : Application ) -> str | None :
6465 """Allow formatting of launch URL"""
6566 user = None
67+ user_data = None
68+
6669 if "request" in self .context :
6770 user = self .context ["request" ].user
68- return app .get_launch_url (user )
71+
72+ # Cache serialized user data to avoid N+1 when formatting launch URLs
73+ # for multiple applications. UserSerializer accesses user.ak_groups which
74+ # would otherwise trigger a query for each application.
75+ if user and user .is_authenticated :
76+ if "_cached_user_data" not in self .context :
77+ # Prefetch groups to avoid N+1
78+ from django .db .models import prefetch_related_objects
79+
80+ from authentik .core .api .users import UserSerializer
81+
82+ prefetch_related_objects ([user ], "ak_groups" )
83+ self .context ["_cached_user_data" ] = UserSerializer (instance = user ).data
84+ user_data = self .context ["_cached_user_data" ]
85+
86+ return app .get_launch_url (user , user_data = user_data )
6987
7088 def validate_slug (self , slug : str ) -> str :
7189 if slug in Application .reserved_slugs :
@@ -262,6 +280,20 @@ def list(self, request: Request) -> Response:
262280 except ValueError as exc :
263281 raise ValidationError from exc
264282 allowed_applications = self ._get_allowed_applications (paginated_apps , user = for_user )
283+
284+ # Re-fetch with proper prefetching for serialization
285+ if allowed_applications :
286+ app_pks = [app .pk for app in allowed_applications ]
287+ allowed_applications = list (
288+ self .get_queryset ()
289+ .filter (pk__in = app_pks )
290+ .order_by (
291+ models .Case (
292+ * [models .When (pk = pk , then = pos ) for pos , pk in enumerate (app_pks )]
293+ )
294+ )
295+ )
296+
265297 serializer = self .get_serializer (allowed_applications , many = True )
266298 return self .get_paginated_response (serializer .data )
267299
@@ -284,6 +316,20 @@ def list(self, request: Request) -> Response:
284316 if only_with_launch_url == "true" :
285317 allowed_applications = self ._filter_applications_with_launch_url (allowed_applications )
286318
319+ # Re-fetch applications with proper prefetching for serialization
320+ # Cached applications don't have prefetched relationships, causing N+1 queries
321+ # during serialization when get_provider() is called
322+ if allowed_applications :
323+ app_pks = [app .pk for app in allowed_applications ]
324+ allowed_applications = list (
325+ self .get_queryset ()
326+ .filter (pk__in = app_pks )
327+ .order_by (
328+ # Preserve the original order from policy evaluation
329+ models .Case (* [models .When (pk = pk , then = pos ) for pos , pk in enumerate (app_pks )])
330+ )
331+ )
332+
287333 serializer = self .get_serializer (allowed_applications , many = True )
288334 return self .get_paginated_response (serializer .data )
289335
0 commit comments