diff --git a/.coveragerc b/.coveragerc
index fab115cb37..596d280140 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,6 +1,6 @@
# .coveragerc for edx-platform
[run]
-data_file = reports/.coverage
+data_file = reports/${TEST_SUITE}.coverage
source =
cms
common/djangoapps
@@ -49,4 +49,4 @@ output = reports/coverage.xml
jenkins_source =
/home/jenkins/workspace/$JOB_NAME
/home/jenkins/workspace/$SUBSET_JOB
- /edx/app/edxapp/edx-platform
+ /home/jenkins/edx-platform
diff --git a/.github/renovate.json b/.github/renovate.json
index 327fbf5f3d..e27406fabe 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -4,7 +4,7 @@
"schedule:weekdays",
":preserveSemverRanges"
],
- "prConcurrentLimit": 2,
+ "prConcurrentLimit": 5,
"includePaths": [
"package.json"
]
diff --git a/.gitignore b/.gitignore
index 26775845b4..eb0bc8d037 100644
--- a/.gitignore
+++ b/.gitignore
@@ -143,10 +143,3 @@ dist
# Locally generated PII reports
pii_report
-
-# Local documentation builds
-docs/_build
-docs/cms
-docs/common
-docs/lms
-docs/openedx
diff --git a/Makefile b/Makefile
index c9fb8b2987..92d761a8da 100644
--- a/Makefile
+++ b/Makefile
@@ -19,8 +19,7 @@ clean: ## archive and delete most git-ignored files
rm $(PRIVATE_FILES)
docs: ## build the developer documentation for this repository
- rm -rf docs/_build docs/cms docs/common docs/lms docs/openedx
- cd docs; make html
+ cd docs/guides; make clean html
extract_translations: ## extract localizable strings from sources
i18n_tool extract -v
diff --git a/cms/djangoapps/api/v1/serializers/course_runs.py b/cms/djangoapps/api/v1/serializers/course_runs.py
index 44f722e79e..90de622bac 100644
--- a/cms/djangoapps/api/v1/serializers/course_runs.py
+++ b/cms/djangoapps/api/v1/serializers/course_runs.py
@@ -1,10 +1,14 @@
""" Course run serializers. """
+from __future__ import absolute_import
+
import logging
-import time
+import time # pylint: disable=unused-import
+
import six
from django.contrib.auth import get_user_model
from django.db import transaction
from django.utils.translation import ugettext_lazy as _
+from opaque_keys import InvalidKeyError
from rest_framework import serializers
from rest_framework.fields import empty
@@ -82,7 +86,7 @@ class CourseRunTeamSerializerMixin(serializers.Serializer):
def image_is_jpeg_or_png(value):
content_type = value.content_type
- if content_type not in IMAGE_TYPES.keys():
+ if content_type not in list(IMAGE_TYPES.keys()):
raise serializers.ValidationError(
u'Only JPEG and PNG image types are supported. {} is not valid'.format(content_type))
@@ -125,7 +129,7 @@ class CourseRunImageSerializer(serializers.Serializer):
class CourseRunSerializerCommonFieldsMixin(serializers.Serializer):
schedule = CourseRunScheduleSerializer(source='*', required=False)
pacing_type = CourseRunPacingTypeField(source='self_paced', required=False,
- choices=(('instructor_paced', False), ('self_paced', True),))
+ choices=((False, 'instructor_paced'), (True, 'self_paced'),))
class CourseRunSerializer(CourseRunSerializerCommonFieldsMixin, CourseRunTeamSerializerMixin, serializers.Serializer):
@@ -165,29 +169,40 @@ class CourseRunCreateSerializer(CourseRunSerializer):
class CourseRunRerunSerializer(CourseRunSerializerCommonFieldsMixin, CourseRunTeamSerializerMixin,
serializers.Serializer):
title = serializers.CharField(source='display_name', required=False)
+ number = serializers.CharField(source='id.course', required=False)
run = serializers.CharField(source='id.run')
- def validate_run(self, value):
+ def validate(self, attrs):
course_run_key = self.instance.id
+ _id = attrs.get('id')
+ number = _id.get('course', course_run_key.course)
+ run = _id['run']
store = modulestore()
- with store.default_store('split'):
- new_course_run_key = store.make_course_key(course_run_key.org, course_run_key.course, value)
+ try:
+ with store.default_store('split'):
+ new_course_run_key = store.make_course_key(course_run_key.org, number, run)
+ except InvalidKeyError:
+ raise serializers.ValidationError(
+ u'Invalid key supplied. Ensure there are no special characters in the Course Number.'
+ )
if store.has_course(new_course_run_key, ignore_case=True):
- raise serializers.ValidationError(u'Course run {key} already exists'.format(key=new_course_run_key))
- return value
+ raise serializers.ValidationError(
+ {'run': u'Course run {key} already exists'.format(key=new_course_run_key)}
+ )
+ return attrs
def update(self, instance, validated_data):
course_run_key = instance.id
_id = validated_data.pop('id')
+ number = _id.get('course', course_run_key.course)
+ run = _id['run']
team = validated_data.pop('team', [])
user = self.context['request'].user
fields = {
'display_name': instance.display_name
}
fields.update(validated_data)
- new_course_run_key = rerun_course(
- user, course_run_key, course_run_key.org, course_run_key.course, _id['run'], fields, False
- )
+ new_course_run_key = rerun_course(user, course_run_key, course_run_key.org, number, run, fields, False)
course_run = get_course_and_check_access(new_course_run_key, user)
self.update_team(course_run, team)
diff --git a/cms/djangoapps/api/v1/tests/test_serializers/test_course_runs.py b/cms/djangoapps/api/v1/tests/test_serializers/test_course_runs.py
index 39ac9c0043..d73595674f 100644
--- a/cms/djangoapps/api/v1/tests/test_serializers/test_course_runs.py
+++ b/cms/djangoapps/api/v1/tests/test_serializers/test_course_runs.py
@@ -1,16 +1,21 @@
+"""Tests for course run serializers"""
+
+from __future__ import absolute_import
+
import datetime
import ddt
import pytz
from django.test import RequestFactory
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
-from xmodule.modulestore.tests.factories import CourseFactory
from openedx.core.lib.courses import course_image_url
from student.roles import CourseInstructorRole, CourseStaffRole
from student.tests.factories import UserFactory
-from ..utils import serialize_datetime
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
+from xmodule.modulestore.tests.factories import CourseFactory
+
from ...serializers.course_runs import CourseRunSerializer
+from ..utils import serialize_datetime
@ddt.ddt
diff --git a/cms/djangoapps/api/v1/tests/test_views/test_course_runs.py b/cms/djangoapps/api/v1/tests/test_views/test_course_runs.py
index d14301eff8..fd58859526 100644
--- a/cms/djangoapps/api/v1/tests/test_views/test_course_runs.py
+++ b/cms/djangoapps/api/v1/tests/test_views/test_course_runs.py
@@ -1,13 +1,22 @@
+"""Tests for Course run views"""
+
+from __future__ import absolute_import
+
import datetime
import ddt
import pytz
from django.core.files.uploadedfile import SimpleUploadedFile
-from django.urls import reverse
from django.test import RequestFactory
+from django.urls import reverse
+from mock import patch
from opaque_keys.edx.keys import CourseKey
-from openedx.core.lib.courses import course_image_url
from rest_framework.test import APIClient
+
+from openedx.core.lib.courses import course_image_url
+from student.models import CourseAccessRole
+from student.tests.factories import TEST_PASSWORD, AdminFactory, UserFactory
+from util.organizations_helpers import add_organization, get_course_organizations
from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.django import contentstore
from xmodule.exceptions import NotFoundError
@@ -15,10 +24,8 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ToyCourseFactory
-from student.models import CourseAccessRole
-from student.tests.factories import AdminFactory, TEST_PASSWORD, UserFactory
-from ..utils import serialize_datetime
from ...serializers.course_runs import CourseRunSerializer
+from ..utils import serialize_datetime
@ddt.ddt
@@ -315,19 +322,25 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
# There should now be an image stored
contentstore().find(content_key)
+ @patch.dict('django.conf.settings.FEATURES', {'ORGANIZATIONS_APP': True})
@ddt.data(
- ('instructor_paced', False),
- ('self_paced', True),
+ ('instructor_paced', False, 'NotOriginalNumber1x'),
+ ('self_paced', True, None),
)
@ddt.unpack
- def test_rerun(self, pacing_type, expected_self_paced_value):
- course_run = ToyCourseFactory()
+ def test_rerun(self, pacing_type, expected_self_paced_value, number):
+ original_course_run = ToyCourseFactory()
+ add_organization({
+ 'name': 'Test Organization',
+ 'short_name': original_course_run.id.org,
+ 'description': 'Testing Organization Description',
+ })
start = datetime.datetime.now(pytz.UTC).replace(microsecond=0)
end = start + datetime.timedelta(days=30)
user = UserFactory()
role = 'instructor'
run = '3T2017'
- url = reverse('api:v1:course_run-rerun', kwargs={'pk': str(course_run.id)})
+ url = reverse('api:v1:course_run-rerun', kwargs={'pk': str(original_course_run.id)})
data = {
'run': run,
'schedule': {
@@ -342,16 +355,31 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
],
'pacing_type': pacing_type,
}
+ # If number is supplied, this should become the course number used in the course run key
+ # If not, it should default to the original course run number that the rerun is based on.
+ if number:
+ data.update({'number': number})
response = self.client.post(url, data, format='json')
assert response.status_code == 201
course_run_key = CourseKey.from_string(response.data['id'])
course_run = modulestore().get_course(course_run_key)
+
assert course_run.id.run == run
assert course_run.self_paced is expected_self_paced_value
+
+ if number:
+ assert course_run.id.course == number
+ assert course_run.id.course != original_course_run.id.course
+ else:
+ assert course_run.id.course == original_course_run.id.course
+
self.assert_course_run_schedule(course_run, start, end)
self.assert_access_role(course_run, user, role)
self.assert_course_access_role_count(course_run, 1)
+ course_orgs = get_course_organizations(course_run_key)
+ self.assertEqual(len(course_orgs), 1)
+ self.assertEqual(course_orgs[0]['short_name'], original_course_run.id.org)
def test_rerun_duplicate_run(self):
course_run = ToyCourseFactory()
@@ -362,3 +390,16 @@ class CourseRunViewSetTests(ModuleStoreTestCase):
response = self.client.post(url, data, format='json')
assert response.status_code == 400
assert response.data == {'run': [u'Course run {key} already exists'.format(key=course_run.id)]}
+
+ def test_rerun_invalid_number(self):
+ course_run = ToyCourseFactory()
+ url = reverse('api:v1:course_run-rerun', kwargs={'pk': str(course_run.id)})
+ data = {
+ 'run': '2T2019',
+ 'number': '!@#$%^&*()',
+ }
+ response = self.client.post(url, data, format='json')
+ assert response.status_code == 400
+ assert response.data == {'non_field_errors': [
+ u'Invalid key supplied. Ensure there are no special characters in the Course Number.'
+ ]}
diff --git a/cms/djangoapps/cms_user_tasks/apps.py b/cms/djangoapps/cms_user_tasks/apps.py
index ae496cb14a..95edb59d8c 100644
--- a/cms/djangoapps/cms_user_tasks/apps.py
+++ b/cms/djangoapps/cms_user_tasks/apps.py
@@ -3,6 +3,8 @@ CMS user tasks application configuration
Signal handlers are connected here.
"""
+from __future__ import absolute_import
+
from django.apps import AppConfig
diff --git a/cms/djangoapps/cms_user_tasks/tasks.py b/cms/djangoapps/cms_user_tasks/tasks.py
index 01a86bb048..bd7fe85819 100644
--- a/cms/djangoapps/cms_user_tasks/tasks.py
+++ b/cms/djangoapps/cms_user_tasks/tasks.py
@@ -2,6 +2,8 @@
Celery tasks used by cms_user_tasks
"""
+from __future__ import absolute_import
+
from boto.exception import NoAuthHandlerFound
from celery.exceptions import MaxRetriesExceededError
from celery.task import task
diff --git a/cms/djangoapps/contentstore/admin.py b/cms/djangoapps/contentstore/admin.py
index 2a73566ee3..54adb440b8 100644
--- a/cms/djangoapps/contentstore/admin.py
+++ b/cms/djangoapps/contentstore/admin.py
@@ -2,11 +2,11 @@
Admin site bindings for contentstore
"""
+from __future__ import absolute_import
+
from config_models.admin import ConfigurationModelAdmin
from django.contrib import admin
-from contentstore.models import PushNotificationConfig, VideoUploadConfig
-
+from contentstore.models import VideoUploadConfig
admin.site.register(VideoUploadConfig, ConfigurationModelAdmin)
-admin.site.register(PushNotificationConfig, ConfigurationModelAdmin)
diff --git a/cms/djangoapps/contentstore/api/tests/base.py b/cms/djangoapps/contentstore/api/tests/base.py
index d8bd02ee17..619b0ad502 100644
--- a/cms/djangoapps/contentstore/api/tests/base.py
+++ b/cms/djangoapps/contentstore/api/tests/base.py
@@ -1,6 +1,8 @@
"""
Base test case for the course API views.
"""
+from __future__ import absolute_import
+
from django.core.urlresolvers import reverse
from rest_framework.test import APITestCase
diff --git a/cms/djangoapps/contentstore/api/tests/test_import.py b/cms/djangoapps/contentstore/api/tests/test_import.py
index 5c9f3f34b6..6df2efb690 100644
--- a/cms/djangoapps/contentstore/api/tests/test_import.py
+++ b/cms/djangoapps/contentstore/api/tests/test_import.py
@@ -1,6 +1,8 @@
"""
Tests for the course import API views
"""
+from __future__ import absolute_import
+
import os
import tarfile
import tempfile
@@ -9,10 +11,10 @@ from django.urls import reverse
from path import Path as path
from rest_framework import status
from rest_framework.test import APITestCase
+from user_tasks.models import UserTaskStatus
from lms.djangoapps.courseware.tests.factories import StaffFactory
from student.tests.factories import UserFactory
-from user_tasks.models import UserTaskStatus
from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
diff --git a/cms/djangoapps/contentstore/api/tests/test_quality.py b/cms/djangoapps/contentstore/api/tests/test_quality.py
index 14249ffaa9..e6c9898d4f 100644
--- a/cms/djangoapps/contentstore/api/tests/test_quality.py
+++ b/cms/djangoapps/contentstore/api/tests/test_quality.py
@@ -1,6 +1,8 @@
"""
Tests for the course import API views
"""
+from __future__ import absolute_import
+
from rest_framework import status
from .base import BaseCourseViewTest
diff --git a/cms/djangoapps/contentstore/api/tests/test_validation.py b/cms/djangoapps/contentstore/api/tests/test_validation.py
index ecbd6cbffe..8330db9855 100644
--- a/cms/djangoapps/contentstore/api/tests/test_validation.py
+++ b/cms/djangoapps/contentstore/api/tests/test_validation.py
@@ -1,7 +1,10 @@
"""
Tests for the course import API views
"""
+from __future__ import absolute_import
+
from datetime import datetime
+
from django.core.urlresolvers import reverse
from rest_framework import status
from rest_framework.test import APITestCase
diff --git a/cms/djangoapps/contentstore/api/urls.py b/cms/djangoapps/contentstore/api/urls.py
index e5bdc40d4e..20357254fc 100644
--- a/cms/djangoapps/contentstore/api/urls.py
+++ b/cms/djangoapps/contentstore/api/urls.py
@@ -1,10 +1,11 @@
""" Course API URLs. """
+from __future__ import absolute_import
+
from django.conf import settings
from django.conf.urls import url
from cms.djangoapps.contentstore.api.views import course_import, course_quality, course_validation
-
app_name = 'contentstore'
urlpatterns = [
diff --git a/cms/djangoapps/contentstore/api/views/course_import.py b/cms/djangoapps/contentstore/api/views/course_import.py
index d13f17a241..239bc27694 100644
--- a/cms/djangoapps/contentstore/api/views/course_import.py
+++ b/cms/djangoapps/contentstore/api/views/course_import.py
@@ -1,20 +1,20 @@
"""
APIs related to Course Import.
"""
+from __future__ import absolute_import
+
import base64
import logging
import os
-from path import Path as path
-from six import text_type
-
from django.conf import settings
-
from django.core.files import File
+from path import Path as path
from rest_framework import status
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
+from six import text_type
from user_tasks.models import UserTaskStatus
from contentstore.storage import course_import_export_storage
@@ -23,7 +23,6 @@ from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_c
from .utils import course_author_access_required
-
log = logging.getLogger(__name__)
diff --git a/cms/djangoapps/contentstore/api/views/course_quality.py b/cms/djangoapps/contentstore/api/views/course_quality.py
index c3921aaca3..e90b948f06 100644
--- a/cms/djangoapps/contentstore/api/views/course_quality.py
+++ b/cms/djangoapps/contentstore/api/views/course_quality.py
@@ -1,19 +1,23 @@
# pylint: disable=missing-docstring
+from __future__ import absolute_import
+
import logging
import time
+
import numpy as np
-from scipy import stats
+import six
+from edxval.api import get_videos_for_course
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
+from scipy import stats
from contentstore.views.item import highlights_setting
-from edxval.api import get_videos_for_course
-from openedx.core.lib.cache_utils import request_cached
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes
+from openedx.core.lib.cache_utils import request_cached
from openedx.core.lib.graph_traversals import traverse_pre_order
from xmodule.modulestore.django import modulestore
-from .utils import get_bool_param, course_author_access_required
+from .utils import course_author_access_required, get_bool_param
log = logging.getLogger(__name__)
@@ -153,25 +157,25 @@ class CourseQualityView(DeveloperErrorViewMixin, GenericAPIView):
def _subsections_quality(self, course, request):
subsection_unit_dict = self._get_subsections_and_units(course, request)
num_block_types_per_subsection_dict = {}
- for subsection_key, unit_dict in subsection_unit_dict.iteritems():
+ for subsection_key, unit_dict in six.iteritems(subsection_unit_dict):
leaf_block_types_in_subsection = (
unit_info['leaf_block_types']
- for unit_info in unit_dict.itervalues()
+ for unit_info in six.itervalues(unit_dict)
)
num_block_types_per_subsection_dict[subsection_key] = len(set().union(*leaf_block_types_in_subsection))
return dict(
total_visible=len(num_block_types_per_subsection_dict),
- num_with_one_block_type=list(num_block_types_per_subsection_dict.itervalues()).count(1),
- num_block_types=self._stats_dict(list(num_block_types_per_subsection_dict.itervalues())),
+ num_with_one_block_type=list(six.itervalues(num_block_types_per_subsection_dict)).count(1),
+ num_block_types=self._stats_dict(list(six.itervalues(num_block_types_per_subsection_dict))),
)
def _units_quality(self, course, request):
subsection_unit_dict = self._get_subsections_and_units(course, request)
num_leaf_blocks_per_unit = [
unit_info['num_leaf_blocks']
- for unit_dict in subsection_unit_dict.itervalues()
- for unit_info in unit_dict.itervalues()
+ for unit_dict in six.itervalues(subsection_unit_dict)
+ for unit_info in six.itervalues(unit_dict)
]
return dict(
total_visible=len(num_leaf_blocks_per_unit),
diff --git a/cms/djangoapps/contentstore/api/views/course_validation.py b/cms/djangoapps/contentstore/api/views/course_validation.py
index a05704c0ad..5594440142 100644
--- a/cms/djangoapps/contentstore/api/views/course_validation.py
+++ b/cms/djangoapps/contentstore/api/views/course_validation.py
@@ -1,10 +1,13 @@
# pylint: disable=missing-docstring
+from __future__ import absolute_import
+
import logging
-from rest_framework.generics import GenericAPIView
-from rest_framework.response import Response
import dateutil
+import six
from pytz import UTC
+from rest_framework.generics import GenericAPIView
+from rest_framework.response import Response
from contentstore.course_info_model import get_course_updates
from contentstore.views.certificates import CertificateManager
@@ -12,8 +15,7 @@ from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_c
from xmodule.course_metadata_utils import DEFAULT_GRADING_POLICY
from xmodule.modulestore.django import modulestore
-from .utils import get_bool_param, course_author_access_required
-
+from .utils import course_author_access_required, get_bool_param
log = logging.getLogger(__name__)
@@ -118,7 +120,7 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView):
]
assignments_with_dates_before_start = (
[
- {'id': unicode(a.location), 'display_name': a.display_name}
+ {'id': six.text_type(a.location), 'display_name': a.display_name}
for a in assignments_with_dates
if a.due < course.start
]
@@ -128,7 +130,7 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView):
assignments_with_dates_after_end = (
[
- {'id': unicode(a.location), 'display_name': a.display_name}
+ {'id': six.text_type(a.location), 'display_name': a.display_name}
for a in assignments_with_dates
if a.due > course.end
]
@@ -144,7 +146,7 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView):
]
assignments_with_dates_before_start = (
[
- {'id': unicode(a.location), 'display_name': a.display_name}
+ {'id': six.text_type(a.location), 'display_name': a.display_name}
for a in assignments_with_dates
if a.due < course.start
]
@@ -154,7 +156,7 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView):
assignments_with_dates_after_end = (
[
- {'id': unicode(a.location), 'display_name': a.display_name}
+ {'id': six.text_type(a.location), 'display_name': a.display_name}
for a in assignments_with_dates
if a.due > course.end
]
@@ -175,14 +177,14 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView):
parent_unit = modulestore().get_item(ora.parent)
parent_assignment = modulestore().get_item(parent_unit.parent)
assignments_with_ora_dates_before_start.append({
- 'id': unicode(parent_assignment.location),
+ 'id': six.text_type(parent_assignment.location),
'display_name': parent_assignment.display_name
})
if course.end and self._has_date_after_end(ora, course.end):
parent_unit = modulestore().get_item(ora.parent)
parent_assignment = modulestore().get_item(parent_unit.parent)
assignments_with_ora_dates_after_end.append({
- 'id': unicode(parent_assignment.location),
+ 'id': six.text_type(parent_assignment.location),
'display_name': parent_assignment.display_name
})
diff --git a/cms/djangoapps/contentstore/api/views/utils.py b/cms/djangoapps/contentstore/api/views/utils.py
index bbf0534bc3..7ae2fb456c 100644
--- a/cms/djangoapps/contentstore/api/views/utils.py
+++ b/cms/djangoapps/contentstore/api/views/utils.py
@@ -1,11 +1,13 @@
"""
Common utilities for Contentstore APIs.
"""
+from __future__ import absolute_import
+
from contextlib import contextmanager
+from opaque_keys.edx.keys import CourseKey
from rest_framework import status
from rest_framework.generics import GenericAPIView
-from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.util.forms import to_bool
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes
diff --git a/cms/djangoapps/contentstore/apps.py b/cms/djangoapps/contentstore/apps.py
index 770ce3a9a0..65388470ec 100644
--- a/cms/djangoapps/contentstore/apps.py
+++ b/cms/djangoapps/contentstore/apps.py
@@ -4,6 +4,8 @@ Contentstore Application Configuration
Above-modulestore level signal handlers are connected here.
"""
+from __future__ import absolute_import
+
from django.apps import AppConfig
diff --git a/cms/djangoapps/contentstore/config/waffle.py b/cms/djangoapps/contentstore/config/waffle.py
index 6457e77b03..3fb8bb0979 100644
--- a/cms/djangoapps/contentstore/config/waffle.py
+++ b/cms/djangoapps/contentstore/config/waffle.py
@@ -2,6 +2,8 @@
This module contains various configuration settings via
waffle switches for the contentstore app.
"""
+from __future__ import absolute_import
+
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlagNamespace, WaffleSwitchNamespace
# Namespace
diff --git a/cms/djangoapps/contentstore/config/waffle_utils.py b/cms/djangoapps/contentstore/config/waffle_utils.py
index 86840acd7e..ac25116922 100644
--- a/cms/djangoapps/contentstore/config/waffle_utils.py
+++ b/cms/djangoapps/contentstore/config/waffle_utils.py
@@ -1,5 +1,7 @@
"""Util methods for Waffle checks"""
+from __future__ import absolute_import
+
from cms.djangoapps.contentstore.config.waffle import ENABLE_CHECKLISTS_QUALITY
diff --git a/cms/djangoapps/contentstore/course_group_config.py b/cms/djangoapps/contentstore/course_group_config.py
index f07c51eebd..911c356d18 100644
--- a/cms/djangoapps/contentstore/course_group_config.py
+++ b/cms/djangoapps/contentstore/course_group_config.py
@@ -1,9 +1,11 @@
"""
Class for manipulating groups configuration on a course object.
"""
-from collections import defaultdict
+from __future__ import absolute_import
+
import json
import logging
+from collections import defaultdict
from django.utils.translation import ugettext as _
@@ -58,7 +60,7 @@ class GroupConfiguration(object):
Deserialize given json that represents group configuration.
"""
try:
- configuration = json.loads(json_string)
+ configuration = json.loads(json_string.decode("utf-8"))
except ValueError:
raise GroupConfigurationsValidationError(_("invalid JSON"))
configuration["version"] = UserPartition.VERSION
diff --git a/cms/djangoapps/contentstore/course_info_model.py b/cms/djangoapps/contentstore/course_info_model.py
index d5631d9e94..d9e647d032 100644
--- a/cms/djangoapps/contentstore/course_info_model.py
+++ b/cms/djangoapps/contentstore/course_info_model.py
@@ -12,15 +12,16 @@ Current db representation:
}
"""
+from __future__ import absolute_import
+
import logging
import re
from django.http import HttpResponseBadRequest
from django.utils.translation import ugettext as _
-from cms.djangoapps.contentstore.push_notification import enqueue_push_course_update
from openedx.core.lib.xblock_utils import get_course_update_items
-from xmodule.html_module import CourseInfoModule
+from xmodule.html_module import CourseInfoBlock
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
@@ -47,7 +48,6 @@ def update_course_updates(location, update, passed_id=None, user=None):
Either add or update the given course update.
Add:
If the passed_id is absent or None, the course update is added.
- If push_notification_selected is set in the update, a celery task for the push notification is created.
Update:
It will update it if it has a passed_id which has a valid value.
Until updates have distinct values, the passed_id is the location url + an index into the html structure.
@@ -74,10 +74,9 @@ def update_course_updates(location, update, passed_id=None, user=None):
"id": len(course_update_items) + 1,
"date": update["date"],
"content": update["content"],
- "status": CourseInfoModule.STATUS_VISIBLE
+ "status": CourseInfoBlock.STATUS_VISIBLE
}
course_update_items.append(course_update_dict)
- enqueue_push_course_update(update, location.course_key)
# update db record
save_course_update_items(location, course_updates, course_update_items, user)
@@ -104,14 +103,14 @@ def _get_visible_update(course_update_items):
"""
if isinstance(course_update_items, dict):
# single course update item
- if course_update_items.get("status") != CourseInfoModule.STATUS_DELETED:
+ if course_update_items.get("status") != CourseInfoBlock.STATUS_DELETED:
return _make_update_dict(course_update_items)
else:
# requested course update item has been deleted (soft delete)
return {"error": _("Course update not found."), "status": 404}
return ([_make_update_dict(update) for update in course_update_items
- if update.get("status") != CourseInfoModule.STATUS_DELETED])
+ if update.get("status") != CourseInfoBlock.STATUS_DELETED])
# pylint: disable=unused-argument
@@ -136,7 +135,7 @@ def delete_course_update(location, update, passed_id, user):
if 0 < passed_index <= len(course_update_items):
course_update_item = course_update_items[passed_index - 1]
# soft delete course update item
- course_update_item["status"] = CourseInfoModule.STATUS_DELETED
+ course_update_item["status"] = CourseInfoBlock.STATUS_DELETED
course_update_items[passed_index - 1] = course_update_item
# update db record
diff --git a/cms/djangoapps/contentstore/courseware_index.py b/cms/djangoapps/contentstore/courseware_index.py
index 4161571e18..2ca873688d 100644
--- a/cms/djangoapps/contentstore/courseware_index.py
+++ b/cms/djangoapps/contentstore/courseware_index.py
@@ -11,7 +11,7 @@ from django.urls import resolve
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_lazy
from search.search_engine_base import SearchEngine
-from six import add_metaclass
+from six import add_metaclass, string_types, text_type
from contentstore.course_group_config import GroupConfiguration
from course_modes.models import CourseMode
@@ -193,22 +193,22 @@ class SearchIndexerBase(object):
for split_test_child in item.get_children():
if split_partition:
for group in split_partition.groups:
- group_id = unicode(group.id)
+ group_id = text_type(group.id)
child_location = item.group_id_to_child.get(group_id, None)
if child_location == split_test_child.location:
groups_usage_info.update({
- unicode(get_item_location(split_test_child)): [group_id],
+ text_type(get_item_location(split_test_child)): [group_id],
})
for component in split_test_child.get_children():
groups_usage_info.update({
- unicode(get_item_location(component)): [group_id]
+ text_type(get_item_location(component)): [group_id]
})
if groups_usage_info:
item_location = get_item_location(item)
- item_content_groups = groups_usage_info.get(unicode(item_location), None)
+ item_content_groups = groups_usage_info.get(text_type(item_location), None)
- item_id = unicode(cls._id_modifier(item.scope_ids.usage_id))
+ item_id = text_type(cls._id_modifier(item.scope_ids.usage_id))
indexed_items.add(item_id)
if item.has_children:
# determine if it's okay to skip adding the children herein based upon how recently any may have changed
@@ -367,7 +367,7 @@ class CoursewareSearchIndexer(SearchIndexerBase):
@classmethod
def _get_location_info(cls, normalized_structure_key):
""" Builds location info dictionary """
- return {"course": unicode(normalized_structure_key), "org": normalized_structure_key.org}
+ return {"course": text_type(normalized_structure_key), "org": normalized_structure_key.org}
@classmethod
def do_course_reindex(cls, modulestore, course_key):
@@ -389,7 +389,7 @@ class CoursewareSearchIndexer(SearchIndexerBase):
for name, group in groups.items():
for module in group:
view, args, kwargs = resolve(module['url']) # pylint: disable=unused-variable
- usage_key_string = unicode(kwargs['usage_key_string'])
+ usage_key_string = text_type(kwargs['usage_key_string'])
if groups_usage_dict.get(usage_key_string, None):
groups_usage_dict[usage_key_string].append(name)
else:
@@ -418,7 +418,7 @@ class CoursewareSearchIndexer(SearchIndexerBase):
while parent is not None:
path_component_name = parent.display_name
if not path_component_name:
- path_component_name = unicode(cls.UNNAMED_MODULE_NAME)
+ path_component_name = text_type(cls.UNNAMED_MODULE_NAME)
location_path.append(path_component_name)
parent = parent.get_parent()
location_path.reverse()
@@ -454,7 +454,7 @@ class LibrarySearchIndexer(SearchIndexerBase):
@classmethod
def _get_location_info(cls, normalized_structure_key):
""" Builds location info dictionary """
- return {"library": unicode(normalized_structure_key)}
+ return {"library": text_type(normalized_structure_key)}
@classmethod
def _id_modifier(cls, usage_id):
@@ -586,7 +586,7 @@ class CourseAboutSearchIndexer(object):
if not searcher:
return
- course_id = unicode(course.id)
+ course_id = text_type(course.id)
course_info = {
'id': course_id,
'course': course_id,
@@ -621,7 +621,7 @@ class CourseAboutSearchIndexer(object):
if section_content:
if about_information.index_flags & AboutInfo.ANALYSE:
analyse_content = section_content
- if isinstance(section_content, basestring):
+ if isinstance(section_content, string_types):
analyse_content = strip_html_content_to_text(section_content)
course_info['content'][about_information.property_name] = analyse_content
if about_information.index_flags & AboutInfo.PROPERTY:
@@ -645,7 +645,7 @@ class CourseAboutSearchIndexer(object):
@classmethod
def _get_location_info(cls, normalized_structure_key):
""" Builds location info dictionary """
- return {"course": unicode(normalized_structure_key), "org": normalized_structure_key.org}
+ return {"course": text_type(normalized_structure_key), "org": normalized_structure_key.org}
@classmethod
def remove_deleted_items(cls, structure_key):
diff --git a/cms/djangoapps/contentstore/debug_file_uploader.py b/cms/djangoapps/contentstore/debug_file_uploader.py
index 12acfe95c3..30c0651f6d 100644
--- a/cms/djangoapps/contentstore/debug_file_uploader.py
+++ b/cms/djangoapps/contentstore/debug_file_uploader.py
@@ -1,3 +1,7 @@
+""" Upload file handler to help test progress bars in uploads. """
+
+from __future__ import absolute_import
+
import time
from django.core.files.uploadhandler import FileUploadHandler
diff --git a/cms/djangoapps/contentstore/git_export_utils.py b/cms/djangoapps/contentstore/git_export_utils.py
index 086f9d9f34..ed3922aca8 100644
--- a/cms/djangoapps/contentstore/git_export_utils.py
+++ b/cms/djangoapps/contentstore/git_export_utils.py
@@ -3,15 +3,18 @@ Utilities for export a course's XML into a git repository,
committing and pushing the changes.
"""
+from __future__ import absolute_import
+
import logging
import os
import subprocess
-from urlparse import urlparse
+import six
from django.conf import settings
from django.contrib.auth.models import User
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
+from six.moves.urllib.parse import urlparse # pylint: disable=import-error
from xmodule.contentstore.django import contentstore
from xmodule.modulestore.django import modulestore
@@ -32,7 +35,7 @@ class GitExportError(Exception):
def __init__(self, message):
# Force the lazy i18n values to turn into actual unicode objects
- super(GitExportError, self).__init__(unicode(message))
+ super(GitExportError, self).__init__(six.text_type(message))
NO_EXPORT_DIR = _(u"GIT_REPO_EXPORT_DIR not set or path {0} doesn't exist, "
"please create it, or configure a different path with "
diff --git a/cms/djangoapps/contentstore/management/commands/clean_cert_name.py b/cms/djangoapps/contentstore/management/commands/clean_cert_name.py
index 25c3636626..4477a146ca 100644
--- a/cms/djangoapps/contentstore/management/commands/clean_cert_name.py
+++ b/cms/djangoapps/contentstore/management/commands/clean_cert_name.py
@@ -3,11 +3,13 @@ A single-use management command that provides an interactive way to remove
erroneous certificate names.
"""
+from __future__ import absolute_import
+
from collections import namedtuple
-from six.moves import input
-from six import text_type
from django.core.management.base import BaseCommand
+from six import text_type
+from six.moves import input, range, zip
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
diff --git a/cms/djangoapps/contentstore/management/commands/cleanup_assets.py b/cms/djangoapps/contentstore/management/commands/cleanup_assets.py
index 0eff9ad035..f335b11c66 100644
--- a/cms/djangoapps/contentstore/management/commands/cleanup_assets.py
+++ b/cms/djangoapps/contentstore/management/commands/cleanup_assets.py
@@ -2,6 +2,8 @@
Script for removing all redundant Mac OS metadata files (with filename ".DS_Store"
or with filename which starts with "._") for all courses
"""
+from __future__ import absolute_import
+
import logging
from django.core.management.base import BaseCommand
diff --git a/cms/djangoapps/contentstore/management/commands/create_course.py b/cms/djangoapps/contentstore/management/commands/create_course.py
index fdfad57927..0f4c7877bc 100644
--- a/cms/djangoapps/contentstore/management/commands/create_course.py
+++ b/cms/djangoapps/contentstore/management/commands/create_course.py
@@ -1,18 +1,19 @@
"""
Django management command to create a course in a specific modulestore
"""
+from __future__ import absolute_import
+
from datetime import datetime, timedelta
-from six import text_type
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand, CommandError
+from six import text_type
from contentstore.management.commands.utils import user_from_str
from contentstore.views.course import create_new_course_in_store
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.exceptions import DuplicateCourseError
-
MODULESTORE_CHOICES = (ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
diff --git a/cms/djangoapps/contentstore/management/commands/delete_course.py b/cms/djangoapps/contentstore/management/commands/delete_course.py
index 1f6c706f1e..f16ea35a68 100644
--- a/cms/djangoapps/contentstore/management/commands/delete_course.py
+++ b/cms/djangoapps/contentstore/management/commands/delete_course.py
@@ -1,14 +1,18 @@
-from __future__ import print_function
-from six import text_type
+"""
+Management Command to delete course.
+"""
+from __future__ import absolute_import, print_function
from django.core.management.base import BaseCommand, CommandError
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
+from six import text_type
from contentstore.utils import delete_course
from xmodule.contentstore.django import contentstore
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
+
from .prompt import query_yes_no
diff --git a/cms/djangoapps/contentstore/management/commands/delete_orphans.py b/cms/djangoapps/contentstore/management/commands/delete_orphans.py
index 146f283ab4..0c1b7c427c 100644
--- a/cms/djangoapps/contentstore/management/commands/delete_orphans.py
+++ b/cms/djangoapps/contentstore/management/commands/delete_orphans.py
@@ -1,5 +1,5 @@
"""Script for deleting orphans"""
-from __future__ import print_function
+from __future__ import absolute_import, print_function
from django.core.management.base import BaseCommand, CommandError
from opaque_keys import InvalidKeyError
diff --git a/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py b/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py
index 14ebfcbbd6..f59dd7bdba 100644
--- a/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py
+++ b/cms/djangoapps/contentstore/management/commands/edit_course_tabs.py
@@ -6,7 +6,7 @@
# Run it this way:
# ./manage.py cms --settings dev edit_course_tabs --course Stanford/CS99/2013_spring
#
-from __future__ import print_function
+from __future__ import absolute_import, print_function
from django.core.management.base import BaseCommand, CommandError
from opaque_keys.edx.keys import CourseKey
diff --git a/cms/djangoapps/contentstore/management/commands/empty_asset_trashcan.py b/cms/djangoapps/contentstore/management/commands/empty_asset_trashcan.py
index 563d9c44b6..c4c78f33aa 100644
--- a/cms/djangoapps/contentstore/management/commands/empty_asset_trashcan.py
+++ b/cms/djangoapps/contentstore/management/commands/empty_asset_trashcan.py
@@ -1,4 +1,8 @@
-from django.core.management.base import BaseCommand, CommandError
+"""Script to Empty the trashcan"""
+
+from __future__ import absolute_import
+
+from django.core.management.base import BaseCommand
from opaque_keys.edx.keys import CourseKey
from xmodule.contentstore.utils import empty_asset_trashcan
@@ -8,6 +12,9 @@ from .prompt import query_yes_no
class Command(BaseCommand):
+ """
+ Command to Empty the trashcan.
+ """
help = 'Empty the trashcan. Can pass an optional course_id to limit the damage.'
def add_arguments(self, parser):
diff --git a/cms/djangoapps/contentstore/management/commands/export.py b/cms/djangoapps/contentstore/management/commands/export.py
index ae56d8f231..db188a87bf 100644
--- a/cms/djangoapps/contentstore/management/commands/export.py
+++ b/cms/djangoapps/contentstore/management/commands/export.py
@@ -1,7 +1,8 @@
"""
Script for exporting courseware from Mongo to a tar.gz file
"""
-from __future__ import print_function
+from __future__ import absolute_import, print_function
+
import os
from django.core.management.base import BaseCommand, CommandError
diff --git a/cms/djangoapps/contentstore/management/commands/export_all_courses.py b/cms/djangoapps/contentstore/management/commands/export_all_courses.py
index 4fd67dfa29..b6933f9309 100644
--- a/cms/djangoapps/contentstore/management/commands/export_all_courses.py
+++ b/cms/djangoapps/contentstore/management/commands/export_all_courses.py
@@ -1,10 +1,10 @@
"""
Script for exporting all courseware from Mongo to a directory and listing the courses which failed to export
"""
-from __future__ import print_function
-from six import text_type
+from __future__ import absolute_import, print_function
from django.core.management.base import BaseCommand
+from six import text_type
from xmodule.contentstore.django import contentstore
from xmodule.modulestore.django import modulestore
diff --git a/cms/djangoapps/contentstore/management/commands/export_content_library.py b/cms/djangoapps/contentstore/management/commands/export_content_library.py
index 9c914485a1..fd47644d30 100644
--- a/cms/djangoapps/contentstore/management/commands/export_content_library.py
+++ b/cms/djangoapps/contentstore/management/commands/export_content_library.py
@@ -1,7 +1,8 @@
"""
Script for exporting a content library from Mongo to a tar.gz file
"""
-from __future__ import print_function
+from __future__ import absolute_import, print_function
+
import os
import shutil
@@ -9,9 +10,9 @@ from django.core.management.base import BaseCommand, CommandError
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locator import LibraryLocator
-from xmodule.modulestore.django import modulestore
from cms.djangoapps.contentstore import tasks
+from xmodule.modulestore.django import modulestore
class Command(BaseCommand):
diff --git a/cms/djangoapps/contentstore/management/commands/export_olx.py b/cms/djangoapps/contentstore/management/commands/export_olx.py
index 4d02aeefd7..bb11b7ad7b 100644
--- a/cms/djangoapps/contentstore/management/commands/export_olx.py
+++ b/cms/djangoapps/contentstore/management/commands/export_olx.py
@@ -14,6 +14,8 @@ At present, it differs from Studio exports in several ways:
* It only supports the export of courses. It does not export libraries.
"""
+from __future__ import absolute_import
+
import os
import re
import shutil
diff --git a/cms/djangoapps/contentstore/management/commands/fix_not_found.py b/cms/djangoapps/contentstore/management/commands/fix_not_found.py
index 7c41230417..05ddf9009f 100644
--- a/cms/djangoapps/contentstore/management/commands/fix_not_found.py
+++ b/cms/djangoapps/contentstore/management/commands/fix_not_found.py
@@ -1,13 +1,14 @@
"""
Script for fixing the item not found errors in a course
"""
+from __future__ import absolute_import
+
from django.core.management.base import BaseCommand, CommandError
from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
-
# To run from command line: ./manage.py cms fix_not_found course-v1:org+course+run
diff --git a/cms/djangoapps/contentstore/management/commands/force_publish.py b/cms/djangoapps/contentstore/management/commands/force_publish.py
index 22839e386b..f02a3404fd 100644
--- a/cms/djangoapps/contentstore/management/commands/force_publish.py
+++ b/cms/djangoapps/contentstore/management/commands/force_publish.py
@@ -1,7 +1,8 @@
"""
Script for force publishing a course
"""
-from __future__ import print_function
+from __future__ import absolute_import, print_function
+
from django.core.management.base import BaseCommand, CommandError
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
@@ -12,7 +13,6 @@ from xmodule.modulestore.django import modulestore
from .prompt import query_yes_no
from .utils import get_course_versions
-
# To run from command line: ./manage.py cms force_publish course-v1:org+course+run
diff --git a/cms/djangoapps/contentstore/management/commands/generate_courses.py b/cms/djangoapps/contentstore/management/commands/generate_courses.py
index ab8b2fdeed..eaa4a530a2 100644
--- a/cms/djangoapps/contentstore/management/commands/generate_courses.py
+++ b/cms/djangoapps/contentstore/management/commands/generate_courses.py
@@ -1,12 +1,14 @@
"""
Django management command to generate a test course from a course config json
"""
+from __future__ import absolute_import
+
import json
import logging
-from six import text_type
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand, CommandError
+from six import text_type
from contentstore.management.commands.utils import user_from_str
from contentstore.views.course import create_new_course_in_store
@@ -73,7 +75,7 @@ class Command(BaseCommand):
def _process_course_fields(self, fields):
""" Returns a validated list of course fields """
- all_fields = CourseFields.__dict__.keys()
+ all_fields = list(CourseFields.__dict__.keys())
non_course_fields = [
"__doc__",
"__module__",
diff --git a/cms/djangoapps/contentstore/management/commands/git_export.py b/cms/djangoapps/contentstore/management/commands/git_export.py
index 27f646ea3a..dd33c3cde5 100644
--- a/cms/djangoapps/contentstore/management/commands/git_export.py
+++ b/cms/djangoapps/contentstore/management/commands/git_export.py
@@ -13,13 +13,15 @@ This functionality is also available as an export view in studio if the giturl
attribute is set and the FEATURE['ENABLE_EXPORT_GIT'] is set.
"""
+from __future__ import absolute_import
+
import logging
-from six import text_type
from django.core.management.base import BaseCommand, CommandError
from django.utils.translation import ugettext as _
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
+from six import text_type
import contentstore.git_export_utils as git_export_utils
from contentstore.git_export_utils import GitExportError
diff --git a/cms/djangoapps/contentstore/management/commands/import.py b/cms/djangoapps/contentstore/management/commands/import.py
index 7aed4105d1..c2e9d54df4 100644
--- a/cms/djangoapps/contentstore/management/commands/import.py
+++ b/cms/djangoapps/contentstore/management/commands/import.py
@@ -1,13 +1,16 @@
"""
Script for importing courseware from XML format
"""
+from __future__ import absolute_import
+
from django.core.management.base import BaseCommand
+
from openedx.core.djangoapps.django_comment_common.utils import are_permissions_roles_seeded, seed_permissions_roles
from xmodule.contentstore.django import contentstore
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
-from xmodule.util.sandboxing import DEFAULT_PYTHON_LIB_FILENAME
from xmodule.modulestore.xml_importer import import_course_from_xml
+from xmodule.util.sandboxing import DEFAULT_PYTHON_LIB_FILENAME
class Command(BaseCommand):
diff --git a/cms/djangoapps/contentstore/management/commands/import_content_library.py b/cms/djangoapps/contentstore/management/commands/import_content_library.py
index 014fa37946..20f63a1e87 100644
--- a/cms/djangoapps/contentstore/management/commands/import_content_library.py
+++ b/cms/djangoapps/contentstore/management/commands/import_content_library.py
@@ -1,7 +1,7 @@
"""
Script for importing a content library from a tar.gz file
"""
-from __future__ import print_function
+from __future__ import absolute_import, print_function
import base64
import os
@@ -14,15 +14,16 @@ from django.core.management.base import BaseCommand, CommandError
from lxml import etree
from opaque_keys.edx.locator import LibraryLocator
from path import Path
+from six.moves import input
+
+from cms.djangoapps.contentstore.utils import add_instructor
+from openedx.core.lib.extract_tar import safetar_extractall
from xmodule.contentstore.django import contentstore
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import DuplicateCourseError
from xmodule.modulestore.xml_importer import import_library_from_xml
-from cms.djangoapps.contentstore.utils import add_instructor
-from openedx.core.lib.extract_tar import safetar_extractall
-
class Command(BaseCommand):
"""
@@ -76,7 +77,7 @@ class Command(BaseCommand):
# Check if data would be overwritten
ans = ''
while not created and ans not in ['y', 'yes', 'n', 'no']:
- inp = raw_input(u'Library "{0}" already exists, overwrite it? [y/n] '.format(courselike_key))
+ inp = input(u'Library "{0}" already exists, overwrite it? [y/n] '.format(courselike_key))
ans = inp.lower()
if ans.startswith('n'):
print(u'Aborting import of "{0}"'.format(courselike_key))
diff --git a/cms/djangoapps/contentstore/management/commands/migrate_to_split.py b/cms/djangoapps/contentstore/management/commands/migrate_to_split.py
index 3655d8b76b..2003058646 100644
--- a/cms/djangoapps/contentstore/management/commands/migrate_to_split.py
+++ b/cms/djangoapps/contentstore/management/commands/migrate_to_split.py
@@ -2,6 +2,8 @@
Django management command to migrate a course from the old Mongo modulestore
to the new split-Mongo modulestore.
"""
+from __future__ import absolute_import
+
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand, CommandError
from opaque_keys import InvalidKeyError
diff --git a/cms/djangoapps/contentstore/management/commands/migrate_transcripts.py b/cms/djangoapps/contentstore/management/commands/migrate_transcripts.py
index b869f76898..5c66732a64 100644
--- a/cms/djangoapps/contentstore/management/commands/migrate_transcripts.py
+++ b/cms/djangoapps/contentstore/management/commands/migrate_transcripts.py
@@ -2,19 +2,24 @@
Command to migrate transcripts to django storage.
"""
+from __future__ import absolute_import
+
import logging
+
from django.core.management import BaseCommand, CommandError
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locator import CourseLocator
+from six.moves import map
+
from cms.djangoapps.contentstore.tasks import (
DEFAULT_ALL_COURSES,
- DEFAULT_FORCE_UPDATE,
DEFAULT_COMMIT,
+ DEFAULT_FORCE_UPDATE,
enqueue_async_migrate_transcripts_tasks
)
+from openedx.core.djangoapps.video_config.models import MigrationEnqueuedCourse, TranscriptMigrationSetting
from openedx.core.lib.command_utils import get_mutually_exclusive_required_option, parse_course_keys
-from openedx.core.djangoapps.video_config.models import TranscriptMigrationSetting, MigrationEnqueuedCourse
from xmodule.modulestore.django import modulestore
log = logging.getLogger(__name__)
@@ -91,7 +96,7 @@ class Command(BaseCommand):
if courses_mode == 'all_courses':
course_keys = [course.id for course in modulestore().get_course_summaries()]
elif courses_mode == 'course_ids':
- course_keys = map(self._parse_course_key, options['course_ids'])
+ course_keys = list(map(self._parse_course_key, options['course_ids']))
else:
migration_settings = self._latest_settings()
if migration_settings.all_courses:
diff --git a/cms/djangoapps/contentstore/management/commands/prompt.py b/cms/djangoapps/contentstore/management/commands/prompt.py
index e658738128..927c5c916c 100644
--- a/cms/djangoapps/contentstore/management/commands/prompt.py
+++ b/cms/djangoapps/contentstore/management/commands/prompt.py
@@ -1,5 +1,12 @@
+"""
+Takes user input.
+"""
+from __future__ import absolute_import
+
import sys
+from six.moves import input
+
def query_yes_no(question, default="yes"):
"""Ask a yes/no question via raw_input() and return their answer.
@@ -29,7 +36,7 @@ def query_yes_no(question, default="yes"):
while True:
sys.stdout.write(question + prompt)
- choice = raw_input().lower()
+ choice = input().lower()
if default is not None and choice == '':
return valid[default]
elif choice in valid:
diff --git a/cms/djangoapps/contentstore/management/commands/reindex_course.py b/cms/djangoapps/contentstore/management/commands/reindex_course.py
index 0171c80dde..0055dbd31f 100644
--- a/cms/djangoapps/contentstore/management/commands/reindex_course.py
+++ b/cms/djangoapps/contentstore/management/commands/reindex_course.py
@@ -1,4 +1,6 @@
""" Management command to update courses' search index """
+from __future__ import absolute_import
+
import logging
from textwrap import dedent
@@ -8,6 +10,7 @@ from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locator import CourseLocator
from search.search_engine_base import SearchEngine
+from six.moves import map
from contentstore.courseware_index import CoursewareSearchIndexer
from xmodule.modulestore.django import modulestore
@@ -101,7 +104,7 @@ class Command(BaseCommand):
return
else:
# in case course keys are provided as arguments
- course_keys = map(self._parse_course_key, course_ids)
+ course_keys = list(map(self._parse_course_key, course_ids))
for course_key in course_keys:
CoursewareSearchIndexer.do_course_reindex(store, course_key)
diff --git a/cms/djangoapps/contentstore/management/commands/reindex_library.py b/cms/djangoapps/contentstore/management/commands/reindex_library.py
index 121ef45e0d..71d2f1fe13 100644
--- a/cms/djangoapps/contentstore/management/commands/reindex_library.py
+++ b/cms/djangoapps/contentstore/management/commands/reindex_library.py
@@ -1,10 +1,12 @@
""" Management command to update libraries' search index """
-from __future__ import print_function
+from __future__ import absolute_import, print_function
+
from textwrap import dedent
from django.core.management import BaseCommand, CommandError
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locator import LibraryLocator
+from six.moves import map
from contentstore.courseware_index import LibrarySearchIndexer
from xmodule.modulestore.django import modulestore
@@ -58,7 +60,7 @@ class Command(BaseCommand):
else:
return
else:
- library_keys = map(self._parse_library_key, options['library_ids'])
+ library_keys = list(map(self._parse_library_key, options['library_ids']))
for library_key in library_keys:
print(u"Indexing library {}".format(library_key))
diff --git a/cms/djangoapps/contentstore/management/commands/restore_asset_from_trashcan.py b/cms/djangoapps/contentstore/management/commands/restore_asset_from_trashcan.py
index ca8de0ceb7..72ee2c5afa 100644
--- a/cms/djangoapps/contentstore/management/commands/restore_asset_from_trashcan.py
+++ b/cms/djangoapps/contentstore/management/commands/restore_asset_from_trashcan.py
@@ -1,9 +1,14 @@
-from django.core.management.base import BaseCommand, CommandError
+"""Management command to restore assets from trash"""
+
+from __future__ import absolute_import
+
+from django.core.management.base import BaseCommand
from xmodule.contentstore.utils import restore_asset_from_trashcan
class Command(BaseCommand):
+ """Command class to handle asset restore"""
help = '''Restore a deleted asset from the trashcan back to it's original course'''
def add_arguments(self, parser):
diff --git a/cms/djangoapps/contentstore/management/commands/sync_courses.py b/cms/djangoapps/contentstore/management/commands/sync_courses.py
new file mode 100644
index 0000000000..47cf557608
--- /dev/null
+++ b/cms/djangoapps/contentstore/management/commands/sync_courses.py
@@ -0,0 +1,70 @@
+"""
+Sync courses from catalog service. This is used to setup a master's
+integration environment.
+"""
+import logging
+from textwrap import dedent
+
+from django.contrib.auth.models import User
+from django.core.management.base import BaseCommand, CommandError
+from opaque_keys.edx.keys import CourseKey
+from six import text_type
+
+from contentstore.management.commands.utils import user_from_str
+from contentstore.views.course import create_new_course_in_store
+from openedx.core.djangoapps.catalog.utils import get_course_runs
+from xmodule.modulestore import ModuleStoreEnum
+from xmodule.modulestore.exceptions import DuplicateCourseError
+
+logger = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """
+ Command to populate modulestore with courses from the discovery service.
+
+ Example: ./manage.py cms sync_courses staff@example.com
+ """
+ help = dedent(__doc__).strip()
+
+ def add_arguments(self, parser):
+ parser.add_argument('instructor')
+
+ def get_user(self, user):
+ """
+ Return a User object.
+ """
+ try:
+ user_object = user_from_str(user)
+ except User.DoesNotExist:
+ raise CommandError(u"No user {user} found.".format(user=user))
+ return user_object
+
+ def handle(self, *args, **options):
+ """Execute the command"""
+ instructor = self.get_user(options['instructor'])
+
+ course_runs = get_course_runs()
+ for course_run in course_runs:
+ course_key = CourseKey.from_string(course_run.get('key'))
+ fields = {
+ "display_name": course_run.get('title')
+ }
+
+ try:
+ new_course = create_new_course_in_store(
+ ModuleStoreEnum.Type.split,
+ instructor,
+ course_key.org,
+ course_key.course,
+ course_key.run,
+ fields,
+ )
+ logger.info(u"Created {}".format(text_type(new_course.id)))
+ except DuplicateCourseError:
+ logger.warning(
+ u"Course already exists for %s, %s, %s. Skipping",
+ course_key.org,
+ course_key.course,
+ course_key.run,
+ )
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_cleanup_assets.py b/cms/djangoapps/contentstore/management/commands/tests/test_cleanup_assets.py
index 0930c6571d..b48d3e507e 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_cleanup_assets.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_cleanup_assets.py
@@ -2,20 +2,20 @@
Test for assets cleanup of courses for Mac OS metadata files (with filename ".DS_Store"
or with filename which starts with "._")
"""
-from django.core.management import call_command
+from __future__ import absolute_import
+from django.conf import settings
+from django.core.management import call_command
from opaque_keys.edx.keys import CourseKey
+
from xmodule.contentstore.content import XASSET_LOCATION_TAG
from xmodule.contentstore.django import contentstore
-from xmodule.modulestore.django import modulestore
from xmodule.modulestore import ModuleStoreEnum
+from xmodule.modulestore.django import modulestore
from xmodule.modulestore.mongo.base import location_to_query
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
-from xmodule.modulestore.tests.utils import (
- add_temp_files_from_dict, remove_temp_files_from_list, DOT_FILES_DICT
-)
+from xmodule.modulestore.tests.utils import DOT_FILES_DICT, add_temp_files_from_dict, remove_temp_files_from_list
from xmodule.modulestore.xml_importer import import_course_from_xml
-from django.conf import settings
TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
@@ -32,7 +32,7 @@ class ExportAllCourses(ModuleStoreTestCase):
self.content_store = contentstore()
# pylint: disable=protected-access
self.module_store = modulestore()._get_modulestore_by_type(ModuleStoreEnum.Type.mongo)
- self.addCleanup(remove_temp_files_from_list, DOT_FILES_DICT.keys(), self.course_dir / "static")
+ self.addCleanup(remove_temp_files_from_list, list(DOT_FILES_DICT.keys()), self.course_dir / "static")
add_temp_files_from_dict(DOT_FILES_DICT, self.course_dir / "static")
def test_export_all_courses(self):
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_create_course.py b/cms/djangoapps/contentstore/management/commands/tests/test_create_course.py
index d235880691..d9e90d57ee 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_create_course.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_create_course.py
@@ -1,14 +1,18 @@
"""
Unittests for creating a course in an chosen modulestore
"""
-from StringIO import StringIO
+from __future__ import absolute_import
+
+from six import StringIO
+
import ddt
+import six
from django.core.management import CommandError, call_command
from django.test import TestCase
from xmodule.modulestore import ModuleStoreEnum
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.django import modulestore
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
class TestArgParsing(TestCase):
@@ -19,7 +23,10 @@ class TestArgParsing(TestCase):
super(TestArgParsing, self).setUp()
def test_no_args(self):
- errstring = "Error: too few arguments"
+ if six.PY2:
+ errstring = "Error: too few arguments"
+ else:
+ errstring = "Error: the following arguments are required: modulestore, user, org, number, run"
with self.assertRaisesRegexp(CommandError, errstring):
call_command('create_course')
@@ -108,7 +115,7 @@ class TestCreateCourse(ModuleStoreTestCase):
)
course = self.store.get_course(lowercase_course_id)
self.assertIsNotNone(course, 'Course not found using lowercase course key.')
- self.assertEqual(unicode(course.id), unicode(lowercase_course_id))
+ self.assertEqual(six.text_type(course.id), six.text_type(lowercase_course_id))
# Verify store does not return course with different case.
uppercase_course_id = self.store.make_course_key(org.upper(), number.upper(), run.upper())
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py b/cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py
index dc72d78e36..8d0388aa14 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_delete_course.py
@@ -1,3 +1,8 @@
+"""
+Delete course tests.
+"""
+from __future__ import absolute_import
+
import mock
from django.core.management import CommandError, call_command
@@ -6,7 +11,7 @@ from student.tests.factories import UserFactory
from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.django import contentstore
from xmodule.modulestore.django import modulestore
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, TEST_DATA_SPLIT_MODULESTORE
+from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py b/cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py
index 816d579eb6..8d4dbf720e 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_delete_orphans.py
@@ -1,11 +1,14 @@
"""Tests running the delete_orphan command"""
-import ddt
-from django.core.management import call_command, CommandError
-from contentstore.tests.test_orphan import TestOrphanBase
+from __future__ import absolute_import
-from xmodule.modulestore.tests.factories import CourseFactory
+import ddt
+import six
+from django.core.management import CommandError, call_command
+
+from contentstore.tests.test_orphan import TestOrphanBase
from xmodule.modulestore import ModuleStoreEnum
+from xmodule.modulestore.tests.factories import CourseFactory
@ddt.ddt
@@ -18,7 +21,11 @@ class TestDeleteOrphan(TestOrphanBase):
"""
Test delete_orphans command with no arguments
"""
- with self.assertRaisesRegexp(CommandError, 'Error: too few arguments'):
+ if six.PY2:
+ errstring = 'Error: too few arguments'
+ else:
+ errstring = 'Error: the following arguments are required: course_id'
+ with self.assertRaisesRegexp(CommandError, errstring):
call_command('delete_orphans')
@ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
@@ -28,7 +35,7 @@ class TestDeleteOrphan(TestOrphanBase):
results in no orphans being deleted
"""
course = self.create_course_with_orphans(default_store)
- call_command('delete_orphans', unicode(course.id))
+ call_command('delete_orphans', six.text_type(course.id))
self.assertTrue(self.store.has_item(course.id.make_usage_key('html', 'multi_parent_html')))
self.assertTrue(self.store.has_item(course.id.make_usage_key('vertical', 'OrphanVert')))
self.assertTrue(self.store.has_item(course.id.make_usage_key('chapter', 'OrphanChapter')))
@@ -42,7 +49,7 @@ class TestDeleteOrphan(TestOrphanBase):
"""
course = self.create_course_with_orphans(default_store)
- call_command('delete_orphans', unicode(course.id), '--commit')
+ call_command('delete_orphans', six.text_type(course.id), '--commit')
# make sure this module wasn't deleted
self.assertTrue(self.store.has_item(course.id.make_usage_key('html', 'multi_parent_html')))
@@ -66,7 +73,7 @@ class TestDeleteOrphan(TestOrphanBase):
# call delete orphans, specifying the published branch
# of the course
- call_command('delete_orphans', unicode(published_branch), '--commit')
+ call_command('delete_orphans', six.text_type(published_branch), '--commit')
# now all orphans should be deleted
self.assertOrphanCount(course.id, 0)
@@ -113,6 +120,6 @@ class TestDeleteOrphan(TestOrphanBase):
# there should be one in published
self.assertOrphanCount(course.id, 0)
self.assertOrphanCount(published_branch, 1)
- self.assertIn(orphan, self.store.get_items(published_branch))
+ self.assertIn(orphan.location, [x.location for x in self.store.get_items(published_branch)])
return course, orphan
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_export.py b/cms/djangoapps/contentstore/management/commands/tests/test_export.py
index 32123455c0..4e02926125 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_export.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_export.py
@@ -1,11 +1,14 @@
"""
Tests for exporting courseware to the desired path
"""
+from __future__ import absolute_import
+
import shutil
import unittest
from tempfile import mkdtemp
import ddt
+import six
from django.core.management import CommandError, call_command
from xmodule.modulestore import ModuleStoreEnum
@@ -22,7 +25,10 @@ class TestArgParsingCourseExport(unittest.TestCase):
"""
Test export command with no arguments
"""
- errstring = "Error: too few arguments"
+ if six.PY2:
+ errstring = "Error: too few arguments"
+ else:
+ errstring = "Error: the following arguments are required: course_id, output_path"
with self.assertRaisesRegexp(CommandError, errstring):
call_command('export')
@@ -49,7 +55,7 @@ class TestCourseExport(ModuleStoreTestCase):
Create a new course try exporting in a path specified
"""
course = CourseFactory.create(default_store=store)
- course_id = unicode(course.id)
+ course_id = six.text_type(course.id)
self.assertTrue(
modulestore().has_course(course.id),
u"Could not find course in {}".format(store)
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_export_all_courses.py b/cms/djangoapps/contentstore/management/commands/tests/test_export_all_courses.py
index 5013bce8cd..6d843eb6c8 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_export_all_courses.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_export_all_courses.py
@@ -1,11 +1,14 @@
"""
Test for export all courses.
"""
+from __future__ import absolute_import
+
import shutil
from tempfile import mkdtemp
-from contentstore.management.commands.export_all_courses import export_courses_to_output_path
+import six
+from contentstore.management.commands.export_all_courses import export_courses_to_output_path
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
@@ -47,4 +50,4 @@ class ExportAllCourses(ModuleStoreTestCase):
courses, failed_export_courses = export_courses_to_output_path(self.temp_dir)
self.assertEqual(len(courses), 2)
self.assertEqual(len(failed_export_courses), 1)
- self.assertEqual(failed_export_courses[0], unicode(second_course_id))
+ self.assertEqual(failed_export_courses[0], six.text_type(second_course_id))
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_export_olx.py b/cms/djangoapps/contentstore/management/commands/tests/test_export_olx.py
index ebfe27ff77..a508505fa3 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_export_olx.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_export_olx.py
@@ -2,13 +2,16 @@
Tests for exporting OLX content.
"""
+from __future__ import absolute_import
+
import shutil
import tarfile
import unittest
-from StringIO import StringIO
+from six import StringIO
from tempfile import mkdtemp
import ddt
+import six
from django.core.management import CommandError, call_command
from path import Path as path
@@ -79,7 +82,7 @@ class TestCourseExportOlx(ModuleStoreTestCase):
tmp_dir = path(mkdtemp())
self.addCleanup(shutil.rmtree, tmp_dir)
filename = tmp_dir / 'test.tar.gz'
- call_command('export_olx', '--output', filename, unicode(test_course_key))
+ call_command('export_olx', '--output', filename, six.text_type(test_course_key))
with tarfile.open(filename) as tar_file:
self.check_export_file(tar_file, test_course_key)
@@ -87,7 +90,7 @@ class TestCourseExportOlx(ModuleStoreTestCase):
def test_export_course_stdout(self, store_type):
test_course_key = self.create_dummy_course(store_type)
out = StringIO()
- call_command('export_olx', unicode(test_course_key), stdout=out)
+ call_command('export_olx', six.text_type(test_course_key), stdout=out)
out.seek(0)
output = out.read()
with tarfile.open(fileobj=StringIO(output)) as tar_file:
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_fix_not_found.py b/cms/djangoapps/contentstore/management/commands/tests/test_fix_not_found.py
index dac5ad0393..5a0894b678 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_fix_not_found.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_fix_not_found.py
@@ -2,7 +2,11 @@
Tests for the fix_not_found management command
"""
+from __future__ import absolute_import
+
+import six
from django.core.management import CommandError, call_command
+
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
@@ -25,7 +29,7 @@ class TestFixNotFound(ModuleStoreTestCase):
"""
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.mongo)
with self.assertRaisesRegexp(CommandError, "The owning modulestore does not support this command."):
- call_command("fix_not_found", unicode(course.id))
+ call_command("fix_not_found", six.text_type(course.id))
def test_fix_not_found(self):
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.split)
@@ -45,7 +49,7 @@ class TestFixNotFound(ModuleStoreTestCase):
self.assertEqual(len(course.children), 2)
self.assertIn(dangling_pointer, course.children)
- call_command("fix_not_found", unicode(course.id))
+ call_command("fix_not_found", six.text_type(course.id))
# make sure the dangling pointer was removed from
# the course block's children
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_force_publish.py b/cms/djangoapps/contentstore/management/commands/tests/test_force_publish.py
index 5fd69318e6..600deaba86 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_force_publish.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_force_publish.py
@@ -1,13 +1,17 @@
"""
Tests for the force_publish management command
"""
+from __future__ import absolute_import
+
import mock
-from django.core.management import call_command, CommandError
-from xmodule.modulestore import ModuleStoreEnum
-from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase, ModuleStoreTestCase
-from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
+import six
+from django.core.management import CommandError, call_command
+
from contentstore.management.commands.force_publish import Command
from contentstore.management.commands.utils import get_course_versions
+from xmodule.modulestore import ModuleStoreEnum
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase
+from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
class TestForcePublish(SharedModuleStoreTestCase):
@@ -25,7 +29,11 @@ class TestForcePublish(SharedModuleStoreTestCase):
"""
Test 'force_publish' command with no arguments
"""
- errstring = "Error: too few arguments"
+ if six.PY2:
+ errstring = "Error: too few arguments"
+ else:
+ errstring = "Error: the following arguments are required: course_key"
+
with self.assertRaisesRegexp(CommandError, errstring):
call_command('force_publish')
@@ -43,7 +51,7 @@ class TestForcePublish(SharedModuleStoreTestCase):
"""
errstring = "Error: unrecognized arguments: invalid-arg"
with self.assertRaisesRegexp(CommandError, errstring):
- call_command('force_publish', unicode(self.course.id), '--commit', 'invalid-arg')
+ call_command('force_publish', six.text_type(self.course.id), '--commit', 'invalid-arg')
def test_course_key_not_found(self):
"""
@@ -51,7 +59,7 @@ class TestForcePublish(SharedModuleStoreTestCase):
"""
errstring = "Course not found."
with self.assertRaisesRegexp(CommandError, errstring):
- call_command('force_publish', unicode('course-v1:org+course+run'))
+ call_command('force_publish', six.text_type('course-v1:org+course+run'))
def test_force_publish_non_split(self):
"""
@@ -60,7 +68,7 @@ class TestForcePublish(SharedModuleStoreTestCase):
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.mongo)
errstring = 'The owning modulestore does not support this command.'
with self.assertRaisesRegexp(CommandError, errstring):
- call_command('force_publish', unicode(course.id))
+ call_command('force_publish', six.text_type(course.id))
class TestForcePublishModifications(ModuleStoreTestCase):
@@ -92,7 +100,7 @@ class TestForcePublishModifications(ModuleStoreTestCase):
self.assertTrue(self.store.has_changes(self.store.get_item(self.course.location)))
# get draft and publish branch versions
- versions = get_course_versions(unicode(self.course.id))
+ versions = get_course_versions(six.text_type(self.course.id))
draft_version = versions['draft-branch']
published_version = versions['published-branch']
@@ -103,13 +111,13 @@ class TestForcePublishModifications(ModuleStoreTestCase):
patched_yes_no.return_value = True
# force publish course
- call_command('force_publish', unicode(self.course.id), '--commit')
+ call_command('force_publish', six.text_type(self.course.id), '--commit')
# verify that course has no changes
self.assertFalse(self.store.has_changes(self.store.get_item(self.course.location)))
# get new draft and publish branch versions
- versions = get_course_versions(unicode(self.course.id))
+ versions = get_course_versions(six.text_type(self.course.id))
new_draft_version = versions['draft-branch']
new_published_version = versions['published-branch']
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_generate_courses.py b/cms/djangoapps/contentstore/management/commands/tests/test_generate_courses.py
index aa83b9d7ce..9d2e560dd4 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_generate_courses.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_generate_courses.py
@@ -1,6 +1,8 @@
"""
Unittest for generate a test course in an given modulestore
"""
+from __future__ import absolute_import
+
import json
import ddt
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_git_export.py b/cms/djangoapps/contentstore/management/commands/tests/test_git_export.py
index 9b2583b85f..ae21e494c5 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_git_export.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_git_export.py
@@ -2,23 +2,26 @@
Unittests for exporting to git via management command.
"""
+from __future__ import absolute_import
+
import copy
import os
import shutil
-import StringIO
+from six import StringIO
import subprocess
import unittest
from uuid import uuid4
+import six
from django.conf import settings
from django.core.management import call_command
from django.core.management.base import CommandError
from django.test.utils import override_settings
+from opaque_keys.edx.locator import CourseLocator
-from contentstore.tests.utils import CourseTestCase
import contentstore.git_export_utils as git_export_utils
from contentstore.git_export_utils import GitExportError
-from opaque_keys.edx.locator import CourseLocator
+from contentstore.tests.utils import CourseTestCase
FEATURES_WITH_EXPORT_GIT = settings.FEATURES.copy()
FEATURES_WITH_EXPORT_GIT['ENABLE_EXPORT_GIT'] = True
@@ -57,29 +60,36 @@ class TestGitExport(CourseTestCase):
test output.
"""
with self.assertRaisesRegexp(CommandError, 'Error: unrecognized arguments:*'):
- call_command('git_export', 'blah', 'blah', 'blah', stderr=StringIO.StringIO())
+ call_command('git_export', 'blah', 'blah', 'blah', stderr=StringIO())
- with self.assertRaisesMessage(CommandError, 'Error: too few arguments'):
- call_command('git_export', stderr=StringIO.StringIO())
+ if six.PY2:
+ with self.assertRaisesMessage(CommandError, 'Error: too few arguments'):
+ call_command('git_export', stderr=StringIO())
+ else:
+ with self.assertRaisesMessage(
+ CommandError,
+ 'Error: the following arguments are required: course_loc, git_url'
+ ):
+ call_command('git_export', stderr=StringIO())
# Send bad url to get course not exported
- with self.assertRaisesRegexp(CommandError, unicode(GitExportError.URL_BAD)):
- call_command('git_export', 'foo/bar/baz', 'silly', stderr=StringIO.StringIO())
+ with self.assertRaisesRegexp(CommandError, six.text_type(GitExportError.URL_BAD)):
+ call_command('git_export', 'foo/bar/baz', 'silly', stderr=StringIO())
# Send bad course_id to get course not exported
- with self.assertRaisesRegexp(CommandError, unicode(GitExportError.BAD_COURSE)):
- call_command('git_export', 'foo/bar:baz', 'silly', stderr=StringIO.StringIO())
+ with self.assertRaisesRegexp(CommandError, six.text_type(GitExportError.BAD_COURSE)):
+ call_command('git_export', 'foo/bar:baz', 'silly', stderr=StringIO())
def test_error_output(self):
"""
Verify that error output is actually resolved as the correct string
"""
- with self.assertRaisesRegexp(CommandError, unicode(GitExportError.BAD_COURSE)):
+ with self.assertRaisesRegexp(CommandError, six.text_type(GitExportError.BAD_COURSE)):
call_command(
'git_export', 'foo/bar:baz', 'silly'
)
- with self.assertRaisesRegexp(CommandError, unicode(GitExportError.URL_BAD)):
+ with self.assertRaisesRegexp(CommandError, six.text_type(GitExportError.URL_BAD)):
call_command(
'git_export', 'foo/bar/baz', 'silly'
)
@@ -89,14 +99,14 @@ class TestGitExport(CourseTestCase):
Test several bad URLs for validation
"""
course_key = CourseLocator('org', 'course', 'run')
- with self.assertRaisesRegexp(GitExportError, unicode(GitExportError.URL_BAD)):
+ with self.assertRaisesRegexp(GitExportError, six.text_type(GitExportError.URL_BAD)):
git_export_utils.export_to_git(course_key, 'Sillyness')
- with self.assertRaisesRegexp(GitExportError, unicode(GitExportError.URL_BAD)):
+ with self.assertRaisesRegexp(GitExportError, six.text_type(GitExportError.URL_BAD)):
git_export_utils.export_to_git(course_key, 'example.com:edx/notreal')
with self.assertRaisesRegexp(GitExportError,
- unicode(GitExportError.URL_NO_AUTH)):
+ six.text_type(GitExportError.URL_NO_AUTH)):
git_export_utils.export_to_git(course_key, 'http://blah')
def test_bad_git_repos(self):
@@ -108,7 +118,7 @@ class TestGitExport(CourseTestCase):
course_key = CourseLocator('foo', 'blah', '100-')
# Test bad clones
with self.assertRaisesRegexp(GitExportError,
- unicode(GitExportError.CANNOT_PULL)):
+ six.text_type(GitExportError.CANNOT_PULL)):
git_export_utils.export_to_git(
course_key,
'https://user:blah@example.com/test_repo.git')
@@ -116,14 +126,14 @@ class TestGitExport(CourseTestCase):
# Setup good repo with bad course to test xml export
with self.assertRaisesRegexp(GitExportError,
- unicode(GitExportError.XML_EXPORT_FAIL)):
+ six.text_type(GitExportError.XML_EXPORT_FAIL)):
git_export_utils.export_to_git(
course_key,
'file://{0}'.format(self.bare_repo_dir))
# Test bad git remote after successful clone
with self.assertRaisesRegexp(GitExportError,
- unicode(GitExportError.CANNOT_PULL)):
+ six.text_type(GitExportError.CANNOT_PULL)):
git_export_utils.export_to_git(
course_key,
'https://user:blah@example.com/r.git')
@@ -180,6 +190,6 @@ class TestGitExport(CourseTestCase):
)
with self.assertRaisesRegexp(GitExportError,
- unicode(GitExportError.CANNOT_COMMIT)):
+ six.text_type(GitExportError.CANNOT_COMMIT)):
git_export_utils.export_to_git(
self.course.id, 'file://{0}'.format(self.bare_repo_dir))
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_import.py b/cms/djangoapps/contentstore/management/commands/tests/test_import.py
index c5771171f8..9c19a5474c 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_import.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_import.py
@@ -2,10 +2,13 @@
Unittests for importing a course via management command
"""
+from __future__ import absolute_import
+
import os
import shutil
import tempfile
+import six
from django.core.management import call_command
from path import Path as path
@@ -91,4 +94,4 @@ class TestImport(ModuleStoreTestCase):
course = modulestore().get_course(self.base_course_key)
# With the bug, this fails because the chapter's course_key is the split mongo form,
# while the course's course_key is the old mongo form.
- self.assertEqual(unicode(course.location.course_key), unicode(course.children[0].course_key))
+ self.assertEqual(six.text_type(course.location.course_key), six.text_type(course.children[0].course_key))
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_migrate_to_split.py b/cms/djangoapps/contentstore/management/commands/tests/test_migrate_to_split.py
index 96373b6fd4..9ed7bf52e7 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_migrate_to_split.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_migrate_to_split.py
@@ -1,13 +1,18 @@
"""
Unittests for migrating a course to split mongo
"""
+from __future__ import absolute_import
+
+import six
+
from django.core.management import CommandError, call_command
from django.test import TestCase
+
from xmodule.modulestore import ModuleStoreEnum
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
-from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
+from xmodule.modulestore.tests.factories import CourseFactory
class TestArgParsing(TestCase):
@@ -21,7 +26,10 @@ class TestArgParsing(TestCase):
"""
Test the arg length error
"""
- errstring = "Error: too few arguments"
+ if six.PY2:
+ errstring = "Error: too few arguments"
+ else:
+ errstring = "Error: the following arguments are required: course_key, email"
with self.assertRaisesRegexp(CommandError, errstring):
call_command("migrate_to_split")
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_migrate_transcripts.py b/cms/djangoapps/contentstore/management/commands/tests/test_migrate_transcripts.py
index 58e81d86e7..d2b3c77d4a 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_migrate_transcripts.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_migrate_transcripts.py
@@ -2,25 +2,27 @@
"""
Tests for course transcript migration management command.
"""
+from __future__ import absolute_import
+
import itertools
import logging
from datetime import datetime
-import pytz
import ddt
+import pytz
+import six
+from django.core.management import CommandError, call_command
from django.test import TestCase
-from django.core.management import call_command, CommandError
+from edxval import api as api
from mock import patch
+from testfixtures import LogCapture
-from openedx.core.djangoapps.video_config.models import (
- TranscriptMigrationSetting, MigrationEnqueuedCourse
-)
+from openedx.core.djangoapps.video_config.models import MigrationEnqueuedCourse, TranscriptMigrationSetting
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
+from xmodule.video_module import VideoBlock
from xmodule.video_module.transcripts_utils import save_to_store
-from edxval import api as api
-from testfixtures import LogCapture
LOGGER_NAME = "cms.djangoapps.contentstore.tasks"
@@ -93,7 +95,7 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
'client_video_id': 'test1.mp4',
'duration': 42.0,
'status': 'upload',
- 'courses': [unicode(self.course.id)],
+ 'courses': [six.text_type(self.course.id)],
'encoded_videos': [],
'created': datetime.now(pytz.utc)
}
@@ -105,9 +107,9 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
show_captions="false"
download_track="false"
- start_time="00:00:01"
+ start_time="1.0"
download_video="false"
- end_time="00:01:00">
+ end_time="60.0">
@@ -122,9 +124,9 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
show_captions="false"
download_track="false"
- start_time="00:00:01"
+ start_time="1.0"
download_video="false"
- end_time="00:01:00">
+ end_time="60.0">
@@ -133,11 +135,11 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
'''
self.video_descriptor = ItemFactory.create(
parent_location=self.course.location, category='video',
- data={'data': video_sample_xml}
+ **VideoBlock.parse_video_xml(video_sample_xml)
)
self.video_descriptor_2 = ItemFactory.create(
parent_location=self.course_2.location, category='video',
- data={'data': video_sample_xml_2}
+ **VideoBlock.parse_video_xml(video_sample_xml_2)
)
save_to_store(SRT_FILEDATA, 'subs_grmtran1.srt', 'text/srt', self.video_descriptor.location)
@@ -154,7 +156,7 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
self.assertFalse(api.is_transcript_available(self.video_descriptor.edx_video_id, 'ge'))
# now call migrate_transcripts command and check the transcript availability
- call_command('migrate_transcripts', '--course-id', unicode(self.course.id), '--commit')
+ call_command('migrate_transcripts', '--course-id', six.text_type(self.course.id), '--commit')
languages = api.get_available_transcript_languages(self.video_descriptor.edx_video_id)
self.assertEqual(len(languages), 2)
@@ -172,7 +174,7 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
self.assertFalse(api.is_transcript_available(self.video_descriptor.edx_video_id, 'ge'))
# now call migrate_transcripts command and check the transcript availability
- call_command('migrate_transcripts', '--course-id', unicode(self.course.id))
+ call_command('migrate_transcripts', '--course-id', six.text_type(self.course.id))
# check that transcripts still do not exist
languages = api.get_available_transcript_languages(self.video_descriptor.edx_video_id)
@@ -185,12 +187,12 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
Test migrating transcripts
"""
translations = self.video_descriptor.available_translations(self.video_descriptor.get_transcripts_info())
- self.assertItemsEqual(translations, ['hr', 'ge'])
+ six.assertCountEqual(self, translations, ['hr', 'ge'])
self.assertFalse(api.is_transcript_available(self.video_descriptor.edx_video_id, 'hr'))
self.assertFalse(api.is_transcript_available(self.video_descriptor.edx_video_id, 'ge'))
# now call migrate_transcripts command and check the transcript availability
- call_command('migrate_transcripts', '--course-id', unicode(self.course.id), '--commit')
+ call_command('migrate_transcripts', '--course-id', six.text_type(self.course.id), '--commit')
self.assertTrue(api.is_transcript_available(self.video_descriptor.edx_video_id, 'hr'))
self.assertTrue(api.is_transcript_available(self.video_descriptor.edx_video_id, 'ge'))
@@ -200,24 +202,24 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
Test migrating transcripts multiple times
"""
translations = self.video_descriptor.available_translations(self.video_descriptor.get_transcripts_info())
- self.assertItemsEqual(translations, ['hr', 'ge'])
+ six.assertCountEqual(self, translations, ['hr', 'ge'])
self.assertFalse(api.is_transcript_available(self.video_descriptor.edx_video_id, 'hr'))
self.assertFalse(api.is_transcript_available(self.video_descriptor.edx_video_id, 'ge'))
# now call migrate_transcripts command and check the transcript availability
- call_command('migrate_transcripts', '--course-id', unicode(self.course.id), '--commit')
+ call_command('migrate_transcripts', '--course-id', six.text_type(self.course.id), '--commit')
self.assertTrue(api.is_transcript_available(self.video_descriptor.edx_video_id, 'hr'))
self.assertTrue(api.is_transcript_available(self.video_descriptor.edx_video_id, 'ge'))
# now call migrate_transcripts command again and check the transcript availability
- call_command('migrate_transcripts', '--course-id', unicode(self.course.id), '--commit')
+ call_command('migrate_transcripts', '--course-id', six.text_type(self.course.id), '--commit')
self.assertTrue(api.is_transcript_available(self.video_descriptor.edx_video_id, 'hr'))
self.assertTrue(api.is_transcript_available(self.video_descriptor.edx_video_id, 'ge'))
# now call migrate_transcripts command with --force-update and check the transcript availability
- call_command('migrate_transcripts', '--course-id', unicode(self.course.id), '--force-update', '--commit')
+ call_command('migrate_transcripts', '--course-id', six.text_type(self.course.id), '--force-update', '--commit')
self.assertTrue(api.is_transcript_available(self.video_descriptor.edx_video_id, 'hr'))
self.assertTrue(api.is_transcript_available(self.video_descriptor.edx_video_id, 'ge'))
@@ -226,7 +228,7 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
"""
Test migrate transcripts logging and output
"""
- course_id = unicode(self.course.id)
+ course_id = six.text_type(self.course.id)
expected_log = (
(
'cms.djangoapps.contentstore.tasks', 'INFO',
@@ -254,7 +256,7 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
)
with LogCapture(LOGGER_NAME, level=logging.INFO) as logger:
- call_command('migrate_transcripts', '--course-id', unicode(self.course.id))
+ call_command('migrate_transcripts', '--course-id', six.text_type(self.course.id))
logger.check(
*expected_log
)
@@ -263,7 +265,7 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
"""
Test migrate transcripts exception logging
"""
- course_id = unicode(self.course_2.id)
+ course_id = six.text_type(self.course_2.id)
expected_log = (
(
'cms.djangoapps.contentstore.tasks', 'INFO',
@@ -291,7 +293,7 @@ class TestMigrateTranscripts(ModuleStoreTestCase):
)
with LogCapture(LOGGER_NAME, level=logging.INFO) as logger:
- call_command('migrate_transcripts', '--course-id', unicode(self.course_2.id), '--commit')
+ call_command('migrate_transcripts', '--course-id', six.text_type(self.course_2.id), '--commit')
logger.check(
*expected_log
)
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_reindex_courses.py b/cms/djangoapps/contentstore/management/commands/tests/test_reindex_courses.py
index ebcc04fe78..339bc5f769 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_reindex_courses.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_reindex_courses.py
@@ -1,17 +1,19 @@
""" Tests for course reindex command """
+from __future__ import absolute_import
+
import ddt
-from django.core.management import call_command, CommandError
import mock
+from django.core.management import CommandError, call_command
+import six
from six import text_type
+from contentstore.courseware_index import SearchIndexingError
+from contentstore.management.commands.reindex_course import Command as ReindexCommand
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, LibraryFactory
-from contentstore.management.commands.reindex_course import Command as ReindexCommand
-from contentstore.courseware_index import SearchIndexingError
-
@ddt.ddt
class TestReindexCourse(ModuleStoreTestCase):
@@ -103,7 +105,7 @@ class TestReindexCourse(ModuleStoreTestCase):
patched_yes_no.assert_called_once_with(ReindexCommand.CONFIRMATION_PROMPT, default='no')
expected_calls = self._build_calls(self.first_course, self.second_course)
- self.assertItemsEqual(patched_index.mock_calls, expected_calls)
+ six.assertCountEqual(self, patched_index.mock_calls, expected_calls)
def test_given_all_key_prompts_and_reindexes_all_courses_cancelled(self):
""" Test that does not reindex anything when --all key is given and cancelled """
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_reindex_library.py b/cms/djangoapps/contentstore/management/commands/tests/test_reindex_library.py
index 0bb7a58de6..88ce8753d9 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_reindex_library.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_reindex_library.py
@@ -1,18 +1,19 @@
""" Tests for library reindex command """
-import ddt
-from django.core.management import call_command, CommandError
-import mock
+from __future__ import absolute_import
+import ddt
+import mock
+import six
+from django.core.management import CommandError, call_command
+from opaque_keys import InvalidKeyError
+
+from contentstore.courseware_index import SearchIndexingError
+from contentstore.management.commands.reindex_library import Command as ReindexCommand
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, LibraryFactory
-from opaque_keys import InvalidKeyError
-
-from contentstore.management.commands.reindex_library import Command as ReindexCommand
-from contentstore.courseware_index import SearchIndexingError
-
@ddt.ddt
class TestReindexLibrary(ModuleStoreTestCase):
@@ -61,34 +62,34 @@ class TestReindexLibrary(ModuleStoreTestCase):
def test_given_course_key_raises_command_error(self):
""" Test that raises CommandError if course key is passed """
with self.assertRaisesRegexp(CommandError, ".* is not a library key"):
- call_command('reindex_library', unicode(self.first_course.id))
+ call_command('reindex_library', six.text_type(self.first_course.id))
with self.assertRaisesRegexp(CommandError, ".* is not a library key"):
- call_command('reindex_library', unicode(self.second_course.id))
+ call_command('reindex_library', six.text_type(self.second_course.id))
with self.assertRaisesRegexp(CommandError, ".* is not a library key"):
call_command(
'reindex_library',
- unicode(self.second_course.id),
- unicode(self._get_lib_key(self.first_lib))
+ six.text_type(self.second_course.id),
+ six.text_type(self._get_lib_key(self.first_lib))
)
def test_given_id_list_indexes_libraries(self):
""" Test that reindexes libraries when given single library key or a list of library keys """
with mock.patch(self.REINDEX_PATH_LOCATION) as patched_index, \
mock.patch(self.MODULESTORE_PATCH_LOCATION, mock.Mock(return_value=self.store)):
- call_command('reindex_library', unicode(self._get_lib_key(self.first_lib)))
+ call_command('reindex_library', six.text_type(self._get_lib_key(self.first_lib)))
self.assertEqual(patched_index.mock_calls, self._build_calls(self.first_lib))
patched_index.reset_mock()
- call_command('reindex_library', unicode(self._get_lib_key(self.second_lib)))
+ call_command('reindex_library', six.text_type(self._get_lib_key(self.second_lib)))
self.assertEqual(patched_index.mock_calls, self._build_calls(self.second_lib))
patched_index.reset_mock()
call_command(
'reindex_library',
- unicode(self._get_lib_key(self.first_lib)),
- unicode(self._get_lib_key(self.second_lib))
+ six.text_type(self._get_lib_key(self.first_lib)),
+ six.text_type(self._get_lib_key(self.second_lib))
)
expected_calls = self._build_calls(self.first_lib, self.second_lib)
self.assertEqual(patched_index.mock_calls, expected_calls)
@@ -103,7 +104,7 @@ class TestReindexLibrary(ModuleStoreTestCase):
patched_yes_no.assert_called_once_with(ReindexCommand.CONFIRMATION_PROMPT, default='no')
expected_calls = self._build_calls(self.first_lib, self.second_lib)
- self.assertItemsEqual(patched_index.mock_calls, expected_calls)
+ six.assertCountEqual(self, patched_index.mock_calls, expected_calls)
def test_given_all_key_prompts_and_reindexes_all_libraries_cancelled(self):
""" Test that does not reindex anything when --all key is given and cancelled """
@@ -122,4 +123,4 @@ class TestReindexLibrary(ModuleStoreTestCase):
patched_index.side_effect = SearchIndexingError("message", [])
with self.assertRaises(SearchIndexingError):
- call_command('reindex_library', unicode(self._get_lib_key(self.second_lib)))
+ call_command('reindex_library', six.text_type(self._get_lib_key(self.second_lib)))
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_sync_courses.py b/cms/djangoapps/contentstore/management/commands/tests/test_sync_courses.py
new file mode 100644
index 0000000000..9a5f6af4dd
--- /dev/null
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_sync_courses.py
@@ -0,0 +1,78 @@
+"""
+Tests for sync courses management command
+"""
+import mock
+
+from django.core.management import call_command
+from testfixtures import LogCapture
+
+from contentstore.views.course import create_new_course_in_store
+from opaque_keys.edx.keys import CourseKey
+from openedx.core.djangoapps.catalog.tests.factories import CourseRunFactory
+from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
+from student.tests.factories import UserFactory
+from xmodule.modulestore import ModuleStoreEnum
+from xmodule.modulestore.django import modulestore
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
+
+COMMAND_MODULE = 'contentstore.management.commands.sync_courses'
+
+
+@mock.patch(COMMAND_MODULE + '.get_course_runs')
+class TestSyncCoursesCommand(ModuleStoreTestCase):
+ """ Test sync_courses command """
+
+ def setUp(self):
+ super(TestSyncCoursesCommand, self).setUp()
+
+ self.user = UserFactory(username='test', email='test@example.com')
+ self.catalog_course_runs = [
+ CourseRunFactory(),
+ CourseRunFactory(),
+ ]
+
+ def _validate_courses(self):
+ for run in self.catalog_course_runs:
+ course_key = CourseKey.from_string(run.get('key'))
+ self.assertTrue(modulestore().has_course(course_key))
+ CourseOverview.objects.get(id=run.get('key'))
+
+ def test_courses_sync(self, mock_catalog_course_runs):
+ mock_catalog_course_runs.return_value = self.catalog_course_runs
+
+ call_command('sync_courses', self.user.email)
+
+ self._validate_courses()
+
+ def test_duplicate_courses_skipped(self, mock_catalog_course_runs):
+ mock_catalog_course_runs.return_value = self.catalog_course_runs
+ initial_display_name = "Test duplicated course"
+ course_run = self.catalog_course_runs[0]
+ course_key = CourseKey.from_string(course_run.get('key'))
+
+ create_new_course_in_store(
+ ModuleStoreEnum.Type.split,
+ self.user,
+ course_key.org,
+ course_key.course,
+ course_key.run,
+ {
+ "display_name": initial_display_name
+ }
+ )
+
+ with LogCapture() as capture:
+ call_command('sync_courses', self.user.email)
+ expected_message = u"Course already exists for {}, {}, {}. Skipping".format(
+ course_key.org,
+ course_key.course,
+ course_key.run,
+ )
+ capture.check_present(
+ (COMMAND_MODULE, 'WARNING', expected_message)
+ )
+
+ self._validate_courses()
+
+ course = modulestore().get_course(course_key)
+ self.assertEqual(course.display_name, initial_display_name)
diff --git a/cms/djangoapps/contentstore/management/commands/tests/test_video_thumbnails.py b/cms/djangoapps/contentstore/management/commands/tests/test_video_thumbnails.py
index a8a184f512..617b3383b2 100644
--- a/cms/djangoapps/contentstore/management/commands/tests/test_video_thumbnails.py
+++ b/cms/djangoapps/contentstore/management/commands/tests/test_video_thumbnails.py
@@ -2,16 +2,20 @@
"""
Tests for course video thumbnails management command.
"""
+from __future__ import absolute_import
+
import logging
-from mock import patch
-from django.core.management import call_command, CommandError
+
+from django.core.management import CommandError, call_command
from django.test import TestCase
-from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
-from xmodule.modulestore.tests.factories import CourseFactory
-from openedx.core.djangoapps.video_config.models import VideoThumbnailSetting
+from mock import patch
from six import text_type
from testfixtures import LogCapture
+from openedx.core.djangoapps.video_config.models import VideoThumbnailSetting
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
+from xmodule.modulestore.tests.factories import CourseFactory
+
LOGGER_NAME = "contentstore.management.commands.video_thumbnails"
diff --git a/cms/djangoapps/contentstore/management/commands/utils.py b/cms/djangoapps/contentstore/management/commands/utils.py
index 0b248ca526..99de627194 100644
--- a/cms/djangoapps/contentstore/management/commands/utils.py
+++ b/cms/djangoapps/contentstore/management/commands/utils.py
@@ -1,6 +1,8 @@
"""
Common methods for cms commands to use
"""
+from __future__ import absolute_import
+
from django.contrib.auth.models import User
from opaque_keys.edx.keys import CourseKey
diff --git a/cms/djangoapps/contentstore/management/commands/video_thumbnails.py b/cms/djangoapps/contentstore/management/commands/video_thumbnails.py
index e5cb570783..7bfe8c2611 100644
--- a/cms/djangoapps/contentstore/management/commands/video_thumbnails.py
+++ b/cms/djangoapps/contentstore/management/commands/video_thumbnails.py
@@ -1,17 +1,19 @@
"""
Command to scrape thumbnails and add them to the course-videos.
"""
+from __future__ import absolute_import
+
import logging
-from six import text_type
import edxval.api as edxval_api
from django.core.management import BaseCommand
from django.core.management.base import CommandError
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
+from six import text_type
-from openedx.core.djangoapps.video_config.models import VideoThumbnailSetting
from cms.djangoapps.contentstore.tasks import enqueue_update_thumbnail_tasks
+from openedx.core.djangoapps.video_config.models import VideoThumbnailSetting
log = logging.getLogger(__name__)
diff --git a/cms/djangoapps/contentstore/management/commands/xlint.py b/cms/djangoapps/contentstore/management/commands/xlint.py
index 5bcb0b3e2c..09dd5c764d 100644
--- a/cms/djangoapps/contentstore/management/commands/xlint.py
+++ b/cms/djangoapps/contentstore/management/commands/xlint.py
@@ -1,7 +1,8 @@
"""
Verify the structure of courseware as to it's suitability for import
"""
-from __future__ import print_function
+from __future__ import absolute_import, print_function
+
from argparse import REMAINDER
from django.core.management.base import BaseCommand
diff --git a/cms/djangoapps/contentstore/migrations/0001_initial.py b/cms/djangoapps/contentstore/migrations/0001_initial.py
index ef69433f2a..863e4ef408 100644
--- a/cms/djangoapps/contentstore/migrations/0001_initial.py
+++ b/cms/djangoapps/contentstore/migrations/0001_initial.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
import django.db.models.deletion
from django.conf import settings
diff --git a/cms/djangoapps/contentstore/migrations/0002_add_assets_page_flag.py b/cms/djangoapps/contentstore/migrations/0002_add_assets_page_flag.py
index aa3fbecc81..8282223c5f 100644
--- a/cms/djangoapps/contentstore/migrations/0002_add_assets_page_flag.py
+++ b/cms/djangoapps/contentstore/migrations/0002_add_assets_page_flag.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
-from django.db import migrations, models
-from django.conf import settings
import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
from opaque_keys.edx.django.models import CourseKeyField
diff --git a/cms/djangoapps/contentstore/migrations/0003_remove_assets_page_flag.py b/cms/djangoapps/contentstore/migrations/0003_remove_assets_page_flag.py
index 1131abb39a..29c4627bc9 100644
--- a/cms/djangoapps/contentstore/migrations/0003_remove_assets_page_flag.py
+++ b/cms/djangoapps/contentstore/migrations/0003_remove_assets_page_flag.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
from django.db import migrations, models
diff --git a/cms/djangoapps/contentstore/migrations/0004_remove_push_notification_configmodel_table.py b/cms/djangoapps/contentstore/migrations/0004_remove_push_notification_configmodel_table.py
new file mode 100644
index 0000000000..947d4eac93
--- /dev/null
+++ b/cms/djangoapps/contentstore/migrations/0004_remove_push_notification_configmodel_table.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.22 on 2019-07-26 20:12
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('contentstore', '0003_remove_assets_page_flag'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='pushnotificationconfig',
+ name='changed_by',
+ ),
+ migrations.DeleteModel(
+ name='PushNotificationConfig',
+ ),
+ ]
diff --git a/cms/djangoapps/contentstore/models.py b/cms/djangoapps/contentstore/models.py
index fa69d45ec7..9ab2e4ad1b 100644
--- a/cms/djangoapps/contentstore/models.py
+++ b/cms/djangoapps/contentstore/models.py
@@ -2,6 +2,8 @@
Models for contentstore
"""
+from __future__ import absolute_import
+
from config_models.models import ConfigurationModel
from django.db.models.fields import TextField
@@ -21,11 +23,3 @@ class VideoUploadConfig(ConfigurationModel):
def get_profile_whitelist(cls):
"""Get the list of profiles to include in the encoding download"""
return [profile for profile in cls.current().profile_whitelist.split(",") if profile]
-
-
-class PushNotificationConfig(ConfigurationModel):
- """
- Configuration for mobile push notifications.
-
- .. no_pii:
- """
diff --git a/cms/djangoapps/contentstore/proctoring.py b/cms/djangoapps/contentstore/proctoring.py
index b5bc115b10..474180d21b 100644
--- a/cms/djangoapps/contentstore/proctoring.py
+++ b/cms/djangoapps/contentstore/proctoring.py
@@ -2,8 +2,11 @@
Code related to the handling of Proctored Exams in Studio
"""
+from __future__ import absolute_import
+
import logging
+import six
from django.conf import settings
from edx_proctoring.api import (
create_exam,
@@ -36,7 +39,7 @@ def register_special_exams(course_key):
course = modulestore().get_course(course_key)
if course is None:
- raise ItemNotFoundError(u"Course {} does not exist", unicode(course_key))
+ raise ItemNotFoundError(u"Course {} does not exist", six.text_type(course_key))
if not course.enable_proctored_exams and not course.enable_timed_exams:
# likewise if course does not have these features turned on
@@ -66,7 +69,7 @@ def register_special_exams(course_key):
for timed_exam in timed_exams:
msg = (
u'Found {location} as a timed-exam in course structure. Inspecting...'.format(
- location=unicode(timed_exam.location)
+ location=six.text_type(timed_exam.location)
)
)
log.info(msg)
@@ -74,7 +77,7 @@ def register_special_exams(course_key):
exam_metadata = {
'exam_name': timed_exam.display_name,
'time_limit_mins': timed_exam.default_time_limit_minutes,
- 'due_date': timed_exam.due,
+ 'due_date': timed_exam.due if not course.self_paced else None,
'is_proctored': timed_exam.is_proctored_exam,
# backends that support onboarding exams will treat onboarding exams as practice
'is_practice_exam': timed_exam.is_practice_exam or timed_exam.is_onboarding_exam,
@@ -84,7 +87,7 @@ def register_special_exams(course_key):
}
try:
- exam = get_exam_by_content_id(unicode(course_key), unicode(timed_exam.location))
+ exam = get_exam_by_content_id(six.text_type(course_key), six.text_type(timed_exam.location))
# update case, make sure everything is synced
exam_metadata['exam_id'] = exam['id']
@@ -93,8 +96,8 @@ def register_special_exams(course_key):
log.info(msg)
except ProctoredExamNotFoundException:
- exam_metadata['course_id'] = unicode(course_key)
- exam_metadata['content_id'] = unicode(timed_exam.location)
+ exam_metadata['course_id'] = six.text_type(course_key)
+ exam_metadata['content_id'] = six.text_type(timed_exam.location)
exam_id = create_exam(**exam_metadata)
msg = u'Created new timed exam {exam_id}'.format(exam_id=exam_id)
@@ -132,7 +135,7 @@ def register_special_exams(course_key):
search = [
timed_exam for timed_exam in timed_exams if
- unicode(timed_exam.location) == exam['content_id']
+ six.text_type(timed_exam.location) == exam['content_id']
]
if not search:
# This means it was turned off in Studio, we need to mark
diff --git a/cms/djangoapps/contentstore/push_notification.py b/cms/djangoapps/contentstore/push_notification.py
deleted file mode 100644
index e7ee741c44..0000000000
--- a/cms/djangoapps/contentstore/push_notification.py
+++ /dev/null
@@ -1,84 +0,0 @@
-"""
-Helper methods for push notifications from Studio.
-"""
-
-from logging import exception as log_exception
-from uuid import uuid4
-
-from django.conf import settings
-from six import text_type
-
-from contentstore.models import PushNotificationConfig
-from contentstore.tasks import push_course_update_task
-from parse_rest.connection import register
-from parse_rest.core import ParseError
-from parse_rest.installation import Push
-from xmodule.modulestore.django import modulestore
-
-
-def push_notification_enabled():
- """
- Returns whether the push notification feature is enabled.
- """
- return PushNotificationConfig.is_enabled()
-
-
-def enqueue_push_course_update(update, course_key):
- """
- Enqueues a task for push notification for the given update for the given course if
- (1) the feature is enabled and
- (2) push_notification is selected for the update
- """
- if push_notification_enabled() and update.get("push_notification_selected"):
- course = modulestore().get_course(course_key)
- if course:
- push_course_update_task.delay(
- unicode(course_key),
- course.clean_id(padding_char='_'),
- course.display_name
- )
-
-
-def send_push_course_update(course_key_string, course_subscription_id, course_display_name):
- """
- Sends a push notification for a course update, given the course's subscription_id and display_name.
- """
- if settings.PARSE_KEYS:
- try:
- register(
- settings.PARSE_KEYS["APPLICATION_ID"],
- settings.PARSE_KEYS["REST_API_KEY"],
- )
- push_payload = {
- "action": "course.announcement",
- "notification-id": unicode(uuid4()),
-
- "course-id": course_key_string,
- "course-name": course_display_name,
- }
- push_channels = [course_subscription_id]
-
- # Push to all Android devices
- Push.alert(
- data=push_payload,
- channels={"$in": push_channels},
- where={"deviceType": "android"},
- )
-
- # Push to all iOS devices
- # With additional payload so that
- # 1. The push is displayed automatically
- # 2. The app gets it even in the background.
- # See http://stackoverflow.com/questions/19239737/silent-push-notification-in-ios-7-does-not-work
- push_payload.update({
- "alert": "",
- "content-available": 1
- })
- Push.alert(
- data=push_payload,
- channels={"$in": push_channels},
- where={"deviceType": "ios"},
- )
-
- except ParseError as error:
- log_exception(text_type(error))
diff --git a/cms/djangoapps/contentstore/signals/handlers.py b/cms/djangoapps/contentstore/signals/handlers.py
index 8f41582da0..fbf088da65 100644
--- a/cms/djangoapps/contentstore/signals/handlers.py
+++ b/cms/djangoapps/contentstore/signals/handlers.py
@@ -1,9 +1,12 @@
""" receivers of course_published and library_updated events in order to trigger indexing task """
+from __future__ import absolute_import
+
+import logging
from datetime import datetime
from functools import wraps
-import logging
+import six
from django.core.cache import cache
from django.dispatch import receiver
from pytz import UTC
@@ -62,7 +65,7 @@ def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=
# import here, because signal is registered at startup, but items in tasks are not yet able to be loaded
from contentstore.tasks import update_search_index
- update_search_index.delay(unicode(course_key), datetime.now(UTC).isoformat())
+ update_search_index.delay(six.text_type(course_key), datetime.now(UTC).isoformat())
@receiver(SignalHandler.library_updated)
@@ -75,7 +78,7 @@ def listen_for_library_update(sender, library_key, **kwargs): # pylint: disable
# import here, because signal is registered at startup, but items in tasks are not yet able to be loaded
from contentstore.tasks import update_library_index
- update_library_index.delay(unicode(library_key), datetime.now(UTC).isoformat())
+ update_library_index.delay(six.text_type(library_key), datetime.now(UTC).isoformat())
@receiver(SignalHandler.item_deleted)
@@ -113,10 +116,10 @@ def handle_grading_policy_changed(sender, **kwargs):
Receives signal and kicks off celery task to recalculate grades
"""
kwargs = {
- 'course_key': unicode(kwargs.get('course_key')),
- 'grading_policy_hash': unicode(kwargs.get('grading_policy_hash')),
- 'event_transaction_id': unicode(get_event_transaction_id()),
- 'event_transaction_type': unicode(get_event_transaction_type()),
+ 'course_key': six.text_type(kwargs.get('course_key')),
+ 'grading_policy_hash': six.text_type(kwargs.get('grading_policy_hash')),
+ 'event_transaction_id': six.text_type(get_event_transaction_id()),
+ 'event_transaction_type': six.text_type(get_event_transaction_type()),
}
result = task_compute_all_grades_for_course.apply_async(kwargs=kwargs, countdown=GRADING_POLICY_COUNTDOWN_SECONDS)
log.info(u"Grades: Created {task_name}[{task_id}] with arguments {kwargs}".format(
diff --git a/cms/djangoapps/contentstore/signals/signals.py b/cms/djangoapps/contentstore/signals/signals.py
index f73b13b9ea..660f608183 100644
--- a/cms/djangoapps/contentstore/signals/signals.py
+++ b/cms/djangoapps/contentstore/signals/signals.py
@@ -1,6 +1,8 @@
"""
Contentstore signals
"""
+from __future__ import absolute_import
+
from django.dispatch import Signal
# Signal that indicates that a course grading policy has been updated.
diff --git a/cms/djangoapps/contentstore/tasks.py b/cms/djangoapps/contentstore/tasks.py
index f715d4e36b..2e247e1042 100644
--- a/cms/djangoapps/contentstore/tasks.py
+++ b/cms/djangoapps/contentstore/tasks.py
@@ -33,6 +33,7 @@ from organizations.models import OrganizationCourse
from path import Path as path
from pytz import UTC
from six import iteritems, text_type
+from six.moves import range
from user_tasks.models import UserTaskArtifact, UserTaskStatus
from user_tasks.tasks import UserTask
@@ -45,6 +46,7 @@ from models.settings.course_metadata import CourseMetadata
from openedx.core.djangoapps.embargo.models import CountryAccessRule, RestrictedCourse
from openedx.core.lib.extract_tar import safetar_extractall
from student.auth import has_course_author_access
+from util.organizations_helpers import add_organization_course, get_organization_by_short_name
from xmodule.contentstore.django import contentstore
from xmodule.course_module import CourseFields
from xmodule.exceptions import SerializationError
@@ -101,7 +103,7 @@ def enqueue_update_thumbnail_tasks(course_videos, videos_per_task, run):
start = 0
end = videos_per_task
chunks_count = int(ceil(batch_size / float(videos_per_task)))
- for __ in xrange(0, chunks_count):
+ for __ in range(0, chunks_count): # pylint: disable=C7620
course_videos_chunk = course_videos[start:end]
tasks.append(task_scrape_youtube_thumbnail.s(
course_videos_chunk, run
@@ -197,7 +199,7 @@ def enqueue_async_migrate_transcripts_tasks(course_keys,
'command_run': command_run
}
group([
- async_migrate_transcript.s(unicode(course_key), **kwargs)
+ async_migrate_transcript.s(text_type(course_key), **kwargs)
for course_key in course_keys
])()
@@ -264,7 +266,7 @@ def async_migrate_transcript(self, course_key, **kwargs): # pylint: disable=un
all_transcripts.update({'en': video.sub})
sub_tasks = []
- video_location = unicode(video.location)
+ video_location = text_type(video.location)
for lang in all_transcripts:
sub_tasks.append(async_migrate_transcript_subtask.s(
video_location, revision, lang, force_update, **kwargs
@@ -484,6 +486,8 @@ def rerun_course(source_course_key_string, destination_course_key_string, user_i
for country_access_rule in country_access_rules:
clone_instance(country_access_rule, {'restricted_course': new_restricted_course})
+ org_data = get_organization_by_short_name(source_course_key.org)
+ add_organization_course(org_data, destination_course_key)
return "succeeded"
except DuplicateCourseError:
@@ -550,16 +554,6 @@ def update_library_index(library_id, triggered_time_isoformat):
LOGGER.debug(u'Search indexing successful for library %s', library_id)
-@task()
-def push_course_update_task(course_key_string, course_subscription_id, course_display_name):
- """
- Sends a push notification for a course update.
- """
- # TODO Use edx-notifications library instead (MA-638).
- from .push_notification import send_push_course_update
- send_push_course_update(course_key_string, course_subscription_id, course_display_name)
-
-
class CourseExportTask(UserTask): # pylint: disable=abstract-method
"""
Base class for course and library export tasks.
diff --git a/cms/djangoapps/contentstore/tests/test_clone_course.py b/cms/djangoapps/contentstore/tests/test_clone_course.py
index 9ef484c91b..92cfded0ab 100644
--- a/cms/djangoapps/contentstore/tests/test_clone_course.py
+++ b/cms/djangoapps/contentstore/tests/test_clone_course.py
@@ -1,8 +1,11 @@
"""
Unit tests for cloning a course between the same and different module stores.
"""
+from __future__ import absolute_import
+
import json
+import six
from django.conf import settings
from mock import Mock, patch
from opaque_keys.edx.locator import CourseLocator
@@ -82,8 +85,8 @@ class CloneCourseTest(CourseTestCase):
split_rerun_id = CourseLocator(org=org, course=course_number, run="2012_Q2")
CourseRerunState.objects.initiated(course.id, split_rerun_id, self.user, fields['display_name'])
result = rerun_course.delay(
- unicode(course.id),
- unicode(split_rerun_id),
+ six.text_type(course.id),
+ six.text_type(split_rerun_id),
self.user.id,
json.dumps(fields, cls=EdxJSONEncoder)
)
@@ -106,7 +109,7 @@ class CloneCourseTest(CourseTestCase):
# Mark the action as initiated
fields = {'display_name': 'rerun'}
CourseRerunState.objects.initiated(mongo_course1_id, split_course3_id, self.user, fields['display_name'])
- result = rerun_course.delay(unicode(mongo_course1_id), unicode(split_course3_id), self.user.id,
+ result = rerun_course.delay(six.text_type(mongo_course1_id), six.text_type(split_course3_id), self.user.id,
json.dumps(fields, cls=EdxJSONEncoder))
self.assertEqual(result.get(), "succeeded")
self.assertTrue(has_course_author_access(self.user, split_course3_id), "Didn't grant access")
@@ -114,7 +117,7 @@ class CloneCourseTest(CourseTestCase):
self.assertEqual(rerun_state.state, CourseRerunUIStateManager.State.SUCCEEDED)
# try creating rerunning again to same name and ensure it generates error
- result = rerun_course.delay(unicode(mongo_course1_id), unicode(split_course3_id), self.user.id)
+ result = rerun_course.delay(six.text_type(mongo_course1_id), six.text_type(split_course3_id), self.user.id)
self.assertEqual(result.get(), "duplicate course")
# the below will raise an exception if the record doesn't exist
CourseRerunState.objects.find_first(
@@ -127,7 +130,7 @@ class CloneCourseTest(CourseTestCase):
split_course4_id = CourseLocator(org="edx3", course="split3", run="rerun_fail")
fields = {'display_name': 'total failure'}
CourseRerunState.objects.initiated(split_course3_id, split_course4_id, self.user, fields['display_name'])
- result = rerun_course.delay(unicode(split_course3_id), unicode(split_course4_id), self.user.id,
+ result = rerun_course.delay(six.text_type(split_course3_id), six.text_type(split_course4_id), self.user.id,
json.dumps(fields, cls=EdxJSONEncoder))
self.assertIn("exception: ", result.get())
self.assertIsNone(self.store.get_course(split_course4_id), "Didn't delete course after error")
diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py
index 5b48c6fd9e..fb60589b53 100644
--- a/cms/djangoapps/contentstore/tests/test_contentstore.py
+++ b/cms/djangoapps/contentstore/tests/test_contentstore.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-from __future__ import print_function
+from __future__ import absolute_import, print_function
import copy
import shutil
@@ -14,6 +14,7 @@ from uuid import uuid4
import ddt
import lxml.html
import mock
+import six
from django.conf import settings
from django.contrib.auth.models import User
from django.middleware.csrf import _compare_salted_tokens
@@ -27,12 +28,13 @@ from opaque_keys.edx.keys import AssetKey, CourseKey, UsageKey
from opaque_keys.edx.locations import CourseLocator
from path import Path as path
from six import text_type
+from six.moves import range
from waffle.testutils import override_switch
+from contentstore.config import waffle
from contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase, get_url, parse_json
from contentstore.utils import delete_course, reverse_course_url, reverse_url
from contentstore.views.component import ADVANCED_COMPONENT_TYPES
-from contentstore.config import waffle
from course_action_state.managers import CourseActionStateItemNotFoundError
from course_action_state.models import CourseRerunState, CourseRerunUIStateManager
from openedx.core.djangoapps.django_comment_common.utils import are_permissions_roles_seeded
@@ -54,6 +56,7 @@ from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, chec
from xmodule.modulestore.xml_exporter import export_course_to_xml
from xmodule.modulestore.xml_importer import import_course_from_xml, perform_xlint
from xmodule.seq_module import SequenceDescriptor
+from xmodule.video_module import VideoBlock
TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex
@@ -676,7 +679,7 @@ class MiscCourseTests(ContentStoreTestCase):
self.assertEqual(resp.status_code, 200)
for expected in expected_types:
- self.assertIn(expected, resp.content)
+ self.assertIn(expected, resp.content.decode('utf-8'))
@ddt.data("", "alert('hi')", "")
def test_container_handler_xss_prevent(self, malicious_code):
@@ -1815,15 +1818,15 @@ class MetadataSaveTestCase(ContentStoreTestCase):
"""
self.video_descriptor = ItemFactory.create(
parent_location=course.location, category='video',
- data={'data': video_sample_xml}
+ **VideoBlock.parse_video_xml(video_sample_xml)
)
def test_metadata_not_persistence(self):
@@ -1840,7 +1843,6 @@ class MetadataSaveTestCase(ContentStoreTestCase):
'youtube_id_1_5',
'start_time',
'end_time',
- 'source',
'html5_sources',
'track'
}
@@ -1930,7 +1932,7 @@ class RerunCourseTest(ContentStoreTestCase):
'course_key': destination_course_key,
'should_display': True,
}
- for field_name, expected_value in expected_states.iteritems():
+ for field_name, expected_value in six.iteritems(expected_states):
self.assertEquals(getattr(rerun_state, field_name), expected_value)
# Verify that the creator is now enrolled in the course.
diff --git a/cms/djangoapps/contentstore/tests/test_core_caching.py b/cms/djangoapps/contentstore/tests/test_core_caching.py
index 6c606d596c..2421b8e2a0 100644
--- a/cms/djangoapps/contentstore/tests/test_core_caching.py
+++ b/cms/djangoapps/contentstore/tests/test_core_caching.py
@@ -2,6 +2,8 @@
Tests core caching facilities.
"""
+from __future__ import absolute_import
+
from django.test import TestCase
from opaque_keys.edx.locator import AssetLocator, CourseLocator
diff --git a/cms/djangoapps/contentstore/tests/test_course_create_rerun.py b/cms/djangoapps/contentstore/tests/test_course_create_rerun.py
index da2c19affa..d7189c016c 100644
--- a/cms/djangoapps/contentstore/tests/test_course_create_rerun.py
+++ b/cms/djangoapps/contentstore/tests/test_course_create_rerun.py
@@ -1,11 +1,14 @@
"""
Test view handler for rerun (and eventually create)
"""
+from __future__ import absolute_import
+
import datetime
import ddt
-from django.urls import reverse
+import six
from django.test.client import RequestFactory
+from django.urls import reverse
from mock import patch
from opaque_keys.edx.keys import CourseKey
@@ -63,12 +66,18 @@ class TestCourseListing(ModuleStoreTestCase):
self.client.logout()
ModuleStoreTestCase.tearDown(self)
+ @patch.dict('django.conf.settings.FEATURES', {'ORGANIZATIONS_APP': True})
def test_rerun(self):
"""
Just testing the functionality the view handler adds over the tasks tested in test_clone_course
"""
+ add_organization({
+ 'name': 'Test Organization',
+ 'short_name': self.source_course_key.org,
+ 'description': 'Testing Organization Description',
+ })
response = self.client.ajax_post(self.course_create_rerun_url, {
- 'source_course_key': unicode(self.source_course_key),
+ 'source_course_key': six.text_type(self.source_course_key),
'org': self.source_course_key.org, 'course': self.source_course_key.course, 'run': 'copy',
'display_name': 'not the same old name',
})
@@ -83,6 +92,9 @@ class TestCourseListing(ModuleStoreTestCase):
self.assertEqual(dest_course.end, source_course.end)
self.assertEqual(dest_course.enrollment_start, None)
self.assertEqual(dest_course.enrollment_end, None)
+ course_orgs = get_course_organizations(dest_course_key)
+ self.assertEqual(len(course_orgs), 1)
+ self.assertEqual(course_orgs[0]['short_name'], self.source_course_key.org)
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_newly_created_course_has_web_certs_enabled(self, store):
diff --git a/cms/djangoapps/contentstore/tests/test_course_listing.py b/cms/djangoapps/contentstore/tests/test_course_listing.py
index 8cc49506fa..99dd9e92fe 100644
--- a/cms/djangoapps/contentstore/tests/test_course_listing.py
+++ b/cms/djangoapps/contentstore/tests/test_course_listing.py
@@ -2,6 +2,8 @@
Unit tests for getting the list of courses for a user through iterating all courses and
by reversing group name formats.
"""
+from __future__ import absolute_import
+
import random
import ddt
@@ -10,6 +12,7 @@ from django.conf import settings
from django.test import RequestFactory
from mock import Mock, patch
from opaque_keys.edx.locations import CourseLocator
+from six.moves import range
from contentstore.tests.utils import AjaxEnabledTestClient
from contentstore.utils import delete_course
@@ -172,7 +175,7 @@ class TestCourseListing(ModuleStoreTestCase):
with self.store.default_store(default_store):
# Create few courses
- for num in xrange(TOTAL_COURSES_COUNT):
+ for num in range(TOTAL_COURSES_COUNT):
course_location = self.store.make_course_key('Org', 'CreatedCourse' + str(num), 'Run')
self._create_course_with_access_groups(course_location, self.user, default_store)
@@ -249,7 +252,7 @@ class TestCourseListing(ModuleStoreTestCase):
reversing django groups
"""
# create list of random course numbers which will be accessible to the user
- user_course_ids = random.sample(range(TOTAL_COURSES_COUNT), USER_COURSES_COUNT)
+ user_course_ids = random.sample(list(range(TOTAL_COURSES_COUNT)), USER_COURSES_COUNT)
# create courses and assign those to the user which have their number in user_course_ids
with self.store.default_store(store):
diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py
index 3a1372fd33..e0a7bc5df6 100644
--- a/cms/djangoapps/contentstore/tests/test_course_settings.py
+++ b/cms/djangoapps/contentstore/tests/test_course_settings.py
@@ -1,26 +1,28 @@
"""
Tests for Studio Course Settings.
"""
+from __future__ import absolute_import
+
import copy
import datetime
import json
import unittest
import ddt
+import mock
+import six
+from crum import set_current_request
from django.conf import settings
from django.test import RequestFactory
from django.test.utils import override_settings
-from pytz import UTC
-import mock
-from mock import Mock, patch
-from crum import set_current_request
-from milestones.tests.utils import MilestonesTestCaseMixin
-
-
-from contentstore.utils import reverse_course_url, reverse_usage_url
-from contentstore.config.waffle import ENABLE_PROCTORING_PROVIDER_OVERRIDES
from milestones.models import MilestoneRelationshipType
-from models.settings.course_grading import CourseGradingModel, GRADING_POLICY_CHANGED_EVENT_TYPE, hash_grading_policy
+from milestones.tests.utils import MilestonesTestCaseMixin
+from mock import Mock, patch
+from pytz import UTC
+
+from contentstore.config.waffle import ENABLE_PROCTORING_PROVIDER_OVERRIDES
+from contentstore.utils import reverse_course_url, reverse_usage_url
+from models.settings.course_grading import GRADING_POLICY_CHANGED_EVENT_TYPE, CourseGradingModel, hash_grading_policy
from models.settings.course_metadata import CourseMetadata
from models.settings.encoder import CourseSettingsEncoder
from openedx.core.djangoapps.models.course_details import CourseDetails
@@ -106,7 +108,7 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin):
payload['enrollment_start'] = CourseDetailsViewTest.convert_datetime_to_iso(details.enrollment_start)
payload['enrollment_end'] = CourseDetailsViewTest.convert_datetime_to_iso(details.enrollment_end)
resp = self.client.ajax_post(url, payload)
- self.compare_details_with_encoding(json.loads(resp.content), details.__dict__, field + str(val))
+ self.compare_details_with_encoding(json.loads(resp.content.decode('utf-8')), details.__dict__, field + str(val))
MilestoneRelationshipType.objects.get_or_create(name='requires')
MilestoneRelationshipType.objects.get_or_create(name='fulfills')
@@ -124,7 +126,7 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin):
# resp s/b json from here on
url = get_url(self.course.id)
resp = self.client.get_json(url)
- self.compare_details_with_encoding(json.loads(resp.content), details.__dict__, "virgin get")
+ self.compare_details_with_encoding(json.loads(resp.content.decode('utf-8')), details.__dict__, "virgin get")
self.alter_field(url, details, 'start_date', datetime.datetime(2012, 11, 12, 1, 30, tzinfo=UTC))
self.alter_field(url, details, 'start_date', datetime.datetime(2012, 11, 1, 13, 30, tzinfo=UTC))
@@ -190,20 +192,20 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin):
url = get_url(self.course.id)
resp = self.client.get_json(url)
- course_detail_json = json.loads(resp.content)
+ course_detail_json = json.loads(resp.content.decode('utf-8'))
# assert pre_requisite_courses is initialized
self.assertEqual([], course_detail_json['pre_requisite_courses'])
# update pre requisite courses with a new course keys
pre_requisite_course = CourseFactory.create(org='edX', course='900', run='test_run')
pre_requisite_course2 = CourseFactory.create(org='edX', course='902', run='test_run')
- pre_requisite_course_keys = [unicode(pre_requisite_course.id), unicode(pre_requisite_course2.id)]
+ pre_requisite_course_keys = [six.text_type(pre_requisite_course.id), six.text_type(pre_requisite_course2.id)]
course_detail_json['pre_requisite_courses'] = pre_requisite_course_keys
self.client.ajax_post(url, course_detail_json)
# fetch updated course to assert pre_requisite_courses has new values
resp = self.client.get_json(url)
- course_detail_json = json.loads(resp.content)
+ course_detail_json = json.loads(resp.content.decode('utf-8'))
self.assertEqual(pre_requisite_course_keys, course_detail_json['pre_requisite_courses'])
self.assertTrue(milestones_helpers.any_unfulfilled_milestones(self.course.id, self.user.id),
@@ -213,7 +215,7 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin):
course_detail_json['pre_requisite_courses'] = []
self.client.ajax_post(url, course_detail_json)
resp = self.client.get_json(url)
- course_detail_json = json.loads(resp.content)
+ course_detail_json = json.loads(resp.content.decode('utf-8'))
self.assertEqual([], course_detail_json['pre_requisite_courses'])
self.assertFalse(milestones_helpers.any_unfulfilled_milestones(self.course.id, self.user.id),
@@ -223,11 +225,11 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin):
def test_invalid_pre_requisite_course(self):
url = get_url(self.course.id)
resp = self.client.get_json(url)
- course_detail_json = json.loads(resp.content)
+ course_detail_json = json.loads(resp.content.decode('utf-8'))
# update pre requisite courses one valid and one invalid key
pre_requisite_course = CourseFactory.create(org='edX', course='900', run='test_run')
- pre_requisite_course_keys = [unicode(pre_requisite_course.id), 'invalid_key']
+ pre_requisite_course_keys = [six.text_type(pre_requisite_course.id), 'invalid_key']
course_detail_json['pre_requisite_courses'] = pre_requisite_course_keys
response = self.client.ajax_post(url, course_detail_json)
self.assertEqual(400, response.status_code)
@@ -252,7 +254,7 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin):
resp = self.client.get_html(course_details_url)
self.assertEqual(
feature_flags[2],
- '
' in resp.content
+ b'' in resp.content
)
@override_settings(MKTG_URLS={'ROOT': 'dummy-root'})
@@ -513,10 +515,10 @@ class CourseGradingTest(CourseTestCase):
mock.call(
GRADING_POLICY_CHANGED_EVENT_TYPE,
{
- 'course_id': unicode(self.course.id),
+ 'course_id': six.text_type(self.course.id),
'event_transaction_type': 'edx.grades.grading_policy_changed',
'grading_policy_hash': policy_hash,
- 'user_id': unicode(self.user.id),
+ 'user_id': six.text_type(self.user.id),
'event_transaction_id': 'mockUUID',
}
) for policy_hash in (
@@ -562,10 +564,10 @@ class CourseGradingTest(CourseTestCase):
mock.call(
GRADING_POLICY_CHANGED_EVENT_TYPE,
{
- 'course_id': unicode(self.course.id),
+ 'course_id': six.text_type(self.course.id),
'event_transaction_type': 'edx.grades.grading_policy_changed',
'grading_policy_hash': policy_hash,
- 'user_id': unicode(self.user.id),
+ 'user_id': six.text_type(self.user.id),
'event_transaction_id': 'mockUUID',
}
) for policy_hash in {grading_policy_1, grading_policy_2, grading_policy_3}
@@ -600,10 +602,10 @@ class CourseGradingTest(CourseTestCase):
mock.call(
GRADING_POLICY_CHANGED_EVENT_TYPE,
{
- 'course_id': unicode(self.course.id),
+ 'course_id': six.text_type(self.course.id),
'event_transaction_type': 'edx.grades.grading_policy_changed',
'grading_policy_hash': policy_hash,
- 'user_id': unicode(self.user.id),
+ 'user_id': six.text_type(self.user.id),
'event_transaction_id': 'mockUUID',
}
) for policy_hash in (grading_policy_1, grading_policy_2, grading_policy_3)
@@ -677,10 +679,10 @@ class CourseGradingTest(CourseTestCase):
mock.call(
GRADING_POLICY_CHANGED_EVENT_TYPE,
{
- 'course_id': unicode(self.course.id),
+ 'course_id': six.text_type(self.course.id),
'event_transaction_type': 'edx.grades.grading_policy_changed',
'grading_policy_hash': policy_hash,
- 'user_id': unicode(self.user.id),
+ 'user_id': six.text_type(self.user.id),
'event_transaction_id': 'mockUUID',
}
) for policy_hash in (grading_policy_1, grading_policy_2)
@@ -688,7 +690,7 @@ class CourseGradingTest(CourseTestCase):
def _model_from_url(self, url_base):
response = self.client.get_json(url_base)
- return json.loads(response.content)
+ return json.loads(response.content.decode('utf-8'))
def test_get_set_grader_types_ajax(self):
"""
@@ -733,7 +735,7 @@ class CourseGradingTest(CourseTestCase):
)
grading_policy_hash1 = self._grading_policy_hash_for_course()
self.assertEqual(200, response.status_code)
- grader_sample = json.loads(response.content)
+ grader_sample = json.loads(response.content.decode('utf-8'))
new_grader['id'] = len(original_model['graders'])
self.assertEqual(new_grader, grader_sample)
@@ -773,12 +775,12 @@ class CourseGradingTest(CourseTestCase):
response = self.client.ajax_post(grade_type_url, {'graderType': u'Homework'})
self.assertEqual(200, response.status_code)
response = self.client.get_json(grade_type_url + '?fields=graderType')
- self.assertEqual(json.loads(response.content).get('graderType'), u'Homework')
+ self.assertEqual(json.loads(response.content.decode('utf-8')).get('graderType'), u'Homework')
# and unset
response = self.client.ajax_post(grade_type_url, {'graderType': u'notgraded'})
self.assertEqual(200, response.status_code)
response = self.client.get_json(grade_type_url + '?fields=graderType')
- self.assertEqual(json.loads(response.content).get('graderType'), u'notgraded')
+ self.assertEqual(json.loads(response.content.decode('utf-8')).get('graderType'), u'notgraded')
def _grading_policy_hash_for_course(self):
return hash_grading_policy(modulestore().get_course(self.course.id).grading_policy)
@@ -1090,12 +1092,12 @@ class CourseMetadataEditingTest(CourseTestCase):
def test_http_fetch_initial_fields(self):
response = self.client.get_json(self.course_setting_url)
- test_model = json.loads(response.content)
+ test_model = json.loads(response.content.decode('utf-8'))
self.assertIn('display_name', test_model, 'Missing editable metadata field')
self.assertEqual(test_model['display_name']['value'], self.course.display_name)
response = self.client.get_json(self.fullcourse_setting_url)
- test_model = json.loads(response.content)
+ test_model = json.loads(response.content.decode('utf-8'))
self.assertNotIn('graceperiod', test_model, 'blacklisted field leaked in')
self.assertIn('display_name', test_model, 'full missing editable metadata field')
self.assertEqual(test_model['display_name']['value'], self.fullcourse.display_name)
@@ -1108,18 +1110,18 @@ class CourseMetadataEditingTest(CourseTestCase):
"advertised_start": {"value": "start A"},
"days_early_for_beta": {"value": 2},
})
- test_model = json.loads(response.content)
+ test_model = json.loads(response.content.decode('utf-8'))
self.update_check(test_model)
response = self.client.get_json(self.course_setting_url)
- test_model = json.loads(response.content)
+ test_model = json.loads(response.content.decode('utf-8'))
self.update_check(test_model)
# now change some of the existing metadata
response = self.client.ajax_post(self.course_setting_url, {
"advertised_start": {"value": "start B"},
"display_name": {"value": "jolly roger"}
})
- test_model = json.loads(response.content)
+ test_model = json.loads(response.content.decode('utf-8'))
self.assertIn('display_name', test_model, 'Missing editable metadata field')
self.assertEqual(test_model['display_name']['value'], 'jolly roger', "not expected value")
self.assertIn('advertised_start', test_model, 'Missing revised advertised_start metadata field')
@@ -1159,7 +1161,7 @@ class CourseMetadataEditingTest(CourseTestCase):
'model': {'display_name': 'Tabs Exception'}
}
]
- self.assertEqual(json.loads(resp.content), error_msg)
+ self.assertEqual(json.loads(resp.content.decode('utf-8')), error_msg)
# verify that the course wasn't saved into the modulestore
course = modulestore().get_course(self.course.id)
@@ -1399,7 +1401,7 @@ class CourseGraderUpdatesTest(CourseTestCase):
"""Test getting a specific grading type record."""
resp = self.client.get_json(self.url + '/0')
self.assertEqual(resp.status_code, 200)
- obj = json.loads(resp.content)
+ obj = json.loads(resp.content.decode('utf-8'))
self.assertEqual(self.starting_graders[0], obj)
def test_delete(self):
@@ -1422,7 +1424,7 @@ class CourseGraderUpdatesTest(CourseTestCase):
}
resp = self.client.ajax_post(self.url + '/0', grader)
self.assertEqual(resp.status_code, 200)
- obj = json.loads(resp.content)
+ obj = json.loads(resp.content.decode('utf-8'))
self.assertEqual(obj, grader)
current_graders = CourseGradingModel.fetch(self.course.id).graders
self.assertEqual(len(self.starting_graders), len(current_graders))
@@ -1441,7 +1443,7 @@ class CourseGraderUpdatesTest(CourseTestCase):
}
resp = self.client.ajax_post('{}/{}'.format(self.url, len(self.starting_graders) + 1), grader)
self.assertEqual(resp.status_code, 200)
- obj = json.loads(resp.content)
+ obj = json.loads(resp.content.decode('utf-8'))
self.assertEqual(obj['id'], len(self.starting_graders))
del obj['id']
self.assertEqual(obj, grader)
@@ -1491,7 +1493,7 @@ id=\"course-enrollment-end-time\" value=\"\" placeholder=\"HH:MM\" autocomplete=
"""
super(CourseEnrollmentEndFieldTest, self).setUp()
self.course = CourseFactory.create(org='edX', number='dummy', display_name='Marketing Site Course')
- self.course_details_url = reverse_course_url('settings_handler', unicode(self.course.id))
+ self.course_details_url = reverse_course_url('settings_handler', six.text_type(self.course.id))
def _get_course_details_response(self, global_staff):
"""
diff --git a/cms/djangoapps/contentstore/tests/test_courseware_index.py b/cms/djangoapps/contentstore/tests/test_courseware_index.py
index 880f160bc9..89f0357868 100644
--- a/cms/djangoapps/contentstore/tests/test_courseware_index.py
+++ b/cms/djangoapps/contentstore/tests/test_courseware_index.py
@@ -1,7 +1,8 @@
"""
Testing indexing of the courseware as it is changed
"""
-from __future__ import print_function
+from __future__ import absolute_import, print_function
+
import json
import time
from datetime import datetime
@@ -10,11 +11,13 @@ from uuid import uuid4
import ddt
import pytest
+import six
from django.conf import settings
from lazy.lazy import lazy
from mock import patch
from pytz import UTC
from search.search_engine_base import SearchEngine
+from six.moves import range
from contentstore.courseware_index import (
CourseAboutSearchIndexer,
@@ -259,7 +262,7 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
)
def _get_default_search(self):
- return {"course": unicode(self.course.id)}
+ return {"course": six.text_type(self.course.id)}
def _test_indexing_course(self, store):
""" indexing course tests """
@@ -383,10 +386,10 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
results = response["results"]
date_map = {
- unicode(self.chapter.location): early_date,
- unicode(self.sequential.location): early_date,
- unicode(self.vertical.location): later_date,
- unicode(self.html_unit.location): later_date,
+ six.text_type(self.chapter.location): early_date,
+ six.text_type(self.sequential.location): early_date,
+ six.text_type(self.vertical.location): later_date,
+ six.text_type(self.html_unit.location): later_date,
}
for result in results:
self.assertEqual(result["data"]["start_date"], date_map[result["data"]["id"]])
@@ -449,7 +452,7 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
self.reindex_course(store)
response = self.searcher.search(
doc_type=CourseAboutSearchIndexer.DISCOVERY_DOCUMENT_TYPE,
- field_dictionary={"course": unicode(self.course.id)}
+ field_dictionary={"course": six.text_type(self.course.id)}
)
self.assertEqual(response["total"], 1)
self.assertEqual(response["results"][0]["data"]["content"]["display_name"], display_name)
@@ -463,7 +466,7 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
self.reindex_course(store)
response = self.searcher.search(
doc_type=CourseAboutSearchIndexer.DISCOVERY_DOCUMENT_TYPE,
- field_dictionary={"course": unicode(self.course.id)}
+ field_dictionary={"course": six.text_type(self.course.id)}
)
self.assertEqual(response["total"], 1)
self.assertEqual(response["results"][0]["data"]["content"]["short_description"], short_description)
@@ -471,13 +474,13 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
def _test_course_about_mode_index(self, store):
""" Test that informational properties in the course modes store end up in the course_info index """
honour_mode = CourseMode(
- course_id=unicode(self.course.id),
+ course_id=six.text_type(self.course.id),
mode_slug=CourseMode.HONOR,
mode_display_name=CourseMode.HONOR
)
honour_mode.save()
verified_mode = CourseMode(
- course_id=unicode(self.course.id),
+ course_id=six.text_type(self.course.id),
mode_slug=CourseMode.VERIFIED,
mode_display_name=CourseMode.VERIFIED,
min_price=1
@@ -487,7 +490,7 @@ class TestCoursewareSearchIndexer(MixedWithOptionsTestCase):
response = self.searcher.search(
doc_type=CourseAboutSearchIndexer.DISCOVERY_DOCUMENT_TYPE,
- field_dictionary={"course": unicode(self.course.id)}
+ field_dictionary={"course": six.text_type(self.course.id)}
)
self.assertEqual(response["total"], 1)
self.assertIn(CourseMode.HONOR, response["results"][0]["data"]["modes"])
@@ -638,13 +641,13 @@ class TestLargeCourseDeletions(MixedWithOptionsTestCase):
""" Test that deleting items from a course works even when present within a very large course """
def id_list(top_parent_object):
""" private function to get ids from object down the tree """
- list_of_ids = [unicode(top_parent_object.location)]
+ list_of_ids = [six.text_type(top_parent_object.location)]
for child in top_parent_object.get_children():
list_of_ids.extend(id_list(child))
return list_of_ids
course, course_size = create_large_course(store, load_factor)
- self.course_id = unicode(course.id)
+ self.course_id = six.text_type(course.id)
# index full course
CoursewareSearchIndexer.do_course_reindex(store, course.id)
@@ -753,7 +756,7 @@ class TestTaskExecution(SharedModuleStoreTestCase):
searcher = SearchEngine.get_search_engine(CoursewareSearchIndexer.INDEX_NAME)
response = searcher.search(
doc_type=CoursewareSearchIndexer.DOCUMENT_TYPE,
- field_dictionary={"course": unicode(self.course.id)}
+ field_dictionary={"course": six.text_type(self.course.id)}
)
self.assertEqual(response["total"], 0)
@@ -762,14 +765,14 @@ class TestTaskExecution(SharedModuleStoreTestCase):
# Note that this test will only succeed if celery is working in inline mode
response = searcher.search(
doc_type=CoursewareSearchIndexer.DOCUMENT_TYPE,
- field_dictionary={"course": unicode(self.course.id)}
+ field_dictionary={"course": six.text_type(self.course.id)}
)
self.assertEqual(response["total"], 3)
def test_task_library_update(self):
""" Making sure that the receiver correctly fires off the task when invoked by signal """
searcher = SearchEngine.get_search_engine(LibrarySearchIndexer.INDEX_NAME)
- library_search_key = unicode(normalize_key_for_search(self.library.location.library_key))
+ library_search_key = six.text_type(normalize_key_for_search(self.library.location.library_key))
response = searcher.search(field_dictionary={"library": library_search_key})
self.assertEqual(response["total"], 0)
@@ -821,7 +824,7 @@ class TestLibrarySearchIndexer(MixedWithOptionsTestCase):
def _get_default_search(self):
""" Returns field_dictionary for default search """
- return {"library": unicode(self.library.location.library_key.replace(version_guid=None, branch=None))}
+ return {"library": six.text_type(self.library.location.library_key.replace(version_guid=None, branch=None))}
def reindex_library(self, store):
""" kick off complete reindex of the course """
@@ -1177,9 +1180,9 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
"""
return {
'course_name': self.course.display_name,
- 'id': unicode(html_unit.location),
+ 'id': six.text_type(html_unit.location),
'content': {'html_content': '', 'display_name': html_unit.display_name},
- 'course': unicode(self.course.id),
+ 'course': six.text_type(self.course.id),
'location': [
self.chapter.display_name,
self.sequential.display_name,
@@ -1197,9 +1200,9 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
"""
return {
'course_name': self.course.display_name,
- 'id': unicode(html_unit.location),
+ 'id': six.text_type(html_unit.location),
'content': {'html_content': '', 'display_name': html_unit.display_name},
- 'course': unicode(self.course.id),
+ 'course': six.text_type(self.course.id),
'location': [
self.chapter.display_name,
self.sequential2.display_name,
@@ -1218,7 +1221,7 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
return {
'start_date': datetime(2015, 4, 1, 0, 0, tzinfo=UTC),
'content': {'display_name': vertical.display_name},
- 'course': unicode(self.course.id),
+ 'course': six.text_type(self.course.id),
'location': [
self.chapter.display_name,
self.sequential2.display_name,
@@ -1226,7 +1229,7 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
],
'content_type': 'Sequence',
'content_groups': content_groups,
- 'id': unicode(vertical.location),
+ 'id': six.text_type(vertical.location),
'course_name': self.course.display_name,
'org': self.course.org
}
@@ -1237,9 +1240,9 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
"""
return {
'course_name': self.course.display_name,
- 'id': unicode(html_unit.location),
+ 'id': six.text_type(html_unit.location),
'content': {'html_content': '', 'display_name': html_unit.display_name},
- 'course': unicode(self.course.id),
+ 'course': six.text_type(self.course.id),
'location': [
self.chapter.display_name,
self.sequential.display_name,
@@ -1269,7 +1272,7 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
# Only published modules should be in the index
added_to_index = self.reindex_course(self.store)
self.assertEqual(added_to_index, 16)
- response = self.searcher.search(field_dictionary={"course": unicode(self.course.id)})
+ response = self.searcher.search(field_dictionary={"course": six.text_type(self.course.id)})
self.assertEqual(response["total"], 17)
group_access_content = {'group_access': {666: [1]}}
@@ -1287,44 +1290,44 @@ class GroupConfigurationSearchMongo(CourseTestCase, MixedWithOptionsTestCase):
self.assertTrue(mock_index.called)
indexed_content = self._get_index_values_from_call_args(mock_index)
self.assertIn(self._html_group_result(self.html_unit1, [1]), indexed_content)
- self.assertIn(self._html_experiment_group_result(self.html_unit4, [unicode(2)]), indexed_content)
- self.assertIn(self._html_experiment_group_result(self.html_unit5, [unicode(3)]), indexed_content)
- self.assertIn(self._html_experiment_group_result(self.html_unit6, [unicode(4)]), indexed_content)
- self.assertNotIn(self._html_experiment_group_result(self.html_unit6, [unicode(5)]), indexed_content)
+ self.assertIn(self._html_experiment_group_result(self.html_unit4, [six.text_type(2)]), indexed_content)
+ self.assertIn(self._html_experiment_group_result(self.html_unit5, [six.text_type(3)]), indexed_content)
+ self.assertIn(self._html_experiment_group_result(self.html_unit6, [six.text_type(4)]), indexed_content)
+ self.assertNotIn(self._html_experiment_group_result(self.html_unit6, [six.text_type(5)]), indexed_content)
self.assertIn(
- self._vertical_experiment_group_result(self.condition_0_vertical, [unicode(2)]),
+ self._vertical_experiment_group_result(self.condition_0_vertical, [six.text_type(2)]),
indexed_content
)
self.assertNotIn(
- self._vertical_experiment_group_result(self.condition_1_vertical, [unicode(2)]),
+ self._vertical_experiment_group_result(self.condition_1_vertical, [six.text_type(2)]),
indexed_content
)
self.assertNotIn(
- self._vertical_experiment_group_result(self.condition_2_vertical, [unicode(2)]),
+ self._vertical_experiment_group_result(self.condition_2_vertical, [six.text_type(2)]),
indexed_content
)
self.assertNotIn(
- self._vertical_experiment_group_result(self.condition_0_vertical, [unicode(3)]),
+ self._vertical_experiment_group_result(self.condition_0_vertical, [six.text_type(3)]),
indexed_content
)
self.assertIn(
- self._vertical_experiment_group_result(self.condition_1_vertical, [unicode(3)]),
+ self._vertical_experiment_group_result(self.condition_1_vertical, [six.text_type(3)]),
indexed_content
)
self.assertNotIn(
- self._vertical_experiment_group_result(self.condition_2_vertical, [unicode(3)]),
+ self._vertical_experiment_group_result(self.condition_2_vertical, [six.text_type(3)]),
indexed_content
)
self.assertNotIn(
- self._vertical_experiment_group_result(self.condition_0_vertical, [unicode(4)]),
+ self._vertical_experiment_group_result(self.condition_0_vertical, [six.text_type(4)]),
indexed_content
)
self.assertNotIn(
- self._vertical_experiment_group_result(self.condition_1_vertical, [unicode(4)]),
+ self._vertical_experiment_group_result(self.condition_1_vertical, [six.text_type(4)]),
indexed_content
)
self.assertIn(
- self._vertical_experiment_group_result(self.condition_2_vertical, [unicode(4)]),
+ self._vertical_experiment_group_result(self.condition_2_vertical, [six.text_type(4)]),
indexed_content
)
mock_index.reset_mock()
diff --git a/cms/djangoapps/contentstore/tests/test_crud.py b/cms/djangoapps/contentstore/tests/test_crud.py
index 700c3a6c4c..86db5dd30c 100644
--- a/cms/djangoapps/contentstore/tests/test_crud.py
+++ b/cms/djangoapps/contentstore/tests/test_crud.py
@@ -1,7 +1,11 @@
+"""Tests for CRUD Operations"""
+
+from __future__ import absolute_import
+
from xmodule import templates
from xmodule.capa_module import ProblemBlock
from xmodule.course_module import CourseDescriptor
-from xmodule.html_module import HtmlDescriptor
+from xmodule.html_module import HtmlBlock
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.exceptions import DuplicateCourseError
from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase
@@ -40,10 +44,10 @@ class TemplateTests(ModuleStoreTestCase):
def test_get_some_templates(self):
self.assertEqual(len(SequenceDescriptor.templates()), 0)
- self.assertGreater(len(HtmlDescriptor.templates()), 0)
+ self.assertGreater(len(HtmlBlock.templates()), 0)
self.assertIsNone(SequenceDescriptor.get_template('doesntexist.yaml'))
- self.assertIsNone(HtmlDescriptor.get_template('doesntexist.yaml'))
- self.assertIsNotNone(HtmlDescriptor.get_template('announcement.yaml'))
+ self.assertIsNone(HtmlBlock.get_template('doesntexist.yaml'))
+ self.assertIsNotNone(HtmlBlock.get_template('announcement.yaml'))
def test_factories(self):
test_course = CourseFactory.create(
diff --git a/cms/djangoapps/contentstore/tests/test_export_git.py b/cms/djangoapps/contentstore/tests/test_export_git.py
index 29d9276c45..27ae16da16 100644
--- a/cms/djangoapps/contentstore/tests/test_export_git.py
+++ b/cms/djangoapps/contentstore/tests/test_export_git.py
@@ -2,6 +2,8 @@
Test the ability to export courses to xml from studio
"""
+from __future__ import absolute_import
+
import copy
import os
import shutil
diff --git a/cms/djangoapps/contentstore/tests/test_gating.py b/cms/djangoapps/contentstore/tests/test_gating.py
index e61da3ace5..e39f78564b 100644
--- a/cms/djangoapps/contentstore/tests/test_gating.py
+++ b/cms/djangoapps/contentstore/tests/test_gating.py
@@ -1,6 +1,8 @@
"""
Unit tests for the gating feature in Studio
"""
+from __future__ import absolute_import
+
from milestones.tests.utils import MilestonesTestCaseMixin
from mock import patch
diff --git a/cms/djangoapps/contentstore/tests/test_i18n.py b/cms/djangoapps/contentstore/tests/test_i18n.py
index 45034e3839..e083a33c37 100644
--- a/cms/djangoapps/contentstore/tests/test_i18n.py
+++ b/cms/djangoapps/contentstore/tests/test_i18n.py
@@ -2,6 +2,8 @@
"""
Tests for validate Internationalization and Module i18n service.
"""
+from __future__ import absolute_import
+
import gettext
from unittest import skip
diff --git a/cms/djangoapps/contentstore/tests/test_import.py b/cms/djangoapps/contentstore/tests/test_import.py
index de79fda8f2..1aeab0618c 100644
--- a/cms/djangoapps/contentstore/tests/test_import.py
+++ b/cms/djangoapps/contentstore/tests/test_import.py
@@ -3,12 +3,13 @@
"""
Tests for import_course_from_xml using the mongo modulestore.
"""
-from __future__ import print_function
+from __future__ import absolute_import, print_function
import copy
from uuid import uuid4
import ddt
+import six
from django.conf import settings
from django.test.client import Client
from django.test.utils import override_settings
@@ -268,7 +269,7 @@ class ContentStoreImportTest(ModuleStoreTestCase):
self.assertIsNotNone(split_test_module)
remapped_verticals = {
- key: target_id.make_usage_key('vertical', value) for key, value in groups_to_verticals.iteritems()
+ key: target_id.make_usage_key('vertical', value) for key, value in six.iteritems(groups_to_verticals)
}
self.assertEqual(remapped_verticals, split_test_module.group_id_to_child)
diff --git a/cms/djangoapps/contentstore/tests/test_import_draft_order.py b/cms/djangoapps/contentstore/tests/test_import_draft_order.py
index 68d6488cf9..c81016898f 100644
--- a/cms/djangoapps/contentstore/tests/test_import_draft_order.py
+++ b/cms/djangoapps/contentstore/tests/test_import_draft_order.py
@@ -1,6 +1,8 @@
"""
Tests Draft import order.
"""
+from __future__ import absolute_import
+
from django.conf import settings
from xmodule.modulestore.django import modulestore
diff --git a/cms/djangoapps/contentstore/tests/test_import_pure_xblock.py b/cms/djangoapps/contentstore/tests/test_import_pure_xblock.py
index 05d2079fd3..72a1ba661c 100644
--- a/cms/djangoapps/contentstore/tests/test_import_pure_xblock.py
+++ b/cms/djangoapps/contentstore/tests/test_import_pure_xblock.py
@@ -2,6 +2,8 @@
Integration tests for importing courses containing pure XBlocks.
"""
+from __future__ import absolute_import
+
from django.conf import settings
from xblock.core import XBlock
from xblock.fields import String
@@ -31,6 +33,7 @@ class StubXBlock(XBlock):
class XBlockImportTest(ModuleStoreTestCase):
+ """Test class to verify xblock import operations"""
@XBlock.register_temp_plugin(StubXBlock)
def test_import_public(self):
diff --git a/cms/djangoapps/contentstore/tests/test_libraries.py b/cms/djangoapps/contentstore/tests/test_libraries.py
index 17ce84d88a..5030deccbf 100644
--- a/cms/djangoapps/contentstore/tests/test_libraries.py
+++ b/cms/djangoapps/contentstore/tests/test_libraries.py
@@ -1,10 +1,14 @@
"""
Content library unit tests that require the CMS runtime.
"""
+from __future__ import absolute_import
+
import ddt
+import six
from django.test.utils import override_settings
from mock import Mock, patch
from opaque_keys.edx.locator import CourseKey, LibraryLocator
+from six.moves import range
from contentstore.tests.utils import AjaxEnabledTestClient, parse_json
from contentstore.utils import reverse_library_url, reverse_url, reverse_usage_url
@@ -81,7 +85,7 @@ class LibraryTestCase(ModuleStoreTestCase):
parent_location=course.location,
user_id=self.user.id,
publish_item=publish_item,
- source_library_id=unicode(library_key),
+ source_library_id=six.text_type(library_key),
**(other_settings or {})
)
@@ -511,11 +515,11 @@ class TestLibraryAccess(LibraryTestCase):
`library` can be a LibraryLocator or the library's root XBlock
"""
- if isinstance(library, (basestring, LibraryLocator)):
+ if isinstance(library, (six.string_types, LibraryLocator)):
lib_key = library
else:
lib_key = library.location.library_key
- response = self.client.get(reverse_library_url('library_handler', unicode(lib_key)))
+ response = self.client.get(reverse_library_url('library_handler', six.text_type(lib_key)))
self.assertIn(response.status_code, (200, 302, 403))
return response.status_code == 200
@@ -579,7 +583,7 @@ class TestLibraryAccess(LibraryTestCase):
# Now non_staff_user should be able to access library2_key only:
lib_list = self._list_libraries()
self.assertEqual(len(lib_list), 1)
- self.assertEqual(lib_list[0]["library_key"], unicode(library2_key))
+ self.assertEqual(lib_list[0]["library_key"], six.text_type(library2_key))
self.assertTrue(self._can_access_library(library2_key))
self.assertFalse(self._can_access_library(self.library))
@@ -606,7 +610,7 @@ class TestLibraryAccess(LibraryTestCase):
# Now non_staff_user should be able to access lib_key_pacific only:
lib_list = self._list_libraries()
self.assertEqual(len(lib_list), 1)
- self.assertEqual(lib_list[0]["library_key"], unicode(lib_key_pacific))
+ self.assertEqual(lib_list[0]["library_key"], six.text_type(lib_key_pacific))
self.assertTrue(self._can_access_library(lib_key_pacific))
self.assertFalse(self._can_access_library(lib_key_atlantic))
self.assertFalse(self._can_access_library(self.lib_key))
@@ -646,8 +650,8 @@ class TestLibraryAccess(LibraryTestCase):
def can_copy_block():
""" Check if studio lets us duplicate the XBlock in the library """
response = self.client.ajax_post(reverse_url('xblock_handler'), {
- 'parent_locator': unicode(self.library.location),
- 'duplicate_source_locator': unicode(block.location),
+ 'parent_locator': six.text_type(self.library.location),
+ 'duplicate_source_locator': six.text_type(block.location),
})
self.assertIn(response.status_code, (200, 403)) # 400 would be ambiguous
return response.status_code == 200
@@ -655,7 +659,7 @@ class TestLibraryAccess(LibraryTestCase):
def can_create_block():
""" Check if studio lets us make a new XBlock in the library """
response = self.client.ajax_post(reverse_url('xblock_handler'), {
- 'parent_locator': unicode(self.library.location), 'category': 'html',
+ 'parent_locator': six.text_type(self.library.location), 'category': 'html',
})
self.assertIn(response.status_code, (200, 403)) # 400 would be ambiguous
return response.status_code == 200
@@ -708,8 +712,8 @@ class TestLibraryAccess(LibraryTestCase):
# Copy block to the course:
response = self.client.ajax_post(reverse_url('xblock_handler'), {
- 'parent_locator': unicode(course.location),
- 'duplicate_source_locator': unicode(block.location),
+ 'parent_locator': six.text_type(course.location),
+ 'duplicate_source_locator': six.text_type(block.location),
})
self.assertIn(response.status_code, (200, 403)) # 400 would be ambiguous
duplicate_action_allowed = (response.status_code == 200)
diff --git a/cms/djangoapps/contentstore/tests/test_orphan.py b/cms/djangoapps/contentstore/tests/test_orphan.py
index f6919d8bdd..2e73911451 100644
--- a/cms/djangoapps/contentstore/tests/test_orphan.py
+++ b/cms/djangoapps/contentstore/tests/test_orphan.py
@@ -1,9 +1,12 @@
"""
Test finding orphans via the view and django config
"""
+from __future__ import absolute_import
+
import json
import ddt
+import six
from opaque_keys.edx.locator import BlockUsageLocator
from contentstore.tests.utils import CourseTestCase
@@ -94,11 +97,11 @@ class TestOrphan(TestOrphanBase):
)
self.assertEqual(len(orphans), 3, u"Wrong # {}".format(orphans))
location = course.location.replace(category='chapter', name='OrphanChapter')
- self.assertIn(unicode(location), orphans)
+ self.assertIn(six.text_type(location), orphans)
location = course.location.replace(category='vertical', name='OrphanVert')
- self.assertIn(unicode(location), orphans)
+ self.assertIn(six.text_type(location), orphans)
location = course.location.replace(category='html', name='OrphanHtml')
- self.assertIn(unicode(location), orphans)
+ self.assertIn(six.text_type(location), orphans)
@ddt.data(
(ModuleStoreEnum.Type.split, 9, 5),
@@ -171,8 +174,8 @@ class TestOrphan(TestOrphanBase):
# HTML component has `vertical1` as its parent.
html_parent = self.store.get_parent_location(multi_parent_html.location)
- self.assertNotEqual(unicode(html_parent), unicode(orphan_vertical.location))
- self.assertEqual(unicode(html_parent), unicode(vertical1.location))
+ self.assertNotEqual(six.text_type(html_parent), six.text_type(orphan_vertical.location))
+ self.assertEqual(six.text_type(html_parent), six.text_type(vertical1.location))
# Get path of the `multi_parent_html` & verify path_to_location returns a expected path
path = path_to_location(self.store, multi_parent_html.location)
@@ -224,7 +227,7 @@ class TestOrphan(TestOrphanBase):
# Verify chapter1 is parent of vertical1.
vertical1_parent = self.store.get_parent_location(vertical1.location)
- self.assertEqual(unicode(vertical1_parent), unicode(chapter1.location))
+ self.assertEqual(six.text_type(vertical1_parent), six.text_type(chapter1.location))
# Make `Vertical1` the parent of `HTML0`. So `HTML0` will have to parents (`Vertical0` & `Vertical1`)
vertical1.children.append(html.location)
@@ -233,7 +236,7 @@ class TestOrphan(TestOrphanBase):
# Get parent location & verify its either of the two verticals. As both parents are non-orphan,
# alphabetically least is returned
html_parent = self.store.get_parent_location(html.location)
- self.assertEquals(unicode(html_parent), unicode(vertical1.location))
+ self.assertEquals(six.text_type(html_parent), six.text_type(vertical1.location))
# verify path_to_location returns a expected path
path = path_to_location(self.store, html.location)
diff --git a/cms/djangoapps/contentstore/tests/test_permissions.py b/cms/djangoapps/contentstore/tests/test_permissions.py
index 1f43a20532..30f7a61242 100644
--- a/cms/djangoapps/contentstore/tests/test_permissions.py
+++ b/cms/djangoapps/contentstore/tests/test_permissions.py
@@ -1,9 +1,12 @@
"""
Test CRUD for authorization.
"""
+from __future__ import absolute_import
+
import copy
from django.contrib.auth.models import User
+from six.moves import range
from contentstore.tests.utils import AjaxEnabledTestClient
from contentstore.utils import reverse_course_url, reverse_url
diff --git a/cms/djangoapps/contentstore/tests/test_proctoring.py b/cms/djangoapps/contentstore/tests/test_proctoring.py
index ef2ad670c4..e842259a50 100644
--- a/cms/djangoapps/contentstore/tests/test_proctoring.py
+++ b/cms/djangoapps/contentstore/tests/test_proctoring.py
@@ -2,15 +2,18 @@
Tests for the edx_proctoring integration into Studio
"""
+from __future__ import absolute_import
+
from datetime import datetime, timedelta
import ddt
+import six
+from django.conf import settings
from edx_proctoring.api import get_all_exams_for_course, get_review_policy_by_exam_id
from mock import patch
from pytz import UTC
from contentstore.signals.handlers import listen_for_course_publish
-from django.conf import settings
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
@@ -41,7 +44,7 @@ class TestProctoredExams(ModuleStoreTestCase):
Helper method to compare the sequence with the stored exam,
which should just be a single one
"""
- exams = get_all_exams_for_course(unicode(self.course.id))
+ exams = get_all_exams_for_course(six.text_type(self.course.id))
self.assertEqual(len(exams), 1)
@@ -56,8 +59,8 @@ class TestProctoredExams(ModuleStoreTestCase):
# the hide after due value only applies to timed exams
self.assertEqual(exam['hide_after_due'], sequence.hide_after_due)
- self.assertEqual(exam['course_id'], unicode(self.course.id))
- self.assertEqual(exam['content_id'], unicode(sequence.location))
+ self.assertEqual(exam['course_id'], six.text_type(self.course.id))
+ self.assertEqual(exam['content_id'], six.text_type(sequence.location))
self.assertEqual(exam['exam_name'], sequence.display_name)
self.assertEqual(exam['time_limit_mins'], sequence.default_time_limit_minutes)
self.assertEqual(exam['is_proctored'], sequence.is_proctored_exam)
@@ -164,7 +167,7 @@ class TestProctoredExams(ModuleStoreTestCase):
listen_for_course_publish(self, self.course.id)
- exams = get_all_exams_for_course(unicode(self.course.id))
+ exams = get_all_exams_for_course(six.text_type(self.course.id))
self.assertEqual(len(exams), 1)
sequence.is_time_limited = False
@@ -195,7 +198,7 @@ class TestProctoredExams(ModuleStoreTestCase):
listen_for_course_publish(self, self.course.id)
- exams = get_all_exams_for_course(unicode(self.course.id))
+ exams = get_all_exams_for_course(six.text_type(self.course.id))
self.assertEqual(len(exams), 1)
self.store.delete_item(chapter.location, self.user.id)
@@ -205,7 +208,7 @@ class TestProctoredExams(ModuleStoreTestCase):
# look through exam table, the dangling exam
# should be disabled
- exams = get_all_exams_for_course(unicode(self.course.id))
+ exams = get_all_exams_for_course(six.text_type(self.course.id))
self.assertEqual(len(exams), 1)
exam = exams[0]
@@ -230,7 +233,7 @@ class TestProctoredExams(ModuleStoreTestCase):
listen_for_course_publish(self, self.course.id)
- exams = get_all_exams_for_course(unicode(self.course.id))
+ exams = get_all_exams_for_course(six.text_type(self.course.id))
self.assertEqual(len(exams), 0)
@ddt.data(
@@ -269,5 +272,42 @@ class TestProctoredExams(ModuleStoreTestCase):
# there shouldn't be any exams because we haven't enabled that
# advanced setting flag
- exams = get_all_exams_for_course(unicode(self.course.id))
+ exams = get_all_exams_for_course(six.text_type(self.course.id))
self.assertEqual(len(exams), expected_count)
+
+ def test_self_paced_no_due_dates(self):
+ self.course = CourseFactory.create(
+ org='edX',
+ course='901',
+ run='test_run2',
+ enable_proctored_exams=True,
+ enable_timed_exams=True,
+ self_paced=True,
+ )
+ chapter = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
+ ItemFactory.create(
+ parent=chapter,
+ category='sequential',
+ display_name='Test Proctored Exam',
+ graded=True,
+ is_time_limited=True,
+ default_time_limit_minutes=60,
+ is_proctored_exam=False,
+ is_practice_exam=False,
+ due=datetime.now(UTC) + timedelta(minutes=60),
+ exam_review_rules="allow_use_of_paper",
+ hide_after_due=True,
+ is_onboarding_exam=False,
+ )
+ listen_for_course_publish(self, self.course.id)
+ exams = get_all_exams_for_course(six.text_type(self.course.id))
+ # self-paced courses should ignore due dates
+ assert exams[0]['due_date'] is None
+
+ # now switch to instructor paced
+ # the exam will be updated with a due date
+ self.course.self_paced = False
+ self.course = self.update_course(self.course, 1)
+ listen_for_course_publish(self, self.course.id)
+ exams = get_all_exams_for_course(six.text_type(self.course.id))
+ assert exams[0]['due_date'] is not None
diff --git a/cms/djangoapps/contentstore/tests/test_request_event.py b/cms/djangoapps/contentstore/tests/test_request_event.py
index bf8821ef4a..a9b84d14d9 100644
--- a/cms/djangoapps/contentstore/tests/test_request_event.py
+++ b/cms/djangoapps/contentstore/tests/test_request_event.py
@@ -1,7 +1,10 @@
"""Tests for CMS's requests to logs"""
+from __future__ import absolute_import
+
import mock
-from django.urls import reverse
from django.test import TestCase
+from django.urls import reverse
+from six import unichr # pylint: disable=W0622
from contentstore.views.helpers import event as cms_user_track
diff --git a/cms/djangoapps/contentstore/tests/test_signals.py b/cms/djangoapps/contentstore/tests/test_signals.py
index 7126fb9ea6..c9c1f724a1 100644
--- a/cms/djangoapps/contentstore/tests/test_signals.py
+++ b/cms/djangoapps/contentstore/tests/test_signals.py
@@ -1,10 +1,12 @@
-import ddt
-from mock import patch, Mock
+"""Tests for verifying availability of resources for locking"""
-from cms.djangoapps.contentstore.signals.handlers import (
- GRADING_POLICY_COUNTDOWN_SECONDS,
- handle_grading_policy_changed
-)
+from __future__ import absolute_import
+
+import ddt
+import six
+from mock import Mock, patch
+
+from cms.djangoapps.contentstore.signals.handlers import GRADING_POLICY_COUNTDOWN_SECONDS, handle_grading_policy_changed
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
@@ -13,6 +15,8 @@ from xmodule.modulestore.tests.factories import CourseFactory
@ddt.ddt
class LockedTest(ModuleStoreTestCase):
+ """Test class to verify locking of mocked resources"""
+
def setUp(self):
super(LockedTest, self).setUp()
self.course = CourseFactory.create(
@@ -24,16 +28,15 @@ class LockedTest(ModuleStoreTestCase):
CourseEnrollment.enroll(self.user, self.course.id)
@patch('cms.djangoapps.contentstore.signals.handlers.cache.add')
- @patch('cms.djangoapps.contentstore.signals.handlers.cache.delete')
@patch('cms.djangoapps.contentstore.signals.handlers.task_compute_all_grades_for_course.apply_async')
@ddt.data(True, False)
- def test_locked(self, lock_available, compute_grades_async_mock, delete_mock, add_mock):
+ def test_locked(self, lock_available, compute_grades_async_mock, add_mock):
add_mock.return_value = lock_available
sender = Mock()
- handle_grading_policy_changed(sender, course_key=unicode(self.course.id))
+ handle_grading_policy_changed(sender, course_key=six.text_type(self.course.id))
- cache_key = 'handle_grading_policy_changed-{}'.format(unicode(self.course.id))
+ cache_key = 'handle_grading_policy_changed-{}'.format(six.text_type(self.course.id))
self.assertEqual(lock_available, compute_grades_async_mock.called)
if lock_available:
add_mock.assert_called_once_with(cache_key, "true", GRADING_POLICY_COUNTDOWN_SECONDS)
diff --git a/cms/djangoapps/contentstore/tests/test_transcripts_utils.py b/cms/djangoapps/contentstore/tests/test_transcripts_utils.py
index c1323126be..bba9984e76 100644
--- a/cms/djangoapps/contentstore/tests/test_transcripts_utils.py
+++ b/cms/djangoapps/contentstore/tests/test_transcripts_utils.py
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
""" Tests for transcripts_utils. """
+from __future__ import absolute_import
+
import copy
import json
import tempfile
diff --git a/cms/djangoapps/contentstore/tests/test_users_default_role.py b/cms/djangoapps/contentstore/tests/test_users_default_role.py
index 32648657ca..bcadfde520 100644
--- a/cms/djangoapps/contentstore/tests/test_users_default_role.py
+++ b/cms/djangoapps/contentstore/tests/test_users_default_role.py
@@ -2,6 +2,8 @@
Unit tests for checking default forum role "Student" of a user when he creates a course or
after deleting it creates same course again
"""
+from __future__ import absolute_import
+
from contentstore.tests.utils import AjaxEnabledTestClient
from contentstore.utils import delete_course, reverse_url
from courseware.tests.factories import UserFactory
diff --git a/cms/djangoapps/contentstore/tests/test_utils.py b/cms/djangoapps/contentstore/tests/test_utils.py
index ef769339ad..487ab0afec 100644
--- a/cms/djangoapps/contentstore/tests/test_utils.py
+++ b/cms/djangoapps/contentstore/tests/test_utils.py
@@ -1,7 +1,10 @@
""" Tests for utils. """
+from __future__ import absolute_import
+
import collections
from datetime import datetime, timedelta
+import six
from django.test import TestCase
from opaque_keys.edx.locator import CourseLocator
from pytz import UTC
@@ -24,19 +27,22 @@ class LMSLinksTestCase(TestCase):
course_key = CourseLocator('mitX', '101', 'test')
location = course_key.make_usage_key('vertical', 'contacting_us')
link = utils.get_lms_link_for_item(location, False)
- self.assertEquals(link, "//localhost:8000/courses/course-v1:mitX+101+test/jump_to/block-v1:mitX+101+test+type@vertical+block@contacting_us")
+ self.assertEquals(link, "//localhost:8000/courses/course-v1:mitX+101+test/jump_to/block-v1:mitX+101+test+type"
+ "@vertical+block@contacting_us")
# test preview
link = utils.get_lms_link_for_item(location, True)
self.assertEquals(
link,
- "//preview.localhost/courses/course-v1:mitX+101+test/jump_to/block-v1:mitX+101+test+type@vertical+block@contacting_us"
+ "//preview.localhost/courses/course-v1:mitX+101+test/jump_to/block-v1:mitX+101+test+type@vertical+block"
+ "@contacting_us "
)
# now test with the course' location
location = course_key.make_usage_key('course', 'test')
link = utils.get_lms_link_for_item(location)
- self.assertEquals(link, "//localhost:8000/courses/course-v1:mitX+101+test/jump_to/block-v1:mitX+101+test+type@course+block@test")
+ self.assertEquals(link, "//localhost:8000/courses/course-v1:mitX+101+test/jump_to/block-v1:mitX+101+test+type"
+ "@course+block@test")
def lms_link_for_certificate_web_view_test(self):
""" Tests get_lms_link_for_certificate_web_view. """
@@ -79,7 +85,7 @@ class ExtraPanelTabTestCase(TestCase):
if tabs is None:
tabs = []
course = collections.namedtuple('MockCourse', ['tabs'])
- if isinstance(tabs, basestring):
+ if isinstance(tabs, six.string_types):
course.tabs = self.get_tab_type_dicts(tabs)
else:
course.tabs = tabs
diff --git a/cms/djangoapps/contentstore/tests/test_video_utils.py b/cms/djangoapps/contentstore/tests/test_video_utils.py
index 0783f0fd2f..76ef0e49aa 100644
--- a/cms/djangoapps/contentstore/tests/test_video_utils.py
+++ b/cms/djangoapps/contentstore/tests/test_video_utils.py
@@ -3,31 +3,29 @@
Unit tests for video utils.
"""
-from unittest import TestCase
+from __future__ import absolute_import
+
from datetime import datetime
+from unittest import TestCase
+
import ddt
import pytz
import requests
+import six
from django.conf import settings
from django.core.files.uploadedfile import UploadedFile
from django.test.utils import override_settings
-from edxval.api import (
- create_profile,
- create_video,
- get_course_video_image_url,
- update_video_image
-)
-from openedx.core.djangoapps.profile_images.tests.helpers import make_image_file
-
+from edxval.api import create_profile, create_video, get_course_video_image_url, update_video_image
from mock import patch
from contentstore.tests.utils import CourseTestCase
from contentstore.video_utils import (
+ YOUTUBE_THUMBNAIL_SIZES,
download_youtube_video_thumbnail,
scrape_youtube_thumbnail,
- validate_video_image,
- YOUTUBE_THUMBNAIL_SIZES
+ validate_video_image
)
+from openedx.core.djangoapps.profile_images.tests.helpers import make_image_file
class ValidateVideoImageTestCase(TestCase):
@@ -63,7 +61,7 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase):
def setUp(self):
super(ScrapeVideoThumbnailsTestCase, self).setUp()
- course_ids = [unicode(self.course.id)]
+ course_ids = [six.text_type(self.course.id)]
profiles = ['youtube']
created = datetime.now(pytz.utc)
previous_uploads = [
@@ -119,7 +117,7 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase):
# Create video images.
with make_image_file() as image_file:
update_video_image(
- 'test-youtube-video-2', unicode(self.course.id), image_file, 'image.jpg'
+ 'test-youtube-video-2', six.text_type(self.course.id), image_file, 'image.jpg'
)
def mocked_youtube_thumbnail_response(
@@ -235,7 +233,7 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase):
"""
Test that youtube thumbnails are correctly scrapped.
"""
- course_id = unicode(self.course.id)
+ course_id = six.text_type(self.course.id)
video1_edx_video_id = 'test-youtube-video-1'
video2_edx_video_id = 'test-youtube-video-2'
@@ -289,7 +287,7 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase):
"""
Test that we get correct logs in case of failure as well as success.
"""
- course_id = unicode(self.course.id)
+ course_id = six.text_type(self.course.id)
video1_edx_video_id = 'test-youtube-video-1'
mocked_request.side_effect = [
self.mocked_youtube_thumbnail_response(
@@ -324,14 +322,14 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase):
'dummy-content',
None,
u'This image file type is not supported. Supported file types are {supported_file_formats}.'.format(
- supported_file_formats=settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.keys()
+ supported_file_formats=list(settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.keys())
)
),
(
None,
None,
u'This image file type is not supported. Supported file types are {supported_file_formats}.'.format(
- supported_file_formats=settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.keys()
+ supported_file_formats=list(settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.keys())
)
),
)
@@ -350,7 +348,7 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase):
Test that when no thumbnail is downloaded, video image is not updated.
"""
mock_download_youtube_thumbnail.return_value = image_content, image_content_type
- course_id = unicode(self.course.id)
+ course_id = six.text_type(self.course.id)
video1_edx_video_id = 'test-youtube-video-1'
# Verify that video1 has no image attached.
diff --git a/cms/djangoapps/contentstore/tests/tests.py b/cms/djangoapps/contentstore/tests/tests.py
index c33a60ff38..128672b93b 100644
--- a/cms/djangoapps/contentstore/tests/tests.py
+++ b/cms/djangoapps/contentstore/tests/tests.py
@@ -1,31 +1,32 @@
"""
This test file will test registration, login, activation, and session activity timeouts
"""
-from __future__ import print_function
+from __future__ import absolute_import, print_function
+
import datetime
import time
import mock
import pytest
+from contentstore.tests.test_course_settings import CourseTestCase
+from contentstore.tests.utils import AjaxEnabledTestClient, parse_json, registration, user
from ddt import data, ddt, unpack
from django.conf import settings
from django.contrib.auth.models import User
from django.core.cache import cache
-from django.urls import reverse
from django.test import TestCase
from django.test.utils import override_settings
+from django.urls import reverse
from freezegun import freeze_time
from pytz import UTC
-from six.moves import xrange
-
-from contentstore.models import PushNotificationConfig
-from contentstore.tests.test_course_settings import CourseTestCase
-from contentstore.tests.utils import AjaxEnabledTestClient, parse_json, registration, user
+from six.moves import range
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
class ContentStoreTestCase(ModuleStoreTestCase):
+ """Test class to verify user account operations"""
+
def _login(self, email, password):
"""
Login. View should always return 200. The success/fail is in the
@@ -62,8 +63,8 @@ class ContentStoreTestCase(ModuleStoreTestCase):
"""Create the account and check that it worked"""
resp = self._create_account(username, email, password)
self.assertEqual(resp.status_code, 200)
- data = parse_json(resp)
- self.assertEqual(data['success'], True)
+ json_data = parse_json(resp)
+ self.assertEqual(json_data['success'], True)
# Check both that the user is created, and inactive
self.assertFalse(user(email).is_active)
@@ -173,7 +174,7 @@ class AuthTestCase(ContentStoreTestCase):
self.create_account(self.username, self.email, self.pw)
# Not activated yet. Login should fail.
- resp = self._login(self.email, self.pw)
+ self._login(self.email, self.pw)
self.activate_user(self.email)
@@ -183,7 +184,7 @@ class AuthTestCase(ContentStoreTestCase):
def test_login_ratelimited(self):
# try logging in 30 times, the default limit in the number of failed
# login attempts in one 5 minute period before the rate gets limited
- for i in xrange(30):
+ for i in range(30):
resp = self._login(self.email, 'wrong_password{0}'.format(i))
self.assertEqual(resp.status_code, 403)
resp = self._login(self.email, 'wrong_password')
@@ -200,7 +201,7 @@ class AuthTestCase(ContentStoreTestCase):
self.create_account(self.username, self.email, self.pw)
self.activate_user(self.email)
- for i in xrange(3):
+ for i in range(3):
resp = self._login(self.email, 'wrong_password{0}'.format(i))
self.assertEqual(resp.status_code, 403)
self.assertIn(
@@ -341,6 +342,8 @@ class AuthTestCase(ContentStoreTestCase):
class ForumTestCase(CourseTestCase):
+ """Tests class to verify course to forum operations"""
+
def setUp(self):
""" Creates the test course. """
super(ForumTestCase, self).setUp()
@@ -388,6 +391,8 @@ class ForumTestCase(CourseTestCase):
@ddt
class CourseKeyVerificationTestCase(CourseTestCase):
+ """Test class to verify course decorator operations"""
+
def setUp(self):
"""
Create test course.
@@ -411,15 +416,3 @@ class CourseKeyVerificationTestCase(CourseTestCase):
)
resp = self.client.get_html(url)
self.assertEqual(resp.status_code, status_code)
-
-
-class PushNotificationConfigTestCase(TestCase):
- """
- Tests PushNotificationConfig.
- """
- def test_notifications_defaults(self):
- self.assertFalse(PushNotificationConfig.is_enabled())
-
- def test_notifications_enabled(self):
- PushNotificationConfig(enabled=True).save()
- self.assertTrue(PushNotificationConfig.is_enabled())
diff --git a/cms/djangoapps/contentstore/tests/utils.py b/cms/djangoapps/contentstore/tests/utils.py
index a22e444fdd..53fb93693e 100644
--- a/cms/djangoapps/contentstore/tests/utils.py
+++ b/cms/djangoapps/contentstore/tests/utils.py
@@ -1,9 +1,12 @@
'''
Utilities for contentstore tests
'''
+from __future__ import absolute_import
+
import json
import textwrap
+import six
from django.conf import settings
from django.contrib.auth.models import User
from django.test.client import Client
@@ -26,7 +29,7 @@ TEST_DATA_DIR = settings.COMMON_TEST_DATA_ROOT
def parse_json(response):
"""Parse response, which is assumed to be json"""
- return json.loads(response.content)
+ return json.loads(response.content.decode('utf-8'))
def user(email):
@@ -48,7 +51,7 @@ class AjaxEnabledTestClient(Client):
Convenience method for client post which serializes the data into json and sets the accept type
to json
"""
- if not isinstance(data, basestring):
+ if not isinstance(data, six.string_types):
data = json.dumps(data or {})
kwargs.setdefault("HTTP_X_REQUESTED_WITH", "XMLHttpRequest")
kwargs.setdefault("HTTP_ACCEPT", "application/json")
@@ -354,7 +357,7 @@ class CourseTestCase(ProceduralCourseTestMixin, ModuleStoreTestCase):
course1_asset_attrs = content_store.get_attrs(course1_id.make_asset_key(category, filename))
course2_asset_attrs = content_store.get_attrs(course2_id.make_asset_key(category, filename))
self.assertEqual(len(course1_asset_attrs), len(course2_asset_attrs))
- for key, value in course1_asset_attrs.iteritems():
+ for key, value in six.iteritems(course1_asset_attrs):
if key in ['_id', 'filename', 'uploadDate', 'content_son', 'thumbnail_location']:
pass
else:
diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py
index 237dd34afc..c6f6ce48ab 100644
--- a/cms/djangoapps/contentstore/utils.py
+++ b/cms/djangoapps/contentstore/utils.py
@@ -1,11 +1,12 @@
"""
Common utility functions useful throughout the contentstore
"""
-from __future__ import print_function
+from __future__ import absolute_import, print_function
import logging
from datetime import datetime
+import six
from django.conf import settings
from django.urls import reverse
from django.utils.translation import ugettext as _
@@ -150,7 +151,7 @@ def get_lms_link_for_certificate_web_view(user_id, course_key, mode):
return u"//{certificate_web_base}/certificates/user/{user_id}/course/{course_id}?preview={mode}".format(
certificate_web_base=lms_base,
user_id=user_id,
- course_id=unicode(course_key),
+ course_id=six.text_type(course_key),
mode=mode
)
@@ -274,7 +275,7 @@ def reverse_url(handler_name, key_name=None, key_value=None, kwargs=None):
Creates the URL for the given handler.
The optional key_name and key_value are passed in as kwargs to the handler.
"""
- kwargs_for_reverse = {key_name: unicode(key_value)} if key_name else None
+ kwargs_for_reverse = {key_name: six.text_type(key_value)} if key_name else None
if kwargs:
kwargs_for_reverse.update(kwargs)
return reverse(handler_name, kwargs=kwargs_for_reverse)
@@ -427,7 +428,7 @@ def get_user_partition_info(xblock, schemes=None, course=None):
# Put together the entire partition dictionary
partitions.append({
"id": p.id,
- "name": unicode(p.name), # Convert into a string in case ugettext_lazy was used
+ "name": six.text_type(p.name), # Convert into a string in case ugettext_lazy was used
"scheme": p.scheme.name,
"groups": groups,
})
diff --git a/cms/djangoapps/contentstore/video_utils.py b/cms/djangoapps/contentstore/video_utils.py
index df64c5c281..39260b1385 100644
--- a/cms/djangoapps/contentstore/video_utils.py
+++ b/cms/djangoapps/contentstore/video_utils.py
@@ -2,16 +2,18 @@
"""
Utils related to the videos.
"""
-import logging
-from urlparse import urljoin
-import requests
+from __future__ import absolute_import
+import logging
+
+import requests
+import six
from django.conf import settings
from django.core.files.images import get_image_dimensions
from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils.translation import ugettext as _
from edxval.api import get_course_video_image_url, update_video_image
-
+from six.moves.urllib.parse import urljoin # pylint: disable=import-error
# Youtube thumbnail sizes.
# https://img.youtube.com/vi/{youtube_id}/{thumbnail_quality}.jpg
@@ -40,9 +42,9 @@ def validate_video_image(image_file, skip_aspect_ratio=False):
if not all(hasattr(image_file, attr) for attr in ['name', 'content_type', 'size']):
error = _('The image must have name, content type, and size information.')
- elif image_file.content_type not in settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.values():
+ elif image_file.content_type not in list(settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.values()):
error = _(u'This image file type is not supported. Supported file types are {supported_file_formats}.').format(
- supported_file_formats=settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.keys()
+ supported_file_formats=list(settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.keys())
)
elif image_file.size > settings.VIDEO_IMAGE_SETTINGS['VIDEO_IMAGE_MAX_BYTES']:
error = _(u'This image file must be smaller than {image_max_size}.').format(
@@ -126,7 +128,7 @@ def scrape_youtube_thumbnail(course_id, edx_video_id, youtube_id):
# Scrape when course video image does not exist for edx_video_id.
if not get_course_video_image_url(course_id, edx_video_id):
thumbnail_content, thumbnail_content_type = download_youtube_video_thumbnail(youtube_id)
- supported_content_types = {v: k for k, v in settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.iteritems()}
+ supported_content_types = {v: k for k, v in six.iteritems(settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS)}
image_filename = '{youtube_id}{image_extention}'.format(
youtube_id=youtube_id,
image_extention=supported_content_types.get(
diff --git a/cms/djangoapps/contentstore/views/__init__.py b/cms/djangoapps/contentstore/views/__init__.py
index e90628aef4..3f81e7f8f9 100644
--- a/cms/djangoapps/contentstore/views/__init__.py
+++ b/cms/djangoapps/contentstore/views/__init__.py
@@ -2,26 +2,25 @@
"All view functions for contentstore, broken out into submodules"
-# Disable warnings about import from wildcard
-# All files below declare exports with __all__
from .assets import *
+from .checklists import *
from .component import *
from .course import *
-from .checklists import *
from .entrance_exam import *
from .error import *
+from .export_git import *
from .helpers import *
-from .item import *
from .import_export import *
+from .item import *
from .library import *
from .preview import *
from .public import *
-from .export_git import *
-from .user import *
from .tabs import *
-from .videos import *
from .transcript_settings import *
from .transcripts_ajax import *
+from .user import *
+from .videos import *
+
try:
from .dev import *
except ImportError:
diff --git a/cms/djangoapps/contentstore/views/access.py b/cms/djangoapps/contentstore/views/access.py
index 50fb74e55c..2ed9de9453 100644
--- a/cms/djangoapps/contentstore/views/access.py
+++ b/cms/djangoapps/contentstore/views/access.py
@@ -1,5 +1,7 @@
""" Helper methods for determining user access permissions in Studio """
+from __future__ import absolute_import
+
from student import auth
from student.roles import CourseInstructorRole
diff --git a/cms/djangoapps/contentstore/views/assets.py b/cms/djangoapps/contentstore/views/assets.py
index 9a08794a0d..c914063283 100644
--- a/cms/djangoapps/contentstore/views/assets.py
+++ b/cms/djangoapps/contentstore/views/assets.py
@@ -1,32 +1,36 @@
+"""Views for assets"""
+from __future__ import absolute_import
+
import json
import logging
import math
-from functools import partial
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
from django.http import HttpResponseBadRequest, HttpResponseNotFound
from django.utils.translation import ugettext as _
from django.views.decorators.csrf import ensure_csrf_cookie
-from django.views.decorators.http import require_POST, require_http_methods
+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 xmodule.contentstore.content import StaticContent
-from xmodule.contentstore.django import contentstore
-from xmodule.exceptions import NotFoundError
-from xmodule.modulestore.django import modulestore
-from xmodule.modulestore.exceptions import ItemNotFoundError
-from contentstore.utils import reverse_course_url
+from ..utils import reverse_course_url
from contentstore.views.exception import AssetNotFoundException, AssetSizeTooLargeException
from edxmako.shortcuts import render_to_response
from openedx.core.djangoapps.contentserver.caching import del_cached_content
from student.auth import has_course_author_access
from util.date_utils import get_default_time_display
from util.json_request import JsonResponse
+from xmodule.contentstore.content import StaticContent
+from xmodule.contentstore.django import contentstore
+from xmodule.exceptions import NotFoundError
+from xmodule.modulestore.django import modulestore
+from xmodule.modulestore.exceptions import ItemNotFoundError
__all__ = ['assets_handler']
@@ -186,6 +190,7 @@ def _get_requested_attribute(request, attribute):
def _get_error_if_invalid_parameters(requested_filter):
+ """Function for returning error messages on filters"""
requested_file_types = _get_requested_file_types_from_requested_filter(requested_filter)
invalid_filters = []
@@ -295,6 +300,7 @@ def _get_sort_type_and_direction(request_options):
def _get_mongo_sort_from_requested_sort(requested_sort):
+ """Function returns sorts dataset based on the key provided"""
if requested_sort == 'date_added':
sort = 'uploadDate'
elif requested_sort == 'display_name':
@@ -320,6 +326,7 @@ def _get_first_asset_index(current_page, page_size):
def _get_assets_for_page(course_key, options):
+ """returns course content for given course and options"""
current_page = options['current_page']
page_size = options['page_size']
sort = options['sort']
@@ -331,10 +338,12 @@ def _get_assets_for_page(course_key, options):
def _update_options_to_requery_final_page(query_options, total_asset_count):
+ """sets current_page value based on asset count and page_size"""
query_options['current_page'] = int(math.floor((total_asset_count - 1) / query_options['page_size']))
def _get_assets_in_json_format(assets, course_key):
+ """returns assets information in JSON Format"""
assets_in_json_format = []
for asset in assets:
thumbnail_asset_key = _get_thumbnail_asset_key(asset, course_key)
@@ -355,6 +364,7 @@ def _get_assets_in_json_format(assets, course_key):
def update_course_run_asset(course_key, upload_file):
+ """returns contents of the uploaded file"""
course_exists_response = _get_error_if_course_does_not_exist(course_key)
if course_exists_response is not None:
@@ -388,6 +398,7 @@ def update_course_run_asset(course_key, upload_file):
@ensure_csrf_cookie
@login_required
def _upload_asset(request, course_key):
+ """uploads the file in request and returns JSON response"""
course_exists_error = _get_error_if_course_does_not_exist(course_key)
if course_exists_error is not None:
@@ -442,11 +453,13 @@ def _get_file_metadata_as_dictionary(upload_file):
def get_file_size(upload_file):
+ """returns the size of the uploaded file"""
# can be used for mocking test file sizes.
return upload_file.size
def _check_file_size_is_too_large(file_metadata):
+ """verifies whether file size is greater than allowed file size"""
upload_file_size = file_metadata['upload_file_size']
maximum_file_size_in_megabytes = settings.MAX_ASSET_UPLOAD_FILE_SIZE_IN_MB
maximum_file_size_in_bytes = maximum_file_size_in_megabytes * 1000 ** 2
@@ -455,6 +468,8 @@ def _check_file_size_is_too_large(file_metadata):
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.'
@@ -465,6 +480,7 @@ def _get_file_too_large_error_message(filename):
def _get_file_content_and_path(file_metadata, course_key):
+ """returns contents of the uploaded file and path for temporary uploaded file"""
content_location = StaticContent.compute_location(course_key, file_metadata['filename'])
upload_file = file_metadata['upload_file']
@@ -483,10 +499,12 @@ def _get_file_content_and_path(file_metadata, course_key):
def _check_thumbnail_uploaded(thumbnail_content):
+ """returns whether thumbnail is None"""
return thumbnail_content is not None
def _get_thumbnail_asset_key(asset, course_key):
+ """returns thumbnail asset key"""
# note, due to the schema change we may not have a 'thumbnail_location' in the result set
thumbnail_location = asset.get('thumbnail_location', None)
thumbnail_asset_key = None
@@ -501,12 +519,12 @@ def _get_thumbnail_asset_key(asset, course_key):
@login_required
@ensure_csrf_cookie
def _update_asset(request, course_key, asset_key):
- '''
+ """
restful CRUD operations for a course asset.
Currently only DELETE, POST, and PUT methods are implemented.
asset_path_encoding: the odd /c4x/org/course/category/name repr of the asset (used by Backbone as the id)
- '''
+ """
if request.method == 'DELETE':
try:
delete_asset(course_key, asset_key)
@@ -520,7 +538,7 @@ def _update_asset(request, course_key, asset_key):
# update existing asset
try:
- modified_asset = json.loads(request.body)
+ modified_asset = json.loads(request.body.decode('utf8'))
except ValueError:
return HttpResponseBadRequest()
contentstore().set_attr(asset_key, 'locked', modified_asset['locked'])
@@ -530,10 +548,12 @@ def _update_asset(request, course_key, asset_key):
def _save_content_to_trash(content):
+ """saves the content to trash"""
contentstore('trashcan').save(content)
def delete_asset(course_key, asset_key):
+ """deletes the cached content based on asset key"""
content = _check_existence_and_get_asset_content(asset_key)
_save_content_to_trash(content)
@@ -583,5 +603,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': unicode(location)
+ 'id': six.text_type(location)
}
diff --git a/cms/djangoapps/contentstore/views/certificates.py b/cms/djangoapps/contentstore/views/certificates.py
index acef5a41d1..69a208c8b9 100644
--- a/cms/djangoapps/contentstore/views/certificates.py
+++ b/cms/djangoapps/contentstore/views/certificates.py
@@ -21,9 +21,12 @@ course.certificates: {
]
}
"""
+from __future__ import absolute_import
+
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
@@ -31,6 +34,7 @@ from django.http import HttpResponse
from django.utils.translation import ugettext as _
from django.views.decorators.csrf import ensure_csrf_cookie
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
@@ -40,7 +44,6 @@ from contentstore.views.assets import delete_asset
from contentstore.views.exception import AssetNotFoundException
from course_modes.models import CourseMode
from edxmako.shortcuts import render_to_response
-from eventtracking import tracker
from student.auth import has_studio_write_access
from student.roles import GlobalStaff
from util.db import MYSQL_MAX_INT, generate_int_id
@@ -346,7 +349,7 @@ def certificate_activation_handler(request, course_key_string):
msg = _(u'PermissionDenied: Failed in authenticating {user}').format(user=request.user)
return JsonResponse({"error": msg}, status=403)
- data = json.loads(request.body)
+ data = json.loads(request.body.decode('utf8'))
is_active = data.get('is_active', False)
certificates = CertificateManager.get_certificates(course)
@@ -358,7 +361,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': unicode(course.id),
+ 'course_id': six.text_type(course.id),
})
return HttpResponse(status=200)
@@ -446,7 +449,7 @@ def certificates_list_handler(request, course_key_string):
)
store.update_item(course, request.user.id)
CertificateManager.track_event('created', {
- 'course_id': unicode(course.id),
+ 'course_id': six.text_type(course.id),
'configuration_id': new_certificate.id
})
course = _get_course_and_check_access(course_key, request.user)
@@ -503,7 +506,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': unicode(course.id),
+ 'course_id': six.text_type(course.id),
'configuration_id': serialized_certificate["id"]
})
return JsonResponse(serialized_certificate, status=201)
@@ -525,7 +528,7 @@ def certificates_detail_handler(request, course_key_string, certificate_id):
certificate_id=certificate_id
)
CertificateManager.track_event('deleted', {
- 'course_id': unicode(course.id),
+ 'course_id': six.text_type(course.id),
'configuration_id': certificate_id
})
return JsonResponse(status=204)
diff --git a/cms/djangoapps/contentstore/views/checklists.py b/cms/djangoapps/contentstore/views/checklists.py
index f3fb7cffba..ebea256a50 100644
--- a/cms/djangoapps/contentstore/views/checklists.py
+++ b/cms/djangoapps/contentstore/views/checklists.py
@@ -1,12 +1,14 @@
# pylint: disable=missing-docstring
+from __future__ import absolute_import
+
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.views.decorators.csrf import ensure_csrf_cookie
from opaque_keys.edx.keys import CourseKey
-from xmodule.modulestore.django import modulestore
from edxmako.shortcuts import render_to_response
from student.auth import has_course_author_access
+from xmodule.modulestore.django import modulestore
__all__ = ['checklists_handler']
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index 1228b724e2..ed75ccd25b 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -1,6 +1,7 @@
from __future__ import absolute_import
import logging
+import six
from django.conf import settings
from django.contrib.auth.decorators import login_required
@@ -130,9 +131,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 " + unicode(unit.location)
+ assert subsection is not None, "Could not determine parent subsection from unit " + six.text_type(
+ unit.location)
section = get_parent_xblock(subsection)
- assert section is not None, "Could not determine ancestor section from unit " + unicode(unit.location)
+ assert section is not None, "Could not determine ancestor section from unit " + six.text_type(unit.location)
# Fetch the XBlock info for use by the container page. Note that it includes information
# about the block's ancestors and siblings for use by the Unit Outline.
diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py
index 0aa32a6647..5b5aa03264 100644
--- a/cms/djangoapps/contentstore/views/course.py
+++ b/cms/djangoapps/contentstore/views/course.py
@@ -1,13 +1,15 @@
"""
Views related to operations on course objects
"""
-from collections import defaultdict
+from __future__ import absolute_import
+
import copy
import json
import logging
import random
import re
import string
+from collections import defaultdict
import django.utils
import six
@@ -15,20 +17,18 @@ from ccx_keys.locator import CCXLocator
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied, ValidationError
-from django.urls import reverse
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseNotFound
from django.shortcuts import redirect
+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 milestones import api as milestones_api
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locator import BlockUsageLocator
-from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
-from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace
-from openedx.features.course_experience.waffle import waffle as course_experience_waffle
-from openedx.features.course_experience.waffle import ENABLE_COURSE_ABOUT_SIDEBAR_HTML
from six import text_type
+from six.moves import filter
from contentstore.course_group_config import (
COHORT_SCHEME,
@@ -39,7 +39,6 @@ from contentstore.course_group_config import (
)
from contentstore.course_info_model import delete_course_update, get_course_updates, update_course_updates
from contentstore.courseware_index import CoursewareSearchIndexer, SearchIndexingError
-from contentstore.push_notification import push_notification_enabled
from contentstore.tasks import rerun_course as rerun_course_task
from contentstore.utils import (
add_instructor,
@@ -56,19 +55,22 @@ from course_action_state.managers import CourseActionStateItemNotFoundError
from course_action_state.models import CourseRerunState, CourseRerunUIStateManager
from course_creators.views import add_user_with_status_unrequested, get_course_creator_status
from edxmako.shortcuts import render_to_response
-from milestones import api as milestones_api
from models.settings.course_grading import CourseGradingModel
from models.settings.course_metadata import CourseMetadata
from models.settings.encoder import CourseSettingsEncoder
+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
from openedx.core.djangoapps.models.course_details import CourseDetails
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
+from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace
from openedx.core.djangolib.js_utils import dump_js_escaped_json
from openedx.core.lib.course_tabs import CourseTabPluginManager
from openedx.core.lib.courses import course_image_url
-from openedx.features.content_type_gating.partitions import CONTENT_TYPE_GATING_SCHEME
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 student import auth
from student.auth import has_course_author_access, has_studio_read_access, has_studio_write_access
from student.roles import CourseCreatorRole, CourseInstructorRole, CourseStaffRole, GlobalStaff, UserBasedRole
@@ -460,7 +462,7 @@ def _accessible_courses_list_from_groups(request):
instructor_courses = UserBasedRole(request.user, CourseInstructorRole.ROLE).courses_with_role()
staff_courses = UserBasedRole(request.user, CourseStaffRole.ROLE).courses_with_role()
- all_courses = filter(filter_ccx, instructor_courses | staff_courses)
+ all_courses = list(filter(filter_ccx, instructor_courses | staff_courses))
courses_list = []
course_keys = {}
@@ -469,7 +471,7 @@ def _accessible_courses_list_from_groups(request):
raise AccessListFallback
course_keys[course_access.course_id] = course_access.course_id
- course_keys = course_keys.values()
+ course_keys = list(course_keys.values())
if course_keys:
courses_list = modulestore().get_course_summaries(course_keys=course_keys)
@@ -514,7 +516,7 @@ def course_listing(request):
"""
return {
u'display_name': uca.display_name,
- u'course_key': unicode(uca.course_key),
+ 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,
@@ -536,8 +538,8 @@ def course_listing(request):
return {
u'display_name': library.display_name,
- u'library_key': unicode(library.location.library_key),
- u'url': reverse_library_url(u'library_handler', unicode(library.location.library_key)),
+ u'library_key': six.text_type(library.location.library_key),
+ u'url': reverse_library_url(u'library_handler', six.text_type(library.location.library_key)),
u'org': library.display_org_with_default,
u'number': library.display_number_with_default,
u'can_edit': has_studio_write_access(request.user, library.location.library_key),
@@ -624,7 +626,7 @@ def course_index(request, course_key):
lms_link = get_lms_link_for_item(course_module.location)
reindex_link = None
if settings.FEATURES.get('ENABLE_COURSEWARE_INDEX', False):
- reindex_link = "/course/{course_id}/search_reindex".format(course_id=unicode(course_key))
+ reindex_link = "/course/{course_id}/search_reindex".format(course_id=six.text_type(course_key))
sections = course_module.get_children()
course_structure = _course_outline_json(request, course_module)
locator_to_show = request.GET.get('show', None)
@@ -708,7 +710,7 @@ def _process_courses_list(courses_iter, in_process_course_actions, split_archive
"""
return {
'display_name': course.display_name,
- 'course_key': unicode(course.location.course_key),
+ 'course_key': six.text_type(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),
@@ -819,14 +821,14 @@ 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': unicode(destination_course_key)
+ 'destination_course_key': six.text_type(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': unicode(new_course.id),
+ 'course_key': six.text_type(new_course.id),
})
except ValidationError as ex:
return JsonResponse({'error': text_type(ex)}, status=400)
@@ -928,7 +930,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 = [unicode(source_course_key), unicode(destination_course_key), user.id, json_fields]
+ args = [six.text_type(source_course_key), six.text_type(destination_course_key), user.id, json_fields]
if background:
rerun_course_task.delay(*args)
@@ -963,7 +965,6 @@ def course_info_handler(request, course_key_string):
'updates_url': reverse_course_url('course_info_update_handler', course_key),
'handouts_locator': course_key.make_usage_key('course_info', 'handouts'),
'base_asset_url': StaticContent.get_base_url_path_for_course_assets(course_module.id),
- 'push_notification_enabled': push_notification_enabled()
}
)
else:
@@ -1221,7 +1222,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(unicode(course_key))
+ update_credit_course_requirements.delay(six.text_type(course_key))
# None implies update the whole model (cutoffs, graceperiod, and graders) not a specific grader
if grader_index is None:
@@ -1369,7 +1370,7 @@ def validate_textbook_json(textbook):
"""
Validate the given text as representing a list of PDF textbooks
"""
- if isinstance(textbook, basestring):
+ if isinstance(textbook, six.string_types):
try:
textbook = json.loads(textbook)
except ValueError:
@@ -1378,7 +1379,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 = unicode(textbook.get("id", ""))
+ tid = six.text_type(textbook.get("id", ""))
if tid and not tid[0].isdigit():
raise TextbookValidationError("textbook ID must start with a digit")
return textbook
@@ -1495,7 +1496,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 unicode(tb.get("id")) == unicode(textbook_id)]
+ if six.text_type(tb.get("id")) == six.text_type(textbook_id)]
if matching_id:
textbook = matching_id[0]
else:
@@ -1683,7 +1684,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 unicode(p.id) == unicode(group_configuration_id)]
+ if six.text_type(p.id) == six.text_type(group_configuration_id)]
if matching_id:
configuration = matching_id[0]
else:
diff --git a/cms/djangoapps/contentstore/views/dev.py b/cms/djangoapps/contentstore/views/dev.py
index 2b2178eaba..a2fc3f7f14 100644
--- a/cms/djangoapps/contentstore/views/dev.py
+++ b/cms/djangoapps/contentstore/views/dev.py
@@ -4,6 +4,8 @@ These views will NOT be shown on production: trying to access them will result
in a 404 error.
"""
# pylint: disable=unused-argument
+from __future__ import absolute_import
+
from edxmako.shortcuts import render_to_response
diff --git a/cms/djangoapps/contentstore/views/entrance_exam.py b/cms/djangoapps/contentstore/views/entrance_exam.py
index c6944a5673..b1d9d0d5db 100644
--- a/cms/djangoapps/contentstore/views/entrance_exam.py
+++ b/cms/djangoapps/contentstore/views/entrance_exam.py
@@ -2,9 +2,12 @@
Entrance Exams view module -- handles all requests related to entrance exam management via Studio
Intended to be utilized as an AJAX callback handler, versus a proper view/screen
"""
+from __future__ import absolute_import
+
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
@@ -130,7 +133,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 = unicode(course.location)
+ parent_locator = six.text_type(course.location)
created_block = create_xblock(
parent_locator=parent_locator,
user=request.user,
@@ -145,13 +148,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': unicode(created_block.location),
+ 'entrance_exam_id': six.text_type(created_block.location),
}
CourseMetadata.update_from_dict(metadata, course, request.user)
# Create the entrance exam section item.
create_xblock(
- parent_locator=unicode(created_block.location),
+ parent_locator=six.text_type(created_block.location),
user=request.user,
category='sequential',
display_name=_('Entrance Exam - Subsection')
@@ -177,7 +180,7 @@ def _get_entrance_exam(request, course_key): # pylint: disable=W0613
try:
exam_descriptor = modulestore().get_item(exam_key)
return HttpResponse(
- dump_js_escaped_json({'locator': unicode(exam_descriptor.location)}),
+ dump_js_escaped_json({'locator': six.text_type(exam_descriptor.location)}),
status=200, content_type='application/json')
except ItemNotFoundError:
return HttpResponse(status=404)
@@ -246,7 +249,7 @@ def add_entrance_exam_milestone(course_id, x_block):
if len(milestones):
milestone = milestones[0]
else:
- description = u'Autogenerated during {} entrance exam creation.'.format(unicode(course_id))
+ description = u'Autogenerated during {} entrance exam creation.'.format(six.text_type(course_id))
milestone = milestones_helpers.add_milestone({
'name': _('Completed Course Entrance Exam'),
'namespace': milestone_namespace,
@@ -254,13 +257,13 @@ def add_entrance_exam_milestone(course_id, x_block):
})
relationship_types = milestones_helpers.get_milestone_relationship_types()
milestones_helpers.add_course_milestone(
- unicode(course_id),
+ six.text_type(course_id),
relationship_types['REQUIRES'],
milestone
)
milestones_helpers.add_course_content_milestone(
- unicode(course_id),
- unicode(x_block.location),
+ six.text_type(course_id),
+ six.text_type(x_block.location),
relationship_types['FULFILLS'],
milestone
)
@@ -277,4 +280,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(unicode(course_child.scope_ids.usage_id))
+ milestones_helpers.remove_content_references(six.text_type(course_child.scope_ids.usage_id))
diff --git a/cms/djangoapps/contentstore/views/error.py b/cms/djangoapps/contentstore/views/error.py
index 755417bda4..8045b0fd4a 100644
--- a/cms/djangoapps/contentstore/views/error.py
+++ b/cms/djangoapps/contentstore/views/error.py
@@ -1,5 +1,7 @@
# pylint: disable=missing-docstring,unused-argument
+from __future__ import absolute_import
+
import functools
from django.http import HttpResponse, HttpResponseNotFound, HttpResponseServerError
diff --git a/cms/djangoapps/contentstore/views/export_git.py b/cms/djangoapps/contentstore/views/export_git.py
index 47a5624170..eb0c0d364b 100644
--- a/cms/djangoapps/contentstore/views/export_git.py
+++ b/cms/djangoapps/contentstore/views/export_git.py
@@ -3,8 +3,11 @@ This views handles exporting the course xml to a git repository if
the giturl attribute is set.
"""
+from __future__ import absolute_import
+
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 _
@@ -46,7 +49,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 = unicode(ex)
+ msg = six.text_type(ex)
return render_to_response('export_git.html', {
'context_course': course_module,
diff --git a/cms/djangoapps/contentstore/views/helpers.py b/cms/djangoapps/contentstore/views/helpers.py
index 73811cf43d..4eb087dfb8 100644
--- a/cms/djangoapps/contentstore/views/helpers.py
+++ b/cms/djangoapps/contentstore/views/helpers.py
@@ -4,9 +4,9 @@ Helper methods for Studio views.
from __future__ import absolute_import
-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 _
@@ -111,7 +111,7 @@ def xblock_studio_url(xblock, parent_xblock=None):
elif category in ('chapter', 'sequential'):
return u'{url}?show={usage_key}'.format(
url=reverse_course_url('course_handler', xblock.location.course_key),
- usage_key=urllib.quote(unicode(xblock.location))
+ usage_key=six.moves.urllib.parse.quote(six.text_type(xblock.location))
)
elif category == 'library':
library_key = xblock.location.course_key
@@ -222,7 +222,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, basestring):
+ if isinstance(data, six.string_types):
data = {'data': data}
created_block = store.create_child(
diff --git a/cms/djangoapps/contentstore/views/import_export.py b/cms/djangoapps/contentstore/views/import_export.py
index aa48d8baff..fdbc4e176a 100644
--- a/cms/djangoapps/contentstore/views/import_export.py
+++ b/cms/djangoapps/contentstore/views/import_export.py
@@ -2,6 +2,8 @@
These views handle all actions in Studio related to import and exporting of
courses
"""
+from __future__ import absolute_import
+
import base64
import json
import logging
@@ -42,10 +44,8 @@ __all__ = [
'export_handler', 'export_output_handler', 'export_status_handler',
]
-
log = logging.getLogger(__name__)
-
# Regex to capture Content-Range header ranges.
CONTENT_RE = re.compile(r"(?P\d{1,11})-(?P\d{1,11})/(?P\d{1,11})")
@@ -147,7 +147,7 @@ def _write_chunk(request, courselike_key):
try:
matches = CONTENT_RE.search(request.META["HTTP_CONTENT_RANGE"])
content_range = matches.groupdict()
- except KeyError: # Single chunk
+ except KeyError: # Single chunk
# no Content-Range header, so make one that will work
content_range = {'start': 0, 'stop': 1, 'end': 2}
@@ -179,7 +179,7 @@ def _write_chunk(request, courselike_key):
elif size > int(content_range['stop']) and size == int(content_range['end']):
return JsonResponse({'ImportStatus': 1})
- with open(temp_filepath, mode) as temp_file:
+ with open(temp_filepath, mode) as temp_file: # pylint: disable=W6005
for chunk in request.FILES['course-data'].chunks():
temp_file.write(chunk)
@@ -199,7 +199,7 @@ def _write_chunk(request, courselike_key):
})
log.info(u"Course import %s: Upload complete", courselike_key)
- with open(temp_filepath, 'rb') as local_file:
+ with open(temp_filepath, 'rb') as local_file: # pylint: disable=W6005
django_file = File(local_file)
storage_path = course_import_export_storage.save(u'olx_import/' + filename, django_file)
import_olx.delay(
@@ -386,7 +386,7 @@ def export_status_handler(request, course_key_string):
elif task_status.state in (UserTaskStatus.FAILED, UserTaskStatus.CANCELED):
status = max(-(task_status.completed_steps + 1), -2)
errors = UserTaskArtifact.objects.filter(status=task_status, name='Error')
- if len(errors):
+ if errors:
error = errors[0].text
try:
error = json.loads(error)
diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py
index 492a36d24d..e96956a8e3 100644
--- a/cms/djangoapps/contentstore/views/item.py
+++ b/cms/djangoapps/contentstore/views/item.py
@@ -225,7 +225,10 @@ def xblock_handler(request, usage_key_string):
request.user,
request.json.get('display_name'),
)
- return JsonResponse({'locator': unicode(dest_usage_key), 'courseKey': unicode(dest_usage_key.course_key)})
+ return JsonResponse({
+ 'locator': text_type(dest_usage_key),
+ 'courseKey': text_type(dest_usage_key.course_key)
+ })
else:
return _create_item(request)
elif request.method == 'PATCH':
@@ -323,14 +326,14 @@ def xblock_view_handler(request, usage_key_string, view_name):
xblock.runtime.wrappers.append(partial(
wrap_xblock,
'StudioRuntime',
- usage_id_serializer=unicode,
+ usage_id_serializer=text_type,
request_token=request_token(request),
))
xblock.runtime.wrappers_asides.append(partial(
wrap_xblock_aside,
'StudioRuntime',
- usage_id_serializer=unicode,
+ usage_id_serializer=text_type,
request_token=request_token(request),
extra_classes=['wrapper-comp-plugins']
))
@@ -508,7 +511,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': unicode(xblock.location)})
+ return JsonResponse({'id': text_type(xblock.location)})
old_metadata = own_metadata(xblock)
old_content = xblock.get_explicitly_set_fields_by_scope(Scope.content)
@@ -615,7 +618,7 @@ def _save_xblock(user, xblock, data=None, children_strings=None, metadata=None,
store.update_item(course, user.id)
result = {
- 'id': unicode(xblock.location),
+ 'id': text_type(xblock.location),
'data': data,
'metadata': own_metadata(xblock)
}
@@ -692,7 +695,7 @@ def _create_item(request):
)
return JsonResponse(
- {'locator': unicode(created_block.location), 'courseKey': unicode(created_block.location.course_key)}
+ {'locator': text_type(created_block.location), 'courseKey': text_type(created_block.location.course_key)}
)
@@ -724,7 +727,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 unicode(source_item.location) == target_ancestor['id']:
+ if text_type(source_item.location) == target_ancestor['id']:
return True
return False
@@ -782,15 +785,15 @@ def _move_item(source_usage_key, target_parent_usage_key, user, target_index=Non
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=unicode(source_usage_key),
- parent_usage_key=unicode(source_parent.location)
+ source_usage_key=text_type(source_usage_key),
+ parent_usage_key=text_type(source_parent.location)
)
else:
try:
target_index = int(target_index) if target_index is not None else None
- if len(target_parent.children) < target_index:
+ 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=unicode(source_usage_key),
+ source_usage_key=text_type(source_usage_key),
target_index=target_index
)
except ValueError:
@@ -813,15 +816,15 @@ 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',
- unicode(source_usage_key),
- unicode(source_parent.location),
- unicode(target_parent_usage_key),
+ text_type(source_usage_key),
+ text_type(source_parent.location),
+ text_type(target_parent_usage_key),
insert_at
)
context = {
- 'move_source_locator': unicode(source_usage_key),
- 'parent_locator': unicode(target_parent_usage_key),
+ 'move_source_locator': text_type(source_usage_key),
+ 'parent_locator': text_type(target_parent_usage_key),
'source_index': target_index if target_index is not None else source_index
}
return JsonResponse(context)
@@ -956,7 +959,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([unicode(item) for item in modulestore().get_orphans(course_usage_key)])
+ return JsonResponse([text_type(item) for item in modulestore().get_orphans(course_usage_key)])
else:
raise PermissionDenied()
if request.method == 'DELETE':
@@ -984,7 +987,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 [unicode(item) for item in items]
+ return [text_type(item) for item in items]
def _get_xblock(usage_key, user):
@@ -1004,7 +1007,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: " + unicode(usage_key)}, 404)
+ return JsonResponse({"error": "Can't find item by location: " + text_type(usage_key)}, 404)
def _get_module_info(xblock, rewrite_static_links=True, include_ancestor_info=False, include_publishing_info=False):
@@ -1056,7 +1059,7 @@ def _get_gating_info(course, xblock):
setattr(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 unicode(xblock.location) not in p['namespace']
+ p for p in course.gating_prerequisites if text_type(xblock.location) not in p['namespace']
]
prereq, prereq_min_score, prereq_min_completion = gating_api.get_required_content(
course.id,
@@ -1158,7 +1161,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
pct_sign=_('%'))
xblock_info = {
- 'id': unicode(xblock.location),
+ 'id': text_type(xblock.location),
'display_name': xblock.display_name_with_default,
'category': xblock.category,
'has_children': xblock.has_children
diff --git a/cms/djangoapps/contentstore/views/library.py b/cms/djangoapps/contentstore/views/library.py
index f26b3dca93..a9bdc0dace 100644
--- a/cms/djangoapps/contentstore/views/library.py
+++ b/cms/djangoapps/contentstore/views/library.py
@@ -106,13 +106,13 @@ def _display_library(library_key_string, request):
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, unicode(library_key)
+ request.user.username, text_type(library_key)
)
raise PermissionDenied()
library = modulestore().get_library(library_key)
if library is None:
- log.exception(u"Library %s not found", unicode(library_key))
+ log.exception(u"Library %s not found", text_type(library_key))
raise Http404
response_format = 'html'
@@ -132,7 +132,7 @@ def _list_libraries(request):
lib_info = [
{
"display_name": lib.display_name,
- "library_key": unicode(lib.location.library_key),
+ "library_key": text_type(lib.location.library_key),
}
for lib in modulestore().get_libraries()
if has_studio_read_access(request.user, lib.location.library_key)
@@ -182,7 +182,7 @@ def _create_library(request):
)
})
- lib_key_str = unicode(new_lib.location.library_key)
+ lib_key_str = text_type(new_lib.location.library_key)
return JsonResponse({
'url': reverse_library_url('library_handler', lib_key_str),
'library_key': lib_key_str,
@@ -208,10 +208,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": unicode(library.location.library_key),
- "version": unicode(library.runtime.course_entry.course_key.version_guid),
- "previous_version": unicode(prev_version) if prev_version else None,
- "blocks": [unicode(x) for x in children],
+ "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],
})
can_edit = has_studio_write_access(user, library.location.library_key)
@@ -261,7 +261,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': unicode(library_key),
+ 'library_key': text_type(library_key),
'lib_users_url': reverse_library_url('manage_library_users', library_key_string),
'show_children_previews': library.show_children_previews
})
diff --git a/cms/djangoapps/contentstore/views/organization.py b/cms/djangoapps/contentstore/views/organization.py
index 253229963a..7fa03d51ce 100644
--- a/cms/djangoapps/contentstore/views/organization.py
+++ b/cms/djangoapps/contentstore/views/organization.py
@@ -1,4 +1,6 @@
"""Organizations views for use with Studio."""
+from __future__ import absolute_import
+
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.utils.decorators import method_decorator
diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py
index 2c4694c691..68a26fb9ba 100644
--- a/cms/djangoapps/contentstore/views/preview.py
+++ b/cms/djangoapps/contentstore/views/preview.py
@@ -2,6 +2,7 @@ from __future__ import absolute_import
import logging
from functools import partial
+import six
from django.conf import settings
from django.contrib.auth.decorators import login_required
@@ -101,7 +102,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': unicode(block.scope_ids.usage_id),
+ 'usage_key_string': six.text_type(block.scope_ids.usage_id),
'handler': handler_name,
'suffix': suffix,
}) + '?' + query
@@ -166,7 +167,7 @@ def _preview_module_system(request, descriptor, field_data):
wrap_xblock,
'PreviewRuntime',
display_name_only=display_name_only,
- usage_id_serializer=unicode,
+ usage_id_serializer=six.text_type,
request_token=request_token(request)
),
@@ -180,7 +181,7 @@ def _preview_module_system(request, descriptor, field_data):
partial(
wrap_xblock_aside,
'PreviewRuntime',
- usage_id_serializer=unicode,
+ usage_id_serializer=six.text_type,
request_token=request_token(request)
)
]
diff --git a/cms/djangoapps/contentstore/views/public.py b/cms/djangoapps/contentstore/views/public.py
index c850afaa58..e9d5603cbb 100644
--- a/cms/djangoapps/contentstore/views/public.py
+++ b/cms/djangoapps/contentstore/views/public.py
@@ -1,18 +1,19 @@
"""
Public views
"""
+from __future__ import absolute_import
+
from django.conf import settings
-from django.template.context_processors import csrf
-from django.urls import reverse
-from django.utils.http import urlquote_plus
from django.shortcuts import redirect
+from django.template.context_processors import csrf
+from django.utils.http import urlquote_plus
from django.views.decorators.clickjacking import xframe_options_deny
from django.views.decorators.csrf import ensure_csrf_cookie
+from waffle.decorators import waffle_switch
+from contentstore.config import waffle
from edxmako.shortcuts import render_to_response
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
-from waffle.decorators import waffle_switch
-from contentstore.config import waffle
__all__ = ['signup', 'login_page', 'login_redirect_to_lms', 'howitworks', 'accessibility']
diff --git a/cms/djangoapps/contentstore/views/tabs.py b/cms/djangoapps/contentstore/views/tabs.py
index edfb438165..f5438f1ab5 100644
--- a/cms/djangoapps/contentstore/views/tabs.py
+++ b/cms/djangoapps/contentstore/views/tabs.py
@@ -1,6 +1,9 @@
"""
Views related to course tabs
"""
+from __future__ import absolute_import
+
+import six
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseNotFound
@@ -197,7 +200,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': unicode(tab_type), u'name': unicode(name)})
+ new_tab = CourseTab.from_json({u'type': six.text_type(tab_type), u'name': six.text_type(name)})
tabs = course.tabs
tabs.insert(num, new_tab)
modulestore().update_item(course, ModuleStoreEnum.UserID.primitive_command)
diff --git a/cms/djangoapps/contentstore/views/tests/test_access.py b/cms/djangoapps/contentstore/views/tests/test_access.py
index a9aefbd4c4..5a17c31e0d 100644
--- a/cms/djangoapps/contentstore/views/tests/test_access.py
+++ b/cms/djangoapps/contentstore/views/tests/test_access.py
@@ -1,6 +1,8 @@
"""
Tests access.py
"""
+from __future__ import absolute_import
+
from django.contrib.auth.models import User
from django.test import TestCase
from opaque_keys.edx.locator import CourseLocator
diff --git a/cms/djangoapps/contentstore/views/tests/test_assets.py b/cms/djangoapps/contentstore/views/tests/test_assets.py
index 4db1a66bbd..ea9c6d7a0c 100644
--- a/cms/djangoapps/contentstore/views/tests/test_assets.py
+++ b/cms/djangoapps/contentstore/views/tests/test_assets.py
@@ -1,11 +1,14 @@
"""
Unit tests for the asset upload endpoint.
"""
+from __future__ import absolute_import
+
import json
from datetime import datetime
from io import BytesIO
import mock
+import six
from ddt import data, ddt
from django.conf import settings
from django.test.utils import override_settings
@@ -242,7 +245,7 @@ class PaginationTestCase(AssetsTestCase):
Get from the url and ensure it contains the expected number of responses
"""
resp = self.client.get(url, HTTP_ACCEPT='application/json')
- json_response = json.loads(resp.content)
+ json_response = json.loads(resp.content.decode('utf-8'))
assets_response = json_response['assets']
self.assertEquals(json_response['start'], expected_start)
self.assertEquals(len(assets_response), expected_length)
@@ -254,7 +257,7 @@ class PaginationTestCase(AssetsTestCase):
"""
resp = self.client.get(
url + '?sort=' + sort + '&direction=' + direction, HTTP_ACCEPT='application/json')
- json_response = json.loads(resp.content)
+ json_response = json.loads(resp.content.decode('utf-8'))
assets_response = json_response['assets']
self.assertEquals(sort, json_response['sort'])
self.assertEquals(direction, json_response['direction'])
@@ -290,7 +293,7 @@ class PaginationTestCase(AssetsTestCase):
resp = self.client.get(
url + '?' + filter_type + '=' + filter_value, HTTP_ACCEPT='application/json')
- json_response = json.loads(resp.content)
+ json_response = json.loads(resp.content.decode('utf-8'))
assets_response = json_response['assets']
self.assertEquals(filter_value_split, json_response['assetTypes'])
@@ -301,7 +304,9 @@ class PaginationTestCase(AssetsTestCase):
for content_type in content_types:
# content_type is either not any defined type (i.e. OTHER) or is a defined type (if multiple
# parameters including OTHER are used)
- self.assertTrue(content_type in requested_file_extensions or content_type not in all_file_extensions)
+ self.assertTrue(
+ content_type in requested_file_extensions or content_type not in all_file_extensions
+ )
else:
for content_type in content_types:
self.assertIn(content_type, requested_file_extensions)
@@ -320,7 +325,7 @@ class PaginationTestCase(AssetsTestCase):
"""
resp = self.client.get(
url + '?text_search=' + text_search, HTTP_ACCEPT='application/json')
- json_response = json.loads(resp.content)
+ json_response = json.loads(resp.content.decode('utf-8'))
assets_response = json_response['assets']
self.assertEquals(text_search, json_response['textSearch'])
self.assertEquals(len(assets_response), number_matches)
@@ -329,7 +334,7 @@ class PaginationTestCase(AssetsTestCase):
for asset_response in assets_response:
for token in text_search_tokens:
- self.assertTrue(token.lower() in asset_response['display_name'].lower())
+ self.assertIn(token.lower(), asset_response['display_name'].lower())
@ddt
@@ -383,7 +388,7 @@ class DownloadTestCase(AssetsTestCase):
self.asset_name = 'download_test'
resp = self.upload_asset(self.asset_name)
self.assertEquals(resp.status_code, 200)
- self.uploaded_url = json.loads(resp.content)['asset']['url']
+ self.uploaded_url = json.loads(resp.content.decode('utf-8'))['asset']['url']
def test_download(self):
# Now, download it.
@@ -424,10 +429,12 @@ class AssetToJsonTestCase(AssetsTestCase):
self.assertEquals(output["display_name"], "my_file")
self.assertEquals(output["date_added"], "Jun 01, 2013 at 10:30 UTC")
self.assertEquals(output["url"], "/asset-v1:org+class+run+type@asset+block@my_file_name.jpg")
- self.assertEquals(output["external_url"], "lms_base_url/asset-v1:org+class+run+type@asset+block@my_file_name.jpg")
+ self.assertEquals(
+ output["external_url"], "lms_base_url/asset-v1:org+class+run+type@asset+block@my_file_name.jpg"
+ )
self.assertEquals(output["portable_url"], "/static/my_file_name.jpg")
self.assertEquals(output["thumbnail"], "/asset-v1:org+class+run+type@thumbnail+block@my_file_name_thumb.jpg")
- self.assertEquals(output["id"], unicode(location))
+ self.assertEquals(output["id"], six.text_type(location))
self.assertEquals(output['locked'], True)
output = assets._get_asset_json("name", content_type, upload_date, location, None, False)
@@ -454,7 +461,9 @@ class LockAssetTestCase(AssetsTestCase):
content_type = 'application/txt'
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': unicode(asset_location)})
+ url = reverse_course_url(
+ 'assets_handler', course.id, kwargs={'asset_key_string': six.text_type(asset_location)}
+ )
resp = self.client.post(
url,
@@ -465,7 +474,7 @@ class LockAssetTestCase(AssetsTestCase):
)
self.assertEqual(resp.status_code, 201)
- return json.loads(resp.content)
+ return json.loads(resp.content.decode('utf-8'))
# Load the toy course.
module_store = modulestore()
@@ -505,7 +514,7 @@ class DeleteAssetTestCase(AssetsTestCase):
response = self.client.post(self.url, {"name": self.asset_name, "file": self.asset})
self.assertEquals(response.status_code, 200)
- self.uploaded_url = json.loads(response.content)['asset']['url']
+ self.uploaded_url = json.loads(response.content.decode('utf-8'))['asset']['url']
self.asset_location = AssetKey.from_string(self.uploaded_url)
self.content = contentstore().find(self.asset_location)
@@ -513,7 +522,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': unicode(self.uploaded_url)})
+ 'assets_handler', self.course.id, kwargs={'asset_key_string': six.text_type(self.uploaded_url)})
resp = self.client.delete(test_url, HTTP_ACCEPT="application/json")
self.assertEquals(resp.status_code, 204)
@@ -525,12 +534,12 @@ class DeleteAssetTestCase(AssetsTestCase):
# upload image
response = self.client.post(self.url, {"name": "delete_image_test", "file": image_asset})
self.assertEquals(response.status_code, 200)
- uploaded_image_url = json.loads(response.content)['asset']['url']
+ uploaded_image_url = json.loads(response.content.decode('utf-8'))['asset']['url']
# upload image thumbnail
response = self.client.post(self.url, {"name": "delete_image_thumb_test", "file": thumbnail_image_asset})
self.assertEquals(response.status_code, 200)
- thumbnail_url = json.loads(response.content)['asset']['url']
+ thumbnail_url = json.loads(response.content.decode('utf-8'))['asset']['url']
thumbnail_location = StaticContent.get_location_from_path(thumbnail_url)
image_asset_location = AssetKey.from_string(uploaded_image_url)
@@ -542,21 +551,23 @@ class DeleteAssetTestCase(AssetsTestCase):
mock_asset_key.return_value = thumbnail_location
test_url = reverse_course_url(
- 'assets_handler', self.course.id, kwargs={'asset_key_string': unicode(uploaded_image_url)})
+ 'assets_handler', self.course.id, kwargs={'asset_key_string': six.text_type(uploaded_image_url)})
resp = self.client.delete(test_url, HTTP_ACCEPT="application/json")
self.assertEquals(resp.status_code, 204)
def test_delete_asset_with_invalid_asset(self):
""" Tests the sad path :( """
test_url = reverse_course_url(
- 'assets_handler', self.course.id, kwargs={'asset_key_string': unicode("/c4x/edX/toy/asset/invalid.pdf")})
+ 'assets_handler',
+ self.course.id, kwargs={'asset_key_string': six.text_type("/c4x/edX/toy/asset/invalid.pdf")}
+ )
resp = self.client.delete(test_url, HTTP_ACCEPT="application/json")
self.assertEquals(resp.status_code, 404)
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': unicode(self.uploaded_url)})
+ 'assets_handler', self.course.id, kwargs={'asset_key_string': six.text_type(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")
diff --git a/cms/djangoapps/contentstore/views/tests/test_certificates.py b/cms/djangoapps/contentstore/views/tests/test_certificates.py
index e315cf76fe..e4001c210e 100644
--- a/cms/djangoapps/contentstore/views/tests/test_certificates.py
+++ b/cms/djangoapps/contentstore/views/tests/test_certificates.py
@@ -3,14 +3,18 @@
"""
Certificates Tests.
"""
+from __future__ import absolute_import
+
import itertools
import json
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 contentstore.tests.utils import CourseTestCase
from contentstore.utils import get_lms_link_for_certificate_web_view, reverse_course_url
@@ -80,7 +84,7 @@ class HelperMethods(object):
'title': 'Title ' + str(i),
'signature_image_path': asset_path_format.format(i),
'id': i
- } for i in xrange(signatory_count)
+ } for i in range(signatory_count)
]
@@ -95,7 +99,7 @@ class HelperMethods(object):
'signatories': signatories,
'version': CERTIFICATE_SCHEMA_VERSION,
'is_active': is_active
- } for i in xrange(count)
+ } for i in range(count)
]
self.course.certificates = {'certificates': certificates}
self.save_course()
@@ -142,7 +146,7 @@ class CertificatesBaseTestCase(object):
self.assertEqual(response.status_code, 400)
self.assertNotIn("Location", response)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
self.assertIn("error", content)
def test_invalid_json(self):
@@ -163,7 +167,7 @@ class CertificatesBaseTestCase(object):
self.assertEqual(response.status_code, 400)
self.assertNotIn("Location", response)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
self.assertIn("error", content)
def test_certificate_data_validation(self):
@@ -231,12 +235,12 @@ class CertificatesListHandlerTestCase(
self.assertEqual(response.status_code, 201)
self.assertIn("Location", response)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
certificate_id = self._remove_ids(content)
self.assertEqual(content, expected)
self.assert_event_emitted(
'edx.certificate.configuration.created',
- course_id=unicode(self.course.id),
+ course_id=six.text_type(self.course.id),
configuration_id=certificate_id,
)
@@ -265,7 +269,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/user/" \
- + str(self.user.id) + "/course/" + unicode(self.course.id) + '?preview=honor'
+ + str(self.user.id) + "/course/" + six.text_type(self.course.id) + '?preview=honor'
link = get_lms_link_for_certificate_web_view(
user_id=self.user.id,
course_key=self.course.id,
@@ -293,7 +297,7 @@ class CertificatesListHandlerTestCase(
# in JSON response
response = self.client.get_json(self._url())
- data = json.loads(response.content)
+ data = json.loads(response.content.decode('utf-8'))
self.assertEquals(len(data), 1)
self.assertEqual(data[0]['name'], 'Test certificate')
self.assertEqual(data[0]['description'], 'Test description')
@@ -413,7 +417,7 @@ class CertificatesListHandlerTestCase(
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
- new_certificate = json.loads(response.content)
+ new_certificate = json.loads(response.content.decode('utf-8'))
for prev_certificate in self.course.certificates['certificates']:
self.assertNotEqual(new_certificate.get('id'), prev_certificate.get('id'))
@@ -467,11 +471,11 @@ class CertificatesDetailHandlerTestCase(
HTTP_ACCEPT="application/json",
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
self.assertEqual(content, expected)
self.assert_event_emitted(
'edx.certificate.configuration.created',
- course_id=unicode(self.course.id),
+ course_id=six.text_type(self.course.id),
configuration_id=666,
)
@@ -499,11 +503,11 @@ class CertificatesDetailHandlerTestCase(
HTTP_ACCEPT="application/json",
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
self.assertEqual(content, expected)
self.assert_event_emitted(
'edx.certificate.configuration.modified',
- course_id=unicode(self.course.id),
+ course_id=six.text_type(self.course.id),
configuration_id=1,
)
self.reload_course()
@@ -550,7 +554,7 @@ class CertificatesDetailHandlerTestCase(
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertEqual(response.status_code, 201)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
self.assertEqual(content, expected)
@ddt.data(C4X_SIGNATORY_PATH, SIGNATORY_PATH)
@@ -568,7 +572,7 @@ class CertificatesDetailHandlerTestCase(
self.assertEqual(response.status_code, 204)
self.assert_event_emitted(
'edx.certificate.configuration.deleted',
- course_id=unicode(self.course.id),
+ course_id=six.text_type(self.course.id),
configuration_id='1',
)
self.reload_course()
@@ -592,7 +596,7 @@ class CertificatesDetailHandlerTestCase(
self.assertEqual(response.status_code, 204)
self.assert_event_emitted(
'edx.certificate.configuration.deleted',
- course_id=unicode(self.course.id),
+ course_id=six.text_type(self.course.id),
configuration_id='1',
)
self.reload_course()
@@ -617,7 +621,7 @@ class CertificatesDetailHandlerTestCase(
self.assertEqual(response.status_code, 204)
self.assert_event_emitted(
'edx.certificate.configuration.deleted',
- course_id=unicode(self.course.id),
+ course_id=six.text_type(self.course.id),
configuration_id='1',
)
self.reload_course()
@@ -785,7 +789,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=unicode(self.course.id),
+ course_id=six.text_type(self.course.id),
)
@ddt.data(*itertools.product([True, False], [C4X_SIGNATORY_PATH, SIGNATORY_PATH]))
diff --git a/cms/djangoapps/contentstore/views/tests/test_container_page.py b/cms/djangoapps/contentstore/views/tests/test_container_page.py
index 25ef5d437c..fddf3bf6e8 100644
--- a/cms/djangoapps/contentstore/views/tests/test_container_page.py
+++ b/cms/djangoapps/contentstore/views/tests/test_container_page.py
@@ -1,9 +1,12 @@
"""
Unit tests for the container page.
"""
+from __future__ import absolute_import
+
import datetime
import re
+import six
from django.http import Http404
from django.test.client import RequestFactory
from django.utils import http
@@ -60,12 +63,12 @@ class ContainerPageTestCase(StudioPageTestCase, LibraryTestCase):
u'data-locator="{0}" data-course-key="{0.course_key}">'.format(self.child_container.location)
),
expected_breadcrumbs=(
- ur'\s*Week 1\s*\s*'
- ur'\s*Lesson 1\s*\s*'
- ur'\s*Unit\s*'
+ u'\\s*Week 1\\s*\\s*'
+ u'\\s*Lesson 1\\s*\\s*'
+ u'\\s*Unit\\s*'
).format(
- course=re.escape(unicode(self.course.id)),
- unit=re.escape(unicode(self.vertical.location)),
+ course=re.escape(six.text_type(self.course.id)),
+ unit=re.escape(six.text_type(self.vertical.location)),
classes='navigation-item navigation-link navigation-parent',
section_parameters=re.escape(u'?show={}'.format(http.urlquote(self.chapter.location))),
subsection_parameters=re.escape(u'?show={}'.format(http.urlquote(self.sequential.location))),
@@ -88,14 +91,14 @@ class ContainerPageTestCase(StudioPageTestCase, LibraryTestCase):
u'data-locator="{0}" data-course-key="{0.course_key}">'.format(draft_container.location)
),
expected_breadcrumbs=(
- ur'\s*Week 1\s*\s*'
- ur'\s*Lesson 1\s*\s*'
- ur'\s*Unit\s*\s*'
- ur'\s*Split Test\s*'
+ u'\\s*Week 1\\s*\\s*'
+ u'\\s*Lesson 1\\s*\\s*'
+ u'\\s*Unit\\s*\\s*'
+ u'\\s*Split Test\\s*'
).format(
- course=re.escape(unicode(self.course.id)),
- unit=re.escape(unicode(self.vertical.location)),
- split_test=re.escape(unicode(self.child_container.location)),
+ course=re.escape(six.text_type(self.course.id)),
+ unit=re.escape(six.text_type(self.vertical.location)),
+ split_test=re.escape(six.text_type(self.child_container.location)),
classes=u'navigation-item navigation-link navigation-parent',
section_parameters=re.escape(u'?show={}'.format(http.urlquote(self.chapter.location))),
subsection_parameters=re.escape(u'?show={}'.format(http.urlquote(self.sequential.location))),
@@ -223,6 +226,6 @@ class ContainerPageTestCase(StudioPageTestCase, LibraryTestCase):
# Check 200 response if 'usage_key_string' is correct
response = views.container_handler(
request=request,
- usage_key_string=unicode(self.vertical.location)
+ usage_key_string=six.text_type(self.vertical.location)
)
self.assertEqual(response.status_code, 200)
diff --git a/cms/djangoapps/contentstore/views/tests/test_course_index.py b/cms/djangoapps/contentstore/views/tests/test_course_index.py
index 42879eed70..e9af42c822 100644
--- a/cms/djangoapps/contentstore/views/tests/test_course_index.py
+++ b/cms/djangoapps/contentstore/views/tests/test_course_index.py
@@ -1,6 +1,8 @@
"""
Unit tests for getting the list of courses and the course outline.
"""
+from __future__ import absolute_import
+
import datetime
import json
@@ -8,24 +10,25 @@ 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
from django.utils.translation import ugettext as _
+from edx_django_utils.monitoring.middleware import _DEFAULT_NAMESPACE as DJANGO_UTILS_NAMESPACE
from opaque_keys.edx.locator import CourseLocator
from search.api import perform_search
-from edx_django_utils.monitoring.middleware import _DEFAULT_NAMESPACE as DJANGO_UTILS_NAMESPACE
+from contentstore.config.waffle import WAFFLE_NAMESPACE as STUDIO_WAFFLE_NAMESPACE
from contentstore.courseware_index import CoursewareSearchIndexer, SearchIndexingError
from contentstore.tests.utils import CourseTestCase
from contentstore.utils import add_instructor, reverse_course_url, reverse_usage_url
+from contentstore.views.course import WAFFLE_NAMESPACE as COURSE_WAFFLE_NAMESPACE
from contentstore.views.course import (
_deprecated_blocks_info,
course_outline_initial_state,
reindex_course_and_check_access
)
-from contentstore.config.waffle import WAFFLE_NAMESPACE as STUDIO_WAFFLE_NAMESPACE
-from contentstore.views.course import WAFFLE_NAMESPACE as COURSE_WAFFLE_NAMESPACE
from contentstore.views.item import VisibilityState, create_xblock_info
from course_action_state.managers import CourseRerunUIStateManager
from course_action_state.models import CourseRerunState
@@ -139,11 +142,11 @@ class TestCourseIndex(CourseTestCase):
ItemFactory.create(parent_location=subsection.location, category="video", display_name="My Video")
resp = self.client.get(outline_url, HTTP_ACCEPT='application/json')
- json_response = json.loads(resp.content)
+ json_response = json.loads(resp.content.decode('utf-8'))
# First spot check some values in the root response
self.assertEqual(json_response['category'], 'course')
- self.assertEqual(json_response['id'], unicode(self.course.location))
+ self.assertEqual(json_response['id'], six.text_type(self.course.location))
self.assertEqual(json_response['display_name'], self.course.display_name)
self.assertTrue(json_response['published'])
self.assertIsNone(json_response['visibility_state'])
@@ -153,7 +156,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'], unicode(chapter.location))
+ self.assertEqual(first_child_response['id'], six.text_type(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)
@@ -195,7 +198,7 @@ class TestCourseIndex(CourseTestCase):
})
resp = self.client.get(notification_url, HTTP_ACCEPT='application/json')
- json_response = json.loads(resp.content)
+ json_response = json.loads(resp.content.decode('utf-8'))
self.assertEquals(json_response['state'], state)
self.assertEquals(json_response['action'], action)
@@ -461,11 +464,11 @@ class TestCourseOutline(CourseTestCase):
outline_url = reverse_course_url('course_handler', self.course.id)
outline_url = outline_url + '?format=concise' if is_concise else outline_url
resp = self.client.get(outline_url, HTTP_ACCEPT='application/json')
- json_response = json.loads(resp.content)
+ json_response = json.loads(resp.content.decode('utf-8'))
# First spot check some values in the root response
self.assertEqual(json_response['category'], 'course')
- self.assertEqual(json_response['id'], unicode(self.course.location))
+ self.assertEqual(json_response['id'], six.text_type(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'))
@@ -475,7 +478,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'], unicode(self.chapter.location))
+ self.assertEqual(first_child_response['id'], six.text_type(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:
@@ -509,12 +512,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 = unicode(self.chapter.location)
+ chapter_locator = six.text_type(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(unicode(self.sequential.location), expanded_locators)
- self.assertIn(unicode(self.vertical.location), expanded_locators)
+ self.assertIn(six.text_type(self.sequential.location), expanded_locators)
+ self.assertIn(six.text_type(self.vertical.location), expanded_locators)
def _create_test_data(self, course_module, create_blocks=False, publish=True, block_types=None):
"""
@@ -551,7 +554,7 @@ class TestCourseOutline(CourseTestCase):
[component for component in advanced_modules if component in deprecated_block_types]
)
- self.assertItemsEqual(info['blocks'], expected_blocks)
+ six.assertCountEqual(self, info['blocks'], expected_blocks)
self.assertEqual(
info['advance_settings_url'],
reverse_course_url('advanced_settings_handler', course_id)
@@ -677,7 +680,7 @@ class TestCourseReIndex(CourseTestCase):
self.assertIn(self.SUCCESSFUL_RESPONSE, response.content)
self.assertEqual(response.status_code, 200)
- @mock.patch('xmodule.html_module.HtmlDescriptor.index_dictionary')
+ @mock.patch('xmodule.html_module.HtmlBlock.index_dictionary')
def test_reindex_course_search_index_error(self, mock_index_dictionary):
"""
Test json response with mocked error data for html
@@ -703,7 +706,7 @@ class TestCourseReIndex(CourseTestCase):
user=self.user,
size=10,
from_=0,
- course_id=unicode(self.course.id))
+ course_id=six.text_type(self.course.id))
self.assertEqual(response['total'], 1)
# Start manual reindex
@@ -715,10 +718,10 @@ class TestCourseReIndex(CourseTestCase):
user=self.user,
size=10,
from_=0,
- course_id=unicode(self.course.id))
+ course_id=six.text_type(self.course.id))
self.assertEqual(response['total'], 1)
- @mock.patch('xmodule.video_module.VideoDescriptor.index_dictionary')
+ @mock.patch('xmodule.video_module.VideoBlock.index_dictionary')
def test_reindex_video_error_json_responses(self, mock_index_dictionary):
"""
Test json response with mocked error data for video
@@ -729,7 +732,7 @@ class TestCourseReIndex(CourseTestCase):
user=self.user,
size=10,
from_=0,
- course_id=unicode(self.course.id))
+ course_id=six.text_type(self.course.id))
self.assertEqual(response['total'], 1)
# set mocked exception response
@@ -740,7 +743,7 @@ class TestCourseReIndex(CourseTestCase):
with self.assertRaises(SearchIndexingError):
reindex_course_and_check_access(self.course.id, self.user)
- @mock.patch('xmodule.html_module.HtmlDescriptor.index_dictionary')
+ @mock.patch('xmodule.html_module.HtmlBlock.index_dictionary')
def test_reindex_html_error_json_responses(self, mock_index_dictionary):
"""
Test json response with mocked error data for html
@@ -751,7 +754,7 @@ class TestCourseReIndex(CourseTestCase):
user=self.user,
size=10,
from_=0,
- course_id=unicode(self.course.id))
+ course_id=six.text_type(self.course.id))
self.assertEqual(response['total'], 1)
# set mocked exception response
@@ -773,7 +776,7 @@ class TestCourseReIndex(CourseTestCase):
user=self.user,
size=10,
from_=0,
- course_id=unicode(self.course.id))
+ course_id=six.text_type(self.course.id))
self.assertEqual(response['total'], 1)
# set mocked exception response
@@ -813,7 +816,7 @@ class TestCourseReIndex(CourseTestCase):
user=self.user,
size=10,
from_=0,
- course_id=unicode(self.course.id))
+ course_id=six.text_type(self.course.id))
self.assertEqual(response['total'], 1)
# Start manual reindex
@@ -825,10 +828,10 @@ class TestCourseReIndex(CourseTestCase):
user=self.user,
size=10,
from_=0,
- course_id=unicode(self.course.id))
+ course_id=six.text_type(self.course.id))
self.assertEqual(response['total'], 1)
- @mock.patch('xmodule.video_module.VideoDescriptor.index_dictionary')
+ @mock.patch('xmodule.video_module.VideoBlock.index_dictionary')
def test_indexing_video_error_responses(self, mock_index_dictionary):
"""
Test do_course_reindex response with mocked error data for video
@@ -839,7 +842,7 @@ class TestCourseReIndex(CourseTestCase):
user=self.user,
size=10,
from_=0,
- course_id=unicode(self.course.id))
+ course_id=six.text_type(self.course.id))
self.assertEqual(response['total'], 1)
# set mocked exception response
@@ -850,7 +853,7 @@ class TestCourseReIndex(CourseTestCase):
with self.assertRaises(SearchIndexingError):
CoursewareSearchIndexer.do_course_reindex(modulestore(), self.course.id)
- @mock.patch('xmodule.html_module.HtmlDescriptor.index_dictionary')
+ @mock.patch('xmodule.html_module.HtmlBlock.index_dictionary')
def test_indexing_html_error_responses(self, mock_index_dictionary):
"""
Test do_course_reindex response with mocked error data for html
@@ -861,7 +864,7 @@ class TestCourseReIndex(CourseTestCase):
user=self.user,
size=10,
from_=0,
- course_id=unicode(self.course.id))
+ course_id=six.text_type(self.course.id))
self.assertEqual(response['total'], 1)
# set mocked exception response
@@ -883,7 +886,7 @@ class TestCourseReIndex(CourseTestCase):
user=self.user,
size=10,
from_=0,
- course_id=unicode(self.course.id))
+ course_id=six.text_type(self.course.id))
self.assertEqual(response['total'], 1)
# set mocked exception response
diff --git a/cms/djangoapps/contentstore/views/tests/test_course_updates.py b/cms/djangoapps/contentstore/views/tests/test_course_updates.py
index 17f411edde..acdfd28b35 100644
--- a/cms/djangoapps/contentstore/views/tests/test_course_updates.py
+++ b/cms/djangoapps/contentstore/views/tests/test_course_updates.py
@@ -1,13 +1,14 @@
"""
unit tests for course_info views and models.
"""
+from __future__ import absolute_import
+
import json
from django.test.utils import override_settings
from mock import patch
from opaque_keys.edx.keys import UsageKey
-from contentstore.models import PushNotificationConfig
from contentstore.tests.test_course_settings import CourseTestCase
from contentstore.utils import reverse_course_url, reverse_usage_url
from xmodule.modulestore.django import modulestore
@@ -36,7 +37,7 @@ class CourseUpdateTest(CourseTestCase):
resp = self.client.ajax_post(url, payload)
self.assertContains(resp, '', status_code=200)
- return json.loads(resp.content)
+ return json.loads(resp.content.decode('utf-8'))
resp = self.client.get_html(
reverse_course_url('course_info_handler', self.course.id)
@@ -56,7 +57,7 @@ class CourseUpdateTest(CourseTestCase):
first_update_url, payload, HTTP_X_HTTP_METHOD_OVERRIDE="PUT", REQUEST_METHOD="POST"
)
- self.assertHTMLEqual(content, json.loads(resp.content)['content'],
+ self.assertHTMLEqual(content, json.loads(resp.content.decode('utf-8'))['content'],
"iframe w/ div")
# refetch using provided id
refetched = self.client.get_json(first_update_url)
@@ -71,7 +72,7 @@ class CourseUpdateTest(CourseTestCase):
course_update_url = self.create_update_url()
resp = self.client.get_json(course_update_url)
- payload = json.loads(resp.content)
+ payload = json.loads(resp.content.decode('utf-8'))
self.assertEqual(len(payload), 2)
# try json w/o required fields
@@ -118,12 +119,12 @@ class CourseUpdateTest(CourseTestCase):
self.assertHTMLEqual(content, payload['content'], "single iframe")
# first count the entries
resp = self.client.get_json(course_update_url)
- payload = json.loads(resp.content)
+ payload = json.loads(resp.content.decode('utf-8'))
before_delete = len(payload)
url = self.create_update_url(provided_id=this_id)
resp = self.client.delete(url)
- payload = json.loads(resp.content)
+ payload = json.loads(resp.content.decode('utf-8'))
self.assertEqual(len(payload), before_delete - 1)
def test_course_updates_compatibility(self):
@@ -148,7 +149,7 @@ class CourseUpdateTest(CourseTestCase):
# test getting all updates list
course_update_url = self.create_update_url()
resp = self.client.get_json(course_update_url)
- payload = json.loads(resp.content)
+ payload = json.loads(resp.content.decode('utf-8'))
self.assertEqual(payload, [{u'date': update_date, u'content': update_content, u'id': 1}])
self.assertEqual(len(payload), 1)
@@ -156,7 +157,7 @@ class CourseUpdateTest(CourseTestCase):
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)
+ 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.assertHTMLEqual(update_date, payload['date'])
self.assertHTMLEqual(update_content, payload['content'])
@@ -171,7 +172,7 @@ class CourseUpdateTest(CourseTestCase):
resp = self.client.ajax_post(
course_update_url + '1', payload, HTTP_X_HTTP_METHOD_OVERRIDE="PUT", REQUEST_METHOD="POST"
)
- self.assertHTMLEqual(update_content, json.loads(resp.content)['content'])
+ 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}])
# course_updates 'data' field should not update automatically
@@ -182,7 +183,7 @@ class CourseUpdateTest(CourseTestCase):
self.assertEqual(course_updates.items, [{u'date': update_date, u'content': update_content, u'id': 1}])
# now try to delete first update item
resp = self.client.delete(course_update_url + '1')
- self.assertEqual(json.loads(resp.content), [])
+ 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,
@@ -190,7 +191,7 @@ class CourseUpdateTest(CourseTestCase):
# now try to get deleted update
resp = self.client.get_json(course_update_url + '1')
- payload = json.loads(resp.content)
+ payload = json.loads(resp.content.decode('utf-8'))
self.assertEqual(payload.get('error'), u"Course update not found.")
self.assertEqual(resp.status_code, 404)
@@ -205,7 +206,7 @@ class CourseUpdateTest(CourseTestCase):
resp = self.client.ajax_post(
course_update_url, payload, REQUEST_METHOD="POST"
)
- self.assertHTMLEqual(update_content, json.loads(resp.content)['content'])
+ self.assertHTMLEqual(update_content, json.loads(resp.content.decode('utf-8'))['content'])
def test_no_ol_course_update(self):
'''Test trying to add to a saved course_update which is not an ol.'''
@@ -228,16 +229,16 @@ class CourseUpdateTest(CourseTestCase):
course_update_url = self.create_update_url()
resp = self.client.ajax_post(course_update_url, payload)
- payload = json.loads(resp.content)
+ payload = json.loads(resp.content.decode('utf-8'))
self.assertHTMLEqual(payload['content'], content)
# now confirm that the bad news and the iframe make up single update
resp = self.client.get_json(course_update_url)
- payload = json.loads(resp.content)
+ payload = json.loads(resp.content.decode('utf-8'))
self.assertEqual(len(payload), 1)
- def post_course_update(self, send_push_notification=False):
+ def post_course_update(self):
"""
Posts an update to the course
"""
@@ -248,26 +249,20 @@ class CourseUpdateTest(CourseTestCase):
content = u"Sample update"
payload = {'content': content, 'date': 'January 8, 2013'}
- if send_push_notification:
- payload['push_notification_selected'] = True
resp = self.client.ajax_post(course_update_url, payload)
# check that response status is 200 not 400
self.assertEqual(resp.status_code, 200)
- payload = json.loads(resp.content)
+ payload = json.loads(resp.content.decode('utf-8'))
self.assertHTMLEqual(payload['content'], content)
- @patch("contentstore.push_notification.send_push_course_update")
- def test_post_course_update(self, mock_push_update):
+ def test_post_course_update(self):
"""
Test that a user can successfully post on course updates and handouts of a course
"""
self.post_course_update()
- # check that push notifications are not sent
- self.assertFalse(mock_push_update.called)
-
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')
@@ -283,34 +278,5 @@ class CourseUpdateTest(CourseTestCase):
# check that response status is 200 not 500
self.assertEqual(resp.status_code, 200)
- payload = json.loads(resp.content)
+ payload = json.loads(resp.content.decode('utf-8'))
self.assertHTMLEqual(payload['data'], content)
-
- @patch("contentstore.push_notification.send_push_course_update")
- def test_notifications_enabled_but_not_requested(self, mock_push_update):
- PushNotificationConfig(enabled=True).save()
- self.post_course_update()
- self.assertFalse(mock_push_update.called)
-
- @patch("contentstore.push_notification.send_push_course_update")
- def test_notifications_enabled_and_sent(self, mock_push_update):
- PushNotificationConfig(enabled=True).save()
- self.post_course_update(send_push_notification=True)
- self.assertTrue(mock_push_update.called)
-
- @override_settings(PARSE_KEYS={"APPLICATION_ID": "TEST_APPLICATION_ID", "REST_API_KEY": "TEST_REST_API_KEY"})
- @patch("contentstore.push_notification.Push")
- def test_notifications_sent_to_parse(self, mock_parse_push):
- PushNotificationConfig(enabled=True).save()
- self.post_course_update(send_push_notification=True)
- self.assertEquals(mock_parse_push.alert.call_count, 2)
-
- @override_settings(PARSE_KEYS={"APPLICATION_ID": "TEST_APPLICATION_ID", "REST_API_KEY": "TEST_REST_API_KEY"})
- @patch("contentstore.push_notification.log_exception")
- @patch("contentstore.push_notification.Push")
- def test_notifications_error_from_parse(self, mock_parse_push, mock_log_exception):
- PushNotificationConfig(enabled=True).save()
- from parse_rest.core import ParseError
- mock_parse_push.alert.side_effect = ParseError
- self.post_course_update(send_push_notification=True)
- self.assertTrue(mock_log_exception.called)
diff --git a/cms/djangoapps/contentstore/views/tests/test_credit_eligibility.py b/cms/djangoapps/contentstore/views/tests/test_credit_eligibility.py
index 6f882a79d1..68710b34eb 100644
--- a/cms/djangoapps/contentstore/views/tests/test_credit_eligibility.py
+++ b/cms/djangoapps/contentstore/views/tests/test_credit_eligibility.py
@@ -2,7 +2,10 @@
Unit tests for credit eligibility UI in Studio.
"""
+from __future__ import absolute_import
+
import mock
+import six
from contentstore.tests.utils import CourseTestCase
from contentstore.utils import reverse_course_url
@@ -20,7 +23,7 @@ class CreditEligibilityTest(CourseTestCase):
def setUp(self):
super(CreditEligibilityTest, self).setUp()
self.course = CourseFactory.create(org='edX', number='dummy', display_name='Credit Course')
- self.course_details_url = reverse_course_url('settings_handler', unicode(self.course.id))
+ self.course_details_url = reverse_course_url('settings_handler', six.text_type(self.course.id))
@mock.patch.dict("django.conf.settings.FEATURES", {'ENABLE_CREDIT_ELIGIBILITY': False})
def test_course_details_with_disabled_setting(self):
@@ -48,7 +51,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=unicode(self.course.id), enabled=True)
+ credit_course = CreditCourse(course_key=six.text_type(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
diff --git a/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py b/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py
index 0f66cbc37b..7e7a2dc84d 100644
--- a/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py
+++ b/cms/djangoapps/contentstore/views/tests/test_entrance_exam.py
@@ -1,7 +1,11 @@
"""
Test module for Entrance Exams AJAX callback handler workflows
"""
+from __future__ import absolute_import
+
import json
+
+import six
from django.conf import settings
from django.contrib.auth.models import User
from django.test.client import RequestFactory
@@ -38,15 +42,15 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
super(EntranceExamHandlerTests, self).setUp()
self.course_key = self.course.id
self.usage_key = self.course.location
- self.course_url = '/course/{}'.format(unicode(self.course.id))
- self.exam_url = '/course/{}/entrance_exam/'.format(unicode(self.course.id))
+ 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.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 = unicode(self.course.location)
+ parent_locator = six.text_type(self.course.location)
created_block = create_xblock(
parent_locator=parent_locator,
user=self.user,
@@ -56,8 +60,8 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
)
add_entrance_exam_milestone(self.course.id, created_block)
content_milestones = milestones_helpers.get_course_content_milestones(
- unicode(self.course.id),
- unicode(created_block.location),
+ six.text_type(self.course.id),
+ six.text_type(created_block.location),
self.milestone_relationship_types['FULFILLS']
)
self.assertTrue(len(content_milestones))
@@ -67,7 +71,7 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
"""
Unit Test: test removal of entrance exam milestone content
"""
- parent_locator = unicode(self.course.location)
+ parent_locator = six.text_type(self.course.location)
created_block = create_xblock(
parent_locator=parent_locator,
user=self.user,
@@ -77,8 +81,8 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
)
add_entrance_exam_milestone(self.course.id, created_block)
content_milestones = milestones_helpers.get_course_content_milestones(
- unicode(self.course.id),
- unicode(created_block.location),
+ six.text_type(self.course.id),
+ six.text_type(created_block.location),
self.milestone_relationship_types['FULFILLS']
)
self.assertEqual(len(content_milestones), 1)
@@ -87,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(
- unicode(self.course.id),
- unicode(created_block.location),
+ six.text_type(self.course.id),
+ six.text_type(created_block.location),
self.milestone_relationship_types['FULFILLS']
)
self.assertEqual(len(content_milestones), 0)
@@ -108,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(unicode(self.course.id))))
+ self.assertTrue(len(milestones_helpers.get_course_milestones(six.text_type(self.course.id))))
content_milestones = milestones_helpers.get_course_content_milestones(
- unicode(self.course.id),
+ six.text_type(self.course.id),
metadata['entrance_exam_id']['value'],
self.milestone_relationship_types['FULFILLS']
)
@@ -130,7 +134,7 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
# Add a new child sequential to the exam module
# Confirm that the grader type is 'Entrance Exam'
- chapter_locator_string = json.loads(resp.content).get('locator')
+ chapter_locator_string = json.loads(resp.content.decode('utf-8')).get('locator')
# chapter_locator = UsageKey.from_string(chapter_locator_string)
seq_data = {
'category': "sequential",
@@ -138,7 +142,7 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
'parent_locator': chapter_locator_string,
}
resp = self.client.ajax_post(reverse_url('xblock_handler'), seq_data)
- seq_locator_string = json.loads(resp.content).get('locator')
+ seq_locator_string = json.loads(resp.content.decode('utf-8')).get('locator')
seq_locator = UsageKey.from_string(seq_locator_string)
section_grader_type = CourseGradingModel.get_section_grader_type(seq_locator)
self.assertEqual(GRADER_TYPES['ENTRANCE_EXAM'], section_grader_type['graderType'])
@@ -176,11 +180,11 @@ class EntranceExamHandlerTests(CourseTestCase, MilestonesTestCaseMixin):
)
user.set_password('test')
user.save()
- milestones = milestones_helpers.get_course_milestones(unicode(self.course_key))
+ milestones = milestones_helpers.get_course_milestones(six.text_type(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(
- unicode(self.course_key),
+ six.text_type(self.course_key),
milestones_helpers.serialize_user(user)
)
diff --git a/cms/djangoapps/contentstore/views/tests/test_gating.py b/cms/djangoapps/contentstore/views/tests/test_gating.py
index 454cc17bcc..cc1d96818e 100644
--- a/cms/djangoapps/contentstore/views/tests/test_gating.py
+++ b/cms/djangoapps/contentstore/views/tests/test_gating.py
@@ -1,9 +1,12 @@
"""
Unit tests for the gating feature in Studio
"""
+from __future__ import absolute_import
+
import json
import ddt
+import six
from mock import patch
from contentstore.tests.utils import CourseTestCase
@@ -86,12 +89,13 @@ class TestSubsectionGating(CourseTestCase):
self.client.ajax_post(
self.seq2_url,
- data={'prereqUsageKey': unicode(self.seq1.location), 'prereqMinScore': '100', 'prereqMinCompletion': '100'}
+ data={'prereqUsageKey': six.text_type(self.seq1.location), 'prereqMinScore': '100',
+ 'prereqMinCompletion': '100'}
)
mock_set_required_content.assert_called_with(
self.course.id,
self.seq2.location,
- unicode(self.seq1.location),
+ six.text_type(self.seq1.location),
'100',
'100'
)
@@ -128,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 = unicode(self.seq1.location), min_score, min_completion
+ mock_get_required_content.return_value = six.text_type(self.seq1.location), min_score, min_completion
mock_get_prereqs.return_value = [
- {'namespace': '{}{}'.format(unicode(self.seq1.location), GATING_NAMESPACE_QUALIFIER)},
- {'namespace': '{}{}'.format(unicode(self.seq2.location), GATING_NAMESPACE_QUALIFIER)}
+ {'namespace': '{}{}'.format(six.text_type(self.seq1.location), GATING_NAMESPACE_QUALIFIER)},
+ {'namespace': '{}{}'.format(six.text_type(self.seq2.location), GATING_NAMESPACE_QUALIFIER)}
]
resp = json.loads(self.client.get_json(self.seq2_url).content)
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'], unicode(self.seq1.location))
+ self.assertEqual(resp['prereq'], six.text_type(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)
diff --git a/cms/djangoapps/contentstore/views/tests/test_group_configurations.py b/cms/djangoapps/contentstore/views/tests/test_group_configurations.py
index 0bc4f14503..3c3ab85e6b 100644
--- a/cms/djangoapps/contentstore/views/tests/test_group_configurations.py
+++ b/cms/djangoapps/contentstore/views/tests/test_group_configurations.py
@@ -3,22 +3,26 @@
"""
Group Configuration Tests.
"""
+from __future__ import absolute_import
+
import json
from operator import itemgetter
import ddt
+import six
from mock import patch
+from six.moves import range
-from contentstore.utils import reverse_course_url, reverse_usage_url
-from contentstore.course_group_config import GroupConfiguration, CONTENT_GROUP_CONFIGURATION_NAME, ENROLLMENT_SCHEME
+from contentstore.course_group_config import CONTENT_GROUP_CONFIGURATION_NAME, ENROLLMENT_SCHEME, GroupConfiguration
from contentstore.tests.utils import CourseTestCase
+from contentstore.utils import reverse_course_url, reverse_usage_url
from openedx.features.content_type_gating.helpers import CONTENT_GATING_PARTITION_ID
from openedx.features.content_type_gating.partitions import CONTENT_TYPE_GATING_SCHEME
-from xmodule.partitions.partitions import Group, UserPartition, ENROLLMENT_TRACK_PARTITION_ID
-from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
-from xmodule.validation import StudioValidation, StudioValidationMessage
-from xmodule.modulestore.django import modulestore
from xmodule.modulestore import ModuleStoreEnum
+from xmodule.modulestore.django import modulestore
+from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
+from xmodule.partitions.partitions import ENROLLMENT_TRACK_PARTITION_ID, Group, UserPartition
+from xmodule.validation import StudioValidation, StudioValidationMessage
GROUP_CONFIGURATION_JSON = {
u'name': u'Test name',
@@ -160,7 +164,7 @@ class HelperMethods(object):
i, 'Name ' + str(i), 'Description ' + str(i),
[Group(0, 'Group A'), Group(1, 'Group B'), Group(2, 'Group C')],
scheme=None, scheme_id=scheme_id
- ) for i in xrange(count)
+ ) for i in range(count)
]
self.course.user_partitions = partitions
self.save_course()
@@ -217,7 +221,7 @@ class GroupConfigurationsBaseTestCase(object):
)
self.assertEqual(response.status_code, 400)
self.assertNotIn("Location", response)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
self.assertIn("error", content)
def test_invalid_json(self):
@@ -236,7 +240,7 @@ class GroupConfigurationsBaseTestCase(object):
)
self.assertEqual(response.status_code, 400)
self.assertNotIn("Location", response)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
self.assertIn("error", content)
@@ -305,7 +309,7 @@ class GroupConfigurationsListHandlerTestCase(CourseTestCase, GroupConfigurations
)
self.assertEqual(response.status_code, 201)
self.assertIn("Location", response)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
configuration_id, group_ids = self._remove_ids(content) # pylint: disable=unused-variable
self.assertEqual(content, expected)
# IDs are unique
@@ -338,7 +342,7 @@ class GroupConfigurationsListHandlerTestCase(CourseTestCase, GroupConfigurations
"""
group_config = dict(GROUP_CONFIGURATION_JSON)
group_config['scheme'] = scheme_id
- group_config.setdefault('parameters', {})['course_id'] = unicode(self.course.id)
+ group_config.setdefault('parameters', {})['course_id'] = six.text_type(self.course.id)
response = self.client.ajax_post(
self._url(),
data=group_config
@@ -388,7 +392,7 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
HTTP_ACCEPT="application/json",
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
self.assertEqual(content, expected)
self.reload_course()
@@ -429,7 +433,7 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
HTTP_ACCEPT="application/json",
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
self.assertEqual(content, expected)
self.reload_course()
@@ -481,7 +485,7 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertEqual(response.status_code, 400)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
self.assertTrue(content['error'])
self.reload_course()
# Verify that user_partitions and groups are still the same.
@@ -534,7 +538,7 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
HTTP_ACCEPT="application/json",
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
self.assertEqual(content, expected)
self.reload_course()
# Verify that user_partitions in the course contains the new group configuration.
@@ -575,7 +579,7 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
HTTP_ACCEPT="application/json",
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
self.assertEqual(content, expected)
self.reload_course()
@@ -623,7 +627,7 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertEqual(response.status_code, 400)
- content = json.loads(response.content)
+ content = json.loads(response.content.decode('utf-8'))
self.assertTrue(content['error'])
self.reload_course()
# Verify that user_partitions is still the same.
@@ -655,7 +659,7 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
"""
group_config = dict(GROUP_CONFIGURATION_JSON)
group_config['scheme'] = scheme_id
- group_config.setdefault('parameters', {})['course_id'] = unicode(self.course.id)
+ group_config.setdefault('parameters', {})['course_id'] = six.text_type(self.course.id)
response = self.client.ajax_post(
self._url(),
data=group_config
@@ -673,7 +677,7 @@ class GroupConfigurationsDetailHandlerTestCase(CourseTestCase, GroupConfiguratio
"""
group_config = dict(GROUP_CONFIGURATION_JSON)
group_config['scheme'] = scheme_id
- group_config.setdefault('parameters', {})['course_id'] = unicode(self.course.id)
+ group_config.setdefault('parameters', {})['course_id'] = six.text_type(self.course.id)
response = self.client.put(
self._url(cid=partition_id),
data=json.dumps(group_config),
@@ -1112,10 +1116,10 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
# This used to cause an exception since the code assumed that
# only one partition would be available.
actual = GroupConfiguration.get_partitions_usage_info(self.store, self.course)
- self.assertEqual(actual.keys(), [0])
+ self.assertEqual(list(actual.keys()), [0])
actual = GroupConfiguration.get_content_groups_items_usage_info(self.store, self.course)
- self.assertEqual(actual.keys(), [0])
+ self.assertEqual(list(actual.keys()), [0])
def test_can_handle_duplicate_group_ids(self):
# Create the user partitions
@@ -1150,14 +1154,14 @@ class GroupConfigurationsUsageInfoTestCase(CourseTestCase, HelperMethods):
# This used to cause an exception since the code assumed that
# only one partition would be available.
actual = GroupConfiguration.get_partitions_usage_info(self.store, self.course)
- self.assertEqual(actual.keys(), [0, 1])
- self.assertEqual(actual[0].keys(), [2])
- self.assertEqual(actual[1].keys(), [3])
+ self.assertEqual(list(actual.keys()), [0, 1])
+ self.assertEqual(list(actual[0].keys()), [2])
+ self.assertEqual(list(actual[1].keys()), [3])
actual = GroupConfiguration.get_content_groups_items_usage_info(self.store, self.course)
- self.assertEqual(actual.keys(), [0, 1])
- self.assertEqual(actual[0].keys(), [2])
- self.assertEqual(actual[1].keys(), [3])
+ self.assertEqual(list(actual.keys()), [0, 1])
+ self.assertEqual(list(actual[0].keys()), [2])
+ self.assertEqual(list(actual[1].keys()), [3])
class GroupConfigurationsValidationTestCase(CourseTestCase, HelperMethods):
diff --git a/cms/djangoapps/contentstore/views/tests/test_header_menu.py b/cms/djangoapps/contentstore/views/tests/test_header_menu.py
index 9c8d7c6f11..a5334f97cb 100644
--- a/cms/djangoapps/contentstore/views/tests/test_header_menu.py
+++ b/cms/djangoapps/contentstore/views/tests/test_header_menu.py
@@ -3,6 +3,8 @@
"""
Course Header Menu Tests.
"""
+from __future__ import absolute_import
+
from django.conf import settings
from django.test.utils import override_settings
diff --git a/cms/djangoapps/contentstore/views/tests/test_helpers.py b/cms/djangoapps/contentstore/views/tests/test_helpers.py
index ace156d281..e141adcd62 100644
--- a/cms/djangoapps/contentstore/views/tests/test_helpers.py
+++ b/cms/djangoapps/contentstore/views/tests/test_helpers.py
@@ -2,6 +2,9 @@
Unit tests for helpers.py.
"""
+from __future__ import absolute_import
+
+import six
from django.utils import http
from contentstore.tests.utils import CourseTestCase
@@ -17,7 +20,7 @@ class HelpersTestCase(CourseTestCase):
def test_xblock_studio_url(self):
# Verify course URL
- course_url = u'/course/{}'.format(unicode(self.course.id))
+ course_url = u'/course/{}'.format(six.text_type(self.course.id))
self.assertEqual(xblock_studio_url(self.course), course_url)
# Verify chapter URL
@@ -53,7 +56,7 @@ class HelpersTestCase(CourseTestCase):
# Verify library URL
library = LibraryFactory.create()
- expected_url = u'/library/{}'.format(unicode(library.location.library_key))
+ expected_url = u'/library/{}'.format(six.text_type(library.location.library_key))
self.assertEqual(xblock_studio_url(library), expected_url)
def test_xblock_type_display_name(self):
diff --git a/cms/djangoapps/contentstore/views/tests/test_import_export.py b/cms/djangoapps/contentstore/views/tests/test_import_export.py
index e2162d7840..cfe8577cf0 100644
--- a/cms/djangoapps/contentstore/views/tests/test_import_export.py
+++ b/cms/djangoapps/contentstore/views/tests/test_import_export.py
@@ -1,19 +1,22 @@
"""
Unit tests for course import and export
"""
+from __future__ import absolute_import
+
import copy
import json
import logging
import os
import re
import shutil
-import StringIO
+from six import StringIO
import tarfile
import tempfile
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
@@ -21,6 +24,7 @@ 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 user_tasks.models import UserTaskStatus
@@ -103,7 +107,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(unicode(self.course.id))
+ exam_url = '/course/{}/entrance_exam/'.format(six.text_type(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)
@@ -113,9 +117,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(unicode(self.course.id))))
+ self.assertTrue(len(milestones_helpers.get_course_milestones(six.text_type(self.course.id))))
content_milestones = milestones_helpers.get_course_content_milestones(
- unicode(self.course.id),
+ six.text_type(self.course.id),
metadata['entrance_exam_id']['value'],
milestones_helpers.get_milestone_relationship_types()['FULFILLS']
)
@@ -149,7 +153,7 @@ class ImportTestCase(CourseTestCase):
def touch(name):
""" Equivalent to shell's 'touch'"""
- with file(name, 'a'):
+ with open(name, 'a'): # pylint: disable=W6005
os.utime(name, None)
# Create tar test files -----------------------------------------------
@@ -349,7 +353,7 @@ class ImportTestCase(CourseTestCase):
kwargs={'filename': os.path.split(tarpath)[1]}
)
)
- status = json.loads(resp.content)["ImportStatus"]
+ status = json.loads(resp.content.decode('utf-8'))["ImportStatus"]
self.assertEqual(status, -1)
try_tar(self._fifo_tar())
@@ -562,7 +566,7 @@ class ExportTestCase(CourseTestCase):
resp = self.client.post(self.url)
self.assertEquals(resp.status_code, 200)
resp = self.client.get(self.status_url)
- result = json.loads(resp.content)
+ result = json.loads(resp.content.decode('utf-8'))
status = result['ExportStatus']
self.assertEquals(status, 3)
self.assertIn('ExportOutput', result)
@@ -570,7 +574,7 @@ class ExportTestCase(CourseTestCase):
resp = self.client.get(output_url)
self._verify_export_succeeded(resp)
- buff = StringIO.StringIO(b"".join(resp.streaming_content))
+ buff = StringIO("".join(resp.streaming_content))
return tarfile.open(fileobj=buff)
def _verify_export_succeeded(self, resp):
@@ -669,7 +673,7 @@ class ExportTestCase(CourseTestCase):
self.assertEquals(resp.status_code, 200)
resp = self.client.get(self.status_url)
self.assertEquals(resp.status_code, 200)
- result = json.loads(resp.content)
+ result = json.loads(resp.content.decode('utf-8'))
self.assertNotIn('ExportOutput', result)
self.assertIn('ExportError', result)
error = result['ExportError']
@@ -757,7 +761,7 @@ class ExportTestCase(CourseTestCase):
"""
resp = self.client.get(self.status_url)
self.assertEqual(resp.status_code, 200)
- result = json.loads(resp.content)
+ result = json.loads(resp.content.decode('utf-8'))
self.assertEqual(result['ExportStatus'], 0)
def test_output_non_course_author(self):
@@ -796,7 +800,7 @@ class ExportTestCase(CourseTestCase):
file_url='/path/to/testfile.tar.gz',
)
resp = self.client.get(self.status_url)
- result = json.loads(resp.content)
+ result = json.loads(resp.content.decode('utf-8'))
self.assertEqual(result['ExportOutput'], '/path/to/testfile.tar.gz')
@patch('contentstore.views.import_export._latest_task_status')
@@ -816,7 +820,7 @@ class ExportTestCase(CourseTestCase):
file_url='/s3/file/path/testfile.tar.gz',
)
resp = self.client.get(self.status_url)
- result = json.loads(resp.content)
+ result = json.loads(resp.content.decode('utf-8'))
self.assertEqual(result['ExportOutput'], '/s3/file/path/testfile.tar.gz')
@patch('contentstore.views.import_export._latest_task_status')
@@ -833,7 +837,7 @@ class ExportTestCase(CourseTestCase):
mock_latest_task_status.return_value = Mock(state=UserTaskStatus.SUCCEEDED)
mock_get_user_task_artifact.return_value = self._mock_artifact(spec=FileSystemStorage)
resp = self.client.get(self.status_url)
- result = json.loads(resp.content)
+ result = json.loads(resp.content.decode('utf-8'))
file_export_output_url = reverse_course_url('export_output_handler', self.course.id)
self.assertEqual(result['ExportOutput'], file_export_output_url)
diff --git a/cms/djangoapps/contentstore/views/tests/test_item.py b/cms/djangoapps/contentstore/views/tests/test_item.py
index 0a46d5ffb3..0b15e57a2b 100644
--- a/cms/djangoapps/contentstore/views/tests/test_item.py
+++ b/cms/djangoapps/contentstore/views/tests/test_item.py
@@ -1,13 +1,17 @@
"""Tests for items views."""
+from __future__ import absolute_import
+
import json
+import re
from datetime import datetime, timedelta
import ddt
+import six
from django.conf import settings
-from django.urls import reverse
from django.http import Http404
from django.test import TestCase
from django.test.client import RequestFactory
+from django.urls import reverse
from mock import Mock, PropertyMock, patch
from opaque_keys import InvalidKeyError
from opaque_keys.edx.asides import AsideUsageKeyV2
@@ -16,6 +20,7 @@ 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
@@ -36,7 +41,7 @@ from contentstore.views.item import (
_xblock_type_and_display_name,
add_container_page_publishing_info,
create_xblock_info,
- highlights_setting,
+ highlights_setting
)
from lms_xblock.mixin import NONSENSICAL_ACCESS_RESTRICTION
from student.tests.factories import UserFactory
@@ -97,7 +102,7 @@ class ItemTest(CourseTestCase):
Get the UsageKey from the response payload and verify that the status_code was 200.
:param response:
"""
- parsed = json.loads(response.content)
+ parsed = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, 200)
key = UsageKey.from_string(parsed['locator'])
if key.course_key.run is None:
@@ -106,7 +111,9 @@ class ItemTest(CourseTestCase):
def create_xblock(self, parent_usage_key=None, display_name=None, category=None, boilerplate=None):
data = {
- 'parent_locator': unicode(self.usage_key) if parent_usage_key is None else unicode(parent_usage_key),
+ 'parent_locator': six.text_type(
+ self.usage_key
+ )if parent_usage_key is None else six.text_type(parent_usage_key),
'category': category
}
if display_name is not None:
@@ -141,7 +148,7 @@ class GetItemTest(ItemTest):
"""
resp = self._get_preview(usage_key, data)
self.assertEqual(resp.status_code, 200)
- resp_content = json.loads(resp.content)
+ resp_content = json.loads(resp.content.decode('utf-8'))
html = resp_content['html']
self.assertTrue(html)
resources = resp_content['resources']
@@ -249,8 +256,8 @@ class GetItemTest(ItemTest):
html,
# The instance of the wrapper class will have an auto-generated ID. Allow any
# characters after wrapper.
- ur'"/container/{}" class="action-button">\s*View'.format(
- wrapper_usage_key
+ u'"/container/{}" class="action-button">\\s*View'.format(
+ re.escape(six.text_type(wrapper_usage_key))
)
)
@@ -377,7 +384,7 @@ class GetItemTest(ItemTest):
self.assertEqual(resp.status_code, 200)
# Check that the partition and group information was returned
- result = json.loads(resp.content)
+ result = json.loads(resp.content.decode('utf-8'))
self.assertEqual(result["user_partitions"], [
{
"id": ENROLLMENT_TRACK_PARTITION_ID,
@@ -447,7 +454,7 @@ class GetItemTest(ItemTest):
xblock (XBlock): An XBlock item.
xblock_info (dict): A dict containing xblock information.
"""
- self.assertEqual(unicode(xblock.location), xblock_info['id'])
+ self.assertEqual(six.text_type(xblock.location), xblock_info['id'])
self.assertEqual(xblock.display_name, xblock_info['display_name'])
self.assertEqual(xblock.category, xblock_info['category'])
@@ -456,7 +463,7 @@ class GetItemTest(ItemTest):
url = reverse_usage_url('xblock_handler', usage_key) + '?fields={field_type}'.format(field_type=field_type)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
- response = json.loads(response.content)
+ response = json.loads(response.content.decode('utf-8'))
if field_type == 'ancestorInfo':
self.assertIn('ancestors', response)
for ancestor_info in response['ancestors']:
@@ -591,21 +598,21 @@ class DuplicateHelper(object):
self.assertEqual(duplicated_asides[0].field13, 'aside1_default_value3')
self.assertNotEqual(
- unicode(original_item.location),
- unicode(duplicated_item.location),
+ six.text_type(original_item.location),
+ six.text_type(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 unicode(original_item.parent) == unicode(parent_usage_key):
+ if parent_usage_key and six.text_type(original_item.parent) == six.text_type(parent_usage_key):
self.assertEqual(
- unicode(parent_usage_key), unicode(duplicated_item.parent),
+ six.text_type(parent_usage_key), six.text_type(duplicated_item.parent),
"Parent of duplicate should equal parent of source for root xblock when duplicated in-place"
)
else:
self.assertNotEqual(
- unicode(original_item.parent), unicode(duplicated_item.parent),
+ six.text_type(original_item.parent), six.text_type(duplicated_item.parent),
"Parent duplicate should be different from source"
)
@@ -622,7 +629,7 @@ class DuplicateHelper(object):
len(duplicated_item.children),
"Duplicated item differs in number of children"
)
- for i in xrange(len(original_item.children)):
+ for i in range(len(original_item.children)):
if not self._check_equality(original_item.children[i], duplicated_item.children[i], is_child=True):
return False
duplicated_item.children = original_item.children
@@ -650,8 +657,8 @@ class DuplicateHelper(object):
"""
# pylint: disable=no-member
data = {
- 'parent_locator': unicode(parent_usage_key),
- 'duplicate_source_locator': unicode(source_usage_key)
+ 'parent_locator': six.text_type(parent_usage_key),
+ 'duplicate_source_locator': six.text_type(source_usage_key)
}
if display_name is not None:
data['display_name'] = display_name
@@ -862,8 +869,8 @@ class TestMoveItem(ItemTest):
resp (JsonResponse): Response after the move operation is complete.
"""
data = {
- 'move_source_locator': unicode(source_usage_key),
- 'parent_locator': unicode(target_usage_key)
+ 'move_source_locator': six.text_type(source_usage_key),
+ 'parent_locator': six.text_type(target_usage_key)
}
if target_index is not None:
data['target_index'] = target_index
@@ -889,9 +896,9 @@ class TestMoveItem(ItemTest):
expected_index = target_index if target_index is not None else source_index
response = self._move_component(source_usage_key, target_usage_key, target_index)
self.assertEqual(response.status_code, 200)
- response = json.loads(response.content)
- self.assertEqual(response['move_source_locator'], unicode(source_usage_key))
- self.assertEqual(response['parent_locator'], unicode(target_usage_key))
+ 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['source_index'], expected_index)
# Verify parent referance has been changed now.
@@ -952,7 +959,7 @@ class TestMoveItem(ItemTest):
# Move component and verify that response contains initial index
response = self._move_component(self.html_usage_key, self.vert2_usage_key)
- response = json.loads(response.content)
+ response = json.loads(response.content.decode('utf-8'))
self.assertEquals(original_index, response['source_index'])
# Verify that new parent has the moved component at the last index.
@@ -965,7 +972,7 @@ class TestMoveItem(ItemTest):
# Undo Move to the original index, use the source index fetched from the response.
response = self._move_component(self.html_usage_key, self.vert_usage_key, response['source_index'])
- response = json.loads(response.content)
+ response = json.loads(response.content.decode('utf-8'))
self.assertEquals(original_index, response['source_index'])
def test_move_large_target_index(self):
@@ -976,7 +983,7 @@ class TestMoveItem(ItemTest):
parent_children_length = len(parent.children)
response = self._move_component(self.html_usage_key, self.vert2_usage_key, parent_children_length + 10)
self.assertEqual(response.status_code, 400)
- response = json.loads(response.content)
+ response = json.loads(response.content.decode('utf-8'))
expected_error = u'You can not move {usage_key} at an invalid index ({target_index}).'.format(
usage_key=self.html_usage_key,
@@ -993,7 +1000,7 @@ class TestMoveItem(ItemTest):
parent_loc = self.store.get_parent_location(self.html_usage_key)
response = self._move_component(self.html_usage_key, self.seq_usage_key)
self.assertEqual(response.status_code, 400)
- response = json.loads(response.content)
+ response = json.loads(response.content.decode('utf-8'))
expected_error = u'You can not move {source_type} into {target_type}.'.format(
source_type=self.html_usage_key.block_type,
@@ -1011,7 +1018,7 @@ class TestMoveItem(ItemTest):
self.assertEqual(parent_loc, self.vert_usage_key)
response = self._move_component(self.html_usage_key, self.vert_usage_key)
self.assertEqual(response.status_code, 400)
- response = json.loads(response.content)
+ response = json.loads(response.content.decode('utf-8'))
self.assertEqual(response['error'], 'Item is already present in target location.')
self.assertEqual(self.store.get_parent_location(self.html_usage_key), parent_loc)
@@ -1028,7 +1035,7 @@ class TestMoveItem(ItemTest):
self.assertEqual(parent_loc, self.vert_usage_key)
response = self._move_component(library_content_usage_key, library_content_usage_key)
self.assertEqual(response.status_code, 400)
- response = json.loads(response.content)
+ response = json.loads(response.content.decode('utf-8'))
self.assertEqual(response['error'], 'You can not move an item into itself.')
self.assertEqual(self.store.get_parent_location(self.html_usage_key), parent_loc)
@@ -1093,7 +1100,7 @@ class TestMoveItem(ItemTest):
self.setup_and_verify_content_experiment(0)
response = self._move_component(self.html_usage_key, self.split_test_usage_key)
self.assertEqual(response.status_code, 400)
- response = json.loads(response.content)
+ response = json.loads(response.content.decode('utf-8'))
self.assertEqual(response['error'], 'You can not move an item directly into content experiment.')
self.assertEqual(self.store.get_parent_location(self.html_usage_key), self.vert_usage_key)
@@ -1108,7 +1115,7 @@ class TestMoveItem(ItemTest):
for child_vert_usage_key in split_test.children:
response = self._move_component(self.split_test_usage_key, child_vert_usage_key)
self.assertEqual(response.status_code, 400)
- response = json.loads(response.content)
+ response = json.loads(response.content.decode('utf-8'))
self.assertEqual(response['error'], 'You can not move an item into it\'s child.')
self.assertEqual(self.store.get_parent_location(self.split_test_usage_key), self.vert_usage_key)
@@ -1125,7 +1132,7 @@ class TestMoveItem(ItemTest):
# Try to move content experiment further down the level to a child group A nested inside main group A.
response = self._move_component(self.split_test_usage_key, child_split_test.children[0])
self.assertEqual(response.status_code, 400)
- response = json.loads(response.content)
+ response = json.loads(response.content.decode('utf-8'))
self.assertEqual(response['error'], 'You can not move an item into it\'s child.')
self.assertEqual(self.store.get_parent_location(self.split_test_usage_key), self.vert_usage_key)
@@ -1138,7 +1145,7 @@ class TestMoveItem(ItemTest):
parent_loc = self.store.get_parent_location(self.html_usage_key)
response = self._move_component(self.html_usage_key, self.vert2_usage_key, target_index)
self.assertEqual(response.status_code, 400)
- response = json.loads(response.content)
+ 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)
self.assertEqual(response['error'], error)
@@ -1149,7 +1156,7 @@ class TestMoveItem(ItemTest):
"""
Test move an item without specifying the target location.
"""
- data = {'move_source_locator': unicode(self.html_usage_key)}
+ data = {'move_source_locator': six.text_type(self.html_usage_key)}
with self.assertRaises(InvalidKeyError):
self.client.patch(
reverse('xblock_handler'),
@@ -1165,7 +1172,7 @@ class TestMoveItem(ItemTest):
reverse('xblock_handler')
)
self.assertEqual(response.status_code, 400)
- response = json.loads(response.content)
+ response = json.loads(response.content.decode('utf-8'))
self.assertEqual(response['error'], 'Patch request did not recognise any parameters to handle.')
def _verify_validation_message(self, message, expected_message, expected_message_type):
@@ -1238,9 +1245,9 @@ class TestMoveItem(ItemTest):
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',
- unicode(self.html_usage_key),
- unicode(self.vert_usage_key),
- unicode(self.vert2_usage_key),
+ six.text_type(self.html_usage_key),
+ six.text_type(self.vert_usage_key),
+ six.text_type(self.vert2_usage_key),
insert_at
)
@@ -1308,8 +1315,8 @@ class TestMoveItem(ItemTest):
self.setup_course(default_store=store_type)
data = {
- 'move_source_locator': unicode(self.usage_key.course_key.make_usage_key('html', 'html_test')),
- 'parent_locator': unicode(self.vert2_usage_key)
+ '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)
}
with self.assertRaises(ItemNotFoundError):
self.client.patch(
@@ -1554,7 +1561,13 @@ class TestEditItem(TestEditItemSetup):
resp = self.client.ajax_post(
self.seq_update_url,
- data={'children': [unicode(self.problem_usage_key), unicode(unit2_usage_key), unicode(unit1_usage_key)]}
+ data={
+ 'children': [
+ six.text_type(self.problem_usage_key),
+ six.text_type(unit2_usage_key),
+ six.text_type(unit1_usage_key)
+ ]
+ }
)
self.assertEqual(resp.status_code, 200)
@@ -1577,7 +1590,7 @@ class TestEditItem(TestEditItemSetup):
# move unit 1 from sequential1 to sequential2
resp = self.client.ajax_post(
self.seq2_update_url,
- data={'children': [unicode(unit_1_key), unicode(unit_2_key)]}
+ data={'children': [six.text_type(unit_1_key), six.text_type(unit_2_key)]}
)
self.assertEqual(resp.status_code, 200)
@@ -1600,7 +1613,7 @@ class TestEditItem(TestEditItemSetup):
# adding orphaned unit 1 should return an error
resp = self.client.ajax_post(
self.seq2_update_url,
- data={'children': [unicode(unit_1_key)]}
+ data={'children': [six.text_type(unit_1_key)]}
)
self.assertEqual(resp.status_code, 400)
self.assertIn("Invalid data, possibly caused by concurrent authors", resp.content)
@@ -1625,7 +1638,7 @@ class TestEditItem(TestEditItemSetup):
# remove unit 2 should return an error
resp = self.client.ajax_post(
self.seq2_update_url,
- data={'children': [unicode(unit_1_key)]}
+ data={'children': [six.text_type(unit_1_key)]}
)
self.assertEqual(resp.status_code, 400)
self.assertIn("Invalid data, possibly caused by concurrent authors", resp.content)
@@ -1793,7 +1806,7 @@ class TestEditItem(TestEditItemSetup):
self.client.ajax_post(
self.problem_update_url,
data={
- 'id': unicode(self.problem_usage_key),
+ 'id': six.text_type(self.problem_usage_key),
'metadata': {},
'data': "Problem content draft.
"
}
@@ -1848,7 +1861,7 @@ class TestEditItem(TestEditItemSetup):
resp = self.client.ajax_post(
unit_update_url,
data={
- 'id': unicode(unit_usage_key),
+ 'id': six.text_type(unit_usage_key),
'metadata': {},
}
)
@@ -1868,14 +1881,14 @@ class TestEditItem(TestEditItemSetup):
response = self.client.ajax_post(
update_url,
data={
- 'id': unicode(video_usage_key),
+ 'id': six.text_type(video_usage_key),
'metadata': {
'saved_video_position': "Not a valid relative time",
},
}
)
self.assertEqual(response.status_code, 400)
- parsed = json.loads(response.content)
+ parsed = json.loads(response.content.decode('utf-8'))
self.assertIn("error", parsed)
self.assertIn("Incorrect RelativeTime value", parsed["error"]) # See xmodule/fields.py
@@ -1894,10 +1907,10 @@ class TestEditItemSplitMongo(TestEditItemSetup):
"""
view_url = reverse_usage_url("xblock_view_handler", self.problem_usage_key, {"view_name": STUDIO_VIEW})
- for __ in xrange(3):
+ for __ in range(3):
resp = self.client.get(view_url, HTTP_ACCEPT='application/json')
self.assertEqual(resp.status_code, 200)
- content = json.loads(resp.content)
+ content = json.loads(resp.content.decode('utf-8'))
self.assertEqual(len(PyQuery(content['html'])('.xblock-{}'.format(STUDIO_VIEW))), 1)
@@ -1910,8 +1923,8 @@ class TestEditSplitModule(ItemTest):
super(TestEditSplitModule, self).setUp()
self.user = UserFactory()
- self.first_user_partition_group_1 = Group(unicode(MINIMUM_STATIC_PARTITION_ID + 1), 'alpha')
- self.first_user_partition_group_2 = Group(unicode(MINIMUM_STATIC_PARTITION_ID + 2), 'beta')
+ 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 = UserPartition(
MINIMUM_STATIC_PARTITION_ID, 'first_partition', 'First Partition',
[self.first_user_partition_group_1, self.first_user_partition_group_2]
@@ -1919,9 +1932,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(unicode(MINIMUM_STATIC_PARTITION_ID + 1), 'Group 1')
- self.second_user_partition_group_2 = Group(unicode(MINIMUM_STATIC_PARTITION_ID + 2), 'Group 2')
- self.second_user_partition_group_3 = Group(unicode(MINIMUM_STATIC_PARTITION_ID + 3), 'Group 3')
+ 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 = UserPartition(
MINIMUM_STATIC_PARTITION_ID + 10, 'second_partition', 'Second Partition',
[
@@ -1989,8 +2002,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 " + unicode(MINIMUM_STATIC_PARTITION_ID + 1), vertical_0.display_name)
- self.assertEqual("Group ID " + unicode(MINIMUM_STATIC_PARTITION_ID + 2), vertical_1.display_name)
+ 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)
# Verify that the group_id_to_child mapping is correct.
self.assertEqual(2, len(split_test.group_id_to_child))
@@ -2264,8 +2277,8 @@ class TestComponentTemplates(CourseTestCase):
"""
self._verify_basic_component("discussion", "Discussion")
self._verify_basic_component("video", "Video")
- self.assertGreater(self.get_templates_of_type('html'), 0)
- self.assertGreater(self.get_templates_of_type('problem'), 0)
+ self.assertGreater(len(self.get_templates_of_type('html')), 0)
+ self.assertGreater(len(self.get_templates_of_type('problem')), 0)
self.assertIsNone(self.get_templates_of_type('advanced'))
# Now fully disable video through XBlockConfiguration
@@ -2483,7 +2496,7 @@ class TestXBlockInfo(ItemTest):
def test_json_responses(self):
outline_url = reverse_usage_url('xblock_outline_handler', self.usage_key)
resp = self.client.get(outline_url, HTTP_ACCEPT='application/json')
- json_response = json.loads(resp.content)
+ json_response = json.loads(resp.content.decode('utf-8'))
self.validate_course_xblock_info(json_response, course_outline=True)
@ddt.data(
@@ -2664,7 +2677,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'], unicode(self.course.location))
+ self.assertEqual(xblock_info['id'], six.text_type(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'])
@@ -2677,7 +2690,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'], unicode(self.chapter.location))
+ self.assertEqual(xblock_info['id'], six.text_type(self.chapter.location))
self.assertEqual(xblock_info['display_name'], 'Week 1')
self.assertTrue(xblock_info['published'])
self.assertIsNone(xblock_info.get('edited_by', None))
@@ -2697,7 +2710,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'], unicode(self.sequential.location))
+ self.assertEqual(xblock_info['id'], six.text_type(self.sequential.location))
self.assertEqual(xblock_info['display_name'], 'Lesson 1')
self.assertTrue(xblock_info['published'])
self.assertIsNone(xblock_info.get('edited_by', None))
@@ -2710,7 +2723,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'], unicode(self.vertical.location))
+ self.assertEqual(xblock_info['id'], six.text_type(self.vertical.location))
self.assertEqual(xblock_info['display_name'], 'Unit 1')
self.assertTrue(xblock_info['published'])
self.assertEqual(xblock_info['edited_by'], 'testuser')
@@ -2732,7 +2745,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'], unicode(self.video.location))
+ self.assertEqual(xblock_info['id'], six.text_type(self.video.location))
self.assertEqual(xblock_info['display_name'], 'My Video')
self.assertTrue(xblock_info['published'])
self.assertIsNone(xblock_info.get('edited_by', None))
@@ -2849,7 +2862,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'], unicode(self.vertical.location))
+ self.assertEqual(ancestors[0]['id'], six.text_type(self.vertical.location))
self.assertEqual(ancestors[1]['category'], 'library')
def validate_component_xblock_info(self, xblock_info, original_block):
@@ -2857,7 +2870,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'], unicode(original_block.location))
+ self.assertEqual(xblock_info['id'], six.text_type(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))
diff --git a/cms/djangoapps/contentstore/views/tests/test_library.py b/cms/djangoapps/contentstore/views/tests/test_library.py
index 042153004d..130db0b5ea 100644
--- a/cms/djangoapps/contentstore/views/tests/test_library.py
+++ b/cms/djangoapps/contentstore/views/tests/test_library.py
@@ -3,12 +3,15 @@ Unit tests for contentstore.views.library
More important high-level tests are in contentstore/tests/test_libraries.py
"""
+from __future__ import absolute_import
+
import ddt
import mock
from django.conf import settings
from mock import patch
from opaque_keys.edx.locator import CourseKey, LibraryLocator
from six import binary_type, text_type
+from six.moves import range
from contentstore.tests.utils import AjaxEnabledTestClient, CourseTestCase, parse_json
from contentstore.utils import reverse_course_url, reverse_library_url
diff --git a/cms/djangoapps/contentstore/views/tests/test_organizations.py b/cms/djangoapps/contentstore/views/tests/test_organizations.py
index d8894d51fc..17f4f77f43 100644
--- a/cms/djangoapps/contentstore/views/tests/test_organizations.py
+++ b/cms/djangoapps/contentstore/views/tests/test_organizations.py
@@ -1,8 +1,10 @@
"""Tests covering the Organizations listing on the Studio home."""
+from __future__ import absolute_import
+
import json
-from django.urls import reverse
from django.test import TestCase
+from django.urls import reverse
from mock import patch
from student.tests.factories import UserFactory
@@ -30,5 +32,5 @@ class TestOrganizationListing(TestCase):
"""Verify that the organization names list api returns list of organization short names."""
response = self.client.get(self.org_names_listing_url, HTTP_ACCEPT='application/json')
self.assertEqual(response.status_code, 200)
- org_names = json.loads(response.content)
+ org_names = json.loads(response.content.decode('utf-8'))
self.assertEqual(org_names, self.org_short_names)
diff --git a/cms/djangoapps/contentstore/views/tests/test_preview.py b/cms/djangoapps/contentstore/views/tests/test_preview.py
index 07d1a208ae..4aa284ecd9 100644
--- a/cms/djangoapps/contentstore/views/tests/test_preview.py
+++ b/cms/djangoapps/contentstore/views/tests/test_preview.py
@@ -1,10 +1,13 @@
"""
Tests for contentstore.views.preview.py
"""
+from __future__ import absolute_import
+
import re
import ddt
import mock
+import six
from django.test.client import Client, RequestFactory
from xblock.core import XBlock, XBlockAside
@@ -56,7 +59,9 @@ class GetPreviewHtmlTestCase(ModuleStoreTestCase):
html = get_preview_fragment(request, html, context).content
# Verify student view html is returned, and the usage ID is as expected.
- html_pattern = re.escape(unicode(course.id.make_usage_key('html', 'replaceme'))).replace('replaceme', r'html_[0-9]*')
+ html_pattern = re.escape(
+ six.text_type(course.id.make_usage_key('html', 'replaceme'))
+ ).replace('replaceme', r'html_[0-9]*')
self.assertRegexpMatches(
html,
'data-usage-id="{}"'.format(html_pattern)
diff --git a/cms/djangoapps/contentstore/views/tests/test_tabs.py b/cms/djangoapps/contentstore/views/tests/test_tabs.py
index 166971c82c..343d12653a 100644
--- a/cms/djangoapps/contentstore/views/tests/test_tabs.py
+++ b/cms/djangoapps/contentstore/views/tests/test_tabs.py
@@ -1,5 +1,7 @@
""" Tests for tab functions (just primitive). """
+from __future__ import absolute_import
+
import json
from contentstore.tests.utils import CourseTestCase
@@ -36,7 +38,7 @@ class TabsPageTests(CourseTestCase):
"""Verify response is an error listing the invalid_tab_id"""
self.assertEqual(resp.status_code, 400)
- resp_content = json.loads(resp.content)
+ resp_content = json.loads(resp.content.decode('utf-8'))
self.assertIn("error", resp_content)
self.assertIn("invalid_tab_id", resp_content['error'])
@@ -118,7 +120,7 @@ class TabsPageTests(CourseTestCase):
data={'tabs': [{'tab_id': tab_id} for tab_id in tab_ids]},
)
self.assertEqual(resp.status_code, 400)
- resp_content = json.loads(resp.content)
+ resp_content = json.loads(resp.content.decode('utf-8'))
self.assertIn("error", resp_content)
def test_reorder_tabs_invalid_tab(self):
@@ -182,7 +184,7 @@ class TabsPageTests(CourseTestCase):
resp = self.client.get(preview_url, HTTP_ACCEPT='application/json')
self.assertEqual(resp.status_code, 200)
- resp_content = json.loads(resp.content)
+ resp_content = json.loads(resp.content.decode('utf-8'))
html = resp_content['html']
# Verify that the HTML contains the expected elements
diff --git a/cms/djangoapps/contentstore/views/tests/test_textbooks.py b/cms/djangoapps/contentstore/views/tests/test_textbooks.py
index 5b6076f49d..53f6b8487d 100644
--- a/cms/djangoapps/contentstore/views/tests/test_textbooks.py
+++ b/cms/djangoapps/contentstore/views/tests/test_textbooks.py
@@ -1,3 +1,7 @@
+""" Test cases for the textbook index page. """
+
+from __future__ import absolute_import
+
import json
from unittest import TestCase
@@ -30,7 +34,7 @@ class TextbookIndexTestCase(CourseTestCase):
HTTP_X_REQUESTED_WITH='XMLHttpRequest'
)
self.assertEqual(resp.status_code, 200)
- obj = json.loads(resp.content)
+ obj = json.loads(resp.content.decode('utf-8'))
self.assertEqual(self.course.pdf_textbooks, obj)
def test_view_index_xhr_content(self):
@@ -63,7 +67,7 @@ class TextbookIndexTestCase(CourseTestCase):
HTTP_X_REQUESTED_WITH='XMLHttpRequest'
)
self.assertEqual(resp.status_code, 200)
- obj = json.loads(resp.content)
+ obj = json.loads(resp.content.decode('utf-8'))
self.assertEqual(content, obj)
@@ -100,7 +104,7 @@ class TextbookIndexTestCase(CourseTestCase):
HTTP_X_REQUESTED_WITH='XMLHttpRequest'
)
self.assertEqual(resp.status_code, 400)
- obj = json.loads(resp.content)
+ obj = json.loads(resp.content.decode('utf-8'))
self.assertIn("error", obj)
@@ -131,7 +135,7 @@ class TextbookCreateTestCase(CourseTestCase):
)
self.assertEqual(resp.status_code, 201)
self.assertIn("Location", resp)
- textbook = json.loads(resp.content)
+ textbook = json.loads(resp.content.decode('utf-8'))
self.assertIn("id", textbook)
del textbook["id"]
self.assertEqual(self.textbook, textbook)
@@ -147,7 +151,7 @@ class TextbookCreateTestCase(CourseTestCase):
HTTP_X_REQUESTED_WITH="XMLHttpRequest",
)
self.assertEqual(resp.status_code, 201)
- textbook = json.loads(resp.content)
+ textbook = json.loads(resp.content.decode('utf-8'))
self.assertEqual(self.textbook, textbook)
def test_invalid_id(self):
@@ -209,14 +213,14 @@ class TextbookDetailTestCase(CourseTestCase):
"Get the first textbook"
resp = self.client.get(self.url1)
self.assertEqual(resp.status_code, 200)
- compare = json.loads(resp.content)
+ compare = json.loads(resp.content.decode('utf-8'))
self.assertEqual(compare, self.textbook1)
def test_get_2(self):
"Get the second textbook"
resp = self.client.get(self.url2)
self.assertEqual(resp.status_code, 200)
- compare = json.loads(resp.content)
+ compare = json.loads(resp.content.decode('utf-8'))
self.assertEqual(compare, self.textbook2)
def test_get_nonexistant(self):
diff --git a/cms/djangoapps/contentstore/views/tests/test_transcript_settings.py b/cms/djangoapps/contentstore/views/tests/test_transcript_settings.py
index f2f7ef9f20..b89e7992a0 100644
--- a/cms/djangoapps/contentstore/views/tests/test_transcript_settings.py
+++ b/cms/djangoapps/contentstore/views/tests/test_transcript_settings.py
@@ -1,8 +1,11 @@
# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
import json
from io import BytesIO
import ddt
+import six
from django.test.testcases import TestCase
from django.urls import reverse
from edxval import api
@@ -103,7 +106,7 @@ class TranscriptCredentialsTest(CourseTestCase):
content_type='application/json'
)
self.assertEqual(response.status_code, expected_status_code)
- self.assertEqual(response.content, expected_response)
+ self.assertEqual(response.content.decode('utf-8'), expected_response)
@ddt.ddt
@@ -240,8 +243,8 @@ class TranscriptDownloadTest(CourseTestCase):
# Assert the actual response
self.assertEqual(response.status_code, 200)
- self.assertEqual(response.content, expected_content)
- for attribute, value in expected_headers.iteritems():
+ self.assertEqual(response.content.decode('utf-8'), expected_content)
+ for attribute, value in six.iteritems(expected_headers):
self.assertEqual(response.get(attribute), value)
@ddt.data(
@@ -267,7 +270,7 @@ class TranscriptDownloadTest(CourseTestCase):
response = self.client.get(self.view_url, data=request_payload)
# Assert the response
self.assertEqual(response.status_code, 400)
- self.assertEqual(json.loads(response.content)['error'], expected_error_message)
+ self.assertEqual(json.loads(response.content.decode('utf-8'))['error'], expected_error_message)
@ddt.ddt
@@ -369,7 +372,7 @@ class TranscriptUploadTest(CourseTestCase):
# Make request to transcript upload handler
response = self.client.post(self.view_url, request_payload, format='multipart')
self.assertEqual(response.status_code, 400)
- self.assertEqual(json.loads(response.content)['error'], expected_error_message)
+ self.assertEqual(json.loads(response.content.decode('utf-8'))['error'], expected_error_message)
@patch('contentstore.views.transcript_settings.get_available_transcript_languages', Mock(return_value=['en', 'es']))
def test_transcript_upload_handler_existing_transcript(self):
@@ -386,7 +389,7 @@ class TranscriptUploadTest(CourseTestCase):
response = self.client.post(self.view_url, request_payload, format='multipart')
self.assertEqual(response.status_code, 400)
self.assertEqual(
- json.loads(response.content)['error'],
+ json.loads(response.content.decode('utf-8'))['error'],
u'A transcript with the "es" language code already exists.'
)
@@ -410,7 +413,7 @@ class TranscriptUploadTest(CourseTestCase):
self.assertEqual(response.status_code, 400)
self.assertEqual(
- json.loads(response.content)['error'],
+ json.loads(response.content.decode('utf-8'))['error'],
u'There is a problem with this transcript file. Try to upload a different file.'
)
@@ -434,7 +437,7 @@ class TranscriptUploadTest(CourseTestCase):
self.assertEqual(response.status_code, 400)
self.assertEqual(
- json.loads(response.content)['error'],
+ json.loads(response.content.decode('utf-8'))['error'],
u'There is a problem with this transcript file. Try to upload a different file.'
)
diff --git a/cms/djangoapps/contentstore/views/tests/test_transcripts.py b/cms/djangoapps/contentstore/views/tests/test_transcripts.py
index 05e03aebd6..f2f5ce4862 100644
--- a/cms/djangoapps/contentstore/views/tests/test_transcripts.py
+++ b/cms/djangoapps/contentstore/views/tests/test_transcripts.py
@@ -1,5 +1,7 @@
"""Tests for items views."""
+from __future__ import absolute_import
+
import copy
import json
import tempfile
@@ -8,6 +10,7 @@ from codecs import BOM_UTF8
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
@@ -21,17 +24,18 @@ from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.django import contentstore
from xmodule.exceptions import NotFoundError
from xmodule.modulestore.django import modulestore
+from xmodule.video_module import VideoBlock
from xmodule.video_module.transcripts_utils import (
GetTranscriptsFromYouTubeException,
Transcript,
get_video_transcript_content,
- remove_subs_from_store,
+ remove_subs_from_store
)
TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex
-SRT_TRANSCRIPT_CONTENT = """0
+SRT_TRANSCRIPT_CONTENT = b"""0
00:00:10,500 --> 00:00:13,000
Elephant's Dream
@@ -83,7 +87,7 @@ class BaseTranscripts(CourseTestCase):
# Add video module
data = {
- 'parent_locator': unicode(self.course.location),
+ 'parent_locator': six.text_type(self.course.location),
'category': 'video',
'type': 'video'
}
@@ -94,7 +98,9 @@ class BaseTranscripts(CourseTestCase):
self.item = modulestore().get_item(self.video_usage_key)
# hI10vDNYz4M - valid Youtube ID with transcripts.
# JMD_ifUUfsU, AKqURZnYqpk, DYpADpL7jAY - valid Youtube IDs without transcripts.
- self.item.data = ''
+ self.set_fields_from_xml(
+ self.item, ''
+ )
modulestore().update_item(self.item, self.user.id)
self.item = modulestore().get_item(self.video_usage_key)
@@ -103,7 +109,7 @@ class BaseTranscripts(CourseTestCase):
def _get_usage_key(self, resp):
""" Returns the usage key from the response returned by a create operation. """
- usage_key_string = json.loads(resp.content).get('locator')
+ usage_key_string = json.loads(resp.content.decode('utf-8')).get('locator')
return UsageKey.from_string(usage_key_string)
def get_youtube_ids(self):
@@ -122,23 +128,28 @@ class BaseTranscripts(CourseTestCase):
Setup non video module for tests.
"""
data = {
- 'parent_locator': unicode(self.course.location),
+ 'parent_locator': six.text_type(self.course.location),
'category': 'non_video',
'type': 'non_video'
}
response = self.client.ajax_post('/xblock/', data)
usage_key = self._get_usage_key(response)
item = modulestore().get_item(usage_key)
- item.data = ''
+ self.set_fields_from_xml(self.item, '')
modulestore().update_item(item, self.user.id)
return usage_key
def assert_response(self, response, expected_status_code, expected_message):
- response_content = json.loads(response.content)
+ response_content = json.loads(response.content.decode('utf-8'))
self.assertEqual(response.status_code, expected_status_code)
self.assertEqual(response_content['status'], expected_message)
+ def set_fields_from_xml(self, item, xml):
+ fields_data = VideoBlock.parse_video_xml(xml)
+ for key, value in fields_data.items():
+ setattr(item, key, value)
+
@ddt.ddt
class TestUploadTranscripts(BaseTranscripts):
@@ -164,7 +175,7 @@ class TestUploadTranscripts(BaseTranscripts):
'client_video_id': u'Test Video',
'duration': 0,
'encoded_videos': [],
- 'courses': [unicode(self.course.id)]
+ 'courses': [six.text_type(self.course.id)]
})
# Add clean up handler
@@ -242,7 +253,7 @@ class TestUploadTranscripts(BaseTranscripts):
self.assert_response(response, expected_status_code=200, expected_message='Success')
# Verify the `edx_video_id` on the video component
- json_response = json.loads(response.content)
+ json_response = json.loads(response.content.decode('utf-8'))
expected_edx_video_id = edx_video_id if edx_video_id else json_response['edx_video_id']
video = modulestore().get_item(self.video_usage_key)
self.assertEqual(video.edx_video_id, expected_edx_video_id)
@@ -391,7 +402,7 @@ class TestChooseTranscripts(BaseTranscripts):
'client_video_id': u'Test Video',
'duration': 0,
'encoded_videos': [],
- 'courses': [unicode(self.course.id)]
+ 'courses': [six.text_type(self.course.id)]
})
def choose_transcript(self, locator, chosen_html5_id):
@@ -400,7 +411,7 @@ class TestChooseTranscripts(BaseTranscripts):
"""
payload = {}
if locator:
- payload.update({'locator': unicode(locator)})
+ payload.update({'locator': six.text_type(locator)})
if chosen_html5_id:
payload.update({'html5_id': chosen_html5_id})
@@ -430,7 +441,7 @@ class TestChooseTranscripts(BaseTranscripts):
self.assert_response(response, expected_status_code=200, expected_message='Success')
# Verify the `edx_video_id` on the video component
- json_response = json.loads(response.content)
+ json_response = json.loads(response.content.decode('utf-8'))
expected_edx_video_id = edx_video_id if edx_video_id else json_response['edx_video_id']
video = modulestore().get_item(self.video_usage_key)
self.assertEqual(video.edx_video_id, expected_edx_video_id)
@@ -511,7 +522,7 @@ class TestRenameTranscripts(BaseTranscripts):
'client_video_id': u'Test Video',
'duration': 0,
'encoded_videos': [],
- 'courses': [unicode(self.course.id)]
+ 'courses': [six.text_type(self.course.id)]
})
def rename_transcript(self, locator):
@@ -520,7 +531,7 @@ class TestRenameTranscripts(BaseTranscripts):
"""
payload = {}
if locator:
- payload.update({'locator': unicode(locator)})
+ payload.update({'locator': six.text_type(locator)})
rename_transcript_url = reverse('rename_transcripts')
response = self.client.get(rename_transcript_url, {'data': json.dumps(payload)})
@@ -547,7 +558,7 @@ class TestRenameTranscripts(BaseTranscripts):
self.assert_response(response, expected_status_code=200, expected_message='Success')
# Verify the `edx_video_id` on the video component
- json_response = json.loads(response.content)
+ json_response = json.loads(response.content.decode('utf-8'))
expected_edx_video_id = edx_video_id if edx_video_id else json_response['edx_video_id']
video = modulestore().get_item(self.video_usage_key)
self.assertEqual(video.edx_video_id, expected_edx_video_id)
@@ -629,7 +640,7 @@ class TestReplaceTranscripts(BaseTranscripts):
'client_video_id': u'Test Video',
'duration': 0,
'encoded_videos': [],
- 'courses': [unicode(self.course.id)]
+ 'courses': [six.text_type(self.course.id)]
})
def replace_transcript(self, locator, youtube_id):
@@ -638,7 +649,7 @@ class TestReplaceTranscripts(BaseTranscripts):
"""
payload = {}
if locator:
- payload.update({'locator': unicode(locator)})
+ payload.update({'locator': six.text_type(locator)})
if youtube_id:
payload.update({
@@ -675,7 +686,7 @@ class TestReplaceTranscripts(BaseTranscripts):
self.assert_response(response, expected_status_code=200, expected_message='Success')
# Verify the `edx_video_id` on the video component
- json_response = json.loads(response.content)
+ json_response = json.loads(response.content.decode('utf-8'))
expected_edx_video_id = edx_video_id if edx_video_id else json_response['edx_video_id']
video = modulestore().get_item(self.video_usage_key)
self.assertEqual(video.edx_video_id, expected_edx_video_id)
@@ -767,7 +778,7 @@ class TestDownloadTranscripts(BaseTranscripts):
"""
payload = {}
if locator:
- payload.update({'locator': unicode(locator)})
+ payload.update({'locator': six.text_type(locator)})
download_transcript_url = reverse('download_transcripts')
response = self.client.get(download_transcript_url, payload)
@@ -836,7 +847,7 @@ class TestCheckTranscripts(BaseTranscripts):
"""
def test_success_download_nonyoutube(self):
subs_id = str(uuid4())
- self.item.data = textwrap.dedent(u"""
+ self.set_fields_from_xml(self.item, u"""