Skip to content

Commit

Permalink
Create ReturnLocation object to store return params (#1229)
Browse files Browse the repository at this point in the history
  • Loading branch information
liamtoozer authored Oct 25, 2023
1 parent 265c704 commit 965dae0
Show file tree
Hide file tree
Showing 30 changed files with 426 additions and 351 deletions.
25 changes: 25 additions & 0 deletions app/questionnaire/return_location.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from dataclasses import asdict, dataclass


@dataclass(kw_only=True, frozen=True)
class ReturnLocation:
"""
Used to store return locations in the questionnaire.
return_to: The name of the type of summary page to return to
return_to_block_id: The block_id of the block to return to
return_to_answer_id: The answer_id of the answer to return to
return_to_list_item_id: The list_item_id to return to if the location is associated with a list
"""

return_to: str | None = None
return_to_block_id: str | None = None
return_to_answer_id: str | None = None
return_to_list_item_id: str | None = None

def to_dict(self, answer_id_is_anchor: bool = False) -> dict:
attributes = asdict(self)
if answer_id_is_anchor:
attributes["_anchor"] = attributes["return_to_answer_id"]
del attributes["return_to_answer_id"]
return {k: v for k, v in attributes.items() if v is not None}
126 changes: 49 additions & 77 deletions app/questionnaire/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from app.questionnaire import QuestionnaireSchema
from app.questionnaire.location import Location, SectionKey
from app.questionnaire.path_finder import PathFinder
from app.questionnaire.return_location import ReturnLocation
from app.questionnaire.routing_path import RoutingPath
from app.questionnaire.rules.rule_evaluator import RuleEvaluator
from app.utilities.types import LocationType
Expand Down Expand Up @@ -120,10 +121,7 @@ def get_next_location_url(
self,
location: LocationType,
routing_path: RoutingPath,
return_to: str | None = None,
return_to_answer_id: str | None = None,
return_to_block_id: str | None = None,
return_to_list_item_id: str | None = None,
return_location: ReturnLocation,
) -> str:
"""
Get the next location in the section. If the section is complete, determine where to go next,
Expand All @@ -135,13 +133,10 @@ def get_next_location_url(

if return_to_url := self.get_return_to_location_url(
location=location,
return_to=return_to,
return_location=return_location,
routing_path=routing_path,
is_for_previous=False,
is_section_complete=is_section_complete,
return_to_answer_id=return_to_answer_id,
return_to_block_id=return_to_block_id,
return_to_list_item_id=return_to_list_item_id,
):
return return_to_url

Expand All @@ -160,9 +155,7 @@ def get_next_location_url(
return self._get_next_block_url(
location,
routing_path,
return_to=return_to,
return_to_answer_id=return_to_answer_id,
return_to_block_id=return_to_block_id,
return_location,
)

def _get_next_location_url_for_complete_section(
Expand All @@ -177,23 +170,17 @@ def get_previous_location_url(
self,
location: LocationType,
routing_path: RoutingPath,
return_to: str | None = None,
return_to_answer_id: str | None = None,
return_to_block_id: str | None = None,
return_to_list_item_id: str | None = None,
return_location: ReturnLocation,
) -> str | None:
"""
Returns the previous 'location' to visit given a set of user answers or returns to the summary if
the `return_to` var is set and the section is complete.
the `return_location.return_to` var is set and the section is complete.
"""
if return_to_url := self.get_return_to_location_url(
location=location,
return_to=return_to,
return_location=return_location,
routing_path=routing_path,
is_for_previous=True,
return_to_answer_id=return_to_answer_id,
return_to_block_id=return_to_block_id,
return_to_list_item_id=return_to_list_item_id,
):
return return_to_url

Expand All @@ -207,17 +194,14 @@ def get_previous_location_url(
return url_for(
"questionnaire.relationships",
last=True,
return_to=return_to,
_anchor=return_to_answer_id,
**return_location.to_dict(answer_id_is_anchor=True),
)
return url_for(
"questionnaire.block",
block_id=previous_block_id,
list_name=routing_path.list_name,
list_item_id=routing_path.list_item_id,
return_to=return_to,
return_to_block_id=return_to_block_id,
_anchor=return_to_answer_id,
**return_location.to_dict(answer_id_is_anchor=True),
)

if self.can_access_hub():
Expand All @@ -229,39 +213,30 @@ def get_return_to_location_url(
self,
*,
location: LocationType,
return_to: str | None,
return_location: ReturnLocation,
routing_path: RoutingPath,
is_for_previous: bool,
is_section_complete: bool | None = None,
return_to_answer_id: str | None = None,
return_to_block_id: str | None = None,
return_to_list_item_id: str | None = None,
) -> str | None:
if not return_to:
if not return_location.return_to:
return None

if return_to == "grand-calculated-summary" and (
if return_location.return_to == "grand-calculated-summary" and (
url := self._get_return_to_for_grand_calculated_summary(
return_to=return_to,
return_to_block_id=return_to_block_id,
return_location=return_location,
section_key=location.section_key,
routing_path=routing_path,
is_for_previous=is_for_previous,
location=location,
return_to_answer_id=return_to_answer_id,
return_to_list_item_id=return_to_list_item_id,
)
):
return url

if return_to.startswith("calculated-summary") and (
if return_location.return_to.startswith("calculated-summary") and (
url := self._get_return_to_for_calculated_summary(
return_to=return_to,
return_to_block_id=return_to_block_id,
return_to_list_item_id=return_to_list_item_id,
location=location,
routing_path=routing_path,
return_to_answer_id=return_to_answer_id,
return_location=return_location,
)
):
return url
Expand All @@ -275,45 +250,49 @@ def get_return_to_location_url(
# go to the next incomplete item in the section whilst preserving return to parameters
return self._get_return_url_for_inaccessible_location(
is_for_previous=is_for_previous,
return_to_block_id=return_to_block_id,
return_to=return_to,
return_to_list_item_id=return_to_list_item_id,
routing_path=routing_path,
return_location=return_location,
)

if return_to == "section-summary":
if return_location.return_to == "section-summary":
return self._get_section_url(
location.section_key, return_to_answer_id=return_to_answer_id
location.section_key,
return_to_answer_id=return_location.return_to_answer_id,
)
if return_to == "final-summary" and self.is_questionnaire_complete:
if (
return_location.return_to == "final-summary"
and self.is_questionnaire_complete
):
return url_for(
"questionnaire.submit_questionnaire", _anchor=return_to_answer_id
"questionnaire.submit_questionnaire",
_anchor=return_location.return_to_answer_id,
)

def _get_return_to_for_grand_calculated_summary(
self,
*,
return_to: str | None,
return_to_block_id: str | None,
return_location: ReturnLocation,
section_key: SectionKey,
routing_path: RoutingPath,
is_for_previous: bool,
location: LocationType,
return_to_answer_id: str | None = None,
return_to_list_item_id: str | None = None,
) -> str | None:
"""
Builds the return url for a grand calculated summary,
and accounts for it possibly being in a different section to the calculated summaries it references
"""
if not (return_to_block_id and self._schema.is_block_valid(return_to_block_id)):
if not (
return_location.return_to_block_id
and self._schema.is_block_valid(return_location.return_to_block_id)
):
return None

return_to_block_id = return_location.return_to_block_id
# Type ignore: if the block is valid, then we'll be able to find a section for it
grand_calculated_summary_section: str = (
self._schema.get_section_id_for_block_id(return_to_block_id) # type: ignore
)
list_item_id = location.list_item_id or return_to_list_item_id
list_item_id = location.list_item_id or return_location.return_to_list_item_id
list_name = (
self._list_store.get_list_name_for_list_item_id(list_item_id)
if list_item_id
Expand All @@ -335,7 +314,7 @@ def _get_return_to_for_grand_calculated_summary(
)
if self.can_access_location(
Location(
block_id=return_to_block_id,
block_id=return_location.return_to_block_id,
section_id=grand_calculated_summary_section,
list_item_id=list_item_id,
list_name=list_name,
Expand All @@ -344,30 +323,25 @@ def _get_return_to_for_grand_calculated_summary(
):
return url_for(
"questionnaire.block",
block_id=return_to_block_id,
block_id=return_location.return_to_block_id,
list_item_id=list_item_id,
list_name=list_name,
_anchor=return_to_answer_id,
_anchor=return_location.return_to_answer_id,
)
# since the above may define a different routing_path,
# retrieval of the next incomplete block needs to be here instead of returning None and allowing default behaviour
return self._get_return_url_for_inaccessible_location(
is_for_previous=is_for_previous,
return_to_block_id=return_to_block_id,
return_to=return_to,
return_to_list_item_id=return_to_list_item_id,
return_location=return_location,
routing_path=routing_path,
)

def _get_return_to_for_calculated_summary(
self,
*,
return_to: str,
return_to_block_id: str | None,
return_to_list_item_id: str | None,
return_location: ReturnLocation,
location: LocationType,
routing_path: RoutingPath,
return_to_answer_id: str | None = None,
) -> str | None:
"""
The return url for a calculated summary varies based on whether it's standalone or part of a grand calculated summary
Expand All @@ -378,10 +352,10 @@ def _get_return_to_for_calculated_summary(
block_id = None
remaining: list[str] = []
# for a calculated summary this might have multiple items, e.g. a calculated summary to go to and then a grand calculated one
if return_to_block_id:
if return_location.return_to_block_id:
# the first item is the block id to route to (e.g. a calculated summary to go back to first)
# anything remaining forms where to go next (e.g. a grand calculated summary)
block_id, *remaining = return_to_block_id.split(",")
block_id, *remaining = return_location.return_to_block_id.split(",")

if self.can_access_location(
Location(
Expand All @@ -393,8 +367,10 @@ def _get_return_to_for_calculated_summary(
):
# if the next location is valid, the new url is that location, and the new 'return to block id' is just what remains
return_to_block_id = ",".join(remaining) if remaining else None

# remove first item and return the remaining ones
return_to_remaining = ",".join(return_to.split(",")[1:]) or None
# Type ignore: return_location.return_to will always be populated at this point
return_to_remaining = ",".join(return_location.return_to.split(",")[1:]) or None # type: ignore

return url_for(
"questionnaire.block",
Expand All @@ -403,17 +379,15 @@ def _get_return_to_for_calculated_summary(
list_item_id=location.list_item_id,
return_to=return_to_remaining,
return_to_block_id=return_to_block_id,
return_to_list_item_id=return_to_list_item_id,
_anchor=return_to_answer_id,
return_to_list_item_id=return_location.return_to_list_item_id,
_anchor=return_location.return_to_answer_id,
)

def _get_return_url_for_inaccessible_location(
self,
*,
is_for_previous: bool,
return_to_block_id: str | None,
return_to: str | None,
return_to_list_item_id: str | None,
return_location: ReturnLocation,
routing_path: RoutingPath,
) -> str | None:
"""
Expand All @@ -422,17 +396,15 @@ def _get_return_url_for_inaccessible_location(
"""
if (
not is_for_previous
and return_to
and return_location.return_to
and (
next_incomplete_location := self._get_first_incomplete_location_in_section(
routing_path
)
)
):
return next_incomplete_location.url(
return_to=return_to,
return_to_block_id=return_to_block_id,
return_to_list_item_id=return_to_list_item_id,
**return_location.to_dict(),
)

def get_next_location_url_for_end_of_section(self) -> str:
Expand Down Expand Up @@ -575,7 +547,7 @@ def _is_section_enabled(self, section: Mapping) -> bool:
def _get_next_block_url(
location: LocationType,
routing_path: RoutingPath,
**kwargs: str | None,
return_location: ReturnLocation,
) -> str:
# Type ignore: the location will have a block
next_block_id = routing_path[routing_path.index(location.block_id) + 1] # type: ignore
Expand All @@ -585,7 +557,7 @@ def _get_next_block_url(
list_name=routing_path.list_name,
list_item_id=routing_path.list_item_id,
_external=False,
**kwargs,
**return_location.to_dict(),
)

@staticmethod
Expand Down
Loading

0 comments on commit 965dae0

Please sign in to comment.