Skip to content

Commit 2ad858f

Browse files
authored
Merge pull request #1192 from rdmorganiser/1191-interview-slow-loading-on-proceed-when-a-lot-of-conditional-pages-are-skipped
feat(projects): add check for finding next relevant page
2 parents b5397a5 + 365b892 commit 2ad858f

File tree

3 files changed

+104
-31
lines changed

3 files changed

+104
-31
lines changed

rdmo/projects/progress.py

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,44 @@ def compute_sets(values):
4747
return sets
4848

4949

50+
def compute_next_relevant_page(current_page, direction, catalog, resolved_conditions):
51+
# recursively compute the next relevant page based on resolved conditions.
52+
# first, get the next page from the catalog based on the specified direction
53+
next_page = (
54+
catalog.get_prev_page(current_page) if direction == 'prev'
55+
else catalog.get_next_page(current_page)
56+
)
57+
58+
# if there is no next page, return None
59+
if not next_page:
60+
return None
61+
62+
# check if the next page meets the conditions
63+
if compute_show_page(next_page, resolved_conditions):
64+
return next_page
65+
66+
# recursive step: check the next page
67+
return compute_next_relevant_page(next_page, direction, catalog, resolved_conditions)
68+
69+
70+
def compute_show_page(page, conditions):
71+
# determine if a page should be shown based on resolved conditions
72+
# show only pages with resolved conditions, but show all pages without conditions
73+
pages_conditions = {page.id for page in page.conditions.all()}
74+
75+
if pages_conditions:
76+
# check if any valuesets for set_prefix = '' resolved
77+
# for non collection pages restrict further to set_index = 0
78+
79+
return any(
80+
(set_prefix == '') and (page.is_collection or set_index == 0)
81+
for page_condition in pages_conditions
82+
for set_prefix, set_index in conditions[page_condition]
83+
)
84+
else:
85+
return True
86+
87+
5088
def compute_navigation(section, project, snapshot=None):
5189
# get all values for this project and snapshot
5290
values = project.values.filter(snapshot=snapshot).select_related('attribute', 'option')
@@ -74,19 +112,9 @@ def compute_navigation(section, project, snapshot=None):
74112
navigation_section['pages'] = []
75113

76114
for page in catalog_section.elements:
77-
pages_conditions = {page.id for page in page.conditions.all()}
78-
79-
# show only pages with resolved conditions, but show all pages without conditions
80-
if pages_conditions:
81-
# check if any valuesets for set_prefix = '' resolved
82-
# for non collection pages restrict further to set_index = 0
83-
show = any(
84-
(set_prefix == '') and (page.is_collection or set_index == 0)
85-
for page_condition in pages_conditions
86-
for set_prefix, set_index in conditions[page_condition]
87-
)
88-
else:
89-
show = True
115+
116+
# determine if a page should be shown or not
117+
show = compute_show_page(page, conditions)
90118

91119
# count the total number of questions, taking sets and conditions into account
92120
counts = count_questions(page, sets, conditions)

rdmo/projects/tests/test_viewset_project_page.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,39 @@ def test_detail_page_with_nested_questionsets(db, client):
8888
assert questionsets_ids == nested_questionsets_id
8989
element_ids = [i['id'] for qs in questionsets for i in qs['elements']]
9090
assert element_ids == nested_element_ids
91+
92+
93+
@pytest.mark.parametrize('direction', ['next', 'prev'])
94+
def test_detail_page_resolve_next_relevant_page(db, client, direction):
95+
project_id = 1
96+
username = 'owner'
97+
start_page_id = 64
98+
end_page_id = 69
99+
100+
client.login(username=username, password=username)
101+
102+
if direction == 'next':
103+
next_page_id = start_page_id + 1
104+
add_url = ''
105+
else: # direction == 'prev':
106+
start_page_id, end_page_id = end_page_id, start_page_id
107+
next_page_id = start_page_id - 1
108+
add_url = '?back=true'
109+
110+
# get the starting page
111+
url = reverse(urlnames['detail'], args=[project_id, start_page_id])
112+
response = client.get(f'{url}{add_url}')
113+
assert response.status_code == 200
114+
assert response.json().get(f'{direction}_page') == next_page_id
115+
116+
# get the following page, depending on direction
117+
url_next = reverse(urlnames['detail'], args=[project_id, next_page_id])
118+
response_next = client.get(f'{url_next}{add_url}')
119+
assert response_next.status_code == 303
120+
121+
# this should show the redirect to the next relevant page
122+
assert response_next.url.endswith(f'{end_page_id}/')
123+
124+
# a get on the redirected url as a double check
125+
response_next_relevant = client.get(response_next.url)
126+
assert response_next_relevant.status_code == 200

