1212import ujson
1313from shelflist .exporters import ItemsToSolr
1414from shelflist .search_indexes import ShelflistItemIndex
15+ from shelflist .serializers import ShelflistItemSerializer
1516from six import text_type
1617from six .moves import range
18+ from utils .redisobjs import RedisObject
1719
1820
1921# FIXTURES AND TEST DATA
@@ -643,7 +645,7 @@ def test_shelflistitem_view_orderby(order_by, api_settings, shelflist_solr_env,
643645
644646def test_shelflistitem_row_order (api_settings , shelflist_solr_env ,
645647 get_shelflist_urls , api_client , redis_obj ,
646- get_found_ids , settings ):
648+ get_found_ids ):
647649 """
648650 The `shelflistitems` list view should list items in the same order
649651 that the shelflist manifest for that location lists them. The
@@ -652,12 +654,11 @@ def test_shelflistitem_row_order(api_settings, shelflist_solr_env,
652654 recs = shelflist_solr_env .records ['shelflistitem' ]
653655 loc = recs [0 ]['location_code' ]
654656 loc_recs = [r for r in recs if r ['location_code' ] == loc ]
655- using = settings .REST_VIEWS_HAYSTACK_CONNECTIONS ['ShelflistItems' ]
657+ using = api_settings .REST_VIEWS_HAYSTACK_CONNECTIONS ['ShelflistItems' ]
656658 index = ShelflistItemIndex (using = using )
657659 manifest = index .get_location_manifest (loc )
658660 redis_key = '{}:{}' .format (REDIS_SHELFLIST_PREFIX , loc )
659661 redis_obj (redis_key ).set (manifest )
660-
661662 url = get_shelflist_urls (shelflist_solr_env .records ['shelflistitem' ])[loc ]
662663 response = api_client .get (url )
663664 total = response .data ['totalCount' ]
@@ -667,6 +668,169 @@ def test_shelflistitem_row_order(api_settings, shelflist_solr_env,
667668 assert row_numbers == [num for num in range (0 , total )]
668669
669670
671+ def test_shelflistitem_row_pagination (api_settings , shelflist_solr_env ,
672+ get_shelflist_urls , api_client ,
673+ redis_obj , get_found_ids ):
674+ """
675+ When paginating a `shelflistitems` list view, the `rowNumber`
676+ values should accurately reflect the requested pagination
677+ parameters.
678+ """
679+ recs = shelflist_solr_env .records ['shelflistitem' ]
680+ loc = recs [0 ]['location_code' ]
681+ loc_recs = [r for r in recs if r ['location_code' ] == loc ]
682+ using = api_settings .REST_VIEWS_HAYSTACK_CONNECTIONS ['ShelflistItems' ]
683+ index = ShelflistItemIndex (using = using )
684+ manifest = index .get_location_manifest (loc )
685+ redis_key = '{}:{}' .format (REDIS_SHELFLIST_PREFIX , loc )
686+ redis_obj (redis_key ).set (manifest )
687+ total = len (loc_recs )
688+ offset = int (total / 2 )
689+ limit = int (total / 4 )
690+ limit_p = api_settings .REST_FRAMEWORK ['PAGINATE_BY_PARAM' ]
691+ offset_p = api_settings .REST_FRAMEWORK ['PAGINATE_PARAM' ]
692+ url = get_shelflist_urls (shelflist_solr_env .records ['shelflistitem' ])[loc ]
693+ paginated_url = f"{ url } ?{ limit_p } ={ limit } &{ offset_p } ={ offset } "
694+ response = api_client .get (paginated_url )
695+ found_ids = get_found_ids ('id' , response )
696+ row_numbers = get_found_ids ('rowNumber' , response )
697+ assert found_ids [0 ] == manifest [offset ]
698+ assert row_numbers [0 ] == offset
699+ assert found_ids [- 1 ] == manifest [offset + limit - 1 ]
700+ assert row_numbers [- 1 ] == offset + limit - 1
701+
702+
703+ def test_shelflistitem_row_filtering (api_settings , shelflist_solr_env ,
704+ get_shelflist_urls , api_client ,
705+ assemble_shelflist_test_records ,
706+ redis_obj , get_found_ids ):
707+ """
708+ When filtering a `shelflistitems` list view, the `rowNumber`
709+ values should accurately reflect the items included in the filter.
710+ """
711+ recs = shelflist_solr_env .records ['shelflistitem' ]
712+ loc = recs [0 ]['location_code' ]
713+ loc_recs = [r for r in recs if r ['location_code' ] == loc ]
714+ using = api_settings .REST_VIEWS_HAYSTACK_CONNECTIONS ['ShelflistItems' ]
715+
716+ exp_rows = [2 , 4 , 10 , 13 , 14 , 17 ]
717+ test_data = [
718+ {
719+ 'id' : f"TEST{ i } " ,
720+ 'call_number_type' : '__A' ,
721+ 'call_number' : f"___A{ i } " ,
722+ 'call_number_sort' : f"___A{ i :04} " ,
723+ 'call_number_search' : f"___A{ i } " ,
724+ 'shelf_status' : 'SELECT_ME' if i in exp_rows else 'SKIP' ,
725+ 'location_code' : loc
726+ } for i in range (20 )
727+ ]
728+ exp_ids = [f"TEST{ i } " for i in exp_rows ]
729+ _ , trecs = assemble_shelflist_test_records (
730+ [(item ['id' ], item ) for item in test_data ]
731+ )
732+
733+ index = ShelflistItemIndex (using = using )
734+ manifest = index .get_location_manifest (loc )
735+ redis_key = '{}:{}' .format (REDIS_SHELFLIST_PREFIX , loc )
736+ redis_obj (redis_key ).set (manifest )
737+
738+ url = get_shelflist_urls (shelflist_solr_env .records ['shelflistitem' ])[loc ]
739+ filtered_url = f"{ url } ?shelfStatus=SELECT_ME"
740+ response = api_client .get (filtered_url )
741+ found_ids = get_found_ids ('id' , response )
742+ row_numbers = get_found_ids ('rowNumber' , response )
743+ assert found_ids == exp_ids
744+ assert row_numbers == exp_rows
745+
746+
747+ def test_shelflistitem_detail_view_rows (api_settings , shelflist_solr_env ,
748+ get_shelflist_urls , api_client ,
749+ redis_obj ):
750+ """
751+ The `shelflistitems` detail view for individual items should
752+ reflect the correct `rowNumber` values.
753+ """
754+ recs = shelflist_solr_env .records ['shelflistitem' ]
755+ loc = recs [0 ]['location_code' ]
756+ loc_recs = [r for r in recs if r ['location_code' ] == loc ]
757+ using = api_settings .REST_VIEWS_HAYSTACK_CONNECTIONS ['ShelflistItems' ]
758+ index = ShelflistItemIndex (using = using )
759+ manifest = index .get_location_manifest (loc )
760+ redis_key = '{}:{}' .format (REDIS_SHELFLIST_PREFIX , loc )
761+ redis_obj (redis_key ).set (manifest )
762+ total = len (loc_recs )
763+ url = get_shelflist_urls (shelflist_solr_env .records ['shelflistitem' ])[loc ]
764+ rows_to_check = (0 , 2 , len (manifest ) - 1 )
765+ for exp_row in rows_to_check :
766+ item_id = manifest [exp_row ]
767+ response = api_client .get (f"{ url } { item_id } " )
768+ assert response .data ['rowNumber' ] == exp_row
769+
770+
771+ def test_shelflistitem_list_row_caching (api_settings , shelflist_solr_env ,
772+ get_shelflist_urls , api_client ,
773+ mocker ):
774+ """
775+ For `shelflistitem` views, the `rowNumber` value comes from Redis.
776+ The serializer is configured to minimize Redis lookups (to speed up
777+ requests). On a list view, it gets the first item from Redis but
778+ calculates the rest of the values, and it caches them. The cache is
779+ refreshed on every list view request. Detail view requests try to
780+ use the cache -- but they look the value up in Redis if there's a
781+ cache miss.
782+ """
783+ mocker .patch .object (
784+ RedisObject , 'get_index' , mocker .Mock (
785+ side_effect = lambda * args : list (range (1000 , 1000 + len (args )))
786+ )
787+ )
788+ ShelflistItemSerializer ._lookup_cache ['row_numbers' ] = {}
789+ recs = shelflist_solr_env .records ['shelflistitem' ]
790+ loc = recs [0 ]['location_code' ]
791+ loc_recs = [r for r in recs if r ['location_code' ] == loc ]
792+ using = api_settings .REST_VIEWS_HAYSTACK_CONNECTIONS ['ShelflistItems' ]
793+ index = ShelflistItemIndex (using = using )
794+ manifest = index .get_location_manifest (loc )
795+ url = get_shelflist_urls (shelflist_solr_env .records ['shelflistitem' ])[loc ]
796+ response = api_client .get (url )
797+ call_stack = []
798+
799+ # The serializer lookup_cache should contain row numbers for all
800+ # items we've requested. Note it starts at 1000 because this is
801+ # what the mock we created above returns.
802+ assert ShelflistItemSerializer ._lookup_cache ['row_numbers' ] == {
803+ item_id : 1000 + i for i , item_id in enumerate (manifest )
804+ }
805+
806+ # Assuming we have multiple items in our list view, we only query
807+ # Redis to get the first item, and we extrapolate to get the rest
808+ # of the items in the list.
809+ assert len (manifest ) > 1
810+ call_stack .append (mocker .call (* manifest ))
811+ assert RedisObject .get_index .mock_calls == call_stack
812+
813+ # Now when we request detail views for individual rows in the list,
814+ # it should still use the cached row number, if it finds it,
815+ # instead of querying Redis.
816+ for ping_row in (0 , 2 , len (manifest ) - 1 ):
817+ api_client .get (f"{ url } { manifest [ping_row ]} " )
818+ assert RedisObject .get_index .mock_calls == call_stack
819+
820+ # When we hit the list view again it should refresh the cache,
821+ # resulting in an additional call to Redis.
822+ api_client .get (url )
823+ call_stack .append (mocker .call (* manifest ))
824+ assert RedisObject .get_index .mock_calls == call_stack
825+
826+ # If we clear the serializer lookup_cache, then hitting the detail
827+ # view for an individual row requests the rowNumber from Redis.
828+ ShelflistItemSerializer ._lookup_cache ['row_numbers' ] = {}
829+ api_client .get (f"{ url } { manifest [- 1 ]} " )
830+ call_stack .append (mocker .call (manifest [- 1 ]))
831+ assert RedisObject .get_index .mock_calls == call_stack
832+
833+
670834def test_shelflistitem_putpatch_requires_auth (api_settings ,
671835 assemble_custom_shelflist ,
672836 get_shelflist_urls , api_client ):
@@ -731,15 +895,15 @@ def test_shelflistitem_update_items(method, api_settings,
731895 shelflist_solr_env ,
732896 filter_serializer_fields_by_opt ,
733897 derive_updated_resource , send_api_data ,
734- get_shelflist_urls , api_client , settings ):
898+ get_shelflist_urls , api_client ):
735899 """
736900 Updating writeable fields on shelflistitems should update/save the
737901 resource: it should update the writeable fields that were changed
738902 and keep all other fields exactly the same.
739903 """
740904 test_lcode , test_id = '1test' , 'i99999999'
741905 _ , _ , trecs = assemble_custom_shelflist (test_lcode , [(test_id , {})])
742- using = settings .REST_VIEWS_HAYSTACK_CONNECTIONS ['ShelflistItems' ]
906+ using = api_settings .REST_VIEWS_HAYSTACK_CONNECTIONS ['ShelflistItems' ]
743907 index = ShelflistItemIndex (using = using )
744908 manifest = index .get_location_manifest (test_lcode )
745909 redis_key = '{}:{}' .format (REDIS_SHELFLIST_PREFIX , test_lcode )
@@ -785,14 +949,14 @@ def test_shelflistitem_delete_data(method, api_settings,
785949 shelflist_solr_env ,
786950 filter_serializer_fields_by_opt ,
787951 derive_updated_resource , send_api_data ,
788- get_shelflist_urls , api_client , settings ):
952+ get_shelflist_urls , api_client ):
789953 """
790954 Updating a writeable field and providing a null value (None) should
791955 delete the stored value.
792956 """
793957 test_lcode , test_id = '1test' , 'i99999999'
794958 _ , _ , trecs = assemble_custom_shelflist (test_lcode , [(test_id , {})])
795- using = settings .REST_VIEWS_HAYSTACK_CONNECTIONS ['ShelflistItems' ]
959+ using = api_settings .REST_VIEWS_HAYSTACK_CONNECTIONS ['ShelflistItems' ]
796960 index = ShelflistItemIndex (using = using )
797961 manifest = index .get_location_manifest (test_lcode )
798962 redis_key = '{}:{}' .format (REDIS_SHELFLIST_PREFIX , test_lcode )
@@ -888,7 +1052,7 @@ def test_shelflist_firstitemperlocation_list(test_data, search, expected,
8881052 api_settings , redis_obj ,
8891053 assemble_custom_shelflist ,
8901054 api_client , get_found_ids ,
891- do_filter_search , settings ):
1055+ do_filter_search ):
8921056 """
8931057 The `firstitemperlocation` resource is basically a custom filter
8941058 for `items` that submits a facet-query to Solr asking for the first
@@ -907,7 +1071,7 @@ def test_shelflist_firstitemperlocation_list(test_data, search, expected,
9071071 recs = test_data_by_location .get (lcode , []) + [(test_id , rec )]
9081072 test_data_by_location [lcode ] = recs
9091073
910- using = settings .REST_VIEWS_HAYSTACK_CONNECTIONS ['ShelflistItems' ]
1074+ using = api_settings .REST_VIEWS_HAYSTACK_CONNECTIONS ['ShelflistItems' ]
9111075 index = ShelflistItemIndex (using = using )
9121076 for test_lcode , data in test_data_by_location .items ():
9131077 assemble_custom_shelflist (test_lcode , data )
0 commit comments