Skip to content

Commit

Permalink
feat: limit the number of resulting learners in Django admin manage l…
Browse files Browse the repository at this point in the history
…earners view

...so that we can support customers with a very large number of learners
ENT-8850
  • Loading branch information
iloveagent57 committed Apr 30, 2024
1 parent b30264c commit 3938fd9
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 20 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ Change Log
Unreleased
----------
* nothing unreleased

[4.17.0]
--------
* feat: limit the number of resulting learners in Django admin manage learners view

[4.16.5]
--------
* feat: splitting out group membership serializer learner id into lms user ID and ecu ID
Expand Down
2 changes: 1 addition & 1 deletion enterprise/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
Your project description goes here.
"""

__version__ = "4.16.5"
__version__ = "4.17.0"
56 changes: 37 additions & 19 deletions enterprise/admin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from enterprise.api_client.discovery import get_course_catalog_api_service_client
from enterprise.api_client.ecommerce import EcommerceApiClient
from enterprise.api_client.sso_orchestrator import EnterpriseSSOOrchestratorApiClient, SsoOrchestratorClientError
from enterprise.constants import PAGE_SIZE
from enterprise.constants import DJANGO_ADMIN_MANAGE_LEARNERS_LIMIT, PAGE_SIZE
from enterprise.errors import LinkUserToEnterpriseError
from enterprise.models import (
EnrollmentNotificationEmailTemplate,
Expand Down Expand Up @@ -357,7 +357,11 @@ def get_search_keyword(self, request):

def get_enterprise_customer_user_queryset(self, request, search_keyword, customer_uuid, page_size=PAGE_SIZE):
"""
Get the list of EnterpriseCustomerUsers we want to render.
Get the list of EnterpriseCustomerUsers we want to render. Note that,
if no ``search_keyword`` is provided, the resulting queryset will
only include up to some constant limit of results, so that we
can support searching for particular user records in customers
with very large numbers of learners.
Arguments:
request (HttpRequest): HTTP Request instance.
Expand All @@ -367,16 +371,29 @@ def get_enterprise_customer_user_queryset(self, request, search_keyword, custome
page_size (int): Number of learners displayed in each paginated set.
"""
page = request.GET.get('page', 1)
learners = EnterpriseCustomerUser.objects.filter(enterprise_customer__uuid=customer_uuid)
user_ids = learners.values_list('user_id', flat=True)
matching_users = User.objects.filter(pk__in=user_ids)
if search_keyword is not None:
matching_users = matching_users.filter(
Q(email__icontains=search_keyword) | Q(username__icontains=search_keyword)
if search_keyword:
enterprise_learners = self._get_searched_enterprise_customer_users(
search_keyword,
customer_uuid,
)
matching_user_ids = matching_users.values_list('pk', flat=True)
learners = learners.filter(user_id__in=matching_user_ids)
return paginated_list(learners, page, page_size)
else:
enterprise_learners = EnterpriseCustomerUser.objects.filter(
enterprise_customer__uuid=customer_uuid,
)[:DJANGO_ADMIN_MANAGE_LEARNERS_LIMIT]
return paginated_list(enterprise_learners, page, page_size)

def _get_searched_enterprise_customer_users(self, search_keyword, customer_uuid):
"""
Helper to return a (possibly truncated) queryset of enterprise customer users
who match some search criteria based on corresponding ``core.User`` records.
"""
matching_user_ids = User.objects.filter(
Q(email__icontains=search_keyword) | Q(username__icontains=search_keyword)
).values_list('id', flat=True)
return EnterpriseCustomerUser.objects.filter(
enterprise_customer__uuid=customer_uuid,
user_id__in=matching_user_ids,
)[:DJANGO_ADMIN_MANAGE_LEARNERS_LIMIT]

def get_pending_users_queryset(self, search_keyword, customer_uuid):
"""
Expand All @@ -387,14 +404,15 @@ def get_pending_users_queryset(self, search_keyword, customer_uuid):
customer_uuid (str): A unique identifier to filter down to only pending users
linked to a particular EnterpriseCustomer.
"""
queryset = PendingEnterpriseCustomerUser.objects.filter(
enterprise_customer__uuid=customer_uuid
)

if search_keyword is not None:
queryset = queryset.filter(user_email__icontains=search_keyword)

return queryset
if search_keyword:
return PendingEnterpriseCustomerUser.objects.filter(
enterprise_customer__uuid=customer_uuid,
user_email__icontains=search_keyword,
)[:DJANGO_ADMIN_MANAGE_LEARNERS_LIMIT]
else:
return PendingEnterpriseCustomerUser.objects.filter(
enterprise_customer__uuid=customer_uuid
)[:DJANGO_ADMIN_MANAGE_LEARNERS_LIMIT]

@classmethod
def _handle_singular(cls, request, enterprise_customer, manage_learners_form):
Expand Down
3 changes: 3 additions & 0 deletions enterprise/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,3 +252,6 @@ class FulfillmentTypes:
)

ENTITY_ID_REGEX = r"<(\w+:)?EntityDescriptor.*?entityID=['\"](.*?)['\"].*?>"

# Max learners included in the Admin Manage Learners page
DJANGO_ADMIN_MANAGE_LEARNERS_LIMIT = 10000
33 changes: 33 additions & 0 deletions tests/test_admin/test_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,39 @@ def test_get_existing_links_only(self):
response = self.client.get(self.view_url)
self._test_get_response(response, linked_learners, [])

def test_users_limit_enforced(self):
"""
Tests that we only return/render up to a constant limit
of enterprise customer user records, both pending and "concrete".
"""
self._login()

_ = [
EnterpriseCustomerUserFactory(
enterprise_customer=self.enterprise_customer,
user_id=UserFactory().id,
),
EnterpriseCustomerUserFactory(
enterprise_customer=self.enterprise_customer,
user_id=UserFactory().id,
),
EnterpriseCustomerUserFactory(
enterprise_customer=self.enterprise_customer,
user_id=UserFactory().id,
),
]
_ = [
PendingEnterpriseCustomerUserFactory(enterprise_customer=self.enterprise_customer),
PendingEnterpriseCustomerUserFactory(enterprise_customer=self.enterprise_customer),
PendingEnterpriseCustomerUserFactory(enterprise_customer=self.enterprise_customer),
]
with mock.patch('enterprise.admin.views.DJANGO_ADMIN_MANAGE_LEARNERS_LIMIT', 2):
response = self.client.get(self.view_url)

assert response.status_code == 200
assert len(list(response.context[self.context_parameters.LEARNERS])) == 2
assert len(list(response.context[self.context_parameters.PENDING_LEARNERS])) == 2

def test_get_existing_and_pending_links(self):
self._login()

Expand Down

0 comments on commit 3938fd9

Please sign in to comment.