@@ -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
|
||||
|
||||
2
.github/renovate.json
vendored
2
.github/renovate.json
vendored
@@ -4,7 +4,7 @@
|
||||
"schedule:weekdays",
|
||||
":preserveSemverRanges"
|
||||
],
|
||||
"prConcurrentLimit": 2,
|
||||
"prConcurrentLimit": 5,
|
||||
"includePaths": [
|
||||
"package.json"
|
||||
]
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -143,10 +143,3 @@ dist
|
||||
|
||||
# Locally generated PII reports
|
||||
pii_report
|
||||
|
||||
# Local documentation builds
|
||||
docs/_build
|
||||
docs/cms
|
||||
docs/common
|
||||
docs/lms
|
||||
docs/openedx
|
||||
|
||||
3
Makefile
3
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.'
|
||||
]}
|
||||
|
||||
@@ -3,6 +3,8 @@ CMS user tasks application configuration
|
||||
Signal handlers are connected here.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Util methods for Waffle checks"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from cms.djangoapps.contentstore.config.waffle import ENABLE_CHECKLISTS_QUALITY
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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__",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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):
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""
|
||||
Unittest for generate a test course in an given modulestore
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
|
||||
import ddt
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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">
|
||||
<source src="http://www.example.com/source.mp4"/>
|
||||
<track src="http://www.example.com/track"/>
|
||||
<handout src="http://www.example.com/handout"/>
|
||||
@@ -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">
|
||||
<source src="http://www.example.com/source.mp4"/>
|
||||
<track src="http://www.example.com/track"/>
|
||||
<handout src="http://www.example.com/handout"/>
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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 """
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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)
|
||||
@@ -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"
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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__)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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',
|
||||
),
|
||||
]
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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("<script>alert(1)</script>", "alert('hi')", "</script><script>alert(1)</script>")
|
||||
def test_container_handler_xss_prevent(self, malicious_code):
|
||||
@@ -1815,15 +1818,15 @@ class MetadataSaveTestCase(ContentStoreTestCase):
|
||||
<video display_name="Test Video"
|
||||
youtube="1.0:p2Q6BrNhdh8,0.75:izygArpw-Qo,1.25:1EeWXzPdhSA,1.5:rABDYkeK0x8"
|
||||
show_captions="false"
|
||||
from="00:00:01"
|
||||
to="00:01:00">
|
||||
from="1.0"
|
||||
to="60.0">
|
||||
<source src="http://www.example.com/file.mp4"/>
|
||||
<track src="http://www.example.com/track"/>
|
||||
</video>
|
||||
"""
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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],
|
||||
'<h3 id="heading-entrance-exam">' in resp.content
|
||||
b'<h3 id="heading-entrance-exam">' 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):
|
||||
"""
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
"""
|
||||
Tests for validate Internationalization and Module i18n service.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import gettext
|
||||
from unittest import skip
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
"""
|
||||
Tests Draft import order.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user