Merge commit 'bbadf48b8e19a0c9f1e1ca750f5ad8b26856bcc1' into feanil/merge_more_of_master

Sorry for the merge commits but release was in a really bad state and so
we need to get changes out piecemeal on the release-candidate.

Slowly mergig master into RC.
This commit is contained in:
Feanil Patel
2019-10-10 10:36:56 -04:00
43 changed files with 307 additions and 178 deletions

View File

@@ -67,7 +67,7 @@ class Command(BaseCommand):
def _get_results(self, filename):
"""Load results from file"""
with open(filename) as f:
with open(filename, 'rb') as f: # pylint: disable=open-builtin
results = f.read()
os.remove(filename)
return results

View File

@@ -89,6 +89,13 @@ class TestCourseExportOlx(ModuleStoreTestCase):
with tarfile.open(filename) as tar_file:
self.check_export_file(tar_file, test_course_key)
# There is a bug in the underlying management/base code that tries to make
# all manageent command output be unicode. This management command
# outputs the binary tar file data and so breaks in python3. In python2
# the code is happy to pass bytes back and forth and in later versions of
# django this is fixed. Howevere it's not possible to get this test to
# pass in Python3 and django 1.11
@unittest.skip("Bug in django 1.11 prevents this from working in python3. Re-enable after django 2.x upgrade.")
@ddt.data(ModuleStoreEnum.Type.mongo, ModuleStoreEnum.Type.split)
def test_export_course_stdout(self, store_type):
test_course_key = self.create_dummy_course(store_type)

View File

@@ -749,7 +749,7 @@ def import_olx(self, user_id, course_key_string, archive_path, archive_name, lan
# Locate the uploaded OLX archive (and download it from S3 if necessary)
# Do everything in a try-except block to make sure everything is properly cleaned up.
data_root = path(settings.GITHUB_REPO_ROOT)
subdir = base64.urlsafe_b64encode(repr(courselike_key))
subdir = base64.urlsafe_b64encode(repr(courselike_key).encode('utf-8')).decode('utf-8')
course_dir = data_root / subdir
try:
self.status.set_state(u'Unpacking')

View File

@@ -32,6 +32,8 @@ class FakeTranslations(ModuleI18nService):
"""
return self.translations.get(msgid, msgid)
gettext = ugettext
@staticmethod
def translator(locales_map): # pylint: disable=method-hidden
"""Build mock translator for the given locales.
@@ -123,6 +125,7 @@ class TestModuleI18nService(ModuleStoreTestCase):
self.assertEqual(i18n_service.ugettext(self.test_language), 'dummy language')
@mock.patch('django.utils.translation.ugettext', mock.Mock(return_value='XYZ-TEST-LANGUAGE'))
@mock.patch('django.utils.translation.gettext', mock.Mock(return_value='XYZ-TEST-LANGUAGE'))
def test_django_translator_in_use_with_empty_block(self):
"""
Test: Django default translator should in use if we have an empty block

View File

@@ -319,7 +319,7 @@ class ScrapeVideoThumbnailsTestCase(CourseTestCase):
)
),
(
'dummy-content',
b'dummy-content',
None,
u'This image file type is not supported. Supported file types are {supported_file_formats}.'.format(
supported_file_formats=list(settings.VIDEO_IMAGE_SUPPORTED_FILE_FORMATS.keys())

View File

@@ -335,8 +335,10 @@ class AuthTestCase(ContentStoreTestCase):
is turned off
"""
response = self.client.get(reverse('login'))
self.assertNotIn('<a href="/signup" class="action action-signin">Don&#39;t have a Studio Account? Sign up!</a>',
response.content)
self.assertNotContains(
response,
'<a href="/signup" class="action action-signin">Don&#39;t have a Studio Account? Sign up!</a>'
)
class ForumTestCase(CourseTestCase):

View File

@@ -117,7 +117,7 @@ def _write_chunk(request, courselike_key):
"""
# Upload .tar.gz to local filesystem for one-server installations not using S3 or Swift
data_root = path(settings.GITHUB_REPO_ROOT)
subdir = base64.urlsafe_b64encode(repr(courselike_key))
subdir = base64.urlsafe_b64encode(repr(courselike_key).encode('utf-8')).decode('utf-8')
course_dir = data_root / subdir
filename = request.FILES['course-data'].name

View File

@@ -295,8 +295,8 @@ class CertificatesListHandlerTestCase(
# in html response
result = self.client.get_html(self._url())
self.assertIn('Test certificate', result.content)
self.assertIn('Test description', result.content)
self.assertContains(result, 'Test certificate')
self.assertContains(result, 'Test description')
# in JSON response
response = self.client.get_json(self._url())
@@ -320,7 +320,7 @@ class CertificatesListHandlerTestCase(
# in html response
result = self.client.get_html(self._url())
self.assertNotIn('Test certificate', result.content)
self.assertNotContains(result, 'Test certificate')
def test_unsupported_http_accept_header(self):
"""

View File

@@ -62,7 +62,7 @@ class CourseUpdateTest(CourseTestCase):
# refetch using provided id
refetched = self.client.get_json(first_update_url)
self.assertHTMLEqual(
content, json.loads(refetched.content)['content'], "get w/ provided id"
content, json.loads(refetched.content.decode('utf-8'))['content'], "get w/ provided id"
)
# now put in an evil update

View File

@@ -137,7 +137,7 @@ class TestSubsectionGating(CourseTestCase):
{'namespace': '{}{}'.format(six.text_type(self.seq1.location), GATING_NAMESPACE_QUALIFIER)},
{'namespace': '{}{}'.format(six.text_type(self.seq2.location), GATING_NAMESPACE_QUALIFIER)}
]
resp = json.loads(self.client.get_json(self.seq2_url).content)
resp = json.loads(self.client.get_json(self.seq2_url).content.decode('utf-8'))
mock_is_prereq.assert_called_with(self.course.id, self.seq2.location)
mock_get_required_content.assert_called_with(self.course.id, self.seq2.location)
mock_get_prereqs.assert_called_with(self.course.id)

View File

