Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The following XBlocks have been migrated here from their respective repositories

- [AudioXBlock](src/audio/README.md) migrated [from](https://github.com/openedx-unsupported/AudioXBlock)
- [FeedbackXBlock](src/feedback/README.rst) migrated [from](https://github.com/openedx/FeedbackXBlock)
- [Free Text Response XBlock](src/freetextresponse/README.rst) migrated [from](https://github.com/openedx/xblock-free-text-response)
- [xblock-image-modal](src/imagemodal/README.rst) migrated [from](https://github.com/openedx-unsupported/xblock-image-modal)
- [xblock-qualtrics-survey](src/qualtricssurvey/README.rst) migrated [from](https://github.com/openedx-unsupported/xblock-qualtrics-survey)
- [xblock-sql-grader](src/sql_grader/README.rst) migrated [from](https://github.com/openedx/xblock-sql-grader)
Expand Down
10 changes: 6 additions & 4 deletions docs/how-tos/migrating-process-operator-guidelines.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ Step-by-Step Migration

Check your requirements files and installed environment::

pip show audio-xblock feedback-xblock openedx-xblock-image-modal \
xblock_qualtrics_survey xblock-sql-grader xblock-submit-and-compare
pip show audio-xblock feedback-xblock xblock-free-text-response \
openedx-xblock-image-modal xblock_qualtrics_survey \
xblock-sql-grader xblock-submit-and-compare

Make a note of which packages are present so you know what to remove.

Expand All @@ -56,8 +57,9 @@ Make a note of which packages are present so you know what to remove.
Remove every standalone package you identified. You can remove them all at
once::

pip uninstall -y audio-xblock feedback-xblock openedx-xblock-image-modal \
xblock_qualtrics_survey xblock-sql-grader xblock-submit-and-compare
pip uninstall -y audio-xblock feedback-xblock xblock-free-text-response \
openedx-xblock-image-modal xblock_qualtrics_survey \
xblock-sql-grader xblock-submit-and-compare

3. Install xblocks-extra
~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
8 changes: 5 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@ dev = [
]
test = [
"coverage",
"ddt",
"edx-lint",
"edx-opaque-keys",
"edx-opaque-keys",
"lazy",
"mock",
"pytest-cov",
"pytest-django",
Expand All @@ -68,6 +69,7 @@ docs = [
[project.entry-points."xblock.v1"]
audio = "audio:AudioXBlock"
feedback = "feedback.feedback:FeedbackXBlock"
freetextresponse = "freetextresponse.xblocks:FreeTextResponse"
imagemodal = "imagemodal.xblocks:ImageModal"
qualtricssurvey = "qualtricssurvey.xblocks:QualtricsSurvey"
sql_grader = "sql_grader:SqlGrader"
Expand Down Expand Up @@ -105,6 +107,7 @@ exclude = ["tests*", "*.tests", "*.tests.*"]
[tool.ruff]
line-length = 120
target-version = "py312"
src = ["src"]

[tool.ruff.lint]
select = [
Expand All @@ -122,7 +125,6 @@ ignore = [
]

[tool.ruff.lint.isort]
known-first-party = []
known-third-party = ["django", "xblock"]
Comment thread
irfanuddinahmad marked this conversation as resolved.

[tool.ruff.format]
Expand All @@ -145,7 +147,7 @@ DJANGO_SETTINGS_MODULE = "feedback.settings.test"
branch = true
# Source paths will be added as xblocks are migrated
# Example: source = ["foo_xblock", "bar_xblock"]
source = ["sql_grader"]
source = ["src"]
omit = [
Comment thread
irfanuddinahmad marked this conversation as resolved.
"*/tests/*",
"*/migrations/*",
Expand Down
1 change: 1 addition & 0 deletions src/feedback/settings/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"django.contrib.auth",
"django.contrib.contenttypes",
"feedback",
"freetextresponse",
"workbench",
Comment thread
irfanuddinahmad marked this conversation as resolved.
]

Expand Down
48 changes: 48 additions & 0 deletions src/freetextresponse/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Free Text Response XBlock
================================

XBlock to capture a free-text response.

|badge-ci|
|badge-coveralls|

This package provides an XBlock for use with the EdX Platform and makes
it possible for instructors to create questions that expect a
free-text response.


Installation
------------


System Administrator
~~~~~~~~~~~~~~~~~~~~

To install the XBlock on your platform,
add the following to your `requirements.txt` file:

xblock-free-text-response

You'll also need to add this to your `INSTALLED_APPS`:

freetextresponse


Course Staff
~~~~~~~~~~~~

To install the XBlock in your course,
access your `Advanced Module List`:

Settings -> Advanced Settings -> Advanced Module List

and add the following:

freetextresponse



.. |badge-coveralls| image:: https://coveralls.io/repos/github/Stanford-Online/xblock-free-text-response/badge.svg?branch=master
:target: https://coveralls.io/github/Stanford-Online/xblock-free-text-response?branch=master
.. |badge-ci| image:: https://github.com/openedx/xblock-free-text-response/workflows/Python%20CI/badge.svg?branch=master
:target: https://github.com/openedx/xblock-free-text-response/actions?query=workflow%3A%22Python+CI%22
5 changes: 5 additions & 0 deletions src/freetextresponse/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
This is an XBlock that accepts a free-text response from students.
Instructors can specify a list of phrases, of which one must be
present in order for the student to receive credit.
"""
8 changes: 8 additions & 0 deletions src/freetextresponse/conf/locale/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Configuration for i18n workflow.

locales:
- en # English - Source Language

# The locales used for fake-accented English, for testing.
dummy_locales:
- eo # Esperanto
3 changes: 3 additions & 0 deletions src/freetextresponse/mixins/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Mixin behavior to XBlocks
"""
34 changes: 34 additions & 0 deletions src/freetextresponse/mixins/dates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
Extend XBlocks with datetime helpers
"""

import datetime


# pylint: disable=too-few-public-methods
class EnforceDueDates:
"""
xBlock Mixin to allow xblocks to check the due date
(taking the graceperiod into account) of the
subsection in which they are placed
"""

def is_past_due(self):
"""
Determine if component is past-due
"""
# These values are pulled from platform.
# They are defaulted to None for tests.
due = getattr(self, "due", None)
graceperiod = getattr(self, "graceperiod", None)
# Calculate the current UTC datetime as a timezone-naive object for comparison.
now = datetime.datetime.now(datetime.UTC).replace(tzinfo=None)
if due is not None:
# Remove timezone information from platform provided due date.
# Dates are stored as UTC timezone aware objects on platform.
due = due.replace(tzinfo=None)
if graceperiod is not None:
# Compare the datetime objects (both have to be timezone naive)
due = due + graceperiod
return now > due
return False
98 changes: 98 additions & 0 deletions src/freetextresponse/mixins/fragment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""
Mixin fragment/html behavior into XBlocks

Note: We should resume test coverage for all lines in this file once
split into its own library.
"""

from web_fragments.fragment import Fragment
from xblock.core import XBlock

try:
from xblock.utils.resources import ResourceLoader
except ModuleNotFoundError: # pragma: no cover
from xblockutils.resources import ResourceLoader

loader = ResourceLoader(__name__)
Comment thread
irfanuddinahmad marked this conversation as resolved.


class XBlockFragmentBuilderMixin:
"""
Create a default XBlock fragment builder
"""

static_css = [
"view.css",
]
static_js = [
"view.js",
]
static_js_init = None
template = "view.html"

def provide_context(self, context): # pragma: no cover
"""
Build a context dictionary to render the student view

This should generally be overridden by child classes.
"""
context = context or {}
context = dict(context)
return context

@XBlock.supports("multi_device")
def student_view(self, context=None):
"""
Build the fragment for the default student view
"""
template = self.template
context = self.provide_context(context)
static_css = self.static_css or []
static_js = self.static_js or []
js_init = self.static_js_init
fragment = self.build_fragment(
template=template,
context=context,
css=static_css,
js=static_js,
js_init=js_init,
)
return fragment

# pylint: disable=too-many-positional-arguments
def build_fragment(
self,
template="",
context=None,
css=None,
js=None,
js_init=None,
):
"""
Creates a fragment for display.
"""
context = context or {}
css = css or []
js = js or []
rendered_template = ""
if template: # pragma: no cover
template = "templates/" + template
rendered_template = self.loader.render_django_template(
Comment thread
irfanuddinahmad marked this conversation as resolved.
template,
context=context,
i18n_service=self._i18n_service(),
)
fragment = Fragment(rendered_template)
for item in css:
if item.startswith("/"):
url = item
else:
url = self.runtime.local_resource_url(self, "public/" + item)
fragment.add_css_url(url)
for item in js:
item = "public/" + item
url = self.runtime.local_resource_url(self, item)
fragment.add_javascript_url(url)
Comment thread
irfanuddinahmad marked this conversation as resolved.
if js_init: # pragma: no cover
fragment.initialize_js(js_init)
return fragment
30 changes: 30 additions & 0 deletions src/freetextresponse/mixins/i18n.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"""
Mixin i18n logic
"""


class I18nXBlockMixin:
"""
Make an XBlock translation-aware
"""

def _i18n_service(self):
"""
Provide the XBlock runtime's i18n service
"""
service = self.runtime.service(self, "i18n")
return service

def gettext(self, text):
"""
Call gettext from the XBlock i18n service
"""
text = self._i18n_service().gettext(text)
return text

def ngettext(self, *args, **kwargs):
"""
Call ngettext from the XBlock i18n service
"""
text = self._i18n_service().ngettext(*args, **kwargs)
return text
45 changes: 45 additions & 0 deletions src/freetextresponse/mixins/scenario.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""
Mixin workbench behavior into XBlocks
"""

try:
from xblock.utils.resources import ResourceLoader
except ModuleNotFoundError:
from xblockutils.resources import ResourceLoader


loader = ResourceLoader(__name__)


def _parse_title(title):
"""
Parse the title of the scenario
"""
title = title.split("-")
title = " ".join(title)
Comment thread
irfanuddinahmad marked this conversation as resolved.
return title.title()


def _parse_scenarios(scenarios):
"""
Parse the content of the scenario files
"""
parsed_scenarios = []
for scenario in scenarios:
title = _parse_title(scenario[0])
parsed_scenarios.append((title, scenario[1]))
return parsed_scenarios


class XBlockWorkbenchMixin:
"""
Provide a default test workbench for the XBlock
"""

@classmethod
def workbench_scenarios(cls):
"""
Gather scenarios to be displayed in the workbench
"""
scenarios = loader.load_scenarios_from_path("../scenarios")
return _parse_scenarios(scenarios)
21 changes: 21 additions & 0 deletions src/freetextresponse/mixins/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
Extend XBlock with additional user functionality
"""


# pylint: disable=too-few-public-methods
class MissingDataFetcherMixin:
"""
The mixin used for getting the student_id of the current user.
"""

def get_student_id(self):
"""
Get the student id.
"""
if hasattr(self, "xmodule_runtime"):
student_id = self.xmodule_runtime.anonymous_student_id
# pylint:disable=E1101
else:
student_id = str(self.scope_ids.user_id or "")
return student_id
Loading