Merge pull request #3 from edx/master

Update the repo
This commit is contained in:
AmiT
2019-09-02 19:28:07 +05:30
committed by GitHub
1817 changed files with 72380 additions and 39361 deletions

View File

@@ -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

View File

@@ -4,7 +4,7 @@
"schedule:weekdays",
":preserveSemverRanges"
],
"prConcurrentLimit": 2,
"prConcurrentLimit": 5,
"includePaths": [
"package.json"
]

7
.gitignore vendored
View File

@@ -143,10 +143,3 @@ dist
# Locally generated PII reports
pii_report
# Local documentation builds
docs/_build
docs/cms
docs/common
docs/lms
docs/openedx

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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.'
]}

View File

@@ -3,6 +3,8 @@ CMS user tasks application configuration
Signal handlers are connected here.
"""
from __future__ import absolute_import
from django.apps import AppConfig

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = [

View File

@@ -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__)

View File

@@ -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),

View File

@@ -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
})

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,5 +1,7 @@
"""Util methods for Waffle checks"""
from __future__ import absolute_import
from cms.djangoapps.contentstore.config.waffle import ENABLE_CHECKLISTS_QUALITY

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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

View File

@@ -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 "

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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):

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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__",

View File

@@ -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

View File

@@ -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):

View File

@@ -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))

View File

@@ -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

View File

@@ -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:

View File

@@ -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:

View File

@@ -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)

View File

@@ -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))

View File

@@ -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):

View File

@@ -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,
)

View File

@@ -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):

View File

@@ -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())

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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))

View File

@@ -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:

View 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

View File

@@ -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']

View File

@@ -1,6 +1,8 @@
"""
Unittest for generate a test course in an given modulestore
"""
from __future__ import absolute_import
import json
import ddt

View File

@@ -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))

View File

@@ -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))

View File

@@ -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")

View File

@@ -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
)

View File

@@ -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 """

View File

@@ -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)))

View File

@@ -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)

View File

@@ -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"

View File

@@ -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

View File

@@ -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__)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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',
),
]

View File

@@ -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:
"""

View File

@@ -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

View File

@@ -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))

View File

@@ -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(

View File

@@ -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.

View File

@@ -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.

View File

@@ -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")

View File

@@ -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.

View File

@@ -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

View File

@@ -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):

View File

@@ -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):

View File

@@ -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):
"""

View File

@@ -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()

View File

@@ -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(

View File

@@ -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

View File

@@ -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

View File

@@ -2,6 +2,8 @@
"""
Tests for validate Internationalization and Module i18n service.
"""
from __future__ import absolute_import
import gettext
from unittest import skip

View File

@@ -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)

View File

@@ -1,6 +1,8 @@
"""
Tests Draft import order.
"""
from __future__ import absolute_import
from django.conf import settings
from xmodule.modulestore.django import modulestore

View File

@@ -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):

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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