@@ -94,7 +94,7 @@ class ImportEntranceExamTestCase(CourseTestCase, MilestonesTestCaseMixin):
self.assertIsNotNone(course)
self.assertEquals(course.entrance_exam_enabled, False)
with open(self.entrance_exam_tar) as gtar:
with open(self.entrance_exam_tar, 'rb') as gtar: # pylint: disable=open-builtin
args = {"name": self.entrance_exam_tar, "course-data": [gtar]}
resp = self.client.post(self.url, args)
self.assertEquals(resp.status_code, 200)
@@ -126,7 +126,7 @@ class ImportEntranceExamTestCase(CourseTestCase, MilestonesTestCaseMixin):
self.assertTrue(len(content_milestones))
# Now import entrance exam course
with open(self.entrance_exam_tar) as gtar:
with open(self.entrance_exam_tar, 'rb') as gtar: # pylint: disable=open-builtin
args = {"name": self.entrance_exam_tar, "course-data": [gtar]}
resp = self.client.post(self.url, args)
self.assertEquals(resp.status_code, 200)
@@ -186,7 +186,7 @@ class ImportTestCase(CourseTestCase):
Check that the response for a tar.gz import without a course.xml is
correct.
"""
with open(self.bad_tar) as btar:
with open(self.bad_tar, 'rb') as btar: # pylint: disable=open-builtin
resp = self.client.post(
self.url,
{
@@ -204,14 +204,14 @@ class ImportTestCase(CourseTestCase):
)
)
self.assertEquals(json.loads(resp_status.content)["ImportStatus"], -2)
self.assertEquals(json.loads(resp_status.content.decode('utf-8'))["ImportStatus"], -2)
def test_with_coursexml(self):
"""
Check that the response for a tar.gz import with a course.xml is
correct.
"""
with open(self.good_tar) as gtar:
with open(self.good_tar, 'rb') as gtar: # pylint: disable=open-builtin
args = {"name": self.good_tar, "course-data": [gtar]}
resp = self.client.post(self.url, args)
@@ -230,7 +230,7 @@ class ImportTestCase(CourseTestCase):
display_name_before_import = course.display_name
# Check that global staff user can import course
with open(self.good_tar) as gtar:
with open(self.good_tar, 'rb') as gtar: # pylint: disable=open-builtin
args = {"name": self.good_tar, "course-data": [gtar]}
resp = self.client.post(self.url, args)
self.assertEquals(resp.status_code, 200)
@@ -248,7 +248,7 @@ class ImportTestCase(CourseTestCase):
# Now course staff user can also successfully import course
self.client.login(username=nonstaff_user.username, password='foo')
with open(self.good_tar) as gtar:
with open(self.good_tar, 'rb') as gtar: # pylint: disable=open-builtin
args = {"name": self.good_tar, "course-data": [gtar]}
resp = self.client.post(self.url, args)
self.assertEquals(resp.status_code, 200)
@@ -342,7 +342,7 @@ class ImportTestCase(CourseTestCase):
def try_tar(tarpath):
""" Attempt to tar an unacceptable file """
with open(tarpath) as tar:
with open(tarpath, 'rb') as tar: # pylint: disable=open-builtin
args = {"name": tarpath, "course-data": [tar]}
resp = self.client.post(self.url, args)
self.assertEquals(resp.status_code, 200)
@@ -376,7 +376,7 @@ class ImportTestCase(CourseTestCase):
kwargs={'filename': os.path.split(self.good_tar)[1]}
)
)
import_status = json.loads(resp_status.content)["ImportStatus"]
import_status = json.loads(resp_status.content.decode('utf-8'))["ImportStatus"]
self.assertIn(import_status, (0, 3))
def test_library_import(self):

View File

@@ -307,7 +307,7 @@ class TranscriptUploadTest(CourseTestCase):
"""
Tests that transcript upload handler works as expected.
"""
transcript_file_stream = BytesIO('0\n00:00:00,010 --> 00:00:00,100\nПривіт, edX вітає вас.\n\n')
transcript_file_stream = six.StringIO('0\n00:00:00,010 --> 00:00:00,100\nПривіт, edX вітає вас.\n\n')
# Make request to transcript upload handler
response = self.client.post(
self.view_url,
@@ -422,7 +422,7 @@ class TranscriptUploadTest(CourseTestCase):
"""
Tests the transcript upload handler with an invalid transcript file.
"""
transcript_file_stream = BytesIO('An invalid transcript SubRip file content')
transcript_file_stream = six.StringIO('An invalid transcript SubRip file content')
# Make request to transcript upload handler
response = self.client.post(
self.view_url,

View File

@@ -35,7 +35,7 @@ from xmodule.video_module.transcripts_utils import (
TEST_DATA_CONTENTSTORE = copy.deepcopy(settings.CONTENTSTORE)
TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'] = 'test_xcontent_%s' % uuid4().hex
SRT_TRANSCRIPT_CONTENT = b"""0
SRT_TRANSCRIPT_CONTENT = u"""0
00:00:10,500 --> 00:00:13,000
Elephant's Dream
@@ -160,7 +160,7 @@ class TestUploadTranscripts(BaseTranscripts):
super(TestUploadTranscripts, self).setUp()
self.contents = {
'good': SRT_TRANSCRIPT_CONTENT,
'bad': b'Some BAD data',
'bad': u'Some BAD data',
}
# Create temporary transcript files
self.good_srt_file = self.create_transcript_file(content=self.contents['good'], suffix='.srt')
@@ -186,7 +186,7 @@ class TestUploadTranscripts(BaseTranscripts):
Setup a transcript file with suffix and content.
"""
transcript_file = tempfile.NamedTemporaryFile(suffix=suffix)
wrapped_content = textwrap.dedent(content.decode('utf-8'))
wrapped_content = textwrap.dedent(content)
if include_bom:
wrapped_content = wrapped_content.encode('utf-8-sig')
# Verify that ufeff(BOM) character is in content.
@@ -791,7 +791,7 @@ class TestDownloadTranscripts(BaseTranscripts):
"""
self.assertEqual(response.status_code, expected_status_code)
if expected_content:
self.assertEqual(response.content, expected_content)
assert response.content.decode('utf-8') == expected_content
def test_download_youtube_transcript_success(self):
"""

View File

