diff --git a/cms/djangoapps/contentstore/management/commands/generate_courses.py b/cms/djangoapps/contentstore/management/commands/generate_courses.py index 4a91761e0b..935dfe014f 100644 --- a/cms/djangoapps/contentstore/management/commands/generate_courses.py +++ b/cms/djangoapps/contentstore/management/commands/generate_courses.py @@ -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 diff --git a/cms/djangoapps/contentstore/rest_api/v0/serializers/advanced_settings.py b/cms/djangoapps/contentstore/rest_api/v0/serializers/advanced_settings.py index 0de09900e2..152ca95be3 100644 --- a/cms/djangoapps/contentstore/rest_api/v0/serializers/advanced_settings.py +++ b/cms/djangoapps/contentstore/rest_api/v0/serializers/advanced_settings.py @@ -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 diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index e40d0c9256..7f2e8a151d 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -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 diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py index 1c64cfb660..05fc1705e8 100644 --- a/cms/djangoapps/contentstore/views/preview.py +++ b/cms/djangoapps/contentstore/views/preview.py @@ -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 diff --git a/cms/djangoapps/models/settings/encoder.py b/cms/djangoapps/models/settings/encoder.py index 4ea147270f..65f43d4245 100644 --- a/cms/djangoapps/models/settings/encoder.py +++ b/cms/djangoapps/models/settings/encoder.py @@ -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 diff --git a/lms/djangoapps/course_home_api/progress/api.py b/lms/djangoapps/course_home_api/progress/api.py index f89ecd3d25..53895d8323 100644 --- a/lms/djangoapps/course_home_api/progress/api.py +++ b/lms/djangoapps/course_home_api/progress/api.py @@ -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 diff --git a/lms/djangoapps/course_home_api/progress/tests/test_api.py b/lms/djangoapps/course_home_api/progress/tests/test_api.py index 48df90fa64..963dd419a2 100644 --- a/lms/djangoapps/course_home_api/progress/tests/test_api.py +++ b/lms/djangoapps/course_home_api/progress/tests/test_api.py @@ -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 diff --git a/lms/djangoapps/course_home_api/progress/views.py b/lms/djangoapps/course_home_api/progress/views.py index 54e71df48c..f0cd42fd56 100644 --- a/lms/djangoapps/course_home_api/progress/views.py +++ b/lms/djangoapps/course_home_api/progress/views.py @@ -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 diff --git a/lms/djangoapps/courseware/block_render.py b/lms/djangoapps/courseware/block_render.py index ad04bcb192..b6e4145e2e 100644 --- a/lms/djangoapps/courseware/block_render.py +++ b/lms/djangoapps/courseware/block_render.py @@ -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 diff --git a/lms/djangoapps/courseware/migrations/0008_move_idde_to_edx_when.py b/lms/djangoapps/courseware/migrations/0008_move_idde_to_edx_when.py index 632409025c..58ea1dead2 100644 --- a/lms/djangoapps/courseware/migrations/0008_move_idde_to_edx_when.py +++ b/lms/djangoapps/courseware/migrations/0008_move_idde_to_edx_when.py @@ -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') diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py index 2c3ece3133..918b83d960 100644 --- a/lms/djangoapps/courseware/tests/test_views.py +++ b/lms/djangoapps/courseware/tests/test_views.py @@ -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 diff --git a/lms/djangoapps/grades/subsection_grade.py b/lms/djangoapps/grades/subsection_grade.py index b0c98497b8..8f3c07cddc 100644 --- a/lms/djangoapps/grades/subsection_grade.py +++ b/lms/djangoapps/grades/subsection_grade.py @@ -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__) diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index ef72306d98..d54da21353 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -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, diff --git a/lms/djangoapps/instructor/tests/test_tools.py b/lms/djangoapps/instructor/tests/test_tools.py index b9f754dbff..243f2afec4 100644 --- a/lms/djangoapps/instructor/tests/test_tools.py +++ b/lms/djangoapps/instructor/tests/test_tools.py @@ -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, ) diff --git a/lms/templates/courseware/progress.html b/lms/templates/courseware/progress.html index 3ee4044fcb..e1b7918bcc 100644 --- a/lms/templates/courseware/progress.html +++ b/lms/templates/courseware/progress.html @@ -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 %> <% diff --git a/openedx/core/djangoapps/models/course_details.py b/openedx/core/djangoapps/models/course_details.py index dbb0fdfaec..c90081f30a 100644 --- a/openedx/core/djangoapps/models/course_details.py +++ b/openedx/core/djangoapps/models/course_details.py @@ -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 diff --git a/xmodule/capa_block.py b/xmodule/capa_block.py index d441683e19..ab07ff9b02 100644 --- a/xmodule/capa_block.py +++ b/xmodule/capa_block.py @@ -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") diff --git a/xmodule/course_block.py b/xmodule/course_block.py index 0ebd00a3e5..2a720baf41 100644 --- a/xmodule/course_block.py +++ b/xmodule/course_block.py @@ -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__) diff --git a/xmodule/exceptions.py b/xmodule/exceptions.py index 3fbc731c48..678cab6f23 100644 --- a/xmodule/exceptions.py +++ b/xmodule/exceptions.py @@ -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 diff --git a/xmodule/fields.py b/xmodule/fields.py deleted file mode 100644 index b5f997f035..0000000000 --- a/xmodule/fields.py +++ /dev/null @@ -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\d+?) day(?:s?))?(\s)?((?P\d+?) hour(?:s?))?(\s)?((?P\d+?) minute(?:s)?)?(\s)?((?P\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: - day[s] (optional) - hour[s] (optional) - minute[s] (optional) - 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 diff --git a/xmodule/graders.py b/xmodule/graders.py index 34f7e61654..0a559e470a 100644 --- a/xmodule/graders.py +++ b/xmodule/graders.py @@ -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 diff --git a/xmodule/modulestore/inheritance.py b/xmodule/modulestore/inheritance.py index 4c5a14b769..a41bf84d99 100644 --- a/xmodule/modulestore/inheritance.py +++ b/xmodule/modulestore/inheritance.py @@ -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 diff --git a/xmodule/modulestore/tests/test_split_modulestore.py b/xmodule/modulestore/tests/test_split_modulestore.py index c574d345d9..09278ff5e8 100644 --- a/xmodule/modulestore/tests/test_split_modulestore.py +++ b/xmodule/modulestore/tests/test_split_modulestore.py @@ -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 ( diff --git a/xmodule/progress.py b/xmodule/progress.py deleted file mode 100644 index 379dbf12a6..0000000000 --- a/xmodule/progress.py +++ /dev/null @@ -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) diff --git a/xmodule/seq_block.py b/xmodule/seq_block.py index f06d3030f5..6e19e04b2f 100644 --- a/xmodule/seq_block.py +++ b/xmodule/seq_block.py @@ -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 diff --git a/xmodule/split_test_block.py b/xmodule/split_test_block.py index 52f4054787..f259ef40e7 100644 --- a/xmodule/split_test_block.py +++ b/xmodule/split_test_block.py @@ -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 diff --git a/xmodule/tests/test_capa_block.py b/xmodule/tests/test_capa_block.py index d46f214b02..6b8e187bdc 100644 --- a/xmodule/tests/test_capa_block.py +++ b/xmodule/tests/test_capa_block.py @@ -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): diff --git a/xmodule/tests/test_delay_between_attempts.py b/xmodule/tests/test_delay_between_attempts.py index 9b1c58d8aa..c0315dca80 100644 --- a/xmodule/tests/test_delay_between_attempts.py +++ b/xmodule/tests/test_delay_between_attempts.py @@ -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), diff --git a/xmodule/tests/test_fields.py b/xmodule/tests/test_fields.py deleted file mode 100644 index 95b329bdb0..0000000000 --- a/xmodule/tests/test_fields.py +++ /dev/null @@ -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)) diff --git a/xmodule/tests/test_graders.py b/xmodule/tests/test_graders.py index b80004c913..ef37234a2d 100644 --- a/xmodule/tests/test_graders.py +++ b/xmodule/tests/test_graders.py @@ -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) diff --git a/xmodule/tests/test_import.py b/xmodule/tests/test_import.py index 0540f7665f..83aae2d664 100644 --- a/xmodule/tests/test_import.py +++ b/xmodule/tests/test_import.py @@ -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 diff --git a/xmodule/tests/test_lti_unit.py b/xmodule/tests/test_lti_unit.py index 99eb0a2834..ae980faf12 100644 --- a/xmodule/tests/test_lti_unit.py +++ b/xmodule/tests/test_lti_unit.py @@ -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 diff --git a/xmodule/tests/test_progress.py b/xmodule/tests/test_progress.py deleted file mode 100644 index c2ae97ced7..0000000000 --- a/xmodule/tests/test_progress.py +++ /dev/null @@ -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 diff --git a/xmodule/tests/test_xml_block.py b/xmodule/tests/test_xml_block.py index 3c25cebee5..012b291df7 100644 --- a/xmodule/tests/test_xml_block.py +++ b/xmodule/tests/test_xml_block.py @@ -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 diff --git a/xmodule/vertical_block.py b/xmodule/vertical_block.py index 150e631085..26aba7c50c 100644 --- a/xmodule/vertical_block.py +++ b/xmodule/vertical_block.py @@ -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 diff --git a/xmodule/video_block/video_handlers.py b/xmodule/video_block/video_handlers.py index 8a4943b04f..adc59ae740 100644 --- a/xmodule/video_block/video_handlers.py +++ b/xmodule/video_block/video_handlers.py @@ -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, diff --git a/xmodule/video_block/video_xfields.py b/xmodule/video_block/video_xfields.py index 873ed01f28..af65e1a275 100644 --- a/xmodule/video_block/video_xfields.py +++ b/xmodule/video_block/video_xfields.py @@ -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 diff --git a/xmodule/x_module.py b/xmodule/x_module.py index 4ed1a03b61..455776adcc 100644 --- a/xmodule/x_module.py +++ b/xmodule/x_module.py @@ -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