1111from django .contrib .messages .views import SuccessMessageMixin
1212from django .core .exceptions import PermissionDenied , ValidationError
1313from django .db import transaction
14+ from django .db .models import Count , Q
1415from django .forms import Form
1516from django .forms .utils import ErrorList
1617from django .http import (
7374from grandchallenge .core .renderers import PaginatedCSVRenderer
7475from grandchallenge .core .templatetags .random_encode import random_encode
7576from grandchallenge .core .utils import strtobool
77+ from grandchallenge .core .utils .query import set_seed
7678from grandchallenge .core .views import PermissionRequestUpdate
7779from grandchallenge .datatables .views import Column
7880from grandchallenge .groups .forms import EditorsForm
@@ -1018,11 +1020,11 @@ class DisplaySetViewSet(
10181020 serializer_class = DisplaySetSerializer
10191021 queryset = (
10201022 DisplaySet .objects .all ()
1021- .with_row_number ()
10221023 .select_related ("reader_study__hanging_protocol" )
10231024 .prefetch_related (
10241025 "values__image" ,
10251026 "values__interface" ,
1027+ "reader_study__display_sets" ,
10261028 "reader_study__optional_hanging_protocols" ,
10271029 )
10281030 )
@@ -1033,6 +1035,7 @@ class DisplaySetViewSet(
10331035 * api_settings .DEFAULT_RENDERER_CLASSES ,
10341036 PaginatedCSVRenderer ,
10351037 )
1038+ randomized_qs = []
10361039
10371040 @property
10381041 def reader_study (self ):
@@ -1043,26 +1046,18 @@ def reader_study(self):
10431046 def get_serializer_class (self ):
10441047 if self .action in ["partial_update" , "update" , "create" ]:
10451048 return DisplaySetPostSerializer
1046- else :
1047- return DisplaySetSerializer
1048-
1049- def get_queryset (self ):
1050- queryset = super ().get_queryset ()
1049+ return DisplaySetSerializer
10511050
1051+ def list (self , request , * args , ** kwargs ):
1052+ queryset = self .filter_queryset (self .get_queryset ())
10521053 # Note: if more fields besides 'reader_study' are added to the
10531054 # filter_set fields, we cannot call super anymore before randomizing
10541055 # as we only want to filter out the display sets for a specific
10551056 # reader study.
10561057 reader_study = self .reader_study
1057-
1058- if reader_study :
1059- if reader_study .shuffle_hanging_list :
1060- queryset = queryset .with_shuffled_index (
1061- reader_study = reader_study , seed = self .request .user .pk
1062- )
1063- else :
1064- queryset = queryset .filter (reader_study = reader_study )
1065-
1058+ if reader_study and reader_study .shuffle_hanging_list :
1059+ queryset = queryset .filter (reader_study = reader_study )
1060+ queryset = self .create_randomized_qs (queryset = queryset )
10661061 unanswered_by_user = strtobool (
10671062 self .request .query_params .get ("unanswered_by_user" , "False" )
10681063 )
@@ -1073,10 +1068,8 @@ def get_queryset(self):
10731068 "Specifying a user is only possible when retrieving unanswered"
10741069 " display sets."
10751070 )
1076-
10771071 if username :
10781072 user = get_user_model ().objects .filter (username = username ).get ()
1079-
10801073 if user != self .request .user and not self .request .user .has_perm (
10811074 "change_readerstudy" , self .reader_study
10821075 ):
@@ -1093,30 +1086,56 @@ def get_queryset(self):
10931086 "Please provide a reader study when filtering for "
10941087 "unanswered display_sets."
10951088 )
1096-
10971089 answerable_question_count = reader_study .answerable_question_count
1098- queryset = queryset .with_answer_count (user = user ).exclude (
1099- answer_count__gte = answerable_question_count ,
1090+ queryset = (
1091+ queryset .annotate (
1092+ answer_count = Count (
1093+ "answers" ,
1094+ filter = Q (
1095+ answers__is_ground_truth = False ,
1096+ answers__creator = user ,
1097+ ),
1098+ )
1099+ )
1100+ .exclude (
1101+ answer_count__gte = answerable_question_count ,
1102+ )
1103+ .order_by ("order" , "created" )
11001104 )
1105+ # Because the filtering has changed the list, we can no longer
1106+ # reapply .order_by("?"), as the ordering would not be consistent
1107+ # with the ordering of the full list. Instead, we use the
1108+ # previously saved randomized_qs and filter the proper items
1109+ # out of it.
1110+ if reader_study and reader_study .shuffle_hanging_list :
1111+ pks = queryset .values_list ("pk" , flat = True )
1112+ queryset = [x for x in self .randomized_qs if x .pk in pks ]
11011113
1102- if reader_study . shuffle_hanging_list :
1103- queryset = queryset . order_by ( "shuffled_index" )
1104- else :
1105- queryset = queryset . order_by ( "order" , "created" )
1114+ page = self . paginate_queryset ( queryset )
1115+ if page is not None :
1116+ serializer = self . get_serializer ( page , many = True )
1117+ return self . get_paginated_response ( serializer . data )
11061118
1107- return queryset
1119+ serializer = self .get_serializer (queryset , many = True )
1120+ return Response (serializer .data )
11081121
11091122 def get_object (self ):
11101123 obj = super ().get_object ()
1111-
1124+ # retrieve the full queryset and save its shuffled version to later
1125+ # determine the shuffled index for this object
11121126 if obj .reader_study .shuffle_hanging_list :
1113- queryset = self .filter_queryset (self .get_queryset ())
1114- queryset = queryset .with_shuffled_index (
1115- reader_study = obj .reader_study , seed = self .request .user .pk
1116- )
1117- return queryset .get (pk = obj .pk )
1118- else :
1119- return obj
1127+ queryset = self .get_queryset ()
1128+ queryset = super ().filter_queryset (queryset )
1129+ queryset = queryset .filter (reader_study = obj .reader_study )
1130+ self .create_randomized_qs (queryset = queryset )
1131+ return obj
1132+
1133+ def create_randomized_qs (self , queryset ):
1134+ set_seed (1 / int (self .request .user .pk ))
1135+ queryset = queryset .order_by ("?" )
1136+ # Save the queryset to determine each item's index in the serializer
1137+ self .randomized_qs = list (queryset )
1138+ return queryset
11201139
11211140
11221141class QuestionViewSet (ReadOnlyModelViewSet ):
0 commit comments