@@ -229,7 +229,7 @@ def transcript_upload_handler(request):
# Convert SRT transcript into an SJSON format
# and upload it to S3.
sjson_subs = Transcript.convert(
content=transcript_file.read(),
content=transcript_file.read().decode('utf-8'),
input_format=Transcript.SRT,
output_format=Transcript.SJSON
)

View File

@@ -219,7 +219,7 @@ def upload_transcripts(request):
# Convert 'srt' transcript into the 'sjson' and upload it to
# configured transcript storage. For example, S3.
sjson_subs = Transcript.convert(
content=transcript_file.read(),
content=transcript_file.read().decode('utf-8'),
input_format=Transcript.SRT,
output_format=Transcript.SJSON
)
@@ -322,7 +322,7 @@ def check_transcripts(request):
filename = 'subs_{0}.srt.sjson'.format(item.sub)
content_location = StaticContent.compute_location(item.location.course_key, filename)
try:
local_transcripts = contentstore().find(content_location).data
local_transcripts = contentstore().find(content_location).data.decode('utf-8')
transcripts_presence['current_item_subs'] = item.sub
except NotFoundError:
pass
@@ -336,7 +336,7 @@ def check_transcripts(request):
filename = 'subs_{0}.srt.sjson'.format(youtube_id)
content_location = StaticContent.compute_location(item.location.course_key, filename)
try:
local_transcripts = contentstore().find(content_location).data
local_transcripts = contentstore().find(content_location).data.decode('utf-8')
transcripts_presence['youtube_local'] = True
except NotFoundError:
log.debug(u"Can't find transcripts in storage for youtube id: %s", youtube_id)

View File

@@ -37,7 +37,8 @@ from student.models import (
RegistrationCookieConfiguration,
UserAttribute,
UserProfile,
UserTestGroup
UserTestGroup,
BulkUnenrollConfiguration
)
from student.roles import REGISTERED_ACCESS_ROLES
from xmodule.modulestore.django import modulestore
@@ -445,6 +446,7 @@ admin.site.register(Registration)
admin.site.register(PendingNameChange)
admin.site.register(DashboardConfiguration, ConfigurationModelAdmin)
admin.site.register(RegistrationCookieConfiguration, ConfigurationModelAdmin)
admin.site.register(BulkUnenrollConfiguration, ConfigurationModelAdmin)
# We must first un-register the User model since it may also be registered by the auth app.

View File

@@ -5,11 +5,9 @@ import logging
import unicodecsv
from django.core.exceptions import ObjectDoesNotExist
from django.core.management.base import BaseCommand
from django.db.models import Q
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from student.models import CourseEnrollment, User
from student.models import CourseEnrollment, User, BulkUnenrollConfiguration
logger = logging.getLogger(__name__) # pylint: disable=invalid-name
@@ -27,39 +25,48 @@ class Command(BaseCommand):
parser.add_argument('-p', '--csv_path',
metavar='csv_path',
dest='csv_path',
required=True,
required=False,
help='Path to CSV file.')
def handle(self, *args, **options):
csv_path = options['csv_path']
with open(csv_path, 'rb') as csvfile:
reader = unicodecsv.DictReader(csvfile)
for row in reader:
username = row['username']
email = row['email']
course_key = row['course_id']
try:
user = User.objects.get(Q(username=username) | Q(email=email))
except ObjectDoesNotExist:
user = None
msg = 'User with username {} or email {} does not exist'.format(username, email)
logger.warning(msg)
if csv_path:
with open(csv_path) as csv_file:
self.unenroll_users(csv_file)
else:
csv_file = BulkUnenrollConfiguration.current().csv_file
self.unenroll_users(csv_file)
try:
course_id = CourseKey.from_string(course_key)
except InvalidKeyError:
course_id = None
msg = 'Invalid course id {course_id}, skipping un-enrollement for {username}, {email}'.format(**row)
logger.warning(msg)
def unenroll_users(self, csv_file):
reader = list(unicodecsv.DictReader(csv_file))
users_unenrolled = {}
for row in reader:
username = row['username']
course_key = row['course_id']
if user and course_id:
enrollment = CourseEnrollment.get_enrollment(user, course_id)
if not enrollment:
msg = 'Enrollment for the user {} in course {} does not exist!'.format(username, course_key)
logger.info(msg)
else:
try:
CourseEnrollment.unenroll(user, course_id, skip_refund=True)
except Exception as err:
msg = 'Error un-enrolling User {} from course {}: '.format(username, course_key, err)
logger.error(msg, exc_info=True)
try:
course_id = CourseKey.from_string(row['course_id'])
except InvalidKeyError:
msg = 'Invalid course id {course_id}, skipping un-enrollement for {username}, {email}'.format(**row)
logger.warning(msg)
continue
try:
enrollment = CourseEnrollment.objects.get(user__username=username, course_id=course_id)
enrollment.update_enrollment(is_active=False, skip_refund=True)
if username in users_unenrolled:
users_unenrolled[username].append(course_key.encode())
else:
users_unenrolled[username] = [course_key.encode()]
except ObjectDoesNotExist:
msg = 'Enrollment for the user {} in course {} does not exist!'.format(username, course_key)
logger.info(msg)
except Exception as err:
msg = 'Error un-enrolling User {} from course {}: '.format(username, course_key, err)
logger.error(msg, exc_info=True)
logger.info("Following users have been unenrolled successfully from the following courses: {users_unenrolled}"
.format(users_unenrolled=["{}:{}".format(k, v) for k, v in users_unenrolled.items()]))

View File

