chore: upstream ProblemBlock exceptions and shared utilities to XBlock (#37806)

* fix: add support for xblock 5.3.0
This commit is contained in:
Irtaza Akram
2026-01-07 13:30:53 +05:00
committed by GitHub
parent 1ffa136d45
commit 33cc1a94ba
38 changed files with 59 additions and 919 deletions

View File

@@ -8,12 +8,12 @@ import logging
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
from django.core.management.base import BaseCommand, CommandError
from xblock.fields import Date
from cms.djangoapps.contentstore.management.commands.utils import user_from_str
from cms.djangoapps.contentstore.views.course import create_new_course_in_store
from openedx.core.djangoapps.credit.models import CreditProvider
from xmodule.course_block import CourseFields # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.fields import Date # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.exceptions import DuplicateCourseError # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.tabs import CourseTabList # lint-amnesty, pylint: disable=wrong-import-order

View File

@@ -6,6 +6,7 @@ from rest_framework import serializers
from rest_framework.fields import Field as SerializerField
from xblock.fields import (
Boolean,
Date,
DateTime,
Dict,
Field as XBlockField,
@@ -15,7 +16,6 @@ from xblock.fields import (
String,
)
from xmodule.course_block import CourseFields, EmailString
from xmodule.fields import Date
from cms.djangoapps.models.settings.course_metadata import CourseMetadata

View File

@@ -27,6 +27,7 @@ from edx_toggles.toggles.testutils import override_waffle_flag
from milestones.models import MilestoneRelationshipType
from milestones.tests.utils import MilestonesTestCaseMixin
from pytz import UTC
from xblock.fields import Date
from cms.djangoapps.contentstore import toggles
from cms.djangoapps.contentstore.utils import reverse_course_url, reverse_usage_url
@@ -49,7 +50,6 @@ from openedx.core.djangoapps.discussions.config.waffle import (
)
from openedx.core.djangoapps.models.course_details import CourseDetails
from openedx.core.lib.teams_config import TeamsConfig
from xmodule.fields import Date # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.tests.factories import CourseFactory # lint-amnesty, pylint: disable=wrong-import-order

View File

@@ -15,12 +15,12 @@ from opaque_keys.edx.locator import LibraryContainerLocator
from rest_framework.request import Request
from web_fragments.fragment import Fragment
from xblock.django.request import django_to_webob_request, webob_to_django_response
from xblock.exceptions import NoSuchHandlerError
from xblock.exceptions import NoSuchHandlerError, NotFoundError, ProcessingError
from xblock.runtime import KvsFieldData
from openedx.core.djangoapps.video_config.services import VideoConfigService
from xmodule.contentstore.django import contentstore
from xmodule.exceptions import NotFoundError, ProcessingError
from xmodule.exceptions import NotFoundError as XModuleNotFoundError
from xmodule.modulestore.django import XBlockI18nService, modulestore
from xmodule.partitions.partitions_service import PartitionService
from xmodule.services import SettingsService, TeamsConfigurationService
@@ -81,7 +81,7 @@ def preview_handler(request, usage_key_string, handler, suffix=''):
log.exception("XBlock %s attempted to access missing handler %r", instance, handler)
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from
except NotFoundError:
except (XModuleNotFoundError, NotFoundError):
log.exception("Module indicating to user that request doesn't exist")
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from

View File

@@ -8,9 +8,9 @@ import json
from json.encoder import JSONEncoder
from opaque_keys.edx.locations import Location
from xblock.fields import Date
from openedx.core.djangoapps.models.course_details import CourseDetails
from xmodule.fields import Date # lint-amnesty, pylint: disable=wrong-import-order
from .course_grading import CourseGradingModel

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from django.contrib.auth import get_user_model
from opaque_keys.edx.keys import CourseKey
from openedx.core.lib.grade_utils import round_away_from_zero
from xmodule.graders import ShowCorrectness
from xblock.scorable import ShowCorrectness
from datetime import datetime, timezone
from lms.djangoapps.courseware.courses import get_course_blocks_completion_summary

View File

@@ -5,12 +5,12 @@ Tests for the Python APIs exposed by the Progress API of the Course Home API app
from unittest.mock import patch
from django.test import TestCase
from xblock.scorable import ShowCorrectness
from lms.djangoapps.course_home_api.progress.api import (
calculate_progress_for_learner_in_course,
aggregate_assignment_type_grade_summary,
)
from xmodule.graders import ShowCorrectness
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace

View File

@@ -11,9 +11,9 @@ from opaque_keys.edx.keys import CourseKey
from rest_framework.generics import RetrieveAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from xblock.scorable import ShowCorrectness
from xmodule.modulestore.django import modulestore
from xmodule.graders import ShowCorrectness
from common.djangoapps.student.models import CourseEnrollment
from lms.djangoapps.course_home_api.progress.serializers import ProgressTabSerializer
from lms.djangoapps.course_home_api.progress.api import aggregate_assignment_type_grade_summary

View File

@@ -37,7 +37,7 @@ from rest_framework.exceptions import APIException
from typing import Callable, TYPE_CHECKING
from web_fragments.fragment import Fragment
from xblock.django.request import django_to_webob_request, webob_to_django_response
from xblock.exceptions import NoSuchHandlerError, NoSuchViewError
from xblock.exceptions import NoSuchHandlerError, NoSuchViewError, NotFoundError, ProcessingError
from xblock.reference.plugins import FSService
from xblock.runtime import KvsFieldData
@@ -45,7 +45,7 @@ from lms.djangoapps.teams.services import TeamsService
from openedx.core.djangoapps.video_config.services import VideoConfigService
from openedx.core.lib.xblock_services.call_to_action import CallToActionService
from xmodule.contentstore.django import contentstore
from xmodule.exceptions import NotFoundError, ProcessingError
from xmodule.exceptions import NotFoundError as XModuleNotFoundError
from xmodule.library_tools import LegacyLibraryToolsService
from xmodule.modulestore.django import XBlockI18nService, modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
@@ -966,7 +966,7 @@ def _invoke_xblock_handler(request, course_id, usage_id, handler, suffix, course
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from
# If we can't find the block, respond with a 404
except NotFoundError:
except (XModuleNotFoundError, NotFoundError):
log.exception("Module indicating to user that request doesn't exist")
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from

View File

@@ -8,7 +8,7 @@ from django.db import migrations
def move_overrides_to_edx_when(apps, schema_editor):
from xmodule.fields import Date
from xblock.fields import Date
from edx_when import api
date_field = Date()
StudentFieldOverride = apps.get_model('courseware', 'StudentFieldOverride')

View File

@@ -33,9 +33,9 @@ from rest_framework.test import APIClient
from web_fragments.fragment import Fragment
from xblock.core import XBlock
from xblock.fields import Scope, String
from xblock.scorable import ShowCorrectness
from xmodule.capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory
from xmodule.data import CertificatesDisplayBehaviors
from xmodule.graders import ShowCorrectness
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import CourseUserType, ModuleStoreTestCase, SharedModuleStoreTestCase

View File

@@ -8,11 +8,12 @@ from collections import OrderedDict
from datetime import datetime, timezone
from logging import getLogger
from lazy import lazy
from xblock.scorable import ShowCorrectness
from lms.djangoapps.grades.models import BlockRecord, PersistentSubsectionGrade
from lms.djangoapps.grades.scores import compute_percent, get_score, possibly_scored
from xmodule import block_metadata_utils, graders # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.graders import AggregatedScore, ShowCorrectness # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.graders import AggregatedScore # lint-amnesty, pylint: disable=wrong-import-order
log = getLogger(__name__)

View File

@@ -31,6 +31,7 @@ from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locator import UsageKey
from pytz import UTC
from testfixtures import LogCapture
from xblock.fields import Date
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
@@ -102,7 +103,6 @@ from openedx.core.djangoapps.user_api.preferences.api import delete_user_prefere
from openedx.core.lib.teams_config import TeamsConfig
from openedx.core.lib.xblock_utils import grade_histogram
from openedx.features.course_experience import RELATIVE_DATES_FLAG
from xmodule.fields import Date
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import (
TEST_DATA_SPLIT_MODULESTORE,

View File

@@ -17,7 +17,7 @@ from edx_when.api import get_dates_for_course, set_dates_for_course
from edx_when.field_data import DateLookupFieldData
from opaque_keys.edx.keys import CourseKey
from pytz import UTC
from xmodule.fields import Date
from xblock.fields import Date
from xmodule.modulestore.tests.django_utils import (
TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase, SharedModuleStoreTestCase,
)

View File

@@ -10,13 +10,13 @@ from django.urls import reverse
from urllib.parse import quote_plus
from django.utils.translation import gettext as _
from pytz import UTC
from xblock.scorable import ShowCorrectness
from common.djangoapps.course_modes.models import CourseMode
from lms.djangoapps.certificates.data import CertificateStatuses
from lms.djangoapps.grades.api import constants as grades_constants
from openedx.core.djangolib.markup import HTML, Text
from openedx.features.enterprise_support.utils import get_enterprise_learner_generic_name
from xmodule.graders import ShowCorrectness
%>
<%

View File

@@ -7,11 +7,11 @@ import logging
import re
from django.conf import settings
from xblock.fields import Date
from openedx.core.djangolib.markup import HTML
from openedx.core.lib.courses import course_image_url
from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.fields import Date # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order

View File

@@ -25,8 +25,23 @@ from lxml import etree
from pytz import utc
from web_fragments.fragment import Fragment
from xblock.core import XBlock
from xblock.fields import Boolean, Dict, Float, Integer, List, Scope, String, XMLString
from xblock.scorable import ScorableXBlockMixin, Score
from xblock.exceptions import NotFoundError, ProcessingError
from xblock.fields import (
Boolean,
Date,
Dict,
Float,
Integer,
List,
ListScoreField,
Scope,
ScoreField,
String,
Timedelta,
XMLString
)
from xblock.progress import Progress
from xblock.scorable import ScorableXBlockMixin, Score, ShowCorrectness
from xblocks_contrib.problem import ProblemBlock as _ExtractedProblemBlock
from common.djangoapps.xblock_django.constants import (
@@ -42,8 +57,6 @@ from xmodule.capa.responsetypes import LoncapaProblemError, ResponseError, Stude
from xmodule.capa.util import convert_files_to_filenames, get_inner_html_from_xpath
from xmodule.contentstore.django import contentstore
from xmodule.editing_block import EditingMixin
from xmodule.exceptions import NotFoundError, ProcessingError
from xmodule.graders import ShowCorrectness
from xmodule.raw_block import RawMixin
from xmodule.util.builtin_assets import add_css_to_fragment, add_webpack_js_to_fragment
from xmodule.util.sandboxing import SandboxService
@@ -51,8 +64,6 @@ from xmodule.x_module import ResourceTemplates, XModuleMixin, XModuleToXBlockMix
from xmodule.xml_block import XmlMixin
from .capa.xqueue_interface import XQueueService
from .fields import Date, ListScoreField, ScoreField, Timedelta
from .progress import Progress
log = logging.getLogger("edx.courseware")

View File

@@ -16,7 +16,7 @@ from lazy import lazy
from lxml import etree
from path import Path as path
from pytz import utc
from xblock.fields import Boolean, Dict, Float, Integer, List, Scope, String
from xblock.fields import Boolean, Date, Dict, Float, Integer, List, Scope, String
from openedx.core.djangoapps.video_pipeline.models import VideoUploadsEnabledByDefault
from openedx.core.djangoapps.video_config.sharing import (
COURSE_VIDEO_SHARING_ALL_VIDEOS,
@@ -32,7 +32,6 @@ from xmodule.graders import grader_from_conf
from xmodule.seq_block import SequenceBlock
from xmodule.tabs import CourseTabList, InvalidTabsException
from .fields import Date
from .modulestore.exceptions import InvalidProctoringProvider
log = logging.getLogger(__name__)

View File

@@ -7,14 +7,6 @@ class NotFoundError(Exception):
pass
class ProcessingError(Exception):
'''
An error occurred while processing a request to the XModule.
For example: if an exception occurs while checking a capa problem.
'''
pass # lint-amnesty, pylint: disable=unnecessary-pass
class InvalidVersionError(Exception):
"""
Tried to save an item with a location that a store cannot support (e.g., draft version

View File

@@ -1,324 +0,0 @@
# lint-amnesty, pylint: disable=missing-module-docstring
import datetime
import logging
import re
import time
import dateutil.parser
from pytz import UTC
from xblock.fields import JSONField, List
from xblock.scorable import Score
log = logging.getLogger(__name__)
class Date(JSONField):
"""
Date fields know how to parse and produce json (iso) compatible formats. Converts to tz aware datetimes.
"""
# See note below about not defaulting these
CURRENT_YEAR = datetime.datetime.now(UTC).year
PREVENT_DEFAULT_DAY_MON_SEED1 = datetime.datetime(CURRENT_YEAR, 1, 1, tzinfo=UTC)
PREVENT_DEFAULT_DAY_MON_SEED2 = datetime.datetime(CURRENT_YEAR, 2, 2, tzinfo=UTC)
MUTABLE = False
def _parse_date_wo_default_month_day(self, field):
"""
Parse the field as an iso string but prevent dateutils from defaulting the day or month while
allowing it to default the other fields.
"""
# It's not trivial to replace dateutil b/c parsing timezones as Z, +03:30, -400 is hard in python
# however, we don't want dateutil to default the month or day (but some tests at least expect
# us to default year); so, we'll see if dateutil uses the defaults for these the hard way
result = dateutil.parser.parse(field, default=self.PREVENT_DEFAULT_DAY_MON_SEED1)
result_other = dateutil.parser.parse(field, default=self.PREVENT_DEFAULT_DAY_MON_SEED2)
if result != result_other:
log.warning(f"Field {self.name} is missing month or day")
return None
if result.tzinfo is None:
result = result.replace(tzinfo=UTC)
return result
def from_json(self, field): # lint-amnesty, pylint: disable=arguments-differ
"""
Parse an optional metadata key containing a time: if present, complain
if it doesn't parse.
Return None if not present or invalid.
"""
if field is None:
return field
elif field == "":
return None
elif isinstance(field, str):
return self._parse_date_wo_default_month_day(field)
elif isinstance(field, int) or isinstance( # lint-amnesty, pylint: disable=consider-merging-isinstance
field, float
):
return datetime.datetime.fromtimestamp(field / 1000, UTC)
elif isinstance(field, time.struct_time):
return datetime.datetime.fromtimestamp(time.mktime(field), UTC)
elif isinstance(field, datetime.datetime):
return field
else:
msg = "Field {} has bad value '{}'".format(self.name, field)
raise TypeError(msg)
def to_json(self, value):
"""
Convert a time struct to a string
"""
if value is None:
return None
if isinstance(value, time.struct_time):
# struct_times are always utc
return time.strftime("%Y-%m-%dT%H:%M:%SZ", value)
elif isinstance(value, datetime.datetime):
if value.tzinfo is None or value.utcoffset().total_seconds() == 0:
if value.year < 1900:
# strftime doesn't work for pre-1900 dates, so use
# isoformat instead
return value.isoformat()
# isoformat adds +00:00 rather than Z
return value.strftime("%Y-%m-%dT%H:%M:%SZ")
else:
return value.isoformat()
else:
raise TypeError(f"Cannot convert {value!r} to json")
enforce_type = from_json
TIMEDELTA_REGEX = re.compile(
r"^((?P<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?) hour(?:s?))?(\s)?((?P<minutes>\d+?) minute(?:s)?)?(\s)?((?P<seconds>\d+?) second(?:s)?)?$" # lint-amnesty, pylint: disable=line-too-long
)
class Timedelta(JSONField): # lint-amnesty, pylint: disable=missing-class-docstring
# Timedeltas are immutable, see http://docs.python.org/2/library/datetime.html#available-types
MUTABLE = False
def from_json(self, time_str): # lint-amnesty, pylint: disable=arguments-differ
"""
time_str: A string with the following components:
<D> day[s] (optional)
<H> hour[s] (optional)
<M> minute[s] (optional)
<S> second[s] (optional)
Returns a datetime.timedelta parsed from the string
"""
if time_str is None:
return None
if isinstance(time_str, datetime.timedelta):
return time_str
parts = TIMEDELTA_REGEX.match(time_str)
if not parts:
return
parts = parts.groupdict()
time_params = {}
for name, param in parts.items():
if param:
time_params[name] = int(param)
return datetime.timedelta(**time_params)
def to_json(self, value):
if value is None:
return None
values = []
for attr in ("days", "hours", "minutes", "seconds"):
cur_value = getattr(value, attr, 0)
if cur_value > 0:
values.append("%d %s" % (cur_value, attr))
return " ".join(values)
def enforce_type(self, value):
"""
Ensure that when set explicitly the Field is set to a timedelta
"""
if isinstance(value, datetime.timedelta) or value is None:
return value
return self.from_json(value)
class RelativeTime(JSONField):
"""
Field for start_time and end_time video block properties.
It was decided, that python representation of start_time and end_time
should be python datetime.timedelta object, to be consistent with
common time representation.
At the same time, serialized representation should be "HH:MM:SS"
This format is convenient to use in XML (and it is used now),
and also it is used in frond-end studio editor of video block as format
for start and end time fields.
In database we previously had float type for start_time and end_time fields,
so we are checking it also.
Python object of RelativeTime is datetime.timedelta.
JSONed representation of RelativeTime is "HH:MM:SS"
"""
# Timedeltas are immutable, see http://docs.python.org/2/library/datetime.html#available-types
MUTABLE = False
@classmethod
def isotime_to_timedelta(cls, value):
"""
Validate that value in "HH:MM:SS" format and convert to timedelta.
Validate that user, that edits XML, sets proper format, and
that max value that can be used by user is "23:59:59".
"""
try:
obj_time = time.strptime(value, "%H:%M:%S")
except ValueError as e:
raise ValueError( # lint-amnesty, pylint: disable=raise-missing-from
"Incorrect RelativeTime value {!r} was set in XML or serialized. "
"Original parse message is {}".format(value, str(e))
)
return datetime.timedelta(hours=obj_time.tm_hour, minutes=obj_time.tm_min, seconds=obj_time.tm_sec)
def from_json(self, value):
"""
Convert value is in 'HH:MM:SS' format to datetime.timedelta.
If not value, returns 0.
If value is float (backward compatibility issue), convert to timedelta.
"""
if not value:
return datetime.timedelta(seconds=0)
if isinstance(value, datetime.timedelta):
return value
# We've seen serialized versions of float in this field
if isinstance(value, float):
return datetime.timedelta(seconds=value)
if isinstance(value, str):
return self.isotime_to_timedelta(value)
msg = f"RelativeTime Field {self.name} has bad value '{value!r}'"
raise TypeError(msg)
def to_json(self, value):
"""
Convert datetime.timedelta to "HH:MM:SS" format.
If not value, return "00:00:00"
Backward compatibility: check if value is float, and convert it. No exceptions here.
If value is not float, but is exceed 23:59:59, raise exception.
"""
if not value:
return "00:00:00"
if isinstance(value, float): # backward compatibility
value = min(value, 86400)
return self.timedelta_to_string(datetime.timedelta(seconds=value))
if isinstance(value, datetime.timedelta):
if value.total_seconds() > 86400: # sanity check
raise ValueError(
"RelativeTime max value is 23:59:59=86400.0 seconds, "
"but {} seconds is passed".format(value.total_seconds())
)
return self.timedelta_to_string(value)
raise TypeError(f"RelativeTime: cannot convert {value!r} to json")
def timedelta_to_string(self, value):
"""
Makes first 'H' in str representation non-optional.
str(timedelta) has [H]H:MM:SS format, which is not suitable
for front-end (and ISO time standard), so we force HH:MM:SS format.
"""
stringified = str(value)
if len(stringified) == 7:
stringified = "0" + stringified
return stringified
def enforce_type(self, value):
"""
Ensure that when set explicitly the Field is set to a timedelta
"""
if isinstance(value, datetime.timedelta) or value is None:
return value
return self.from_json(value)
class ScoreField(JSONField):
"""
Field for blocks that need to store a Score. XBlocks that implement
the ScorableXBlockMixin may need to store their score separately
from their problem state, specifically for use in staff override
of problem scores.
"""
MUTABLE = False
def from_json(self, value):
if value is None:
return value
if isinstance(value, Score):
return value
if set(value) != {"raw_earned", "raw_possible"}:
raise TypeError("Scores must contain only a raw earned and raw possible value. Got {}".format(set(value)))
raw_earned = value["raw_earned"]
raw_possible = value["raw_possible"]
if raw_possible < 0:
raise ValueError(
"Error deserializing field of type {}: Expected a positive number for raw_possible, got {}.".format(
self.display_name,
raw_possible,
)
)
if not (0 <= raw_earned <= raw_possible): # lint-amnesty, pylint: disable=superfluous-parens
raise ValueError(
"Error deserializing field of type {}: Expected raw_earned between 0 and {}, got {}.".format(
self.display_name, raw_possible, raw_earned
)
)
return Score(raw_earned, raw_possible)
enforce_type = from_json
class ListScoreField(ScoreField, List):
"""
Field for blocks that need to store a list of Scores.
"""
MUTABLE = True
_default = []
def from_json(self, value):
if value is None:
return value
if isinstance(value, list):
scores = []
for score_json in value:
score = super().from_json(score_json)
scores.append(score)
return scores
raise TypeError("Value must be a list of Scores. Got {}".format(type(value)))
enforce_type = from_json

View File

@@ -9,9 +9,7 @@ import logging
import random
import sys
from collections import OrderedDict
from datetime import datetime
from pytz import UTC
from django.utils.translation import gettext_lazy as _
from xmodule.util.misc import get_short_labeler
@@ -471,37 +469,3 @@ def _min_or_none(itr):
return min(itr)
except ValueError:
return None
class ShowCorrectness:
"""
Helper class for determining whether correctness is currently hidden for a block.
When correctness is hidden, this limits the user's access to the correct/incorrect flags, messages, problem scores,
and aggregate subsection and course grades.
"""
# Constants used to indicate when to show correctness
ALWAYS = "always"
PAST_DUE = "past_due"
NEVER = "never"
NEVER_BUT_INCLUDE_GRADE = "never_but_include_grade"
@classmethod
def correctness_available(cls, show_correctness='', due_date=None, has_staff_access=False):
"""
Returns whether correctness is available now, for the given attributes.
"""
if show_correctness in (cls.NEVER, cls.NEVER_BUT_INCLUDE_GRADE):
return False
elif has_staff_access:
# This is after the 'never' check because course staff can see correctness
# unless the sequence/problem explicitly prevents it
return True
elif show_correctness == cls.PAST_DUE:
# Is it now past the due date?
return (due_date is None or
due_date < datetime.now(UTC))
# else: show_correctness == cls.ALWAYS
return True

View File

@@ -6,11 +6,10 @@ Support for inheritance of fields down an XBlock hierarchy.
import warnings
from django.utils import timezone
from xblock.core import XBlockMixin
from xblock.fields import Boolean, Dict, Float, Integer, List, Scope, String
from xblock.fields import Boolean, Date, Dict, Float, Integer, List, Scope, String, Timedelta
from xblock.runtime import KeyValueStore, KvsFieldData
from xmodule.error_block import ErrorBlock
from xmodule.fields import Date, Timedelta
from xmodule.partitions.partitions import UserPartition
from ..course_metadata_utils import DEFAULT_START_DATE

View File

@@ -16,13 +16,12 @@ import ddt
from ccx_keys.locator import CCXBlockUsageLocator
from django.core.cache import InvalidCacheBackendError, caches
from opaque_keys.edx.locator import BlockUsageLocator, CourseKey, CourseLocator, LocalId
from xblock.fields import Reference, ReferenceList, ReferenceValueDict
from xblock.fields import Date, Reference, ReferenceList, ReferenceValueDict, Timedelta
from openedx.core.djangolib.testing.utils import CacheIsolationMixin
from openedx.core.lib import tempdir
from openedx.core.lib.tests import attr
from xmodule.course_block import CourseBlock
from xmodule.fields import Date, Timedelta
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.edit_info import EditInfoMixin
from xmodule.modulestore.exceptions import (

View File

@@ -1,133 +0,0 @@
"""
Progress class for blocks. Represents where a student is in a block.
For most subclassing needs, you should only need to reimplement
frac() and __str__().
"""
import numbers
class Progress:
"""Represents a progress of a/b (a out of b done)
a and b must be numeric, but not necessarily integer, with
0 <= a <= b and b > 0.
Progress can only represent Progress for blocks where that makes sense. Other
blocks (e.g. html) should return None from get_progress().
TODO: add tag for module type? Would allow for smarter merging.
"""
def __init__(self, a, b):
"""Construct a Progress object. a and b must be numbers, and must have
0 <= a <= b and b > 0
"""
# Want to do all checking at construction time, so explicitly check types
if not (isinstance(a, numbers.Number) and isinstance(b, numbers.Number)):
raise TypeError(f"a and b must be numbers. Passed {a}/{b}")
if a > b: # lint-amnesty, pylint: disable=consider-using-min-builtin
a = b
if a < 0: # lint-amnesty, pylint: disable=consider-using-max-builtin
a = 0
if b <= 0:
raise ValueError(f"fraction a/b = {a}/{b} must have b > 0")
self._a = a
self._b = b
def frac(self):
"""Return tuple (a,b) representing progress of a/b"""
return (self._a, self._b)
def percent(self):
"""Returns a percentage progress as a float between 0 and 100.
subclassing note: implemented in terms of frac(), assumes sanity
checking is done at construction time.
"""
(a, b) = self.frac()
return 100.0 * a / b
def started(self):
"""Returns True if fractional progress is greater than 0.
subclassing note: implemented in terms of frac(), assumes sanity
checking is done at construction time.
"""
return self.frac()[0] > 0
def inprogress(self):
"""Returns True if fractional progress is strictly between 0 and 1.
subclassing note: implemented in terms of frac(), assumes sanity
checking is done at construction time.
"""
(a, b) = self.frac()
return a > 0 and a < b # lint-amnesty, pylint: disable=chained-comparison
def done(self):
"""Return True if this represents done.
subclassing note: implemented in terms of frac(), assumes sanity
checking is done at construction time.
"""
(a, b) = self.frac()
return a == b
def ternary_str(self):
"""Return a string version of this progress: either
"none", "in_progress", or "done".
subclassing note: implemented in terms of frac()
"""
(a, b) = self.frac()
if a == 0:
return "none"
if a < b:
return "in_progress"
return "done"
def __eq__(self, other):
"""Two Progress objects are equal if they have identical values.
Implemented in terms of frac()"""
if not isinstance(other, Progress):
return False
(a, b) = self.frac()
(a2, b2) = other.frac()
return a == a2 and b == b2
def __ne__(self, other):
"""The opposite of equal"""
return not self.__eq__(other)
def __str__(self):
"""Return a string representation of this string. Rounds results to
two decimal places, stripping out any trailing zeroes.
subclassing note: implemented in terms of frac().
"""
(a, b) = self.frac()
display = lambda n: f"{n:.2f}".rstrip("0").rstrip(".")
return f"{display(a)}/{display(b)}"
@staticmethod
def add_counts(a, b):
"""Add two progress indicators, assuming that each represents items done:
(a / b) + (c / d) = (a + c) / (b + d).
If either is None, returns the other.
"""
if a is None:
return b
if b is None:
return a
# get numerators + denominators
(n, d) = a.frac()
(n2, d2) = b.frac()
return Progress(n + n2, d + d2)

View File

@@ -20,7 +20,8 @@ from web_fragments.fragment import Fragment
from xblock.completable import XBlockCompletionMode
from xblock.core import XBlock
from xblock.exceptions import NoSuchServiceError
from xblock.fields import Boolean, Integer, List, Scope, String
from xblock.fields import Boolean, Date, Integer, List, Scope, String
from xblock.progress import Progress
from edx_toggles.toggles import WaffleFlag, SettingDictToggle
from xmodule.util.builtin_assets import add_webpack_js_to_fragment, add_css_to_fragment
@@ -35,13 +36,10 @@ from xmodule.x_module import (
from common.djangoapps.xblock_django.constants import ATTR_KEY_USER_ID, ATTR_KEY_USER_IS_STAFF
from .exceptions import NotFoundError
from .fields import Date
from .mako_block import MakoTemplateBlockBase
from .progress import Progress
from .x_module import AUTHOR_VIEW, PUBLIC_VIEW
from .xml_block import XmlMixin
log = logging.getLogger(__name__)
# HACK: This shouldn't be hard-coded to two types

View File

@@ -17,9 +17,9 @@ from webob import Response
from xblock.core import XBlock
from xblock.exceptions import NoSuchServiceError
from xblock.fields import Integer, ReferenceValueDict, Scope, String
from xblock.progress import Progress
from xmodule.mako_block import MakoTemplateBlockBase
from xmodule.modulestore.inheritance import UserPartitionList
from xmodule.progress import Progress
from xmodule.seq_block import ProctoringFields, SequenceMixin
from xmodule.studio_editable import StudioEditableBlock
from xmodule.util.builtin_assets import add_webpack_js_to_fragment

View File

@@ -25,11 +25,11 @@ from lxml import etree
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from pytz import UTC
from webob.multidict import MultiDict
from xblock.exceptions import NotFoundError
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
from xblock.scorable import Score
import xmodule
from lms.djangoapps.courseware.user_state_client import XBlockUserState
from openedx.core.djangolib.testing.utils import skip_unless_lms
from xmodule.capa import responsetypes
@@ -1198,7 +1198,7 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss
# Simulate that ProblemBlock.closed() always returns True
with patch("xmodule.capa_block.ProblemBlock.closed") as mock_closed:
mock_closed.return_value = True
with pytest.raises(xmodule.exceptions.NotFoundError):
with pytest.raises(NotFoundError):
get_request_dict = {CapaFactory.input_key(): "3.14"}
block.submit_problem(get_request_dict)
@@ -1214,7 +1214,7 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss
block.done = True
# Expect that we cannot submit
with pytest.raises(xmodule.exceptions.NotFoundError):
with pytest.raises(NotFoundError):
get_request_dict = {CapaFactory.input_key(): "3.14"}
block.submit_problem(get_request_dict)
@@ -1848,7 +1848,7 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss
block = CapaFactory.create(done=False)
# Try to rescore the problem, and get exception
with pytest.raises(xmodule.exceptions.NotFoundError):
with pytest.raises(NotFoundError):
block.rescore(only_if_higher=False)
def test_rescore_problem_not_supported(self):

View File

@@ -16,11 +16,11 @@ from unittest.mock import Mock
import pytest
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from pytz import UTC
from xblock.exceptions import NotFoundError
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
from xblock.scorable import Score
import xmodule
from xmodule.capa_block import ProblemBlock
from . import get_test_system
@@ -244,7 +244,7 @@ class XModuleQuizAttemptsDelayTest(unittest.TestCase):
# Already attempted once (just now) and thus has a submitted time
num_attempts = 99
# Regular create_and_check should fail
with pytest.raises(xmodule.exceptions.NotFoundError):
with pytest.raises(NotFoundError):
(block, unused_result) = self.create_and_check(
num_attempts=num_attempts,
last_submission_time=datetime.datetime(2013, 12, 6, 0, 17, 36, tzinfo=UTC),

View File

@@ -1,164 +0,0 @@
"""Tests for classes defined in fields.py."""
import datetime
import unittest
import pytest
from pytz import UTC
from xmodule.fields import Date, RelativeTime, Timedelta
class DateTest(unittest.TestCase): # lint-amnesty, pylint: disable=missing-class-docstring
date = Date()
def compare_dates(self, dt1, dt2, expected_delta):
assert (dt1 - dt2) == expected_delta, (((str(dt1) + "-") + str(dt2)) + "!=") + str(expected_delta)
def test_from_json(self):
"""Test conversion from iso compatible date strings to struct_time"""
self.compare_dates(
DateTest.date.from_json("2013-01-01"), DateTest.date.from_json("2012-12-31"), datetime.timedelta(days=1)
)
self.compare_dates(
DateTest.date.from_json("2013-01-01T00"),
DateTest.date.from_json("2012-12-31T23"),
datetime.timedelta(hours=1),
)
self.compare_dates(
DateTest.date.from_json("2013-01-01T00:00"),
DateTest.date.from_json("2012-12-31T23:59"),
datetime.timedelta(minutes=1),
)
self.compare_dates(
DateTest.date.from_json("2013-01-01T00:00:00"),
DateTest.date.from_json("2012-12-31T23:59:59"),
datetime.timedelta(seconds=1),
)
self.compare_dates(
DateTest.date.from_json("2013-01-01T00:00:00Z"),
DateTest.date.from_json("2012-12-31T23:59:59Z"),
datetime.timedelta(seconds=1),
)
self.compare_dates(
DateTest.date.from_json("2012-12-31T23:00:01-01:00"),
DateTest.date.from_json("2013-01-01T00:00:00+01:00"),
datetime.timedelta(hours=1, seconds=1),
)
def test_enforce_type(self):
assert DateTest.date.enforce_type(None) is None
assert DateTest.date.enforce_type("") is None
assert DateTest.date.enforce_type("2012-12-31T23:00:01") == datetime.datetime(
2012, 12, 31, 23, 0, 1, tzinfo=UTC
)
assert DateTest.date.enforce_type(1234567890000) == datetime.datetime(2009, 2, 13, 23, 31, 30, tzinfo=UTC)
assert DateTest.date.enforce_type(datetime.datetime(2014, 5, 9, 21, 1, 27, tzinfo=UTC)) == datetime.datetime(
2014, 5, 9, 21, 1, 27, tzinfo=UTC
)
with pytest.raises(TypeError):
DateTest.date.enforce_type([1])
def test_return_None(self):
assert DateTest.date.from_json("") is None
assert DateTest.date.from_json(None) is None
with pytest.raises(TypeError):
DateTest.date.from_json(["unknown value"])
def test_old_due_date_format(self):
current = datetime.datetime.today()
assert datetime.datetime(current.year, 3, 12, 12, tzinfo=UTC) == DateTest.date.from_json("March 12 12:00")
assert datetime.datetime(current.year, 12, 4, 16, 30, tzinfo=UTC) == DateTest.date.from_json("December 4 16:30")
assert DateTest.date.from_json("12 12:00") is None
def test_non_std_from_json(self):
"""
Test the non-standard args being passed to from_json
"""
now = datetime.datetime.now(UTC)
delta = now - datetime.datetime.fromtimestamp(0, UTC)
assert DateTest.date.from_json(delta.total_seconds() * 1000) == now
yesterday = datetime.datetime.now(UTC) - datetime.timedelta(days=-1)
assert DateTest.date.from_json(yesterday) == yesterday
def test_to_json(self):
"""
Test converting time reprs to iso dates
"""
assert (
DateTest.date.to_json(datetime.datetime.strptime("2012-12-31T23:59:59Z", "%Y-%m-%dT%H:%M:%SZ"))
== "2012-12-31T23:59:59Z"
)
assert DateTest.date.to_json(DateTest.date.from_json("2012-12-31T23:59:59Z")) == "2012-12-31T23:59:59Z"
assert (
DateTest.date.to_json(DateTest.date.from_json("2012-12-31T23:00:01-01:00")) == "2012-12-31T23:00:01-01:00"
)
with pytest.raises(TypeError):
DateTest.date.to_json("2012-12-31T23:00:01-01:00")
class TimedeltaTest(unittest.TestCase): # lint-amnesty, pylint: disable=missing-class-docstring
delta = Timedelta()
def test_from_json(self):
assert TimedeltaTest.delta.from_json("1 day 12 hours 59 minutes 59 seconds") == datetime.timedelta(
days=1, hours=12, minutes=59, seconds=59
)
assert TimedeltaTest.delta.from_json("1 day 46799 seconds") == datetime.timedelta(days=1, seconds=46799)
def test_enforce_type(self):
assert TimedeltaTest.delta.enforce_type(None) is None
assert TimedeltaTest.delta.enforce_type(datetime.timedelta(days=1, seconds=46799)) == datetime.timedelta(
days=1, seconds=46799
)
assert TimedeltaTest.delta.enforce_type("1 day 46799 seconds") == datetime.timedelta(days=1, seconds=46799)
with pytest.raises(TypeError):
TimedeltaTest.delta.enforce_type([1])
def test_to_json(self):
assert "1 days 46799 seconds" == TimedeltaTest.delta.to_json(
datetime.timedelta(days=1, hours=12, minutes=59, seconds=59)
)
class RelativeTimeTest(unittest.TestCase): # lint-amnesty, pylint: disable=missing-class-docstring
delta = RelativeTime()
def test_from_json(self):
assert RelativeTimeTest.delta.from_json("0:05:07") == datetime.timedelta(seconds=307)
assert RelativeTimeTest.delta.from_json(100.0) == datetime.timedelta(seconds=100)
assert RelativeTimeTest.delta.from_json(None) == datetime.timedelta(seconds=0)
with pytest.raises(TypeError):
RelativeTimeTest.delta.from_json(1234) # int
with pytest.raises(ValueError):
RelativeTimeTest.delta.from_json("77:77:77")
def test_enforce_type(self):
assert RelativeTimeTest.delta.enforce_type(None) is None
assert RelativeTimeTest.delta.enforce_type(datetime.timedelta(days=1, seconds=46799)) == datetime.timedelta(
days=1, seconds=46799
)
assert RelativeTimeTest.delta.enforce_type("0:05:07") == datetime.timedelta(seconds=307)
with pytest.raises(TypeError):
RelativeTimeTest.delta.enforce_type([1])
def test_to_json(self):
assert "01:02:03" == RelativeTimeTest.delta.to_json(datetime.timedelta(seconds=3723))
assert "00:00:00" == RelativeTimeTest.delta.to_json(None)
assert "00:01:40" == RelativeTimeTest.delta.to_json(100.0)
error_msg = "RelativeTime max value is 23:59:59=86400.0 seconds, but 90000.0 seconds is passed"
with self.assertRaisesRegex(ValueError, error_msg):
RelativeTimeTest.delta.to_json(datetime.timedelta(seconds=90000))
with pytest.raises(TypeError):
RelativeTimeTest.delta.to_json("123")
def test_str(self):
assert "01:02:03" == RelativeTimeTest.delta.to_json(datetime.timedelta(seconds=3723))
assert "11:02:03" == RelativeTimeTest.delta.to_json(datetime.timedelta(seconds=39723))

View File

@@ -4,14 +4,13 @@ Grading tests
import unittest
from datetime import datetime, timedelta
from datetime import datetime
import pytest
import ddt
from pytz import UTC
from lms.djangoapps.grades.scores import compute_percent
from xmodule import graders
from xmodule.graders import AggregatedScore, ProblemScore, ShowCorrectness, aggregate_scores
from xmodule.graders import AggregatedScore, ProblemScore, aggregate_scores
class GradesheetTest(unittest.TestCase):
@@ -415,89 +414,3 @@ class GraderTest(unittest.TestCase):
for i, section_breakdown in enumerate(graded['section_breakdown']):
assert expected_sequential_ids[i] == section_breakdown.get('sequential_id')
@ddt.ddt
class ShowCorrectnessTest(unittest.TestCase):
"""
Tests the correctness_available method
"""
def setUp(self):
super().setUp()
now = datetime.now(UTC)
day_delta = timedelta(days=1)
self.yesterday = now - day_delta
self.today = now
self.tomorrow = now + day_delta
def test_show_correctness_default(self):
"""
Test that correctness is visible by default.
"""
assert ShowCorrectness.correctness_available()
@ddt.data(
(ShowCorrectness.ALWAYS, True),
(ShowCorrectness.ALWAYS, False),
# Any non-constant values behave like "always"
('', True),
('', False),
('other-value', True),
('other-value', False),
)
@ddt.unpack
def test_show_correctness_always(self, show_correctness, has_staff_access):
"""
Test that correctness is visible when show_correctness is turned on.
"""
assert ShowCorrectness.correctness_available(show_correctness=show_correctness,
has_staff_access=has_staff_access)
@ddt.data(True, False)
def test_show_correctness_never(self, has_staff_access):
"""
Test that show_correctness="never" hides correctness from learners and course staff.
"""
assert not ShowCorrectness.correctness_available(show_correctness=ShowCorrectness.NEVER,
has_staff_access=has_staff_access)
@ddt.data(
# Correctness not visible to learners if due date in the future
('tomorrow', False, False),
# Correctness is visible to learners if due date in the past
('yesterday', False, True),
# Correctness is visible to learners if due date in the past (just)
('today', False, True),
# Correctness is visible to learners if there is no due date
(None, False, True),
# Correctness is visible to staff if due date in the future
('tomorrow', True, True),
# Correctness is visible to staff if due date in the past
('yesterday', True, True),
# Correctness is visible to staff if there is no due date
(None, True, True),
)
@ddt.unpack
def test_show_correctness_past_due(self, due_date_str, has_staff_access, expected_result):
"""
Test show_correctness="past_due" to ensure:
* correctness is always visible to course staff
* correctness is always visible to everyone if there is no due date
* correctness is visible to learners after the due date, when there is a due date.
"""
if due_date_str is None:
due_date = None
else:
due_date = getattr(self, due_date_str)
assert ShowCorrectness.correctness_available(ShowCorrectness.PAST_DUE, due_date, has_staff_access) ==\
expected_result
@ddt.data(True, False)
def test_show_correctness_never_but_include_grade(self, has_staff_access):
"""
Test that show_correctness="never_but_include_grade" hides correctness from learners and course staff.
"""
assert not ShowCorrectness.correctness_available(show_correctness=ShowCorrectness.NEVER_BUT_INCLUDE_GRADE,
has_staff_access=has_staff_access)

View File

@@ -13,10 +13,9 @@ from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from pytz import UTC
from xblock.core import XBlock
from xblock.fields import Integer, Scope, String
from xblock.fields import Date, Integer, Scope, String
from xblock.runtime import DictKeyValueStore, KvsFieldData
from xmodule.fields import Date
from xmodule.modulestore.inheritance import InheritanceMixin, compute_inherited_metadata
from xmodule.modulestore.xml import XMLImportingModuleStoreRuntime, LibraryXMLModuleStore, XMLModuleStore
from xmodule.tests import DATA_DIR

View File

@@ -17,11 +17,10 @@ from opaque_keys.edx.locator import BlockUsageLocator
from pytz import UTC
from webob.request import Request
from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds
from xblock.fields import ScopeIds, Timedelta
from common.djangoapps.xblock_django.constants import ATTR_KEY_ANONYMOUS_USER_ID
from xmodule.fields import Timedelta
from xmodule.lti_2_util import LTIError
from xmodule.lti_block import LTIBlock
from xmodule.tests.helpers import StubUserService

View File

@@ -1,109 +0,0 @@
"""Module progress tests"""
import unittest
from xmodule.progress import Progress
class ProgressTest(unittest.TestCase):
"""Test that basic Progress objects work. A Progress represents a
fraction between 0 and 1.
"""
not_started = Progress(0, 17)
part_done = Progress(2, 6)
half_done = Progress(3, 6)
also_half_done = Progress(1, 2)
done = Progress(7, 7)
def test_create_object(self):
# These should work:
prg1 = Progress(0, 2) # pylint: disable=unused-variable
prg2 = Progress(1, 2) # pylint: disable=unused-variable
prg3 = Progress(2, 2) # pylint: disable=unused-variable
prg4 = Progress(2.5, 5.0) # pylint: disable=unused-variable
prg5 = Progress(3.7, 12.3333) # pylint: disable=unused-variable
# These shouldn't
self.assertRaises(ValueError, Progress, 0, 0)
self.assertRaises(ValueError, Progress, 2, 0)
self.assertRaises(ValueError, Progress, 1, -2)
self.assertRaises(TypeError, Progress, 0, "all")
# check complex numbers just for the heck of it :)
self.assertRaises(TypeError, Progress, 2j, 3)
def test_clamp(self):
assert (2, 2) == Progress(3, 2).frac()
assert (0, 2) == Progress((-2), 2).frac()
def test_frac(self):
prg = Progress(1, 2)
(a_mem, b_mem) = prg.frac()
assert a_mem == 1
assert b_mem == 2
def test_percent(self):
assert self.not_started.percent() == 0
assert round(self.part_done.percent() - 33.33333333333333, 7) >= 0
assert self.half_done.percent() == 50
assert self.done.percent() == 100
assert self.half_done.percent() == self.also_half_done.percent()
def test_started(self):
assert not self.not_started.started()
assert self.part_done.started()
assert self.half_done.started()
assert self.done.started()
def test_inprogress(self):
# only true if working on it
assert not self.done.inprogress()
assert not self.not_started.inprogress()
assert self.part_done.inprogress()
assert self.half_done.inprogress()
def test_done(self):
assert self.done.done()
assert not self.half_done.done()
assert not self.not_started.done()
def test_str(self):
assert str(self.not_started) == "0/17"
assert str(self.part_done) == "2/6"
assert str(self.done) == "7/7"
assert str(Progress(2.1234, 7)) == "2.12/7"
assert str(Progress(2.0034, 7)) == "2/7"
assert str(Progress(0.999, 7)) == "1/7"
def test_add(self):
"""Test the Progress.add_counts() method"""
prg1 = Progress(0, 2)
prg2 = Progress(1, 3)
prg3 = Progress(2, 5)
prg_none = None
add = lambda a, b: Progress.add_counts(a, b).frac()
assert add(prg1, prg1) == (0, 4)
assert add(prg1, prg2) == (1, 5)
assert add(prg2, prg3) == (3, 8)
assert add(prg2, prg_none) == prg2.frac()
assert add(prg_none, prg2) == prg2.frac()
def test_equality(self):
"""Test that comparing Progress objects for equality
works correctly."""
prg1 = Progress(1, 2)
prg2 = Progress(2, 4)
prg3 = Progress(1, 2)
assert prg1 == prg3
assert prg1 != prg2
# Check != while we're at it
assert prg1 != prg2
assert prg1 == prg3

View File

@@ -8,11 +8,10 @@ from unittest.mock import Mock
import dateutil.parser
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
from xblock.field_data import DictFieldData
from xblock.fields import Any, Boolean, Dict, Float, Integer, List, Scope, String
from xblock.fields import Any, Boolean, Date, Dict, Float, Integer, List, RelativeTime, Scope, String, Timedelta
from xblock.runtime import DictKeyValueStore, KvsFieldData
from xmodule.course_block import CourseBlock
from xmodule.fields import Date, RelativeTime, Timedelta
from xmodule.modulestore.inheritance import InheritanceKeyValueStore, InheritanceMixin, InheritingFieldData
from xmodule.modulestore.split_mongo.split_mongo_kvs import SplitMongoKVS
from xmodule.seq_block import SequenceBlock

View File

@@ -15,9 +15,9 @@ from openedx_filters.learning.filters import VerticalBlockChildRenderStarted, Ve
from web_fragments.fragment import Fragment
from xblock.core import XBlock # lint-amnesty, pylint: disable=wrong-import-order
from xblock.fields import Boolean, Scope
from xblock.progress import Progress
from xmodule.mako_block import MakoTemplateBlockBase
from xmodule.progress import Progress
from xmodule.seq_block import SequenceFields
from xmodule.studio_editable import StudioEditableBlock
from xmodule.util.builtin_assets import add_webpack_js_to_fragment

View File

@@ -15,9 +15,9 @@ from opaque_keys.edx.locator import CourseLocator
from webob import Response
from xblock.core import XBlock
from xblock.exceptions import JsonHandlerError
from xblock.fields import RelativeTime
from xmodule.exceptions import NotFoundError
from xmodule.fields import RelativeTime
from openedx.core.djangoapps.video_config.transcripts_utils import (
Transcript,

View File

@@ -5,9 +5,7 @@ XFields for video block.
import datetime
from xblock.fields import Boolean, DateTime, Dict, Float, List, Scope, String
from xmodule.fields import RelativeTime
from xblock.fields import Boolean, DateTime, Dict, Float, List, RelativeTime, Scope, String
# Make '_' a no-op so we can scrape strings. Using lambda instead of
# `django.utils.translation.ugettext_noop` because Django cannot be imported in this file

View File

@@ -17,7 +17,7 @@ from web_fragments.fragment import Fragment
from webob import Response
from webob.multidict import MultiDict
from xblock.core import XBlock
from xblock.fields import Dict, Float, Integer, List, Scope, String, UserScope
from xblock.fields import Dict, Float, Integer, List, RelativeTime, Scope, String, UserScope
from xblock.runtime import IdGenerator, IdReader, Runtime
from common.djangoapps.xblock_django.constants import (
@@ -31,7 +31,6 @@ from common.djangoapps.xblock_django.constants import (
)
from openedx.core.djangolib.markup import HTML
from xmodule import block_metadata_utils
from xmodule.fields import RelativeTime
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.util.builtin_assets import add_webpack_js_to_fragment