rdmo/projects/viewsets.py

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
from rdmo.conditions.models import Condition
2323
from rdmo.core.permissions import HasModelPermission
24-
from rdmo.core.utils import human2bytes, return_file_response
24+
from rdmo.core.utils import human2bytes, is_truthy, return_file_response
2525
from rdmo.options.models import OptionSet
2626
from rdmo.questions.models import Catalog, Page, Question, QuestionSet
2727
from rdmo.tasks.models import Task
@@ -42,7 +42,14 @@
4242
HasProjectProgressObjectPermission,
4343
HasProjectsPermission,
4444
)
45-
from .progress import compute_navigation, compute_progress
45+
from .progress import (
46+
compute_navigation,
47+
compute_next_relevant_page,
48+
compute_progress,
49+
compute_sets,
50+
compute_show_page,
51+
resolve_conditions,
52+
)
4653
from .serializers.v1 import (
4754
IntegrationSerializer,
4855
InviteSerializer,
@@ -541,29 +548,31 @@ def dispatch(self, *args, **kwargs):
541548

542549
def retrieve(self, request, *args, **kwargs):
543550
page = self.get_object()
544-
conditions = page.conditions.select_related('source', 'target_option')
545-
551+
catalog = self.project.catalog
546552
values = self.project.values.filter(snapshot=None).select_related('attribute', 'option')
547553

548-
if check_conditions(conditions, values):
554+
sets = compute_sets(values)
555+
resolved_conditions = resolve_conditions(catalog, values, sets)
556+
557+
# check if the current page meets conditions
558+
if compute_show_page(page, resolved_conditions):
549559
serializer = self.get_serializer(page)
550560
return Response(serializer.data)
551561
else:
552-
if request.GET.get('back') == 'true':
553-
prev_page = self.project.catalog.get_prev_page(page)
554-
if prev_page is not None:
555-
url = reverse('v1-projects:project-page-detail',
556-
args=[self.project.id, prev_page.id]) + '?back=true'
557-
return HttpResponseRedirect(url, status=303)
558-
else:
559-
next_page = self.project.catalog.get_next_page(page)
560-
if next_page is not None:
561-
url = reverse('v1-projects:project-page-detail', args=[self.project.id, next_page.id])
562-
return HttpResponseRedirect(url, status=303)
563-
564-
# indicate end of catalog
562+
# determine the direction of navigation (previous or next)
563+
direction = 'prev' if is_truthy(request.GET.get('back')) else 'next'
564+
565+
# find the next relevant page with from pages and resolved conditions
566+
next_relevant_page = compute_next_relevant_page(page, direction, catalog, resolved_conditions)
567+
568+
if next_relevant_page is not None:
569+
url = reverse('v1-projects:project-page-detail', args=[self.project.id, next_relevant_page.id])
570+
return HttpResponseRedirect(url, status=303)
571+
572+
# end of catalog, if no next relevant page is found
565573
return Response(status=204)
566574

575+
567576
@action(detail=False, url_path='continue', permission_classes=(HasModelPermission | HasProjectPagePermission, ))
568577
def get_continue(self, request, pk=None, parent_lookup_project=None):
569578
try:

0 commit comments

Comments
 (0)