@@ -1,14 +1,14 @@
from __future__ import absolute_import
import six
from tempfile import NamedTemporaryFile
from django.core.management import call_command
from testfixtures import LogCapture
from course_modes.tests.factories import CourseModeFactory
from student.models import CourseEnrollment
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.management import call_command
from student.models import CourseEnrollment, BulkUnenrollConfiguration
from student.tests.factories import UserFactory
from testfixtures import LogCapture
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
@@ -47,21 +47,6 @@ class BulkUnenrollTests(SharedModuleStoreTestCase):
csv.seek(0)
return csv
def test_user_not_exist(self):
"""Verify that warning user not exist is logged for non existing user."""
with NamedTemporaryFile() as csv:
csv = self._write_test_csv(csv, lines=["111,test,test@example.com,course-v1:edX+DemoX+Demo_Course\n"])
with LogCapture(LOGGER_NAME) as log:
call_command("bulk_unenroll", "--csv_path={}".format(csv.name))
log.check(
(
LOGGER_NAME,
'WARNING',
'User with username {} or email {} does not exist'.format('test', 'test@example.com')
)
)
def test_invalid_course_key(self):
"""Verify in case of invalid course key warning is logged."""
with NamedTemporaryFile() as csv:
@@ -69,13 +54,11 @@ class BulkUnenrollTests(SharedModuleStoreTestCase):
with LogCapture(LOGGER_NAME) as log:
call_command("bulk_unenroll", "--csv_path={}".format(csv.name))
log.check(
(
LOGGER_NAME,
'WARNING',
'Invalid course id {}, skipping un-enrollement for {}, {}'.format(
'test_course', 'amy', 'amy@pond.com')
)
expected_message = 'Invalid course id {}, skipping un-enrollement for {}, {}'.\
format('test_course', 'amy', 'amy@pond.com')
log.check_present(
(LOGGER_NAME, 'WARNING', expected_message)
)
def test_user_not_enrolled(self):
@@ -85,13 +68,11 @@ class BulkUnenrollTests(SharedModuleStoreTestCase):
with LogCapture(LOGGER_NAME) as log:
call_command("bulk_unenroll", "--csv_path={}".format(csv.name))
log.check(
(
LOGGER_NAME,
'INFO',
'Enrollment for the user {} in course {} does not exist!'.format(
'amy', 'course-v1:edX+DemoX+Demo_Course')
)
expected_message = 'Enrollment for the user {} in course {} does not exist!'.\
format('amy', 'course-v1:edX+DemoX+Demo_Course')
log.check_present(
(LOGGER_NAME, 'INFO', expected_message)
)
def test_bulk_un_enroll(self):
@@ -107,3 +88,46 @@ class BulkUnenrollTests(SharedModuleStoreTestCase):
call_command("bulk_unenroll", "--csv_path={}".format(csv.name))
for enrollment in CourseEnrollment.objects.all():
self.assertEqual(enrollment.is_active, False)
def test_bulk_unenroll_from_config_model(self):
"""Verify users are unenrolled using the command."""
lines = "user_id,username,email,course_id\n"
for enrollment in self.enrollments:
lines += str(enrollment.user.id) + "," + enrollment.user.username + "," + \
enrollment.user.email + "," + str(enrollment.course.id) + "\n"
csv_file = SimpleUploadedFile(name='test.csv', content=lines, content_type='text/csv')
BulkUnenrollConfiguration.objects.create(enabled=True, csv_file=csv_file)
call_command("bulk_unenroll")
for enrollment in CourseEnrollment.objects.all():
self.assertEqual(enrollment.is_active, False)
def test_users_unenroll_successfully_logged(self):
"""Verify users unenrolled are logged """
lines = "user_id,username,email,course_id\n"
users_unenrolled = {}
for enrollment in self.enrollments:
username = enrollment.user.username
if username in users_unenrolled:
users_unenrolled[username].append(str(enrollment.course.id))
else:
users_unenrolled[username] = [str(enrollment.course.id)]
lines += str(enrollment.user.id) + "," + username + "," + \
enrollment.user.email + "," + str(enrollment.course.id) + "\n"
csv_file = SimpleUploadedFile(name='test.csv', content=lines, content_type='text/csv')
BulkUnenrollConfiguration.objects.create(enabled=True, csv_file=csv_file)
with LogCapture(LOGGER_NAME) as log:
call_command("bulk_unenroll")
log.check(
(
LOGGER_NAME,
'INFO',
'Following users have been unenrolled successfully from the following courses:'
' {users_unenrolled}'.format(users_unenrolled=["{}:{}".format(k, v) for k, v in
users_unenrolled.items()])
)
)

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.24 on 2019-09-19 19:51
from __future__ import unicode_literals
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('student', '0022_indexing_in_courseenrollment'),
]
operations = [
migrations.CreateModel(
name='BulkUnenrollConfiguration',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
('csv_file', models.FileField(help_text='It expect that the data will be provided in a csv file format with first row being the header and columns will be as follows: user_id, username, email, course_id, is_verified, verification_date', upload_to=b'', validators=[django.core.validators.FileExtensionValidator(allowed_extensions=[b'csv'])])),
('changed_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL, verbose_name='Changed by')),
],
options={
'ordering': ('-change_date',),
'abstract': False,
},
),
]

View File

@@ -18,6 +18,7 @@ import logging
import uuid
from collections import OrderedDict, defaultdict, namedtuple
from datetime import datetime, timedelta
from django.core.validators import FileExtensionValidator
from functools import total_ordering
from importlib import import_module
@@ -2845,6 +2846,18 @@ class RegistrationCookieConfiguration(ConfigurationModel):
)
class BulkUnenrollConfiguration(ConfigurationModel):
"""
"""
csv_file = models.FileField(
validators=[FileExtensionValidator(allowed_extensions=['csv'])],
help_text=_("It expect that the data will be provided in a csv file format with \
first row being the header and columns will be as follows: \
user_id, username, email, course_id, is_verified, verification_date")
)
@python_2_unicode_compatible
class UserAttribute(TimeStampedModel):
"""

View File

@@ -19,7 +19,7 @@ import inspect
import json
import logging
import numbers
import random
import random2 as random
import re
import sys
import textwrap

View File

@@ -8,7 +8,7 @@ from __future__ import absolute_import
import io
import json
import os
import random
import random2 as random
import textwrap
import unittest
import zipfile

View File

@@ -3,7 +3,6 @@ from __future__ import absolute_import, print_function
import textwrap
import unittest
import six
from capa.responsetypes import LoncapaProblemError
from capa.tests.helpers import new_loncapa_problem, test_capa_system
@@ -59,10 +58,7 @@ class CapaShuffleTest(unittest.TestCase):
response = list(problem.responders.values())[0]
self.assertFalse(response.has_mask())
self.assertTrue(response.has_shuffle())
if six.PY2:
self.assertEqual(response.unmask_order(), ['choice_0', 'choice_aaa', 'choice_1', 'choice_ddd'])
else:
self.assertEqual(response.unmask_order(), ['choice_1', 'choice_aaa', 'choice_0', 'choice_ddd'])
self.assertEqual(response.unmask_order(), ['choice_0', 'choice_aaa', 'choice_1', 'choice_ddd'])
def test_shuffle_different_seed(self):
xml_str = textwrap.dedent("""

