feat(numericalInput): endpoint added to validate a numerical input (#37677)

To be used in the visual Problem editor to validate numeric input. 

Part of: https://github.com/openedx/frontend-app-authoring/issues/1680
This commit is contained in:
Jesus Balderrama
2025-12-16 16:23:31 -06:00
committed by GitHub
parent a032955243
commit 2637cc6b0f
5 changed files with 79 additions and 11 deletions

View File

@@ -0,0 +1,17 @@
"""
Serializers for the contentstore v2 utils views module.
This module contains DRF serializers for different utils like validations.
"""
from rest_framework import serializers
class NumericalInputValidationRequestSerializer(serializers.Serializer):
formula = serializers.CharField()
class NumericalInputValidationReponseSerializer(serializers.Serializer):
preview = serializers.CharField()
is_valid = serializers.BooleanField()
error = serializers.CharField(allow_null=True)

View File

@@ -3,7 +3,7 @@
from django.conf import settings
from django.urls import path, re_path
from cms.djangoapps.contentstore.rest_api.v2.views import downstreams, home
from cms.djangoapps.contentstore.rest_api.v2.views import downstreams, home, utils
app_name = "v2"
@@ -33,4 +33,8 @@ urlpatterns = [
downstreams.SyncFromUpstreamView.as_view(),
name="sync_from_upstream"
),
re_path(
'^validate/numerical-input/$',
utils.NumericalInputValidationView.as_view(),
name='numerical_input_validation'),
]

View File

@@ -0,0 +1,22 @@
"""
Common utilities for V2 APIs.
"""
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from rest_framework import permissions
from cms.djangoapps.contentstore.rest_api.v2.serializers.utils import NumericalInputValidationRequestSerializer
from xmodule.capa.inputtypes import preview_numeric_input
class NumericalInputValidationView(GenericAPIView):
"""Class in charge of NumericalInputValidations"""
permission_classes = (permissions.IsAuthenticated,)
serializer_class = NumericalInputValidationRequestSerializer
def post(self, request):
"""function to validate a math expression (formula) and return of the numeric input is valid or not"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
formula = serializer.validated_data['formula']
result = preview_numeric_input(formula)
return Response(result, status=200)

View File

@@ -49,9 +49,9 @@ from datetime import datetime
import html5lib
import nh3
import pyparsing
import six
from calc.preview import latex_preview
import pyparsing
from chem import chemcalc
from lxml import etree
@@ -1300,22 +1300,47 @@ class FormulaEquationInput(InputTypeBase):
result["request_start"] = int(get.get("request_start", 0))
# TODO add references to valid variables and functions
# At some point, we might want to mark invalid variables as red
# or something, and this is where we would need to pass those in.
try:
# TODO add references to valid variables and functions
# At some point, we might want to mark invalid variables as red
# or something, and this is where we would need to pass those in.
result["preview"] = latex_preview(formula)
numeric_result = preview_numeric_input(formula)
# Map results into the correct format
result["preview"] = numeric_result["preview"]
if numeric_result["error"]:
result["error"] = numeric_result["error"]
# if formula is invalid return formula
if not numeric_result["is_valid"]:
result["formula"] = formula
except pyparsing.ParseException:
result["error"] = _("Sorry, couldn't parse formula")
result["formula"] = formula
result['error'] = _("Sorry, couldn't parse formula")
result['formula'] = formula
except Exception: # lint-amnesty, pylint: disable=broad-except
# this is unexpected, so log
log.warning("Error while previewing formula", exc_info=True)
result["error"] = _("Error while rendering preview")
return result
return result
def preview_numeric_input(formula):
"""
Handles numeric validations, validates that the formula provided is a valid formula.
"""
result = {'preview': '', 'is_valid': True, 'error': ''}
try:
result['preview'] = latex_preview(formula)
except pyparsing.ParseException:
result["error"] = "Sorry, couldn't parse formula"
result['is_valid'] = False
return result
except Exception: # pylint: disable=broad-exception-caught
log.warning("Error while previewing formula", exc_info=True)
result['error'] = "Error while rendering preview"
result['is_valid'] = False
return result
# -----------------------------------------------------------------------------

View File

@@ -1361,7 +1361,7 @@ class FormulaEquationTest(unittest.TestCase):
With parse errors, FormulaEquationInput should give an error message
"""
# Simulate answering a problem that raises the exception
with patch("xmodule.capa.inputtypes.latex_preview") as mock_preview:
with patch('xmodule.capa.inputtypes.preview_numeric_input') as mock_preview:
mock_preview.side_effect = ParseException("Oopsie")
response = self.the_input.handle_ajax(
"preview_formcalc",
@@ -1379,7 +1379,7 @@ class FormulaEquationTest(unittest.TestCase):
"""
With other errors, test that FormulaEquationInput also logs it
"""
with patch("xmodule.capa.inputtypes.latex_preview") as mock_preview:
with patch('xmodule.capa.inputtypes.preview_numeric_input') as mock_preview:
mock_preview.side_effect = Exception()
response = self.the_input.handle_ajax(
"preview_formcalc",