Add basic pagination in video upload page
This commit is contained in:
committed by
Noraiz Anwar
parent
5350064731
commit
3b7918b07a
@@ -191,7 +191,8 @@ class CourseQualityView(DeveloperErrorViewMixin, GenericAPIView):
|
||||
|
||||
def _videos_quality(self, course):
|
||||
video_blocks_in_course = modulestore().get_items(course.id, qualifiers={'category': 'video'})
|
||||
videos_in_val = list(get_videos_for_course(course.id))
|
||||
videos, __ = get_videos_for_course(course.id)
|
||||
videos_in_val = list(videos)
|
||||
video_durations = [video['duration'] for video in videos_in_val]
|
||||
|
||||
return dict(
|
||||
|
||||
@@ -1953,7 +1953,8 @@ class RerunCourseTest(ContentStoreTestCase):
|
||||
source_course = CourseFactory.create()
|
||||
destination_course_key = self.post_rerun_request(source_course.id)
|
||||
self.verify_rerun_course(source_course.id, destination_course_key, self.destination_course_data['display_name'])
|
||||
videos = list(get_videos_for_course(text_type(destination_course_key)))
|
||||
videos, __ = get_videos_for_course(text_type(destination_course_key))
|
||||
videos = list(videos)
|
||||
self.assertEqual(0, len(videos))
|
||||
self.assertInCourseListing(destination_course_key)
|
||||
|
||||
@@ -1989,8 +1990,10 @@ class RerunCourseTest(ContentStoreTestCase):
|
||||
self.verify_rerun_course(source_course.id, destination_course_key, self.destination_course_data['display_name'])
|
||||
|
||||
# Verify that the VAL copies videos to the rerun
|
||||
source_videos = list(get_videos_for_course(text_type(source_course.id)))
|
||||
target_videos = list(get_videos_for_course(text_type(destination_course_key)))
|
||||
videos, __ = get_videos_for_course(text_type(source_course.id))
|
||||
source_videos = list(videos)
|
||||
videos, __ = get_videos_for_course(text_type(destination_course_key))
|
||||
target_videos = list(videos)
|
||||
self.assertEqual(1, len(source_videos))
|
||||
self.assertEqual(source_videos, target_videos)
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ from contentstore.utils import reverse_course_url
|
||||
from contentstore.views.videos import (
|
||||
_get_default_video_image_url,
|
||||
VIDEO_IMAGE_UPLOAD_ENABLED,
|
||||
ENABLE_VIDEO_UPLOAD_PAGINATION,
|
||||
WAFFLE_SWITCHES,
|
||||
TranscriptProvider
|
||||
)
|
||||
@@ -39,6 +40,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from openedx.core.djangoapps.video_pipeline.config.waffle import waffle_flags, DEPRECATE_YOUTUBE
|
||||
from openedx.core.djangoapps.profile_images.tests.helpers import make_image_file
|
||||
from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel
|
||||
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
|
||||
|
||||
from edxval.api import create_or_update_transcript_preferences, get_transcript_preferences
|
||||
from waffle.testutils import override_flag
|
||||
@@ -326,6 +328,16 @@ class VideosHandlerTestCase(VideoUploadTestMixin, CourseTestCase):
|
||||
# Crude check for presence of data in returned HTML
|
||||
for video in self.previous_uploads:
|
||||
self.assertIn(video["edx_video_id"], response.content)
|
||||
self.assertNotIn('video_upload_pagination', response.content)
|
||||
|
||||
@override_waffle_flag(ENABLE_VIDEO_UPLOAD_PAGINATION, active=True)
|
||||
def test_get_html_paginated(self):
|
||||
"""
|
||||
Tests that response is paginated.
|
||||
"""
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn('video_upload_pagination', response.content)
|
||||
|
||||
def test_post_non_json(self):
|
||||
response = self.client.post(self.url, {"files": []})
|
||||
|
||||
@@ -43,7 +43,7 @@ from contentstore.video_utils import validate_video_image
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from openedx.core.djangoapps.video_config.models import VideoTranscriptEnabledFlag
|
||||
from openedx.core.djangoapps.video_pipeline.config.waffle import waffle_flags, DEPRECATE_YOUTUBE
|
||||
from openedx.core.djangoapps.waffle_utils import WaffleSwitchNamespace
|
||||
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleSwitchNamespace, WaffleFlagNamespace
|
||||
from util.json_request import JsonResponse, expect_json
|
||||
|
||||
from .course import get_course_and_check_access
|
||||
@@ -64,6 +64,14 @@ WAFFLE_SWITCHES = WaffleSwitchNamespace(name=WAFFLE_NAMESPACE)
|
||||
# Waffle switch for enabling/disabling video image upload feature
|
||||
VIDEO_IMAGE_UPLOAD_ENABLED = 'video_image_upload_enabled'
|
||||
|
||||
# Waffle flag namespace for studio
|
||||
WAFFLE_STUDIO_FLAG_NAMESPACE = WaffleFlagNamespace(name=u'studio')
|
||||
|
||||
ENABLE_VIDEO_UPLOAD_PAGINATION = CourseWaffleFlag(
|
||||
waffle_namespace=WAFFLE_STUDIO_FLAG_NAMESPACE,
|
||||
flag_name=u'enable_video_upload_pagination',
|
||||
flag_undefined_default=False
|
||||
)
|
||||
# Default expiration, in seconds, of one-time URLs used for uploading videos.
|
||||
KEY_EXPIRATION_IN_SECONDS = 86400
|
||||
|
||||
@@ -77,6 +85,8 @@ VIDEO_UPLOAD_MAX_FILE_SIZE_GB = 5
|
||||
# maximum time for video to remain in upload state
|
||||
MAX_UPLOAD_HOURS = 24
|
||||
|
||||
VIDEOS_PER_PAGE = 100
|
||||
|
||||
|
||||
class TranscriptProvider(object):
|
||||
"""
|
||||
@@ -176,14 +186,16 @@ def videos_handler(request, course_key_string, edx_video_id=None):
|
||||
if request.method == "GET":
|
||||
if "application/json" in request.META.get("HTTP_ACCEPT", ""):
|
||||
return videos_index_json(course)
|
||||
else:
|
||||
return videos_index_html(course)
|
||||
pagination_conf = _generate_pagination_configuration(course_key_string, request)
|
||||
return videos_index_html(course, pagination_conf)
|
||||
elif request.method == "DELETE":
|
||||
remove_video_for_course(course_key_string, edx_video_id)
|
||||
return JsonResponse()
|
||||
else:
|
||||
if is_status_update_request(request.json):
|
||||
return send_video_status_update(request.json)
|
||||
elif _is_pagination_context_update_request(request):
|
||||
return _update_pagination_context(request)
|
||||
|
||||
return videos_post(course, request)
|
||||
|
||||
@@ -361,8 +373,8 @@ def video_encodings_download(request, course_key_string):
|
||||
return _("{profile_name} URL").format(profile_name=profile)
|
||||
|
||||
profile_whitelist = VideoUploadConfig.get_profile_whitelist()
|
||||
|
||||
videos = list(_get_videos(course))
|
||||
videos, __ = _get_videos(course)
|
||||
videos = list(videos)
|
||||
name_col = _("Name")
|
||||
duration_col = _("Duration")
|
||||
added_col = _("Date Added")
|
||||
@@ -479,11 +491,17 @@ def convert_video_status(video, is_video_encodes_ready=False):
|
||||
return status
|
||||
|
||||
|
||||
def _get_videos(course):
|
||||
def _get_videos(course, pagination_conf=None):
|
||||
"""
|
||||
Retrieves the list of videos from VAL corresponding to this course.
|
||||
"""
|
||||
videos = list(get_videos_for_course(unicode(course.id), VideoSortField.created, SortDirection.desc))
|
||||
videos, pagination_context = get_videos_for_course(
|
||||
unicode(course.id),
|
||||
VideoSortField.created,
|
||||
SortDirection.desc,
|
||||
pagination_conf
|
||||
)
|
||||
videos = list(videos)
|
||||
|
||||
# This is required to see if edx video pipeline is enabled while converting the video status.
|
||||
course_video_upload_token = course.video_upload_pipeline.get('course_video_upload_token')
|
||||
@@ -506,7 +524,7 @@ def _get_videos(course):
|
||||
# Convert the video status.
|
||||
video['status'] = convert_video_status(video, is_video_encodes_ready)
|
||||
|
||||
return videos
|
||||
return videos, pagination_context
|
||||
|
||||
|
||||
def _get_default_video_image_url():
|
||||
@@ -516,7 +534,7 @@ def _get_default_video_image_url():
|
||||
return staticfiles_storage.url(settings.VIDEO_IMAGE_DEFAULT_FILENAME)
|
||||
|
||||
|
||||
def _get_index_videos(course):
|
||||
def _get_index_videos(course, pagination_conf=None):
|
||||
"""
|
||||
Returns the information about each video upload required for the video list
|
||||
"""
|
||||
@@ -539,10 +557,10 @@ def _get_index_videos(course):
|
||||
values[attr] = video[attr]
|
||||
|
||||
return values
|
||||
|
||||
videos, pagination_context = _get_videos(course, pagination_conf)
|
||||
return [
|
||||
_get_values(video) for video in _get_videos(course)
|
||||
]
|
||||
_get_values(video) for video in videos
|
||||
], pagination_context
|
||||
|
||||
|
||||
def get_all_transcript_languages():
|
||||
@@ -570,18 +588,19 @@ def get_all_transcript_languages():
|
||||
return all_languages
|
||||
|
||||
|
||||
def videos_index_html(course):
|
||||
def videos_index_html(course, pagination_conf=None):
|
||||
"""
|
||||
Returns an HTML page to display previous video uploads and allow new ones
|
||||
"""
|
||||
is_video_transcript_enabled = VideoTranscriptEnabledFlag.feature_enabled(course.id)
|
||||
previous_uploads, pagination_context = _get_index_videos(course, pagination_conf)
|
||||
context = {
|
||||
'context_course': course,
|
||||
'image_upload_url': reverse_course_url('video_images_handler', unicode(course.id)),
|
||||
'video_handler_url': reverse_course_url('videos_handler', unicode(course.id)),
|
||||
'encodings_download_url': reverse_course_url('video_encodings_download', unicode(course.id)),
|
||||
'default_video_image_url': _get_default_video_image_url(),
|
||||
'previous_uploads': _get_index_videos(course),
|
||||
'previous_uploads': previous_uploads,
|
||||
'concurrent_upload_limit': settings.VIDEO_UPLOAD_PIPELINE.get('CONCURRENT_UPLOAD_LIMIT', 0),
|
||||
'video_supported_file_formats': VIDEO_SUPPORTED_FILE_FORMATS.keys(),
|
||||
'video_upload_max_file_size': VIDEO_UPLOAD_MAX_FILE_SIZE_GB,
|
||||
@@ -602,7 +621,8 @@ def videos_index_html(course):
|
||||
'transcript_upload_handler_url': reverse('transcript_upload_handler'),
|
||||
'transcript_delete_handler_url': reverse_course_url('transcript_delete_handler', unicode(course.id)),
|
||||
'trancript_download_file_format': Transcript.SRT
|
||||
}
|
||||
},
|
||||
'pagination_context': pagination_context
|
||||
}
|
||||
|
||||
if is_video_transcript_enabled:
|
||||
@@ -638,7 +658,8 @@ def videos_index_json(course):
|
||||
}]
|
||||
}
|
||||
"""
|
||||
return JsonResponse({"videos": _get_index_videos(course)}, status=200)
|
||||
index_videos, __ = _get_index_videos(course)
|
||||
return JsonResponse({"videos": index_videos}, status=200)
|
||||
|
||||
|
||||
def videos_post(course, request):
|
||||
@@ -784,3 +805,39 @@ def is_status_update_request(request_data):
|
||||
Returns True if `request_data` contains status update else False.
|
||||
"""
|
||||
return any('status' in update for update in request_data)
|
||||
|
||||
|
||||
def _generate_pagination_configuration(course_key_string, request):
|
||||
"""
|
||||
Returns pagination configuration
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_key_string)
|
||||
if not ENABLE_VIDEO_UPLOAD_PAGINATION.is_enabled(course_key):
|
||||
return None
|
||||
return {
|
||||
'page_number': request.GET.get('page', 1),
|
||||
'videos_per_page': request.session.get("VIDEOS_PER_PAGE", VIDEOS_PER_PAGE)
|
||||
}
|
||||
|
||||
|
||||
def _is_pagination_context_update_request(request):
|
||||
"""
|
||||
Checks if request contains `videos_per_page`
|
||||
"""
|
||||
return request.POST.get('id', '') == "videos_per_page"
|
||||
|
||||
|
||||
def _update_pagination_context(request):
|
||||
"""
|
||||
Updates session with posted value
|
||||
"""
|
||||
error_msg = _(u'A non zero postive integar is expected')
|
||||
try:
|
||||
videos_per_page = int(request.POST.get('value'))
|
||||
if videos_per_page <= 0:
|
||||
return JsonResponse({'error': error_msg}, status=500)
|
||||
except ValueError:
|
||||
return JsonResponse({'error': error_msg}, status=500)
|
||||
|
||||
request.session['VIDEOS_PER_PAGE'] = videos_per_page
|
||||
return JsonResponse()
|
||||
|
||||
@@ -75,4 +75,8 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
% if pagination_context:
|
||||
<%include file="videos_index_pagination.html"/>
|
||||
% endif
|
||||
|
||||
</%block>
|
||||
|
||||
40
cms/templates/videos_index_pagination.html
Normal file
40
cms/templates/videos_index_pagination.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<%page expression_filter="h"/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangolib.js_utils import dump_js_escaped_json
|
||||
%>
|
||||
<link href="//cdnjs.cloudflare.com/ajax/libs/simplePagination.js/1.6/simplePagination.min.css" rel="stylesheet">
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/simplePagination.js/1.6/jquery.simplePagination.min.js"></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/jinplace/1.2.1/jinplace.min.js"></script>
|
||||
<nav style="text-align: center">
|
||||
<br>
|
||||
<div id="video_upload_pagination" style="display: inline-block"></div>
|
||||
<br>
|
||||
<span id="videos_per_page"
|
||||
data-ok-button="${_('Submit')}"
|
||||
data-cancel-button="${_('Cancel')}"
|
||||
data-data="${pagination_context['items_on_one_page']}"
|
||||
data-placeholder="${_('Changing..')}"
|
||||
data-activator="#edit-activator">
|
||||
${_('Videos per page:')} ${pagination_context['items_on_one_page']}
|
||||
</span>
|
||||
<button id="edit-activator" class="btn-default edit-button action-button">
|
||||
<span class="icon fa fa-pencil" aria-hidden="true"></span>
|
||||
<span class="action-button-text">${_("Change")}</span>
|
||||
</button>
|
||||
</nav>
|
||||
<script>
|
||||
$(function() {
|
||||
$('#video_upload_pagination').pagination({
|
||||
pages: "${pagination_context['total_pages']| n, dump_js_escaped_json}",
|
||||
currentPage:"${pagination_context['current_page']| n, dump_js_escaped_json}",
|
||||
cssStyle: 'light-theme',
|
||||
hrefTextPrefix:"?page=",
|
||||
});
|
||||
$('#videos_per_page').jinplace({
|
||||
}).on('jinplace:done',
|
||||
function() {
|
||||
window.location = window.location.pathname;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -49,7 +49,8 @@ argparse==1.4.0
|
||||
asn1crypto==0.24.0
|
||||
attrs==17.4.0
|
||||
babel==1.3
|
||||
beautifulsoup4==4.6.3 # via pynliner
|
||||
backports.functools-lru-cache==1.5 # via soupsieve
|
||||
beautifulsoup4==4.7.0 # via pynliner
|
||||
billiard==3.3.0.23 # via celery
|
||||
bleach==2.1.4
|
||||
boto3==1.4.8
|
||||
@@ -89,7 +90,7 @@ django-oauth-toolkit==1.1.3
|
||||
django-object-actions==0.10.0 # via edx-enterprise
|
||||
django-pyfs==2.0
|
||||
django-ratelimit-backend==1.1.1
|
||||
django-ratelimit==1.1.0
|
||||
django-ratelimit==2.0.0
|
||||
django-require==1.0.11
|
||||
django-rest-swagger==2.2.0
|
||||
django-sekizai==0.10.0
|
||||
@@ -109,7 +110,7 @@ docopt==0.6.2
|
||||
docutils==0.14 # via botocore
|
||||
dogapi==1.2.1
|
||||
edx-ace==0.1.10
|
||||
edx-analytics-data-api-client==0.14.4
|
||||
edx-analytics-data-api-client==0.15.2
|
||||
edx-ccx-keys==0.2.1
|
||||
edx-celeryutils==0.2.7
|
||||
edx-completion==1.0.1
|
||||
@@ -129,7 +130,7 @@ edx-rest-api-client==1.9.2
|
||||
edx-search==1.2.1
|
||||
edx-submissions==2.0.12
|
||||
edx-user-state-client==1.0.4
|
||||
edxval==0.1.23
|
||||
edxval==0.1.25
|
||||
elasticsearch==1.9.0 # via edx-search
|
||||
enum34==1.1.6
|
||||
event-tracking==0.2.7
|
||||
@@ -228,6 +229,7 @@ social-auth-app-django==2.1.0
|
||||
social-auth-core==1.7.0
|
||||
sorl-thumbnail==12.3
|
||||
sortedcontainers==0.9.2
|
||||
soupsieve==1.6.2 # via beautifulsoup4
|
||||
sqlparse==0.2.4
|
||||
stevedore==1.10.0
|
||||
sympy==0.7.1
|
||||
@@ -240,7 +242,7 @@ voluptuous==0.11.5
|
||||
watchdog==0.9.0
|
||||
web-fragments==0.2.2
|
||||
webencodings==0.5.1 # via html5lib
|
||||
webob==1.8.4 # via xblock
|
||||
webob==1.8.5 # via xblock
|
||||
wrapt==1.10.5
|
||||
xblock-review==1.1.5
|
||||
xblock-utils==1.2.0
|
||||
|
||||
@@ -57,7 +57,7 @@ atomicwrites==1.2.1
|
||||
attrs==17.4.0
|
||||
babel==1.3
|
||||
backports.functools-lru-cache==1.5
|
||||
beautifulsoup4==4.6.3
|
||||
beautifulsoup4==4.7.0
|
||||
before-after==1.0.1
|
||||
billiard==3.3.0.23
|
||||
bleach==2.1.4
|
||||
@@ -108,7 +108,7 @@ django-oauth-toolkit==1.1.3
|
||||
django-object-actions==0.10.0
|
||||
django-pyfs==2.0
|
||||
django-ratelimit-backend==1.1.1
|
||||
django-ratelimit==1.1.0
|
||||
django-ratelimit==2.0.0
|
||||
django-require==1.0.11
|
||||
django-rest-swagger==2.2.0
|
||||
django-sekizai==0.10.0
|
||||
@@ -128,7 +128,7 @@ docopt==0.6.2
|
||||
docutils==0.14
|
||||
dogapi==1.2.1
|
||||
edx-ace==0.1.10
|
||||
edx-analytics-data-api-client==0.14.4
|
||||
edx-analytics-data-api-client==0.15.2
|
||||
edx-ccx-keys==0.2.1
|
||||
edx-celeryutils==0.2.7
|
||||
edx-completion==1.0.1
|
||||
@@ -150,7 +150,7 @@ edx-search==1.2.1
|
||||
edx-sphinx-theme==1.4.0
|
||||
edx-submissions==2.0.12
|
||||
edx-user-state-client==1.0.4
|
||||
edxval==0.1.23
|
||||
edxval==0.1.25
|
||||
elasticsearch==1.9.0
|
||||
enum34==1.1.6
|
||||
event-tracking==0.2.7
|
||||
@@ -213,7 +213,7 @@ mccabe==0.6.1
|
||||
mock==1.0.1
|
||||
modernize==0.6.1
|
||||
mongoengine==0.10.0
|
||||
more-itertools==4.3.0
|
||||
more-itertools==5.0.0
|
||||
moto==0.3.1
|
||||
mysql-python==1.2.5
|
||||
networkx==1.7
|
||||
@@ -241,8 +241,8 @@ polib==1.1.0
|
||||
psutil==1.2.1
|
||||
py2neo==3.1.2
|
||||
py==1.7.0
|
||||
pyasn1-modules==0.2.2
|
||||
pyasn1==0.4.4
|
||||
pyasn1-modules==0.2.3
|
||||
pyasn1==0.4.5
|
||||
pycodestyle==2.4.0
|
||||
pycontracts==1.7.1
|
||||
pycountry==1.20
|
||||
@@ -314,6 +314,7 @@ social-auth-app-django==2.1.0
|
||||
social-auth-core==1.7.0
|
||||
sorl-thumbnail==12.3
|
||||
sortedcontainers==0.9.2
|
||||
soupsieve==1.6.2
|
||||
sphinx==1.8.3
|
||||
sphinxcontrib-websupport==1.1.0 # via sphinx
|
||||
splinter==0.9.0
|
||||
@@ -339,14 +340,14 @@ uritemplate==3.0.0
|
||||
urllib3==1.23
|
||||
urlobject==2.4.3
|
||||
user-util==0.1.5
|
||||
virtualenv==16.1.0
|
||||
virtualenv==16.2.0
|
||||
voluptuous==0.11.5
|
||||
vulture==1.0
|
||||
w3lib==1.19.0
|
||||
watchdog==0.9.0
|
||||
web-fragments==0.2.2
|
||||
webencodings==0.5.1
|
||||
webob==1.8.4
|
||||
webob==1.8.5
|
||||
werkzeug==0.14.1
|
||||
wrapt==1.10.5
|
||||
xblock-review==1.1.5
|
||||
|
||||
@@ -53,8 +53,8 @@ astroid==1.5.3 # via edx-lint, pylint, pylint-celery
|
||||
atomicwrites==1.2.1 # via pytest
|
||||
attrs==17.4.0
|
||||
babel==1.3
|
||||
backports.functools-lru-cache==1.5 # via astroid, pylint
|
||||
beautifulsoup4==4.6.3
|
||||
backports.functools-lru-cache==1.5
|
||||
beautifulsoup4==4.7.0
|
||||
before-after==1.0.1
|
||||
billiard==3.3.0.23
|
||||
bleach==2.1.4
|
||||
@@ -104,7 +104,7 @@ django-oauth-toolkit==1.1.3
|
||||
django-object-actions==0.10.0
|
||||
django-pyfs==2.0
|
||||
django-ratelimit-backend==1.1.1
|
||||
django-ratelimit==1.1.0
|
||||
django-ratelimit==2.0.0
|
||||
django-require==1.0.11
|
||||
django-rest-swagger==2.2.0
|
||||
django-sekizai==0.10.0
|
||||
@@ -123,7 +123,7 @@ docopt==0.6.2
|
||||
docutils==0.14
|
||||
dogapi==1.2.1
|
||||
edx-ace==0.1.10
|
||||
edx-analytics-data-api-client==0.14.4
|
||||
edx-analytics-data-api-client==0.15.2
|
||||
edx-ccx-keys==0.2.1
|
||||
edx-celeryutils==0.2.7
|
||||
edx-completion==1.0.1
|
||||
@@ -144,7 +144,7 @@ edx-rest-api-client==1.9.2
|
||||
edx-search==1.2.1
|
||||
edx-submissions==2.0.12
|
||||
edx-user-state-client==1.0.4
|
||||
edxval==0.1.23
|
||||
edxval==0.1.25
|
||||
elasticsearch==1.9.0
|
||||
enum34==1.1.6
|
||||
event-tracking==0.2.7
|
||||
@@ -205,7 +205,7 @@ markupsafe==1.1.0
|
||||
mccabe==0.6.1 # via flake8, pylint
|
||||
mock==1.0.1
|
||||
mongoengine==0.10.0
|
||||
more-itertools==4.3.0 # via pytest
|
||||
more-itertools==5.0.0 # via pytest
|
||||
moto==0.3.1
|
||||
mysql-python==1.2.5
|
||||
networkx==1.7
|
||||
@@ -231,8 +231,8 @@ polib==1.1.0
|
||||
psutil==1.2.1
|
||||
py2neo==3.1.2
|
||||
py==1.7.0 # via pytest, tox
|
||||
pyasn1-modules==0.2.2 # via service-identity
|
||||
pyasn1==0.4.4 # via pyasn1-modules, service-identity
|
||||
pyasn1-modules==0.2.3 # via service-identity
|
||||
pyasn1==0.4.5 # via pyasn1-modules, service-identity
|
||||
pycodestyle==2.4.0
|
||||
pycontracts==1.7.1
|
||||
pycountry==1.20
|
||||
@@ -301,6 +301,7 @@ social-auth-app-django==2.1.0
|
||||
social-auth-core==1.7.0
|
||||
sorl-thumbnail==12.3
|
||||
sortedcontainers==0.9.2
|
||||
soupsieve==1.6.2
|
||||
splinter==0.9.0
|
||||
sqlparse==0.2.4
|
||||
stevedore==1.10.0
|
||||
@@ -323,13 +324,13 @@ uritemplate==3.0.0
|
||||
urllib3==1.23
|
||||
urlobject==2.4.3 # via pa11ycrawler
|
||||
user-util==0.1.5
|
||||
virtualenv==16.1.0 # via tox
|
||||
virtualenv==16.2.0 # via tox
|
||||
voluptuous==0.11.5
|
||||
w3lib==1.19.0 # via parsel, scrapy
|
||||
watchdog==0.9.0
|
||||
web-fragments==0.2.2
|
||||
webencodings==0.5.1
|
||||
webob==1.8.4
|
||||
webob==1.8.5
|
||||
werkzeug==0.14.1 # via flask
|
||||
wrapt==1.10.5
|
||||
xblock-review==1.1.5
|
||||
|
||||
Reference in New Issue
Block a user