View File

@@ -101,12 +101,19 @@ class MongoContentStore(ContentStore):
locked=getattr(content, 'locked', False)) as fp:
# It seems that this code thought that only some specific object would have the `__iter__` attribute
# but the bytes object in python 3 has one and should not use the chunking logic.
if hasattr(content.data, '__iter__') and not isinstance(content.data, six.binary_type):
# but many more objects have this in python3 and shouldn't be using the chunking logic. For string and
# byte streams we write them directly to gridfs and convert them to byetarrys if necessary.
if hasattr(content.data, '__iter__') and not isinstance(content.data, (six.binary_type, six.string_types)):
for chunk in content.data:
fp.write(chunk)
else:
fp.write(content.data)
# Ideally we could just ensure that we don't get strings in here and only byte streams
# but being confident of that wolud be a lot more work than we have time for so we just
# handle both cases here.
if isinstance(content.data, six.text_type):
fp.write(content.data.encode('utf-8'))
else:
fp.write(content.data)
return content

View File

@@ -16,13 +16,13 @@ def empty_asset_trashcan(course_locs):
thumbs = store.get_all_content_thumbnails_for_course(course_loc)
for thumb in thumbs:
print("Deleting {0}...".format(thumb))
store.delete(thumb['_id'])
store.delete(thumb['asset_key'])
# then delete all of the assets
assets, __ = store.get_all_content_for_course(course_loc)
for asset in assets:
print("Deleting {0}...".format(asset))
store.delete(asset['_id'])
store.delete(asset['asset_key'])
def restore_asset_from_trashcan(location):

View File

