Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: data layer API call for ORA block data #2002

Merged
merged 24 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b25008a
feat: wip datalayer
jansenk Jul 26, 2023
37a8f23
feat: start ORA block info serialization
nsprenkle Jul 26, 2023
a1c5a98
feat: add submission config serializer
nsprenkle Jul 26, 2023
dfb40d3
style: black
nsprenkle Jul 26, 2023
aa7823a
feat: add assessment steps config
nsprenkle Jul 28, 2023
9882dcf
feat: add leaderboard config
nsprenkle Jul 31, 2023
22023ba
style: fix pep8 issues
nsprenkle Jul 31, 2023
4aa0717
docs: update docstring
nsprenkle Jul 31, 2023
2ee6f3e
style: fix indent
nsprenkle Jul 31, 2023
ff97a25
fix: default flex grading field when disabled
nsprenkle Aug 1, 2023
5dca627
test: assessment and leaderboard serializer
nsprenkle Aug 1, 2023
0a51e17
test: add tests for rubrics
nsprenkle Aug 2, 2023
62a43b9
test: add tests for submissions
nsprenkle Aug 2, 2023
9ef2539
chore: remove context and fix pylint issue
nsprenkle Aug 2, 2023
0287548
style: convert output to camelCase
nsprenkle Aug 2, 2023
a43873d
refactor: create IsRequired field
nsprenkle Aug 2, 2023
305ed83
test: update rubric criteria tests
nsprenkle Aug 2, 2023
1c4d25c
refactor: update assessment step names
nsprenkle Aug 2, 2023
e6c6de2
refactor: common assessment step serializer
nsprenkle Aug 2, 2023
2db062d
refactor: remove non-reused mixins
nsprenkle Aug 2, 2023
99177f6
style: fix indent issue
nsprenkle Aug 2, 2023
31cd21b
style: fix indent issue (for real)
nsprenkle Aug 2, 2023
890bfe1
fix: update incorrect fields to match contract
nsprenkle Aug 4, 2023
1fcd34f
chore: bump version to 5.2.2
nsprenkle Aug 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions openassessment/xblock/data_layer/data_layer_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""
Data layer for ORA

