BOM-2369 (D): pyupgrade on contentstore/views (#26767)
* pyupgrade on contentstore/views * Apply suggestions from code review Co-authored-by: Usama Sadiq <usama.sadiq@arbisoft.com> Co-authored-by: Usama Sadiq <usama.sadiq@arbisoft.com>
This commit is contained in:
@@ -7,7 +7,6 @@ import math
|
||||
import re
|
||||
from functools import partial
|
||||
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
@@ -17,13 +16,12 @@ from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.views.decorators.http import require_http_methods, require_POST
|
||||
from opaque_keys.edx.keys import AssetKey, CourseKey
|
||||
from pymongo import ASCENDING, DESCENDING
|
||||
from six import text_type
|
||||
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_response
|
||||
from openedx.core.djangoapps.contentserver.caching import del_cached_content
|
||||
from common.djangoapps.student.auth import has_course_author_access
|
||||
from common.djangoapps.util.date_utils import get_default_time_display
|
||||
from common.djangoapps.util.json_request import JsonResponse
|
||||
from openedx.core.djangoapps.contentserver.caching import del_cached_content
|
||||
from xmodule.contentstore.content import StaticContent
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.exceptions import NotFoundError
|
||||
@@ -206,9 +204,9 @@ def _get_error_if_invalid_parameters(requested_filter):
|
||||
if invalid_filters:
|
||||
error_message = {
|
||||
'error_code': 'invalid_asset_type_filter',
|
||||
'developer_message': u'The asset_type parameter to the request is invalid. '
|
||||
u'The {} filters are not described in the settings.FILES_AND_UPLOAD_TYPE_FILTERS '
|
||||
u'dictionary.'.format(invalid_filters)
|
||||
'developer_message': 'The asset_type parameter to the request is invalid. '
|
||||
'The {} filters are not described in the settings.FILES_AND_UPLOAD_TYPE_FILTERS '
|
||||
'dictionary.'.format(invalid_filters)
|
||||
}
|
||||
return JsonResponse({'error': error_message}, status=400)
|
||||
|
||||
@@ -414,7 +412,7 @@ def _upload_asset(request, course_key):
|
||||
try:
|
||||
content = update_course_run_asset(course_key, upload_file)
|
||||
except AssetSizeTooLargeException as exception:
|
||||
return JsonResponse({'error': text_type(exception)}, status=413)
|
||||
return JsonResponse({'error': str(exception)}, status=413)
|
||||
|
||||
# readback the saved content - we need the database timestamp
|
||||
readback = contentstore().find(content.location)
|
||||
@@ -436,7 +434,7 @@ def _get_error_if_course_does_not_exist(course_key): # lint-amnesty, pylint: di
|
||||
try:
|
||||
modulestore().get_course(course_key)
|
||||
except ItemNotFoundError:
|
||||
logging.error(u'Could not find course: %s', course_key)
|
||||
logging.error('Could not find course: %s', course_key)
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
|
||||
@@ -472,8 +470,8 @@ def _get_file_too_large_error_message(filename):
|
||||
"""returns formatted error message for large files"""
|
||||
|
||||
return _(
|
||||
u'File {filename} exceeds maximum size of '
|
||||
u'{maximum_size_in_megabytes} MB.'
|
||||
'File {filename} exceeds maximum size of '
|
||||
'{maximum_size_in_megabytes} MB.'
|
||||
).format(
|
||||
filename=filename,
|
||||
maximum_size_in_megabytes=settings.MAX_ASSET_UPLOAD_FILE_SIZE_IN_MB,
|
||||
@@ -585,7 +583,7 @@ def _delete_thumbnail(thumbnail_location, course_key, asset_key): # lint-amnest
|
||||
contentstore().delete(thumbnail_content.get_id())
|
||||
del_cached_content(thumbnail_location)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
logging.warning(u'Could not delete thumbnail: %s', thumbnail_location)
|
||||
logging.warning('Could not delete thumbnail: %s', thumbnail_location)
|
||||
|
||||
|
||||
def _get_asset_json(display_name, content_type, date, location, thumbnail_location, locked):
|
||||
@@ -604,5 +602,5 @@ def _get_asset_json(display_name, content_type, date, location, thumbnail_locati
|
||||
'thumbnail': StaticContent.serialize_asset_key_with_slash(thumbnail_location) if thumbnail_location else None,
|
||||
'locked': locked,
|
||||
# needed for Backbone delete/update.
|
||||
'id': six.text_type(location)
|
||||
'id': str(location)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ course.certificates: {
|
||||
import json
|
||||
import logging
|
||||
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
@@ -37,7 +36,6 @@ from django.views.decorators.http import require_http_methods
|
||||
from eventtracking import tracker
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import AssetKey, CourseKey
|
||||
from six import text_type
|
||||
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_response
|
||||
@@ -87,7 +85,7 @@ def _delete_asset(course_key, asset_key_string):
|
||||
except InvalidKeyError:
|
||||
# Unable to parse the asset key, log and return
|
||||
LOGGER.info(
|
||||
u"In course %r, unable to parse asset key %r, not attempting to delete signatory.",
|
||||
"In course %r, unable to parse asset key %r, not attempting to delete signatory.",
|
||||
course_key,
|
||||
asset_key_string,
|
||||
)
|
||||
@@ -95,7 +93,7 @@ def _delete_asset(course_key, asset_key_string):
|
||||
else:
|
||||
# Unable to parse the asset key, log and return
|
||||
LOGGER.info(
|
||||
u"In course %r, unable to parse asset key %r, not attempting to delete signatory.",
|
||||
"In course %r, unable to parse asset key %r, not attempting to delete signatory.",
|
||||
course_key,
|
||||
asset_key_string,
|
||||
)
|
||||
@@ -123,7 +121,7 @@ class CertificateValidationError(CertificateException):
|
||||
pass # lint-amnesty, pylint: disable=unnecessary-pass
|
||||
|
||||
|
||||
class CertificateManager(object):
|
||||
class CertificateManager:
|
||||
"""
|
||||
The CertificateManager is responsible for storage, retrieval, and manipulation of Certificates
|
||||
Certificates are not stored in the Django ORM, they are a field/setting on the course descriptor
|
||||
@@ -153,7 +151,7 @@ class CertificateManager(object):
|
||||
# Ensure the schema version meets our expectations
|
||||
if certificate_data.get("version") != CERTIFICATE_SCHEMA_VERSION:
|
||||
raise TypeError(
|
||||
u"Unsupported certificate schema version: {0}. Expected version: {1}.".format(
|
||||
"Unsupported certificate schema version: {}. Expected version: {}.".format(
|
||||
certificate_data.get("version"),
|
||||
CERTIFICATE_SCHEMA_VERSION
|
||||
)
|
||||
@@ -238,13 +236,13 @@ class CertificateManager(object):
|
||||
Deserialize from a JSON representation into a Certificate object.
|
||||
'value' should be either a Certificate instance, or a valid JSON string
|
||||
"""
|
||||
if not six.PY2 and isinstance(value, bytes):
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode('utf-8')
|
||||
|
||||
# Ensure the schema fieldset meets our expectations
|
||||
for key in ("name", "description", "version"):
|
||||
if key not in value:
|
||||
raise CertificateValidationError(_(u"Certificate dict {0} missing value key '{1}'").format(value, key))
|
||||
raise CertificateValidationError(_("Certificate dict {0} missing value key '{1}'").format(value, key))
|
||||
|
||||
# Load up the Certificate data
|
||||
certificate_data = CertificateManager.parse(value)
|
||||
@@ -314,7 +312,7 @@ class CertificateManager(object):
|
||||
tracker.emit(event_name, event_data)
|
||||
|
||||
|
||||
class Certificate(object):
|
||||
class Certificate:
|
||||
"""
|
||||
The logical representation of an individual course certificate
|
||||
"""
|
||||
@@ -349,7 +347,7 @@ def certificate_activation_handler(request, course_key_string):
|
||||
try:
|
||||
course = _get_course_and_check_access(course_key, request.user)
|
||||
except PermissionDenied:
|
||||
msg = _(u'PermissionDenied: Failed in authenticating {user}').format(user=request.user)
|
||||
msg = _('PermissionDenied: Failed in authenticating {user}').format(user=request.user)
|
||||
return JsonResponse({"error": msg}, status=403)
|
||||
|
||||
data = json.loads(request.body.decode('utf8'))
|
||||
@@ -364,7 +362,7 @@ def certificate_activation_handler(request, course_key_string):
|
||||
store.update_item(course, request.user.id)
|
||||
cert_event_type = 'activated' if is_active else 'deactivated'
|
||||
CertificateManager.track_event(cert_event_type, {
|
||||
'course_id': six.text_type(course.id),
|
||||
'course_id': str(course.id),
|
||||
})
|
||||
return HttpResponse(status=200)
|
||||
|
||||
@@ -387,7 +385,7 @@ def certificates_list_handler(request, course_key_string):
|
||||
try:
|
||||
course = _get_course_and_check_access(course_key, request.user)
|
||||
except PermissionDenied:
|
||||
msg = _(u'PermissionDenied: Failed in authenticating {user}').format(user=request.user)
|
||||
msg = _('PermissionDenied: Failed in authenticating {user}').format(user=request.user)
|
||||
return JsonResponse({"error": msg}, status=403)
|
||||
|
||||
if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'):
|
||||
@@ -442,7 +440,7 @@ def certificates_list_handler(request, course_key_string):
|
||||
try:
|
||||
new_certificate = CertificateManager.deserialize_certificate(course, request.body)
|
||||
except CertificateValidationError as err:
|
||||
return JsonResponse({"error": text_type(err)}, status=400)
|
||||
return JsonResponse({"error": str(err)}, status=400)
|
||||
if course.certificates.get('certificates') is None:
|
||||
course.certificates['certificates'] = []
|
||||
course.certificates['certificates'].append(new_certificate.certificate_data)
|
||||
@@ -454,7 +452,7 @@ def certificates_list_handler(request, course_key_string):
|
||||
)
|
||||
store.update_item(course, request.user.id)
|
||||
CertificateManager.track_event('created', {
|
||||
'course_id': six.text_type(course.id),
|
||||
'course_id': str(course.id),
|
||||
'configuration_id': new_certificate.id
|
||||
})
|
||||
course = _get_course_and_check_access(course_key, request.user)
|
||||
@@ -499,7 +497,7 @@ def certificates_detail_handler(request, course_key_string, certificate_id):
|
||||
try:
|
||||
new_certificate = CertificateManager.deserialize_certificate(course, request.body)
|
||||
except CertificateValidationError as err:
|
||||
return JsonResponse({"error": text_type(err)}, status=400)
|
||||
return JsonResponse({"error": str(err)}, status=400)
|
||||
|
||||
serialized_certificate = CertificateManager.serialize_certificate(new_certificate)
|
||||
cert_event_type = 'created'
|
||||
@@ -511,7 +509,7 @@ def certificates_detail_handler(request, course_key_string, certificate_id):
|
||||
|
||||
store.update_item(course, request.user.id)
|
||||
CertificateManager.track_event(cert_event_type, {
|
||||
'course_id': six.text_type(course.id),
|
||||
'course_id': str(course.id),
|
||||
'configuration_id': serialized_certificate["id"]
|
||||
})
|
||||
return JsonResponse(serialized_certificate, status=201)
|
||||
@@ -533,7 +531,7 @@ def certificates_detail_handler(request, course_key_string, certificate_id):
|
||||
certificate_id=certificate_id
|
||||
)
|
||||
CertificateManager.track_event('deleted', {
|
||||
'course_id': six.text_type(course.id),
|
||||
'course_id': str(course.id),
|
||||
'configuration_id': certificate_id
|
||||
})
|
||||
return JsonResponse(status=204)
|
||||
|
||||
@@ -4,8 +4,8 @@ Studio component views
|
||||
|
||||
|
||||
import logging
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
@@ -14,7 +14,6 @@ from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.http import require_GET
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
from six.moves.urllib.parse import quote_plus
|
||||
from xblock.core import XBlock
|
||||
from xblock.django.request import django_to_webob_request, webob_to_django_response
|
||||
from xblock.exceptions import NoSuchHandlerError
|
||||
@@ -22,10 +21,10 @@ from xblock.plugin import PluginMissingError
|
||||
from xblock.runtime import Mixologist
|
||||
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_response
|
||||
from openedx.core.lib.xblock_utils import get_aside_from_xblock, is_xblock_aside
|
||||
from common.djangoapps.student.auth import has_course_author_access
|
||||
from common.djangoapps.xblock_django.api import authorable_xblocks, disabled_xblocks
|
||||
from common.djangoapps.xblock_django.models import XBlockStudioConfigurationFlag
|
||||
from openedx.core.lib.xblock_utils import get_aside_from_xblock, is_xblock_aside
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
@@ -43,7 +42,7 @@ log = logging.getLogger(__name__)
|
||||
# NOTE: This list is disjoint from ADVANCED_COMPONENT_TYPES
|
||||
COMPONENT_TYPES = ['discussion', 'html', 'openassessment', 'problem', 'video']
|
||||
|
||||
ADVANCED_COMPONENT_TYPES = sorted(set(name for name, class_ in XBlock.load_classes()) - set(COMPONENT_TYPES))
|
||||
ADVANCED_COMPONENT_TYPES = sorted({name for name, class_ in XBlock.load_classes()} - set(COMPONENT_TYPES))
|
||||
|
||||
ADVANCED_PROBLEM_TYPES = settings.ADVANCED_PROBLEM_TYPES
|
||||
|
||||
@@ -155,10 +154,10 @@ def container_handler(request, usage_key_string):
|
||||
|
||||
assert unit is not None, "Could not determine unit page"
|
||||
subsection = get_parent_xblock(unit)
|
||||
assert subsection is not None, "Could not determine parent subsection from unit " + six.text_type(
|
||||
assert subsection is not None, "Could not determine parent subsection from unit " + str(
|
||||
unit.location)
|
||||
section = get_parent_xblock(subsection)
|
||||
assert section is not None, "Could not determine ancestor section from unit " + six.text_type(unit.location)
|
||||
assert section is not None, "Could not determine ancestor section from unit " + str(unit.location)
|
||||
|
||||
# for the sequence navigator
|
||||
prev_url, next_url = get_sibling_urls(subsection)
|
||||
@@ -270,7 +269,7 @@ def get_component_templates(courselike, library=False): # lint-amnesty, pylint:
|
||||
return {
|
||||
"show_legend": XBlockStudioConfigurationFlag.is_enabled(),
|
||||
"allow_unsupported_xblocks": allow_unsupported,
|
||||
"documentation_label": _(u"{platform_name} Support Levels:").format(platform_name=settings.PLATFORM_NAME)
|
||||
"documentation_label": _("{platform_name} Support Levels:").format(platform_name=settings.PLATFORM_NAME)
|
||||
}
|
||||
|
||||
component_display_names = {
|
||||
@@ -367,7 +366,7 @@ def get_component_templates(courselike, library=False): # lint-amnesty, pylint:
|
||||
try:
|
||||
component_display_name = xblock_type_display_name(component)
|
||||
except PluginMissingError:
|
||||
log.warning(u'Unable to load xblock type %s to read display_name', component, exc_info=True)
|
||||
log.warning('Unable to load xblock type %s to read display_name', component, exc_info=True)
|
||||
else:
|
||||
templates_for_category.append(
|
||||
create_template_dict(
|
||||
@@ -425,12 +424,12 @@ def get_component_templates(courselike, library=False): # lint-amnesty, pylint:
|
||||
# prevents any authors from trying to instantiate the
|
||||
# non-existent component type by not showing it in the menu
|
||||
log.warning(
|
||||
u"Advanced component %s does not exist. It will not be added to the Studio new component menu.",
|
||||
"Advanced component %s does not exist. It will not be added to the Studio new component menu.",
|
||||
category
|
||||
)
|
||||
else:
|
||||
log.error(
|
||||
u"Improper format for course advanced keys! %s",
|
||||
"Improper format for course advanced keys! %s",
|
||||
course_advanced_keys
|
||||
)
|
||||
if advanced_component_templates['templates']:
|
||||
@@ -503,7 +502,7 @@ def component_handler(request, usage_key_string, handler, suffix=''):
|
||||
handler_descriptor.xmodule_runtime = StudioEditModuleRuntime(request.user)
|
||||
resp = handler_descriptor.handle(handler, req, suffix)
|
||||
except NoSuchHandlerError:
|
||||
log.info(u"XBlock %s attempted to access missing handler %r", handler_descriptor, handler, exc_info=True)
|
||||
log.info("XBlock %s attempted to access missing handler %r", handler_descriptor, handler, exc_info=True)
|
||||
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from
|
||||
|
||||
# unintentional update to handle any side effects of handle call
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Views related to operations on course objects
|
||||
"""
|
||||
# pylint: disable=filter-builtin-not-iterating
|
||||
|
||||
|
||||
import copy
|
||||
@@ -12,7 +13,6 @@ import string
|
||||
from collections import defaultdict
|
||||
|
||||
import django.utils
|
||||
import six
|
||||
from ccx_keys.locator import CCXLocator
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
@@ -23,6 +23,7 @@ from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.views.decorators.http import require_GET, require_http_methods
|
||||
from edx_django_utils.monitoring import function_trace
|
||||
from edx_toggles.toggles import LegacyWaffleSwitchNamespace
|
||||
from milestones import api as milestones_api
|
||||
from opaque_keys import InvalidKeyError
|
||||
@@ -30,9 +31,6 @@ from opaque_keys.edx.keys import CourseKey
|
||||
from opaque_keys.edx.locator import BlockUsageLocator
|
||||
from organizations.api import add_organization_course, ensure_organization
|
||||
from organizations.exceptions import InvalidOrganizationException
|
||||
from six import text_type
|
||||
from six.moves import filter
|
||||
from edx_django_utils.monitoring import function_trace
|
||||
|
||||
from cms.djangoapps.course_creators.views import add_user_with_status_unrequested, get_course_creator_status
|
||||
from cms.djangoapps.models.settings.course_grading import CourseGradingModel
|
||||
@@ -42,6 +40,27 @@ from common.djangoapps.course_action_state.managers import CourseActionStateItem
|
||||
from common.djangoapps.course_action_state.models import CourseRerunState, CourseRerunUIStateManager
|
||||
from common.djangoapps.course_modes.models import CourseMode
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_response
|
||||
from common.djangoapps.student import auth
|
||||
from common.djangoapps.student.auth import has_course_author_access, has_studio_read_access, has_studio_write_access
|
||||
from common.djangoapps.student.roles import (
|
||||
CourseCreatorRole,
|
||||
CourseInstructorRole,
|
||||
CourseStaffRole,
|
||||
GlobalStaff,
|
||||
UserBasedRole
|
||||
)
|
||||
from common.djangoapps.util.course import get_link_for_about_page
|
||||
from common.djangoapps.util.date_utils import get_default_time_display
|
||||
from common.djangoapps.util.json_request import JsonResponse, JsonResponseBadRequest, expect_json
|
||||
from common.djangoapps.util.milestones_helpers import (
|
||||
is_prerequisite_courses_enabled,
|
||||
is_valid_course_key,
|
||||
remove_prerequisite_course,
|
||||
set_prerequisite_courses
|
||||
)
|
||||
from common.djangoapps.util.string_utils import _has_non_ascii_characters
|
||||
from common.djangoapps.xblock_django.api import deprecated_xblocks
|
||||
from openedx.core import toggles as core_toggles
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from openedx.core.djangoapps.credit.api import get_credit_requirements, is_credit_course
|
||||
from openedx.core.djangoapps.credit.tasks import update_credit_course_requirements
|
||||
@@ -54,23 +73,6 @@ from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
from openedx.features.content_type_gating.partitions import CONTENT_TYPE_GATING_SCHEME
|
||||
from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML
|
||||
from openedx.features.course_experience.waffle import waffle as course_experience_waffle
|
||||
from common.djangoapps.student import auth
|
||||
from common.djangoapps.student.auth import has_course_author_access, has_studio_read_access, has_studio_write_access
|
||||
from common.djangoapps.student.roles import (
|
||||
CourseCreatorRole, CourseInstructorRole, CourseStaffRole, GlobalStaff, UserBasedRole
|
||||
)
|
||||
from common.djangoapps.util.course import get_link_for_about_page
|
||||
from common.djangoapps.util.date_utils import get_default_time_display
|
||||
from common.djangoapps.util.json_request import JsonResponse, JsonResponseBadRequest, expect_json
|
||||
from common.djangoapps.util.milestones_helpers import (
|
||||
is_prerequisite_courses_enabled,
|
||||
is_valid_course_key,
|
||||
remove_prerequisite_course,
|
||||
set_prerequisite_courses
|
||||
)
|
||||
from openedx.core import toggles as core_toggles
|
||||
from common.djangoapps.util.string_utils import _has_non_ascii_characters
|
||||
from common.djangoapps.xblock_django.api import deprecated_xblocks
|
||||
from xmodule.contentstore.content import StaticContent
|
||||
from xmodule.course_module import DEFAULT_START_DATE, CourseFields
|
||||
from xmodule.error_module import ErrorBlock
|
||||
@@ -112,7 +114,6 @@ from .library import (
|
||||
should_redirect_to_library_authoring_mfe
|
||||
)
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -403,7 +404,7 @@ def _accessible_courses_summary_iter(request, org=None):
|
||||
courses_summary = [] if org == '' else CourseOverview.get_all_courses(orgs=[org])
|
||||
else:
|
||||
courses_summary = modulestore().get_course_summaries()
|
||||
courses_summary = six.moves.filter(course_filter, courses_summary)
|
||||
courses_summary = filter(course_filter, courses_summary)
|
||||
in_process_course_actions = get_in_process_course_actions(request)
|
||||
return courses_summary, in_process_course_actions
|
||||
|
||||
@@ -430,7 +431,7 @@ def _accessible_courses_iter(request):
|
||||
|
||||
return has_studio_read_access(request.user, course.id)
|
||||
|
||||
courses = six.moves.filter(course_filter, modulestore().get_courses())
|
||||
courses = filter(course_filter, modulestore().get_courses())
|
||||
|
||||
in_process_course_actions = get_in_process_course_actions(request)
|
||||
return courses, in_process_course_actions
|
||||
@@ -458,7 +459,7 @@ def _accessible_courses_iter_for_tests(request):
|
||||
|
||||
return has_studio_read_access(request.user, course.id)
|
||||
|
||||
courses = six.moves.filter(course_filter, modulestore().get_course_summaries())
|
||||
courses = filter(course_filter, modulestore().get_course_summaries())
|
||||
|
||||
in_process_course_actions = get_in_process_course_actions(request)
|
||||
return courses, in_process_course_actions
|
||||
@@ -516,7 +517,7 @@ def course_listing(request):
|
||||
"""
|
||||
|
||||
optimization_enabled = GlobalStaff().has_user(request.user) and \
|
||||
LegacyWaffleSwitchNamespace(name=WAFFLE_NAMESPACE).is_enabled(u'enable_global_staff_optimization')
|
||||
LegacyWaffleSwitchNamespace(name=WAFFLE_NAMESPACE).is_enabled('enable_global_staff_optimization')
|
||||
|
||||
org = request.GET.get('org', '') if optimization_enabled else None
|
||||
courses_iter, in_process_course_actions = get_courses_accessible_to_user(request, org)
|
||||
@@ -530,27 +531,27 @@ def course_listing(request):
|
||||
Return a dict of the data which the view requires for each unsucceeded course
|
||||
"""
|
||||
return {
|
||||
u'display_name': uca.display_name,
|
||||
u'course_key': six.text_type(uca.course_key),
|
||||
u'org': uca.course_key.org,
|
||||
u'number': uca.course_key.course,
|
||||
u'run': uca.course_key.run,
|
||||
u'is_failed': True if uca.state == CourseRerunUIStateManager.State.FAILED else False, # lint-amnesty, pylint: disable=simplifiable-if-expression
|
||||
u'is_in_progress': True if uca.state == CourseRerunUIStateManager.State.IN_PROGRESS else False, # lint-amnesty, pylint: disable=simplifiable-if-expression
|
||||
u'dismiss_link': reverse_course_url(
|
||||
u'course_notifications_handler',
|
||||
'display_name': uca.display_name,
|
||||
'course_key': str(uca.course_key),
|
||||
'org': uca.course_key.org,
|
||||
'number': uca.course_key.course,
|
||||
'run': uca.course_key.run,
|
||||
'is_failed': uca.state == CourseRerunUIStateManager.State.FAILED,
|
||||
'is_in_progress': uca.state == CourseRerunUIStateManager.State.IN_PROGRESS,
|
||||
'dismiss_link': reverse_course_url(
|
||||
'course_notifications_handler',
|
||||
uca.course_key,
|
||||
kwargs={
|
||||
u'action_state_id': uca.id,
|
||||
'action_state_id': uca.id,
|
||||
},
|
||||
) if uca.state == CourseRerunUIStateManager.State.FAILED else u''
|
||||
) if uca.state == CourseRerunUIStateManager.State.FAILED else ''
|
||||
}
|
||||
|
||||
split_archived = settings.FEATURES.get(u'ENABLE_SEPARATE_ARCHIVED_COURSES', False)
|
||||
split_archived = settings.FEATURES.get('ENABLE_SEPARATE_ARCHIVED_COURSES', False)
|
||||
active_courses, archived_courses = _process_courses_list(courses_iter, in_process_course_actions, split_archived)
|
||||
in_process_course_actions = [format_in_process_course_view(uca) for uca in in_process_course_actions]
|
||||
|
||||
return render_to_response(u'index.html', {
|
||||
return render_to_response('index.html', {
|
||||
'courses': active_courses,
|
||||
'split_studio_home': split_library_view_on_dashboard(),
|
||||
'archived_courses': archived_courses,
|
||||
@@ -564,8 +565,8 @@ def course_listing(request):
|
||||
'request_course_creator_url': reverse('request_course_creator'),
|
||||
'course_creator_status': _get_course_creator_status(user),
|
||||
'rerun_creator_status': GlobalStaff().has_user(user),
|
||||
'allow_unicode_course_id': settings.FEATURES.get(u'ALLOW_UNICODE_COURSE_ID', False),
|
||||
'allow_course_reruns': settings.FEATURES.get(u'ALLOW_COURSE_RERUNS', True),
|
||||
'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False),
|
||||
'allow_course_reruns': settings.FEATURES.get('ALLOW_COURSE_RERUNS', True),
|
||||
'optimization_enabled': optimization_enabled,
|
||||
'active_tab': 'courses'
|
||||
})
|
||||
@@ -589,7 +590,7 @@ def library_listing(request):
|
||||
'course_creator_status': _get_course_creator_status(request.user),
|
||||
'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False),
|
||||
'archived_courses': True,
|
||||
'allow_course_reruns': settings.FEATURES.get(u'ALLOW_COURSE_RERUNS', True),
|
||||
'allow_course_reruns': settings.FEATURES.get('ALLOW_COURSE_RERUNS', True),
|
||||
'rerun_creator_status': GlobalStaff().has_user(request.user),
|
||||
'split_studio_home': split_library_view_on_dashboard(),
|
||||
'active_tab': 'libraries'
|
||||
@@ -604,8 +605,8 @@ def _format_library_for_view(library, request):
|
||||
|
||||
return {
|
||||
'display_name': library.display_name,
|
||||
'library_key': six.text_type(library.location.library_key),
|
||||
'url': reverse_library_url(u'library_handler', six.text_type(library.location.library_key)),
|
||||
'library_key': str(library.location.library_key),
|
||||
'url': reverse_library_url('library_handler', str(library.location.library_key)),
|
||||
'org': library.display_org_with_default,
|
||||
'number': library.display_number_with_default,
|
||||
'can_edit': has_studio_write_access(request.user, library.location.library_key),
|
||||
@@ -673,7 +674,7 @@ def course_index(request, course_key):
|
||||
reindex_link = None
|
||||
if settings.FEATURES.get('ENABLE_COURSEWARE_INDEX', False):
|
||||
if GlobalStaff().has_user(request.user):
|
||||
reindex_link = "/course/{course_id}/search_reindex".format(course_id=six.text_type(course_key))
|
||||
reindex_link = "/course/{course_id}/search_reindex".format(course_id=str(course_key))
|
||||
sections = course_module.get_children()
|
||||
course_structure = _course_outline_json(request, course_module)
|
||||
locator_to_show = request.GET.get('show', None)
|
||||
@@ -774,7 +775,7 @@ def _process_courses_list(courses_iter, in_process_course_actions, split_archive
|
||||
"""
|
||||
return {
|
||||
'display_name': course.display_name,
|
||||
'course_key': six.text_type(course.location.course_key),
|
||||
'course_key': str(course.location.course_key),
|
||||
'url': reverse_course_url('course_handler', course.id),
|
||||
'lms_link': get_lms_link_for_item(course.location),
|
||||
'rerun_link': _get_rerun_link_for_item(course.id),
|
||||
@@ -875,7 +876,7 @@ def _create_or_rerun_course(request):
|
||||
# existing xml courses this cannot be changed in CourseBlock.
|
||||
# # TODO get rid of defining wiki slug in this org/course/run specific way and reconcile
|
||||
# w/ xmodule.course_module.CourseBlock.__init__
|
||||
wiki_slug = u"{0}.{1}.{2}".format(org, course, run)
|
||||
wiki_slug = f"{org}.{course}.{run}"
|
||||
definition_data = {'wiki_slug': wiki_slug}
|
||||
fields.update(definition_data)
|
||||
|
||||
@@ -885,17 +886,17 @@ def _create_or_rerun_course(request):
|
||||
destination_course_key = rerun_course(request.user, source_course_key, org, course, run, fields)
|
||||
return JsonResponse({
|
||||
'url': reverse_url('course_handler'),
|
||||
'destination_course_key': six.text_type(destination_course_key)
|
||||
'destination_course_key': str(destination_course_key)
|
||||
})
|
||||
else:
|
||||
try:
|
||||
new_course = create_new_course(request.user, org, course, run, fields)
|
||||
return JsonResponse({
|
||||
'url': reverse_course_url('course_handler', new_course.id),
|
||||
'course_key': six.text_type(new_course.id),
|
||||
'course_key': str(new_course.id),
|
||||
})
|
||||
except ValidationError as ex:
|
||||
return JsonResponse({'error': text_type(ex)}, status=400)
|
||||
return JsonResponse({'error': str(ex)}, status=400)
|
||||
except DuplicateCourseError:
|
||||
return JsonResponse({
|
||||
'ErrMsg': _(
|
||||
@@ -912,7 +913,7 @@ def _create_or_rerun_course(request):
|
||||
})
|
||||
except InvalidKeyError as error:
|
||||
return JsonResponse({
|
||||
"ErrMsg": _(u"Unable to create course '{name}'.\n\n{err}").format(name=display_name, err=text_type(error))}
|
||||
"ErrMsg": _("Unable to create course '{name}'.\n\n{err}").format(name=display_name, err=str(error))}
|
||||
)
|
||||
|
||||
|
||||
@@ -997,7 +998,7 @@ def rerun_course(user, source_course_key, org, number, run, fields, background=T
|
||||
fields['video_upload_pipeline'] = {}
|
||||
|
||||
json_fields = json.dumps(fields, cls=EdxJSONEncoder)
|
||||
args = [six.text_type(source_course_key), six.text_type(destination_course_key), user.id, json_fields]
|
||||
args = [str(source_course_key), str(destination_course_key), user.id, json_fields]
|
||||
|
||||
if background:
|
||||
rerun_course_task.delay(*args)
|
||||
@@ -1306,7 +1307,7 @@ def grading_handler(request, course_key_string, grader_index=None):
|
||||
# update credit course requirements if 'minimum_grade_credit'
|
||||
# field value is changed
|
||||
if 'minimum_grade_credit' in request.json:
|
||||
update_credit_course_requirements.delay(six.text_type(course_key))
|
||||
update_credit_course_requirements.delay(str(course_key))
|
||||
|
||||
# None implies update the whole model (cutoffs, graceperiod, and graders) not a specific grader
|
||||
if grader_index is None:
|
||||
@@ -1419,7 +1420,7 @@ def advanced_settings_handler(request, course_key_string):
|
||||
# update the course tabs if required by any setting changes
|
||||
_refresh_course_tabs(request, course_module)
|
||||
except InvalidTabsException as err:
|
||||
log.exception(text_type(err))
|
||||
log.exception(str(err))
|
||||
response_message = [
|
||||
{
|
||||
'message': _('An error occurred while trying to save your tabs'),
|
||||
@@ -1438,7 +1439,7 @@ def advanced_settings_handler(request, course_key_string):
|
||||
# Handle all errors that validation doesn't catch
|
||||
except (TypeError, ValueError, InvalidTabsException) as err:
|
||||
return HttpResponseBadRequest(
|
||||
django.utils.html.escape(text_type(err)),
|
||||
django.utils.html.escape(str(err)),
|
||||
content_type="text/plain"
|
||||
)
|
||||
|
||||
@@ -1476,7 +1477,7 @@ def validate_textbook_json(textbook):
|
||||
"""
|
||||
if isinstance(textbook, (bytes, bytearray)): # data appears as bytes
|
||||
textbook = textbook.decode('utf-8')
|
||||
if isinstance(textbook, six.string_types):
|
||||
if isinstance(textbook, str):
|
||||
try:
|
||||
textbook = json.loads(textbook)
|
||||
except ValueError:
|
||||
@@ -1485,7 +1486,7 @@ def validate_textbook_json(textbook):
|
||||
raise TextbookValidationError("must be JSON object")
|
||||
if not textbook.get("tab_title"):
|
||||
raise TextbookValidationError("must have tab_title")
|
||||
tid = six.text_type(textbook.get("id", ""))
|
||||
tid = str(textbook.get("id", ""))
|
||||
if tid and not tid[0].isdigit():
|
||||
raise TextbookValidationError("textbook ID must start with a digit")
|
||||
return textbook
|
||||
@@ -1544,9 +1545,9 @@ def textbooks_list_handler(request, course_key_string):
|
||||
try:
|
||||
textbooks = validate_textbooks_json(request.body)
|
||||
except TextbookValidationError as err:
|
||||
return JsonResponse({"error": text_type(err)}, status=400)
|
||||
return JsonResponse({"error": str(err)}, status=400)
|
||||
|
||||
tids = set(t["id"] for t in textbooks if "id" in t)
|
||||
tids = {t["id"] for t in textbooks if "id" in t}
|
||||
for textbook in textbooks:
|
||||
if "id" not in textbook:
|
||||
tid = assign_textbook_id(textbook, tids)
|
||||
@@ -1563,9 +1564,9 @@ def textbooks_list_handler(request, course_key_string):
|
||||
try:
|
||||
textbook = validate_textbook_json(request.body)
|
||||
except TextbookValidationError as err:
|
||||
return JsonResponse({"error": text_type(err)}, status=400)
|
||||
return JsonResponse({"error": str(err)}, status=400)
|
||||
if not textbook.get("id"):
|
||||
tids = set(t["id"] for t in course.pdf_textbooks if "id" in t)
|
||||
tids = {t["id"] for t in course.pdf_textbooks if "id" in t}
|
||||
textbook["id"] = assign_textbook_id(textbook, tids)
|
||||
existing = course.pdf_textbooks
|
||||
existing.append(textbook)
|
||||
@@ -1602,7 +1603,7 @@ def textbooks_detail_handler(request, course_key_string, textbook_id):
|
||||
with store.bulk_operations(course_key):
|
||||
course_module = get_course_and_check_access(course_key, request.user)
|
||||
matching_id = [tb for tb in course_module.pdf_textbooks
|
||||
if six.text_type(tb.get("id")) == six.text_type(textbook_id)]
|
||||
if str(tb.get("id")) == str(textbook_id)]
|
||||
if matching_id:
|
||||
textbook = matching_id[0]
|
||||
else:
|
||||
@@ -1616,7 +1617,7 @@ def textbooks_detail_handler(request, course_key_string, textbook_id):
|
||||
try:
|
||||
new_textbook = validate_textbook_json(request.body)
|
||||
except TextbookValidationError as err:
|
||||
return JsonResponse({"error": text_type(err)}, status=400)
|
||||
return JsonResponse({"error": str(err)}, status=400)
|
||||
new_textbook["id"] = textbook_id
|
||||
if textbook:
|
||||
i = course_module.pdf_textbooks.index(textbook)
|
||||
@@ -1761,7 +1762,7 @@ def group_configurations_list_handler(request, course_key_string):
|
||||
try:
|
||||
new_configuration = GroupConfiguration(request.body, course).get_user_partition()
|
||||
except GroupConfigurationsValidationError as err:
|
||||
return JsonResponse({"error": text_type(err)}, status=400)
|
||||
return JsonResponse({"error": str(err)}, status=400)
|
||||
|
||||
course.user_partitions.append(new_configuration)
|
||||
response = JsonResponse(new_configuration.to_json(), status=201)
|
||||
@@ -1793,7 +1794,7 @@ def group_configurations_detail_handler(request, course_key_string, group_config
|
||||
with store.bulk_operations(course_key):
|
||||
course = get_course_and_check_access(course_key, request.user)
|
||||
matching_id = [p for p in course.user_partitions
|
||||
if six.text_type(p.id) == six.text_type(group_configuration_id)]
|
||||
if str(p.id) == str(group_configuration_id)]
|
||||
if matching_id:
|
||||
configuration = matching_id[0]
|
||||
else:
|
||||
@@ -1803,7 +1804,7 @@ def group_configurations_detail_handler(request, course_key_string, group_config
|
||||
try:
|
||||
new_configuration = GroupConfiguration(request.body, course, group_configuration_id).get_user_partition() # lint-amnesty, pylint: disable=line-too-long
|
||||
except GroupConfigurationsValidationError as err:
|
||||
return JsonResponse({"error": text_type(err)}, status=400)
|
||||
return JsonResponse({"error": str(err)}, status=400)
|
||||
|
||||
if configuration:
|
||||
index = course.user_partitions.index(configuration)
|
||||
|
||||
@@ -7,7 +7,6 @@ Intended to be utilized as an AJAX callback handler, versus a proper view/screen
|
||||
import logging
|
||||
from functools import wraps
|
||||
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpResponse, HttpResponseBadRequest
|
||||
@@ -17,10 +16,10 @@ from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
|
||||
from cms.djangoapps.models.settings.course_metadata import CourseMetadata
|
||||
from openedx.core.djangolib.js_utils import dump_js_escaped_json
|
||||
from common.djangoapps.student.auth import has_course_author_access
|
||||
from common.djangoapps.util import milestones_helpers
|
||||
from openedx.core import toggles as core_toggles
|
||||
from openedx.core.djangolib.js_utils import dump_js_escaped_json
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
@@ -132,7 +131,7 @@ def _create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct=N
|
||||
return HttpResponse(status=400)
|
||||
|
||||
# Create the entrance exam item (currently it's just a chapter)
|
||||
parent_locator = six.text_type(course.location)
|
||||
parent_locator = str(course.location)
|
||||
created_block = create_xblock(
|
||||
parent_locator=parent_locator,
|
||||
user=request.user,
|
||||
@@ -147,13 +146,13 @@ def _create_entrance_exam(request, course_key, entrance_exam_minimum_score_pct=N
|
||||
metadata = {
|
||||
'entrance_exam_enabled': True,
|
||||
'entrance_exam_minimum_score_pct': entrance_exam_minimum_score_pct,
|
||||
'entrance_exam_id': six.text_type(created_block.location),
|
||||
'entrance_exam_id': str(created_block.location),
|
||||
}
|
||||
CourseMetadata.update_from_dict(metadata, course, request.user)
|
||||
|
||||
# Create the entrance exam section item.
|
||||
create_xblock(
|
||||
parent_locator=six.text_type(created_block.location),
|
||||
parent_locator=str(created_block.location),
|
||||
user=request.user,
|
||||
category='sequential',
|
||||
display_name=_('Entrance Exam - Subsection')
|
||||
@@ -179,7 +178,7 @@ def _get_entrance_exam(request, course_key):
|
||||
try:
|
||||
exam_descriptor = modulestore().get_item(exam_key)
|
||||
return HttpResponse( # lint-amnesty, pylint: disable=http-response-with-content-type-json
|
||||
dump_js_escaped_json({'locator': six.text_type(exam_descriptor.location)}),
|
||||
dump_js_escaped_json({'locator': str(exam_descriptor.location)}),
|
||||
status=200, content_type='application/json')
|
||||
except ItemNotFoundError:
|
||||
return HttpResponse(status=404)
|
||||
@@ -248,7 +247,7 @@ def add_entrance_exam_milestone(course_id, x_block): # lint-amnesty, pylint: di
|
||||
if len(milestones): # lint-amnesty, pylint: disable=len-as-condition
|
||||
milestone = milestones[0]
|
||||
else:
|
||||
description = u'Autogenerated during {} entrance exam creation.'.format(six.text_type(course_id))
|
||||
description = 'Autogenerated during {} entrance exam creation.'.format(str(course_id))
|
||||
milestone = milestones_helpers.add_milestone({
|
||||
'name': _('Completed Course Entrance Exam'),
|
||||
'namespace': milestone_namespace,
|
||||
@@ -256,13 +255,13 @@ def add_entrance_exam_milestone(course_id, x_block): # lint-amnesty, pylint: di
|
||||
})
|
||||
relationship_types = milestones_helpers.get_milestone_relationship_types()
|
||||
milestones_helpers.add_course_milestone(
|
||||
six.text_type(course_id),
|
||||
str(course_id),
|
||||
relationship_types['REQUIRES'],
|
||||
milestone
|
||||
)
|
||||
milestones_helpers.add_course_content_milestone(
|
||||
six.text_type(course_id),
|
||||
six.text_type(x_block.location),
|
||||
str(course_id),
|
||||
str(x_block.location),
|
||||
relationship_types['FULFILLS'],
|
||||
milestone
|
||||
)
|
||||
@@ -279,4 +278,4 @@ def remove_entrance_exam_milestone_reference(request, course_key):
|
||||
for course_child in course_children:
|
||||
if course_child.is_entrance_exam:
|
||||
delete_item(request, course_child.scope_ids.usage_id)
|
||||
milestones_helpers.remove_content_references(six.text_type(course_child.scope_ids.usage_id))
|
||||
milestones_helpers.remove_content_references(str(course_child.scope_ids.usage_id))
|
||||
|
||||
@@ -4,8 +4,8 @@ import functools
|
||||
from django.http import HttpResponse, HttpResponseNotFound, HttpResponseServerError
|
||||
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_response, render_to_string
|
||||
from openedx.core.djangolib.js_utils import dump_js_escaped_json
|
||||
from common.djangoapps.util.views import fix_crum_request
|
||||
from openedx.core.djangolib.js_utils import dump_js_escaped_json
|
||||
|
||||
__all__ = ['not_found', 'server_error', 'render_404', 'render_500']
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ the giturl attribute is set.
|
||||
|
||||
import logging
|
||||
|
||||
import six
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.utils.translation import ugettext as _
|
||||
@@ -34,7 +33,7 @@ def export_git(request, course_key_string):
|
||||
course_module = modulestore().get_course(course_key)
|
||||
failed = False
|
||||
|
||||
log.debug(u'export_git course_module=%s', course_module)
|
||||
log.debug('export_git course_module=%s', course_module)
|
||||
|
||||
msg = ""
|
||||
if 'action' in request.GET and course_module.giturl:
|
||||
@@ -48,7 +47,7 @@ def export_git(request, course_key_string):
|
||||
msg = _('Course successfully exported to git repository')
|
||||
except git_export_utils.GitExportError as ex:
|
||||
failed = True
|
||||
msg = six.text_type(ex)
|
||||
msg = str(ex)
|
||||
|
||||
return render_to_response('export_git.html', {
|
||||
'context_course': course_module,
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
Helper methods for Studio views.
|
||||
"""
|
||||
|
||||
import urllib
|
||||
from uuid import uuid4
|
||||
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import ugettext as _
|
||||
@@ -12,8 +12,8 @@ from opaque_keys.edx.keys import UsageKey
|
||||
from xblock.core import XBlock
|
||||
|
||||
from cms.djangoapps.models.settings.course_grading import CourseGradingModel
|
||||
from openedx.core.toggles import ENTRANCE_EXAMS
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_string
|
||||
from openedx.core.toggles import ENTRANCE_EXAMS
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.tabs import StaticTab
|
||||
|
||||
@@ -107,9 +107,9 @@ def xblock_studio_url(xblock, parent_xblock=None):
|
||||
if category == 'course':
|
||||
return reverse_course_url('course_handler', xblock.location.course_key)
|
||||
elif category in ('chapter', 'sequential'):
|
||||
return u'{url}?show={usage_key}'.format(
|
||||
return '{url}?show={usage_key}'.format(
|
||||
url=reverse_course_url('course_handler', xblock.location.course_key),
|
||||
usage_key=six.moves.urllib.parse.quote(six.text_type(xblock.location))
|
||||
usage_key=urllib.parse.quote(str(xblock.location))
|
||||
)
|
||||
elif category == 'library':
|
||||
library_key = xblock.location.course_key
|
||||
@@ -220,7 +220,7 @@ def create_xblock(parent_locator, user, category, display_name, boilerplate=None
|
||||
|
||||
# TODO need to fix components that are sending definition_data as strings, instead of as dicts
|
||||
# For now, migrate them into dicts here.
|
||||
if isinstance(data, six.string_types):
|
||||
if isinstance(data, str):
|
||||
data = {'data': data}
|
||||
|
||||
created_block = store.create_child(
|
||||
|
||||
@@ -25,7 +25,6 @@ from django.views.decorators.http import require_GET, require_http_methods
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from opaque_keys.edx.locator import LibraryLocator
|
||||
from path import Path as path
|
||||
from six import text_type
|
||||
from storages.backends.s3boto import S3BotoStorage
|
||||
from storages.backends.s3boto3 import S3Boto3Storage
|
||||
from user_tasks.conf import settings as user_tasks_settings
|
||||
@@ -123,7 +122,7 @@ def _write_chunk(request, courselike_key):
|
||||
course_dir = data_root / subdir
|
||||
filename = request.FILES['course-data'].name
|
||||
|
||||
courselike_string = text_type(courselike_key) + filename
|
||||
courselike_string = str(courselike_key) + filename
|
||||
# Do everything in a try-except block to make sure everything is properly cleaned up.
|
||||
try:
|
||||
# Use sessions to keep info about import progress
|
||||
@@ -143,7 +142,7 @@ def _write_chunk(request, courselike_key):
|
||||
if not course_dir.isdir():
|
||||
os.mkdir(course_dir)
|
||||
|
||||
logging.debug(u'importing course to {0}'.format(temp_filepath))
|
||||
logging.debug(f'importing course to {temp_filepath}')
|
||||
|
||||
# Get upload chunks byte ranges
|
||||
try:
|
||||
@@ -165,7 +164,7 @@ def _write_chunk(request, courselike_key):
|
||||
if size < int(content_range['start']):
|
||||
_save_request_status(request, courselike_string, -1)
|
||||
log.warning(
|
||||
u"Reported range %s does not match size downloaded so far %s",
|
||||
"Reported range %s does not match size downloaded so far %s",
|
||||
content_range['start'],
|
||||
size
|
||||
)
|
||||
@@ -200,19 +199,19 @@ def _write_chunk(request, courselike_key):
|
||||
}]
|
||||
})
|
||||
|
||||
log.info(u"Course import %s: Upload complete", courselike_key)
|
||||
log.info("Course import %s: Upload complete", courselike_key)
|
||||
with open(temp_filepath, 'rb') as local_file:
|
||||
django_file = File(local_file)
|
||||
storage_path = course_import_export_storage.save(u'olx_import/' + filename, django_file)
|
||||
storage_path = course_import_export_storage.save('olx_import/' + filename, django_file)
|
||||
import_olx.delay(
|
||||
request.user.id, text_type(courselike_key), storage_path, filename, request.LANGUAGE_CODE)
|
||||
request.user.id, str(courselike_key), storage_path, filename, request.LANGUAGE_CODE)
|
||||
|
||||
# Send errors to client with stage at which error occurred.
|
||||
except Exception as exception: # pylint: disable=broad-except
|
||||
_save_request_status(request, courselike_string, -1)
|
||||
if course_dir.isdir():
|
||||
shutil.rmtree(course_dir)
|
||||
log.info(u"Course import %s: Temp data cleared", courselike_key)
|
||||
log.info("Course import %s: Temp data cleared", courselike_key)
|
||||
|
||||
log.exception(
|
||||
"error importing course"
|
||||
@@ -250,12 +249,12 @@ def import_status_handler(request, course_key_string, filename=None):
|
||||
raise PermissionDenied()
|
||||
|
||||
# The task status record is authoritative once it's been created
|
||||
args = {u'course_key_string': course_key_string, u'archive_name': filename}
|
||||
args = {'course_key_string': course_key_string, 'archive_name': filename}
|
||||
name = CourseImportTask.generate_name(args)
|
||||
task_status = UserTaskStatus.objects.filter(name=name)
|
||||
for status_filter in STATUS_FILTERS:
|
||||
task_status = status_filter().filter_queryset(request, task_status, import_status_handler)
|
||||
task_status = task_status.order_by(u'-created').first()
|
||||
task_status = task_status.order_by('-created').first()
|
||||
if task_status is None:
|
||||
# The task hasn't been initialized yet; did we store info in the session already?
|
||||
try:
|
||||
@@ -279,7 +278,7 @@ def send_tarball(tarball, size):
|
||||
"""
|
||||
wrapper = FileWrapper(tarball, settings.COURSE_EXPORT_DOWNLOAD_CHUNK_SIZE)
|
||||
response = StreamingHttpResponse(wrapper, content_type='application/x-tgz')
|
||||
response['Content-Disposition'] = u'attachment; filename=%s' % os.path.basename(tarball.name)
|
||||
response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(tarball.name)
|
||||
response['Content-Length'] = size
|
||||
return response
|
||||
|
||||
@@ -377,7 +376,7 @@ def export_status_handler(request, course_key_string):
|
||||
output_url = reverse_course_url('export_output_handler', course_key)
|
||||
elif isinstance(artifact.file.storage, S3BotoStorage):
|
||||
filename = os.path.basename(artifact.file.name)
|
||||
disposition = u'attachment; filename="{}"'.format(filename)
|
||||
disposition = f'attachment; filename="{filename}"'
|
||||
output_url = artifact.file.storage.url(artifact.file.name, response_headers={
|
||||
'response-content-disposition': disposition,
|
||||
'response-content-encoding': 'application/octet-stream',
|
||||
@@ -385,7 +384,7 @@ def export_status_handler(request, course_key_string):
|
||||
})
|
||||
elif isinstance(artifact.file.storage, S3Boto3Storage):
|
||||
filename = os.path.basename(artifact.file.name)
|
||||
disposition = u'attachment; filename="{}"'.format(filename)
|
||||
disposition = f'attachment; filename="{filename}"'
|
||||
output_url = artifact.file.storage.url(artifact.file.name, parameters={
|
||||
'ResponseContentDisposition': disposition,
|
||||
'ResponseContentEncoding': 'application/octet-stream',
|
||||
@@ -450,9 +449,9 @@ def _latest_task_status(request, course_key_string, view_func=None):
|
||||
Get the most recent export status update for the specified course/library
|
||||
key.
|
||||
"""
|
||||
args = {u'course_key_string': course_key_string}
|
||||
args = {'course_key_string': course_key_string}
|
||||
name = CourseExportTask.generate_name(args)
|
||||
task_status = UserTaskStatus.objects.filter(name=name)
|
||||
for status_filter in STATUS_FILTERS:
|
||||
task_status = status_filter().filter_queryset(request, task_status, view_func)
|
||||
return task_status.order_by(u'-created').first()
|
||||
return task_status.order_by('-created').first()
|
||||
|
||||
@@ -24,7 +24,6 @@ from help_tokens.core import HelpUrlExpert
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from opaque_keys.edx.locator import LibraryUsageLocator
|
||||
from pytz import UTC
|
||||
from six import binary_type, text_type
|
||||
from web_fragments.fragment import Fragment
|
||||
from xblock.core import XBlock
|
||||
from xblock.fields import Scope
|
||||
@@ -34,15 +33,15 @@ from cms.djangoapps.models.settings.course_grading import CourseGradingModel
|
||||
from cms.djangoapps.xblock_config.models import CourseEditLTIFieldsEnabledFlag
|
||||
from cms.lib.xblock.authoring_mixin import VISIBILITY_VIEW
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_string
|
||||
from openedx.core.lib.gating import api as gating_api
|
||||
from openedx.core.lib.xblock_utils import hash_resource, request_token, wrap_xblock, wrap_xblock_aside
|
||||
from openedx.core.djangoapps.bookmarks import api as bookmarks_api
|
||||
from common.djangoapps.static_replace import replace_static_urls
|
||||
from common.djangoapps.student.auth import has_studio_read_access, has_studio_write_access
|
||||
from openedx.core.toggles import ENTRANCE_EXAMS
|
||||
from common.djangoapps.util.date_utils import get_default_time_display
|
||||
from common.djangoapps.util.json_request import JsonResponse, expect_json
|
||||
from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService
|
||||
from openedx.core.djangoapps.bookmarks import api as bookmarks_api
|
||||
from openedx.core.lib.gating import api as gating_api
|
||||
from openedx.core.lib.xblock_utils import hash_resource, request_token, wrap_xblock, wrap_xblock_aside
|
||||
from openedx.core.toggles import ENTRANCE_EXAMS
|
||||
from xmodule.course_module import DEFAULT_START_DATE
|
||||
from xmodule.library_tools import LibraryToolsService
|
||||
from xmodule.modulestore import EdxJSONEncoder, ModuleStoreEnum
|
||||
@@ -96,7 +95,7 @@ def _filter_entrance_exam_grader(graders):
|
||||
the grader type for a given section of a course
|
||||
"""
|
||||
if ENTRANCE_EXAMS.is_enabled():
|
||||
graders = [grader for grader in graders if grader.get('type') != u'Entrance Exam']
|
||||
graders = [grader for grader in graders if grader.get('type') != 'Entrance Exam']
|
||||
return graders
|
||||
|
||||
|
||||
@@ -230,7 +229,7 @@ def xblock_handler(request, usage_key_string):
|
||||
_is_library_component_limit_reached(parent_usage_key)):
|
||||
return JsonResponse(
|
||||
{
|
||||
'error': _(u'Libraries cannot have more than {limit} components').format(
|
||||
'error': _('Libraries cannot have more than {limit} components').format(
|
||||
limit=settings.MAX_BLOCKS_PER_CONTENT_LIBRARY
|
||||
)
|
||||
},
|
||||
@@ -244,8 +243,8 @@ def xblock_handler(request, usage_key_string):
|
||||
request.json.get('display_name'),
|
||||
)
|
||||
return JsonResponse({
|
||||
'locator': text_type(dest_usage_key),
|
||||
'courseKey': text_type(dest_usage_key.course_key)
|
||||
'locator': str(dest_usage_key),
|
||||
'courseKey': str(dest_usage_key.course_key)
|
||||
})
|
||||
else:
|
||||
return _create_item(request)
|
||||
@@ -269,7 +268,7 @@ def xblock_handler(request, usage_key_string):
|
||||
)
|
||||
|
||||
|
||||
class StudioPermissionsService(object):
|
||||
class StudioPermissionsService:
|
||||
"""
|
||||
Service that can provide information about a user's permissions.
|
||||
|
||||
@@ -289,7 +288,7 @@ class StudioPermissionsService(object):
|
||||
return has_studio_write_access(self._user, course_key)
|
||||
|
||||
|
||||
class StudioEditModuleRuntime(object):
|
||||
class StudioEditModuleRuntime:
|
||||
"""
|
||||
An extremely minimal ModuleSystem shim used for XBlock edits and studio_view.
|
||||
(i.e. whenever we're not using PreviewModuleSystem.) This is required to make information
|
||||
@@ -321,7 +320,7 @@ class StudioEditModuleRuntime(object):
|
||||
return None
|
||||
|
||||
|
||||
@require_http_methods(("GET"))
|
||||
@require_http_methods("GET")
|
||||
@login_required
|
||||
@expect_json
|
||||
def xblock_view_handler(request, usage_key_string, view_name):
|
||||
@@ -349,14 +348,14 @@ def xblock_view_handler(request, usage_key_string, view_name):
|
||||
xblock.runtime.wrappers.append(partial(
|
||||
wrap_xblock,
|
||||
'StudioRuntime',
|
||||
usage_id_serializer=text_type,
|
||||
usage_id_serializer=str,
|
||||
request_token=request_token(request),
|
||||
))
|
||||
|
||||
xblock.runtime.wrappers_asides.append(partial(
|
||||
wrap_xblock_aside,
|
||||
'StudioRuntime',
|
||||
usage_id_serializer=text_type,
|
||||
usage_id_serializer=str,
|
||||
request_token=request_token(request),
|
||||
extra_classes=['wrapper-comp-plugins']
|
||||
))
|
||||
@@ -371,7 +370,7 @@ def xblock_view_handler(request, usage_key_string, view_name):
|
||||
# dungeon and surface as uneditable, unsaveable, and undeletable
|
||||
# component-goblins.
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
log.debug(u"Unable to render %s for %r", view_name, xblock, exc_info=True)
|
||||
log.debug("Unable to render %s for %r", view_name, xblock, exc_info=True)
|
||||
fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)}))
|
||||
|
||||
elif view_name in PREVIEW_VIEWS + container_views:
|
||||
@@ -395,8 +394,8 @@ def xblock_view_handler(request, usage_key_string, view_name):
|
||||
}
|
||||
except ValueError:
|
||||
return HttpResponse(
|
||||
content=u"Couldn't parse paging parameters: enable_paging: "
|
||||
u"{0}, page_number: {1}, page_size: {2}".format(
|
||||
content="Couldn't parse paging parameters: enable_paging: "
|
||||
"{}, page_number: {}, page_size: {}".format(
|
||||
request.GET.get('enable_paging', 'false'),
|
||||
request.GET.get('page_number', 0),
|
||||
request.GET.get('page_size', 0)
|
||||
@@ -440,7 +439,7 @@ def xblock_view_handler(request, usage_key_string, view_name):
|
||||
hashed_resources[hash_resource(resource)] = resource._asdict()
|
||||
|
||||
fragment_content = fragment.content
|
||||
if isinstance(fragment_content, binary_type):
|
||||
if isinstance(fragment_content, bytes):
|
||||
fragment_content = fragment.content.decode('utf-8')
|
||||
|
||||
return JsonResponse({
|
||||
@@ -452,7 +451,7 @@ def xblock_view_handler(request, usage_key_string, view_name):
|
||||
return HttpResponse(status=406)
|
||||
|
||||
|
||||
@require_http_methods(("GET"))
|
||||
@require_http_methods("GET")
|
||||
@login_required
|
||||
@expect_json
|
||||
def xblock_outline_handler(request, usage_key_string):
|
||||
@@ -480,7 +479,7 @@ def xblock_outline_handler(request, usage_key_string):
|
||||
return Http404
|
||||
|
||||
|
||||
@require_http_methods(("GET"))
|
||||
@require_http_methods("GET")
|
||||
@login_required
|
||||
@expect_json
|
||||
def xblock_container_handler(request, usage_key_string):
|
||||
@@ -540,7 +539,7 @@ def _save_xblock(user, xblock, data=None, children_strings=None, metadata=None,
|
||||
store.revert_to_published(xblock.location, user.id)
|
||||
# Returning the same sort of result that we do for other save operations. In the future,
|
||||
# we may want to return the full XBlockInfo.
|
||||
return JsonResponse({'id': text_type(xblock.location)})
|
||||
return JsonResponse({'id': str(xblock.location)})
|
||||
|
||||
old_metadata = own_metadata(xblock)
|
||||
old_content = xblock.get_explicitly_set_fields_by_scope(Scope.content)
|
||||
@@ -619,8 +618,8 @@ def _save_xblock(user, xblock, data=None, children_strings=None, metadata=None,
|
||||
value = field.from_json(value)
|
||||
except ValueError as verr:
|
||||
reason = _("Invalid data")
|
||||
if text_type(verr):
|
||||
reason = _(u"Invalid data ({details})").format(details=text_type(verr))
|
||||
if str(verr):
|
||||
reason = _("Invalid data ({details})").format(details=str(verr))
|
||||
return JsonResponse({"error": reason}, 400)
|
||||
|
||||
field.write_to(xblock, value)
|
||||
@@ -647,7 +646,7 @@ def _save_xblock(user, xblock, data=None, children_strings=None, metadata=None,
|
||||
store.update_item(course, user.id)
|
||||
|
||||
result = {
|
||||
'id': text_type(xblock.location),
|
||||
'id': str(xblock.location),
|
||||
'data': data,
|
||||
'metadata': own_metadata(xblock)
|
||||
}
|
||||
@@ -712,13 +711,13 @@ def _create_item(request):
|
||||
# Only these categories are supported at this time.
|
||||
if category not in ['html', 'problem', 'video']:
|
||||
return HttpResponseBadRequest(
|
||||
u"Category '%s' not supported for Libraries" % category, content_type='text/plain'
|
||||
"Category '%s' not supported for Libraries" % category, content_type='text/plain'
|
||||
)
|
||||
|
||||
if _is_library_component_limit_reached(usage_key):
|
||||
return JsonResponse(
|
||||
{
|
||||
'error': _(u'Libraries cannot have more than {limit} components').format(
|
||||
'error': _('Libraries cannot have more than {limit} components').format(
|
||||
limit=settings.MAX_BLOCKS_PER_CONTENT_LIBRARY
|
||||
)
|
||||
},
|
||||
@@ -734,7 +733,7 @@ def _create_item(request):
|
||||
)
|
||||
|
||||
return JsonResponse(
|
||||
{'locator': text_type(created_block.location), 'courseKey': text_type(created_block.location.course_key)}
|
||||
{'locator': str(created_block.location), 'courseKey': str(created_block.location.course_key)}
|
||||
)
|
||||
|
||||
|
||||
@@ -766,7 +765,7 @@ def is_source_item_in_target_parents(source_item, target_parent):
|
||||
"""
|
||||
target_ancestors = _create_xblock_ancestor_info(target_parent, is_concise=True)['ancestors']
|
||||
for target_ancestor in target_ancestors:
|
||||
if text_type(source_item.location) == target_ancestor['id']:
|
||||
if str(source_item.location) == target_ancestor['id']:
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -786,7 +785,7 @@ def _move_item(source_usage_key, target_parent_usage_key, user, target_index=Non
|
||||
"""
|
||||
# Get the list of all parentable component type XBlocks.
|
||||
parent_component_types = list(
|
||||
set(name for name, class_ in XBlock.load_classes() if getattr(class_, 'has_children', False)) -
|
||||
{name for name, class_ in XBlock.load_classes() if getattr(class_, 'has_children', False)} -
|
||||
set(DIRECT_ONLY_CATEGORIES)
|
||||
)
|
||||
|
||||
@@ -810,7 +809,7 @@ def _move_item(source_usage_key, target_parent_usage_key, user, target_index=Non
|
||||
|
||||
if (valid_move_type.get(target_parent_type, '') != source_type and
|
||||
target_parent_type not in parent_component_types):
|
||||
error = _(u'You can not move {source_type} into {target_parent_type}.').format(
|
||||
error = _('You can not move {source_type} into {target_parent_type}.').format(
|
||||
source_type=source_type,
|
||||
target_parent_type=target_parent_type,
|
||||
)
|
||||
@@ -823,20 +822,20 @@ def _move_item(source_usage_key, target_parent_usage_key, user, target_index=Non
|
||||
elif target_parent_type == 'split_test':
|
||||
error = _('You can not move an item directly into content experiment.')
|
||||
elif source_index is None:
|
||||
error = _(u'{source_usage_key} not found in {parent_usage_key}.').format(
|
||||
source_usage_key=text_type(source_usage_key),
|
||||
parent_usage_key=text_type(source_parent.location)
|
||||
error = _('{source_usage_key} not found in {parent_usage_key}.').format(
|
||||
source_usage_key=str(source_usage_key),
|
||||
parent_usage_key=str(source_parent.location)
|
||||
)
|
||||
else:
|
||||
try:
|
||||
target_index = int(target_index) if target_index is not None else None
|
||||
if target_index is not None and len(target_parent.children) < target_index:
|
||||
error = _(u'You can not move {source_usage_key} at an invalid index ({target_index}).').format(
|
||||
source_usage_key=text_type(source_usage_key),
|
||||
error = _('You can not move {source_usage_key} at an invalid index ({target_index}).').format(
|
||||
source_usage_key=str(source_usage_key),
|
||||
target_index=target_index
|
||||
)
|
||||
except ValueError:
|
||||
error = _(u'You must provide target_index ({target_index}) as an integer.').format(
|
||||
error = _('You must provide target_index ({target_index}) as an integer.').format(
|
||||
target_index=target_index
|
||||
)
|
||||
if error:
|
||||
@@ -854,16 +853,16 @@ def _move_item(source_usage_key, target_parent_usage_key, user, target_index=Non
|
||||
)
|
||||
|
||||
log.info(
|
||||
u'MOVE: %s moved from %s to %s at %d index',
|
||||
text_type(source_usage_key),
|
||||
text_type(source_parent.location),
|
||||
text_type(target_parent_usage_key),
|
||||
'MOVE: %s moved from %s to %s at %d index',
|
||||
str(source_usage_key),
|
||||
str(source_parent.location),
|
||||
str(target_parent_usage_key),
|
||||
insert_at
|
||||
)
|
||||
|
||||
context = {
|
||||
'move_source_locator': text_type(source_usage_key),
|
||||
'parent_locator': text_type(target_parent_usage_key),
|
||||
'move_source_locator': str(source_usage_key),
|
||||
'parent_locator': str(target_parent_usage_key),
|
||||
'source_index': target_index if target_index is not None else source_index
|
||||
}
|
||||
return JsonResponse(context)
|
||||
@@ -895,9 +894,9 @@ def _duplicate_item(parent_usage_key, duplicate_source_usage_key, user, display_
|
||||
duplicate_metadata['display_name'] = display_name
|
||||
else:
|
||||
if source_item.display_name is None:
|
||||
duplicate_metadata['display_name'] = _(u"Duplicate of {0}").format(source_item.category)
|
||||
duplicate_metadata['display_name'] = _("Duplicate of {0}").format(source_item.category)
|
||||
else:
|
||||
duplicate_metadata['display_name'] = _(u"Duplicate of '{0}'").format(source_item.display_name)
|
||||
duplicate_metadata['display_name'] = _("Duplicate of '{0}'").format(source_item.display_name)
|
||||
|
||||
asides_to_create = []
|
||||
for aside in source_item.runtime.get_asides(source_item):
|
||||
@@ -1000,7 +999,7 @@ def orphan_handler(request, course_key_string):
|
||||
course_usage_key = CourseKey.from_string(course_key_string)
|
||||
if request.method == 'GET':
|
||||
if has_studio_read_access(request.user, course_usage_key):
|
||||
return JsonResponse([text_type(item) for item in modulestore().get_orphans(course_usage_key)])
|
||||
return JsonResponse([str(item) for item in modulestore().get_orphans(course_usage_key)])
|
||||
else:
|
||||
raise PermissionDenied()
|
||||
if request.method == 'DELETE':
|
||||
@@ -1028,7 +1027,7 @@ def _delete_orphans(course_usage_key, user_id, commit=False):
|
||||
if branch == ModuleStoreEnum.BranchName.published:
|
||||
revision = ModuleStoreEnum.RevisionOption.published_only
|
||||
store.delete_item(itemloc, user_id, revision=revision)
|
||||
return [text_type(item) for item in items]
|
||||
return [str(item) for item in items]
|
||||
|
||||
|
||||
def _get_xblock(usage_key, user):
|
||||
@@ -1052,7 +1051,7 @@ def _get_xblock(usage_key, user):
|
||||
raise
|
||||
except InvalidLocationError:
|
||||
log.error("Can't find item by location.")
|
||||
return JsonResponse({"error": "Can't find item by location: " + text_type(usage_key)}, 404)
|
||||
return JsonResponse({"error": "Can't find item by location: " + str(usage_key)}, 404)
|
||||
|
||||
|
||||
def _get_module_info(xblock, rewrite_static_links=True, include_ancestor_info=False, include_publishing_info=False):
|
||||
@@ -1104,7 +1103,7 @@ def _get_gating_info(course, xblock):
|
||||
course.gating_prerequisites = gating_api.get_prerequisites(course.id)
|
||||
info["is_prereq"] = gating_api.is_prerequisite(course.id, xblock.location)
|
||||
info["prereqs"] = [
|
||||
p for p in course.gating_prerequisites if text_type(xblock.location) not in p['namespace']
|
||||
p for p in course.gating_prerequisites if str(xblock.location) not in p['namespace']
|
||||
]
|
||||
prereq, prereq_min_score, prereq_min_completion = gating_api.get_required_content(
|
||||
course.id,
|
||||
@@ -1199,14 +1198,14 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
|
||||
# Translators: The {pct_sign} here represents the percent sign, i.e., '%'
|
||||
# in many languages. This is used to avoid Transifex's misinterpreting of
|
||||
# '% o'. The percent sign is also translatable as a standalone string.
|
||||
explanatory_message = _(u'Students must score {score}{pct_sign} or higher to access course materials.').format(
|
||||
explanatory_message = _('Students must score {score}{pct_sign} or higher to access course materials.').format(
|
||||
score=int(parent_xblock.entrance_exam_minimum_score_pct * 100),
|
||||
# Translators: This is the percent sign. It will be used to represent
|
||||
# a percent value out of 100, e.g. "58%" means "58/100".
|
||||
pct_sign=_('%'))
|
||||
|
||||
xblock_info = {
|
||||
'id': text_type(xblock.location),
|
||||
'id': str(xblock.location),
|
||||
'display_name': xblock.display_name_with_default,
|
||||
'category': xblock.category,
|
||||
'has_children': xblock.has_children
|
||||
@@ -1399,7 +1398,7 @@ def add_container_page_publishing_info(xblock, xblock_info):
|
||||
xblock_info["staff_lock_from"] = None
|
||||
|
||||
|
||||
class VisibilityState(object):
|
||||
class VisibilityState:
|
||||
"""
|
||||
Represents the possible visibility states for an xblock:
|
||||
|
||||
@@ -1575,6 +1574,6 @@ def _xblock_type_and_display_name(xblock):
|
||||
"""
|
||||
Returns a string representation of the xblock's type and display name
|
||||
"""
|
||||
return _(u'{section_or_subsection} "{display_name}"').format(
|
||||
return _('{section_or_subsection} "{display_name}"').format(
|
||||
section_or_subsection=xblock_type_display_name(xblock),
|
||||
display_name=xblock.display_name_with_default)
|
||||
|
||||
@@ -19,7 +19,6 @@ from opaque_keys.edx.keys import CourseKey
|
||||
from opaque_keys.edx.locator import LibraryLocator, LibraryUsageLocator
|
||||
from organizations.api import ensure_organization
|
||||
from organizations.exceptions import InvalidOrganizationException
|
||||
from six import text_type
|
||||
|
||||
from cms.djangoapps.course_creators.views import get_course_creator_status
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_response
|
||||
@@ -123,14 +122,14 @@ def _display_library(library_key_string, request):
|
||||
raise Http404 # This is not a library
|
||||
if not has_studio_read_access(request.user, library_key):
|
||||
log.exception(
|
||||
u"User %s tried to access library %s without permission",
|
||||
request.user.username, text_type(library_key)
|
||||
"User %s tried to access library %s without permission",
|
||||
request.user.username, str(library_key)
|
||||
)
|
||||
raise PermissionDenied()
|
||||
|
||||
library = modulestore().get_library(library_key)
|
||||
if library is None:
|
||||
log.exception(u"Library %s not found", text_type(library_key))
|
||||
log.exception("Library %s not found", str(library_key))
|
||||
raise Http404
|
||||
|
||||
response_format = 'html'
|
||||
@@ -161,7 +160,7 @@ def _list_libraries(request):
|
||||
lib_info = [
|
||||
{
|
||||
"display_name": lib.display_name,
|
||||
"library_key": text_type(lib.location.library_key),
|
||||
"library_key": str(lib.location.library_key),
|
||||
}
|
||||
for lib in libraries
|
||||
if (
|
||||
@@ -202,12 +201,12 @@ def _create_library(request):
|
||||
except KeyError as error:
|
||||
log.exception("Unable to create library - missing required JSON key.")
|
||||
return JsonResponseBadRequest({
|
||||
"ErrMsg": _(u"Unable to create library - missing required field '{field}'").format(field=text_type(error))
|
||||
"ErrMsg": _("Unable to create library - missing required field '{field}'").format(field=str(error))
|
||||
})
|
||||
except InvalidKeyError as error:
|
||||
log.exception("Unable to create library - invalid key.")
|
||||
return JsonResponseBadRequest({
|
||||
"ErrMsg": _(u"Unable to create library '{name}'.\n\n{err}").format(name=display_name, err=text_type(error))
|
||||
"ErrMsg": _("Unable to create library '{name}'.\n\n{err}").format(name=display_name, err=str(error))
|
||||
})
|
||||
except DuplicateCourseError:
|
||||
log.exception("Unable to create library - one already exists with the same key.")
|
||||
@@ -226,7 +225,7 @@ def _create_library(request):
|
||||
).format(organization_key=org)
|
||||
})
|
||||
|
||||
lib_key_str = text_type(new_lib.location.library_key)
|
||||
lib_key_str = str(new_lib.location.library_key)
|
||||
return JsonResponse({
|
||||
'url': reverse_library_url('library_handler', lib_key_str),
|
||||
'library_key': lib_key_str,
|
||||
@@ -252,10 +251,10 @@ def library_blocks_view(library, user, response_format):
|
||||
prev_version = library.runtime.course_entry.structure['previous_version']
|
||||
return JsonResponse({
|
||||
"display_name": library.display_name,
|
||||
"library_id": text_type(library.location.library_key),
|
||||
"version": text_type(library.runtime.course_entry.course_key.version_guid),
|
||||
"previous_version": text_type(prev_version) if prev_version else None,
|
||||
"blocks": [text_type(x) for x in children],
|
||||
"library_id": str(library.location.library_key),
|
||||
"version": str(library.runtime.course_entry.course_key.version_guid),
|
||||
"previous_version": str(prev_version) if prev_version else None,
|
||||
"blocks": [str(x) for x in children],
|
||||
})
|
||||
|
||||
can_edit = has_studio_write_access(user, library.location.library_key)
|
||||
@@ -305,7 +304,7 @@ def manage_library_users(request, library_key_string):
|
||||
'context_library': library,
|
||||
'users': formatted_users,
|
||||
'allow_actions': bool(user_perms & STUDIO_EDIT_ROLES),
|
||||
'library_key': text_type(library_key),
|
||||
'library_key': str(library_key),
|
||||
'lib_users_url': reverse_library_url('manage_library_users', library_key_string),
|
||||
'show_children_previews': library.show_children_previews
|
||||
})
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import logging
|
||||
from functools import partial
|
||||
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import Http404, HttpResponseBadRequest
|
||||
@@ -16,10 +15,11 @@ from xblock.django.request import django_to_webob_request, webob_to_django_respo
|
||||
from xblock.exceptions import NoSuchHandlerError
|
||||
from xblock.runtime import KvsFieldData
|
||||
|
||||
from common.djangoapps import static_replace
|
||||
from cms.djangoapps.xblock_config.models import StudioConfig
|
||||
from cms.lib.xblock.field_data import CmsFieldData
|
||||
from common.djangoapps import static_replace
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_string
|
||||
from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService
|
||||
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
|
||||
from openedx.core.lib.license import wrap_with_license
|
||||
from openedx.core.lib.xblock_utils import (
|
||||
@@ -30,7 +30,6 @@ from openedx.core.lib.xblock_utils import (
|
||||
wrap_xblock_aside,
|
||||
xblock_local_resource_url
|
||||
)
|
||||
from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.error_module import ErrorBlock
|
||||
from xmodule.exceptions import NotFoundError, ProcessingError
|
||||
@@ -73,7 +72,7 @@ def preview_handler(request, usage_key_string, handler, suffix=''):
|
||||
resp = instance.handle(handler, req, suffix)
|
||||
|
||||
except NoSuchHandlerError:
|
||||
log.exception(u"XBlock %s attempted to access missing handler %r", instance, handler)
|
||||
log.exception("XBlock %s attempted to access missing handler %r", instance, handler)
|
||||
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from
|
||||
|
||||
except NotFoundError:
|
||||
@@ -102,7 +101,7 @@ class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method
|
||||
|
||||
def handler_url(self, block, handler_name, suffix='', query='', thirdparty=False):
|
||||
return reverse('preview_handler', kwargs={
|
||||
'usage_key_string': six.text_type(block.scope_ids.usage_id),
|
||||
'usage_key_string': str(block.scope_ids.usage_id),
|
||||
'handler': handler_name,
|
||||
'suffix': suffix,
|
||||
}) + '?' + query
|
||||
@@ -121,7 +120,7 @@ class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method
|
||||
# (see https://openedx.atlassian.net/browse/TE-811)
|
||||
return [
|
||||
aside_type
|
||||
for aside_type in super(PreviewModuleSystem, self).applicable_aside_types(block) # lint-amnesty, pylint: disable=super-with-arguments
|
||||
for aside_type in super().applicable_aside_types(block)
|
||||
if aside_type != 'acid_aside'
|
||||
]
|
||||
|
||||
@@ -138,7 +137,7 @@ class PreviewModuleSystem(ModuleSystem): # pylint: disable=abstract-method
|
||||
|
||||
for aside, aside_fn in aside_frag_fns:
|
||||
aside_frag = aside_fn(block, context)
|
||||
if aside_frag.content != u'':
|
||||
if aside_frag.content != '':
|
||||
aside_frag_wrapped = self.wrap_aside(block, aside, view_name, aside_frag, context)
|
||||
aside.save()
|
||||
result.add_fragment_resources(aside_frag_wrapped)
|
||||
@@ -167,7 +166,7 @@ def _preview_module_system(request, descriptor, field_data):
|
||||
wrap_xblock,
|
||||
'PreviewRuntime',
|
||||
display_name_only=display_name_only,
|
||||
usage_id_serializer=six.text_type,
|
||||
usage_id_serializer=str,
|
||||
request_token=request_token(request)
|
||||
),
|
||||
|
||||
@@ -181,7 +180,7 @@ def _preview_module_system(request, descriptor, field_data):
|
||||
partial(
|
||||
wrap_xblock_aside,
|
||||
'PreviewRuntime',
|
||||
usage_id_serializer=six.text_type,
|
||||
usage_id_serializer=str,
|
||||
request_token=request_token(request)
|
||||
)
|
||||
]
|
||||
@@ -285,7 +284,7 @@ def _studio_wrap_xblock(xblock, view, frag, context, display_name_only=False):
|
||||
is_reorderable = _is_xblock_reorderable(xblock, context)
|
||||
selected_groups_label = get_visibility_partition_info(xblock)['selected_groups_label']
|
||||
if selected_groups_label:
|
||||
selected_groups_label = _(u'Access restricted to: {list_of_groups}').format(list_of_groups=selected_groups_label) # lint-amnesty, pylint: disable=line-too-long
|
||||
selected_groups_label = _('Access restricted to: {list_of_groups}').format(list_of_groups=selected_groups_label) # lint-amnesty, pylint: disable=line-too-long
|
||||
course = modulestore().get_course(xblock.location.course_key)
|
||||
template_context = {
|
||||
'xblock_context': context,
|
||||
@@ -326,6 +325,6 @@ def get_preview_fragment(request, descriptor, context):
|
||||
try:
|
||||
fragment = module.render(preview_view, context)
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
log.warning(u"Unable to render %s for %r", preview_view, module, exc_info=True)
|
||||
log.warning("Unable to render %s for %r", preview_view, module, exc_info=True)
|
||||
fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)}))
|
||||
return fragment
|
||||
|
||||
@@ -59,7 +59,7 @@ def howitworks(request):
|
||||
return render_to_response('howitworks.html', {})
|
||||
|
||||
|
||||
@waffle_switch('{}.{}'.format(waffle.WAFFLE_NAMESPACE, waffle.ENABLE_ACCESSIBILITY_POLICY_PAGE))
|
||||
@waffle_switch(f'{waffle.WAFFLE_NAMESPACE}.{waffle.ENABLE_ACCESSIBILITY_POLICY_PAGE}')
|
||||
def accessibility(request):
|
||||
"""
|
||||
Display the accessibility accommodation form.
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
"""
|
||||
Views related to course tabs
|
||||
"""
|
||||
|
||||
|
||||
import six
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.http import HttpResponseNotFound
|
||||
@@ -100,7 +97,7 @@ def reorder_tabs_handler(course_item, request):
|
||||
tab = get_tab_by_tab_id_locator(old_tab_list, tab_id_locator)
|
||||
if tab is None:
|
||||
return JsonResponse(
|
||||
{"error": u"Tab with id_locator '{0}' does not exist.".format(tab_id_locator)}, status=400
|
||||
{"error": f"Tab with id_locator '{tab_id_locator}' does not exist."}, status=400
|
||||
)
|
||||
new_tab_list.append(tab)
|
||||
|
||||
@@ -114,7 +111,7 @@ def reorder_tabs_handler(course_item, request):
|
||||
CourseTabList.validate_tabs(new_tab_list)
|
||||
except InvalidTabsException as exception:
|
||||
return JsonResponse(
|
||||
{"error": u"New list of tabs is not valid: {0}.".format(str(exception))}, status=400
|
||||
{"error": "New list of tabs is not valid: {}.".format(str(exception))}, status=400
|
||||
)
|
||||
|
||||
# persist the new order of the tabs
|
||||
@@ -136,7 +133,7 @@ def edit_tab_handler(course_item, request):
|
||||
tab = get_tab_by_tab_id_locator(course_item.tabs, tab_id_locator)
|
||||
if tab is None:
|
||||
return JsonResponse(
|
||||
{"error": u"Tab with id_locator '{0}' does not exist.".format(tab_id_locator)}, status=400
|
||||
{"error": f"Tab with id_locator '{tab_id_locator}' does not exist."}, status=400
|
||||
)
|
||||
|
||||
if 'is_hidden' in request.json:
|
||||
@@ -144,7 +141,7 @@ def edit_tab_handler(course_item, request):
|
||||
tab.is_hidden = request.json['is_hidden']
|
||||
modulestore().update_item(course_item, request.user.id)
|
||||
else:
|
||||
raise NotImplementedError(u'Unsupported request to edit tab: {0}'.format(request.json))
|
||||
raise NotImplementedError(f'Unsupported request to edit tab: {request.json}')
|
||||
|
||||
return JsonResponse()
|
||||
|
||||
@@ -200,7 +197,7 @@ def primitive_delete(course, num):
|
||||
def primitive_insert(course, num, tab_type, name):
|
||||
"Inserts a new tab at the given number (0 based)."
|
||||
validate_args(num, tab_type)
|
||||
new_tab = CourseTab.from_json({u'type': six.text_type(tab_type), u'name': six.text_type(name)})
|
||||
new_tab = CourseTab.from_json({'type': str(tab_type), 'name': str(name)})
|
||||
tabs = course.tabs
|
||||
tabs.insert(num, new_tab)
|
||||
modulestore().update_item(course, ModuleStoreEnum.UserID.primitive_command)
|
||||
|
||||
@@ -20,7 +20,7 @@ class RolesTest(TestCase):
|
||||
"""
|
||||
def setUp(self):
|
||||
""" Test case setup """
|
||||
super(RolesTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
self.global_admin = AdminFactory()
|
||||
self.instructor = User.objects.create_user('testinstructor', 'testinstructor+courses@edx.org', 'foo')
|
||||
|
||||
@@ -6,13 +6,12 @@ Unit tests for the asset upload endpoint.
|
||||
import json
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
from unittest import mock
|
||||
from unittest.mock import patch
|
||||
|
||||
import mock
|
||||
import six
|
||||
from ddt import data, ddt
|
||||
from django.conf import settings
|
||||
from django.test.utils import override_settings
|
||||
from mock import patch
|
||||
from opaque_keys.edx.keys import AssetKey
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
from PIL import Image
|
||||
@@ -43,7 +42,7 @@ class AssetsTestCase(CourseTestCase):
|
||||
Parent class for all asset tests.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(AssetsTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.url = reverse_course_url('assets_handler', self.course.id)
|
||||
|
||||
def upload_asset(self, name="asset-1", asset_type='text'):
|
||||
@@ -61,14 +60,14 @@ class AssetsTestCase(CourseTestCase):
|
||||
sample_asset = BytesIO()
|
||||
sample_file_contents = b"This file is generated by python unit test"
|
||||
if asset_type == 'text':
|
||||
sample_asset.name = '{name}.txt'.format(name=name)
|
||||
sample_asset.name = f'{name}.txt'
|
||||
sample_asset.write(sample_file_contents)
|
||||
elif asset_type == 'image':
|
||||
image = Image.new("RGB", size=(50, 50), color=(256, 0, 0))
|
||||
image.save(sample_asset, 'jpeg')
|
||||
sample_asset.name = '{name}.jpg'.format(name=name)
|
||||
sample_asset.name = f'{name}.jpg'
|
||||
elif asset_type == 'opendoc':
|
||||
sample_asset.name = '{name}.odt'.format(name=name)
|
||||
sample_asset.name = f'{name}.odt'
|
||||
sample_asset.write(sample_file_contents)
|
||||
sample_asset.seek(0)
|
||||
return sample_asset
|
||||
@@ -131,12 +130,12 @@ class BasicAssetsTestCase(AssetsTestCase):
|
||||
course = module_store.get_course(course_id)
|
||||
|
||||
filename = 'sample_static.html'
|
||||
html_src_attribute = '"/static/{}"'.format(filename)
|
||||
html_src_attribute = f'"/static/{filename}"'
|
||||
asset_url = replace_static_urls(html_src_attribute, course_id=course.id)
|
||||
url = asset_url.replace('"', '')
|
||||
base_url = url.replace(filename, '')
|
||||
|
||||
self.assertIn("/{}".format(filename), url)
|
||||
self.assertIn(f"/{filename}", url)
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
@@ -146,7 +145,7 @@ class BasicAssetsTestCase(AssetsTestCase):
|
||||
# browser append relative_path with base_url
|
||||
absolute_path = base_url + relative_path
|
||||
|
||||
self.assertIn("/{}".format(relative_path), absolute_path)
|
||||
self.assertIn(f"/{relative_path}", absolute_path)
|
||||
resp = self.client.get(absolute_path)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
@@ -343,7 +342,7 @@ class UploadTestCase(AssetsTestCase):
|
||||
Unit tests for uploading a file
|
||||
"""
|
||||
def setUp(self):
|
||||
super(UploadTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.url = reverse_course_url('assets_handler', self.course.id)
|
||||
|
||||
def test_happy_path(self):
|
||||
@@ -378,7 +377,7 @@ class DownloadTestCase(AssetsTestCase):
|
||||
Unit tests for downloading a file.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(DownloadTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.url = reverse_course_url('assets_handler', self.course.id)
|
||||
# First, upload something.
|
||||
self.asset_name = 'download_test'
|
||||
@@ -430,7 +429,7 @@ class AssetToJsonTestCase(AssetsTestCase):
|
||||
)
|
||||
self.assertEqual(output["portable_url"], "/static/my_file_name.jpg")
|
||||
self.assertEqual(output["thumbnail"], "/asset-v1:org+class+run+type@thumbnail+block@my_file_name_thumb.jpg")
|
||||
self.assertEqual(output["id"], six.text_type(location))
|
||||
self.assertEqual(output["id"], str(location))
|
||||
self.assertEqual(output['locked'], True)
|
||||
|
||||
output = assets._get_asset_json("name", content_type, upload_date, location, None, False)
|
||||
@@ -458,7 +457,7 @@ class LockAssetTestCase(AssetsTestCase):
|
||||
upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=UTC)
|
||||
asset_location = course.id.make_asset_key('asset', 'sample_static.html')
|
||||
url = reverse_course_url(
|
||||
'assets_handler', course.id, kwargs={'asset_key_string': six.text_type(asset_location)}
|
||||
'assets_handler', course.id, kwargs={'asset_key_string': str(asset_location)}
|
||||
)
|
||||
|
||||
resp = self.client.post(
|
||||
@@ -502,7 +501,7 @@ class DeleteAssetTestCase(AssetsTestCase):
|
||||
"""
|
||||
def setUp(self):
|
||||
""" Scaffolding """
|
||||
super(DeleteAssetTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.url = reverse_course_url('assets_handler', self.course.id)
|
||||
# First, upload something.
|
||||
self.asset_name = 'delete_test'
|
||||
@@ -518,7 +517,7 @@ class DeleteAssetTestCase(AssetsTestCase):
|
||||
def test_delete_asset(self):
|
||||
""" Tests the happy path :) """
|
||||
test_url = reverse_course_url(
|
||||
'assets_handler', self.course.id, kwargs={'asset_key_string': six.text_type(self.uploaded_url)})
|
||||
'assets_handler', self.course.id, kwargs={'asset_key_string': str(self.uploaded_url)})
|
||||
resp = self.client.delete(test_url, HTTP_ACCEPT="application/json")
|
||||
self.assertEqual(resp.status_code, 204)
|
||||
|
||||
@@ -547,7 +546,7 @@ class DeleteAssetTestCase(AssetsTestCase):
|
||||
mock_asset_key.return_value = thumbnail_location
|
||||
|
||||
test_url = reverse_course_url(
|
||||
'assets_handler', self.course.id, kwargs={'asset_key_string': six.text_type(uploaded_image_url)})
|
||||
'assets_handler', self.course.id, kwargs={'asset_key_string': str(uploaded_image_url)})
|
||||
resp = self.client.delete(test_url, HTTP_ACCEPT="application/json")
|
||||
self.assertEqual(resp.status_code, 204)
|
||||
|
||||
@@ -555,7 +554,7 @@ class DeleteAssetTestCase(AssetsTestCase):
|
||||
""" Tests the sad path :( """
|
||||
test_url = reverse_course_url(
|
||||
'assets_handler',
|
||||
self.course.id, kwargs={'asset_key_string': six.text_type("/c4x/edX/toy/asset/invalid.pdf")}
|
||||
self.course.id, kwargs={'asset_key_string': "/c4x/edX/toy/asset/invalid.pdf"}
|
||||
)
|
||||
resp = self.client.delete(test_url, HTTP_ACCEPT="application/json")
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
@@ -563,7 +562,7 @@ class DeleteAssetTestCase(AssetsTestCase):
|
||||
def test_delete_asset_with_invalid_thumbnail(self):
|
||||
""" Tests the sad path :( """
|
||||
test_url = reverse_course_url(
|
||||
'assets_handler', self.course.id, kwargs={'asset_key_string': six.text_type(self.uploaded_url)})
|
||||
'assets_handler', self.course.id, kwargs={'asset_key_string': str(self.uploaded_url)})
|
||||
self.content.thumbnail_location = StaticContent.get_location_from_path('/c4x/edX/toy/asset/invalid')
|
||||
contentstore().save(self.content)
|
||||
resp = self.client.delete(test_url, HTTP_ACCEPT="application/json")
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Certificates Tests.
|
||||
"""
|
||||
@@ -7,14 +5,12 @@ Certificates Tests.
|
||||
|
||||
import itertools
|
||||
import json
|
||||
from unittest import mock
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.test.utils import override_settings
|
||||
from opaque_keys.edx.keys import AssetKey
|
||||
from six.moves import range
|
||||
|
||||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
|
||||
from cms.djangoapps.contentstore.utils import get_lms_link_for_certificate_web_view, reverse_course_url
|
||||
@@ -33,19 +29,19 @@ FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
|
||||
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
|
||||
|
||||
CERTIFICATE_JSON = {
|
||||
u'name': u'Test certificate',
|
||||
u'description': u'Test description',
|
||||
u'is_active': True,
|
||||
u'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
'name': 'Test certificate',
|
||||
'description': 'Test description',
|
||||
'is_active': True,
|
||||
'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
}
|
||||
|
||||
CERTIFICATE_JSON_WITH_SIGNATORIES = {
|
||||
u'name': u'Test certificate',
|
||||
u'description': u'Test description',
|
||||
u'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
u'course_title': 'Course Title Override',
|
||||
u'is_active': True,
|
||||
u'signatories': [
|
||||
'name': 'Test certificate',
|
||||
'description': 'Test description',
|
||||
'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
'course_title': 'Course Title Override',
|
||||
'is_active': True,
|
||||
'signatories': [
|
||||
{
|
||||
"name": "Bob Smith",
|
||||
"title": "The DEAN.",
|
||||
@@ -59,7 +55,7 @@ SIGNATORY_PATH = 'asset-v1:test+CSS101+SP2017+type@asset+block@Signature{}.png'
|
||||
|
||||
|
||||
# pylint: disable=no-member
|
||||
class HelperMethods(object):
|
||||
class HelperMethods:
|
||||
"""
|
||||
Mixin that provides useful methods for certificate configuration tests.
|
||||
"""
|
||||
@@ -107,7 +103,7 @@ class HelperMethods(object):
|
||||
|
||||
|
||||
# pylint: disable=no-member
|
||||
class CertificatesBaseTestCase(object):
|
||||
class CertificatesBaseTestCase:
|
||||
"""
|
||||
Mixin with base test cases for the certificates.
|
||||
"""
|
||||
@@ -128,8 +124,8 @@ class CertificatesBaseTestCase(object):
|
||||
bad_jsons = [
|
||||
# must have name of the certificate
|
||||
{
|
||||
u'description': 'Test description',
|
||||
u'version': CERTIFICATE_SCHEMA_VERSION
|
||||
'description': 'Test description',
|
||||
'version': CERTIFICATE_SCHEMA_VERSION
|
||||
},
|
||||
|
||||
# an empty json
|
||||
@@ -155,8 +151,8 @@ class CertificatesBaseTestCase(object):
|
||||
Test invalid json handling.
|
||||
"""
|
||||
# Invalid JSON.
|
||||
invalid_json = u"{u'name': 'Test Name', u'description': 'Test description'," \
|
||||
u" u'version': " + str(CERTIFICATE_SCHEMA_VERSION) + ", []}"
|
||||
invalid_json = "{u'name': 'Test Name', u'description': 'Test description'," \
|
||||
" u'version': " + str(CERTIFICATE_SCHEMA_VERSION) + ", []}"
|
||||
|
||||
response = self.client.post(
|
||||
self._url(),
|
||||
@@ -174,9 +170,9 @@ class CertificatesBaseTestCase(object):
|
||||
def test_certificate_data_validation(self):
|
||||
#Test certificate schema version
|
||||
json_data_1 = {
|
||||
u'version': 100,
|
||||
u'name': u'Test certificate',
|
||||
u'description': u'Test description'
|
||||
'version': 100,
|
||||
'name': 'Test certificate',
|
||||
'description': 'Test description'
|
||||
}
|
||||
|
||||
with self.assertRaises(Exception) as context:
|
||||
@@ -189,8 +185,8 @@ class CertificatesBaseTestCase(object):
|
||||
|
||||
#Test certificate name is missing
|
||||
json_data_2 = {
|
||||
u'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
u'description': u'Test description'
|
||||
'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
'description': 'Test description'
|
||||
}
|
||||
|
||||
with self.assertRaises(Exception) as context:
|
||||
@@ -212,7 +208,7 @@ class CertificatesListHandlerTestCase(
|
||||
"""
|
||||
Set up CertificatesListHandlerTestCase.
|
||||
"""
|
||||
super(CertificatesListHandlerTestCase, self).setUp('cms.djangoapps.contentstore.views.certificates.tracker') # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp('cms.djangoapps.contentstore.views.certificates.tracker')
|
||||
self.reset_urls()
|
||||
|
||||
def _url(self):
|
||||
@@ -226,11 +222,11 @@ class CertificatesListHandlerTestCase(
|
||||
Test that you can create a certificate.
|
||||
"""
|
||||
expected = {
|
||||
u'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
u'name': u'Test certificate',
|
||||
u'description': u'Test description',
|
||||
u'is_active': True,
|
||||
u'signatories': []
|
||||
'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
'name': 'Test certificate',
|
||||
'description': 'Test description',
|
||||
'is_active': True,
|
||||
'signatories': []
|
||||
}
|
||||
response = self.client.ajax_post(
|
||||
self._url(),
|
||||
@@ -244,7 +240,7 @@ class CertificatesListHandlerTestCase(
|
||||
self.assertEqual(content, expected)
|
||||
self.assert_event_emitted(
|
||||
'edx.certificate.configuration.created',
|
||||
course_id=six.text_type(self.course.id),
|
||||
course_id=str(self.course.id),
|
||||
configuration_id=certificate_id,
|
||||
)
|
||||
|
||||
@@ -272,7 +268,7 @@ class CertificatesListHandlerTestCase(
|
||||
@override_settings(LMS_BASE="lms_base_url")
|
||||
def test_lms_link_for_certificate_web_view(self):
|
||||
test_url = "//lms_base_url/certificates/" \
|
||||
"course/" + six.text_type(self.course.id) + '?preview=honor'
|
||||
"course/" + str(self.course.id) + '?preview=honor'
|
||||
link = get_lms_link_for_certificate_web_view(
|
||||
course_key=self.course.id,
|
||||
mode='honor'
|
||||
@@ -403,11 +399,11 @@ class CertificatesListHandlerTestCase(
|
||||
"""
|
||||
self._add_course_certificates(count=2)
|
||||
json_data = {
|
||||
u'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
u'name': u'New test certificate',
|
||||
u'description': u'New test description',
|
||||
u'is_active': True,
|
||||
u'signatories': []
|
||||
'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
'name': 'New test certificate',
|
||||
'description': 'New test description',
|
||||
'is_active': True,
|
||||
'signatories': []
|
||||
}
|
||||
|
||||
response = self.client.post(
|
||||
@@ -437,7 +433,7 @@ class CertificatesDetailHandlerTestCase(
|
||||
"""
|
||||
Set up CertificatesDetailHandlerTestCase.
|
||||
"""
|
||||
super(CertificatesDetailHandlerTestCase, self).setUp('cms.djangoapps.contentstore.views.certificates.tracker') # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp('cms.djangoapps.contentstore.views.certificates.tracker')
|
||||
self.reset_urls()
|
||||
|
||||
def _url(self, cid=-1):
|
||||
@@ -456,13 +452,13 @@ class CertificatesDetailHandlerTestCase(
|
||||
PUT/POST new certificate.
|
||||
"""
|
||||
expected = {
|
||||
u'id': 666,
|
||||
u'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
u'name': u'Test certificate',
|
||||
u'description': u'Test description',
|
||||
u'is_active': True,
|
||||
u'course_title': u'Course Title Override',
|
||||
u'signatories': []
|
||||
'id': 666,
|
||||
'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
'name': 'Test certificate',
|
||||
'description': 'Test description',
|
||||
'is_active': True,
|
||||
'course_title': 'Course Title Override',
|
||||
'signatories': []
|
||||
}
|
||||
|
||||
response = self.client.put(
|
||||
@@ -476,7 +472,7 @@ class CertificatesDetailHandlerTestCase(
|
||||
self.assertEqual(content, expected)
|
||||
self.assert_event_emitted(
|
||||
'edx.certificate.configuration.created',
|
||||
course_id=six.text_type(self.course.id),
|
||||
course_id=str(self.course.id),
|
||||
configuration_id=666,
|
||||
)
|
||||
|
||||
@@ -487,13 +483,13 @@ class CertificatesDetailHandlerTestCase(
|
||||
self._add_course_certificates(count=2)
|
||||
|
||||
expected = {
|
||||
u'id': 1,
|
||||
u'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
u'name': u'New test certificate',
|
||||
u'description': u'New test description',
|
||||
u'is_active': True,
|
||||
u'course_title': u'Course Title Override',
|
||||
u'signatories': []
|
||||
'id': 1,
|
||||
'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
'name': 'New test certificate',
|
||||
'description': 'New test description',
|
||||
'is_active': True,
|
||||
'course_title': 'Course Title Override',
|
||||
'signatories': []
|
||||
|
||||
}
|
||||
|
||||
@@ -508,7 +504,7 @@ class CertificatesDetailHandlerTestCase(
|
||||
self.assertEqual(content, expected)
|
||||
self.assert_event_emitted(
|
||||
'edx.certificate.configuration.modified',
|
||||
course_id=six.text_type(self.course.id),
|
||||
course_id=str(self.course.id),
|
||||
configuration_id=1,
|
||||
)
|
||||
self.reload_course()
|
||||
@@ -516,7 +512,7 @@ class CertificatesDetailHandlerTestCase(
|
||||
# Verify that certificate is properly updated in the course.
|
||||
course_certificates = self.course.certificates['certificates']
|
||||
self.assertEqual(len(course_certificates), 2)
|
||||
self.assertEqual(course_certificates[1].get('name'), u'New test certificate')
|
||||
self.assertEqual(course_certificates[1].get('name'), 'New test certificate')
|
||||
self.assertEqual(course_certificates[1].get('description'), 'New test description')
|
||||
|
||||
def test_can_edit_certificate_without_is_active(self):
|
||||
@@ -537,13 +533,13 @@ class CertificatesDetailHandlerTestCase(
|
||||
self.save_course()
|
||||
|
||||
expected = {
|
||||
u'id': 1,
|
||||
u'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
u'name': u'New test certificate',
|
||||
u'description': u'New test description',
|
||||
u'is_active': True,
|
||||
u'course_title': u'Course Title Override',
|
||||
u'signatories': []
|
||||
'id': 1,
|
||||
'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
'name': 'New test certificate',
|
||||
'description': 'New test description',
|
||||
'is_active': True,
|
||||
'course_title': 'Course Title Override',
|
||||
'signatories': []
|
||||
|
||||
}
|
||||
|
||||
@@ -573,7 +569,7 @@ class CertificatesDetailHandlerTestCase(
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assert_event_emitted(
|
||||
'edx.certificate.configuration.deleted',
|
||||
course_id=six.text_type(self.course.id),
|
||||
course_id=str(self.course.id),
|
||||
configuration_id='1',
|
||||
)
|
||||
self.reload_course()
|
||||
@@ -597,7 +593,7 @@ class CertificatesDetailHandlerTestCase(
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assert_event_emitted(
|
||||
'edx.certificate.configuration.deleted',
|
||||
course_id=six.text_type(self.course.id),
|
||||
course_id=str(self.course.id),
|
||||
configuration_id='1',
|
||||
)
|
||||
self.reload_course()
|
||||
@@ -622,7 +618,7 @@ class CertificatesDetailHandlerTestCase(
|
||||
self.assertEqual(response.status_code, 204)
|
||||
self.assert_event_emitted(
|
||||
'edx.certificate.configuration.deleted',
|
||||
course_id=six.text_type(self.course.id),
|
||||
course_id=str(self.course.id),
|
||||
configuration_id='1',
|
||||
)
|
||||
self.reload_course()
|
||||
@@ -673,14 +669,14 @@ class CertificatesDetailHandlerTestCase(
|
||||
"""
|
||||
self._add_course_certificates(count=2, signatory_count=1, is_active=True, asset_path_format=signatory_path)
|
||||
cert_data = {
|
||||
u'id': 1,
|
||||
u'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
u'name': u'New test certificate',
|
||||
u'description': u'New test description',
|
||||
u'course_title': u'Course Title Override',
|
||||
u'org_logo_path': '',
|
||||
u'is_active': False,
|
||||
u'signatories': []
|
||||
'id': 1,
|
||||
'version': CERTIFICATE_SCHEMA_VERSION,
|
||||
'name': 'New test certificate',
|
||||
'description': 'New test description',
|
||||
'course_title': 'Course Title Override',
|
||||
'org_logo_path': '',
|
||||
'is_active': False,
|
||||
'signatories': []
|
||||
}
|
||||
user = UserFactory()
|
||||
for role in [CourseInstructorRole, CourseStaffRole]:
|
||||
@@ -790,7 +786,7 @@ class CertificatesDetailHandlerTestCase(
|
||||
cert_event_type = 'activated' if is_active else 'deactivated'
|
||||
self.assert_event_emitted(
|
||||
'.'.join(['edx.certificate.configuration', cert_event_type]),
|
||||
course_id=six.text_type(self.course.id),
|
||||
course_id=str(self.course.id),
|
||||
)
|
||||
|
||||
@ddt.data(*itertools.product([True, False], [C4X_SIGNATORY_PATH, SIGNATORY_PATH]))
|
||||
|
||||
@@ -5,12 +5,11 @@ Unit tests for the container page.
|
||||
|
||||
import datetime
|
||||
import re
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import six
|
||||
from django.http import Http404
|
||||
from django.test.client import RequestFactory
|
||||
from django.utils import http
|
||||
from mock import Mock, patch
|
||||
from pytz import UTC
|
||||
|
||||
import cms.djangoapps.contentstore.views.component as views
|
||||
@@ -31,7 +30,7 @@ class ContainerPageTestCase(StudioPageTestCase, LibraryTestCase):
|
||||
reorderable_child_view = 'reorderable_container_child_preview'
|
||||
|
||||
def setUp(self):
|
||||
super(ContainerPageTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.vertical = self._create_item(self.sequential.location, 'vertical', 'Unit')
|
||||
self.html = self._create_item(self.vertical.location, "html", "HTML")
|
||||
self.child_container = self._create_item(self.vertical.location, 'split_test', 'Split Test')
|
||||
@@ -60,18 +59,18 @@ class ContainerPageTestCase(StudioPageTestCase, LibraryTestCase):
|
||||
self._test_html_content(
|
||||
self.child_container,
|
||||
expected_section_tag=(
|
||||
u'<section class="wrapper-xblock level-page is-hidden studio-xblock-wrapper" '
|
||||
u'data-locator="{0}" data-course-key="{0.course_key}">'.format(self.child_container.location)
|
||||
'<section class="wrapper-xblock level-page is-hidden studio-xblock-wrapper" '
|
||||
'data-locator="{0}" data-course-key="{0.course_key}">'.format(self.child_container.location)
|
||||
),
|
||||
expected_breadcrumbs=(
|
||||
u'<li class="nav-item">\\s*<a href="/course/{course}{section_parameters}">Week 1<\\/a>.*'
|
||||
u'<a href="/course/{course}{subsection_parameters}">Lesson 1</a>'
|
||||
'<li class="nav-item">\\s*<a href="/course/{course}{section_parameters}">Week 1<\\/a>.*'
|
||||
'<a href="/course/{course}{subsection_parameters}">Lesson 1</a>'
|
||||
).format(
|
||||
course=re.escape(six.text_type(self.course.id)),
|
||||
section_parameters=re.escape(u'?show={}'.format(http.urlquote(
|
||||
course=re.escape(str(self.course.id)),
|
||||
section_parameters=re.escape('?show={}'.format(http.urlquote(
|
||||
str(self.chapter.location).encode()
|
||||
))),
|
||||
subsection_parameters=re.escape(u'?show={}'.format(http.urlquote(
|
||||
subsection_parameters=re.escape('?show={}'.format(http.urlquote(
|
||||
str(self.sequential.location).encode()
|
||||
))),
|
||||
),
|
||||
@@ -89,16 +88,16 @@ class ContainerPageTestCase(StudioPageTestCase, LibraryTestCase):
|
||||
self._test_html_content(
|
||||
xblock,
|
||||
expected_section_tag=(
|
||||
u'<section class="wrapper-xblock level-page is-hidden studio-xblock-wrapper" '
|
||||
u'data-locator="{0}" data-course-key="{0.course_key}">'.format(draft_container.location)
|
||||
'<section class="wrapper-xblock level-page is-hidden studio-xblock-wrapper" '
|
||||
'data-locator="{0}" data-course-key="{0.course_key}">'.format(draft_container.location)
|
||||
),
|
||||
expected_breadcrumbs=(
|
||||
u'<a href="/course/{course}{subsection_parameters}">Lesson 1</a>.*'
|
||||
u'<a href="/container/{unit_parameters}">Unit</a>.*'
|
||||
'<a href="/course/{course}{subsection_parameters}">Lesson 1</a>.*'
|
||||
'<a href="/container/{unit_parameters}">Unit</a>.*'
|
||||
).format(
|
||||
course=re.escape(six.text_type(self.course.id)),
|
||||
course=re.escape(str(self.course.id)),
|
||||
unit_parameters=re.escape(str(self.vertical.location)),
|
||||
subsection_parameters=re.escape(u'?show={}'.format(http.urlquote(
|
||||
subsection_parameters=re.escape('?show={}'.format(http.urlquote(
|
||||
str(self.sequential.location).encode()
|
||||
))),
|
||||
),
|
||||
@@ -228,6 +227,6 @@ class ContainerPageTestCase(StudioPageTestCase, LibraryTestCase):
|
||||
# Check 200 response if 'usage_key_string' is correct
|
||||
response = views.container_handler(
|
||||
request=request,
|
||||
usage_key_string=six.text_type(self.vertical.location)
|
||||
usage_key_string=str(self.vertical.location)
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@@ -5,12 +5,11 @@ Unit tests for getting the list of courses and the course outline.
|
||||
|
||||
import datetime
|
||||
import json
|
||||
from unittest import mock
|
||||
|
||||
import ddt
|
||||
import lxml
|
||||
import mock
|
||||
import pytz
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.test.utils import override_settings
|
||||
@@ -23,10 +22,10 @@ from cms.djangoapps.contentstore.tests.utils import CourseTestCase
|
||||
from cms.djangoapps.contentstore.utils import add_instructor, reverse_course_url, reverse_usage_url
|
||||
from common.djangoapps.course_action_state.managers import CourseRerunUIStateManager
|
||||
from common.djangoapps.course_action_state.models import CourseRerunState
|
||||
from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES
|
||||
from common.djangoapps.student.auth import has_course_author_access
|
||||
from common.djangoapps.student.roles import CourseStaffRole, GlobalStaff, LibraryUserRole
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from openedx.core.djangoapps.waffle_utils.testutils import WAFFLE_TABLES
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, LibraryFactory, check_mongo_calls
|
||||
@@ -44,7 +43,7 @@ class TestCourseIndex(CourseTestCase):
|
||||
"""
|
||||
Add a course with odd characters in the fields
|
||||
"""
|
||||
super(TestCourseIndex, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
# had a problem where index showed course but has_access failed to retrieve it for non-staff
|
||||
self.odd_course = CourseFactory.create(
|
||||
org='test.org_1-2',
|
||||
@@ -140,7 +139,7 @@ class TestCourseIndex(CourseTestCase):
|
||||
|
||||
# First spot check some values in the root response
|
||||
self.assertEqual(json_response['category'], 'course')
|
||||
self.assertEqual(json_response['id'], six.text_type(self.course.location))
|
||||
self.assertEqual(json_response['id'], str(self.course.location))
|
||||
self.assertEqual(json_response['display_name'], self.course.display_name)
|
||||
self.assertTrue(json_response['published'])
|
||||
self.assertIsNone(json_response['visibility_state'])
|
||||
@@ -150,7 +149,7 @@ class TestCourseIndex(CourseTestCase):
|
||||
self.assertGreater(len(children), 0)
|
||||
first_child_response = children[0]
|
||||
self.assertEqual(first_child_response['category'], 'chapter')
|
||||
self.assertEqual(first_child_response['id'], six.text_type(chapter.location))
|
||||
self.assertEqual(first_child_response['id'], str(chapter.location))
|
||||
self.assertEqual(first_child_response['display_name'], 'Week 1')
|
||||
self.assertTrue(json_response['published'])
|
||||
self.assertEqual(first_child_response['visibility_state'], VisibilityState.unscheduled)
|
||||
@@ -250,7 +249,7 @@ class TestCourseIndex(CourseTestCase):
|
||||
"""
|
||||
# Testing the response code by passing slash separated course id whose format is valid but no course
|
||||
# having this id exists.
|
||||
invalid_course_key = '{}_blah_blah_blah'.format(self.course.id)
|
||||
invalid_course_key = f'{self.course.id}_blah_blah_blah'
|
||||
course_updates_url = reverse_course_url('course_info_handler', invalid_course_key)
|
||||
response = self.client.get(course_updates_url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
@@ -263,7 +262,7 @@ class TestCourseIndex(CourseTestCase):
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
# Testing the response by passing split course id whose format is invalid.
|
||||
invalid_course_id = 'invalid.course.key/{}'.format(split_course_key)
|
||||
invalid_course_id = f'invalid.course.key/{split_course_key}'
|
||||
course_updates_url_split = reverse_course_url('course_info_handler', invalid_course_id)
|
||||
response = self.client.get(course_updates_url_split)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
@@ -274,7 +273,7 @@ class TestCourseIndex(CourseTestCase):
|
||||
"""
|
||||
# Testing the response code by passing slash separated course key, no course
|
||||
# having this key exists.
|
||||
invalid_course_key = '{}_some_invalid_run'.format(self.course.id)
|
||||
invalid_course_key = f'{self.course.id}_some_invalid_run'
|
||||
course_outline_url = reverse_course_url('course_handler', invalid_course_key)
|
||||
response = self.client.get_html(course_outline_url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
@@ -329,7 +328,7 @@ class TestCourseIndexArchived(CourseTestCase):
|
||||
"""
|
||||
Add courses with the end date set to various values
|
||||
"""
|
||||
super(TestCourseIndexArchived, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
# Base course has no end date (so is active)
|
||||
self.course.end = None
|
||||
@@ -426,7 +425,7 @@ class TestCourseOutline(CourseTestCase):
|
||||
"""
|
||||
Set up the for the course outline tests.
|
||||
"""
|
||||
super(TestCourseOutline, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
self.chapter = ItemFactory.create(
|
||||
parent_location=self.course.location, category='chapter', display_name="Week 1"
|
||||
@@ -456,7 +455,7 @@ class TestCourseOutline(CourseTestCase):
|
||||
|
||||
# First spot check some values in the root response
|
||||
self.assertEqual(json_response['category'], 'course')
|
||||
self.assertEqual(json_response['id'], six.text_type(self.course.location))
|
||||
self.assertEqual(json_response['id'], str(self.course.location))
|
||||
self.assertEqual(json_response['display_name'], self.course.display_name)
|
||||
self.assertNotEqual(json_response.get('published', False), is_concise)
|
||||
self.assertIsNone(json_response.get('visibility_state'))
|
||||
@@ -466,7 +465,7 @@ class TestCourseOutline(CourseTestCase):
|
||||
self.assertGreater(len(children), 0)
|
||||
first_child_response = children[0]
|
||||
self.assertEqual(first_child_response['category'], 'chapter')
|
||||
self.assertEqual(first_child_response['id'], six.text_type(self.chapter.location))
|
||||
self.assertEqual(first_child_response['id'], str(self.chapter.location))
|
||||
self.assertEqual(first_child_response['display_name'], 'Week 1')
|
||||
self.assertNotEqual(json_response.get('published', False), is_concise)
|
||||
if not is_concise:
|
||||
@@ -500,12 +499,12 @@ class TestCourseOutline(CourseTestCase):
|
||||
self.assertIsNone(course_outline_initial_state('no-such-locator', course_structure))
|
||||
|
||||
# Verify that the correct initial state is returned for the test chapter
|
||||
chapter_locator = six.text_type(self.chapter.location)
|
||||
chapter_locator = str(self.chapter.location)
|
||||
initial_state = course_outline_initial_state(chapter_locator, course_structure)
|
||||
self.assertEqual(initial_state['locator_to_show'], chapter_locator)
|
||||
expanded_locators = initial_state['expanded_locators']
|
||||
self.assertIn(six.text_type(self.sequential.location), expanded_locators)
|
||||
self.assertIn(six.text_type(self.vertical.location), expanded_locators)
|
||||
self.assertIn(str(self.sequential.location), expanded_locators)
|
||||
self.assertIn(str(self.vertical.location), expanded_locators)
|
||||
|
||||
def _create_test_data(self, course_module, create_blocks=False, publish=True, block_types=None):
|
||||
"""
|
||||
@@ -516,7 +515,7 @@ class TestCourseOutline(CourseTestCase):
|
||||
ItemFactory.create(
|
||||
parent_location=self.vertical.location,
|
||||
category=block_type,
|
||||
display_name=u'{} Problem'.format(block_type)
|
||||
display_name=f'{block_type} Problem'
|
||||
)
|
||||
|
||||
if not publish:
|
||||
@@ -533,7 +532,7 @@ class TestCourseOutline(CourseTestCase):
|
||||
expected_blocks.append(
|
||||
[
|
||||
reverse_usage_url('container_handler', self.vertical.location),
|
||||
u'{} Problem'.format(block_type)
|
||||
f'{block_type} Problem'
|
||||
]
|
||||
)
|
||||
|
||||
@@ -542,7 +541,7 @@ class TestCourseOutline(CourseTestCase):
|
||||
[component for component in advanced_modules if component in deprecated_block_types]
|
||||
)
|
||||
|
||||
six.assertCountEqual(self, info['blocks'], expected_blocks)
|
||||
self.assertCountEqual(info['blocks'], expected_blocks)
|
||||
self.assertEqual(
|
||||
info['advance_settings_url'],
|
||||
reverse_course_url('advanced_settings_handler', course_id)
|
||||
@@ -604,7 +603,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
Set up the for the course outline tests.
|
||||
"""
|
||||
|
||||
super(TestCourseReIndex, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
self.course.start = datetime.datetime(2014, 1, 1, tzinfo=pytz.utc)
|
||||
modulestore().update_item(self.course, self.user.id)
|
||||
@@ -694,7 +693,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
user=self.user,
|
||||
size=10,
|
||||
from_=0,
|
||||
course_id=six.text_type(self.course.id))
|
||||
course_id=str(self.course.id))
|
||||
self.assertEqual(response['total'], 1)
|
||||
|
||||
# Start manual reindex
|
||||
@@ -706,7 +705,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
user=self.user,
|
||||
size=10,
|
||||
from_=0,
|
||||
course_id=six.text_type(self.course.id))
|
||||
course_id=str(self.course.id))
|
||||
self.assertEqual(response['total'], 1)
|
||||
|
||||
@mock.patch('xmodule.video_module.VideoBlock.index_dictionary')
|
||||
@@ -720,7 +719,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
user=self.user,
|
||||
size=10,
|
||||
from_=0,
|
||||
course_id=six.text_type(self.course.id))
|
||||
course_id=str(self.course.id))
|
||||
self.assertEqual(response['total'], 1)
|
||||
|
||||
# set mocked exception response
|
||||
@@ -742,7 +741,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
user=self.user,
|
||||
size=10,
|
||||
from_=0,
|
||||
course_id=six.text_type(self.course.id))
|
||||
course_id=str(self.course.id))
|
||||
self.assertEqual(response['total'], 1)
|
||||
|
||||
# set mocked exception response
|
||||
@@ -764,7 +763,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
user=self.user,
|
||||
size=10,
|
||||
from_=0,
|
||||
course_id=six.text_type(self.course.id))
|
||||
course_id=str(self.course.id))
|
||||
self.assertEqual(response['total'], 1)
|
||||
|
||||
# set mocked exception response
|
||||
@@ -804,7 +803,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
user=self.user,
|
||||
size=10,
|
||||
from_=0,
|
||||
course_id=six.text_type(self.course.id))
|
||||
course_id=str(self.course.id))
|
||||
self.assertEqual(response['total'], 1)
|
||||
|
||||
# Start manual reindex
|
||||
@@ -816,7 +815,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
user=self.user,
|
||||
size=10,
|
||||
from_=0,
|
||||
course_id=six.text_type(self.course.id))
|
||||
course_id=str(self.course.id))
|
||||
self.assertEqual(response['total'], 1)
|
||||
|
||||
@mock.patch('xmodule.video_module.VideoBlock.index_dictionary')
|
||||
@@ -830,7 +829,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
user=self.user,
|
||||
size=10,
|
||||
from_=0,
|
||||
course_id=six.text_type(self.course.id))
|
||||
course_id=str(self.course.id))
|
||||
self.assertEqual(response['total'], 1)
|
||||
|
||||
# set mocked exception response
|
||||
@@ -852,7 +851,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
user=self.user,
|
||||
size=10,
|
||||
from_=0,
|
||||
course_id=six.text_type(self.course.id))
|
||||
course_id=str(self.course.id))
|
||||
self.assertEqual(response['total'], 1)
|
||||
|
||||
# set mocked exception response
|
||||
@@ -874,7 +873,7 @@ class TestCourseReIndex(CourseTestCase):
|
||||
user=self.user,
|
||||
size=10,
|
||||
from_=0,
|
||||
course_id=six.text_type(self.course.id))
|
||||
course_id=str(self.course.id))
|
||||
self.assertEqual(response['total'], 1)
|
||||
|
||||
# set mocked exception response
|
||||
|
||||
@@ -4,9 +4,9 @@ unit tests for course_info views and models.
|
||||
|
||||
|
||||
import json
|
||||
from unittest.mock import patch # lint-amnesty, pylint: disable=unused-import
|
||||
|
||||
from django.test.utils import override_settings # lint-amnesty, pylint: disable=unused-import
|
||||
from mock import patch # lint-amnesty, pylint: disable=unused-import
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
|
||||
from cms.djangoapps.contentstore.tests.test_course_settings import CourseTestCase
|
||||
@@ -141,9 +141,9 @@ class CourseUpdateTest(CourseTestCase): # lint-amnesty, pylint: disable=missing
|
||||
location.block_type,
|
||||
block_id=location.block_id
|
||||
)
|
||||
update_date = u"January 23, 2014"
|
||||
update_content = u"Hello world!"
|
||||
update_data = u"<ol><li><h2>" + update_date + "</h2>" + update_content + "</li></ol>"
|
||||
update_date = "January 23, 2014"
|
||||
update_content = "Hello world!"
|
||||
update_data = "<ol><li><h2>" + update_date + "</h2>" + update_content + "</li></ol>"
|
||||
course_updates.data = update_data
|
||||
modulestore().update_item(course_updates, self.user.id)
|
||||
|
||||
@@ -151,7 +151,7 @@ class CourseUpdateTest(CourseTestCase): # lint-amnesty, pylint: disable=missing
|
||||
course_update_url = self.create_update_url()
|
||||
resp = self.client.get_json(course_update_url)
|
||||
payload = json.loads(resp.content.decode('utf-8'))
|
||||
self.assertEqual(payload, [{u'date': update_date, u'content': update_content, u'id': 1}])
|
||||
self.assertEqual(payload, [{'date': update_date, 'content': update_content, 'id': 1}])
|
||||
self.assertEqual(len(payload), 1)
|
||||
|
||||
# test getting single update item
|
||||
@@ -159,7 +159,7 @@ class CourseUpdateTest(CourseTestCase): # lint-amnesty, pylint: disable=missing
|
||||
first_update_url = self.create_update_url(provided_id=payload[0]['id'])
|
||||
resp = self.client.get_json(first_update_url)
|
||||
payload = json.loads(resp.content.decode('utf-8'))
|
||||
self.assertEqual(payload, {u'date': u'January 23, 2014', u'content': u'Hello world!', u'id': 1})
|
||||
self.assertEqual(payload, {'date': 'January 23, 2014', 'content': 'Hello world!', 'id': 1})
|
||||
self.assertHTMLEqual(update_date, payload['date'])
|
||||
self.assertHTMLEqual(update_content, payload['content'])
|
||||
|
||||
@@ -175,29 +175,29 @@ class CourseUpdateTest(CourseTestCase): # lint-amnesty, pylint: disable=missing
|
||||
)
|
||||
self.assertHTMLEqual(update_content, json.loads(resp.content.decode('utf-8'))['content'])
|
||||
course_updates = modulestore().get_item(location)
|
||||
self.assertEqual(course_updates.items, [{u'date': update_date, u'content': update_content, u'id': 1}])
|
||||
self.assertEqual(course_updates.items, [{'date': update_date, 'content': update_content, 'id': 1}])
|
||||
# course_updates 'data' field should not update automatically
|
||||
self.assertEqual(course_updates.data, '')
|
||||
|
||||
# test delete course update item (soft delete)
|
||||
course_updates = modulestore().get_item(location)
|
||||
self.assertEqual(course_updates.items, [{u'date': update_date, u'content': update_content, u'id': 1}])
|
||||
self.assertEqual(course_updates.items, [{'date': update_date, 'content': update_content, 'id': 1}])
|
||||
# now try to delete first update item
|
||||
resp = self.client.delete(course_update_url + '1')
|
||||
self.assertEqual(json.loads(resp.content.decode('utf-8')), [])
|
||||
# confirm that course update is soft deleted ('status' flag set to 'deleted') in db
|
||||
course_updates = modulestore().get_item(location)
|
||||
self.assertEqual(course_updates.items,
|
||||
[{u'date': update_date, u'content': update_content, u'id': 1, u'status': 'deleted'}])
|
||||
[{'date': update_date, 'content': update_content, 'id': 1, 'status': 'deleted'}])
|
||||
|
||||
# now try to get deleted update
|
||||
resp = self.client.get_json(course_update_url + '1')
|
||||
payload = json.loads(resp.content.decode('utf-8'))
|
||||
self.assertEqual(payload.get('error'), u"Course update not found.")
|
||||
self.assertEqual(payload.get('error'), "Course update not found.")
|
||||
self.assertEqual(resp.status_code, 404)
|
||||
|
||||
# now check that course update don't munges html
|
||||
update_content = u"""<problem>
|
||||
update_content = """<problem>
|
||||
<p></p>
|
||||
<multiplechoiceresponse>
|
||||
<pre><problem>
|
||||
@@ -248,7 +248,7 @@ class CourseUpdateTest(CourseTestCase): # lint-amnesty, pylint: disable=missing
|
||||
# create a course via the view handler
|
||||
self.client.ajax_post(course_update_url)
|
||||
|
||||
content = u"Sample update"
|
||||
content = "Sample update"
|
||||
payload = {'content': content, 'date': 'January 8, 2013'}
|
||||
resp = self.client.ajax_post(course_update_url, payload)
|
||||
|
||||
@@ -266,13 +266,13 @@ class CourseUpdateTest(CourseTestCase): # lint-amnesty, pylint: disable=missing
|
||||
|
||||
updates_location = self.course.id.make_usage_key('course_info', 'updates')
|
||||
self.assertTrue(isinstance(updates_location, UsageKey))
|
||||
self.assertEqual(updates_location.block_id, u'updates')
|
||||
self.assertEqual(updates_location.block_id, 'updates')
|
||||
|
||||
# check posting on handouts
|
||||
handouts_location = self.course.id.make_usage_key('course_info', 'handouts')
|
||||
course_handouts_url = reverse_usage_url('xblock_handler', handouts_location)
|
||||
|
||||
content = u"Sample handout"
|
||||
content = "Sample handout"
|
||||
payload = {'data': content}
|
||||
resp = self.client.ajax_post(course_handouts_url, payload)
|
||||
|
||||
@@ -292,7 +292,7 @@ class CourseUpdateTest(CourseTestCase): # lint-amnesty, pylint: disable=missing
|
||||
|
||||
updates_location = self.course.id.make_usage_key('course_info', 'updates')
|
||||
self.assertTrue(isinstance(updates_location, UsageKey))
|
||||
self.assertEqual(updates_location.block_id, u'updates')
|
||||
self.assertEqual(updates_location.block_id, 'updates')
|
||||
|
||||
course_updates = modulestore().get_item(updates_location)
|
||||
course_update_items = list(reversed(get_course_update_items(course_updates)))
|
||||
@@ -309,7 +309,7 @@ class CourseUpdateTest(CourseTestCase): # lint-amnesty, pylint: disable=missing
|
||||
modulestore().update_item(course_updates, self.user.id)
|
||||
|
||||
update_content = 'Testing'
|
||||
update_date = u"January 23, 2014"
|
||||
update_date = "January 23, 2014"
|
||||
course_update_url = self.create_update_url()
|
||||
payload = {'content': update_content, 'date': update_date}
|
||||
resp = self.client.ajax_post(
|
||||
@@ -319,4 +319,4 @@ class CourseUpdateTest(CourseTestCase): # lint-amnesty, pylint: disable=missing
|
||||
self.assertHTMLEqual(update_content, json.loads(resp.content.decode('utf-8'))['content'])
|
||||
course_updates = modulestore().get_item(updates_location)
|
||||
del course_updates.items[0]["status"]
|
||||
self.assertEqual(course_updates.items, [{u'date': update_date, u'content': update_content, u'id': 2}])
|
||||
self.assertEqual(course_updates.items, [{'date': update_date, 'content': update_content, 'id': 2}])
|
||||
|
||||
@@ -3,8 +3,7 @@ Unit tests for credit eligibility UI in Studio.
|
||||
"""
|
||||
|
||||
|
||||
import mock
|
||||
import six
|
||||
from unittest import mock
|
||||
|
||||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
|
||||
from cms.djangoapps.contentstore.utils import reverse_course_url
|
||||
@@ -20,9 +19,9 @@ class CreditEligibilityTest(CourseTestCase):
|
||||
eligibility requirements.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(CreditEligibilityTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.course = CourseFactory.create(org='edX', number='dummy', display_name='Credit Course')
|
||||
self.course_details_url = reverse_course_url('settings_handler', six.text_type(self.course.id))
|
||||
self.course_details_url = reverse_course_url('settings_handler', str(self.course.id))
|
||||
|
||||
@mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_CREDIT_ELIGIBILITY': False})
|
||||
def test_course_details_with_disabled_setting(self):
|
||||
@@ -50,7 +49,7 @@ class CreditEligibilityTest(CourseTestCase):
|
||||
|
||||
# verify that credit eligibility requirements block shows if the
|
||||
# course is set as credit course and it has eligibility requirements
|
||||
credit_course = CreditCourse(course_key=six.text_type(self.course.id), enabled=True)
|
||||
credit_course = CreditCourse(course_key=str(self.course.id), enabled=True)
|
||||
credit_course.save()
|
||||
self.assertEqual(len(get_credit_requirements(self.course.id)), 0)
|
||||
# test that after publishing course, minimum grade requirement is added
|
||||
|
||||
@@ -4,13 +4,12 @@ Test module for Entrance Exams AJAX callback handler workflows
|
||||
|
||||
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
|
||||
from django.test.client import RequestFactory
|
||||
from milestones.tests.utils import MilestonesTestCaseMixin
|
||||
from mock import patch
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
|
||||
from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase
|
||||
@@ -40,18 +39,18 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
|
||||
"""
|
||||
Shared scaffolding for individual test runs
|
||||
"""
|
||||
super(EntranceExamHandlerTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.course_key = self.course.id
|
||||
self.usage_key = self.course.location
|
||||
self.course_url = '/course/{}'.format(six.text_type(self.course.id))
|
||||
self.exam_url = '/course/{}/entrance_exam/'.format(six.text_type(self.course.id))
|
||||
self.course_url = '/course/{}'.format(str(self.course.id))
|
||||
self.exam_url = '/course/{}/entrance_exam/'.format(str(self.course.id))
|
||||
self.milestone_relationship_types = milestones_helpers.get_milestone_relationship_types()
|
||||
|
||||
def test_entrance_exam_milestone_addition(self):
|
||||
"""
|
||||
Unit Test: test addition of entrance exam milestone content
|
||||
"""
|
||||
parent_locator = six.text_type(self.course.location)
|
||||
parent_locator = str(self.course.location)
|
||||
created_block = create_xblock(
|
||||
parent_locator=parent_locator,
|
||||
user=self.user,
|
||||
@@ -61,8 +60,8 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
|
||||
)
|
||||
add_entrance_exam_milestone(self.course.id, created_block)
|
||||
content_milestones = milestones_helpers.get_course_content_milestones(
|
||||
six.text_type(self.course.id),
|
||||
six.text_type(created_block.location),
|
||||
str(self.course.id),
|
||||
str(created_block.location),
|
||||
self.milestone_relationship_types['FULFILLS']
|
||||
)
|
||||
self.assertTrue(len(content_milestones))
|
||||
@@ -72,7 +71,7 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
|
||||
"""
|
||||
Unit Test: test removal of entrance exam milestone content
|
||||
"""
|
||||
parent_locator = six.text_type(self.course.location)
|
||||
parent_locator = str(self.course.location)
|
||||
created_block = create_xblock(
|
||||
parent_locator=parent_locator,
|
||||
user=self.user,
|
||||
@@ -82,8 +81,8 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
|
||||
)
|
||||
add_entrance_exam_milestone(self.course.id, created_block)
|
||||
content_milestones = milestones_helpers.get_course_content_milestones(
|
||||
six.text_type(self.course.id),
|
||||
six.text_type(created_block.location),
|
||||
str(self.course.id),
|
||||
str(created_block.location),
|
||||
self.milestone_relationship_types['FULFILLS']
|
||||
)
|
||||
self.assertEqual(len(content_milestones), 1)
|
||||
@@ -92,8 +91,8 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
|
||||
request.user = user
|
||||
remove_entrance_exam_milestone_reference(request, self.course.id)
|
||||
content_milestones = milestones_helpers.get_course_content_milestones(
|
||||
six.text_type(self.course.id),
|
||||
six.text_type(created_block.location),
|
||||
str(self.course.id),
|
||||
str(created_block.location),
|
||||
self.milestone_relationship_types['FULFILLS']
|
||||
)
|
||||
self.assertEqual(len(content_milestones), 0)
|
||||
@@ -113,9 +112,9 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
|
||||
self.assertTrue(metadata['entrance_exam_enabled'])
|
||||
self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct'])
|
||||
self.assertIsNotNone(metadata['entrance_exam_id']['value'])
|
||||
self.assertTrue(len(milestones_helpers.get_course_milestones(six.text_type(self.course.id))))
|
||||
self.assertTrue(len(milestones_helpers.get_course_milestones(str(self.course.id))))
|
||||
content_milestones = milestones_helpers.get_course_content_milestones(
|
||||
six.text_type(self.course.id),
|
||||
str(self.course.id),
|
||||
metadata['entrance_exam_id']['value'],
|
||||
self.milestone_relationship_types['FULFILLS']
|
||||
)
|
||||
@@ -181,11 +180,11 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
|
||||
)
|
||||
user.set_password('test')
|
||||
user.save()
|
||||
milestones = milestones_helpers.get_course_milestones(six.text_type(self.course_key))
|
||||
milestones = milestones_helpers.get_course_milestones(str(self.course_key))
|
||||
self.assertEqual(len(milestones), 1)
|
||||
milestone_key = '{}.{}'.format(milestones[0]['namespace'], milestones[0]['name'])
|
||||
paths = milestones_helpers.get_course_milestones_fulfillment_paths(
|
||||
six.text_type(self.course_key),
|
||||
str(self.course_key),
|
||||
milestones_helpers.serialize_user(user)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Exam Settings View Tests
|
||||
"""
|
||||
@@ -32,7 +30,7 @@ class TestExamSettingsView(CourseTestCase, UrlResetMixin):
|
||||
"""
|
||||
Set up the for the exam settings view tests.
|
||||
"""
|
||||
super(TestExamSettingsView, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.reset_urls()
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -4,10 +4,9 @@ Unit tests for the gating feature in Studio
|
||||
|
||||
|
||||
import json
|
||||
from unittest.mock import patch
|
||||
|
||||
import ddt
|
||||
import six
|
||||
from mock import patch
|
||||
|
||||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
|
||||
from cms.djangoapps.contentstore.utils import reverse_usage_url
|
||||
@@ -30,7 +29,7 @@ class TestSubsectionGating(CourseTestCase):
|
||||
"""
|
||||
Initial data setup
|
||||
"""
|
||||
super(TestSubsectionGating, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
# Enable subsection gating for the test course
|
||||
self.course.enable_subsection_gating = True
|
||||
@@ -90,13 +89,13 @@ class TestSubsectionGating(CourseTestCase):
|
||||
|
||||
self.client.ajax_post(
|
||||
self.seq2_url,
|
||||
data={'prereqUsageKey': six.text_type(self.seq1.location), 'prereqMinScore': '100',
|
||||
data={'prereqUsageKey': str(self.seq1.location), 'prereqMinScore': '100',
|
||||
'prereqMinCompletion': '100'}
|
||||
)
|
||||
mock_set_required_content.assert_called_with(
|
||||
self.course.id,
|
||||
self.seq2.location,
|
||||
six.text_type(self.seq1.location),
|
||||
str(self.seq1.location),
|
||||
'100',
|
||||
'100'
|
||||
)
|
||||
@@ -133,17 +132,17 @@ class TestSubsectionGating(CourseTestCase):
|
||||
mock_is_prereq, mock_get_required_content, mock_get_prereqs
|
||||
):
|
||||
mock_is_prereq.return_value = True
|
||||
mock_get_required_content.return_value = six.text_type(self.seq1.location), min_score, min_completion
|
||||
mock_get_required_content.return_value = str(self.seq1.location), min_score, min_completion
|
||||
mock_get_prereqs.return_value = [
|
||||
{'namespace': '{}{}'.format(six.text_type(self.seq1.location), GATING_NAMESPACE_QUALIFIER)},
|
||||
{'namespace': '{}{}'.format(six.text_type(self.seq2.location), GATING_NAMESPACE_QUALIFIER)}
|
||||
{'namespace': '{}{}'.format(str(self.seq1.location), GATING_NAMESPACE_QUALIFIER)},
|
||||
{'namespace': '{}{}'.format(str(self.seq2.location), GATING_NAMESPACE_QUALIFIER)}
|
||||
]
|
||||
resp = json.loads(self.client.get_json(self.seq2_url).content.decode('utf-8'))
|
||||
mock_is_prereq.assert_called_with(self.course.id, self.seq2.location)
|
||||
mock_get_required_content.assert_called_with(self.course.id, self.seq2.location)
|
||||
mock_get_prereqs.assert_called_with(self.course.id)
|
||||
self.assertTrue(resp['is_prereq'])
|
||||
self.assertEqual(resp['prereq'], six.text_type(self.seq1.location))
|
||||
self.assertEqual(resp['prereq'], str(self.seq1.location))
|
||||
self.assertEqual(resp['prereq_min_score'], min_score)
|
||||
self.assertEqual(resp['prereq_min_completion'], min_completion)
|
||||
self.assertEqual(resp['visibility_state'], VisibilityState.gated)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Group Configuration Tests.
|
||||
"""
|
||||
@@ -7,11 +5,9 @@ Group Configuration Tests.
|
||||
|
||||
import json
|
||||
from operator import itemgetter
|
||||
from unittest.mock import patch
|
||||
|
||||
import ddt
|
||||
import six
|
||||
from mock import patch
|
||||
from six.moves import range
|
||||
|
||||
from cms.djangoapps.contentstore.course_group_config import (
|
||||
CONTENT_GROUP_CONFIGURATION_NAME,
|
||||
@@ -29,24 +25,24 @@ from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID, Group,
|
||||
from xmodule.validation import StudioValidation, StudioValidationMessage
|
||||
|
||||
GROUP_CONFIGURATION_JSON = {
|
||||
u'name': u'Test name',
|
||||
u'scheme': u'random',
|
||||
u'description': u'Test description',
|
||||
u'version': UserPartition.VERSION,
|
||||
u'groups': [
|
||||
'name': 'Test name',
|
||||
'scheme': 'random',
|
||||
'description': 'Test description',
|
||||
'version': UserPartition.VERSION,
|
||||
'groups': [
|
||||
{
|
||||
u'name': u'Group A',
|
||||
u'version': 1,
|
||||
'name': 'Group A',
|
||||
'version': 1,
|
||||
}, {
|
||||
u'name': u'Group B',
|
||||
u'version': 1,
|
||||
'name': 'Group B',
|
||||
'version': 1,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
# pylint: disable=no-member
|
||||
class HelperMethods(object):
|
||||
class HelperMethods:
|
||||
"""
|
||||
Mixin that provides useful methods for Group Configuration tests.
|
||||
"""
|
||||
@@ -61,12 +57,12 @@ class HelperMethods(object):
|
||||
sequential = ItemFactory.create(
|
||||
category='sequential',
|
||||
parent_location=self.course.location,
|
||||
display_name=u'Test Subsection {}'.format(name_suffix)
|
||||
display_name=f'Test Subsection {name_suffix}'
|
||||
)
|
||||
vertical = ItemFactory.create(
|
||||
category='vertical',
|
||||
parent_location=sequential.location,
|
||||
display_name=u'Test Unit {}'.format(name_suffix)
|
||||
display_name=f'Test Unit {name_suffix}'
|
||||
)
|
||||
c0_url = self.course.id.make_usage_key("vertical", "split_test_cond0")
|
||||
c1_url = self.course.id.make_usage_key("vertical", "split_test_cond1")
|
||||
@@ -75,7 +71,7 @@ class HelperMethods(object):
|
||||
category='split_test',
|
||||
parent_location=vertical.location,
|
||||
user_partition_id=cid,
|
||||
display_name=u"Test Content Experiment {}{}".format(name_suffix, special_characters),
|
||||
display_name=f"Test Content Experiment {name_suffix}{special_characters}",
|
||||
group_id_to_child={"0": c0_url, "1": c1_url, "2": c2_url}
|
||||
)
|
||||
ItemFactory.create(
|
||||
@@ -102,7 +98,7 @@ class HelperMethods(object):
|
||||
problem = ItemFactory.create(
|
||||
category='problem',
|
||||
parent_location=c1_vertical.location,
|
||||
display_name=u"Test Problem"
|
||||
display_name="Test Problem"
|
||||
)
|
||||
self.client.ajax_post(
|
||||
reverse_usage_url("xblock_handler", problem.location),
|
||||
@@ -130,20 +126,20 @@ class HelperMethods(object):
|
||||
subsection = ItemFactory.create(
|
||||
category='sequential',
|
||||
parent_location=self.course.location,
|
||||
display_name=u"Test Subsection {}".format(name_suffix)
|
||||
display_name=f"Test Subsection {name_suffix}"
|
||||
)
|
||||
vertical_parent_location = subsection.location
|
||||
|
||||
vertical = ItemFactory.create(
|
||||
category='vertical',
|
||||
parent_location=vertical_parent_location,
|
||||
display_name=u"Test Unit {}".format(name_suffix)
|
||||
display_name=f"Test Unit {name_suffix}"
|
||||
)
|
||||
|
||||
problem = ItemFactory.create(
|
||||
category='problem',
|
||||
parent_location=vertical.location,
|
||||
display_name=u"Test Problem {}{}".format(name_suffix, special_characters)
|
||||
display_name=f"Test Problem {name_suffix}{special_characters}"
|
||||
)
|
||||
|
||||
group_access_content = {'group_access': {cid: [group_id]}}
|
||||
@@ -175,7 +171,7 @@ class HelperMethods(object):
|
||||
|
||||
|
||||
# pylint: disable=no-member
|
||||
class GroupConfigurationsBaseTestCase(object):
|
||||
class GroupConfigurationsBaseTestCase:
|
||||
"""
|
||||
Mixin with base test cases for the group configurations.
|
||||
"""
|
||||
@@ -199,17 +195,17 @@ class GroupConfigurationsBaseTestCase(object):
|
||||
bad_jsons = [
|
||||
# must have name of the configuration
|
||||
{
|
||||
u'description': 'Test description',
|
||||
u'groups': [
|
||||
{u'name': u'Group A'},
|
||||
{u'name': u'Group B'},
|
||||
'description': 'Test description',
|
||||
'groups': [
|
||||
{'name': 'Group A'},
|
||||
{'name': 'Group B'},
|
||||
],
|
||||
},
|
||||
# must have at least one group
|
||||
{
|
||||
u'name': u'Test name',
|
||||
u'description': u'Test description',
|
||||
u'groups': [],
|
||||
'name': 'Test name',
|
||||
'description': 'Test description',
|
||||
'groups': [],
|
||||
},
|
||||
# an empty json
|
||||
{},
|
||||
@@ -233,7 +229,7 @@ class GroupConfigurationsBaseTestCase(object):
|
||||
Test invalid json handling.
|
||||
"""
|
||||
# No property name.
|
||||
invalid_json = u"{u'name': 'Test Name', []}"
|
||||
invalid_json = "{'name': 'Test Name', []}"
|
||||
|
||||
response = self.client.post(
|
||||
self._url(),
|
||||
@@ -296,16 +292,16 @@ class GroupConfigurationsListHandlerTestCase(CourseTestCase, GroupConfigurations
|
||||
Test that you can create a group configuration.
|
||||
"""
|
||||
expected = {
|
||||
u'description': u'Test description',
|
||||
u'name': u'Test name',
|
||||
u'scheme': u'random',
|
||||
u'version': UserPartition.VERSION,
|
||||
u'groups': [
|
||||
{u'name': u'Group A', u'version': 1},
|
||||
{u'name': u'Group B', u'version': 1},
|
||||
'description': 'Test description',
|
||||
'name': 'Test name',
|
||||
'scheme': 'random',
|
||||
'version': UserPartition.VERSION,
|
||||
'groups': [
|
||||
{'name': 'Group A', 'version': 1},
|
||||
{'name': 'Group B', 'version': 1},
|
||||
],
|
||||
u'parameters': {},
|
||||
u'active': True
|
||||
'parameters': {},
|
||||
'active': True
|
||||
}
|
||||
response = self.client.ajax_post(
|
||||
self._url(),
|
||||
@@ -323,10 +319,10 @@ class GroupConfigurationsListHandlerTestCase(CourseTestCase, GroupConfigurations
|
||||
# Verify that user_partitions in the course contains the new group configuration.
|
||||
user_partititons = self.course.user_partitions
|
||||
self.assertEqual(len(user_partititons), 1)
|
||||
self.assertEqual(user_partititons[0].name, u'Test name')
|
||||
self.assertEqual(user_partititons[0].name, 'Test name')
|
||||
self.assertEqual(len(user_partititons[0].groups), 2)
|
||||
self.assertEqual(user_partititons[0].groups[0].name, u'Group A')
|
||||
self.assertEqual(user_partititons[0].groups[1].name, u'Group B')
|
||||
self.assertEqual(user_partititons[0].groups[0].name, 'Group A')
|
||||
self.assertEqual(user_partititons[0].groups[1].name, 'Group B')
|
||||
self.assertEqual(user_partititons[0].parameters, {})
|
||||
|
||||
def test_lazily_creates_cohort_configuration(self):
|
||||
@@ -346,7 +342,7 @@ class GroupConfigurationsListHandlerTestCase(CourseTestCase, GroupConfigurations
|
||||
"""
|
||||
group_config = dict(GROUP_CONFIGURATION_JSON)
|
||||
group_config['scheme'] = scheme_id
|
||||
group_config.setdefault('parameters', {})['course_id'] = six.text_type(self.course.id)
|
||||
group_config.setdefault('parameters', {})['course_id'] = str(self.course.id)
|
||||
response = self.client.ajax_post(
|
||||
self._url(),
|
||||
data=group_config
|
||||
@@ -377,17 +373,17 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
|
||||
PUT new content group.
|
||||
"""
|
||||
expected = {
|
||||
u'id': 666,
|
||||
u'name': u'Test name',
|
||||
u'scheme': u'cohort',
|
||||
u'description': u'Test description',
|
||||
u'version': UserPartition.VERSION,
|
||||
u'groups': [
|
||||
{u'id': 0, u'name': u'Group A', u'version': 1, u'usage': []},
|
||||
{u'id': 1, u'name': u'Group B', u'version': 1, u'usage': []},
|
||||
'id': 666,
|
||||
'name': 'Test name',
|
||||
'scheme': 'cohort',
|
||||
'description': 'Test description',
|
||||
'version': UserPartition.VERSION,
|
||||
'groups': [
|
||||
{'id': 0, 'name': 'Group A', 'version': 1, 'usage': []},
|
||||
{'id': 1, 'name': 'Group B', 'version': 1, 'usage': []},
|
||||
],
|
||||
u'parameters': {},
|
||||
u'active': True,
|
||||
'parameters': {},
|
||||
'active': True,
|
||||
}
|
||||
response = self.client.put(
|
||||
self._url(cid=666),
|
||||
@@ -403,10 +399,10 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
|
||||
# Verify that user_partitions in the course contains the new group configuration.
|
||||
user_partitions = self.course.user_partitions
|
||||
self.assertEqual(len(user_partitions), 1)
|
||||
self.assertEqual(user_partitions[0].name, u'Test name')
|
||||
self.assertEqual(user_partitions[0].name, 'Test name')
|
||||
self.assertEqual(len(user_partitions[0].groups), 2)
|
||||
self.assertEqual(user_partitions[0].groups[0].name, u'Group A')
|
||||
self.assertEqual(user_partitions[0].groups[1].name, u'Group B')
|
||||
self.assertEqual(user_partitions[0].groups[0].name, 'Group A')
|
||||
self.assertEqual(user_partitions[0].groups[1].name, 'Group B')
|
||||
self.assertEqual(user_partitions[0].parameters, {})
|
||||
|
||||
def test_can_edit_content_group(self):
|
||||
@@ -417,17 +413,17 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
|
||||
self.save_course()
|
||||
|
||||
expected = {
|
||||
u'id': self.ID,
|
||||
u'name': u'New Test name',
|
||||
u'scheme': u'cohort',
|
||||
u'description': u'New Test description',
|
||||
u'version': UserPartition.VERSION,
|
||||
u'groups': [
|
||||
{u'id': 0, u'name': u'New Group Name', u'version': 1, u'usage': []},
|
||||
{u'id': 2, u'name': u'Group C', u'version': 1, u'usage': []},
|
||||
'id': self.ID,
|
||||
'name': 'New Test name',
|
||||
'scheme': 'cohort',
|
||||
'description': 'New Test description',
|
||||
'version': UserPartition.VERSION,
|
||||
'groups': [
|
||||
{'id': 0, 'name': 'New Group Name', 'version': 1, 'usage': []},
|
||||
{'id': 2, 'name': 'Group C', 'version': 1, 'usage': []},
|
||||
],
|
||||
u'parameters': {},
|
||||
u'active': True,
|
||||
'parameters': {},
|
||||
'active': True,
|
||||
}
|
||||
|
||||
response = self.client.put(
|
||||
@@ -445,10 +441,10 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
|
||||
user_partititons = self.course.user_partitions
|
||||
|
||||
self.assertEqual(len(user_partititons), 1)
|
||||
self.assertEqual(user_partititons[0].name, u'New Test name')
|
||||
self.assertEqual(user_partititons[0].name, 'New Test name')
|
||||
self.assertEqual(len(user_partititons[0].groups), 2)
|
||||
self.assertEqual(user_partititons[0].groups[0].name, u'New Group Name')
|
||||
self.assertEqual(user_partititons[0].groups[1].name, u'Group C')
|
||||
self.assertEqual(user_partititons[0].groups[0].name, 'New Group Name')
|
||||
self.assertEqual(user_partititons[0].groups[1].name, 'Group C')
|
||||
self.assertEqual(user_partititons[0].parameters, {})
|
||||
|
||||
def test_can_delete_content_group(self):
|
||||
@@ -521,18 +517,18 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
|
||||
PUT new group configuration when no configurations exist in the course.
|
||||
"""
|
||||
expected = {
|
||||
u'id': 999,
|
||||
u'name': u'Test name',
|
||||
u'scheme': u'random',
|
||||
u'description': u'Test description',
|
||||
u'version': UserPartition.VERSION,
|
||||
u'groups': [
|
||||
{u'id': 0, u'name': u'Group A', u'version': 1},
|
||||
{u'id': 1, u'name': u'Group B', u'version': 1},
|
||||
'id': 999,
|
||||
'name': 'Test name',
|
||||
'scheme': 'random',
|
||||
'description': 'Test description',
|
||||
'version': UserPartition.VERSION,
|
||||
'groups': [
|
||||
{'id': 0, 'name': 'Group A', 'version': 1},
|
||||
{'id': 1, 'name': 'Group B', 'version': 1},
|
||||
],
|
||||
u'usage': [],
|
||||
u'parameters': {},
|
||||
u'active': True,
|
||||
'usage': [],
|
||||
'parameters': {},
|
||||
'active': True,
|
||||
}
|
||||
|
||||
response = self.client.put(
|
||||
@@ -548,10 +544,10 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
|
||||
# Verify that user_partitions in the course contains the new group configuration.
|
||||
user_partitions = self.course.user_partitions
|
||||
self.assertEqual(len(user_partitions), 1)
|
||||
self.assertEqual(user_partitions[0].name, u'Test name')
|
||||
self.assertEqual(user_partitions[0].name, 'Test name')
|
||||
self.assertEqual(len(user_partitions[0].groups), 2)
|
||||
self.assertEqual(user_partitions[0].groups[0].name, u'Group A')
|
||||
self.assertEqual(user_partitions[0].groups[1].name, u'Group B')
|
||||
self.assertEqual(user_partitions[0].groups[0].name, 'Group A')
|
||||
self.assertEqual(user_partitions[0].groups[1].name, 'Group B')
|
||||
self.assertEqual(user_partitions[0].parameters, {})
|
||||
|
||||
def test_can_edit_group_configuration(self):
|
||||
@@ -562,18 +558,18 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
|
||||
self.save_course()
|
||||
|
||||
expected = {
|
||||
u'id': self.ID,
|
||||
u'name': u'New Test name',
|
||||
u'scheme': u'random',
|
||||
u'description': u'New Test description',
|
||||
u'version': UserPartition.VERSION,
|
||||
u'groups': [
|
||||
{u'id': 0, u'name': u'New Group Name', u'version': 1},
|
||||
{u'id': 2, u'name': u'Group C', u'version': 1},
|
||||
'id': self.ID,
|
||||
'name': 'New Test name',
|
||||
'scheme': 'random',
|
||||
'description': 'New Test description',
|
||||
'version': UserPartition.VERSION,
|
||||
'groups': [
|
||||
{'id': 0, 'name': 'New Group Name', 'version': 1},
|
||||
{'id': 2, 'name': 'Group C', 'version': 1},
|
||||
],
|
||||
u'usage': [],
|
||||
u'parameters': {},
|
||||
u'active': True,
|
||||
'usage': [],
|
||||
'parameters': {},
|
||||
'active': True,
|
||||
}
|
||||
|
||||
response = self.client.put(
|
||||
@@ -591,10 +587,10 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
|
||||
user_partititons = self.course.user_partitions
|
||||
|
||||
self.assertEqual(len(user_partititons), 1)
|
||||
self.assertEqual(user_partititons[0].name, u'New Test name')
|
||||
self.assertEqual(user_partititons[0].name, 'New Test name')
|
||||
self.assertEqual(len(user_partititons[0].groups), 2)
|
||||
self.assertEqual(user_partititons[0].groups[0].name, u'New Group Name')
|
||||
self.assertEqual(user_partititons[0].groups[1].name, u'Group C')
|
||||
self.assertEqual(user_partititons[0].groups[0].name, 'New Group Name')
|
||||
self.assertEqual(user_partititons[0].groups[1].name, 'Group C')
|
||||
self.assertEqual(user_partititons[0].parameters, {})
|
||||
|
||||
def test_can_delete_group_configuration(self):
|
||||
@@ -663,7 +659,7 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
|
||||
"""
|
||||
group_config = dict(GROUP_CONFIGURATION_JSON)
|
||||
group_config['scheme'] = scheme_id
|
||||
group_config.setdefault('parameters', {})['course_id'] = six.text_type(self.course.id)
|
||||
group_config.setdefault('parameters', {})['course_id'] = str(self.course.id)
|
||||
response = self.client.ajax_post(
|
||||
self._url(),
|
||||
data=group_config
|
||||
@@ -681,7 +677,7 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
|
||||
"""
|
||||
group_config = dict(GROUP_CONFIGURATION_JSON)
|
||||
group_config['scheme'] = scheme_id
|
||||
group_config.setdefault('parameters', {})['course_id'] = six.text_type(self.course.id)
|
||||
group_config.setdefault('parameters', {})['course_id'] = str(self.course.id)
|
||||
response = self.client.put(
|
||||
self._url(cid=partition_id),
|
||||
data=json.dumps(group_config),
|
||||
@@ -722,8 +718,8 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
{'id': 1, 'name': 'Group B', 'version': 1, 'usage': usage_for_group},
|
||||
{'id': 2, 'name': 'Group C', 'version': 1, 'usage': []},
|
||||
],
|
||||
u'parameters': {},
|
||||
u'active': True,
|
||||
'parameters': {},
|
||||
'active': True,
|
||||
}
|
||||
|
||||
def test_content_group_not_used(self):
|
||||
@@ -741,15 +737,15 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
"""
|
||||
self._add_user_partitions(count=1, scheme_id='cohort')
|
||||
vertical, __ = self._create_problem_with_content_group(
|
||||
cid=0, group_id=1, name_suffix='0', special_characters=u"JOSÉ ANDRÉS"
|
||||
cid=0, group_id=1, name_suffix='0', special_characters="JOSÉ ANDRÉS"
|
||||
)
|
||||
|
||||
actual = self._get_user_partition('cohort')
|
||||
expected = self._get_expected_content_group(
|
||||
usage_for_group=[
|
||||
{
|
||||
'url': u"/container/{}".format(vertical.location),
|
||||
'label': u"Test Unit 0 / Test Problem 0JOSÉ ANDRÉS"
|
||||
'url': f"/container/{vertical.location}",
|
||||
'label': "Test Unit 0 / Test Problem 0JOSÉ ANDRÉS"
|
||||
}
|
||||
]
|
||||
)
|
||||
@@ -767,7 +763,7 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
|
||||
expected = self._get_expected_content_group(usage_for_group=[
|
||||
{
|
||||
'url': '/container/{}'.format(vertical.location),
|
||||
'url': f'/container/{vertical.location}',
|
||||
'label': 'Test Unit 0 / Test Problem 0'
|
||||
}
|
||||
])
|
||||
@@ -792,7 +788,7 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
if module_store_type == ModuleStoreEnum.Type.mongo:
|
||||
expected = self._get_expected_content_group(usage_for_group=[
|
||||
{
|
||||
'url': '/container/{}'.format(vertical.location),
|
||||
'url': f'/container/{vertical.location}',
|
||||
'label': 'Test Unit 0 / Test Problem 0'
|
||||
}
|
||||
])
|
||||
@@ -818,11 +814,11 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
|
||||
expected = self._get_expected_content_group(usage_for_group=[
|
||||
{
|
||||
'url': '/container/{}'.format(vertical1.location),
|
||||
'url': f'/container/{vertical1.location}',
|
||||
'label': 'Test Unit 1 / Test Problem 1'
|
||||
},
|
||||
{
|
||||
'url': '/container/{}'.format(vertical.location),
|
||||
'url': f'/container/{vertical.location}',
|
||||
'label': 'Test Unit 0 / Test Problem 0'
|
||||
}
|
||||
])
|
||||
@@ -884,13 +880,13 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
'groups': [
|
||||
{'id': 3, 'name': 'Problem Group', 'version': 1, 'usage': [
|
||||
{
|
||||
'url': '/container/{}'.format(split_test.location),
|
||||
'url': f'/container/{split_test.location}',
|
||||
'label': 'Condition 1 vertical / Test Problem'
|
||||
}
|
||||
]},
|
||||
],
|
||||
u'parameters': {},
|
||||
u'active': True,
|
||||
'parameters': {},
|
||||
'active': True,
|
||||
}
|
||||
actual = self._get_user_partition('cohort')
|
||||
|
||||
@@ -933,17 +929,17 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
'groups': [
|
||||
{'id': 0, 'name': 'Group', 'version': 1, 'usage': [
|
||||
{
|
||||
'url': u"/container/{}".format(vertical.location),
|
||||
'label': u"Test Subsection 0 / Test Unit 0"
|
||||
'url': f"/container/{vertical.location}",
|
||||
'label': "Test Subsection 0 / Test Unit 0"
|
||||
},
|
||||
{
|
||||
'url': u"/container/{}".format(vertical.location),
|
||||
'label': u"Test Unit 0 / Test Problem 0"
|
||||
'url': f"/container/{vertical.location}",
|
||||
'label': "Test Unit 0 / Test Problem 0"
|
||||
}
|
||||
]},
|
||||
],
|
||||
u'parameters': {},
|
||||
u'active': True,
|
||||
'parameters': {},
|
||||
'active': True,
|
||||
}
|
||||
|
||||
self.maxDiff = None
|
||||
@@ -972,7 +968,7 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
{'id': 2, 'name': 'Group C', 'version': 1},
|
||||
],
|
||||
'usage': [{
|
||||
'url': '/container/{}'.format(split_test.location),
|
||||
'url': f'/container/{split_test.location}',
|
||||
'label': 'Test Unit 0 / Test Content Experiment 0',
|
||||
'validation': None,
|
||||
}],
|
||||
@@ -1002,7 +998,7 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
characters are being used in content experiment
|
||||
"""
|
||||
self._add_user_partitions(count=1)
|
||||
__, split_test, __ = self._create_content_experiment(cid=0, name_suffix='0', special_characters=u"JOSÉ ANDRÉS")
|
||||
__, split_test, __ = self._create_content_experiment(cid=0, name_suffix='0', special_characters="JOSÉ ANDRÉS")
|
||||
|
||||
actual = GroupConfiguration.get_split_test_partitions_with_usage(self.store, self.course, )
|
||||
|
||||
@@ -1019,7 +1015,7 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
],
|
||||
'usage': [{
|
||||
'url': reverse_usage_url("container_handler", split_test.location),
|
||||
'label': u"Test Unit 0 / Test Content Experiment 0JOSÉ ANDRÉS",
|
||||
'label': "Test Unit 0 / Test Content Experiment 0JOSÉ ANDRÉS",
|
||||
'validation': None,
|
||||
}],
|
||||
'parameters': {},
|
||||
@@ -1051,11 +1047,11 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
|
||||
{'id': 2, 'name': 'Group C', 'version': 1},
|
||||
],
|
||||
'usage': [{
|
||||
'url': '/container/{}'.format(split_test.location),
|
||||
'url': f'/container/{split_test.location}',
|
||||
'label': 'Test Unit 0 / Test Content Experiment 0',
|
||||
'validation': None,
|
||||
}, {
|
||||
'url': '/container/{}'.format(split_test1.location),
|
||||
'url': f'/container/{split_test1.location}',
|
||||
'label': 'Test Unit 1 / Test Content Experiment 1',
|
||||
'validation': None,
|
||||
}],
|
||||
@@ -1192,9 +1188,9 @@ class GroupConfigurationsValidationTestCase(CourseTestCase, HelperMethods):
|
||||
"""
|
||||
Tests if validation message is present (error case).
|
||||
"""
|
||||
mocked_message = StudioValidationMessage(StudioValidationMessage.ERROR, u"Validation message")
|
||||
mocked_message = StudioValidationMessage(StudioValidationMessage.ERROR, "Validation message")
|
||||
expected_result = StudioValidationMessage(
|
||||
StudioValidationMessage.ERROR, u"This content experiment has issues that affect content visibility."
|
||||
StudioValidationMessage.ERROR, "This content experiment has issues that affect content visibility."
|
||||
)
|
||||
self.verify_validation_add_usage_info(expected_result, mocked_message) # pylint: disable=no-value-for-parameter
|
||||
|
||||
@@ -1202,9 +1198,9 @@ class GroupConfigurationsValidationTestCase(CourseTestCase, HelperMethods):
|
||||
"""
|
||||
Tests if validation message is present (warning case).
|
||||
"""
|
||||
mocked_message = StudioValidationMessage(StudioValidationMessage.WARNING, u"Validation message")
|
||||
mocked_message = StudioValidationMessage(StudioValidationMessage.WARNING, "Validation message")
|
||||
expected_result = StudioValidationMessage(
|
||||
StudioValidationMessage.WARNING, u"This content experiment has issues that affect content visibility."
|
||||
StudioValidationMessage.WARNING, "This content experiment has issues that affect content visibility."
|
||||
)
|
||||
self.verify_validation_add_usage_info(expected_result, mocked_message) # pylint: disable=no-value-for-parameter
|
||||
|
||||
@@ -1233,9 +1229,9 @@ class GroupConfigurationsValidationTestCase(CourseTestCase, HelperMethods):
|
||||
"""
|
||||
Tests if validation message is present when updating usage info.
|
||||
"""
|
||||
mocked_message = StudioValidationMessage(StudioValidationMessage.WARNING, u"Validation message")
|
||||
mocked_message = StudioValidationMessage(StudioValidationMessage.WARNING, "Validation message")
|
||||
expected_result = StudioValidationMessage(
|
||||
StudioValidationMessage.WARNING, u"This content experiment has issues that affect content visibility."
|
||||
StudioValidationMessage.WARNING, "This content experiment has issues that affect content visibility."
|
||||
)
|
||||
# pylint: disable=no-value-for-parameter
|
||||
self.verify_validation_update_usage_info(expected_result, mocked_message)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#-*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Course Header Menu Tests.
|
||||
"""
|
||||
@@ -31,7 +29,7 @@ class TestHeaderMenu(CourseTestCase, UrlResetMixin):
|
||||
"""
|
||||
Set up the for the course header menu tests.
|
||||
"""
|
||||
super(TestHeaderMenu, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.reset_urls()
|
||||
|
||||
def test_header_menu_without_web_certs_enabled(self):
|
||||
|
||||
@@ -3,7 +3,6 @@ Unit tests for helpers.py.
|
||||
"""
|
||||
|
||||
|
||||
import six
|
||||
from django.utils import http
|
||||
|
||||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
|
||||
@@ -20,7 +19,7 @@ class HelpersTestCase(CourseTestCase):
|
||||
def test_xblock_studio_url(self):
|
||||
|
||||
# Verify course URL
|
||||
course_url = u'/course/{}'.format(six.text_type(self.course.id))
|
||||
course_url = '/course/{}'.format(str(self.course.id))
|
||||
self.assertEqual(xblock_studio_url(self.course), course_url)
|
||||
|
||||
# Verify chapter URL
|
||||
@@ -28,7 +27,7 @@ class HelpersTestCase(CourseTestCase):
|
||||
display_name="Week 1")
|
||||
self.assertEqual(
|
||||
xblock_studio_url(chapter),
|
||||
u'{}?show={}'.format(course_url, http.urlquote(str(chapter.location).encode()))
|
||||
'{}?show={}'.format(course_url, http.urlquote(str(chapter.location).encode()))
|
||||
)
|
||||
|
||||
# Verify sequential URL
|
||||
@@ -36,18 +35,18 @@ class HelpersTestCase(CourseTestCase):
|
||||
display_name="Lesson 1")
|
||||
self.assertEqual(
|
||||
xblock_studio_url(sequential),
|
||||
u'{}?show={}'.format(course_url, http.urlquote(str(sequential.location).encode()))
|
||||
'{}?show={}'.format(course_url, http.urlquote(str(sequential.location).encode()))
|
||||
)
|
||||
|
||||
# Verify unit URL
|
||||
vertical = ItemFactory.create(parent_location=sequential.location, category='vertical',
|
||||
display_name='Unit')
|
||||
self.assertEqual(xblock_studio_url(vertical), u'/container/{}'.format(vertical.location))
|
||||
self.assertEqual(xblock_studio_url(vertical), f'/container/{vertical.location}')
|
||||
|
||||
# Verify child vertical URL
|
||||
child_vertical = ItemFactory.create(parent_location=vertical.location, category='vertical',
|
||||
display_name='Child Vertical')
|
||||
self.assertEqual(xblock_studio_url(child_vertical), u'/container/{}'.format(child_vertical.location))
|
||||
self.assertEqual(xblock_studio_url(child_vertical), f'/container/{child_vertical.location}')
|
||||
|
||||
# Verify video URL
|
||||
video = ItemFactory.create(parent_location=child_vertical.location, category="video",
|
||||
@@ -56,37 +55,37 @@ class HelpersTestCase(CourseTestCase):
|
||||
|
||||
# Verify library URL
|
||||
library = LibraryFactory.create()
|
||||
expected_url = u'/library/{}'.format(six.text_type(library.location.library_key))
|
||||
expected_url = '/library/{}'.format(str(library.location.library_key))
|
||||
self.assertEqual(xblock_studio_url(library), expected_url)
|
||||
|
||||
def test_xblock_type_display_name(self):
|
||||
|
||||
# Verify chapter type display name
|
||||
chapter = ItemFactory.create(parent_location=self.course.location, category='chapter')
|
||||
self.assertEqual(xblock_type_display_name(chapter), u'Section')
|
||||
self.assertEqual(xblock_type_display_name('chapter'), u'Section')
|
||||
self.assertEqual(xblock_type_display_name(chapter), 'Section')
|
||||
self.assertEqual(xblock_type_display_name('chapter'), 'Section')
|
||||
|
||||
# Verify sequential type display name
|
||||
sequential = ItemFactory.create(parent_location=chapter.location, category='sequential')
|
||||
self.assertEqual(xblock_type_display_name(sequential), u'Subsection')
|
||||
self.assertEqual(xblock_type_display_name('sequential'), u'Subsection')
|
||||
self.assertEqual(xblock_type_display_name(sequential), 'Subsection')
|
||||
self.assertEqual(xblock_type_display_name('sequential'), 'Subsection')
|
||||
|
||||
# Verify unit type display names
|
||||
vertical = ItemFactory.create(parent_location=sequential.location, category='vertical')
|
||||
self.assertEqual(xblock_type_display_name(vertical), u'Unit')
|
||||
self.assertEqual(xblock_type_display_name('vertical'), u'Unit')
|
||||
self.assertEqual(xblock_type_display_name(vertical), 'Unit')
|
||||
self.assertEqual(xblock_type_display_name('vertical'), 'Unit')
|
||||
|
||||
# Verify child vertical type display name
|
||||
child_vertical = ItemFactory.create(parent_location=vertical.location, category='vertical',
|
||||
display_name='Child Vertical')
|
||||
self.assertEqual(xblock_type_display_name(child_vertical), u'Vertical')
|
||||
self.assertEqual(xblock_type_display_name(child_vertical), 'Vertical')
|
||||
|
||||
# Verify video type display names
|
||||
video = ItemFactory.create(parent_location=vertical.location, category="video")
|
||||
self.assertEqual(xblock_type_display_name(video), u'Video')
|
||||
self.assertEqual(xblock_type_display_name('video'), u'Video')
|
||||
self.assertEqual(xblock_type_display_name(video), 'Video')
|
||||
self.assertEqual(xblock_type_display_name('video'), 'Video')
|
||||
|
||||
# Verify split test type display names
|
||||
split_test = ItemFactory.create(parent_location=vertical.location, category="split_test")
|
||||
self.assertEqual(xblock_type_display_name(split_test), u'Content Experiment')
|
||||
self.assertEqual(xblock_type_display_name('split_test'), u'Content Experiment')
|
||||
self.assertEqual(xblock_type_display_name(split_test), 'Content Experiment')
|
||||
self.assertEqual(xblock_type_display_name('split_test'), 'Content Experiment')
|
||||
|
||||
@@ -10,19 +10,18 @@ import re
|
||||
import shutil
|
||||
import tarfile
|
||||
import tempfile
|
||||
from io import BytesIO
|
||||
from unittest.mock import Mock, patch
|
||||
from uuid import uuid4
|
||||
|
||||
import ddt
|
||||
import lxml
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
from django.test.utils import override_settings
|
||||
from milestones.tests.utils import MilestonesTestCaseMixin
|
||||
from mock import Mock, patch
|
||||
from opaque_keys.edx.locator import LibraryLocator
|
||||
from path import Path as path
|
||||
from six.moves import zip
|
||||
from storages.backends.s3boto import S3BotoStorage
|
||||
from storages.backends.s3boto3 import S3Boto3Storage
|
||||
from user_tasks.models import UserTaskStatus
|
||||
@@ -31,10 +30,10 @@ from cms.djangoapps.contentstore.tests.test_libraries import LibraryTestCase
|
||||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
|
||||
from cms.djangoapps.contentstore.utils import reverse_course_url
|
||||
from cms.djangoapps.models.settings.course_metadata import CourseMetadata
|
||||
from openedx.core.lib.extract_tar import safetar_extractall
|
||||
from common.djangoapps.student import auth
|
||||
from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRole
|
||||
from common.djangoapps.util import milestones_helpers
|
||||
from openedx.core.lib.extract_tar import safetar_extractall
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.modulestore import LIBRARY_ROOT, ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
@@ -57,7 +56,7 @@ class ImportEntranceExamTestCase(CourseTestCase, MilestonesTestCaseMixin):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(ImportEntranceExamTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.url = reverse_course_url('import_handler', self.course.id)
|
||||
self.content_dir = path(tempfile.mkdtemp())
|
||||
self.addCleanup(shutil.rmtree, self.content_dir)
|
||||
@@ -107,7 +106,7 @@ class ImportEntranceExamTestCase(CourseTestCase, MilestonesTestCaseMixin):
|
||||
"""
|
||||
Check that pre existed entrance exam content should be overwrite with the imported course.
|
||||
"""
|
||||
exam_url = '/course/{}/entrance_exam/'.format(six.text_type(self.course.id))
|
||||
exam_url = '/course/{}/entrance_exam/'.format(str(self.course.id))
|
||||
resp = self.client.post(exam_url, {'entrance_exam_minimum_score_pct': 0.5}, http_accept='application/json')
|
||||
self.assertEqual(resp.status_code, 201)
|
||||
|
||||
@@ -117,9 +116,9 @@ class ImportEntranceExamTestCase(CourseTestCase, MilestonesTestCaseMixin):
|
||||
self.assertTrue(metadata['entrance_exam_enabled'])
|
||||
self.assertIsNotNone(metadata['entrance_exam_minimum_score_pct'])
|
||||
self.assertEqual(metadata['entrance_exam_minimum_score_pct']['value'], 0.5)
|
||||
self.assertTrue(len(milestones_helpers.get_course_milestones(six.text_type(self.course.id))))
|
||||
self.assertTrue(len(milestones_helpers.get_course_milestones(str(self.course.id))))
|
||||
content_milestones = milestones_helpers.get_course_content_milestones(
|
||||
six.text_type(self.course.id),
|
||||
str(self.course.id),
|
||||
metadata['entrance_exam_id']['value'],
|
||||
milestones_helpers.get_milestone_relationship_types()['FULFILLS']
|
||||
)
|
||||
@@ -145,7 +144,7 @@ class ImportTestCase(CourseTestCase):
|
||||
CREATE_USER = True
|
||||
|
||||
def setUp(self):
|
||||
super(ImportTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.url = reverse_course_url('import_handler', self.course.id)
|
||||
self.content_dir = path(tempfile.mkdtemp())
|
||||
self.addCleanup(shutil.rmtree, self.content_dir)
|
||||
@@ -538,7 +537,7 @@ class ExportTestCase(CourseTestCase):
|
||||
"""
|
||||
Sets up the test course.
|
||||
"""
|
||||
super(ExportTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.url = reverse_course_url('export_handler', self.course.id)
|
||||
self.status_url = reverse_course_url('export_status_handler', self.course.id)
|
||||
|
||||
@@ -577,7 +576,7 @@ class ExportTestCase(CourseTestCase):
|
||||
for item in resp.streaming_content:
|
||||
resp_content += item
|
||||
|
||||
buff = six.BytesIO(resp_content)
|
||||
buff = BytesIO(resp_content)
|
||||
return tarfile.open(fileobj=buff)
|
||||
|
||||
def _verify_export_succeeded(self, resp):
|
||||
@@ -854,7 +853,7 @@ class TestLibraryImportExport(CourseTestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestLibraryImportExport, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.export_dir = tempfile.mkdtemp()
|
||||
self.addCleanup(shutil.rmtree, self.export_dir, ignore_errors=True)
|
||||
|
||||
@@ -912,7 +911,7 @@ class TestCourseExportImport(LibraryTestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestCourseExportImport, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.export_dir = tempfile.mkdtemp()
|
||||
|
||||
# Create a problem in library
|
||||
@@ -1035,7 +1034,7 @@ class TestCourseExportImportProblem(CourseTestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestCourseExportImportProblem, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.export_dir = tempfile.mkdtemp()
|
||||
self.source_course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split)
|
||||
self.addCleanup(shutil.rmtree, self.export_dir, ignore_errors=True)
|
||||
|
||||
@@ -4,24 +4,21 @@
|
||||
import json
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import Mock, PropertyMock, patch
|
||||
|
||||
import ddt
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.http import Http404
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.urls import reverse
|
||||
from edx_proctoring.exceptions import ProctoredExamNotFoundException
|
||||
from mock import Mock, PropertyMock, patch
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.asides import AsideUsageKeyV2
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
|
||||
from pyquery import PyQuery
|
||||
from pytz import UTC
|
||||
from six import text_type
|
||||
from six.moves import range
|
||||
from web_fragments.fragment import Fragment
|
||||
from webob import Response
|
||||
from xblock.core import XBlockAside
|
||||
@@ -34,12 +31,14 @@ from xblock.validation import ValidationMessage
|
||||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
|
||||
from cms.djangoapps.contentstore.utils import reverse_course_url, reverse_usage_url
|
||||
from cms.djangoapps.contentstore.views import item as item_module
|
||||
from lms.djangoapps.lms_xblock.mixin import NONSENSICAL_ACCESS_RESTRICTION
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from common.djangoapps.xblock_django.models import (
|
||||
XBlockConfiguration, XBlockStudioConfiguration, XBlockStudioConfigurationFlag
|
||||
XBlockConfiguration,
|
||||
XBlockStudioConfiguration,
|
||||
XBlockStudioConfigurationFlag
|
||||
)
|
||||
from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService
|
||||
from lms.djangoapps.lms_xblock.mixin import NONSENSICAL_ACCESS_RESTRICTION
|
||||
from xmodule.capa_module import ProblemBlock
|
||||
from xmodule.course_module import DEFAULT_START_DATE
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
@@ -72,7 +71,7 @@ class AsideTest(XBlockAside):
|
||||
"""
|
||||
Test xblock aside class
|
||||
"""
|
||||
FRAG_CONTENT = u"<p>Aside Foo rendered</p>"
|
||||
FRAG_CONTENT = "<p>Aside Foo rendered</p>"
|
||||
|
||||
field11 = String(default="aside1_default_value1", scope=Scope.content)
|
||||
field12 = String(default="aside1_default_value2", scope=Scope.settings)
|
||||
@@ -87,7 +86,7 @@ class AsideTest(XBlockAside):
|
||||
class ItemTest(CourseTestCase):
|
||||
""" Base test class for create, save, and delete """
|
||||
def setUp(self):
|
||||
super(ItemTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
self.course_key = self.course.id
|
||||
self.usage_key = self.course.location
|
||||
@@ -115,9 +114,9 @@ class ItemTest(CourseTestCase):
|
||||
|
||||
def create_xblock(self, parent_usage_key=None, display_name=None, category=None, boilerplate=None): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
data = {
|
||||
'parent_locator': six.text_type(
|
||||
'parent_locator': str(
|
||||
self.usage_key
|
||||
)if parent_usage_key is None else six.text_type(parent_usage_key),
|
||||
)if parent_usage_key is None else str(parent_usage_key),
|
||||
'category': category
|
||||
}
|
||||
if display_name is not None:
|
||||
@@ -260,8 +259,8 @@ class GetItemTest(ItemTest):
|
||||
html,
|
||||
# The instance of the wrapper class will have an auto-generated ID. Allow any
|
||||
# characters after wrapper.
|
||||
u'"/container/{}" class="action-button">\\s*<span class="action-button-text">View</span>'.format(
|
||||
re.escape(six.text_type(wrapper_usage_key))
|
||||
'"/container/{}" class="action-button">\\s*<span class="action-button-text">View</span>'.format(
|
||||
re.escape(str(wrapper_usage_key))
|
||||
)
|
||||
)
|
||||
|
||||
@@ -304,14 +303,14 @@ class GetItemTest(ItemTest):
|
||||
|
||||
# Rename groups in group configuration
|
||||
GROUP_CONFIGURATION_JSON = {
|
||||
u'id': 0,
|
||||
u'name': u'first_partition',
|
||||
u'scheme': u'random',
|
||||
u'description': u'First Partition',
|
||||
u'version': UserPartition.VERSION,
|
||||
u'groups': [
|
||||
{u'id': 0, u'name': u'New_NAME_A', u'version': 1},
|
||||
{u'id': 1, u'name': u'New_NAME_B', u'version': 1},
|
||||
'id': 0,
|
||||
'name': 'first_partition',
|
||||
'scheme': 'random',
|
||||
'description': 'First Partition',
|
||||
'version': UserPartition.VERSION,
|
||||
'groups': [
|
||||
{'id': 0, 'name': 'New_NAME_A', 'version': 1},
|
||||
{'id': 1, 'name': 'New_NAME_B', 'version': 1},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -458,13 +457,13 @@ class GetItemTest(ItemTest):
|
||||
xblock (XBlock): An XBlock item.
|
||||
xblock_info (dict): A dict containing xblock information.
|
||||
"""
|
||||
self.assertEqual(six.text_type(xblock.location), xblock_info['id'])
|
||||
self.assertEqual(str(xblock.location), xblock_info['id'])
|
||||
self.assertEqual(xblock.display_name, xblock_info['display_name'])
|
||||
self.assertEqual(xblock.category, xblock_info['category'])
|
||||
|
||||
for usage_key in (problem_usage_key, vert_usage_key, seq_usage_key, chapter_usage_key):
|
||||
xblock = self.get_item_from_modulestore(usage_key)
|
||||
url = reverse_usage_url('xblock_handler', usage_key) + '?fields={field_type}'.format(field_type=field_type)
|
||||
url = reverse_usage_url('xblock_handler', usage_key) + f'?fields={field_type}'
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = json.loads(response.content.decode('utf-8'))
|
||||
@@ -567,7 +566,7 @@ class TestCreateItem(ItemTest):
|
||||
self.assertEqual(new_tab.display_name, 'Empty')
|
||||
|
||||
|
||||
class DuplicateHelper(object):
|
||||
class DuplicateHelper:
|
||||
"""
|
||||
Helper mixin class for TestDuplicateItem and TestDuplicateItemWithAsides
|
||||
"""
|
||||
@@ -602,21 +601,21 @@ class DuplicateHelper(object):
|
||||
self.assertEqual(duplicated_asides[0].field13, 'aside1_default_value3')
|
||||
|
||||
self.assertNotEqual(
|
||||
six.text_type(original_item.location),
|
||||
six.text_type(duplicated_item.location),
|
||||
str(original_item.location),
|
||||
str(duplicated_item.location),
|
||||
"Location of duplicate should be different from original"
|
||||
)
|
||||
|
||||
# Parent will only be equal for root of duplicated structure, in the case
|
||||
# where an item is duplicated in-place.
|
||||
if parent_usage_key and six.text_type(original_item.parent) == six.text_type(parent_usage_key):
|
||||
if parent_usage_key and str(original_item.parent) == str(parent_usage_key):
|
||||
self.assertEqual(
|
||||
six.text_type(parent_usage_key), six.text_type(duplicated_item.parent),
|
||||
str(parent_usage_key), str(duplicated_item.parent),
|
||||
"Parent of duplicate should equal parent of source for root xblock when duplicated in-place"
|
||||
)
|
||||
else:
|
||||
self.assertNotEqual(
|
||||
six.text_type(original_item.parent), six.text_type(duplicated_item.parent),
|
||||
str(original_item.parent), str(duplicated_item.parent),
|
||||
"Parent duplicate should be different from source"
|
||||
)
|
||||
|
||||
@@ -648,10 +647,10 @@ class DuplicateHelper(object):
|
||||
return duplicated_item.display_name == original_item.category
|
||||
return duplicated_item.display_name == original_item.display_name
|
||||
if original_item.display_name is not None:
|
||||
return duplicated_item.display_name == u"Duplicate of '{display_name}'".format(
|
||||
return duplicated_item.display_name == "Duplicate of '{display_name}'".format(
|
||||
display_name=original_item.display_name
|
||||
)
|
||||
return duplicated_item.display_name == u"Duplicate of {display_name}".format(
|
||||
return duplicated_item.display_name == "Duplicate of {display_name}".format(
|
||||
display_name=original_item.category
|
||||
)
|
||||
|
||||
@@ -661,8 +660,8 @@ class DuplicateHelper(object):
|
||||
"""
|
||||
# pylint: disable=no-member
|
||||
data = {
|
||||
'parent_locator': six.text_type(parent_usage_key),
|
||||
'duplicate_source_locator': six.text_type(source_usage_key)
|
||||
'parent_locator': str(parent_usage_key),
|
||||
'duplicate_source_locator': str(source_usage_key)
|
||||
}
|
||||
if display_name is not None:
|
||||
data['display_name'] = display_name
|
||||
@@ -678,7 +677,7 @@ class TestDuplicateItem(ItemTest, DuplicateHelper):
|
||||
|
||||
def setUp(self):
|
||||
""" Creates the test course structure and a few components to 'duplicate'. """
|
||||
super(TestDuplicateItem, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
# Create a parent chapter (for testing children of children).
|
||||
resp = self.create_xblock(parent_usage_key=self.usage_key, category='chapter')
|
||||
self.chapter_usage_key = self.response_usage_key(resp)
|
||||
@@ -787,7 +786,7 @@ class TestMoveItem(ItemTest):
|
||||
"""
|
||||
Creates the test course structure to build course outline tree.
|
||||
"""
|
||||
super(TestMoveItem, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.setup_course()
|
||||
|
||||
def setup_course(self, default_store=None):
|
||||
@@ -873,8 +872,8 @@ class TestMoveItem(ItemTest):
|
||||
resp (JsonResponse): Response after the move operation is complete.
|
||||
"""
|
||||
data = {
|
||||
'move_source_locator': six.text_type(source_usage_key),
|
||||
'parent_locator': six.text_type(target_usage_key)
|
||||
'move_source_locator': str(source_usage_key),
|
||||
'parent_locator': str(target_usage_key)
|
||||
}
|
||||
if target_index is not None:
|
||||
data['target_index'] = target_index
|
||||
@@ -901,8 +900,8 @@ class TestMoveItem(ItemTest):
|
||||
response = self._move_component(source_usage_key, target_usage_key, target_index)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(response['move_source_locator'], six.text_type(source_usage_key))
|
||||
self.assertEqual(response['parent_locator'], six.text_type(target_usage_key))
|
||||
self.assertEqual(response['move_source_locator'], str(source_usage_key))
|
||||
self.assertEqual(response['parent_locator'], str(target_usage_key))
|
||||
self.assertEqual(response['source_index'], expected_index)
|
||||
|
||||
# Verify parent referance has been changed now.
|
||||
@@ -989,7 +988,7 @@ class TestMoveItem(ItemTest):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response = json.loads(response.content.decode('utf-8'))
|
||||
|
||||
expected_error = u'You can not move {usage_key} at an invalid index ({target_index}).'.format(
|
||||
expected_error = 'You can not move {usage_key} at an invalid index ({target_index}).'.format(
|
||||
usage_key=self.html_usage_key,
|
||||
target_index=parent_children_length + 10
|
||||
)
|
||||
@@ -1006,7 +1005,7 @@ class TestMoveItem(ItemTest):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response = json.loads(response.content.decode('utf-8'))
|
||||
|
||||
expected_error = u'You can not move {source_type} into {target_type}.'.format(
|
||||
expected_error = 'You can not move {source_type} into {target_type}.'.format(
|
||||
source_type=self.html_usage_key.block_type,
|
||||
target_type=self.seq_usage_key.block_type
|
||||
)
|
||||
@@ -1151,7 +1150,7 @@ class TestMoveItem(ItemTest):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response = json.loads(response.content.decode('utf-8'))
|
||||
|
||||
error = u'You must provide target_index ({target_index}) as an integer.'.format(target_index=target_index)
|
||||
error = f'You must provide target_index ({target_index}) as an integer.'
|
||||
self.assertEqual(response['error'], error)
|
||||
new_parent_loc = self.store.get_parent_location(self.html_usage_key)
|
||||
self.assertEqual(new_parent_loc, parent_loc)
|
||||
@@ -1160,7 +1159,7 @@ class TestMoveItem(ItemTest):
|
||||
"""
|
||||
Test move an item without specifying the target location.
|
||||
"""
|
||||
data = {'move_source_locator': six.text_type(self.html_usage_key)}
|
||||
data = {'move_source_locator': str(self.html_usage_key)}
|
||||
with self.assertRaises(InvalidKeyError):
|
||||
self.client.patch(
|
||||
reverse('xblock_handler'),
|
||||
@@ -1248,10 +1247,10 @@ class TestMoveItem(ItemTest):
|
||||
insert_at = 0
|
||||
self.assert_move_item(self.html_usage_key, self.vert2_usage_key, insert_at)
|
||||
mock_logger.info.assert_called_with(
|
||||
u'MOVE: %s moved from %s to %s at %d index',
|
||||
six.text_type(self.html_usage_key),
|
||||
six.text_type(self.vert_usage_key),
|
||||
six.text_type(self.vert2_usage_key),
|
||||
'MOVE: %s moved from %s to %s at %d index',
|
||||
str(self.html_usage_key),
|
||||
str(self.vert_usage_key),
|
||||
str(self.vert2_usage_key),
|
||||
insert_at
|
||||
)
|
||||
|
||||
@@ -1319,8 +1318,8 @@ class TestMoveItem(ItemTest):
|
||||
self.setup_course(default_store=store_type)
|
||||
|
||||
data = {
|
||||
'move_source_locator': six.text_type(self.usage_key.course_key.make_usage_key('html', 'html_test')),
|
||||
'parent_locator': six.text_type(self.vert2_usage_key)
|
||||
'move_source_locator': str(self.usage_key.course_key.make_usage_key('html', 'html_test')),
|
||||
'parent_locator': str(self.vert2_usage_key)
|
||||
}
|
||||
with self.assertRaises(ItemNotFoundError):
|
||||
self.client.patch(
|
||||
@@ -1339,7 +1338,7 @@ class TestDuplicateItemWithAsides(ItemTest, DuplicateHelper):
|
||||
|
||||
def setUp(self):
|
||||
""" Creates the test course structure and a few components to 'duplicate'. """
|
||||
super(TestDuplicateItemWithAsides, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
# Create a parent chapter
|
||||
resp = self.create_xblock(parent_usage_key=self.usage_key, category='chapter')
|
||||
self.chapter_usage_key = self.response_usage_key(resp)
|
||||
@@ -1400,7 +1399,7 @@ class TestEditItemSetup(ItemTest):
|
||||
|
||||
def setUp(self):
|
||||
""" Creates the test course structure and a couple problems to 'edit'. """
|
||||
super(TestEditItemSetup, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
# create a chapter
|
||||
display_name = 'chapter created'
|
||||
resp = self.create_xblock(display_name=display_name, category='chapter')
|
||||
@@ -1503,7 +1502,7 @@ class TestEditItem(TestEditItemSetup):
|
||||
user=self.user
|
||||
)
|
||||
# Both display and actual value should be None
|
||||
self.assertEqual(xblock_info['due_date'], u'')
|
||||
self.assertEqual(xblock_info['due_date'], '')
|
||||
self.assertIsNone(xblock_info['due'])
|
||||
|
||||
def test_update_generic_fields(self):
|
||||
@@ -1567,9 +1566,9 @@ class TestEditItem(TestEditItemSetup):
|
||||
self.seq_update_url,
|
||||
data={
|
||||
'children': [
|
||||
six.text_type(self.problem_usage_key),
|
||||
six.text_type(unit2_usage_key),
|
||||
six.text_type(unit1_usage_key)
|
||||
str(self.problem_usage_key),
|
||||
str(unit2_usage_key),
|
||||
str(unit1_usage_key)
|
||||
]
|
||||
}
|
||||
)
|
||||
@@ -1594,7 +1593,7 @@ class TestEditItem(TestEditItemSetup):
|
||||
# move unit 1 from sequential1 to sequential2
|
||||
resp = self.client.ajax_post(
|
||||
self.seq2_update_url,
|
||||
data={'children': [six.text_type(unit_1_key), six.text_type(unit_2_key)]}
|
||||
data={'children': [str(unit_1_key), str(unit_2_key)]}
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
@@ -1617,7 +1616,7 @@ class TestEditItem(TestEditItemSetup):
|
||||
# adding orphaned unit 1 should return an error
|
||||
resp = self.client.ajax_post(
|
||||
self.seq2_update_url,
|
||||
data={'children': [six.text_type(unit_1_key)]}
|
||||
data={'children': [str(unit_1_key)]}
|
||||
)
|
||||
self.assertContains(resp, "Invalid data, possibly caused by concurrent authors", status_code=400)
|
||||
|
||||
@@ -1641,7 +1640,7 @@ class TestEditItem(TestEditItemSetup):
|
||||
# remove unit 2 should return an error
|
||||
resp = self.client.ajax_post(
|
||||
self.seq2_update_url,
|
||||
data={'children': [six.text_type(unit_1_key)]}
|
||||
data={'children': [str(unit_1_key)]}
|
||||
)
|
||||
self.assertContains(resp, "Invalid data, possibly caused by concurrent authors", status_code=400)
|
||||
|
||||
@@ -1808,7 +1807,7 @@ class TestEditItem(TestEditItemSetup):
|
||||
self.client.ajax_post(
|
||||
self.problem_update_url,
|
||||
data={
|
||||
'id': six.text_type(self.problem_usage_key),
|
||||
'id': str(self.problem_usage_key),
|
||||
'metadata': {},
|
||||
'data': "<p>Problem content draft.</p>"
|
||||
}
|
||||
@@ -1863,7 +1862,7 @@ class TestEditItem(TestEditItemSetup):
|
||||
resp = self.client.ajax_post(
|
||||
unit_update_url,
|
||||
data={
|
||||
'id': six.text_type(unit_usage_key),
|
||||
'id': str(unit_usage_key),
|
||||
'metadata': {},
|
||||
}
|
||||
)
|
||||
@@ -1883,7 +1882,7 @@ class TestEditItem(TestEditItemSetup):
|
||||
response = self.client.ajax_post(
|
||||
update_url,
|
||||
data={
|
||||
'id': six.text_type(video_usage_key),
|
||||
'id': str(video_usage_key),
|
||||
'metadata': {
|
||||
'saved_video_position': "Not a valid relative time",
|
||||
},
|
||||
@@ -1913,7 +1912,7 @@ class TestEditItemSplitMongo(TestEditItemSetup):
|
||||
resp = self.client.get(view_url, HTTP_ACCEPT='application/json')
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
content = json.loads(resp.content.decode('utf-8'))
|
||||
self.assertEqual(len(PyQuery(content['html'])('.xblock-{}'.format(STUDIO_VIEW))), 1)
|
||||
self.assertEqual(len(PyQuery(content['html'])(f'.xblock-{STUDIO_VIEW}')), 1)
|
||||
|
||||
|
||||
class TestEditSplitModule(ItemTest):
|
||||
@@ -1922,11 +1921,11 @@ class TestEditSplitModule(ItemTest):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestEditSplitModule, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.user = UserFactory()
|
||||
|
||||
self.first_user_partition_group_1 = Group(six.text_type(MINIMUM_STATIC_PARTITION_ID + 1), 'alpha')
|
||||
self.first_user_partition_group_2 = Group(six.text_type(MINIMUM_STATIC_PARTITION_ID + 2), 'beta')
|
||||
self.first_user_partition_group_1 = Group(str(MINIMUM_STATIC_PARTITION_ID + 1), 'alpha')
|
||||
self.first_user_partition_group_2 = Group(str(MINIMUM_STATIC_PARTITION_ID + 2), 'beta')
|
||||
self.first_user_partition = UserPartition(
|
||||
MINIMUM_STATIC_PARTITION_ID, 'first_partition', 'First Partition',
|
||||
[self.first_user_partition_group_1, self.first_user_partition_group_2]
|
||||
@@ -1934,9 +1933,9 @@ class TestEditSplitModule(ItemTest):
|
||||
|
||||
# There is a test point below (test_create_groups) that purposefully wants the group IDs
|
||||
# of the 2 partitions to overlap (which is not something that normally happens).
|
||||
self.second_user_partition_group_1 = Group(six.text_type(MINIMUM_STATIC_PARTITION_ID + 1), 'Group 1')
|
||||
self.second_user_partition_group_2 = Group(six.text_type(MINIMUM_STATIC_PARTITION_ID + 2), 'Group 2')
|
||||
self.second_user_partition_group_3 = Group(six.text_type(MINIMUM_STATIC_PARTITION_ID + 3), 'Group 3')
|
||||
self.second_user_partition_group_1 = Group(str(MINIMUM_STATIC_PARTITION_ID + 1), 'Group 1')
|
||||
self.second_user_partition_group_2 = Group(str(MINIMUM_STATIC_PARTITION_ID + 2), 'Group 2')
|
||||
self.second_user_partition_group_3 = Group(str(MINIMUM_STATIC_PARTITION_ID + 3), 'Group 3')
|
||||
self.second_user_partition = UserPartition(
|
||||
MINIMUM_STATIC_PARTITION_ID + 10, 'second_partition', 'Second Partition',
|
||||
[
|
||||
@@ -2004,8 +2003,8 @@ class TestEditSplitModule(ItemTest):
|
||||
vertical_1 = self.get_item_from_modulestore(split_test.children[1], verify_is_draft=True)
|
||||
self.assertEqual("vertical", vertical_0.category)
|
||||
self.assertEqual("vertical", vertical_1.category)
|
||||
self.assertEqual("Group ID " + six.text_type(MINIMUM_STATIC_PARTITION_ID + 1), vertical_0.display_name)
|
||||
self.assertEqual("Group ID " + six.text_type(MINIMUM_STATIC_PARTITION_ID + 2), vertical_1.display_name)
|
||||
self.assertEqual("Group ID " + str(MINIMUM_STATIC_PARTITION_ID + 1), vertical_0.display_name)
|
||||
self.assertEqual("Group ID " + str(MINIMUM_STATIC_PARTITION_ID + 2), vertical_1.display_name)
|
||||
|
||||
# Verify that the group_id_to_child mapping is correct.
|
||||
self.assertEqual(2, len(split_test.group_id_to_child))
|
||||
@@ -2145,7 +2144,7 @@ class TestComponentHandler(TestCase):
|
||||
"""Tests for component handler api"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestComponentHandler, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
self.request_factory = RequestFactory()
|
||||
|
||||
@@ -2161,7 +2160,7 @@ class TestComponentHandler(TestCase):
|
||||
self.usage_key = BlockUsageLocator(
|
||||
CourseLocator('dummy_org', 'dummy_course', 'dummy_run'), 'dummy_category', 'dummy_name'
|
||||
)
|
||||
self.usage_key_string = text_type(self.usage_key)
|
||||
self.usage_key_string = str(self.usage_key)
|
||||
|
||||
self.user = UserFactory()
|
||||
|
||||
@@ -2213,7 +2212,7 @@ class TestComponentHandler(TestCase):
|
||||
def get_usage_key():
|
||||
"""return usage key"""
|
||||
return (
|
||||
text_type(AsideUsageKeyV2(self.usage_key, "aside"))
|
||||
str(AsideUsageKeyV2(self.usage_key, "aside"))
|
||||
if is_xblock_aside
|
||||
else self.usage_key_string
|
||||
)
|
||||
@@ -2244,7 +2243,7 @@ class TestComponentTemplates(CourseTestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestComponentTemplates, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
# Advanced Module support levels.
|
||||
XBlockStudioConfiguration.objects.create(name='poll', enabled=True, support_level="fs")
|
||||
XBlockStudioConfiguration.objects.create(name='survey', enabled=True, support_level="ps")
|
||||
@@ -2319,9 +2318,9 @@ class TestComponentTemplates(CourseTestCase):
|
||||
{
|
||||
'boilerplate_name': None,
|
||||
'category': 'drag-and-drop-v2',
|
||||
'display_name': u'Drag and Drop',
|
||||
'display_name': 'Drag and Drop',
|
||||
'hinted': False,
|
||||
'support_level': u'fs',
|
||||
'support_level': 'fs',
|
||||
'tab': 'advanced'
|
||||
}
|
||||
]
|
||||
@@ -2331,7 +2330,7 @@ class TestComponentTemplates(CourseTestCase):
|
||||
self.templates = get_component_templates(self.course)
|
||||
self._verify_basic_component("video", "Video", "us")
|
||||
problem_templates = self.get_templates_of_type('problem')
|
||||
problem_no_boilerplate = self.get_template(problem_templates, u'Blank Advanced Problem')
|
||||
problem_no_boilerplate = self.get_template(problem_templates, 'Blank Advanced Problem')
|
||||
self.assertIsNotNone(problem_no_boilerplate)
|
||||
self.assertEqual('us', problem_no_boilerplate['support_level'])
|
||||
|
||||
@@ -2350,7 +2349,7 @@ class TestComponentTemplates(CourseTestCase):
|
||||
self.assertEqual(len(advanced_templates), 1)
|
||||
world_cloud_template = advanced_templates[0]
|
||||
self.assertEqual(world_cloud_template.get('category'), 'word_cloud')
|
||||
self.assertEqual(world_cloud_template.get('display_name'), u'Word cloud')
|
||||
self.assertEqual(world_cloud_template.get('display_name'), 'Word cloud')
|
||||
self.assertIsNone(world_cloud_template.get('boilerplate_name', None))
|
||||
|
||||
# Verify that non-advanced components are not added twice
|
||||
@@ -2373,7 +2372,7 @@ class TestComponentTemplates(CourseTestCase):
|
||||
Test the handling of advanced problem templates.
|
||||
"""
|
||||
problem_templates = self.get_templates_of_type('problem')
|
||||
circuit_template = self.get_template(problem_templates, u'Circuit Schematic Builder')
|
||||
circuit_template = self.get_template(problem_templates, 'Circuit Schematic Builder')
|
||||
self.assertIsNotNone(circuit_template)
|
||||
self.assertEqual(circuit_template.get('category'), 'problem')
|
||||
self.assertEqual(circuit_template.get('boilerplate_name'), 'circuitschematic.yaml')
|
||||
@@ -2506,7 +2505,7 @@ class TestXBlockInfo(ItemTest):
|
||||
Unit tests for XBlock's outline handling.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestXBlockInfo, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
user_id = self.user.id
|
||||
self.chapter = ItemFactory.create(
|
||||
parent_location=self.course.location, category='chapter', display_name="Week 1", user_id=user_id,
|
||||
@@ -2702,7 +2701,7 @@ class TestXBlockInfo(ItemTest):
|
||||
Validate that the xblock info is correct for the test course.
|
||||
"""
|
||||
self.assertEqual(xblock_info['category'], 'course')
|
||||
self.assertEqual(xblock_info['id'], six.text_type(self.course.location))
|
||||
self.assertEqual(xblock_info['id'], str(self.course.location))
|
||||
self.assertEqual(xblock_info['display_name'], self.course.display_name)
|
||||
self.assertTrue(xblock_info['published'])
|
||||
self.assertFalse(xblock_info['highlights_enabled_for_messaging'])
|
||||
@@ -2715,7 +2714,7 @@ class TestXBlockInfo(ItemTest):
|
||||
Validate that the xblock info is correct for the test chapter.
|
||||
"""
|
||||
self.assertEqual(xblock_info['category'], 'chapter')
|
||||
self.assertEqual(xblock_info['id'], six.text_type(self.chapter.location))
|
||||
self.assertEqual(xblock_info['id'], str(self.chapter.location))
|
||||
self.assertEqual(xblock_info['display_name'], 'Week 1')
|
||||
self.assertTrue(xblock_info['published'])
|
||||
self.assertIsNone(xblock_info.get('edited_by', None))
|
||||
@@ -2735,7 +2734,7 @@ class TestXBlockInfo(ItemTest):
|
||||
Validate that the xblock info is correct for the test sequential.
|
||||
"""
|
||||
self.assertEqual(xblock_info['category'], 'sequential')
|
||||
self.assertEqual(xblock_info['id'], six.text_type(self.sequential.location))
|
||||
self.assertEqual(xblock_info['id'], str(self.sequential.location))
|
||||
self.assertEqual(xblock_info['display_name'], 'Lesson 1')
|
||||
self.assertTrue(xblock_info['published'])
|
||||
self.assertIsNone(xblock_info.get('edited_by', None))
|
||||
@@ -2748,7 +2747,7 @@ class TestXBlockInfo(ItemTest):
|
||||
Validate that the xblock info is correct for the test vertical.
|
||||
"""
|
||||
self.assertEqual(xblock_info['category'], 'vertical')
|
||||
self.assertEqual(xblock_info['id'], six.text_type(self.vertical.location))
|
||||
self.assertEqual(xblock_info['id'], str(self.vertical.location))
|
||||
self.assertEqual(xblock_info['display_name'], 'Unit 1')
|
||||
self.assertTrue(xblock_info['published'])
|
||||
self.assertEqual(xblock_info['edited_by'], 'testuser')
|
||||
@@ -2770,7 +2769,7 @@ class TestXBlockInfo(ItemTest):
|
||||
Validate that the xblock info is correct for the test component.
|
||||
"""
|
||||
self.assertEqual(xblock_info['category'], 'video')
|
||||
self.assertEqual(xblock_info['id'], six.text_type(self.video.location))
|
||||
self.assertEqual(xblock_info['id'], str(self.video.location))
|
||||
self.assertEqual(xblock_info['display_name'], 'My Video')
|
||||
self.assertTrue(xblock_info['published'])
|
||||
self.assertIsNone(xblock_info.get('edited_by', None))
|
||||
@@ -2947,7 +2946,7 @@ class TestLibraryXBlockInfo(ModuleStoreTestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestLibraryXBlockInfo, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
user_id = self.user.id
|
||||
self.library = LibraryFactory.create()
|
||||
self.top_level_html = ItemFactory.create(
|
||||
@@ -2975,7 +2974,7 @@ class TestLibraryXBlockInfo(ModuleStoreTestCase):
|
||||
ancestors = xblock_info['ancestor_info']['ancestors']
|
||||
self.assertEqual(len(ancestors), 2)
|
||||
self.assertEqual(ancestors[0]['category'], 'vertical')
|
||||
self.assertEqual(ancestors[0]['id'], six.text_type(self.vertical.location))
|
||||
self.assertEqual(ancestors[0]['id'], str(self.vertical.location))
|
||||
self.assertEqual(ancestors[1]['category'], 'library')
|
||||
|
||||
def validate_component_xblock_info(self, xblock_info, original_block):
|
||||
@@ -2983,7 +2982,7 @@ class TestLibraryXBlockInfo(ModuleStoreTestCase):
|
||||
Validate that the xblock info is correct for the test component.
|
||||
"""
|
||||
self.assertEqual(xblock_info['category'], original_block.category)
|
||||
self.assertEqual(xblock_info['id'], six.text_type(original_block.location))
|
||||
self.assertEqual(xblock_info['id'], str(original_block.location))
|
||||
self.assertEqual(xblock_info['display_name'], original_block.display_name)
|
||||
self.assertIsNone(xblock_info.get('has_changes', None))
|
||||
self.assertIsNone(xblock_info.get('published', None))
|
||||
|
||||
@@ -5,17 +5,16 @@ More important high-level tests are in contentstore/tests/test_libraries.py
|
||||
"""
|
||||
|
||||
|
||||
from unittest import mock
|
||||
from unittest.mock import patch
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from django.conf import settings
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from mock import patch
|
||||
from opaque_keys.edx.locator import CourseKey, LibraryLocator
|
||||
from organizations.api import get_organization_by_short_name
|
||||
from organizations.exceptions import InvalidOrganizationException
|
||||
from opaque_keys.edx.locator import CourseKey, LibraryLocator
|
||||
from six import text_type
|
||||
from six.moves import range
|
||||
|
||||
from cms.djangoapps.contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase, parse_json
|
||||
from cms.djangoapps.contentstore.utils import reverse_course_url, reverse_library_url
|
||||
@@ -32,7 +31,7 @@ LIBRARY_REST_URL = '/library/' # URL for GET/POST requests involving libraries
|
||||
def make_url_for_lib(key):
|
||||
""" Get the RESTful/studio URL for testing the given library """
|
||||
if isinstance(key, LibraryLocator):
|
||||
key = text_type(key)
|
||||
key = str(key)
|
||||
return LIBRARY_REST_URL + key
|
||||
|
||||
|
||||
@@ -44,7 +43,7 @@ class UnitTestLibraries(CourseTestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(UnitTestLibraries, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
self.client = AjaxEnabledTestClient()
|
||||
self.client.login(username=self.user.username, password=self.user_password)
|
||||
@@ -283,11 +282,11 @@ class UnitTestLibraries(CourseTestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
info = parse_json(response)
|
||||
self.assertEqual(info['display_name'], lib.display_name)
|
||||
self.assertEqual(info['library_id'], text_type(lib_key))
|
||||
self.assertEqual(info['library_id'], str(lib_key))
|
||||
self.assertEqual(info['previous_version'], None)
|
||||
self.assertNotEqual(info['version'], None)
|
||||
self.assertNotEqual(info['version'], '')
|
||||
self.assertEqual(info['version'], text_type(version))
|
||||
self.assertEqual(info['version'], str(version))
|
||||
|
||||
def test_get_lib_edit_html(self):
|
||||
"""
|
||||
@@ -368,7 +367,7 @@ class UnitTestLibraries(CourseTestCase):
|
||||
"""
|
||||
library = LibraryFactory.create()
|
||||
extra_user, _ = self.create_non_staff_user()
|
||||
manage_users_url = reverse_library_url('manage_library_users', text_type(library.location.library_key))
|
||||
manage_users_url = reverse_library_url('manage_library_users', str(library.location.library_key))
|
||||
|
||||
response = self.client.get(manage_users_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@@ -13,16 +13,16 @@ from common.djangoapps.student.tests.factories import UserFactory
|
||||
class TestOrganizationListing(TestCase):
|
||||
"""Verify Organization listing behavior."""
|
||||
def setUp(self):
|
||||
super(TestOrganizationListing, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.staff = UserFactory(is_staff=True)
|
||||
self.client.login(username=self.staff.username, password='test')
|
||||
self.org_names_listing_url = reverse('organizations')
|
||||
self.org_short_names = ["alphaX", "betaX", "orgX"]
|
||||
for index, short_name in enumerate(self.org_short_names):
|
||||
add_organization(organization_data={
|
||||
'name': u'Test Organization %s' % index,
|
||||
'name': 'Test Organization %s' % index,
|
||||
'short_name': short_name,
|
||||
'description': u'Testing Organization %s Description' % index,
|
||||
'description': 'Testing Organization %s Description' % index,
|
||||
})
|
||||
|
||||
def test_organization_list(self):
|
||||
|
||||
@@ -4,10 +4,9 @@ Tests for contentstore.views.preview.py
|
||||
|
||||
|
||||
import re
|
||||
from unittest import mock
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
import six
|
||||
from django.test.client import Client, RequestFactory
|
||||
from xblock.core import XBlock, XBlockAside
|
||||
|
||||
@@ -61,11 +60,11 @@ class GetPreviewHtmlTestCase(ModuleStoreTestCase):
|
||||
|
||||
# Verify student view html is returned, and the usage ID is as expected.
|
||||
html_pattern = re.escape(
|
||||
six.text_type(course.id.make_usage_key('html', 'replaceme'))
|
||||
str(course.id.make_usage_key('html', 'replaceme'))
|
||||
).replace('replaceme', r'html_[0-9]*')
|
||||
self.assertRegex(
|
||||
html,
|
||||
'data-usage-id="{}"'.format(html_pattern)
|
||||
f'data-usage-id="{html_pattern}"'
|
||||
)
|
||||
self.assertRegex(html, '<html>foobar</html>')
|
||||
self.assertRegex(html, r"data-block-type=[\"\']test_aside[\"\']")
|
||||
@@ -186,7 +185,7 @@ class StudioXBlockServiceBindingTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Set up the user and request that will be used.
|
||||
"""
|
||||
super(StudioXBlockServiceBindingTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.user = UserFactory()
|
||||
self.course = CourseFactory.create()
|
||||
self.request = mock.Mock()
|
||||
|
||||
@@ -20,7 +20,7 @@ class TabsPageTests(CourseTestCase):
|
||||
"""Common setup for tests"""
|
||||
|
||||
# call super class to setup course, etc.
|
||||
super(TabsPageTests, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
# Set the URL for tests
|
||||
self.url = reverse_course_url('tabs_handler', self.course.id)
|
||||
@@ -178,7 +178,7 @@ class TabsPageTests(CourseTestCase):
|
||||
"""
|
||||
Verify that the static tab renders itself with the correct HTML
|
||||
"""
|
||||
preview_url = '/xblock/{}/{}'.format(self.test_tab.location, STUDENT_VIEW)
|
||||
preview_url = f'/xblock/{self.test_tab.location}/{STUDENT_VIEW}'
|
||||
|
||||
resp = self.client.get(preview_url, HTTP_ACCEPT='application/json')
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
@@ -205,7 +205,7 @@ class PrimitiveTabEdit(ModuleStoreTestCase):
|
||||
with self.assertRaises(IndexError):
|
||||
tabs.primitive_delete(course, 7)
|
||||
tabs.primitive_delete(course, 2)
|
||||
self.assertNotIn({u'type': u'textbooks'}, course.tabs)
|
||||
self.assertNotIn({'type': 'textbooks'}, course.tabs)
|
||||
# Check that discussion has shifted up
|
||||
self.assertEqual(course.tabs[2], {'type': 'discussion', 'name': 'Discussion'})
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ class TextbookIndexTestCase(CourseTestCase):
|
||||
"Test cases for the textbook index page"
|
||||
def setUp(self):
|
||||
"Set the URL for tests"
|
||||
super(TextbookIndexTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.url = reverse_course_url('textbooks_list_handler', self.course.id)
|
||||
|
||||
def test_view_index(self):
|
||||
@@ -113,7 +113,7 @@ class TextbookCreateTestCase(CourseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
"Set up a url and some textbook content for tests"
|
||||
super(TextbookCreateTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.url = reverse_course_url('textbooks_list_handler', self.course.id)
|
||||
|
||||
self.textbook = {
|
||||
@@ -173,7 +173,7 @@ class TextbookDetailTestCase(CourseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
"Set some useful content and URLs for tests"
|
||||
super(TextbookDetailTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.textbook1 = {
|
||||
"tab_title": "Economics",
|
||||
"id": 1,
|
||||
@@ -295,7 +295,7 @@ class TextbookValidationTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"Set some useful content for tests"
|
||||
super(TextbookValidationTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
self.tb1 = {
|
||||
"tab_title": "Hi, mom!",
|
||||
|
||||
@@ -2,19 +2,18 @@
|
||||
|
||||
|
||||
import json
|
||||
from io import BytesIO # lint-amnesty, pylint: disable=unused-import
|
||||
from io import StringIO
|
||||
from unittest.mock import ANY, Mock, patch
|
||||
|
||||
import ddt
|
||||
import six
|
||||
from django.test.testcases import TestCase
|
||||
from django.urls import reverse
|
||||
from edxval import api
|
||||
from mock import ANY, Mock, patch
|
||||
|
||||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase
|
||||
from cms.djangoapps.contentstore.utils import reverse_course_url
|
||||
from openedx.core.djangoapps.profile_images.tests.helpers import make_image_file
|
||||
from common.djangoapps.student.roles import CourseStaffRole
|
||||
from openedx.core.djangoapps.profile_images.tests.helpers import make_image_file
|
||||
|
||||
from ..transcript_settings import TranscriptionProviderErrorType, validate_transcript_credentials
|
||||
|
||||
@@ -235,31 +234,31 @@ class TranscriptDownloadTest(CourseTestCase):
|
||||
)
|
||||
|
||||
# Expected response
|
||||
expected_content = u'0\n00:00:00,010 --> 00:00:00,100\nHi, welcome to Edx.\n\n'
|
||||
expected_content = '0\n00:00:00,010 --> 00:00:00,100\nHi, welcome to Edx.\n\n'
|
||||
expected_headers = {
|
||||
'Content-Disposition': 'attachment; filename="edx.srt"',
|
||||
'Content-Language': u'en',
|
||||
'Content-Language': 'en',
|
||||
'Content-Type': 'application/x-subrip; charset=utf-8'
|
||||
}
|
||||
|
||||
# Assert the actual response
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content.decode('utf-8'), expected_content)
|
||||
for attribute, value in six.iteritems(expected_headers):
|
||||
for attribute, value in expected_headers.items():
|
||||
self.assertEqual(response.get(attribute), value)
|
||||
|
||||
@ddt.data(
|
||||
(
|
||||
{},
|
||||
u'The following parameters are required: edx_video_id, language_code.'
|
||||
'The following parameters are required: edx_video_id, language_code.'
|
||||
),
|
||||
(
|
||||
{'edx_video_id': '123'},
|
||||
u'The following parameters are required: language_code.'
|
||||
'The following parameters are required: language_code.'
|
||||
),
|
||||
(
|
||||
{'language_code': 'en'},
|
||||
u'The following parameters are required: edx_video_id.'
|
||||
'The following parameters are required: edx_video_id.'
|
||||
),
|
||||
)
|
||||
@ddt.unpack
|
||||
@@ -311,7 +310,7 @@ class TranscriptUploadTest(CourseTestCase):
|
||||
"""
|
||||
Tests that transcript upload handler works as expected.
|
||||
"""
|
||||
transcript_file_stream = six.StringIO('0\n00:00:00,010 --> 00:00:00,100\nПривіт, edX вітає вас.\n\n')
|
||||
transcript_file_stream = StringIO('0\n00:00:00,010 --> 00:00:00,100\nПривіт, edX вітає вас.\n\n')
|
||||
# Make request to transcript upload handler
|
||||
response = self.client.post(
|
||||
self.view_url,
|
||||
@@ -329,7 +328,7 @@ class TranscriptUploadTest(CourseTestCase):
|
||||
video_id='123',
|
||||
language_code='en',
|
||||
metadata={
|
||||
'language_code': u'es',
|
||||
'language_code': 'es',
|
||||
'file_format': 'sjson',
|
||||
'provider': 'Custom'
|
||||
},
|
||||
@@ -343,28 +342,28 @@ class TranscriptUploadTest(CourseTestCase):
|
||||
'language_code': 'en',
|
||||
'new_language_code': 'en',
|
||||
},
|
||||
u'A transcript file is required.'
|
||||
'A transcript file is required.'
|
||||
),
|
||||
(
|
||||
{
|
||||
'language_code': u'en',
|
||||
'file': u'0\n00:00:00,010 --> 00:00:00,100\nHi, welcome to Edx.\n\n'
|
||||
'language_code': 'en',
|
||||
'file': '0\n00:00:00,010 --> 00:00:00,100\nHi, welcome to Edx.\n\n'
|
||||
},
|
||||
u'The following parameters are required: edx_video_id, new_language_code.'
|
||||
'The following parameters are required: edx_video_id, new_language_code.'
|
||||
),
|
||||
(
|
||||
{
|
||||
'language_code': u'en',
|
||||
'new_language_code': u'en',
|
||||
'file': u'0\n00:00:00,010 --> 00:00:00,100\nHi, welcome to Edx.\n\n'
|
||||
'language_code': 'en',
|
||||
'new_language_code': 'en',
|
||||
'file': '0\n00:00:00,010 --> 00:00:00,100\nHi, welcome to Edx.\n\n'
|
||||
},
|
||||
u'The following parameters are required: edx_video_id.'
|
||||
'The following parameters are required: edx_video_id.'
|
||||
),
|
||||
(
|
||||
{
|
||||
'file': u'0\n00:00:00,010 --> 00:00:00,100\nHi, welcome to Edx.\n\n'
|
||||
'file': '0\n00:00:00,010 --> 00:00:00,100\nHi, welcome to Edx.\n\n'
|
||||
},
|
||||
u'The following parameters are required: edx_video_id, language_code, new_language_code.'
|
||||
'The following parameters are required: edx_video_id, language_code, new_language_code.'
|
||||
)
|
||||
)
|
||||
@ddt.unpack
|
||||
@@ -400,7 +399,7 @@ class TranscriptUploadTest(CourseTestCase):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(
|
||||
json.loads(response.content.decode('utf-8'))['error'],
|
||||
u'A transcript with the "es" language code already exists.'
|
||||
'A transcript with the "es" language code already exists.'
|
||||
)
|
||||
|
||||
@patch(
|
||||
@@ -427,7 +426,7 @@ class TranscriptUploadTest(CourseTestCase):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(
|
||||
json.loads(response.content.decode('utf-8'))['error'],
|
||||
u'There is a problem with this transcript file. Try to upload a different file.'
|
||||
'There is a problem with this transcript file. Try to upload a different file.'
|
||||
)
|
||||
|
||||
@patch(
|
||||
@@ -438,7 +437,7 @@ class TranscriptUploadTest(CourseTestCase):
|
||||
"""
|
||||
Tests the transcript upload handler with an invalid transcript file.
|
||||
"""
|
||||
transcript_file_stream = six.StringIO('An invalid transcript SubRip file content')
|
||||
transcript_file_stream = StringIO('An invalid transcript SubRip file content')
|
||||
# Make request to transcript upload handler
|
||||
response = self.client.post(
|
||||
self.view_url,
|
||||
@@ -454,7 +453,7 @@ class TranscriptUploadTest(CourseTestCase):
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(
|
||||
json.loads(response.content.decode('utf-8'))['error'],
|
||||
u'There is a problem with this transcript file. Try to upload a different file.'
|
||||
'There is a problem with this transcript file. Try to upload a different file.'
|
||||
)
|
||||
|
||||
|
||||
@@ -535,7 +534,7 @@ class TranscriptDeleteTest(CourseTestCase):
|
||||
self.assertEqual(self.user.is_staff, is_staff)
|
||||
self.assertEqual(CourseStaffRole(self.course.id).has_user(self.user), is_course_staff)
|
||||
|
||||
video_id, language_code = u'1234', u'en'
|
||||
video_id, language_code = '1234', 'en'
|
||||
# Create a real transcript in VAL.
|
||||
api.create_or_update_video_transcript(
|
||||
video_id=video_id,
|
||||
|
||||
@@ -6,15 +6,14 @@ import json
|
||||
import tempfile
|
||||
import textwrap
|
||||
from codecs import BOM_UTF8
|
||||
from unittest.mock import Mock, patch
|
||||
from uuid import uuid4
|
||||
|
||||
import ddt
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from edxval.api import create_video
|
||||
from mock import Mock, patch
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
|
||||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase, mock_requests_get
|
||||
@@ -34,7 +33,7 @@ from xmodule.video_module.transcripts_utils import (
|
||||
TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
|
||||
TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex
|
||||
|
||||
SRT_TRANSCRIPT_CONTENT = u"""0
|
||||
SRT_TRANSCRIPT_CONTENT = """0
|
||||
00:00:10,500 --> 00:00:13,000
|
||||
Elephant's Dream
|
||||
|
||||
@@ -58,7 +57,7 @@ class BaseTranscripts(CourseTestCase):
|
||||
def clear_subs_content(self):
|
||||
"""Remove, if transcripts content exists."""
|
||||
for youtube_id in self.get_youtube_ids().values():
|
||||
filename = 'subs_{0}.srt.sjson'.format(youtube_id)
|
||||
filename = f'subs_{youtube_id}.srt.sjson'
|
||||
content_location = StaticContent.compute_location(self.course.id, filename)
|
||||
try:
|
||||
content = contentstore().find(content_location)
|
||||
@@ -72,7 +71,7 @@ class BaseTranscripts(CourseTestCase):
|
||||
"""
|
||||
filedata = json.dumps(subs, indent=2)
|
||||
mime_type = 'application/json'
|
||||
filename = 'subs_{0}.srt.sjson'.format(subs_id)
|
||||
filename = f'subs_{subs_id}.srt.sjson'
|
||||
|
||||
content_location = StaticContent.compute_location(self.course.id, filename)
|
||||
content = StaticContent(content_location, filename, mime_type, filedata)
|
||||
@@ -82,11 +81,11 @@ class BaseTranscripts(CourseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""Create initial data."""
|
||||
super(BaseTranscripts, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
# Add video module
|
||||
data = {
|
||||
'parent_locator': six.text_type(self.course.location),
|
||||
'parent_locator': str(self.course.location),
|
||||
'category': 'video',
|
||||
'type': 'video'
|
||||
}
|
||||
@@ -127,7 +126,7 @@ class BaseTranscripts(CourseTestCase):
|
||||
Setup non video module for tests.
|
||||
"""
|
||||
data = {
|
||||
'parent_locator': six.text_type(self.course.location),
|
||||
'parent_locator': str(self.course.location),
|
||||
'category': 'non_video',
|
||||
'type': 'non_video'
|
||||
}
|
||||
@@ -156,10 +155,10 @@ class TestUploadTranscripts(BaseTranscripts):
|
||||
Tests for '/transcripts/upload' endpoint.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestUploadTranscripts, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.contents = {
|
||||
'good': SRT_TRANSCRIPT_CONTENT,
|
||||
'bad': u'Some BAD data',
|
||||
'bad': 'Some BAD data',
|
||||
}
|
||||
# Create temporary transcript files
|
||||
self.good_srt_file = self.create_transcript_file(content=self.contents['good'], suffix='.srt')
|
||||
@@ -169,12 +168,12 @@ class TestUploadTranscripts(BaseTranscripts):
|
||||
|
||||
# Setup a VEDA produced video and persist `edx_video_id` in VAL.
|
||||
create_video({
|
||||
'edx_video_id': u'123-456-789',
|
||||
'edx_video_id': '123-456-789',
|
||||
'status': 'upload',
|
||||
'client_video_id': u'Test Video',
|
||||
'client_video_id': 'Test Video',
|
||||
'duration': 0,
|
||||
'encoded_videos': [],
|
||||
'courses': [six.text_type(self.course.id)]
|
||||
'courses': [str(self.course.id)]
|
||||
})
|
||||
|
||||
# Add clean up handler
|
||||
@@ -226,9 +225,9 @@ class TestUploadTranscripts(BaseTranscripts):
|
||||
return response
|
||||
|
||||
@ddt.data(
|
||||
(u'123-456-789', False),
|
||||
(u'', False),
|
||||
(u'123-456-789', True)
|
||||
('123-456-789', False),
|
||||
('', False),
|
||||
('123-456-789', True)
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_transcript_upload_success(self, edx_video_id, include_bom):
|
||||
@@ -259,7 +258,7 @@ class TestUploadTranscripts(BaseTranscripts):
|
||||
self.assertEqual(video.edx_video_id, expected_edx_video_id)
|
||||
|
||||
# Verify transcript content
|
||||
actual_transcript = get_video_transcript_content(video.edx_video_id, language_code=u'en')
|
||||
actual_transcript = get_video_transcript_content(video.edx_video_id, language_code='en')
|
||||
actual_sjson_content = json.loads(actual_transcript['content'].decode('utf-8'))
|
||||
expected_sjson_content = json.loads(Transcript.convert(
|
||||
self.contents['good'],
|
||||
@@ -276,7 +275,7 @@ class TestUploadTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'Video locator is required.'
|
||||
expected_message='Video locator is required.'
|
||||
)
|
||||
|
||||
def test_transcript_upload_without_file(self):
|
||||
@@ -287,7 +286,7 @@ class TestUploadTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'A transcript file is required.'
|
||||
expected_message='A transcript file is required.'
|
||||
)
|
||||
|
||||
def test_transcript_upload_bad_format(self):
|
||||
@@ -302,7 +301,7 @@ class TestUploadTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'This transcript file type is not supported.'
|
||||
expected_message='This transcript file type is not supported.'
|
||||
)
|
||||
|
||||
def test_transcript_upload_bad_content(self):
|
||||
@@ -318,7 +317,7 @@ class TestUploadTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'There is a problem with this transcript file. Try to upload a different file.'
|
||||
expected_message='There is a problem with this transcript file. Try to upload a different file.'
|
||||
)
|
||||
|
||||
def test_transcript_upload_unknown_category(self):
|
||||
@@ -332,7 +331,7 @@ class TestUploadTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'Transcripts are supported only for "video" modules.'
|
||||
expected_message='Transcripts are supported only for "video" modules.'
|
||||
)
|
||||
|
||||
def test_transcript_upload_non_existent_item(self):
|
||||
@@ -348,7 +347,7 @@ class TestUploadTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'Cannot find item by locator.'
|
||||
expected_message='Cannot find item by locator.'
|
||||
)
|
||||
|
||||
def test_transcript_upload_without_edx_video_id(self):
|
||||
@@ -359,7 +358,7 @@ class TestUploadTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'Video ID is required.'
|
||||
expected_message='Video ID is required.'
|
||||
)
|
||||
|
||||
def test_transcript_upload_with_non_existant_edx_video_id(self):
|
||||
@@ -379,7 +378,7 @@ class TestUploadTranscripts(BaseTranscripts):
|
||||
self.assert_response(response, expected_status_code=400, expected_message='Invalid Video ID')
|
||||
|
||||
# Verify transcript does not exist for non-existant `edx_video_id`
|
||||
self.assertIsNone(get_video_transcript_content(non_existant_edx_video_id, language_code=u'en'))
|
||||
self.assertIsNone(get_video_transcript_content(non_existant_edx_video_id, language_code='en'))
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@@ -388,7 +387,7 @@ class TestChooseTranscripts(BaseTranscripts):
|
||||
Tests for '/transcripts/choose' endpoint.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestChooseTranscripts, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
# Create test transcript in contentstore
|
||||
self.chosen_html5_id = 'test_html5_subs'
|
||||
@@ -397,12 +396,12 @@ class TestChooseTranscripts(BaseTranscripts):
|
||||
|
||||
# Setup a VEDA produced video and persist `edx_video_id` in VAL.
|
||||
create_video({
|
||||
'edx_video_id': u'123-456-789',
|
||||
'edx_video_id': '123-456-789',
|
||||
'status': 'upload',
|
||||
'client_video_id': u'Test Video',
|
||||
'client_video_id': 'Test Video',
|
||||
'duration': 0,
|
||||
'encoded_videos': [],
|
||||
'courses': [six.text_type(self.course.id)]
|
||||
'courses': [str(self.course.id)]
|
||||
})
|
||||
|
||||
def choose_transcript(self, locator, chosen_html5_id):
|
||||
@@ -411,7 +410,7 @@ class TestChooseTranscripts(BaseTranscripts):
|
||||
"""
|
||||
payload = {}
|
||||
if locator:
|
||||
payload.update({'locator': six.text_type(locator)})
|
||||
payload.update({'locator': str(locator)})
|
||||
|
||||
if chosen_html5_id:
|
||||
payload.update({'html5_id': chosen_html5_id})
|
||||
@@ -420,7 +419,7 @@ class TestChooseTranscripts(BaseTranscripts):
|
||||
response = self.client.get(choose_transcript_url, {'data': json.dumps(payload)})
|
||||
return response
|
||||
|
||||
@ddt.data(u'123-456-789', u'')
|
||||
@ddt.data('123-456-789', '')
|
||||
def test_choose_transcript_success(self, edx_video_id):
|
||||
"""
|
||||
Verify that choosing transcript file in video component basic tab works as
|
||||
@@ -447,7 +446,7 @@ class TestChooseTranscripts(BaseTranscripts):
|
||||
self.assertEqual(video.edx_video_id, expected_edx_video_id)
|
||||
|
||||
# Verify transcript content
|
||||
actual_transcript = get_video_transcript_content(video.edx_video_id, language_code=u'en')
|
||||
actual_transcript = get_video_transcript_content(video.edx_video_id, language_code='en')
|
||||
actual_sjson_content = json.loads(actual_transcript['content'].decode('utf-8'))
|
||||
expected_sjson_content = json.loads(self.sjson_subs)
|
||||
self.assertDictEqual(actual_sjson_content, expected_sjson_content)
|
||||
@@ -460,7 +459,7 @@ class TestChooseTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'Incoming video data is empty.'
|
||||
expected_message='Incoming video data is empty.'
|
||||
)
|
||||
|
||||
def test_choose_transcript_fails_without_locator(self):
|
||||
@@ -471,7 +470,7 @@ class TestChooseTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'Cannot find item by locator.'
|
||||
expected_message='Cannot find item by locator.'
|
||||
)
|
||||
|
||||
def test_choose_transcript_with_no_html5_transcript(self):
|
||||
@@ -483,7 +482,7 @@ class TestChooseTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u"No such transcript."
|
||||
expected_message="No such transcript."
|
||||
)
|
||||
|
||||
def test_choose_transcript_fails_on_unknown_category(self):
|
||||
@@ -497,7 +496,7 @@ class TestChooseTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'Transcripts are supported only for "video" modules.'
|
||||
expected_message='Transcripts are supported only for "video" modules.'
|
||||
)
|
||||
|
||||
|
||||
@@ -507,7 +506,7 @@ class TestRenameTranscripts(BaseTranscripts):
|
||||
Tests for '/transcripts/rename' endpoint.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestRenameTranscripts, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
|
||||
# Create test transcript in contentstore and update item's sub.
|
||||
self.item.sub = 'test_video_subs'
|
||||
@@ -517,12 +516,12 @@ class TestRenameTranscripts(BaseTranscripts):
|
||||
|
||||
# Setup a VEDA produced video and persist `edx_video_id` in VAL.
|
||||
create_video({
|
||||
'edx_video_id': u'123-456-789',
|
||||
'edx_video_id': '123-456-789',
|
||||
'status': 'upload',
|
||||
'client_video_id': u'Test Video',
|
||||
'client_video_id': 'Test Video',
|
||||
'duration': 0,
|
||||
'encoded_videos': [],
|
||||
'courses': [six.text_type(self.course.id)]
|
||||
'courses': [str(self.course.id)]
|
||||
})
|
||||
|
||||
def rename_transcript(self, locator):
|
||||
@@ -531,13 +530,13 @@ class TestRenameTranscripts(BaseTranscripts):
|
||||
"""
|
||||
payload = {}
|
||||
if locator:
|
||||
payload.update({'locator': six.text_type(locator)})
|
||||
payload.update({'locator': str(locator)})
|
||||
|
||||
rename_transcript_url = reverse('rename_transcripts')
|
||||
response = self.client.get(rename_transcript_url, {'data': json.dumps(payload)})
|
||||
return response
|
||||
|
||||
@ddt.data(u'123-456-789', u'')
|
||||
@ddt.data('123-456-789', '')
|
||||
def test_rename_transcript_success(self, edx_video_id):
|
||||
"""
|
||||
Verify that "use current transcript" in video component basic tab works as
|
||||
@@ -564,7 +563,7 @@ class TestRenameTranscripts(BaseTranscripts):
|
||||
self.assertEqual(video.edx_video_id, expected_edx_video_id)
|
||||
|
||||
# Verify transcript content
|
||||
actual_transcript = get_video_transcript_content(video.edx_video_id, language_code=u'en')
|
||||
actual_transcript = get_video_transcript_content(video.edx_video_id, language_code='en')
|
||||
actual_sjson_content = json.loads(actual_transcript['content'].decode('utf-8'))
|
||||
expected_sjson_content = json.loads(self.sjson_subs)
|
||||
self.assertDictEqual(actual_sjson_content, expected_sjson_content)
|
||||
@@ -577,7 +576,7 @@ class TestRenameTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'Incoming video data is empty.'
|
||||
expected_message='Incoming video data is empty.'
|
||||
)
|
||||
|
||||
def test_rename_transcript_fails_with_invalid_locator(self):
|
||||
@@ -588,7 +587,7 @@ class TestRenameTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'Cannot find item by locator.'
|
||||
expected_message='Cannot find item by locator.'
|
||||
)
|
||||
|
||||
def test_rename_transcript_with_non_existent_sub(self):
|
||||
@@ -605,7 +604,7 @@ class TestRenameTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u"No such transcript."
|
||||
expected_message="No such transcript."
|
||||
)
|
||||
|
||||
def test_rename_transcript_fails_on_unknown_category(self):
|
||||
@@ -619,7 +618,7 @@ class TestRenameTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'Transcripts are supported only for "video" modules.'
|
||||
expected_message='Transcripts are supported only for "video" modules.'
|
||||
)
|
||||
|
||||
|
||||
@@ -633,17 +632,17 @@ class TestReplaceTranscripts(BaseTranscripts):
|
||||
Tests for '/transcripts/replace' endpoint.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(TestReplaceTranscripts, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.youtube_id = 'test_yt_id'
|
||||
|
||||
# Setup a VEDA produced video and persist `edx_video_id` in VAL.
|
||||
create_video({
|
||||
'edx_video_id': u'123-456-789',
|
||||
'edx_video_id': '123-456-789',
|
||||
'status': 'upload',
|
||||
'client_video_id': u'Test Video',
|
||||
'client_video_id': 'Test Video',
|
||||
'duration': 0,
|
||||
'encoded_videos': [],
|
||||
'courses': [six.text_type(self.course.id)]
|
||||
'courses': [str(self.course.id)]
|
||||
})
|
||||
|
||||
def replace_transcript(self, locator, youtube_id):
|
||||
@@ -652,7 +651,7 @@ class TestReplaceTranscripts(BaseTranscripts):
|
||||
"""
|
||||
payload = {}
|
||||
if locator:
|
||||
payload.update({'locator': six.text_type(locator)})
|
||||
payload.update({'locator': str(locator)})
|
||||
|
||||
if youtube_id:
|
||||
payload.update({
|
||||
@@ -668,7 +667,7 @@ class TestReplaceTranscripts(BaseTranscripts):
|
||||
response = self.client.get(replace_transcript_url, {'data': json.dumps(payload)})
|
||||
return response
|
||||
|
||||
@ddt.data(u'123-456-789', u'')
|
||||
@ddt.data('123-456-789', '')
|
||||
def test_replace_transcript_success(self, edx_video_id):
|
||||
"""
|
||||
Verify that "import from youtube" in video component basic tab works as
|
||||
@@ -695,7 +694,7 @@ class TestReplaceTranscripts(BaseTranscripts):
|
||||
self.assertEqual(video.edx_video_id, expected_edx_video_id)
|
||||
|
||||
# Verify transcript content
|
||||
actual_transcript = get_video_transcript_content(video.edx_video_id, language_code=u'en')
|
||||
actual_transcript = get_video_transcript_content(video.edx_video_id, language_code='en')
|
||||
actual_sjson_content = json.loads(actual_transcript['content'].decode('utf-8'))
|
||||
expected_sjson_content = json.loads(SJSON_TRANSCRIPT_CONTENT)
|
||||
self.assertDictEqual(actual_sjson_content, expected_sjson_content)
|
||||
@@ -708,7 +707,7 @@ class TestReplaceTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'Incoming video data is empty.'
|
||||
expected_message='Incoming video data is empty.'
|
||||
)
|
||||
|
||||
def test_replace_transcript_fails_with_invalid_locator(self):
|
||||
@@ -719,7 +718,7 @@ class TestReplaceTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'Cannot find item by locator.'
|
||||
expected_message='Cannot find item by locator.'
|
||||
)
|
||||
|
||||
def test_replace_transcript_fails_without_yt_id(self):
|
||||
@@ -730,14 +729,14 @@ class TestReplaceTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'YouTube ID is required.'
|
||||
expected_message='YouTube ID is required.'
|
||||
)
|
||||
|
||||
def test_replace_transcript_no_transcript_on_yt(self):
|
||||
"""
|
||||
Verify that replace transcript fails if YouTube does not have transcript for the given youtube id.
|
||||
"""
|
||||
error_message = u'YT ID not found.'
|
||||
error_message = 'YT ID not found.'
|
||||
patch_path = 'cms.djangoapps.contentstore.views.transcripts_ajax.download_youtube_subs'
|
||||
with patch(patch_path) as mock_download_youtube_subs:
|
||||
mock_download_youtube_subs.side_effect = GetTranscriptsFromYouTubeException(error_message)
|
||||
@@ -754,7 +753,7 @@ class TestReplaceTranscripts(BaseTranscripts):
|
||||
self.assert_response(
|
||||
response,
|
||||
expected_status_code=400,
|
||||
expected_message=u'Transcripts are supported only for "video" modules.'
|
||||
expected_message='Transcripts are supported only for "video" modules.'
|
||||
)
|
||||
|
||||
|
||||
@@ -782,7 +781,7 @@ class TestDownloadTranscripts(BaseTranscripts):
|
||||
"""
|
||||
payload = {}
|
||||
if locator:
|
||||
payload.update({'locator': six.text_type(locator)})
|
||||
payload.update({'locator': str(locator)})
|
||||
|
||||
download_transcript_url = reverse('download_transcripts')
|
||||
response = self.client.get(download_transcript_url, payload)
|
||||
@@ -851,7 +850,7 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
"""
|
||||
def test_success_download_nonyoutube(self):
|
||||
subs_id = str(uuid4())
|
||||
self.set_fields_from_xml(self.item, u"""
|
||||
self.set_fields_from_xml(self.item, """
|
||||
<video youtube="" sub="{}">
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
|
||||
@@ -872,7 +871,7 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
self.save_subs_to_store(subs, subs_id)
|
||||
|
||||
data = {
|
||||
'locator': six.text_type(self.video_usage_key),
|
||||
'locator': str(self.video_usage_key),
|
||||
'videos': [{
|
||||
'type': 'html5',
|
||||
'video': subs_id,
|
||||
@@ -885,15 +884,15 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
self.assertDictEqual(
|
||||
json.loads(resp.content.decode('utf-8')),
|
||||
{
|
||||
u'status': u'Success',
|
||||
u'youtube_local': False,
|
||||
u'is_youtube_mode': False,
|
||||
u'youtube_server': False,
|
||||
u'command': u'found',
|
||||
u'current_item_subs': six.text_type(subs_id),
|
||||
u'youtube_diff': True,
|
||||
u'html5_local': [six.text_type(subs_id)],
|
||||
u'html5_equal': False,
|
||||
'status': 'Success',
|
||||
'youtube_local': False,
|
||||
'is_youtube_mode': False,
|
||||
'youtube_server': False,
|
||||
'command': 'found',
|
||||
'current_item_subs': str(subs_id),
|
||||
'youtube_diff': True,
|
||||
'html5_local': [str(subs_id)],
|
||||
'html5_equal': False,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -915,7 +914,7 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
self.save_subs_to_store(subs, 'JMD_ifUUfsU')
|
||||
link = reverse('check_transcripts')
|
||||
data = {
|
||||
'locator': six.text_type(self.video_usage_key),
|
||||
'locator': str(self.video_usage_key),
|
||||
'videos': [{
|
||||
'type': 'youtube',
|
||||
'video': 'JMD_ifUUfsU',
|
||||
@@ -929,15 +928,15 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
self.assertDictEqual(
|
||||
json.loads(resp.content.decode('utf-8')),
|
||||
{
|
||||
u'status': u'Success',
|
||||
u'youtube_local': True,
|
||||
u'is_youtube_mode': True,
|
||||
u'youtube_server': False,
|
||||
u'command': u'found',
|
||||
u'current_item_subs': None,
|
||||
u'youtube_diff': True,
|
||||
u'html5_local': [],
|
||||
u'html5_equal': False,
|
||||
'status': 'Success',
|
||||
'youtube_local': True,
|
||||
'is_youtube_mode': True,
|
||||
'youtube_server': False,
|
||||
'command': 'found',
|
||||
'current_item_subs': None,
|
||||
'youtube_diff': True,
|
||||
'html5_local': [],
|
||||
'html5_equal': False,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -961,7 +960,7 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
self.save_subs_to_store(subs, 'good_id_2')
|
||||
link = reverse('check_transcripts')
|
||||
data = {
|
||||
'locator': six.text_type(self.video_usage_key),
|
||||
'locator': str(self.video_usage_key),
|
||||
'videos': [{
|
||||
'type': 'youtube',
|
||||
'video': 'good_id_2',
|
||||
@@ -980,15 +979,15 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
self.assertDictEqual(
|
||||
json.loads(resp.content.decode('utf-8')),
|
||||
{
|
||||
u'status': u'Success',
|
||||
u'youtube_local': True,
|
||||
u'is_youtube_mode': True,
|
||||
u'youtube_server': True,
|
||||
u'command': u'replace',
|
||||
u'current_item_subs': None,
|
||||
u'youtube_diff': True,
|
||||
u'html5_local': [],
|
||||
u'html5_equal': False,
|
||||
'status': 'Success',
|
||||
'youtube_local': True,
|
||||
'is_youtube_mode': True,
|
||||
'youtube_server': True,
|
||||
'command': 'replace',
|
||||
'current_item_subs': None,
|
||||
'youtube_diff': True,
|
||||
'html5_local': [],
|
||||
'html5_equal': False,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1023,7 +1022,7 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
|
||||
# Test for raising `ItemNotFoundError` exception.
|
||||
data = {
|
||||
'locator': '{0}_{1}'.format(self.video_usage_key, 'BAD_LOCATOR'),
|
||||
'locator': '{}_{}'.format(self.video_usage_key, 'BAD_LOCATOR'),
|
||||
'videos': [{
|
||||
'type': '',
|
||||
'video': '',
|
||||
@@ -1037,7 +1036,7 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
def test_fail_for_non_video_module(self):
|
||||
# Not video module: setup
|
||||
data = {
|
||||
'parent_locator': six.text_type(self.course.location),
|
||||
'parent_locator': str(self.course.location),
|
||||
'category': 'not_video',
|
||||
'type': 'not_video'
|
||||
}
|
||||
@@ -1045,7 +1044,7 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
usage_key = self._get_usage_key(resp)
|
||||
subs_id = str(uuid4())
|
||||
item = modulestore().get_item(usage_key)
|
||||
self.set_fields_from_xml(self.item, (u"""
|
||||
self.set_fields_from_xml(self.item, ("""
|
||||
<not_video youtube="" sub="{}">
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
|
||||
@@ -1066,7 +1065,7 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
self.save_subs_to_store(subs, subs_id)
|
||||
|
||||
data = {
|
||||
'locator': six.text_type(usage_key),
|
||||
'locator': str(usage_key),
|
||||
'videos': [{
|
||||
'type': '',
|
||||
'video': '',
|
||||
@@ -1096,7 +1095,7 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
}
|
||||
|
||||
# video_transcript_feature.return_value = feature_enabled
|
||||
self.set_fields_from_xml(self.item, (u"""
|
||||
self.set_fields_from_xml(self.item, ("""
|
||||
<video youtube="" sub="" edx_video_id="123">
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4"/>
|
||||
<source src="http://www.quirksmode.org/html5/videos/big_buck_bunny.webm"/>
|
||||
@@ -1107,7 +1106,7 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
|
||||
# Make request to check transcript view
|
||||
data = {
|
||||
'locator': six.text_type(self.video_usage_key),
|
||||
'locator': str(self.video_usage_key),
|
||||
'videos': [{
|
||||
'type': 'html5',
|
||||
'video': "",
|
||||
@@ -1122,14 +1121,14 @@ class TestCheckTranscripts(BaseTranscripts):
|
||||
self.assertDictEqual(
|
||||
json.loads(response.content.decode('utf-8')),
|
||||
{
|
||||
u'status': u'Success',
|
||||
u'youtube_local': False,
|
||||
u'is_youtube_mode': False,
|
||||
u'youtube_server': False,
|
||||
u'command': 'found',
|
||||
u'current_item_subs': None,
|
||||
u'youtube_diff': True,
|
||||
u'html5_local': [],
|
||||
u'html5_equal': False,
|
||||
'status': 'Success',
|
||||
'youtube_local': False,
|
||||
'is_youtube_mode': False,
|
||||
'youtube_server': False,
|
||||
'command': 'found',
|
||||
'current_item_subs': None,
|
||||
'youtube_diff': True,
|
||||
'html5_local': [],
|
||||
'html5_equal': False,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ class UnitPageTestCase(StudioPageTestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(UnitPageTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.vertical = ItemFactory.create(parent_location=self.sequential.location,
|
||||
category='vertical', display_name='Unit')
|
||||
self.video = ItemFactory.create(parent_location=self.vertical.location,
|
||||
|
||||
@@ -16,7 +16,7 @@ from common.djangoapps.student.roles import CourseInstructorRole, CourseStaffRol
|
||||
|
||||
class UsersTestCase(CourseTestCase): # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
def setUp(self):
|
||||
super(UsersTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.ext_user = User.objects.create_user(
|
||||
"joe", "joe@comedycentral.com", "haha")
|
||||
self.ext_user.is_active = True
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#-*- coding: utf-8 -*-
|
||||
"""
|
||||
Unit tests for video-related REST APIs.
|
||||
"""
|
||||
@@ -9,11 +8,12 @@ import json
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime
|
||||
from io import StringIO
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import dateutil.parser
|
||||
import ddt
|
||||
import pytz
|
||||
import six
|
||||
from django.conf import settings
|
||||
from django.test.utils import override_settings
|
||||
from edx_toggles.toggles import LegacyWaffleSwitch
|
||||
@@ -27,8 +27,6 @@ from edxval.api import (
|
||||
get_transcript_preferences,
|
||||
get_video_info
|
||||
)
|
||||
from mock import Mock, patch
|
||||
from six import StringIO
|
||||
from waffle.testutils import override_flag
|
||||
|
||||
from cms.djangoapps.contentstore.models import VideoUploadConfig
|
||||
@@ -57,7 +55,7 @@ from ..videos import (
|
||||
VIDEO_IMAGE_UPLOAD_ENABLED_SWITCH = LegacyWaffleSwitch(WAFFLE_SWITCHES, VIDEO_IMAGE_UPLOAD_ENABLED)
|
||||
|
||||
|
||||
class VideoUploadTestBase(object):
|
||||
class VideoUploadTestBase:
|
||||
"""
|
||||
Test cases for the video upload feature
|
||||
"""
|
||||
@@ -67,7 +65,7 @@ class VideoUploadTestBase(object):
|
||||
return reverse_course_url(self.VIEW_NAME, course_key, kwargs) # lint-amnesty, pylint: disable=no-member
|
||||
|
||||
def setUp(self):
|
||||
super(VideoUploadTestBase, self).setUp() # lint-amnesty, pylint: disable=no-member, super-with-arguments
|
||||
super().setUp() # lint-amnesty, pylint: disable=no-member
|
||||
self.url = self.get_url_for_course_key(self.course.id)
|
||||
self.test_token = "test_token"
|
||||
self.course.video_upload_pipeline = {
|
||||
@@ -84,7 +82,7 @@ class VideoUploadTestBase(object):
|
||||
self.store.update_item(self.course2, self.user.id) # lint-amnesty, pylint: disable=no-member
|
||||
|
||||
# course ids for videos
|
||||
course_ids = [six.text_type(self.course.id), six.text_type(self.course2.id)]
|
||||
course_ids = [str(self.course.id), str(self.course2.id)]
|
||||
created = datetime.now(pytz.utc)
|
||||
|
||||
self.profiles = ["profile1", "profile2"]
|
||||
@@ -122,7 +120,7 @@ class VideoUploadTestBase(object):
|
||||
},
|
||||
{
|
||||
"edx_video_id": "non-ascii",
|
||||
"client_video_id": u"nón-ascii-näme.mp4",
|
||||
"client_video_id": "nón-ascii-näme.mp4",
|
||||
"duration": 256.0,
|
||||
"status": "transcode_active",
|
||||
"courses": course_ids,
|
||||
@@ -130,7 +128,7 @@ class VideoUploadTestBase(object):
|
||||
"encoded_videos": [
|
||||
{
|
||||
"profile": "profile1",
|
||||
"url": u"http://example.com/profile1/nón-ascii-näme.mp4",
|
||||
"url": "http://example.com/profile1/nón-ascii-näme.mp4",
|
||||
"file_size": 3200,
|
||||
"bitrate": 100,
|
||||
},
|
||||
@@ -140,7 +138,7 @@ class VideoUploadTestBase(object):
|
||||
# Ensure every status string is tested
|
||||
self.previous_uploads += [
|
||||
{
|
||||
"edx_video_id": "status_test_{}".format(status),
|
||||
"edx_video_id": f"status_test_{status}",
|
||||
"client_video_id": "status_test.mp4",
|
||||
"duration": 3.14,
|
||||
"status": status,
|
||||
@@ -206,7 +204,7 @@ class VideoUploadTestMixin(VideoUploadTestBase):
|
||||
self.assertEqual(self.client.get(self.url).status_code, 404)
|
||||
|
||||
|
||||
class VideoUploadPostTestsMixin(object):
|
||||
class VideoUploadPostTestsMixin:
|
||||
"""
|
||||
Shared test cases for video post tests.
|
||||
"""
|
||||
@@ -284,7 +282,7 @@ class VideoUploadPostTestsMixin(object):
|
||||
'client_video_id',
|
||||
file_info['file_name']
|
||||
)
|
||||
mock_key_instance.set_metadata.assert_any_call('course_key', six.text_type(self.course.id))
|
||||
mock_key_instance.set_metadata.assert_any_call('course_key', str(self.course.id))
|
||||
mock_key_instance.generate_url.assert_called_once_with(
|
||||
KEY_EXPIRATION_IN_SECONDS,
|
||||
'PUT',
|
||||
@@ -297,7 +295,7 @@ class VideoUploadPostTestsMixin(object):
|
||||
self.assertEqual(val_info['client_video_id'], file_info['file_name'])
|
||||
self.assertEqual(val_info['status'], 'upload')
|
||||
self.assertEqual(val_info['duration'], 0)
|
||||
self.assertEqual(val_info['courses'], [{six.text_type(self.course.id): None}])
|
||||
self.assertEqual(val_info['courses'], [{str(self.course.id): None}])
|
||||
|
||||
# Ensure response is correct
|
||||
response_file = response_obj['files'][i]
|
||||
@@ -352,7 +350,7 @@ class VideosHandlerTestCase(VideoUploadTestMixin, VideoUploadPostTestsMixin, Cou
|
||||
original_video = self.previous_uploads[-(i + 1)]
|
||||
self.assertEqual(
|
||||
set(response_video.keys()),
|
||||
set([
|
||||
{
|
||||
'edx_video_id',
|
||||
'client_video_id',
|
||||
'created',
|
||||
@@ -362,7 +360,7 @@ class VideosHandlerTestCase(VideoUploadTestMixin, VideoUploadPostTestsMixin, Cou
|
||||
'transcripts',
|
||||
'transcription_status',
|
||||
'error_description'
|
||||
])
|
||||
}
|
||||
)
|
||||
dateutil.parser.parse(response_video['created'])
|
||||
for field in ['edx_video_id', 'client_video_id', 'duration']:
|
||||
@@ -530,7 +528,7 @@ class VideosHandlerTestCase(VideoUploadTestMixin, VideoUploadPostTestsMixin, Cou
|
||||
"""
|
||||
Test that video uploads throws error message when file name contains special characters.
|
||||
"""
|
||||
file_name = u'test\u2019_file.mp4'
|
||||
file_name = 'test\u2019_file.mp4'
|
||||
files = [{'file_name': file_name, 'content_type': 'video/mp4'}]
|
||||
|
||||
bucket = Mock()
|
||||
@@ -543,7 +541,7 @@ class VideosHandlerTestCase(VideoUploadTestMixin, VideoUploadPostTestsMixin, Cou
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
response = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(response['error'], u'The file name for %s must contain only ASCII characters.' % file_name)
|
||||
self.assertEqual(response['error'], 'The file name for %s must contain only ASCII characters.' % file_name)
|
||||
|
||||
@override_settings(AWS_ACCESS_KEY_ID='test_key_id', AWS_SECRET_ACCESS_KEY='test_secret', AWS_SECURITY_TOKEN='token')
|
||||
@patch('boto.s3.key.Key')
|
||||
@@ -784,7 +782,7 @@ class VideosHandlerTestCase(VideoUploadTestMixin, VideoUploadPostTestsMixin, Cou
|
||||
)
|
||||
|
||||
mock_logger.info.assert_called_with(
|
||||
u'VIDEOS: Video status update with id [%s], status [%s] and message [%s]',
|
||||
'VIDEOS: Video status update with id [%s], status [%s] and message [%s]',
|
||||
edx_video_id,
|
||||
'upload_failed',
|
||||
'server down'
|
||||
@@ -1019,7 +1017,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
{
|
||||
'extension': '.tiff'
|
||||
},
|
||||
u'This image file type is not supported. Supported file types are {supported_file_formats}.'.format(
|
||||
'This image file type is not supported. Supported file types are {supported_file_formats}.'.format(
|
||||
supported_file_formats=list(settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.keys())
|
||||
)
|
||||
),
|
||||
@@ -1028,7 +1026,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
{
|
||||
'size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MAX_BYTES'] + 10
|
||||
},
|
||||
u'This image file must be smaller than {image_max_size}.'.format(
|
||||
'This image file must be smaller than {image_max_size}.'.format(
|
||||
image_max_size=settings.VIDEO_IMAGE_MAX_FILE_SIZE_MB
|
||||
)
|
||||
),
|
||||
@@ -1036,7 +1034,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
{
|
||||
'size': settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MIN_BYTES'] - 10
|
||||
},
|
||||
u'This image file must be larger than {image_min_size}.'.format(
|
||||
'This image file must be larger than {image_min_size}.'.format(
|
||||
image_min_size=settings.VIDEO_IMAGE_MIN_FILE_SIZE_KB
|
||||
)
|
||||
),
|
||||
@@ -1046,7 +1044,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
'width': 16, # 16x9
|
||||
'height': 9
|
||||
},
|
||||
u'Recommended image resolution is {image_file_max_width}x{image_file_max_height}. The minimum resolution is {image_file_min_width}x{image_file_min_height}.'.format( # lint-amnesty, pylint: disable=line-too-long
|
||||
'Recommended image resolution is {image_file_max_width}x{image_file_max_height}. The minimum resolution is {image_file_min_width}x{image_file_min_height}.'.format( # lint-amnesty, pylint: disable=line-too-long
|
||||
image_file_max_width=settings.VIDEO_IMAGE_MAX_WIDTH,
|
||||
image_file_max_height=settings.VIDEO_IMAGE_MAX_HEIGHT,
|
||||
image_file_min_width=settings.VIDEO_IMAGE_MIN_WIDTH,
|
||||
@@ -1058,7 +1056,7 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
'width': settings.VIDEO_IMAGE_MIN_WIDTH - 10,
|
||||
'height': settings.VIDEO_IMAGE_MIN_HEIGHT
|
||||
},
|
||||
u'Recommended image resolution is {image_file_max_width}x{image_file_max_height}. The minimum resolution is {image_file_min_width}x{image_file_min_height}.'.format( # lint-amnesty, pylint: disable=line-too-long
|
||||
'Recommended image resolution is {image_file_max_width}x{image_file_max_height}. The minimum resolution is {image_file_min_width}x{image_file_min_height}.'.format( # lint-amnesty, pylint: disable=line-too-long
|
||||
image_file_max_width=settings.VIDEO_IMAGE_MAX_WIDTH,
|
||||
image_file_max_height=settings.VIDEO_IMAGE_MAX_HEIGHT,
|
||||
image_file_min_width=settings.VIDEO_IMAGE_MIN_WIDTH,
|
||||
@@ -1071,8 +1069,8 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
'height': settings.VIDEO_IMAGE_MIN_HEIGHT - 10
|
||||
},
|
||||
(
|
||||
u'Recommended image resolution is {image_file_max_width}x{image_file_max_height}. '
|
||||
u'The minimum resolution is {image_file_min_width}x{image_file_min_height}.'
|
||||
'Recommended image resolution is {image_file_max_width}x{image_file_max_height}. '
|
||||
'The minimum resolution is {image_file_min_width}x{image_file_min_height}.'
|
||||
).format(
|
||||
image_file_max_width=settings.VIDEO_IMAGE_MAX_WIDTH,
|
||||
image_file_max_height=settings.VIDEO_IMAGE_MAX_HEIGHT,
|
||||
@@ -1086,8 +1084,8 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
'height': 100
|
||||
},
|
||||
(
|
||||
u'Recommended image resolution is {image_file_max_width}x{image_file_max_height}. '
|
||||
u'The minimum resolution is {image_file_min_width}x{image_file_min_height}.'
|
||||
'Recommended image resolution is {image_file_max_width}x{image_file_max_height}. '
|
||||
'The minimum resolution is {image_file_min_width}x{image_file_min_height}.'
|
||||
).format(
|
||||
image_file_max_width=settings.VIDEO_IMAGE_MAX_WIDTH,
|
||||
image_file_max_height=settings.VIDEO_IMAGE_MAX_HEIGHT,
|
||||
@@ -1122,14 +1120,14 @@ class VideoImageTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
'width': settings.VIDEO_IMAGE_MIN_WIDTH + 100,
|
||||
'height': settings.VIDEO_IMAGE_MIN_HEIGHT + 200
|
||||
},
|
||||
u'This image file must have an aspect ratio of {video_image_aspect_ratio_text}.'.format(
|
||||
'This image file must have an aspect ratio of {video_image_aspect_ratio_text}.'.format(
|
||||
video_image_aspect_ratio_text=settings.VIDEO_IMAGE_ASPECT_RATIO_TEXT
|
||||
)
|
||||
),
|
||||
# Image file name validation
|
||||
(
|
||||
{
|
||||
'prefix': u'nøn-åßç¡¡'
|
||||
'prefix': 'nøn-åßç¡¡'
|
||||
},
|
||||
'The image file name can only contain letters, numbers, hyphens (-), and underscores (_).'
|
||||
)
|
||||
@@ -1199,7 +1197,7 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
(
|
||||
{},
|
||||
True,
|
||||
u"Invalid provider None.",
|
||||
"Invalid provider None.",
|
||||
400
|
||||
),
|
||||
(
|
||||
@@ -1207,7 +1205,7 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
'provider': ''
|
||||
},
|
||||
True,
|
||||
u"Invalid provider .",
|
||||
"Invalid provider .",
|
||||
400
|
||||
),
|
||||
(
|
||||
@@ -1215,7 +1213,7 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
'provider': 'dummy-provider'
|
||||
},
|
||||
True,
|
||||
u"Invalid provider dummy-provider.",
|
||||
"Invalid provider dummy-provider.",
|
||||
400
|
||||
),
|
||||
(
|
||||
@@ -1223,7 +1221,7 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
'provider': TranscriptProvider.CIELO24
|
||||
},
|
||||
True,
|
||||
u"Invalid cielo24 fidelity None.",
|
||||
"Invalid cielo24 fidelity None.",
|
||||
400
|
||||
),
|
||||
(
|
||||
@@ -1232,7 +1230,7 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
'cielo24_fidelity': 'PROFESSIONAL',
|
||||
},
|
||||
True,
|
||||
u"Invalid cielo24 turnaround None.",
|
||||
"Invalid cielo24 turnaround None.",
|
||||
400
|
||||
),
|
||||
(
|
||||
@@ -1243,7 +1241,7 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
'video_source_language': 'en'
|
||||
},
|
||||
True,
|
||||
u"Invalid languages [].",
|
||||
"Invalid languages [].",
|
||||
400
|
||||
),
|
||||
(
|
||||
@@ -1254,7 +1252,7 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
'video_source_language': 'es'
|
||||
},
|
||||
True,
|
||||
u"Unsupported source language es.",
|
||||
"Unsupported source language es.",
|
||||
400
|
||||
),
|
||||
(
|
||||
@@ -1274,7 +1272,7 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
'provider': TranscriptProvider.THREE_PLAY_MEDIA
|
||||
},
|
||||
True,
|
||||
u"Invalid 3play turnaround None.",
|
||||
"Invalid 3play turnaround None.",
|
||||
400
|
||||
),
|
||||
(
|
||||
@@ -1284,7 +1282,7 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
'video_source_language': 'zh',
|
||||
},
|
||||
True,
|
||||
u"Unsupported source language zh.",
|
||||
"Unsupported source language zh.",
|
||||
400
|
||||
),
|
||||
(
|
||||
@@ -1375,7 +1373,7 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
Test that transcript handler removes transcript preferences correctly.
|
||||
"""
|
||||
# First add course wide transcript preferences.
|
||||
preferences = create_or_update_transcript_preferences(six.text_type(self.course.id))
|
||||
preferences = create_or_update_transcript_preferences(str(self.course.id))
|
||||
|
||||
# Verify transcript preferences exist
|
||||
self.assertIsNotNone(preferences)
|
||||
@@ -1388,7 +1386,7 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
self.assertEqual(response.status_code, 204)
|
||||
|
||||
# Verify transcript preferences no loger exist
|
||||
preferences = get_transcript_preferences(six.text_type(self.course.id))
|
||||
preferences = get_transcript_preferences(str(self.course.id))
|
||||
self.assertIsNone(preferences)
|
||||
|
||||
def test_remove_transcript_preferences_not_found(self):
|
||||
@@ -1454,7 +1452,7 @@ class TranscriptPreferencesTestCase(VideoUploadTestBase, CourseTestCase):
|
||||
mock_conn.return_value = Mock(get_bucket=Mock(return_value=bucket))
|
||||
mock_key_instance = Mock(
|
||||
generate_url=Mock(
|
||||
return_value='http://example.com/url_{file_name}'.format(file_name=file_name)
|
||||
return_value=f'http://example.com/url_{file_name}'
|
||||
)
|
||||
)
|
||||
# If extra calls are made, return a dummy
|
||||
@@ -1487,7 +1485,7 @@ class VideoUrlsCsvTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
VIEW_NAME = "video_encodings_download"
|
||||
|
||||
def setUp(self):
|
||||
super(VideoUrlsCsvTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
VideoUploadConfig(profile_whitelist="profile1").save()
|
||||
|
||||
def _check_csv_response(self, expected_profiles):
|
||||
@@ -1499,37 +1497,32 @@ class VideoUrlsCsvTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
response["Content-Disposition"],
|
||||
u"attachment; filename=\"{course}_video_urls.csv\"".format(course=self.course.id.course)
|
||||
f"attachment; filename=\"{self.course.id.course}_video_urls.csv\""
|
||||
)
|
||||
response_content = b"".join(response.streaming_content)
|
||||
response_reader = StringIO(response_content.decode('utf-8') if six.PY3 else response_content)
|
||||
response_reader = StringIO(response_content.decode())
|
||||
reader = csv.DictReader(response_reader, dialect=csv.excel)
|
||||
self.assertEqual(
|
||||
reader.fieldnames,
|
||||
(
|
||||
["Name", "Duration", "Date Added", "Video ID", "Status"] +
|
||||
[u"{} URL".format(profile) for profile in expected_profiles]
|
||||
[f"{profile} URL" for profile in expected_profiles]
|
||||
)
|
||||
)
|
||||
rows = list(reader)
|
||||
self.assertEqual(len(rows), len(self.previous_uploads))
|
||||
for i, row in enumerate(rows):
|
||||
response_video = {
|
||||
key.decode("utf-8") if six.PY2 else key: value.decode("utf-8") if six.PY2 else value
|
||||
for key, value in row.items()
|
||||
}
|
||||
response_video = dict(row.items())
|
||||
# Videos should be returned by creation date descending
|
||||
original_video = self.previous_uploads[-(i + 1)]
|
||||
client_video_id = original_video["client_video_id"].encode('utf-8') if six.PY2 \
|
||||
else original_video["client_video_id"]
|
||||
self.assertEqual(response_video["Name"].encode('utf-8') if six.PY2
|
||||
else response_video["Name"], client_video_id)
|
||||
client_video_id = original_video["client_video_id"]
|
||||
self.assertEqual(response_video["Name"], client_video_id)
|
||||
self.assertEqual(response_video["Duration"], str(original_video["duration"]))
|
||||
dateutil.parser.parse(response_video["Date Added"])
|
||||
self.assertEqual(response_video["Video ID"], original_video["edx_video_id"])
|
||||
self.assertEqual(response_video["Status"], convert_video_status(original_video))
|
||||
for profile in expected_profiles:
|
||||
response_profile_url = response_video["{} URL".format(profile)]
|
||||
response_profile_url = response_video[f"{profile} URL"]
|
||||
original_encoded_for_profile = next(
|
||||
(
|
||||
original_encoded
|
||||
@@ -1539,10 +1532,8 @@ class VideoUrlsCsvTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
None
|
||||
)
|
||||
if original_encoded_for_profile:
|
||||
original_encoded_for_profile_url = original_encoded_for_profile["url"].encode('utf-8') if six.PY2 \
|
||||
else original_encoded_for_profile["url"]
|
||||
self.assertEqual(response_profile_url.encode('utf-8') if six.PY2 else response_profile_url,
|
||||
original_encoded_for_profile_url)
|
||||
original_encoded_for_profile_url = original_encoded_for_profile["url"]
|
||||
self.assertEqual(response_profile_url, original_encoded_for_profile_url)
|
||||
else:
|
||||
self.assertEqual(response_profile_url, "")
|
||||
|
||||
@@ -1555,7 +1546,7 @@ class VideoUrlsCsvTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
|
||||
def test_non_ascii_course(self):
|
||||
course = CourseFactory.create(
|
||||
number=u"nón-äscii",
|
||||
number="nón-äscii",
|
||||
video_upload_pipeline={
|
||||
"course_video_upload_token": self.test_token,
|
||||
}
|
||||
@@ -1564,5 +1555,5 @@ class VideoUrlsCsvTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(
|
||||
response["Content-Disposition"],
|
||||
u"attachment; filename*=utf-8''n%C3%B3n-%C3%A4scii_video_urls.csv"
|
||||
"attachment; filename*=utf-8''n%C3%B3n-%C3%A4scii_video_urls.csv"
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ class StudioPageTestCase(CourseTestCase):
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(StudioPageTestCase, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
|
||||
super().setUp()
|
||||
self.chapter = ItemFactory.create(parent_location=self.course.location,
|
||||
category='chapter', display_name="Week 1")
|
||||
self.sequential = ItemFactory.create(parent_location=self.chapter.location,
|
||||
@@ -37,7 +37,7 @@ class StudioPageTestCase(CourseTestCase):
|
||||
"""
|
||||
Returns the HTML for the xblock when shown within a unit or container page.
|
||||
"""
|
||||
preview_url = '/xblock/{usage_key}/{view_name}'.format(usage_key=xblock.location, view_name=view_name)
|
||||
preview_url = f'/xblock/{xblock.location}/{view_name}'
|
||||
resp = self.client.get_json(preview_url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
resp_content = json.loads(resp.content.decode('utf-8'))
|
||||
|
||||
@@ -21,10 +21,10 @@ from edxval.api import (
|
||||
)
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag
|
||||
from openedx.core.djangoapps.video_pipeline.api import update_3rd_party_transcription_service_credentials
|
||||
from common.djangoapps.student.auth import has_studio_write_access
|
||||
from common.djangoapps.util.json_request import JsonResponse, expect_json
|
||||
from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag
|
||||
from openedx.core.djangoapps.video_pipeline.api import update_3rd_party_transcription_service_credentials
|
||||
from xmodule.video_module.transcripts_utils import Transcript, TranscriptsGenerationException
|
||||
|
||||
from .videos import TranscriptProvider
|
||||
@@ -72,14 +72,14 @@ def validate_transcript_credentials(provider, **credentials):
|
||||
must_have_prop for must_have_prop in must_have_props if must_have_prop not in list(credentials.keys())
|
||||
]
|
||||
if missing:
|
||||
error_message = u'{missing} must be specified.'.format(missing=' and '.join(missing))
|
||||
error_message = '{missing} must be specified.'.format(missing=' and '.join(missing))
|
||||
return error_message, validated_credentials
|
||||
|
||||
validated_credentials.update({
|
||||
prop: credentials[prop] for prop in must_have_props
|
||||
})
|
||||
else:
|
||||
error_message = u'Invalid Provider {provider}.'.format(provider=provider)
|
||||
error_message = f'Invalid Provider {provider}.'
|
||||
|
||||
return error_message, validated_credentials
|
||||
|
||||
@@ -148,7 +148,7 @@ def transcript_download_handler(request):
|
||||
missing = [attr for attr in ['edx_video_id', 'language_code'] if attr not in request.GET]
|
||||
if missing:
|
||||
return JsonResponse(
|
||||
{'error': _(u'The following parameters are required: {missing}.').format(missing=', '.join(missing))},
|
||||
{'error': _('The following parameters are required: {missing}.').format(missing=', '.join(missing))},
|
||||
status=400
|
||||
)
|
||||
|
||||
@@ -158,7 +158,7 @@ def transcript_download_handler(request):
|
||||
if transcript:
|
||||
name_and_extension = os.path.splitext(transcript['file_name'])
|
||||
basename, file_format = name_and_extension[0], name_and_extension[1][1:]
|
||||
transcript_filename = '{base_name}.{ext}'.format(base_name=basename, ext=Transcript.SRT)
|
||||
transcript_filename = f'{basename}.{Transcript.SRT}'
|
||||
transcript_content = Transcript.convert(
|
||||
content=transcript['content'],
|
||||
input_format=file_format,
|
||||
@@ -166,7 +166,7 @@ def transcript_download_handler(request):
|
||||
)
|
||||
# Construct an HTTP response
|
||||
response = HttpResponse(transcript_content, content_type=Transcript.mime_types[Transcript.SRT])
|
||||
response['Content-Disposition'] = u'attachment; filename="{filename}"'.format(filename=transcript_filename)
|
||||
response['Content-Disposition'] = f'attachment; filename="{transcript_filename}"'
|
||||
else:
|
||||
response = HttpResponseNotFound()
|
||||
|
||||
@@ -188,16 +188,16 @@ def validate_transcript_upload_data(data, files):
|
||||
must_have_attrs = ['edx_video_id', 'language_code', 'new_language_code']
|
||||
missing = [attr for attr in must_have_attrs if attr not in data]
|
||||
if missing:
|
||||
error = _(u'The following parameters are required: {missing}.').format(missing=', '.join(missing))
|
||||
error = _('The following parameters are required: {missing}.').format(missing=', '.join(missing))
|
||||
elif (
|
||||
data['language_code'] != data['new_language_code'] and
|
||||
data['new_language_code'] in get_available_transcript_languages(video_id=data['edx_video_id'])
|
||||
):
|
||||
error = _(u'A transcript with the "{language_code}" language code already exists.'.format( # lint-amnesty, pylint: disable=translation-of-non-string
|
||||
error = _('A transcript with the "{language_code}" language code already exists.'.format( # lint-amnesty, pylint: disable=translation-of-non-string
|
||||
language_code=data['new_language_code']
|
||||
))
|
||||
elif 'file' not in files:
|
||||
error = _(u'A transcript file is required.')
|
||||
error = _('A transcript file is required.')
|
||||
|
||||
return error
|
||||
|
||||
@@ -247,7 +247,7 @@ def transcript_upload_handler(request):
|
||||
response = JsonResponse(status=201)
|
||||
except (TranscriptsGenerationException, UnicodeDecodeError):
|
||||
response = JsonResponse(
|
||||
{'error': _(u'There is a problem with this transcript file. Try to upload a different file.')},
|
||||
{'error': _('There is a problem with this transcript file. Try to upload a different file.')},
|
||||
status=400
|
||||
)
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ from django.utils.translation import ugettext as _
|
||||
from edxval.api import create_external_video, create_or_update_video_transcript
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import UsageKey
|
||||
from six import text_type
|
||||
|
||||
from cms.djangoapps.contentstore.views.videos import TranscriptProvider
|
||||
from common.djangoapps.student.auth import has_course_author_access
|
||||
@@ -82,7 +81,7 @@ def link_video_to_component(video_component, user):
|
||||
"""
|
||||
edx_video_id = clean_video_id(video_component.edx_video_id)
|
||||
if not edx_video_id:
|
||||
edx_video_id = create_external_video(display_name=u'external video')
|
||||
edx_video_id = create_external_video(display_name='external video')
|
||||
video_component.edx_video_id = edx_video_id
|
||||
video_component.save_with_metadata(user)
|
||||
|
||||
@@ -146,9 +145,9 @@ def validate_video_module(request, locator):
|
||||
try:
|
||||
item = _get_item(request, {'locator': locator})
|
||||
if item.category != 'video':
|
||||
error = _(u'Transcripts are supported only for "video" modules.')
|
||||
error = _('Transcripts are supported only for "video" modules.')
|
||||
except (InvalidKeyError, ItemNotFoundError):
|
||||
error = _(u'Cannot find item by locator.')
|
||||
error = _('Cannot find item by locator.')
|
||||
|
||||
return error, item
|
||||
|
||||
@@ -169,13 +168,13 @@ def validate_transcript_upload_data(request):
|
||||
video_locator = data.get('locator')
|
||||
edx_video_id = data.get('edx_video_id')
|
||||
if not video_locator:
|
||||
error = _(u'Video locator is required.')
|
||||
error = _('Video locator is required.')
|
||||
elif 'transcript-file' not in files:
|
||||
error = _(u'A transcript file is required.')
|
||||
error = _('A transcript file is required.')
|
||||
elif os.path.splitext(files['transcript-file'].name)[1][1:] != Transcript.SRT:
|
||||
error = _(u'This transcript file type is not supported.')
|
||||
error = _('This transcript file type is not supported.')
|
||||
elif 'edx_video_id' not in data:
|
||||
error = _(u'Video ID is required.')
|
||||
error = _('Video ID is required.')
|
||||
|
||||
if not error:
|
||||
error, video = validate_video_module(request, video_locator)
|
||||
@@ -209,7 +208,7 @@ def upload_transcripts(request):
|
||||
# check if we need to create an external VAL video to associate the transcript
|
||||
# and save its ID on the video component.
|
||||
if not edx_video_id:
|
||||
edx_video_id = create_external_video(display_name=u'external video')
|
||||
edx_video_id = create_external_video(display_name='external video')
|
||||
video.edx_video_id = edx_video_id
|
||||
video.save_with_metadata(request.user)
|
||||
|
||||
@@ -225,11 +224,11 @@ def upload_transcripts(request):
|
||||
).encode()
|
||||
transcript_created = create_or_update_video_transcript(
|
||||
video_id=edx_video_id,
|
||||
language_code=u'en',
|
||||
language_code='en',
|
||||
metadata={
|
||||
'provider': TranscriptProvider.CUSTOM,
|
||||
'file_format': Transcript.SJSON,
|
||||
'language_code': u'en'
|
||||
'language_code': 'en'
|
||||
},
|
||||
file_data=ContentFile(sjson_subs),
|
||||
)
|
||||
@@ -240,7 +239,7 @@ def upload_transcripts(request):
|
||||
except (TranscriptsGenerationException, UnicodeDecodeError):
|
||||
|
||||
response = JsonResponse({
|
||||
'status': _(u'There is a problem with this transcript file. Try to upload a different file.')
|
||||
'status': _('There is a problem with this transcript file. Try to upload a different file.')
|
||||
}, status=400)
|
||||
|
||||
return response
|
||||
@@ -258,13 +257,13 @@ def download_transcripts(request):
|
||||
raise Http404
|
||||
|
||||
try:
|
||||
content, filename, mimetype = get_transcript(video, lang=u'en')
|
||||
content, filename, mimetype = get_transcript(video, lang='en')
|
||||
except NotFoundError:
|
||||
raise Http404 # lint-amnesty, pylint: disable=raise-missing-from
|
||||
|
||||
# Construct an HTTP response
|
||||
response = HttpResponse(content, content_type=mimetype)
|
||||
response['Content-Disposition'] = u'attachment; filename="{filename}"'.format(filename=filename)
|
||||
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
||||
return response
|
||||
|
||||
|
||||
@@ -310,16 +309,16 @@ def check_transcripts(request): # lint-amnesty, pylint: disable=too-many-statem
|
||||
try:
|
||||
__, videos, item = _validate_transcripts_data(request)
|
||||
except TranscriptsRequestValidationException as e:
|
||||
return error_response(transcripts_presence, text_type(e))
|
||||
return error_response(transcripts_presence, str(e))
|
||||
|
||||
transcripts_presence['status'] = 'Success'
|
||||
|
||||
try:
|
||||
edx_video_id = clean_video_id(videos.get('edx_video_id'))
|
||||
get_transcript_from_val(edx_video_id=edx_video_id, lang=u'en')
|
||||
get_transcript_from_val(edx_video_id=edx_video_id, lang='en')
|
||||
command = 'found'
|
||||
except NotFoundError:
|
||||
filename = 'subs_{0}.srt.sjson'.format(item.sub)
|
||||
filename = f'subs_{item.sub}.srt.sjson'
|
||||
content_location = StaticContent.compute_location(item.location.course_key, filename)
|
||||
try:
|
||||
local_transcripts = contentstore().find(content_location).data.decode('utf-8')
|
||||
@@ -333,13 +332,13 @@ def check_transcripts(request): # lint-amnesty, pylint: disable=too-many-statem
|
||||
transcripts_presence['is_youtube_mode'] = True
|
||||
|
||||
# youtube local
|
||||
filename = 'subs_{0}.srt.sjson'.format(youtube_id)
|
||||
filename = f'subs_{youtube_id}.srt.sjson'
|
||||
content_location = StaticContent.compute_location(item.location.course_key, filename)
|
||||
try:
|
||||
local_transcripts = contentstore().find(content_location).data.decode('utf-8')
|
||||
transcripts_presence['youtube_local'] = True
|
||||
except NotFoundError:
|
||||
log.debug(u"Can't find transcripts in storage for youtube id: %s", youtube_id)
|
||||
log.debug("Can't find transcripts in storage for youtube id: %s", youtube_id)
|
||||
|
||||
# youtube server
|
||||
youtube_text_api = copy.deepcopy(settings.YOUTUBE['TEXT_API'])
|
||||
@@ -367,13 +366,13 @@ def check_transcripts(request): # lint-amnesty, pylint: disable=too-many-statem
|
||||
# Check for html5 local transcripts presence
|
||||
html5_subs = []
|
||||
for html5_id in videos['html5']:
|
||||
filename = 'subs_{0}.srt.sjson'.format(html5_id)
|
||||
filename = f'subs_{html5_id}.srt.sjson'
|
||||
content_location = StaticContent.compute_location(item.location.course_key, filename)
|
||||
try:
|
||||
html5_subs.append(contentstore().find(content_location).data)
|
||||
transcripts_presence['html5_local'].append(html5_id)
|
||||
except NotFoundError:
|
||||
log.debug(u"Can't find transcripts in storage for non-youtube video_id: %s", html5_id)
|
||||
log.debug("Can't find transcripts in storage for non-youtube video_id: %s", html5_id)
|
||||
if len(html5_subs) == 2: # check html5 transcripts for equality
|
||||
transcripts_presence['html5_equal'] = (
|
||||
json.loads(html5_subs[0].decode('utf-8')) == json.loads(html5_subs[1].decode('utf-8'))
|
||||
@@ -430,12 +429,12 @@ def _transcripts_logic(transcripts_presence, videos):
|
||||
else: # html5 source have no subtitles
|
||||
# check if item sub has subtitles
|
||||
if transcripts_presence['current_item_subs'] and not transcripts_presence['is_youtube_mode']:
|
||||
log.debug(u"Command is use existing %s subs", transcripts_presence['current_item_subs'])
|
||||
log.debug("Command is use existing %s subs", transcripts_presence['current_item_subs'])
|
||||
command = 'use_existing'
|
||||
else:
|
||||
command = 'not_found'
|
||||
log.debug(
|
||||
u"Resulted command: %s, current transcripts: %s, youtube mode: %s",
|
||||
"Resulted command: %s, current transcripts: %s, youtube mode: %s",
|
||||
command,
|
||||
transcripts_presence['current_item_subs'],
|
||||
transcripts_presence['is_youtube_mode']
|
||||
@@ -500,7 +499,7 @@ def validate_transcripts_request(request, include_yt=False, include_html5=False)
|
||||
# Loads the request data
|
||||
data = json.loads(request.GET.get('data', '{}'))
|
||||
if not data:
|
||||
error = _(u'Incoming video data is empty.')
|
||||
error = _('Incoming video data is empty.')
|
||||
else:
|
||||
error, video = validate_video_module(request, locator=data.get('locator'))
|
||||
if not error:
|
||||
@@ -546,7 +545,7 @@ def choose_transcripts(request):
|
||||
video.location,
|
||||
subs_id=chosen_html5_id,
|
||||
file_name=chosen_html5_id,
|
||||
language=u'en'
|
||||
language='en'
|
||||
)
|
||||
except NotFoundError:
|
||||
return error_response({}, _('No such transcript.'))
|
||||
@@ -555,7 +554,7 @@ def choose_transcripts(request):
|
||||
edx_video_id = link_video_to_component(video, request.user)
|
||||
|
||||
# 3. Upload the retrieved transcript to DS for the linked video ID.
|
||||
success = save_video_transcript(edx_video_id, input_format, transcript_content, language_code=u'en')
|
||||
success = save_video_transcript(edx_video_id, input_format, transcript_content, language_code='en')
|
||||
if success:
|
||||
response = JsonResponse({'edx_video_id': edx_video_id, 'status': 'Success'}, status=200)
|
||||
else:
|
||||
@@ -585,7 +584,7 @@ def rename_transcripts(request):
|
||||
video.location,
|
||||
subs_id=video.sub,
|
||||
file_name=video.sub,
|
||||
language=u'en'
|
||||
language='en'
|
||||
)
|
||||
except NotFoundError:
|
||||
return error_response({}, _('No such transcript.'))
|
||||
@@ -594,7 +593,7 @@ def rename_transcripts(request):
|
||||
edx_video_id = link_video_to_component(video, request.user)
|
||||
|
||||
# 3. Upload the retrieved transcript to DS for the linked video ID.
|
||||
success = save_video_transcript(edx_video_id, input_format, transcript_content, language_code=u'en')
|
||||
success = save_video_transcript(edx_video_id, input_format, transcript_content, language_code='en')
|
||||
if success:
|
||||
response = JsonResponse({'edx_video_id': edx_video_id, 'status': 'Success'}, status=200)
|
||||
else:
|
||||
@@ -619,20 +618,20 @@ def replace_transcripts(request):
|
||||
if error:
|
||||
response = error_response({}, error)
|
||||
elif not youtube_id:
|
||||
response = error_response({}, _(u'YouTube ID is required.'))
|
||||
response = error_response({}, _('YouTube ID is required.'))
|
||||
else:
|
||||
# 1. Download transcript from YouTube.
|
||||
try:
|
||||
video = validated_data['video']
|
||||
transcript_content = download_youtube_subs(youtube_id, video, settings)
|
||||
except GetTranscriptsFromYouTubeException as e:
|
||||
return error_response({}, text_type(e))
|
||||
return error_response({}, str(e))
|
||||
|
||||
# 2. Link a video to video component if its not already linked to one.
|
||||
edx_video_id = link_video_to_component(video, request.user)
|
||||
|
||||
# 3. Upload YT transcript to DS for the linked video ID.
|
||||
success = save_video_transcript(edx_video_id, Transcript.SJSON, transcript_content, language_code=u'en')
|
||||
success = save_video_transcript(edx_video_id, Transcript.SJSON, transcript_content, language_code='en')
|
||||
if success:
|
||||
response = JsonResponse({'edx_video_id': edx_video_id, 'status': 'Success'}, status=200)
|
||||
else:
|
||||
|
||||
@@ -117,7 +117,7 @@ def _course_team_user(request, course_key, email):
|
||||
user = User.objects.get(email=email)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
msg = {
|
||||
"error": _(u"Could not find user by email address '{email}'.").format(email=email),
|
||||
"error": _("Could not find user by email address '{email}'.").format(email=email),
|
||||
}
|
||||
return JsonResponse(msg, 404)
|
||||
|
||||
@@ -161,7 +161,7 @@ def _course_team_user(request, course_key, email):
|
||||
# can't modify an inactive user but can remove it
|
||||
if not (user.is_active or new_role is None):
|
||||
msg = {
|
||||
"error": _(u'User {email} has registered but has not yet activated their account.').format(email=email),
|
||||
"error": _('User {email} has registered but has not yet activated their account.').format(email=email),
|
||||
}
|
||||
return JsonResponse(msg, 400)
|
||||
|
||||
|
||||
@@ -12,9 +12,7 @@ from contextlib import closing
|
||||
from datetime import datetime, timedelta
|
||||
from uuid import uuid4
|
||||
|
||||
import six
|
||||
from boto import s3
|
||||
from boto.sts import STSConnection # lint-amnesty, pylint: disable=unused-import
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
@@ -23,6 +21,7 @@ from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ugettext_noop
|
||||
from django.views.decorators.http import require_GET, require_http_methods, require_POST
|
||||
from edx_toggles.toggles import LegacyWaffleFlagNamespace, LegacyWaffleSwitchNamespace
|
||||
from edxval.api import (
|
||||
SortDirection,
|
||||
VideoSortField,
|
||||
@@ -44,8 +43,8 @@ from rest_framework import status as rest_status
|
||||
from rest_framework.decorators import api_view
|
||||
from rest_framework.response import Response
|
||||
|
||||
from edx_toggles.toggles import LegacyWaffleFlagNamespace, LegacyWaffleSwitchNamespace
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_response
|
||||
from common.djangoapps.util.json_request import JsonResponse, expect_json
|
||||
from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag
|
||||
from openedx.core.djangoapps.video_pipeline.config.waffle import (
|
||||
DEPRECATE_YOUTUBE,
|
||||
@@ -54,7 +53,6 @@ from openedx.core.djangoapps.video_pipeline.config.waffle import (
|
||||
)
|
||||
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag
|
||||
from openedx.core.lib.api.view_utils import view_auth_classes
|
||||
from common.djangoapps.util.json_request import JsonResponse, expect_json
|
||||
from xmodule.video_module.transcripts_utils import Transcript
|
||||
|
||||
from ..models import VideoUploadConfig
|
||||
@@ -80,11 +78,11 @@ WAFFLE_SWITCHES = LegacyWaffleSwitchNamespace(name=WAFFLE_NAMESPACE)
|
||||
VIDEO_IMAGE_UPLOAD_ENABLED = 'video_image_upload_enabled'
|
||||
|
||||
# Waffle flag namespace for studio
|
||||
WAFFLE_STUDIO_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name=u'studio')
|
||||
WAFFLE_STUDIO_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='studio')
|
||||
|
||||
ENABLE_VIDEO_UPLOAD_PAGINATION = CourseWaffleFlag(
|
||||
waffle_namespace=WAFFLE_STUDIO_FLAG_NAMESPACE,
|
||||
flag_name=u'enable_video_upload_pagination',
|
||||
flag_name='enable_video_upload_pagination',
|
||||
module_name=__name__,
|
||||
)
|
||||
# Default expiration, in seconds, of one-time URLs used for uploading videos.
|
||||
@@ -103,7 +101,7 @@ MAX_UPLOAD_HOURS = 24
|
||||
VIDEOS_PER_PAGE = 100
|
||||
|
||||
|
||||
class TranscriptProvider(object):
|
||||
class TranscriptProvider:
|
||||
"""
|
||||
Transcription Provider Enumeration
|
||||
"""
|
||||
@@ -112,7 +110,7 @@ class TranscriptProvider(object):
|
||||
CUSTOM = 'Custom'
|
||||
|
||||
|
||||
class StatusDisplayStrings(object):
|
||||
class StatusDisplayStrings:
|
||||
"""
|
||||
A class to map status strings as stored in VAL to display strings for the
|
||||
video upload page
|
||||
@@ -251,7 +249,7 @@ def video_images_handler(request, course_key_string, edx_video_id=None):
|
||||
return HttpResponseNotFound()
|
||||
|
||||
if 'file' not in request.FILES:
|
||||
return JsonResponse({'error': _(u'An image file is required.')}, status=400)
|
||||
return JsonResponse({'error': _('An image file is required.')}, status=400)
|
||||
|
||||
image_file = request.FILES['file']
|
||||
error = validate_video_image(image_file)
|
||||
@@ -261,7 +259,7 @@ def video_images_handler(request, course_key_string, edx_video_id=None):
|
||||
with closing(image_file):
|
||||
image_url = update_video_image(edx_video_id, course_key_string, image_file, image_file.name)
|
||||
LOGGER.info(
|
||||
u'VIDEOS: Video image uploaded for edx_video_id [%s] in course [%s]', edx_video_id, course_key_string
|
||||
'VIDEOS: Video image uploaded for edx_video_id [%s] in course [%s]', edx_video_id, course_key_string
|
||||
)
|
||||
|
||||
return JsonResponse({'image_url': image_url})
|
||||
@@ -297,17 +295,17 @@ def validate_transcript_preferences(provider, cielo24_fidelity, cielo24_turnarou
|
||||
|
||||
# Validate transcription turnaround
|
||||
if cielo24_turnaround not in transcription_plans[provider]['turnaround']:
|
||||
error = u'Invalid cielo24 turnaround {}.'.format(cielo24_turnaround)
|
||||
error = f'Invalid cielo24 turnaround {cielo24_turnaround}.'
|
||||
return error, preferences
|
||||
|
||||
# Validate transcription languages
|
||||
supported_languages = transcription_plans[provider]['fidelity'][cielo24_fidelity]['languages']
|
||||
if video_source_language not in supported_languages:
|
||||
error = u'Unsupported source language {}.'.format(video_source_language)
|
||||
error = f'Unsupported source language {video_source_language}.'
|
||||
return error, preferences
|
||||
|
||||
if not preferred_languages or not set(preferred_languages) <= set(supported_languages.keys()):
|
||||
error = 'Invalid languages {}.'.format(preferred_languages)
|
||||
error = f'Invalid languages {preferred_languages}.'
|
||||
return error, preferences
|
||||
|
||||
# Validated Cielo24 preferences
|
||||
@@ -318,23 +316,23 @@ def validate_transcript_preferences(provider, cielo24_fidelity, cielo24_turnarou
|
||||
'preferred_languages': preferred_languages,
|
||||
}
|
||||
else:
|
||||
error = u'Invalid cielo24 fidelity {}.'.format(cielo24_fidelity)
|
||||
error = f'Invalid cielo24 fidelity {cielo24_fidelity}.'
|
||||
elif provider == TranscriptProvider.THREE_PLAY_MEDIA:
|
||||
|
||||
# Validate transcription turnaround
|
||||
if three_play_turnaround not in transcription_plans[provider]['turnaround']:
|
||||
error = u'Invalid 3play turnaround {}.'.format(three_play_turnaround)
|
||||
error = f'Invalid 3play turnaround {three_play_turnaround}.'
|
||||
return error, preferences
|
||||
|
||||
# Validate transcription languages
|
||||
valid_translations_map = transcription_plans[provider]['translations']
|
||||
if video_source_language not in list(valid_translations_map.keys()):
|
||||
error = u'Unsupported source language {}.'.format(video_source_language)
|
||||
error = f'Unsupported source language {video_source_language}.'
|
||||
return error, preferences
|
||||
|
||||
valid_target_languages = valid_translations_map[video_source_language]
|
||||
if not preferred_languages or not set(preferred_languages) <= set(valid_target_languages):
|
||||
error = u'Invalid languages {}.'.format(preferred_languages)
|
||||
error = f'Invalid languages {preferred_languages}.'
|
||||
return error, preferences
|
||||
|
||||
# Validated 3PlayMedia preferences
|
||||
@@ -344,7 +342,7 @@ def validate_transcript_preferences(provider, cielo24_fidelity, cielo24_turnarou
|
||||
'preferred_languages': preferred_languages,
|
||||
}
|
||||
else:
|
||||
error = u'Invalid provider {}.'.format(provider)
|
||||
error = f'Invalid provider {provider}.'
|
||||
|
||||
return error, preferences
|
||||
|
||||
@@ -410,7 +408,7 @@ def video_encodings_download(request, course_key_string):
|
||||
# Translators: This is the header for a CSV file column
|
||||
# containing URLs for video encodings for the named profile
|
||||
# (e.g. desktop, mobile high quality, mobile low quality)
|
||||
return _(u"{profile_name} URL").format(profile_name=profile)
|
||||
return _("{profile_name} URL").format(profile_name=profile)
|
||||
|
||||
profile_whitelist = VideoUploadConfig.get_profile_whitelist()
|
||||
videos, __ = _get_videos(course)
|
||||
@@ -447,10 +445,7 @@ def video_encodings_download(request, course_key_string):
|
||||
if encoded_video["profile"] in profile_whitelist
|
||||
]
|
||||
)
|
||||
return {
|
||||
key.encode("utf-8") if six.PY2 else key: value.encode("utf-8") if six.PY2 else value
|
||||
for key, value in ret.items()
|
||||
}
|
||||
return dict(ret.items())
|
||||
|
||||
# Write csv to bytes-like object. We need a separate writer and buffer as the csv
|
||||
# writer writes str and the FileResponse expects a bytes files.
|
||||
@@ -458,11 +453,7 @@ def video_encodings_download(request, course_key_string):
|
||||
buffer_writer = codecs.getwriter("utf-8")(buffer)
|
||||
writer = csv.DictWriter(
|
||||
buffer_writer,
|
||||
[
|
||||
col_name.encode("utf-8") if six.PY2 else col_name
|
||||
for col_name
|
||||
in [name_col, duration_col, added_col, video_id_col, status_col] + profile_cols
|
||||
],
|
||||
[name_col, duration_col, added_col, video_id_col, status_col] + profile_cols,
|
||||
dialect=csv.excel
|
||||
)
|
||||
writer.writeheader()
|
||||
@@ -511,7 +502,7 @@ def convert_video_status(video, is_video_encodes_ready=False):
|
||||
if video['status'] == 'upload' and (now - video['created']) > timedelta(hours=MAX_UPLOAD_HOURS):
|
||||
new_status = 'upload_failed'
|
||||
status = StatusDisplayStrings.get(new_status)
|
||||
message = u'Video with id [%s] is still in upload after [%s] hours, setting status to [%s]' % (
|
||||
message = 'Video with id [{}] is still in upload after [{}] hours, setting status to [{}]'.format(
|
||||
video['edx_video_id'], MAX_UPLOAD_HOURS, new_status
|
||||
)
|
||||
send_video_status_update([
|
||||
@@ -536,7 +527,7 @@ def _get_videos(course, pagination_conf=None):
|
||||
Retrieves the list of videos from VAL corresponding to this course.
|
||||
"""
|
||||
videos, pagination_context = get_videos_for_course(
|
||||
six.text_type(course.id),
|
||||
str(course.id),
|
||||
VideoSortField.created,
|
||||
SortDirection.desc,
|
||||
pagination_conf
|
||||
@@ -576,7 +567,7 @@ def _get_index_videos(course, pagination_conf=None):
|
||||
"""
|
||||
Returns the information about each video upload required for the video list
|
||||
"""
|
||||
course_id = six.text_type(course.id)
|
||||
course_id = str(course.id)
|
||||
attrs = [
|
||||
'edx_video_id', 'client_video_id', 'created', 'duration',
|
||||
'status', 'courses', 'transcripts', 'transcription_status',
|
||||
@@ -618,7 +609,7 @@ def get_all_transcript_languages():
|
||||
all_languages_dict = dict(settings.ALL_LANGUAGES, **third_party_transcription_languages)
|
||||
# Return combined system settings and 3rd party transcript languages.
|
||||
all_languages = []
|
||||
for key, value in sorted(six.iteritems(all_languages_dict), key=lambda k_v: k_v[1]):
|
||||
for key, value in sorted(all_languages_dict.items(), key=lambda k_v: k_v[1]):
|
||||
all_languages.append({
|
||||
'language_code': key,
|
||||
'language_text': value
|
||||
@@ -634,9 +625,9 @@ def videos_index_html(course, pagination_conf=None):
|
||||
previous_uploads, pagination_context = _get_index_videos(course, pagination_conf)
|
||||
context = {
|
||||
'context_course': course,
|
||||
'image_upload_url': reverse_course_url('video_images_handler', six.text_type(course.id)),
|
||||
'video_handler_url': reverse_course_url('videos_handler', six.text_type(course.id)),
|
||||
'encodings_download_url': reverse_course_url('video_encodings_download', six.text_type(course.id)),
|
||||
'image_upload_url': reverse_course_url('video_images_handler', str(course.id)),
|
||||
'video_handler_url': reverse_course_url('videos_handler', str(course.id)),
|
||||
'encodings_download_url': reverse_course_url('video_encodings_download', str(course.id)),
|
||||
'default_video_image_url': _get_default_video_image_url(),
|
||||
'previous_uploads': previous_uploads,
|
||||
'concurrent_upload_limit': settings.VIDEO_UPLOAD_PIPELINE.get('CONCURRENT_UPLOAD_LIMIT', 0),
|
||||
@@ -657,7 +648,7 @@ def videos_index_html(course, pagination_conf=None):
|
||||
'video_transcript_settings': {
|
||||
'transcript_download_handler_url': reverse('transcript_download_handler'),
|
||||
'transcript_upload_handler_url': reverse('transcript_upload_handler'),
|
||||
'transcript_delete_handler_url': reverse_course_url('transcript_delete_handler', six.text_type(course.id)),
|
||||
'transcript_delete_handler_url': reverse_course_url('transcript_delete_handler', str(course.id)),
|
||||
'trancript_download_file_format': Transcript.SRT
|
||||
},
|
||||
'pagination_context': pagination_context
|
||||
@@ -667,15 +658,15 @@ def videos_index_html(course, pagination_conf=None):
|
||||
context['video_transcript_settings'].update({
|
||||
'transcript_preferences_handler_url': reverse_course_url(
|
||||
'transcript_preferences_handler',
|
||||
six.text_type(course.id)
|
||||
str(course.id)
|
||||
),
|
||||
'transcript_credentials_handler_url': reverse_course_url(
|
||||
'transcript_credentials_handler',
|
||||
six.text_type(course.id)
|
||||
str(course.id)
|
||||
),
|
||||
'transcription_plans': get_3rd_party_transcription_plans(),
|
||||
})
|
||||
context['active_transcript_preferences'] = get_transcript_preferences(six.text_type(course.id))
|
||||
context['active_transcript_preferences'] = get_transcript_preferences(str(course.id))
|
||||
# Cached state for transcript providers' credentials (org-specific)
|
||||
context['transcript_credentials'] = get_transcript_credentials_state_for_org(course.id.org)
|
||||
|
||||
@@ -748,15 +739,15 @@ def videos_post(course, request):
|
||||
try:
|
||||
file_name.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
error_msg = u'The file name for %s must contain only ASCII characters.' % file_name
|
||||
error_msg = 'The file name for %s must contain only ASCII characters.' % file_name
|
||||
return {'error': error_msg}, 400
|
||||
|
||||
edx_video_id = six.text_type(uuid4())
|
||||
edx_video_id = str(uuid4())
|
||||
key = storage_service_key(bucket, file_name=edx_video_id)
|
||||
|
||||
metadata_list = [
|
||||
('client_video_id', file_name),
|
||||
('course_key', six.text_type(course.id)),
|
||||
('course_key', str(course.id)),
|
||||
]
|
||||
|
||||
deprecate_youtube = waffle_flags()[DEPRECATE_YOUTUBE]
|
||||
@@ -769,7 +760,7 @@ def videos_post(course, request):
|
||||
|
||||
is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled(course.id)
|
||||
if is_video_transcript_enabled:
|
||||
transcript_preferences = get_transcript_preferences(six.text_type(course.id))
|
||||
transcript_preferences = get_transcript_preferences(str(course.id))
|
||||
if transcript_preferences is not None:
|
||||
metadata_list.append(('transcript_preferences', json.dumps(transcript_preferences)))
|
||||
|
||||
@@ -788,7 +779,7 @@ def videos_post(course, request):
|
||||
'client_video_id': file_name,
|
||||
'duration': 0,
|
||||
'encoded_videos': [],
|
||||
'courses': [six.text_type(course.id)]
|
||||
'courses': [str(course.id)]
|
||||
})
|
||||
|
||||
resp_files.append({'file_name': file_name, 'upload_url': upload_url, 'edx_video_id': edx_video_id})
|
||||
@@ -840,7 +831,7 @@ def send_video_status_update(updates):
|
||||
for update in updates:
|
||||
update_video_status(update.get('edxVideoId'), update.get('status'))
|
||||
LOGGER.info(
|
||||
u'VIDEOS: Video status update with id [%s], status [%s] and message [%s]',
|
||||
'VIDEOS: Video status update with id [%s], status [%s] and message [%s]',
|
||||
update.get('edxVideoId'),
|
||||
update.get('status'),
|
||||
update.get('message')
|
||||
@@ -880,7 +871,7 @@ def _update_pagination_context(request):
|
||||
"""
|
||||
Updates session with posted value
|
||||
"""
|
||||
error_msg = _(u'A non zero positive integer is expected')
|
||||
error_msg = _('A non zero positive integer is expected')
|
||||
try:
|
||||
videos_per_page = int(request.POST.get('value'))
|
||||
if videos_per_page <= 0:
|
||||
|
||||
Reference in New Issue
Block a user