@@ -145,7 +145,7 @@ def youtube_video_transcript_name(youtube_text_api):
# http://video.google.com/timedtext?type=list&v={VideoId}
youtube_response = requests.get('http://' + youtube_text_api['url'], params=transcripts_param)
if youtube_response.status_code == 200 and youtube_response.text:
youtube_data = etree.fromstring(youtube_response.content.encode('utf-8'), parser=utf8_parser)
youtube_data = etree.fromstring(youtube_response.text.encode('utf-8'), parser=utf8_parser)
# iterate all transcripts information from youtube server
for element in youtube_data:
# search specific language code such as 'en' in transcripts info list
@@ -346,7 +346,7 @@ def copy_or_rename_transcript(new_name, old_name, item, delete_old=False, user=N
"""
filename = u'subs_{0}.srt.sjson'.format(old_name)
content_location = StaticContent.compute_location(item.location.course_key, filename)
transcripts = contentstore().find(content_location).data
transcripts = contentstore().find(content_location).data.decode('utf-8')
save_subs_to_store(json.loads(transcripts), new_name, item)
item.sub = new_name
item.save_with_metadata(user)
@@ -579,6 +579,8 @@ def get_video_transcript_content(edx_video_id, language_code):
edx_video_id = clean_video_id(edx_video_id)
if edxval_api and edx_video_id:
transcript = edxval_api.get_video_transcript_data(edx_video_id, language_code)
if transcript and 'content' in transcript:
transcript['content'] = transcript['content'].decode('utf-8')
return transcript
@@ -654,7 +656,7 @@ class Transcript(object):
if input_format == 'srt':
if output_format == 'txt':
text = SubRipFile.from_string(content.decode('utf-8')).text
text = SubRipFile.from_string(content).text
return HTMLParser().unescape(text)
elif output_format == 'sjson':
@@ -663,7 +665,7 @@ class Transcript(object):
# the exception if something went wrong in parsing the transcript.
srt_subs = SubRipFile.from_string(
# Skip byte order mark(BOM) character
content.decode('utf-8-sig') if six.PY2 else content.encode('utf-8').decode('utf-8-sig'),
content.encode('utf-8').decode('utf-8-sig'),
error_handling=SubRipFile.ERROR_RAISE
)
except Error as ex: # Base exception from pysrt
@@ -925,11 +927,11 @@ def get_transcript_for_video(video_location, subs_id, file_name, language):
try:
if subs_id is None:
raise NotFoundError
content = Transcript.asset(video_location, subs_id, language).data
content = Transcript.asset(video_location, subs_id, language).data.decode('utf-8')
base_name = subs_id
input_format = Transcript.SJSON
except NotFoundError:
content = Transcript.asset(video_location, None, language, file_name).data
content = Transcript.asset(video_location, None, language, file_name).data.decode('utf-8')
base_name = os.path.splitext(file_name)[0]
input_format = Transcript.SRT

View File

@@ -466,7 +466,7 @@ class VideoStudioViewHandlers(object):
# Convert SRT transcript into an SJSON format
# and upload it to S3.
sjson_subs = Transcript.convert(
content=transcript_file.read(),
content=transcript_file.read().decode('utf-8'),
input_format=Transcript.SRT,
output_format=Transcript.SJSON
)

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
5b2b96c1f6ef523876a5a900158a35c2658264a2
9ad3d121db0c71754dc37475498270555f1d163f

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -366,7 +366,7 @@ CREATE TABLE `auth_permission` (
PRIMARY KEY (`id`),
UNIQUE KEY `auth_permission_content_type_id_codename_01ab375a_uniq` (`content_type_id`,`codename`),
CONSTRAINT `auth_permission_content_type_id_2f476e4b_fk_django_co` FOREIGN KEY (`content_type_id`) REFERENCES `django_content_type` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2363 DEFAULT CHARSET=utf8;
) ENGINE=InnoDB AUTO_INCREMENT=2366 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `auth_registration`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@@ -2419,7 +2419,7 @@ CREATE TABLE `django_content_type` (
`model` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `django_content_type_app_label_model_76bd3d3b_uniq` (`app_label`,`model`)
) ENGINE=InnoDB AUTO_INCREMENT=785 DEFAULT CHARSET=utf8;
) ENGINE=InnoDB AUTO_INCREMENT=786 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `django_migrations`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@@ -2430,7 +2430,7 @@ CREATE TABLE `django_migrations` (
`name` varchar(255) NOT NULL,
`applied` datetime(6) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=595 DEFAULT CHARSET=utf8;
) ENGINE=InnoDB AUTO_INCREMENT=600 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `django_openid_auth_association`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
@@ -2634,8 +2634,10 @@ CREATE TABLE `edx_when_datepolicy` (
`created` datetime(6) NOT NULL,
`modified` datetime(6) NOT NULL,
`abs_date` datetime(6) DEFAULT NULL,
`rel_date` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `edx_when_datepolicy_abs_date_1a510cd3` (`abs_date`)
KEY `edx_when_datepolicy_abs_date_1a510cd3` (`abs_date`),
KEY `edx_when_datepolicy_rel_date_836d6051` (`rel_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `edx_when_userdate`;
@@ -2646,7 +2648,7 @@ CREATE TABLE `edx_when_userdate` (
`created` datetime(6) NOT NULL,
`modified` datetime(6) NOT NULL,
`abs_date` datetime(6) DEFAULT NULL,
`rel_date` int(11) DEFAULT NULL,
`rel_date` bigint(20) DEFAULT NULL,
`reason` longtext NOT NULL,
`actor_id` int(11) DEFAULT NULL,
`user_id` int(11) NOT NULL,
@@ -2655,6 +2657,7 @@ CREATE TABLE `edx_when_userdate` (
KEY `edx_when_userdate_user_id_46e8cc36_fk_auth_user_id` (`user_id`),
KEY `edx_when_userdate_content_date_id_35c5e2e2_fk_edx_when_` (`content_date_id`),
KEY `edx_when_userdate_actor_id_cbef1cdc_fk_auth_user_id` (`actor_id`),
KEY `edx_when_userdate_rel_date_954ee5b4` (`rel_date`),
CONSTRAINT `edx_when_userdate_actor_id_cbef1cdc_fk_auth_user_id` FOREIGN KEY (`actor_id`) REFERENCES `auth_user` (`id`),
CONSTRAINT `edx_when_userdate_content_date_id_35c5e2e2_fk_edx_when_` FOREIGN KEY (`content_date_id`) REFERENCES `edx_when_contentdate` (`id`),
CONSTRAINT `edx_when_userdate_user_id_46e8cc36_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
@@ -5456,6 +5459,20 @@ CREATE TABLE `student_anonymoususerid` (
CONSTRAINT `student_anonymoususerid_user_id_0fb2ad5c_fk_auth_user_id` FOREIGN KEY (`user_id`) REFERENCES `auth_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `student_bulkunenrollconfiguration`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `student_bulkunenrollconfiguration` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`change_date` datetime(6) NOT NULL,
`enabled` tinyint(1) NOT NULL,
`csv_file` varchar(100) NOT NULL,
`changed_by_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `student_bulkunenroll_changed_by_id_7b6131b9_fk_auth_user` (`changed_by_id`),
CONSTRAINT `student_bulkunenroll_changed_by_id_7b6131b9_fk_auth_user` FOREIGN KEY (`changed_by_id`) REFERENCES `auth_user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
DROP TABLE IF EXISTS `student_courseaccessrole`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;

View File

@@ -36,7 +36,7 @@ CREATE TABLE `django_migrations` (
`name` varchar(255) NOT NULL,
`applied` datetime(6) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=595 DEFAULT CHARSET=utf8;
) ENGINE=InnoDB AUTO_INCREMENT=600 DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

View File

@@ -3,7 +3,9 @@ Helpers for courseware tests.
"""
from __future__ import absolute_import
import ast
import json
from collections import OrderedDict
from datetime import timedelta
import six
@@ -17,10 +19,10 @@ from six import text_type
from six.moves import range
from xblock.field_data import DictFieldData
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.courseware.masquerade import handle_ajax, setup_masquerade
from edxmako.shortcuts import render_to_string
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
from lms.djangoapps.courseware.masquerade import handle_ajax, setup_masquerade
from lms.djangoapps.lms_xblock.field_data import LmsFieldData
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.lib.url_utils import quote_slashes
@@ -404,3 +406,15 @@ def get_expiration_banner_text(user, course, language='en'):
expiration_date=formatted_expiration_date
)
return bannerText
def get_context_dict_from_string(data):
"""
Retrieve dictionary from string.
"""
# Replace tuple and un-necessary info from inside string and get the dictionary.
cleaned_data = ast.literal_eval(data.split('((\'video.html\',')[1].replace("),\n {})", '').strip()) # pylint: disable=unicode-format-string
cleaned_data['metadata'] = OrderedDict(
sorted(json.loads(cleaned_data['metadata']).items(), key=lambda t: t[0])
)
return cleaned_data

View File