XBlock handlers which surface info about an ORA, instead of being tied to views.
"""
from xblock.core import XBlock

from openassessment.xblock.data_layer.serializers import OraBlockInfoSerializer


class DataLayerMixin:
@XBlock.json_handler
def get_block_info(self, data, suffix=""): # pylint: disable=unused-argument
block_info = OraBlockInfoSerializer(self)
return block_info.data

Check warning on line 15 in openassessment/xblock/data_layer/data_layer_mixin.py

View check run for this annotation

Codecov / codecov/patch

openassessment/xblock/data_layer/data_layer_mixin.py#L14-L15

Added lines #L14 - L15 were not covered by tests
214 changes: 214 additions & 0 deletions openassessment/xblock/data_layer/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
from rest_framework.serializers import (
BooleanField,
DateTimeField,
IntegerField,
Serializer,
CharField,
ListField,
SerializerMethodField,
)


class CharListField(ListField):
child = CharField()


class IsRequiredField(BooleanField):
"""
Utility for checking if a field is "required" to reduce repeated code.
"""

def to_representation(self, value):
return value == "required"


class TextResponseConfigSerializer(Serializer):
enabled = SerializerMethodField()
required = IsRequiredField(source="text_response")
editorType = CharField(source="text_response_editor")
allowLatexPreview = BooleanField(source="allow_latex")

def get_enabled(self, block):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish there was a simple way to remove this semi-duplicated code across the few serializers but i think doing that would complicate things more than they need to be

return block.text_response is not None


class FileResponseConfigSerializer(Serializer):
enabled = SerializerMethodField()
required = IsRequiredField(source="file_upload_response")
fileUploadLimit = SerializerMethodField()
allowedExtensions = CharListField(source="get_allowed_file_types_or_preset")
blockedExtensions = CharListField(source="FILE_EXT_BLACK_LIST")
fileTypeDescription = CharField(source="file_upload_type")

def get_enabled(self, block):
return block.file_upload_response is not None

def get_fileUploadLimit(self, block):
if not block.allow_multiple_files:
return 1

Check warning on line 48 in openassessment/xblock/data_layer/serializers.py

View check run for this annotation

Codecov / codecov/patch

openassessment/xblock/data_layer/serializers.py#L48

Added line #L48 was not covered by tests
return block.MAX_FILES_COUNT


class TeamsConfigSerializer(Serializer):
enabled = BooleanField(source="is_team_assignment")
teamsetName = SerializerMethodField()

def get_teamsetName(self, block):
if block.teamset_config is not None:
return block.teamset_config.name


class SubmissionConfigSerializer(Serializer):
startDatetime = DateTimeField(source="submission_start")
endDatetime = DateTimeField(source="submission_due")

textResponseConfig = TextResponseConfigSerializer(source="*")
fileResponseConfig = FileResponseConfigSerializer(source="*")

teamsConfig = TeamsConfigSerializer(source="*")


class RubricFeedbackConfigSerializer(Serializer):
description = CharField(source="rubric_feedback_prompt") # is this this field?
defaultText = CharField(source="rubric_feedback_default_text")


class RubricCriterionOptionSerializer(Serializer):
name = CharField()
label = CharField()
points = IntegerField()
description = CharField(source="explanation")


class RubricCriterionSerializer(Serializer):
name = CharField(source="label")
description = CharField(source="prompt")
feedbackEnabled = SerializerMethodField()
feedbackRequired = IsRequiredField(source="feedback")
options = RubricCriterionOptionSerializer(many=True)

@staticmethod
def _feedback(criterion):
# Feedback is disabled as a default
return criterion.get("feedback", "disabled")

def get_feedbackEnabled(self, criterion):
# Feedback can be specified as optional or required
return self._feedback(criterion) != "disabled"


class RubricConfigSerializer(Serializer):
showDuringResponse = BooleanField(source="show_rubric_during_response")
feedbackConfig = RubricFeedbackConfigSerializer(source="*")
criteria = RubricCriterionSerializer(
many=True, source="rubric_criteria_with_labels"
)


class SelfSettingsSerializer(Serializer):
required = BooleanField(default=True)

startTime = DateTimeField(source="start")
endTime = DateTimeField(source="due")


class PeerSettingsSerializer(Serializer):
required = BooleanField(default=True)

startTime = DateTimeField(source="start")
endTime = DateTimeField(source="due")

minNumberToGrade = IntegerField(source="must_grade")
minNumberToBeGradedBy = IntegerField(source="must_be_graded_by")

enableFlexibleGrading = BooleanField(
source="enable_flexible_grading", required=False
)


class AssessmentStepSettingsSerializer(Serializer):
"""
Generic Assessments step, where we just need to know if the step is
required given the ora.rubric_assessments soruce.
"""

required = BooleanField(default=True)

def _get_step(self, rubric_assessments, step_name):
"""Get the assessment step config for a given step_name"""
for step in rubric_assessments:
if step["name"] == step_name:
return step
return None

def __init__(self, *args, **kwargs):
self.step_name = kwargs.pop("step_name")
return super().__init__(*args, **kwargs)

def to_representation(self, rubric_assessments):
assessment_step = self._get_step(rubric_assessments, self.step_name)

# Special handling for the peer step which includes extra fields
if assessment_step and self.step_name == "peer-assessment":
return PeerSettingsSerializer(assessment_step).data
leangseu-edx marked this conversation as resolved.
Show resolved Hide resolved
elif assessment_step and self.step_name == "self-assessment":
return SelfSettingsSerializer(assessment_step).data

# If we didn't find a step, it is not required
if assessment_step is None:
assessment_step = {"required": False}

return super().to_representation(assessment_step)


class AssessmentStepsSettingsSerializer(Serializer):
training = AssessmentStepSettingsSerializer(
step_name="student-training", source="rubric_assessments"
)
peer = AssessmentStepSettingsSerializer(
step_name="peer-assessment", source="rubric_assessments"
)
# Workaround to allow reserved keyword in serializer key
vars()["self"] = AssessmentStepSettingsSerializer(
step_name="self-assessment", source="rubric_assessments"
)
staff = AssessmentStepSettingsSerializer(
step_name="staff-assessment", source="rubric_assessments"
)


class AssessmentStepsSerializer(Serializer):
order = SerializerMethodField()
settings = AssessmentStepsSettingsSerializer(source="*")

def get_order(self, block):
return [step["name"] for step in block.rubric_assessments]


class LeaderboardConfigSerializer(Serializer):
enabled = SerializerMethodField()
numberOfEntries = IntegerField(source="leaderboard_show")

def get_enabled(self, block):
return block.leaderboard_show > 0


class OraBlockInfoSerializer(Serializer):
"""
Main serializer for statically-defined ORA Block information
"""

title = CharField()
prompts = SerializerMethodField(source="*")
baseAssetUrl = SerializerMethodField(source="*")

submissionConfig = SubmissionConfigSerializer(source="*")
assessmentSteps = AssessmentStepsSerializer(source="*")
rubricConfig = RubricConfigSerializer(source="*")
leaderboardConfig = LeaderboardConfigSerializer(source="*")

def get_baseAssetUrl(self, block):
return block._get_base_url_path_for_course_assets(block.course.id)

Check warning on line 211 in openassessment/xblock/data_layer/serializers.py

View check run for this annotation

Codecov / codecov/patch

openassessment/xblock/data_layer/serializers.py#L211

Added line #L211 was not covered by tests

def get_prompts(self, block):
return [prompt["description"] for prompt in block.prompts]
Loading
Loading