Merge branch 'master' into jill/log-heartbeat-failures
This commit is contained in:
@@ -131,36 +131,6 @@ djcelery.WorkerState:
|
||||
edx_oauth2_provider.TrustedClient:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
# Via Proctoring
|
||||
edx_proctoring.ProctoredExam:
|
||||
".. no_pii:": "No PII"
|
||||
edx_proctoring.ProctoredExamReviewPolicy:
|
||||
".. no_pii:": "No PII"
|
||||
edx_proctoring.ProctoredExamReviewPolicyHistory:
|
||||
".. no_pii:": "No PII"
|
||||
edx_proctoring.ProctoredExamSoftwareSecureComment:
|
||||
".. no_pii:": "No PII"
|
||||
edx_proctoring.ProctoredExamSoftwareSecureReview:
|
||||
".. pii:": "Proctored exam review feedback from Software Secure, contains video_url. Retained for record keeping."
|
||||
".. pii_types:": video
|
||||
".. pii_retirement:": retained
|
||||
edx_proctoring.ProctoredExamSoftwareSecureReviewHistory:
|
||||
".. pii:": "Proctored exam review feedback from Software Secure, contains video_url. Retained for record keeping."
|
||||
".. pii_types:": video
|
||||
".. pii_retirement:": retained
|
||||
edx_proctoring.ProctoredExamStudentAllowance:
|
||||
".. no_pii:": "No PII"
|
||||
edx_proctoring.ProctoredExamStudentAllowanceHistory:
|
||||
".. no_pii:": "No PII"
|
||||
edx_proctoring.ProctoredExamStudentAttempt:
|
||||
".. pii:": "Tracks attempts by a user to take a proctored exam. Contains student_name. Retained for record keeping."
|
||||
".. pii_types:": name
|
||||
".. pii_retirement:": retained
|
||||
edx_proctoring.ProctoredExamStudentAttemptHistory:
|
||||
".. pii:": "Tracks attempts by a user to take a proctored exam. Contains student_name. Retained for record keeping."
|
||||
".. pii_types:": name
|
||||
".. pii_retirement:": retained
|
||||
|
||||
# Via VAL
|
||||
edxval.CourseVideo:
|
||||
".. no_pii:": "No PII"
|
||||
|
||||
@@ -30,6 +30,7 @@ omit =
|
||||
|
||||
concurrency=multiprocessing
|
||||
parallel = true
|
||||
relative_files = true
|
||||
|
||||
[report]
|
||||
ignore_errors = True
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
source_path: ./
|
||||
report_path: pii_report
|
||||
safelist_path: .annotation_safe_list.yml
|
||||
coverage_target: 100.0
|
||||
coverage_target: 94.5
|
||||
# See OEP-30 for more information on these values and what they mean:
|
||||
# https://open-edx-proposals.readthedocs.io/en/latest/oep-0030-arch-pii-markup-and-auditing.html#docstring-annotations
|
||||
annotations:
|
||||
|
||||
@@ -22,10 +22,9 @@ JIRA
|
||||
----
|
||||
|
||||
If you've got an idea for a new feature or new functionality for an existing feature,
|
||||
please start a discussion on the `edx-code`_ mailing list to get feedback from
|
||||
the community about the idea and your implementation choices.
|
||||
|
||||
.. _edx-code: https://groups.google.com/forum/#!forum/edx-code
|
||||
please start a discussion in the `development <https://discuss.openedx.org/c/development>`__
|
||||
category of the discussion forum list to get feedback from the community about the idea
|
||||
and your implementation choices.
|
||||
|
||||
If you then plan to contribute your code upstream, please `start a discussion on JIRA`_
|
||||
(you may first need to `create a free JIRA account`_).
|
||||
@@ -33,14 +32,31 @@ Start a discussion by visiting the JIRA website and clicking the "Create" button
|
||||
top of the page. Choose the project "Open Source Pull Requests" and the issue type
|
||||
"Feature Proposal". In the description give us as much detail as you can for the feature
|
||||
or functionality you are thinking about implementing. Include a link to any relevant
|
||||
edx-code mailing list discussions about your idea. We encourage you to do this before
|
||||
you begin implementing your feature, in order to get valuable feedback from the edX
|
||||
product team early on in your journey and increase the likelihood of a successful
|
||||
pull request.
|
||||
forum discussions about your idea. We encourage you to do this before you begin
|
||||
implementing your feature, in order to get valuable feedback from the edX product team
|
||||
early on in your journey and increase the likelihood of a successful pull request.
|
||||
|
||||
.. _start a discussion on JIRA: https://openedx.atlassian.net/secure/Dashboard.jspa
|
||||
.. _create a free JIRA account: https://openedx.atlassian.net/admin/users/sign-up
|
||||
|
||||
.. _forum:
|
||||
|
||||
Discussion forum
|
||||
----------------
|
||||
|
||||
To ask technical questions and chat with the community, do not hesitate to join the
|
||||
`Open edX discussion forum <https://discuss.openedx.org/>`__. There are different
|
||||
categories for different topics:
|
||||
|
||||
- `Devops <https://discuss.openedx.org/c/devops>`__ for installation help
|
||||
- `Development <https://discuss.openedx.org/c/development>`__, where Open edX developers
|
||||
unite
|
||||
- `Community <https://discuss.openedx.org/c/community>`__ to discuss organizational
|
||||
matters in the open source community
|
||||
- `Announcements <https://discuss.openedx.org/c/announcements>`__ where official Open edX
|
||||
announcement are made
|
||||
- `Educators <https://discuss.openedx.org/c/educators>`__, to discuss online learning in general
|
||||
|
||||
Slack
|
||||
-----
|
||||
|
||||
@@ -51,34 +67,17 @@ between 13:00 and 21:00 UTC (9am to 5pm US Eastern time),
|
||||
but interesting conversations can happen at any time.
|
||||
There are many different channels available for different topics, including:
|
||||
|
||||
* ``#ops`` for installation help
|
||||
* ``#events`` for upcoming events related to Open edX
|
||||
* ``#content`` for discussions about course content and creating the best courses
|
||||
|
||||
And lots more! You can also make your own channels to discuss new topics.
|
||||
|
||||
Note that Slack is no longer the recommended communication channel to ask about
|
||||
technical issues. To do so, you should instead join the `official forum <#forum>`__.
|
||||
|
||||
.. _Slack: https://slack.com/
|
||||
.. _Sign up for a free account: https://openedx-slack-invite.herokuapp.com/
|
||||
|
||||
Mailing Lists
|
||||
-------------
|
||||
|
||||
For asynchronous conversation, we have several mailing lists on Google Groups:
|
||||
|
||||
* `openedx-ops`_: everything related to *running* Open edX. This includes
|
||||
installation issues, server management, cost analysis, and so on.
|
||||
* `openedx-translation`_: everything related to *translating* Open edX into
|
||||
other languages. This includes volunteer translators, our internationalization
|
||||
infrastructure, issues related to Transifex, and so on.
|
||||
* `openedx-analytics`_: everything related to *analytics* in Open edX.
|
||||
* `edx-code`_: everything related to the *code* in Open edX. This includes
|
||||
feature requests, idea proposals, refactorings, and so on.
|
||||
|
||||
.. _openedx-ops: https://groups.google.com/forum/#!forum/openedx-ops
|
||||
.. _openedx-translation: https://groups.google.com/forum/#!forum/openedx-translation
|
||||
.. _openedx-analytics: https://groups.google.com/forum/#!forum/openedx-analytics
|
||||
.. _edx-code: https://groups.google.com/forum/#!forum/edx-code
|
||||
|
||||
Byte-sized Tasks & Bugs
|
||||
-----------------------
|
||||
|
||||
@@ -195,7 +194,7 @@ By opening up a pull request, we expect the following things:
|
||||
unable to participate in the review process.
|
||||
|
||||
3. If you have questions, you will ask them by either commenting on the pull
|
||||
request or asking us in Slack or on the mailing list.
|
||||
request or asking us in the discussion forum or on Slack.
|
||||
|
||||
4. If you do not respond to comments on your pull request within 7 days, we
|
||||
will close it. You are welcome to re-open it when you are ready to engage.
|
||||
|
||||
17
Makefile
17
Makefile
@@ -24,11 +24,13 @@ SWAGGER = docs/swagger.yaml
|
||||
docs: api-docs guides ## build all the developer documentation for this repository
|
||||
|
||||
swagger: ## generate the swagger.yaml file
|
||||
DJANGO_SETTINGS_MODULE=docs.docs_settings python manage.py lms generate_swagger --generator-class=openedx.core.apidocs.ApiSchemaGenerator -o $(SWAGGER)
|
||||
DJANGO_SETTINGS_MODULE=docs.docs_settings python manage.py lms generate_swagger --generator-class=edx_api_doc_tools.ApiSchemaGenerator -o $(SWAGGER)
|
||||
|
||||
api-docs: swagger ## build the REST api docs
|
||||
api-docs-sphinx: swagger ## generate the sphinx source files for api-docs
|
||||
rm -f docs/api/gen/*
|
||||
python docs/sw2md.py $(SWAGGER) docs/api/gen
|
||||
python docs/sw2sphinxopenapi.py $(SWAGGER) docs/api/gen
|
||||
|
||||
api-docs: api-docs-sphinx ## build the REST api docs
|
||||
cd docs/api; make html
|
||||
|
||||
guides: ## build the developer guide docs
|
||||
@@ -50,6 +52,7 @@ pull_translations: ## pull translations from Transifex
|
||||
git clean -fdX conf/locale/eo
|
||||
i18n_tool validate
|
||||
|
||||
|
||||
detect_changed_source_translations: ## check if translation files are up-to-date
|
||||
i18n_tool changed
|
||||
|
||||
@@ -72,7 +75,7 @@ REQ_FILES = \
|
||||
requirements/edx/coverage \
|
||||
requirements/edx/paver \
|
||||
requirements/edx-sandbox/shared \
|
||||
requirements/edx-sandbox/base \
|
||||
requirements/edx-sandbox/py35 \
|
||||
requirements/edx/base \
|
||||
requirements/edx/testing \
|
||||
requirements/edx/development \
|
||||
@@ -88,7 +91,7 @@ upgrade: ## update the pip requirements files to use the latest releases satisfy
|
||||
done
|
||||
# Post process all of the files generated above to work around open pip-tools issues
|
||||
scripts/post-pip-compile.sh $(REQ_FILES:=.txt)
|
||||
# Let tox control the Django version for tests
|
||||
grep "^django==" requirements/edx/base.txt > requirements/edx/django.txt
|
||||
sed '/^[dD]jango==/d' requirements/edx/testing.txt > requirements/edx/testing.tmp
|
||||
# Let tox control the Django version & django-oauth-toolkit version for tests
|
||||
grep -e "^django==" -e "^django-oauth-toolkit==" requirements/edx/base.txt > requirements/edx/django.txt
|
||||
sed '/^[dD]jango==/d;/^django-oauth-toolkit==/d' requirements/edx/testing.txt > requirements/edx/testing.tmp
|
||||
mv requirements/edx/testing.tmp requirements/edx/testing.txt
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Celery needs to be loaded when the cms modules are so that task
|
||||
registration and discovery can work correctly.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
# We monkey patch Kombu's entrypoints listing because scanning through this
|
||||
# accounts for the majority of LMS/Studio startup time for tests, and we don't
|
||||
|
||||
@@ -4,7 +4,7 @@ and auto discover tasks in all installed django apps.
|
||||
|
||||
Taken from: https://celery.readthedocs.org/en/latest/django/first-steps-with-django.html
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import os
|
||||
|
||||
|
||||
@@ -6,13 +6,15 @@ pytest from looking for the conftest.py module in the parent directory when
|
||||
only running cms tests.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import logging
|
||||
import contracts
|
||||
import pytest
|
||||
|
||||
from openedx.core.pytest_hooks import DeferPlugin
|
||||
|
||||
|
||||
# Patch the xml libs before anything else.
|
||||
from safe_lxml import defuse_xml_libs
|
||||
@@ -23,6 +25,11 @@ def pytest_configure(config):
|
||||
"""
|
||||
Do core setup operations from manage.py before collecting tests.
|
||||
"""
|
||||
if config.pluginmanager.hasplugin("pytest_jsonreport") or config.pluginmanager.hasplugin("json-report"):
|
||||
config.pluginmanager.register(DeferPlugin())
|
||||
else:
|
||||
logging.info("pytest did not register json_report correctly")
|
||||
|
||||
if config.getoption('help'):
|
||||
return
|
||||
enable_contracts = os.environ.get('ENABLE_CONTRACTS', False)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Configuration for Studio API Django application
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
URLs for the Studio API app
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.conf.urls import include, url
|
||||
|
||||
|
||||
app_name = 'cms.djangoapps.api'
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^v1/', include('cms.djangoapps.api.v1.urls', namespace='v1')),
|
||||
]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
""" Course run serializers. """
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import logging
|
||||
import time # pylint: disable=unused-import
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Tests for course run serializers"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import datetime
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Tests for Course run views"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import datetime
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
URLs for the Studio API [Course Run]
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from .views.course_runs import CourseRunViewSet
|
||||
|
||||
app_name = 'cms.djangoapps.api.v1'
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'course_runs', CourseRunViewSet, base_name='course_run')
|
||||
router.register(r'course_runs', CourseRunViewSet, basename='course_run')
|
||||
urlpatterns = router.urls
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""HTTP endpoints for the Course Run API."""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import Http404
|
||||
@@ -8,7 +7,7 @@ from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthenticat
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from rest_framework import parsers, permissions, status, viewsets
|
||||
from rest_framework.authentication import SessionAuthentication
|
||||
from rest_framework.decorators import detail_route
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
from contentstore.views.course import _accessible_courses_iter, get_course_and_check_access
|
||||
@@ -75,7 +74,8 @@ class CourseRunViewSet(viewsets.GenericViewSet):
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
@detail_route(
|
||||
@action(
|
||||
detail=True,
|
||||
methods=['post', 'put'],
|
||||
parser_classes=(parsers.FormParser, parsers.MultiPartParser,),
|
||||
serializer_class=CourseRunImageSerializer)
|
||||
@@ -86,7 +86,7 @@ class CourseRunViewSet(viewsets.GenericViewSet):
|
||||
serializer.save()
|
||||
return Response(serializer.data)
|
||||
|
||||
@detail_route(methods=['post'])
|
||||
@action(detail=True, methods=['post'])
|
||||
def rerun(self, request, *args, **kwargs):
|
||||
course_run = self.get_object()
|
||||
serializer = CourseRunRerunSerializer(course_run, data=request.data, context=self.get_serializer_context())
|
||||
|
||||
@@ -3,7 +3,6 @@ CMS user tasks application configuration
|
||||
Signal handlers are connected here.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Receivers of signals sent from django-user-tasks
|
||||
"""
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Celery tasks used by cms_user_tasks
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from boto.exception import NoAuthHandlerFound
|
||||
from celery.exceptions import MaxRetriesExceededError
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Unit tests for integration of the django-user-tasks app and its REST API.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import logging
|
||||
from uuid import uuid4
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Admin site bindings for contentstore
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from config_models.admin import ConfigurationModelAdmin
|
||||
from django.contrib import admin
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Base test case for the course API views.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Tests for the course import API views
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import os
|
||||
import tarfile
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Tests for the course import API views
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
from rest_framework import status
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Tests for the course import API views
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
""" Course API URLs. """
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
APIs related to Course Import.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import base64
|
||||
import logging
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# pylint: disable=missing-docstring
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import logging
|
||||
import time
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# pylint: disable=missing-docstring
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Common utilities for Contentstore APIs.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ Contentstore Application Configuration
|
||||
Above-modulestore level signal handlers are connected here.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
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
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Util methods for Waffle checks"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from cms.djangoapps.contentstore.config.waffle import ENABLE_CHECKLISTS_QUALITY
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Class for manipulating groups configuration on a course object.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
@@ -12,7 +12,6 @@ Current db representation:
|
||||
}
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
""" Code to allow module store to interface with courseware index """
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
""" Upload file handler to help test progress bars in uploads. """
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import time
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ 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
|
||||
|
||||
@@ -3,7 +3,6 @@ A single-use management command that provides an interactive way to remove
|
||||
erroneous certificate names.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
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
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Django management command to create a course in a specific modulestore
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Script for deleting orphans"""
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from opaque_keys import InvalidKeyError
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# Run it this way:
|
||||
# ./manage.py cms --settings dev edit_course_tabs --course Stanford/CS99/2013_spring
|
||||
#
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Script to Empty the trashcan"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Script for exporting courseware from Mongo to a tar.gz file
|
||||
"""
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
|
||||
import os
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Script for exporting all courseware from Mongo to a directory and listing the courses which failed to export
|
||||
"""
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from six import text_type
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Script for exporting a content library from Mongo to a tar.gz file
|
||||
"""
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
@@ -14,7 +14,6 @@ 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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Script for force publishing a course
|
||||
"""
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from opaque_keys import InvalidKeyError
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Django management command to generate a test course from a course config json
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
@@ -13,7 +13,6 @@ 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
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Script for importing courseware from XML format
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Script for importing a content library from a tar.gz file
|
||||
"""
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
|
||||
import base64
|
||||
import os
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
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
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Command to migrate transcripts to django storage.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Takes user input.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
""" Management command to update courses' search index """
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import logging
|
||||
from textwrap import dedent
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
""" Management command to update libraries' search index """
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
|
||||
from textwrap import dedent
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Management command to restore assets from trash"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Test for assets cleanup of courses for Mac OS metadata files (with filename ".DS_Store"
|
||||
or with filename which starts with "._")
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Unittests for creating a course in an chosen modulestore
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
from six import StringIO
|
||||
|
||||
@@ -27,7 +27,7 @@ class TestArgParsing(TestCase):
|
||||
errstring = "Error: too few arguments"
|
||||
else:
|
||||
errstring = "Error: the following arguments are required: modulestore, user, org, number, run"
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('create_course')
|
||||
|
||||
def test_invalid_store(self):
|
||||
@@ -36,12 +36,12 @@ class TestArgParsing(TestCase):
|
||||
|
||||
def test_nonexistent_user_id(self):
|
||||
errstring = "No user 99 found"
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('create_course', "split", "99", "org", "course", "run")
|
||||
|
||||
def test_nonexistent_user_email(self):
|
||||
errstring = "No user fake@example.com found"
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('create_course', "mongo", "fake@example.com", "org", "course", "run")
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Delete course tests.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import mock
|
||||
from django.core.management import CommandError, call_command
|
||||
@@ -25,13 +25,13 @@ class DeleteCourseTests(ModuleStoreTestCase):
|
||||
def test_invalid_course_key(self):
|
||||
course_run_key = 'foo/TestX/TS01/2015_Q7'
|
||||
expected_error_message = 'Invalid course_key: ' + course_run_key
|
||||
with self.assertRaisesRegexp(CommandError, expected_error_message):
|
||||
with self.assertRaisesRegex(CommandError, expected_error_message):
|
||||
call_command('delete_course', course_run_key)
|
||||
|
||||
def test_course_not_found(self):
|
||||
course_run_key = 'TestX/TS01/2015_Q7'
|
||||
expected_error_message = 'Course not found: ' + course_run_key
|
||||
with self.assertRaisesRegexp(CommandError, expected_error_message):
|
||||
with self.assertRaisesRegex(CommandError, expected_error_message):
|
||||
call_command('delete_course', course_run_key)
|
||||
|
||||
def test_asset_and_course_deletion(self):
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Tests running the delete_orphan command"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import ddt
|
||||
import six
|
||||
@@ -25,7 +24,7 @@ class TestDeleteOrphan(TestOrphanBase):
|
||||
errstring = 'Error: too few arguments'
|
||||
else:
|
||||
errstring = 'Error: the following arguments are required: course_id'
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('delete_orphans')
|
||||
|
||||
@ddt.data(ModuleStoreEnum.Type.split, ModuleStoreEnum.Type.mongo)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Tests for exporting courseware to the desired path
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import shutil
|
||||
import unittest
|
||||
@@ -29,7 +29,7 @@ class TestArgParsingCourseExport(unittest.TestCase):
|
||||
errstring = "Error: too few arguments"
|
||||
else:
|
||||
errstring = "Error: the following arguments are required: course_id, output_path"
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('export')
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class TestCourseExport(ModuleStoreTestCase):
|
||||
)
|
||||
# Test `export` management command with invalid course_id
|
||||
errstring = "Invalid course_key: 'InvalidCourseID'."
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('export', "InvalidCourseID", self.temp_dir_1)
|
||||
|
||||
# Test `export` management command with correct course_id
|
||||
@@ -74,5 +74,5 @@ class TestCourseExport(ModuleStoreTestCase):
|
||||
Test export command with a valid course key that doesn't exist
|
||||
"""
|
||||
errstring = "Course with x/y/z key not found."
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('export', "x/y/z", self.temp_dir_1)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Test for export all courses.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import shutil
|
||||
from tempfile import mkdtemp
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Tests for exporting OLX content.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import shutil
|
||||
import tarfile
|
||||
@@ -33,7 +32,7 @@ class TestArgParsingCourseExportOlx(unittest.TestCase):
|
||||
errstring = "Error: too few arguments"
|
||||
else:
|
||||
errstring = "Error: the following arguments are required: course_id"
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('export_olx')
|
||||
|
||||
|
||||
@@ -48,7 +47,7 @@ class TestCourseExportOlx(ModuleStoreTestCase):
|
||||
Test export command with an invalid course key.
|
||||
"""
|
||||
errstring = "Unparsable course_id"
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('export_olx', 'InvalidCourseID')
|
||||
|
||||
def test_course_key_not_found(self):
|
||||
@@ -56,7 +55,7 @@ class TestCourseExportOlx(ModuleStoreTestCase):
|
||||
Test export command with a valid course key that doesn't exist.
|
||||
"""
|
||||
errstring = "Invalid course_id"
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('export_olx', 'x/y/z')
|
||||
|
||||
def create_dummy_course(self, store_type):
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Tests for the fix_not_found management command
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import six
|
||||
from django.core.management import CommandError, call_command
|
||||
@@ -25,7 +24,7 @@ class TestFixNotFound(ModuleStoreTestCase):
|
||||
else:
|
||||
msg = "Error: the following arguments are required: course_id"
|
||||
|
||||
with self.assertRaisesRegexp(CommandError, msg):
|
||||
with self.assertRaisesRegex(CommandError, msg):
|
||||
call_command('fix_not_found')
|
||||
|
||||
def test_fix_not_found_non_split(self):
|
||||
@@ -33,7 +32,7 @@ class TestFixNotFound(ModuleStoreTestCase):
|
||||
The management command doesn't work on non split courses
|
||||
"""
|
||||
course = CourseFactory.create(default_store=ModuleStoreEnum.Type.mongo)
|
||||
with self.assertRaisesRegexp(CommandError, "The owning modulestore does not support this command."):
|
||||
with self.assertRaisesRegex(CommandError, "The owning modulestore does not support this command."):
|
||||
call_command("fix_not_found", six.text_type(course.id))
|
||||
|
||||
def test_fix_not_found(self):
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Tests for the force_publish management command
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import mock
|
||||
import six
|
||||
@@ -34,7 +34,7 @@ class TestForcePublish(SharedModuleStoreTestCase):
|
||||
else:
|
||||
errstring = "Error: the following arguments are required: course_key"
|
||||
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('force_publish')
|
||||
|
||||
def test_invalid_course_key(self):
|
||||
@@ -42,7 +42,7 @@ class TestForcePublish(SharedModuleStoreTestCase):
|
||||
Test 'force_publish' command with invalid course key
|
||||
"""
|
||||
errstring = "Invalid course key."
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('force_publish', 'TestX/TS01')
|
||||
|
||||
def test_too_many_arguments(self):
|
||||
@@ -50,7 +50,7 @@ class TestForcePublish(SharedModuleStoreTestCase):
|
||||
Test 'force_publish' command with more than 2 arguments
|
||||
"""
|
||||
errstring = "Error: unrecognized arguments: invalid-arg"
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('force_publish', six.text_type(self.course.id), '--commit', 'invalid-arg')
|
||||
|
||||
def test_course_key_not_found(self):
|
||||
@@ -58,7 +58,7 @@ class TestForcePublish(SharedModuleStoreTestCase):
|
||||
Test 'force_publish' command with non-existing course key
|
||||
"""
|
||||
errstring = "Course not found."
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('force_publish', six.text_type('course-v1:org+course+run'))
|
||||
|
||||
def test_force_publish_non_split(self):
|
||||
@@ -67,7 +67,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):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('force_publish', six.text_type(course.id))
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Unittest for generate a test course in an given modulestore
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import json
|
||||
|
||||
@@ -43,7 +43,7 @@ class TestGenerateCourses(ModuleStoreTestCase):
|
||||
"""
|
||||
Test that providing an invalid JSON object will result in the appropriate command error
|
||||
"""
|
||||
with self.assertRaisesRegexp(CommandError, "Invalid JSON object"):
|
||||
with self.assertRaisesRegex(CommandError, "Invalid JSON object"):
|
||||
arg = "invalid_json"
|
||||
call_command("generate_courses", arg)
|
||||
|
||||
@@ -51,7 +51,7 @@ class TestGenerateCourses(ModuleStoreTestCase):
|
||||
"""
|
||||
Test that a missing list of courses in json will result in the appropriate command error
|
||||
"""
|
||||
with self.assertRaisesRegexp(CommandError, "JSON object is missing courses list"):
|
||||
with self.assertRaisesRegex(CommandError, "JSON object is missing courses list"):
|
||||
settings = {}
|
||||
arg = json.dumps(settings)
|
||||
call_command("generate_courses", arg)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Unittests for exporting to git via management command.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import copy
|
||||
import os
|
||||
@@ -59,7 +58,7 @@ class TestGitExport(CourseTestCase):
|
||||
Test that the command interface works. Ignore stderr for clean
|
||||
test output.
|
||||
"""
|
||||
with self.assertRaisesRegexp(CommandError, 'Error: unrecognized arguments:*'):
|
||||
with self.assertRaisesRegex(CommandError, 'Error: unrecognized arguments:*'):
|
||||
call_command('git_export', 'blah', 'blah', 'blah', stderr=StringIO())
|
||||
|
||||
if six.PY2:
|
||||
@@ -73,23 +72,23 @@ class TestGitExport(CourseTestCase):
|
||||
call_command('git_export', stderr=StringIO())
|
||||
|
||||
# Send bad url to get course not exported
|
||||
with self.assertRaisesRegexp(CommandError, six.text_type(GitExportError.URL_BAD)):
|
||||
with self.assertRaisesRegex(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, six.text_type(GitExportError.BAD_COURSE)):
|
||||
with self.assertRaisesRegex(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, six.text_type(GitExportError.BAD_COURSE)):
|
||||
with self.assertRaisesRegex(CommandError, six.text_type(GitExportError.BAD_COURSE)):
|
||||
call_command(
|
||||
'git_export', 'foo/bar:baz', 'silly'
|
||||
)
|
||||
|
||||
with self.assertRaisesRegexp(CommandError, six.text_type(GitExportError.URL_BAD)):
|
||||
with self.assertRaisesRegex(CommandError, six.text_type(GitExportError.URL_BAD)):
|
||||
call_command(
|
||||
'git_export', 'foo/bar/baz', 'silly'
|
||||
)
|
||||
@@ -99,14 +98,13 @@ class TestGitExport(CourseTestCase):
|
||||
Test several bad URLs for validation
|
||||
"""
|
||||
course_key = CourseLocator('org', 'course', 'run')
|
||||
with self.assertRaisesRegexp(GitExportError, six.text_type(GitExportError.URL_BAD)):
|
||||
with self.assertRaisesRegex(GitExportError, six.text_type(GitExportError.URL_BAD)):
|
||||
git_export_utils.export_to_git(course_key, 'Sillyness')
|
||||
|
||||
with self.assertRaisesRegexp(GitExportError, six.text_type(GitExportError.URL_BAD)):
|
||||
with self.assertRaisesRegex(GitExportError, six.text_type(GitExportError.URL_BAD)):
|
||||
git_export_utils.export_to_git(course_key, 'example.com:edx/notreal')
|
||||
|
||||
with self.assertRaisesRegexp(GitExportError,
|
||||
six.text_type(GitExportError.URL_NO_AUTH)):
|
||||
with self.assertRaisesRegex(GitExportError, six.text_type(GitExportError.URL_NO_AUTH)):
|
||||
git_export_utils.export_to_git(course_key, 'http://blah')
|
||||
|
||||
def test_bad_git_repos(self):
|
||||
@@ -117,23 +115,20 @@ class TestGitExport(CourseTestCase):
|
||||
self.assertFalse(os.path.isdir(test_repo_path))
|
||||
course_key = CourseLocator('foo', 'blah', '100-')
|
||||
# Test bad clones
|
||||
with self.assertRaisesRegexp(GitExportError,
|
||||
six.text_type(GitExportError.CANNOT_PULL)):
|
||||
with self.assertRaisesRegex(GitExportError, six.text_type(GitExportError.CANNOT_PULL)):
|
||||
git_export_utils.export_to_git(
|
||||
course_key,
|
||||
'https://user:blah@example.com/test_repo.git')
|
||||
self.assertFalse(os.path.isdir(test_repo_path))
|
||||
|
||||
# Setup good repo with bad course to test xml export
|
||||
with self.assertRaisesRegexp(GitExportError,
|
||||
six.text_type(GitExportError.XML_EXPORT_FAIL)):
|
||||
with self.assertRaisesRegex(GitExportError, 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,
|
||||
six.text_type(GitExportError.CANNOT_PULL)):
|
||||
with self.assertRaisesRegex(GitExportError, six.text_type(GitExportError.CANNOT_PULL)):
|
||||
git_export_utils.export_to_git(
|
||||
course_key,
|
||||
'https://user:blah@example.com/r.git')
|
||||
@@ -189,7 +184,6 @@ class TestGitExport(CourseTestCase):
|
||||
'file://{0}'.format(self.bare_repo_dir)
|
||||
)
|
||||
|
||||
with self.assertRaisesRegexp(GitExportError,
|
||||
six.text_type(GitExportError.CANNOT_COMMIT)):
|
||||
with self.assertRaisesRegex(GitExportError, six.text_type(GitExportError.CANNOT_COMMIT)):
|
||||
git_export_utils.export_to_git(
|
||||
self.course.id, 'file://{0}'.format(self.bare_repo_dir))
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Unittests for importing a course via management command
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Unittests for migrating a course to split mongo
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import six
|
||||
|
||||
@@ -30,7 +30,7 @@ class TestArgParsing(TestCase):
|
||||
errstring = "Error: too few arguments"
|
||||
else:
|
||||
errstring = "Error: the following arguments are required: course_key, email"
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command("migrate_to_split")
|
||||
|
||||
def test_invalid_location(self):
|
||||
@@ -38,7 +38,7 @@ class TestArgParsing(TestCase):
|
||||
Test passing an unparsable course id
|
||||
"""
|
||||
errstring = "Invalid location string"
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command("migrate_to_split", "foo", "bar")
|
||||
|
||||
def test_nonexistent_user_id(self):
|
||||
@@ -46,7 +46,7 @@ class TestArgParsing(TestCase):
|
||||
Test error for using an unknown user primary key
|
||||
"""
|
||||
errstring = "No user found identified by 99"
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command("migrate_to_split", "org/course/name", "99")
|
||||
|
||||
def test_nonexistent_user_email(self):
|
||||
@@ -54,7 +54,7 @@ class TestArgParsing(TestCase):
|
||||
Test error for using an unknown user email
|
||||
"""
|
||||
errstring = "No user found identified by fake@example.com"
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command("migrate_to_split", "org/course/name", "fake@example.com")
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
Tests for course transcript migration management command.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import itertools
|
||||
import logging
|
||||
@@ -69,12 +69,12 @@ class TestArgParsing(TestCase):
|
||||
"""
|
||||
def test_no_args(self):
|
||||
errstring = "Must specify exactly one of --course_ids, --all_courses, --from_settings"
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('migrate_transcripts')
|
||||
|
||||
def test_invalid_course(self):
|
||||
errstring = "Invalid course_key: 'invalid-course'."
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('migrate_transcripts', '--course-id', 'invalid-course')
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
""" Tests for course reindex command """
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
@@ -50,25 +50,25 @@ class TestReindexCourse(ModuleStoreTestCase):
|
||||
|
||||
def test_given_no_arguments_raises_command_error(self):
|
||||
""" Test that raises CommandError for incorrect arguments """
|
||||
with self.assertRaisesRegexp(CommandError, ".* requires one or more *"):
|
||||
with self.assertRaisesRegex(CommandError, ".* requires one or more *"):
|
||||
call_command('reindex_course')
|
||||
|
||||
@ddt.data('qwerty', 'invalid_key', 'xblockv1:qwerty')
|
||||
def test_given_invalid_course_key_raises_not_found(self, invalid_key):
|
||||
""" Test that raises InvalidKeyError for invalid keys """
|
||||
err_string = u"Invalid course_key: '{0}'".format(invalid_key)
|
||||
with self.assertRaisesRegexp(CommandError, err_string):
|
||||
with self.assertRaisesRegex(CommandError, err_string):
|
||||
call_command('reindex_course', invalid_key)
|
||||
|
||||
def test_given_library_key_raises_command_error(self):
|
||||
""" Test that raises CommandError if library key is passed """
|
||||
with self.assertRaisesRegexp(CommandError, ".* is not a course key"):
|
||||
with self.assertRaisesRegex(CommandError, ".* is not a course key"):
|
||||
call_command('reindex_course', text_type(self._get_lib_key(self.first_lib)))
|
||||
|
||||
with self.assertRaisesRegexp(CommandError, ".* is not a course key"):
|
||||
with self.assertRaisesRegex(CommandError, ".* is not a course key"):
|
||||
call_command('reindex_course', text_type(self._get_lib_key(self.second_lib)))
|
||||
|
||||
with self.assertRaisesRegexp(CommandError, ".* is not a course key"):
|
||||
with self.assertRaisesRegex(CommandError, ".* is not a course key"):
|
||||
call_command(
|
||||
'reindex_course',
|
||||
text_type(self.second_course.id),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
""" Tests for library reindex command """
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
@@ -50,7 +50,7 @@ class TestReindexLibrary(ModuleStoreTestCase):
|
||||
|
||||
def test_given_no_arguments_raises_command_error(self):
|
||||
""" Test that raises CommandError for incorrect arguments """
|
||||
with self.assertRaisesRegexp(CommandError, ".* requires one or more *"):
|
||||
with self.assertRaisesRegex(CommandError, ".* requires one or more *"):
|
||||
call_command('reindex_library')
|
||||
|
||||
@ddt.data('qwerty', 'invalid_key', 'xblock-v1:qwe+rty')
|
||||
@@ -61,13 +61,13 @@ 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"):
|
||||
with self.assertRaisesRegex(CommandError, ".* is not a library key"):
|
||||
call_command('reindex_library', six.text_type(self.first_course.id))
|
||||
|
||||
with self.assertRaisesRegexp(CommandError, ".* is not a library key"):
|
||||
with self.assertRaisesRegex(CommandError, ".* is not a library key"):
|
||||
call_command('reindex_library', six.text_type(self.second_course.id))
|
||||
|
||||
with self.assertRaisesRegexp(CommandError, ".* is not a library key"):
|
||||
with self.assertRaisesRegex(CommandError, ".* is not a library key"):
|
||||
call_command(
|
||||
'reindex_library',
|
||||
six.text_type(self.second_course.id),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"""
|
||||
Tests for course video thumbnails management command.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
@@ -36,7 +36,7 @@ class TestArgParsing(TestCase):
|
||||
def test_invalid_course(self):
|
||||
errstring = "Invalid key specified: <class 'opaque_keys.edx.locator.CourseLocator'>: invalid-course"
|
||||
setup_video_thumbnails_config(course_ids='invalid-course')
|
||||
with self.assertRaisesRegexp(CommandError, errstring):
|
||||
with self.assertRaisesRegex(CommandError, errstring):
|
||||
call_command('video_thumbnails')
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Common methods for cms commands to use
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Command to scrape thumbnails and add them to the course-videos.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Verify the structure of courseware as to it's suitability for import
|
||||
"""
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
|
||||
from argparse import REMAINDER
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
@@ -32,7 +32,7 @@ class Migration(migrations.Migration):
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
|
||||
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
|
||||
('profile_whitelist', models.TextField(help_text=b'A comma-separated list of names of profiles to include in video encoding downloads.', blank=True)),
|
||||
('profile_whitelist', models.TextField(help_text=u'A comma-separated list of names of profiles to include in video encoding downloads.', blank=True)),
|
||||
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')),
|
||||
],
|
||||
options={
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# -*- 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
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Models for contentstore
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from config_models.models import ConfigurationModel
|
||||
from django.db.models.fields import TextField
|
||||
@@ -16,7 +15,7 @@ class VideoUploadConfig(ConfigurationModel):
|
||||
"""
|
||||
profile_whitelist = TextField(
|
||||
blank=True,
|
||||
help_text="A comma-separated list of names of profiles to include in video encoding downloads."
|
||||
help_text=u"A comma-separated list of names of profiles to include in video encoding downloads."
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Code related to the handling of Proctored Exams in Studio
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Authorization rules related to content management.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import user_tasks.rules
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
""" 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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Contentstore signals
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
from django.dispatch import Signal
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Storage backend for course import and export.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import get_storage_class
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
This file contains celery tasks for contentstore views
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import base64
|
||||
import json
|
||||
@@ -32,7 +32,7 @@ from opaque_keys.edx.locator import LibraryLocator, BlockUsageLocator
|
||||
from organizations.models import OrganizationCourse
|
||||
from path import Path as path
|
||||
from pytz import UTC
|
||||
from six import iteritems, text_type
|
||||
from six import iteritems, text_type, binary_type
|
||||
from six.moves import range
|
||||
from user_tasks.models import UserTaskArtifact, UserTaskStatus
|
||||
from user_tasks.tasks import UserTask
|
||||
@@ -528,7 +528,7 @@ def _parse_time(time_isoformat):
|
||||
).replace(tzinfo=UTC)
|
||||
|
||||
|
||||
@task()
|
||||
@task(routing_key=settings.UPDATE_SEARCH_INDEX_JOB_QUEUE)
|
||||
def update_search_index(course_id, triggered_time_isoformat):
|
||||
""" Updates course search index. """
|
||||
try:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Unit tests for cloning a course between the same and different module stores.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import json
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import copy
|
||||
import shutil
|
||||
@@ -1352,7 +1351,7 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
resp = self.client.ajax_post('/course/', self.course_data)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = parse_json(resp)
|
||||
self.assertRegexpMatches(data['ErrMsg'], error_message)
|
||||
self.assertRegex(data['ErrMsg'], error_message)
|
||||
if test_enrollment:
|
||||
# One test case involves trying to create the same course twice. Hence for that course,
|
||||
# the user will be enrolled. In the other cases, initially_enrolled will be False.
|
||||
@@ -1518,7 +1517,7 @@ class ContentStoreTest(ContentStoreTestCase):
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = parse_json(resp)
|
||||
retarget = text_type(course.id.make_usage_key('chapter', 'REPLACE')).replace('REPLACE', r'([0-9]|[a-f]){3,}')
|
||||
self.assertRegexpMatches(data['locator'], retarget)
|
||||
self.assertRegex(data['locator'], retarget)
|
||||
|
||||
def test_capa_module(self):
|
||||
"""Test that a problem treats markdown specially."""
|
||||
@@ -2186,10 +2185,12 @@ class EntryPageTestCase(TestCase):
|
||||
self._test_page("/howitworks")
|
||||
|
||||
def test_signup(self):
|
||||
self._test_page("/signup")
|
||||
# deprecated signup url redirects to LMS register.
|
||||
self._test_page("/signup", 301)
|
||||
|
||||
def test_login(self):
|
||||
self._test_page("/signin")
|
||||
# deprecated signin url redirects to LMS login.
|
||||
self._test_page("/signin", 302)
|
||||
|
||||
def test_logout(self):
|
||||
# Logout redirects.
|
||||
@@ -2202,36 +2203,6 @@ class EntryPageTestCase(TestCase):
|
||||
self._test_page('/accessibility')
|
||||
|
||||
|
||||
class SigninPageTestCase(TestCase):
|
||||
"""
|
||||
Tests that the CSRF token is directly included in the signin form. This is
|
||||
important to make sure that the script is functional independently of any
|
||||
other script.
|
||||
"""
|
||||
|
||||
def test_csrf_token_is_present_in_form(self):
|
||||
# Expected html:
|
||||
# <form>
|
||||
# ...
|
||||
# <fieldset>
|
||||
# ...
|
||||
# <input name="csrfmiddlewaretoken" value="...">
|
||||
# ...
|
||||
# </fieldset>
|
||||
# ...
|
||||
# </form>
|
||||
response = self.client.get("/signin")
|
||||
csrf_token = response.cookies.get("csrftoken")
|
||||
form = lxml.html.fromstring(response.content).get_element_by_id("login_form")
|
||||
csrf_input_field = form.find(".//input[@name='csrfmiddlewaretoken']")
|
||||
|
||||
self.assertIsNotNone(csrf_token)
|
||||
self.assertIsNotNone(csrf_token.value)
|
||||
self.assertIsNotNone(csrf_input_field)
|
||||
|
||||
self.assertTrue(_compare_salted_tokens(csrf_token.value, csrf_input_field.attrib["value"]))
|
||||
|
||||
|
||||
def _create_course(test, course_key, course_data):
|
||||
"""
|
||||
Creates a course via an AJAX request and verifies the URL returned in the response.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Tests core caching facilities.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.test import TestCase
|
||||
from opaque_keys.edx.locator import AssetLocator, CourseLocator
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Test view handler for rerun (and eventually create)
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import datetime
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
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
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Tests for Studio Course Settings.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
@@ -87,10 +87,37 @@ class CourseSettingsEncoderTest(CourseTestCase):
|
||||
jsondetails = json.dumps(details, cls=CourseSettingsEncoder)
|
||||
jsondetails = json.loads(jsondetails)
|
||||
|
||||
self.assertEquals(1, jsondetails['number'])
|
||||
self.assertEqual(1, jsondetails['number'])
|
||||
self.assertEqual(jsondetails['string'], 'string')
|
||||
|
||||
|
||||
class CourseAdvanceSettingViewTest(CourseTestCase, MilestonesTestCaseMixin):
|
||||
"""
|
||||
Tests for AdvanceSettings View.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(CourseAdvanceSettingViewTest, self).setUp()
|
||||
self.fullcourse = CourseFactory.create()
|
||||
self.course_setting_url = get_url(self.course.id, 'advanced_settings_handler')
|
||||
|
||||
@override_settings(FEATURES={'DISABLE_MOBILE_COURSE_AVAILABLE': True})
|
||||
def test_mobile_field_available(self):
|
||||
|
||||
"""
|
||||
Test to check `Mobile Course Available` field is not viewable in Studio
|
||||
when DISABLE_MOBILE_COURSE_AVAILABLE is true.
|
||||
"""
|
||||
|
||||
response = self.client.get_html(self.course_setting_url)
|
||||
start = response.content.decode('utf-8').find("mobile_available")
|
||||
end = response.content.decode('utf-8').find("}", start)
|
||||
settings_fields = json.loads(response.content.decode('utf-8')[start + len("mobile_available: "):end + 1])
|
||||
|
||||
self.assertEqual(settings_fields["display_name"], "Mobile Course Available")
|
||||
self.assertEqual(settings_fields["deprecated"], True)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin):
|
||||
"""
|
||||
@@ -335,10 +362,10 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin):
|
||||
}
|
||||
response = self.client.post(settings_details_url, data=json.dumps(data), content_type='application/json',
|
||||
HTTP_ACCEPT='application/json')
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
course = modulestore().get_course(self.course.id)
|
||||
self.assertTrue(course.entrance_exam_enabled)
|
||||
self.assertEquals(course.entrance_exam_minimum_score_pct, .60)
|
||||
self.assertEqual(course.entrance_exam_minimum_score_pct, .60)
|
||||
|
||||
# Update the entrance exam
|
||||
data['entrance_exam_enabled'] = "true"
|
||||
@@ -349,10 +376,10 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin):
|
||||
content_type='application/json',
|
||||
HTTP_ACCEPT='application/json'
|
||||
)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
course = modulestore().get_course(self.course.id)
|
||||
self.assertTrue(course.entrance_exam_enabled)
|
||||
self.assertEquals(course.entrance_exam_minimum_score_pct, .80)
|
||||
self.assertEqual(course.entrance_exam_minimum_score_pct, .80)
|
||||
|
||||
self.assertTrue(milestones_helpers.any_unfulfilled_milestones(self.course.id, self.user.id),
|
||||
msg='The entrance exam should be required.')
|
||||
@@ -366,9 +393,9 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin):
|
||||
HTTP_ACCEPT='application/json'
|
||||
)
|
||||
course = modulestore().get_course(self.course.id)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertFalse(course.entrance_exam_enabled)
|
||||
self.assertEquals(course.entrance_exam_minimum_score_pct, None)
|
||||
self.assertEqual(course.entrance_exam_minimum_score_pct, None)
|
||||
|
||||
self.assertFalse(milestones_helpers.any_unfulfilled_milestones(self.course.id, self.user.id),
|
||||
msg='The entrance exam should not be required anymore')
|
||||
@@ -396,12 +423,12 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin):
|
||||
content_type='application/json',
|
||||
HTTP_ACCEPT='application/json'
|
||||
)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
course = modulestore().get_course(self.course.id)
|
||||
self.assertTrue(course.entrance_exam_enabled)
|
||||
|
||||
# entrance_exam_minimum_score_pct is not present in the request so default value should be saved.
|
||||
self.assertEquals(course.entrance_exam_minimum_score_pct, .5)
|
||||
self.assertEqual(course.entrance_exam_minimum_score_pct, .5)
|
||||
|
||||
#add entrance_exam_minimum_score_pct with empty value in json request.
|
||||
test_data_2 = {
|
||||
@@ -422,10 +449,10 @@ class CourseDetailsViewTest(CourseTestCase, MilestonesTestCaseMixin):
|
||||
content_type='application/json',
|
||||
HTTP_ACCEPT='application/json'
|
||||
)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
course = modulestore().get_course(self.course.id)
|
||||
self.assertTrue(course.entrance_exam_enabled)
|
||||
self.assertEquals(course.entrance_exam_minimum_score_pct, .5)
|
||||
self.assertEqual(course.entrance_exam_minimum_score_pct, .5)
|
||||
|
||||
def test_editable_short_description_fetch(self):
|
||||
settings_details_url = get_url(self.course.id)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Testing indexing of the courseware as it is changed
|
||||
"""
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
|
||||
import json
|
||||
import time
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""Tests for CRUD Operations"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from xmodule import templates
|
||||
from xmodule.capa_module import ProblemBlock
|
||||
@@ -39,8 +38,8 @@ class TemplateTests(ModuleStoreTestCase):
|
||||
self.assertIsNotNone(dropdown)
|
||||
self.assertIn('markdown', dropdown['metadata'])
|
||||
self.assertIn('data', dropdown)
|
||||
self.assertRegexpMatches(dropdown['metadata']['markdown'], r'.*dropdown problems.*')
|
||||
self.assertRegexpMatches(dropdown['data'], r'<problem>\s*<optionresponse>\s*<p>.*dropdown problems.*')
|
||||
self.assertRegex(dropdown['metadata']['markdown'], r'.*dropdown problems.*')
|
||||
self.assertRegex(dropdown['data'], r'<problem>\s*<optionresponse>\s*<p>.*dropdown problems.*')
|
||||
|
||||
def test_get_some_templates(self):
|
||||
self.assertEqual(len(SequenceDescriptor.templates()), 0)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Test the ability to export courses to xml from studio
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import copy
|
||||
import os
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user