Repository: uccser/cs-unplugged

Issue:
Allow pre-filling of resource forms
For example, on [this page](https://cs-unplugged-dev.appspot.com/en/topics/sorting-networks/integrations/growing-into-a-butterfly/) there is a link to the Sorting Network cards, however we should prefill the form with the butterfly option selected.

The URL could include some extra data (as query parameters or processed server side) to detect whether to pre-fill a form option/s.
Allow pre-filling of resource forms
For example, on [this page](https://cs-unplugged-dev.appspot.com/en/topics/sorting-networks/integrations/growing-into-a-butterfly/) there is a link to the Sorting Network cards, however we should prefill the form with the butterfly option selected.

The URL could include some extra data (as query parameters or processed server side) to detect whether to pre-fill a form option/s.


Discussion:
This feature has been developed on branch `issue/768` however this requires an update and release to Verto for the following feature: https://github.com/uccser/verto/issues/255
This feature has been developed on branch `issue/768` however this requires an update and release to Verto for the following feature: https://github.com/uccser/verto/issues/255

Resolving patch:
diff --git a/csunplugged/resources/utils/get_options_html.py b/csunplugged/resources/utils/get_options_html.py
--- a/csunplugged/resources/utils/get_options_html.py
+++ b/csunplugged/resources/utils/get_options_html.py
@@ -5,26 +5,27 @@
 from django.utils.translation import ugettext as _
 
 
-def get_options_html(options, local_options):
+def get_options_html(options, local_options, request_parameters=None):
     """Return HTML string of form elements for given options.
 
     Args:
-        options: (list of ResourceParameters) options list
-        local_options: (list of ResourceParameters) local options list
+        options (list): List of ResourceParameters options.
+        local_options (list): List of ResourceParameters local options.
+        request_parameters (QueryDict): Request QueryDict for resource form.
 
     Returns:
         HTML string
     """
     html_elements = []
     for parameter in options.values():
-        html_elements.append(parameter.html_element())
+        html_elements.append(parameter.html_element(request_parameters))
     if settings.DEBUG:
         html_elements.append(etree.Element("hr"))
         h3 = etree.Element("h3")
         h3.text = _("Local Generation Only")
         html_elements.append(h3)
         for parameter in local_options.values():
-            html_elements.append(parameter.html_element())
+            html_elements.append(parameter.html_element(request_parameters))
 
     html_string = ""
     for html_elem in html_elements:
diff --git a/csunplugged/resources/utils/resource_parameters.py b/csunplugged/resources/utils/resource_parameters.py
--- a/csunplugged/resources/utils/resource_parameters.py
+++ b/csunplugged/resources/utils/resource_parameters.py
@@ -83,6 +83,26 @@ def process_requested_values(self, requested_values):
         else:
             self.value = self.process_value(requested_values[0])
 
+    def html_default(self, request_parameters):
+        """Return the default parameter HTML value.
+
+        If the HTTP request for the resource form has a requested parameter
+        default, return the requested value. Otherwise return the parameter
+        default.
+
+        Args:
+            request_parameters (QueryDict): Request QueryDict for resource form.
+
+        Return:
+            String of default value.
+        """
+        default = self.default
+        if request_parameters:
+            requested_default = request_parameters.get(self.name)
+            if requested_default and requested_default in self.valid_values.keys():
+                default = requested_default
+        return default
+
 
 class MultiValuedParameter(ResourceParameter):
     """Resource parameter class where requesting multiple values is valid."""
@@ -121,8 +141,16 @@ def __init__(self, values=dict(), default=None, **kwargs):
             self.default = list(self.valid_values.keys())[0]  # Select first value
         self.value = None
 
-    def html_element(self):
-        """Return HTML element for the EnumResourceParameter."""
+    def html_element(self, request_parameters=None):
+        """Return HTML element for the EnumResourceParameter.
+
+        Args:
+            request_parameters (QueryDict): QueryDict of request_parameters.
+
+        Returns:
+            Element tree of HTML.
+        """
+        default_value = super().html_default(request_parameters)
         base_elem = super().html_element()
         for value, value_desc in self.valid_values.items():
             input_elem = etree.Element(
@@ -132,7 +160,7 @@ def html_element(self):
                 id='{}_{}'.format(self.name, value),
                 value=str(value)
             )
-            if value == self.default:
+            if value == default_value:
                 input_elem.set("checked", "checked")
             base_elem.append(input_elem)
             label_elem = etree.Element(
@@ -214,8 +242,15 @@ def __init__(self, placeholder="", **kwargs):
         super().__init__(**kwargs)
         self.placeholder = placeholder
 
-    def html_element(self):
-        """Return HTML element for the TextResourceParameter."""
+    def html_element(self, request_parameters=None):
+        """Return HTML element for the TextResourceParameter.
+
+        Args:
+            request_parameters (QueryDict): QueryDict of request_parameters.
+
+        Returns:
+            Element tree of HTML.
+        """
         base_elem = super().html_element()
         input_elem = etree.Element(
             "input",
@@ -244,8 +279,16 @@ def __init__(self, default=None, min_val=None, max_val=None, **kwargs):
         self.max_val = max_val
         self.default = default
 
-    def html_element(self):
-        """Return HTML element for the IntegerResourceParameter."""
+    def html_element(self, request_parameters=None):
+        """Return HTML element for the IntegerResourceParameter.
+
+        Args:
+            request_parameters (QueryDict): QueryDict of request_parameters.
+
+        Returns:
+            Element tree of HTML.
+        """
+        default_value = super().html_default(request_parameters)
         base_elem = super().html_element()
         input_elem = etree.Element(
             "input",
@@ -256,8 +299,8 @@ def html_element(self):
             input_elem.set("min", str(self.min_val))
         if self.max_val:
             input_elem.set("max", str(self.max_val))
-        if self.default:
-            input_elem.set("value", str(self.default))
+        if default_value:
+            input_elem.set("value", str(default_value))
         base_elem.append(input_elem)
         return base_elem
 
diff --git a/csunplugged/resources/views.py b/csunplugged/resources/views.py
--- a/csunplugged/resources/views.py
+++ b/csunplugged/resources/views.py
@@ -44,7 +44,7 @@ def resource(request, resource_slug):
     resource = get_object_or_404(Resource, slug=resource_slug)
     context = dict()
     generator = get_resource_generator(resource.generator_module)
-    context["options_html"] = get_options_html(generator.get_options(), generator.get_local_options())
+    context["options_html"] = get_options_html(generator.get_options(), generator.get_local_options(), request.GET)
     context["resource"] = resource
     context["debug"] = settings.DEBUG
     context["resource_thumbnail_base"] = "{}img/resources/{}/thumbnails/".format(settings.STATIC_URL, resource.slug)


Test patch:
diff --git a/csunplugged/tests/resources/utils/test_resource_parameters.py b/csunplugged/tests/resources/utils/test_resource_parameters.py
--- a/csunplugged/tests/resources/utils/test_resource_parameters.py
+++ b/csunplugged/tests/resources/utils/test_resource_parameters.py
@@ -3,7 +3,7 @@
 from utils.errors.QueryParameterMissingError import QueryParameterMissingError
 from utils.errors.QueryParameterInvalidError import QueryParameterInvalidError
 from utils.errors.QueryParameterMultipleValuesError import QueryParameterMultipleValuesError
-
+from django.http import QueryDict
 from resources.utils.resource_parameters import (
     ResourceParameter,
     SingleValuedParameter,
@@ -56,6 +56,39 @@ def test_single_valued_parameter_process_requested_values_multiple_values(self):
         with self.assertRaises(QueryParameterMultipleValuesError):
             param.process_requested_values(["value1", "value2"])
 
+    def test_single_valued_parameter_html_default_of_parameter_blank_query(self):
+        param = SingleValuedParameter(name="param", default="value")
+        query = QueryDict()
+        result = param.html_default(query)
+        self.assertEqual(result, "value")
+
+    def test_single_valued_parameter_html_default_of_parameter_query(self):
+        param = SingleValuedParameter(name="param", default="value")
+        query = QueryDict("other-param=value")
+        result = param.html_default(query)
+        self.assertEqual(result, "value")
+
+    def test_single_valued_parameter_html_default_query(self):
+        param = SingleValuedParameter(name="param", default="value")
+        param.valid_values = {"override": ""}
+        query = QueryDict("param=override")
+        result = param.html_default(query)
+        self.assertEqual(result, "override")
+
+    def test_single_valued_parameter_html_default_query_multiple(self):
+        param = SingleValuedParameter(name="param", default="value")
+        param.valid_values = {"override": "", "override2": ""}
+        query = QueryDict("param=override&param=override2")
+        result = param.html_default(query)
+        self.assertEqual(result, "override2")
+
+    def test_single_valued_parameter_html_default_query_invalid_value(self):
+        param = SingleValuedParameter(name="param", default="value")
+        param.valid_values = dict()
+        query = QueryDict("param=override")
+        result = param.html_default(query)
+        self.assertEqual(result, "value")
+
     def test_multi_valued_parameter_process_requested_values_single_value(self):
         param = MultiValuedParameter()
         param.process_requested_values(["value1"])