diff --git a/common/djangoapps/student/admin.py b/common/djangoapps/student/admin.py index 9446a02249e102594a408e2616e385085322dd6b..2b102fdb1fd98f83419f1ead7cb42a07971d0201 100644 --- a/common/djangoapps/student/admin.py +++ b/common/djangoapps/student/admin.py @@ -5,6 +5,7 @@ from django.contrib import admin from django.contrib.admin.sites import NotRegistered from django.contrib.auth import get_user_model from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.db import models from django.utils.translation import ugettext_lazy as _ from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey @@ -181,6 +182,21 @@ class CourseEnrollmentAdmin(admin.ModelAdmin): search_fields = ('course__id', 'mode', 'user__username',) form = CourseEnrollmentForm + def get_search_results(self, request, queryset, search_term): + qs, use_distinct = super(CourseEnrollmentAdmin, self).get_search_results(request, queryset, search_term) + + # annotate each enrollment with whether the username was an + # exact match for the search term + qs = qs.annotate(exact_username_match=models.Case( + models.When(user__username=search_term, then=models.Value(True)), + default=models.Value(False), + output_field=models.BooleanField())) + + # present exact matches first + qs = qs.order_by('-exact_username_match', 'user__username', 'course_id') + + return qs, use_distinct + def queryset(self, request): return super(CourseEnrollmentAdmin, self).queryset(request).select_related('user') diff --git a/common/djangoapps/student/tests/test_admin_views.py b/common/djangoapps/student/tests/test_admin_views.py index 641d93439728058738fec527a9ea6ea7dd793d83..ef5d2cb882c34c2ad150df5aba06fcfc6c1f6f8c 100644 --- a/common/djangoapps/student/tests/test_admin_views.py +++ b/common/djangoapps/student/tests/test_admin_views.py @@ -208,11 +208,12 @@ class CourseEnrollmentAdminTest(SharedModuleStoreTestCase): def setUp(self): super(CourseEnrollmentAdminTest, self).setUp() self.user = UserFactory.create(is_staff=True, is_superuser=True) - self.client.login(username=self.user.username, password='test') + self.course = CourseFactory() CourseEnrollmentFactory( user=self.user, - course_id=CourseFactory().id, # pylint: disable=no-member + course_id=self.course.id, # pylint: disable=no-member ) + self.client.login(username=self.user.username, password='test') @ddt.data(*ADMIN_URLS) @ddt.unpack @@ -232,3 +233,24 @@ class CourseEnrollmentAdminTest(SharedModuleStoreTestCase): with COURSE_ENROLLMENT_ADMIN_SWITCH.override(active=True): response = getattr(self.client, method)(url) self.assertEqual(response.status_code, 200) + + def test_username_exact_match(self): + """ + Ensure that course enrollment searches return exact matches on username first. + """ + user2 = UserFactory.create(username='aaa_{}'.format(self.user.username)) + CourseEnrollmentFactory( + user=user2, + course_id=self.course.id, # pylint: disable=no-member + ) + search_url = '{}?q={}'.format(reverse('admin:student_courseenrollment_changelist'), self.user.username) + with COURSE_ENROLLMENT_ADMIN_SWITCH.override(active=True): + response = self.client.get(search_url) + self.assertEqual(response.status_code, 200) + + # context['results'] is an array of arrays of HTML <td> elements to be rendered + self.assertEqual(len(response.context['results']), 2) + for idx, username in enumerate([self.user.username, user2.username]): + # Locate the <td> column containing the username + user_field = next(col for col in response.context['results'][idx] if "field-user" in col) + self.assertIn(username, user_field)