@@ -5,7 +5,6 @@ Video xmodule tests in mongo.
from __future__ import absolute_import
import ast
import io
import json
import shutil
@@ -39,6 +38,7 @@ from mock import MagicMock, Mock, patch
from path import Path as path
from waffle.testutils import override_flag
from lms.djangoapps.courseware.tests.helpers import get_context_dict_from_string
from openedx.core.djangoapps.video_pipeline.config.waffle import DEPRECATE_YOUTUBE, waffle_flags
from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
@@ -134,8 +134,10 @@ class TestVideoYouTube(TestVideo): # pylint: disable=test-inherits-tests
}
self.assertEqual(
context,
self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context),
get_context_dict_from_string(context),
get_context_dict_from_string(
self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
)
)
@@ -214,10 +216,15 @@ class TestVideoNonYouTube(TestVideo): # pylint: disable=test-inherits-tests
'poster': 'null',
}
self.assertEqual(
context,
self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context),
expected_result = get_context_dict_from_string(
self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
)
self.assertEqual(
get_context_dict_from_string(context),
expected_result
)
self.assertEqual(expected_result['download_video_link'], 'example.mp4')
self.assertEqual(expected_result['display_name'], 'A Name')
@ddt.ddt
@@ -383,8 +390,8 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
})
self.assertEqual(
self.get_context_dict_from_string(context),
self.get_context_dict_from_string(
get_context_dict_from_string(context),
get_context_dict_from_string(
self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
)
)
@@ -493,8 +500,8 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
})
self.assertEqual(
self.get_context_dict_from_string(context),
self.get_context_dict_from_string(
get_context_dict_from_string(context),
get_context_dict_from_string(
self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
)
)
@@ -634,23 +641,12 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
})
self.assertEqual(
self.get_context_dict_from_string(context),
self.get_context_dict_from_string(
get_context_dict_from_string(context),
get_context_dict_from_string(
self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
)
)
def get_context_dict_from_string(self, data):
"""
Retrieve dictionary from string.
"""
# Replace tuple and un-necessary info from inside string and get the dictionary.
cleaned_data = ast.literal_eval(data.split('((\'video.html\',')[1].replace("),\n {})", '').strip()) # pylint: disable=unicode-format-string
cleaned_data['metadata'] = OrderedDict(
sorted(json.loads(cleaned_data['metadata']).items(), key=lambda t: t[0])
)
return cleaned_data
def test_get_html_with_existing_edx_video_id(self):
"""
Tests the `VideoBlock` `get_html` where `edx_video_id` is given and related video is found
@@ -676,8 +672,8 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
# expected_context, a dict to assert with context
context, expected_context = self.helper_get_html_with_edx_video_id(data)
self.assertEqual(
self.get_context_dict_from_string(context),
self.get_context_dict_from_string(
get_context_dict_from_string(context),
get_context_dict_from_string(
self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
)
)
@@ -710,8 +706,8 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
context, expected_context = self.helper_get_html_with_edx_video_id(data)
self.assertEqual(
self.get_context_dict_from_string(context),
self.get_context_dict_from_string(
get_context_dict_from_string(context),
get_context_dict_from_string(
self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
)
)
@@ -921,8 +917,8 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
})
self.assertEqual(
self.get_context_dict_from_string(context),
self.get_context_dict_from_string(
get_context_dict_from_string(context),
get_context_dict_from_string(
self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
)
)
@@ -1026,8 +1022,8 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
})
self.assertEqual(
self.get_context_dict_from_string(context),
self.get_context_dict_from_string(
get_context_dict_from_string(context),
get_context_dict_from_string(
self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
)
)
@@ -1083,7 +1079,7 @@ class TestGetHtmlMethod(BaseTestVideoXBlock):
self.assertIn('"streams": "1.00:https://yt.com/?v=v0TFmdO4ZP0"', context)
self.assertEqual(
sorted(["https://webm.com/dw.webm", "https://mp4.com/dm.mp4", "https://hls.com/hls.m3u8"]),
sorted(self.get_context_dict_from_string(context)['metadata']['sources'])
sorted(get_context_dict_from_string(context)['metadata']['sources'])
)
def test_get_html_hls_no_video_id(self):
@@ -2266,7 +2262,9 @@ class TestVideoWithBumper(TestVideo): # pylint: disable=test-inherits-tests
}
expected_content = self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
self.assertEqual(content, expected_content)
self.assertEqual(
get_context_dict_from_string(content), get_context_dict_from_string(expected_content)
)
@ddt.ddt
@@ -2358,7 +2356,7 @@ class TestAutoAdvanceVideo(TestVideo):
with override_settings(FEATURES=self.FEATURES):
expected_content = self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context)
self.assertEqual(content, expected_content)
self.assertEqual(get_context_dict_from_string(content), get_context_dict_from_string(expected_content))
def change_course_setting_autoadvance(self, new_value):
"""

View File

@@ -292,7 +292,7 @@ def get_library_block_olx(usage_key):
bundle_uuid=definition_key.bundle_uuid, # pylint: disable=no-member
path=definition_key.olx_path, # pylint: disable=no-member
use_draft=DRAFT_NAME,
)
).decode('utf-8')
return xml_str
@@ -314,7 +314,7 @@ def set_library_block_olx(usage_key, new_olx_str):
raise ValueError("Invalid root tag in OLX, expected {}".format(block_type))
# Write the new XML/OLX file into the library bundle's draft
draft = get_or_create_bundle_draft(metadata.def_key.bundle_uuid, DRAFT_NAME)
write_draft_file(draft.uuid, metadata.def_key.olx_path, new_olx_str)
write_draft_file(draft.uuid, metadata.def_key.olx_path, new_olx_str.encode('utf-8'))
# Clear the bundle cache so everyone sees the new block immediately:
BundleCache(metadata.def_key.bundle_uuid, draft_name=DRAFT_NAME).clear()
@@ -347,7 +347,7 @@ def create_library_block(library_key, block_type, definition_id):
path = "{}/{}/definition.xml".format(block_type, definition_id)
# Write the new XML/OLX file into the library bundle's draft
draft = get_or_create_bundle_draft(ref.bundle_uuid, DRAFT_NAME)
write_draft_file(draft.uuid, path, new_definition_xml)
write_draft_file(draft.uuid, path, new_definition_xml.encode('utf-8'))
# Clear the bundle cache so everyone sees the new block immediately:
BundleCache(ref.bundle_uuid, draft_name=DRAFT_NAME).clear()
# Now return the metadata about the new block:

View File

@@ -254,7 +254,7 @@ class ContentLibrariesTest(APITestCase):
new_olx = """
<problem display_name="New Multi Choice Question" max_attempts="5">
<multiplechoiceresponse>
<p>This is a normal capa problem. It has "maximum attempts" set to **5**.</p>
<p>This is a normal capa problem with unicode 🔥. It has "maximum attempts" set to **5**.</p>
<label>Blockstore is designed to store.</label>
<choicegroup type="MultipleChoice">
<choice correct="false">XBlock metadata only</choice>

View File

@@ -272,7 +272,7 @@ class ContentStoreToyCourseTest(SharedModuleStoreTestCase):
416 Requested Range Not Satisfiable.
"""
resp = self.client.get(self.url_unlocked, HTTP_RANGE='bytes={first}-{last}'.format(
first=(self.length_unlocked / 2), last=(self.length_unlocked / 4)))
first=(self.length_unlocked // 2), last=(self.length_unlocked // 4)))
self.assertEqual(resp.status_code, 416)
def test_range_request_malformed_out_of_bounds(self):

View File

@@ -138,6 +138,7 @@ pytz # Time zone information database
PyYAML # Used to parse XModule resource templates
redis==2.10.6 # celery task broker
requests-oauthlib # Simplifies use of OAuth via the requests library, used for CCX and LTI
random2
rules # Django extension for rules-based authorization checks
sailthru-client==2.2.3 # For Sailthru integration
Shapely # Geometry library, used for image click regions in capa

View File

@@ -4,7 +4,7 @@
#
# make upgrade
#
-e git+https://github.com/edx/acid-block.git@e46f9cda8a03e121a00c7e347084d142d22ebfb7#egg=acid-xblock
-e git+https://github.com/edx/acid-block.git@98aecba94ecbfa934e2d00262741c0ea9f557fc9#egg=acid-xblock
-e common/lib/capa
-e git+https://github.com/edx/codejail.git@ed3d36c27913254a23273da95ad627a1bbbffa44#egg=codejail
-e git+https://github.com/edx/django-wiki.git@v0.0.23#egg=django-wiki
@@ -207,6 +207,7 @@ python3-saml==1.5.0
pytz==2019.2
pyuca==1.1
pyyaml==5.1.2
random2==1.0.1
recommender-xblock==1.4.4
redis==2.10.6
requests-oauthlib==1.1.0
@@ -250,7 +251,7 @@ web-fragments==0.3.0
webencodings==0.5.1 # via html5lib
webob==1.8.5 # via xblock
wrapt==1.10.5
git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.4#egg=xblock-drag-and-drop-v2==2.2.4
git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.6#egg=xblock-drag-and-drop-v2==2.2.6
git+https://github.com/open-craft/xblock-poll@add89e14558c30f3c8dc7431e5cd6536fff6d941#egg=xblock-poll==1.5.1
xblock-utils==1.2.2
xblock==1.2.6

View File

@@ -4,7 +4,7 @@
#
# make upgrade
#
-e git+https://github.com/edx/acid-block.git@e46f9cda8a03e121a00c7e347084d142d22ebfb7#egg=acid-xblock
-e git+https://github.com/edx/acid-block.git@98aecba94ecbfa934e2d00262741c0ea9f557fc9#egg=acid-xblock
-e common/lib/capa
-e git+https://github.com/edx/codejail.git@ed3d36c27913254a23273da95ad627a1bbbffa44#egg=codejail
-e git+https://github.com/edx/django-wiki.git@v0.0.23#egg=django-wiki
@@ -279,6 +279,7 @@ pytz==2019.2
pyuca==1.1
pyyaml==5.1.2
radon==4.0.0
random2==1.0.1
recommender-xblock==1.4.4
recommonmark==0.6.0
redis==2.10.6
@@ -339,7 +340,7 @@ webob==1.8.5
websocket-client==0.56.0
werkzeug==0.16.0
wrapt==1.10.5
git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.4#egg=xblock-drag-and-drop-v2==2.2.4
git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.6#egg=xblock-drag-and-drop-v2==2.2.6
git+https://github.com/open-craft/xblock-poll@add89e14558c30f3c8dc7431e5cd6536fff6d941#egg=xblock-poll==1.5.1
xblock-utils==1.2.2
xblock==1.2.6

View File

@@ -80,7 +80,7 @@ git+https://github.com/edx/bridgekeeper.git@4e34894e4ac5d0467ed1901811a81fd87ee0
# Our libraries:
-e git+https://github.com/edx/codejail.git@ed3d36c27913254a23273da95ad627a1bbbffa44#egg=codejail
-e git+https://github.com/edx/acid-block.git@e46f9cda8a03e121a00c7e347084d142d22ebfb7#egg=acid-xblock
-e git+https://github.com/edx/acid-block.git@98aecba94ecbfa934e2d00262741c0ea9f557fc9#egg=acid-xblock
git+https://github.com/edx/edx-ora2.git@2.3.1#egg=ora2==2.3.1
git+https://github.com/edx/crowdsourcehinter.git@a7ffc85b134b7d8909bf1fefd23dbdb8eb28e467#egg=crowdsourcehinter-xblock==0.2
-e git+https://github.com/edx/RateXBlock.git@367e19c0f6eac8a5f002fd0f1559555f8e74bfff#egg=rate-xblock
@@ -94,4 +94,4 @@ git+https://github.com/edx/xblock-lti-consumer.git@v1.1.8#egg=lti_consumer-xbloc
git+https://github.com/joestump/python-oauth2.git@b94f69b1ad195513547924e380d9265133e995fa#egg=oauth2
git+https://github.com/mitodl/edx-sga.git@237ad328ba3f03d189c421073c85e48091041f8b#egg=edx-sga==0.0
git+https://github.com/open-craft/xblock-poll@add89e14558c30f3c8dc7431e5cd6536fff6d941#egg=xblock-poll==1.5.1
git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.4#egg=xblock-drag-and-drop-v2==2.2.4
git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.6#egg=xblock-drag-and-drop-v2==2.2.6

View File

@@ -4,7 +4,7 @@
#
# make upgrade
#
-e git+https://github.com/edx/acid-block.git@e46f9cda8a03e121a00c7e347084d142d22ebfb7#egg=acid-xblock
-e git+https://github.com/edx/acid-block.git@98aecba94ecbfa934e2d00262741c0ea9f557fc9#egg=acid-xblock
-e common/lib/capa
-e git+https://github.com/edx/codejail.git@ed3d36c27913254a23273da95ad627a1bbbffa44#egg=codejail
-e git+https://github.com/edx/django-wiki.git@v0.0.23#egg=django-wiki
@@ -270,6 +270,7 @@ pytz==2019.2
pyuca==1.1
pyyaml==5.1.2
radon==4.0.0
random2==1.0.1
recommender-xblock==1.4.4
redis==2.10.6
requests-oauthlib==1.1.0
@@ -325,7 +326,7 @@ webob==1.8.5
websocket-client==0.56.0 # via docker
werkzeug==0.16.0 # via moto
wrapt==1.10.5
git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.4#egg=xblock-drag-and-drop-v2==2.2.4
git+https://github.com/edx-solutions/xblock-drag-and-drop-v2@v2.2.6#egg=xblock-drag-and-drop-v2==2.2.6
git+https://github.com/open-craft/xblock-poll@add89e14558c30f3c8dc7431e5cd6536fff6d941#egg=xblock-poll==1.5.1
xblock-utils==1.2.2
xblock==1.2.6