Merge pull request #17599 from edx/fsheets/EDUCATOR-2324
Clean up old Studio Assets Page
This commit is contained in:
@@ -1,90 +0,0 @@
|
||||
"""
|
||||
Tests for the models that control the
|
||||
persistent grading feature.
|
||||
"""
|
||||
import itertools
|
||||
|
||||
import ddt
|
||||
from django.test import TestCase
|
||||
from opaque_keys.edx.locator import CourseLocator
|
||||
|
||||
from contentstore.config.models import NewAssetsPageFlag
|
||||
from contentstore.config.tests.utils import new_assets_page_feature_flags
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class NewAssetsPageFlagTests(TestCase):
|
||||
"""
|
||||
Tests the behavior of the feature flags for the new assets page.
|
||||
These are set via Django admin settings.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(NewAssetsPageFlagTests, self).setUp()
|
||||
self.course_id_1 = CourseLocator(org="edx", course="course", run="run")
|
||||
self.course_id_2 = CourseLocator(org="edx", course="course2", run="run")
|
||||
|
||||
@ddt.data(*itertools.product(
|
||||
(True, False),
|
||||
(True, False),
|
||||
(True, False),
|
||||
))
|
||||
@ddt.unpack
|
||||
def test_new_assets_page_feature_flags(self, global_flag, enabled_for_all_courses, enabled_for_course_1):
|
||||
with new_assets_page_feature_flags(
|
||||
global_flag=global_flag,
|
||||
enabled_for_all_courses=enabled_for_all_courses,
|
||||
course_id=self.course_id_1,
|
||||
enabled_for_course=enabled_for_course_1
|
||||
):
|
||||
self.assertEqual(NewAssetsPageFlag.feature_enabled(), global_flag and enabled_for_all_courses)
|
||||
self.assertEqual(
|
||||
NewAssetsPageFlag.feature_enabled(self.course_id_1),
|
||||
global_flag and (enabled_for_all_courses or enabled_for_course_1)
|
||||
)
|
||||
self.assertEqual(
|
||||
NewAssetsPageFlag.feature_enabled(self.course_id_2),
|
||||
global_flag and enabled_for_all_courses
|
||||
)
|
||||
|
||||
def test_enable_disable_course_flag(self):
|
||||
"""
|
||||
Ensures that the flag, once enabled for a course, can also be disabled.
|
||||
"""
|
||||
with new_assets_page_feature_flags(
|
||||
global_flag=True,
|
||||
enabled_for_all_courses=False,
|
||||
course_id=self.course_id_1,
|
||||
enabled_for_course=True
|
||||
):
|
||||
self.assertTrue(NewAssetsPageFlag.feature_enabled(self.course_id_1))
|
||||
with new_assets_page_feature_flags(
|
||||
global_flag=True,
|
||||
enabled_for_all_courses=False,
|
||||
course_id=self.course_id_1,
|
||||
enabled_for_course=False
|
||||
):
|
||||
self.assertFalse(NewAssetsPageFlag.feature_enabled(self.course_id_1))
|
||||
|
||||
def test_enable_disable_globally(self):
|
||||
"""
|
||||
Ensures that the flag, once enabled globally, can also be disabled.
|
||||
"""
|
||||
with new_assets_page_feature_flags(
|
||||
global_flag=True,
|
||||
enabled_for_all_courses=True,
|
||||
):
|
||||
self.assertTrue(NewAssetsPageFlag.feature_enabled())
|
||||
self.assertTrue(NewAssetsPageFlag.feature_enabled(self.course_id_1))
|
||||
with new_assets_page_feature_flags(
|
||||
global_flag=True,
|
||||
enabled_for_all_courses=False,
|
||||
course_id=self.course_id_1,
|
||||
enabled_for_course=True
|
||||
):
|
||||
self.assertFalse(NewAssetsPageFlag.feature_enabled())
|
||||
self.assertTrue(NewAssetsPageFlag.feature_enabled(self.course_id_1))
|
||||
with new_assets_page_feature_flags(
|
||||
global_flag=False,
|
||||
):
|
||||
self.assertFalse(NewAssetsPageFlag.feature_enabled())
|
||||
self.assertFalse(NewAssetsPageFlag.feature_enabled(self.course_id_1))
|
||||
@@ -1,27 +0,0 @@
|
||||
"""
|
||||
Provides helper functions for tests that want
|
||||
to configure flags related to persistent grading.
|
||||
"""
|
||||
from contextlib import contextmanager
|
||||
|
||||
from contentstore.config.models import NewAssetsPageFlag, CourseNewAssetsPageFlag
|
||||
from openedx.core.djangoapps.request_cache.middleware import RequestCache
|
||||
|
||||
|
||||
@contextmanager
|
||||
def new_assets_page_feature_flags(
|
||||
global_flag,
|
||||
enabled_for_all_courses=False,
|
||||
course_id=None,
|
||||
enabled_for_course=False
|
||||
):
|
||||
"""
|
||||
Most test cases will use a single call to this manager,
|
||||
as they need to set the global setting and the course-specific
|
||||
setting for a single course.
|
||||
"""
|
||||
RequestCache.clear_request_cache()
|
||||
NewAssetsPageFlag.objects.create(enabled=global_flag, enabled_for_all_courses=enabled_for_all_courses)
|
||||
if course_id:
|
||||
CourseNewAssetsPageFlag.objects.create(course_id=course_id, enabled=enabled_for_course)
|
||||
yield
|
||||
@@ -1,84 +0,0 @@
|
||||
@shard_1 @requires_stub_youtube
|
||||
Feature: CMS Transcripts
|
||||
As a course author, I want to be able to create video components
|
||||
|
||||
# For transcripts acceptance tests there are 3 available caption
|
||||
# files. They can be used to test various transcripts features. Two of
|
||||
# them can be imported from YouTube.
|
||||
#
|
||||
# The length of each file name is 11 characters. This is because the
|
||||
# YouTube's ID length is 11 characters. If file name is not of length 11,
|
||||
# front-end validation will not pass.
|
||||
#
|
||||
# t__eq_exist - this file exists on YouTube, and can be imported
|
||||
# via the transcripts menu; after import, this file will
|
||||
# be equal to the one stored locally
|
||||
# t_neq_exist - same as above, except local file will differ from the
|
||||
# one stored on YouTube
|
||||
# t_not_exist - this file does not exist on YouTube; it exists locally
|
||||
|
||||
#3
|
||||
Scenario: Youtube id only: check "not found" and "import" states
|
||||
Given I have created a Video component with subtitles
|
||||
And I edit the component
|
||||
|
||||
# Not found: w/o local or server subs
|
||||
And I remove "t_not_exist" transcripts id from store
|
||||
And I enter a "http://youtu.be/t_not_exist" source to field number 1
|
||||
Then I see status message "not found"
|
||||
And I see value "" in the field "Default Timed Transcript"
|
||||
|
||||
# Import: w/o local but with server subs
|
||||
And I remove "t__eq_exist" transcripts id from store
|
||||
And I enter a "http://youtu.be/t__eq_exist" source to field number 1
|
||||
Then I see status message "not found on edx"
|
||||
And I see button "import"
|
||||
And I click transcript button "import"
|
||||
Then I see status message "found"
|
||||
And I see button "upload_new_timed_transcripts"
|
||||
And I see button "download_to_edit"
|
||||
And I see value "t__eq_exist" in the field "Default Timed Transcript"
|
||||
|
||||
|
||||
# Disabled 1/29/14 due to flakiness observed in master
|
||||
#10
|
||||
#Scenario: User sets youtube_id w/o local but with server subs and one html5 link w/o subs
|
||||
# Given I have created a Video component
|
||||
# And I edit the component
|
||||
#
|
||||
# And I enter a "http://youtu.be/t__eq_exist" source to field number 1
|
||||
# Then I see status message "not found on edx"
|
||||
# And I see button "import"
|
||||
# And I click transcript button "import"
|
||||
# Then I see status message "found"
|
||||
#
|
||||
# And I enter a "t_not_exist.mp4" source to field number 2
|
||||
# Then I see status message "found"
|
||||
# And I see value "t__eq_exist" in the field "Default Timed Transcript"
|
||||
|
||||
# Flaky test fails occasionally in master. https://openedx.atlassian.net/browse/BLD-892
|
||||
#21
|
||||
#Scenario: Work with 1 field only: Enter HTML5 source with transcripts - save - > change it to another one HTML5 source w/o #transcripts - click on use existing - > change it to another one HTML5 source w/o transcripts - click on use existing
|
||||
# Given I have created a Video component with subtitles "t_not_exist"
|
||||
# And I edit the component
|
||||
#
|
||||
# And I enter a "t_not_exist.mp4" source to field number 1
|
||||
# Then I see status message "found"
|
||||
# And I see button "download_to_edit"
|
||||
# And I see button "upload_new_timed_transcripts"
|
||||
# And I see value "t_not_exist" in the field "Default Timed Transcript"
|
||||
#
|
||||
# And I save changes
|
||||
# And I edit the component
|
||||
#
|
||||
# And I enter a "video_name_2.mp4" source to field number 1
|
||||
# Then I see status message "use existing"
|
||||
# And I see button "use_existing"
|
||||
# And I click transcript button "use_existing"
|
||||
# And I see value "video_name_2" in the field "Default Timed Transcript"
|
||||
#
|
||||
# And I enter a "video_name_3.mp4" source to field number 1
|
||||
# Then I see status message "use existing"
|
||||
# And I see button "use_existing"
|
||||
# And I click transcript button "use_existing"
|
||||
# And I see value "video_name_3" in the field "Default Timed Transcript"
|
||||
@@ -1,261 +0,0 @@
|
||||
# disable missing docstring
|
||||
# pylint: disable=missing-docstring
|
||||
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from lettuce import step, world
|
||||
from splinter.request_handler.request_handler import RequestHandler
|
||||
|
||||
from xmodule.contentstore.content import StaticContent
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.exceptions import NotFoundError
|
||||
|
||||
TEST_ROOT = settings.COMMON_TEST_DATA_ROOT
|
||||
|
||||
# We should wait 300 ms for event handler invocation + 200ms for safety.
|
||||
DELAY = 0.5
|
||||
|
||||
ERROR_MESSAGES = {
|
||||
'url_format': u'Incorrect url format.',
|
||||
'file_type': u'Link types should be unique.',
|
||||
'links_duplication': u'Links should be unique.',
|
||||
}
|
||||
|
||||
STATUSES = {
|
||||
'found': u'Timed Transcript Found',
|
||||
'not found on edx': u'No EdX Timed Transcript',
|
||||
'not found': u'No Timed Transcript',
|
||||
'replace': u'Timed Transcript Conflict',
|
||||
'uploaded_successfully': u'Timed Transcript Uploaded Successfully',
|
||||
'use existing': u'Confirm Timed Transcript',
|
||||
}
|
||||
|
||||
SELECTORS = {
|
||||
'error_bar': '.transcripts-error-message',
|
||||
'url_inputs': '.videolist-settings-item input.input',
|
||||
'collapse_link': '.collapse-action.collapse-setting',
|
||||
'collapse_bar': '.videolist-extra-videos',
|
||||
'status_bar': '.transcripts-message-status',
|
||||
}
|
||||
|
||||
# button type , button css selector, button message
|
||||
TRANSCRIPTS_BUTTONS = {
|
||||
'import': ('.setting-import', 'Import YouTube Transcript'),
|
||||
'download_to_edit': ('.setting-download', 'Download Transcript for Editing'),
|
||||
'disabled_download_to_edit': ('.setting-download.is-disabled', 'Download Transcript for Editing'),
|
||||
'upload_new_timed_transcripts': ('.setting-upload', 'Upload New Transcript'),
|
||||
'replace': ('.setting-replace', 'Yes, replace the edX transcript with the YouTube transcript'),
|
||||
'choose': ('.setting-choose', 'Timed Transcript from {}'),
|
||||
'use_existing': ('.setting-use-existing', 'Use Current Transcript'),
|
||||
}
|
||||
|
||||
|
||||
@step('I clear fields$')
|
||||
def clear_fields(_step):
|
||||
|
||||
# Clear the input fields and trigger an 'input' event
|
||||
script = """
|
||||
$('{selector}')
|
||||
.prop('disabled', false)
|
||||
.removeClass('is-disabled')
|
||||
.attr('aria-disabled', false)
|
||||
.val('')
|
||||
.trigger('input');
|
||||
""".format(selector=SELECTORS['url_inputs'])
|
||||
world.browser.execute_script(script)
|
||||
|
||||
world.wait(DELAY)
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
|
||||
@step('I clear field number (.+)$')
|
||||
def clear_field(_step, index):
|
||||
index = int(index) - 1
|
||||
world.css_fill(SELECTORS['url_inputs'], '', index)
|
||||
|
||||
# For some reason ChromeDriver doesn't trigger an 'input' event after filling
|
||||
# the field with an empty value. That's why we trigger it manually via jQuery.
|
||||
world.trigger_event(SELECTORS['url_inputs'], event='input', index=index)
|
||||
|
||||
world.wait(DELAY)
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
|
||||
@step('I expect (.+) inputs are disabled$')
|
||||
def inputs_are_disabled(_step, indexes):
|
||||
index_list = [int(i.strip()) - 1 for i in indexes.split(',')]
|
||||
for index in index_list:
|
||||
el = world.css_find(SELECTORS['url_inputs'])[index]
|
||||
|
||||
assert el['disabled']
|
||||
|
||||
|
||||
@step('I expect inputs are enabled$')
|
||||
def inputs_are_enabled(_step):
|
||||
for index in range(3):
|
||||
el = world.css_find(SELECTORS['url_inputs'])[index]
|
||||
|
||||
assert not el['disabled']
|
||||
|
||||
|
||||
@step('I do not see error message$')
|
||||
def i_do_not_see_error_message(_step):
|
||||
assert not world.css_visible(SELECTORS['error_bar'])
|
||||
|
||||
|
||||
@step('I see error message "([^"]*)"$')
|
||||
def i_see_error_message(_step, error):
|
||||
assert world.css_has_text(SELECTORS['error_bar'], ERROR_MESSAGES[error])
|
||||
|
||||
|
||||
@step('I do not see status message$')
|
||||
def i_do_not_see_status_message(_step):
|
||||
assert not world.css_visible(SELECTORS['status_bar'])
|
||||
|
||||
|
||||
@step('I see status message "([^"]*)"$')
|
||||
def i_see_status_message(_step, status):
|
||||
assert not world.css_visible(SELECTORS['error_bar'])
|
||||
assert world.css_has_text(SELECTORS['status_bar'], STATUSES[status])
|
||||
|
||||
DOWNLOAD_BUTTON = TRANSCRIPTS_BUTTONS["download_to_edit"][0]
|
||||
if world.is_css_present(DOWNLOAD_BUTTON, wait_time=1) and not world.css_find(DOWNLOAD_BUTTON)[0].has_class('is-disabled'):
|
||||
assert _transcripts_are_downloaded()
|
||||
|
||||
|
||||
@step('I (.*)see button "([^"]*)"$')
|
||||
def i_see_button(_step, not_see, button_type):
|
||||
button = button_type.strip()
|
||||
|
||||
if not_see.strip():
|
||||
assert world.is_css_not_present(TRANSCRIPTS_BUTTONS[button][0])
|
||||
else:
|
||||
assert world.css_has_text(TRANSCRIPTS_BUTTONS[button][0], TRANSCRIPTS_BUTTONS[button][1])
|
||||
|
||||
|
||||
@step(r'I (.*)see (.*)button "([^"]*)" number (\d+)$')
|
||||
def i_see_button_with_custom_text(_step, not_see, button_type, custom_text, index):
|
||||
button = button_type.strip()
|
||||
custom_text = custom_text.strip()
|
||||
index = int(index.strip()) - 1
|
||||
|
||||
if not_see.strip():
|
||||
assert world.is_css_not_present(TRANSCRIPTS_BUTTONS[button][0])
|
||||
else:
|
||||
assert world.css_has_text(TRANSCRIPTS_BUTTONS[button][0], TRANSCRIPTS_BUTTONS[button][1].format(custom_text), index)
|
||||
|
||||
|
||||
@step('I click transcript button "([^"]*)"$')
|
||||
def click_button_transcripts_variant(_step, button_type):
|
||||
button = button_type.strip()
|
||||
world.css_click(TRANSCRIPTS_BUTTONS[button][0])
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
|
||||
@step(r'I click transcript button "([^"]*)" number (\d+)$')
|
||||
def click_button_index(_step, button_type, index):
|
||||
button = button_type.strip()
|
||||
index = int(index.strip()) - 1
|
||||
|
||||
world.css_click(TRANSCRIPTS_BUTTONS[button][0], index)
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
|
||||
@step('I remove "([^"]+)" transcripts id from store')
|
||||
def remove_transcripts_from_store(_step, subs_id):
|
||||
"""Remove from store, if transcripts content exists."""
|
||||
filename = 'subs_{0}.srt.sjson'.format(subs_id.strip())
|
||||
content_location = StaticContent.compute_location(
|
||||
world.scenario_dict['COURSE'].id,
|
||||
filename
|
||||
)
|
||||
try:
|
||||
content = contentstore().find(content_location)
|
||||
contentstore().delete(content.location)
|
||||
print 'Transcript file was removed from store.'
|
||||
except NotFoundError:
|
||||
print 'Transcript file was NOT found and not removed.'
|
||||
|
||||
|
||||
@step(r'I enter a "([^"]+)" source to field number (\d+)$')
|
||||
def i_enter_a_source(_step, link, index):
|
||||
index = int(index) - 1
|
||||
|
||||
if index is not 0 and not world.css_visible(SELECTORS['collapse_bar']):
|
||||
world.css_click(SELECTORS['collapse_link'])
|
||||
|
||||
assert world.css_visible(SELECTORS['collapse_bar'])
|
||||
|
||||
world.css_fill(SELECTORS['url_inputs'], link, index)
|
||||
world.wait(DELAY)
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
|
||||
@step('I upload the transcripts file "([^"]*)"$')
|
||||
def upload_file(_step, file_name):
|
||||
path = os.path.join(TEST_ROOT, 'uploads/', file_name.strip())
|
||||
world.browser.execute_script("$('form.file-chooser').show()")
|
||||
world.browser.attach_file('transcript-file', os.path.abspath(path))
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
|
||||
@step('I see "([^"]*)" text in the captions')
|
||||
def check_text_in_the_captions(_step, text):
|
||||
world.wait_for_present('.video.is-captions-rendered')
|
||||
world.wait_for(lambda _: world.css_text('.subtitles'), timeout=30)
|
||||
actual_text = world.css_text('.subtitles')
|
||||
assert text in actual_text
|
||||
|
||||
|
||||
@step('I see value "([^"]*)" in the field "([^"]*)"$')
|
||||
def check_transcripts_field(_step, values, field_name):
|
||||
world.select_editor_tab('Advanced')
|
||||
tab = world.css_find('#settings-tab').first
|
||||
field_id = '#' + tab.find_by_xpath('.//label[text()="%s"]' % field_name.strip())[0]['for']
|
||||
values_list = [i.strip() == world.css_value(field_id) for i in values.split('|')]
|
||||
assert any(values_list)
|
||||
world.select_editor_tab('Basic')
|
||||
|
||||
|
||||
@step('I save changes$')
|
||||
def save_changes(_step):
|
||||
world.save_component()
|
||||
|
||||
|
||||
@step('I open tab "([^"]*)"$')
|
||||
def open_tab(_step, tab_name):
|
||||
world.select_editor_tab(tab_name)
|
||||
|
||||
|
||||
@step('I set value "([^"]*)" to the field "([^"]*)"$')
|
||||
def set_value_transcripts_field(_step, value, field_name):
|
||||
tab = world.css_find('#settings-tab').first
|
||||
XPATH = './/label[text()="{name}"]'.format(name=field_name)
|
||||
SELECTOR = '#' + tab.find_by_xpath(XPATH)[0]['for']
|
||||
element = world.css_find(SELECTOR).first
|
||||
if element['type'] == 'text':
|
||||
SCRIPT = '$("{selector}").val("{value}").change()'.format(
|
||||
selector=SELECTOR,
|
||||
value=value
|
||||
)
|
||||
world.browser.execute_script(SCRIPT)
|
||||
assert world.css_has_value(SELECTOR, value)
|
||||
else:
|
||||
assert False, 'Incorrect element type.'
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
|
||||
@step('I revert the transcript field "([^"]*)"$')
|
||||
def revert_transcripts_field(_step, field_name):
|
||||
world.revert_setting_entry(field_name)
|
||||
|
||||
|
||||
def _transcripts_are_downloaded():
|
||||
world.wait_for_ajax_complete()
|
||||
request = RequestHandler()
|
||||
DOWNLOAD_BUTTON = world.css_find(TRANSCRIPTS_BUTTONS["download_to_edit"][0]).first
|
||||
url = DOWNLOAD_BUTTON['href']
|
||||
request.connect(url)
|
||||
|
||||
return request.status_code.is_success()
|
||||
@@ -1,118 +0,0 @@
|
||||
@shard_2
|
||||
Feature: CMS.Upload Files
|
||||
As a course author, I want to be able to upload files for my students
|
||||
|
||||
# Uploading isn't working on safari with sauce labs
|
||||
@skip_safari
|
||||
Scenario: Users can upload files
|
||||
Given I am at the files and upload page of a Studio course
|
||||
When I upload the file "test" by clicking "Upload your first asset"
|
||||
Then I should see the file "test" was uploaded
|
||||
And The url for the file "test" is valid
|
||||
When I upload the file "test2"
|
||||
Then I should see the file "test2" was uploaded
|
||||
And The url for the file "test2" is valid
|
||||
|
||||
# Uploading isn't working on safari with sauce labs
|
||||
@skip_safari
|
||||
Scenario: Users can upload multiple files
|
||||
Given I am at the files and upload page of a Studio course
|
||||
When I upload the files "test,test2"
|
||||
Then I should see the file "test" was uploaded
|
||||
And I should see the file "test2" was uploaded
|
||||
And The url for the file "test2" is valid
|
||||
And The url for the file "test" is valid
|
||||
|
||||
# Uploading isn't working on safari with sauce labs
|
||||
@skip_safari
|
||||
Scenario: Users can update files
|
||||
Given I am at the files and upload page of a Studio course
|
||||
When I upload the file "test"
|
||||
And I upload the file "test"
|
||||
Then I should see only one "test"
|
||||
|
||||
# Uploading isn't working on safari with sauce labs
|
||||
@skip_safari
|
||||
Scenario: Users can delete uploaded files
|
||||
Given I am at the files and upload page of a Studio course
|
||||
When I upload the file "test"
|
||||
And I delete the file "test"
|
||||
Then I should not see the file "test" was uploaded
|
||||
And I see a confirmation that the file was deleted
|
||||
|
||||
# Uploading isn't working on safari with sauce labs
|
||||
@skip_safari
|
||||
Scenario: Users can download files
|
||||
Given I am at the files and upload page of a Studio course
|
||||
When I upload the file "test"
|
||||
Then I can download the correct "test" file
|
||||
|
||||
# Uploading isn't working on safari with sauce labs
|
||||
@skip_safari
|
||||
Scenario: Users can download updated files
|
||||
Given I am at the files and upload page of a Studio course
|
||||
When I upload the file "test"
|
||||
And I modify "test"
|
||||
And I reload the page
|
||||
And I upload the file "test"
|
||||
Then I can download the correct "test" file
|
||||
|
||||
# Uploading isn't working on safari with sauce labs
|
||||
@skip_safari
|
||||
Scenario: Users can lock assets through asset index
|
||||
Given I am at the files and upload page of a Studio course
|
||||
When I upload an asset
|
||||
And I lock the asset
|
||||
Then the asset is locked
|
||||
And I see a "saving" notification
|
||||
And I reload the page
|
||||
Then the asset is locked
|
||||
|
||||
# Uploading isn't working on safari with sauce labs
|
||||
@skip_safari
|
||||
Scenario: Users can unlock assets through asset index
|
||||
Given I have created a course with a locked asset
|
||||
When I unlock the asset
|
||||
Then the asset is unlocked
|
||||
And I see a "saving" notification
|
||||
And I reload the page
|
||||
Then the asset is unlocked
|
||||
|
||||
# Uploading isn't working on safari with sauce labs
|
||||
@skip_safari
|
||||
Scenario: Locked assets can't be viewed if logged in as an unregistered user
|
||||
Given I have created a course with a locked asset
|
||||
And the user "bob" exists
|
||||
When "bob" logs in
|
||||
Then the asset is protected
|
||||
|
||||
# Uploading isn't working on safari with sauce labs
|
||||
@skip_safari
|
||||
Scenario: Locked assets can be viewed if logged in as a registered user
|
||||
Given I have created a course with a locked asset
|
||||
And the user "bob" exists
|
||||
And the user "bob" is enrolled in the course
|
||||
When "bob" logs in
|
||||
Then the asset is viewable
|
||||
|
||||
# Uploading isn't working on safari with sauce labs
|
||||
@skip_safari
|
||||
Scenario: Locked assets can't be viewed if logged out
|
||||
Given I have created a course with a locked asset
|
||||
When I log out
|
||||
Then the asset is protected
|
||||
|
||||
# Uploading isn't working on safari with sauce labs
|
||||
@skip_safari
|
||||
Scenario: Locked assets can be viewed with is_staff account
|
||||
Given I have created a course with a locked asset
|
||||
And the user "staff" exists as a course is_staff
|
||||
When "staff" logs in
|
||||
Then the asset is viewable
|
||||
|
||||
# Uploading isn't working on safari with sauce labs
|
||||
@skip_safari
|
||||
Scenario: Unlocked assets can be viewed by anyone
|
||||
Given I have created a course with a unlocked asset
|
||||
When I log out
|
||||
Then the asset is viewable
|
||||
@@ -1,230 +0,0 @@
|
||||
# pylint: disable=missing-docstring
|
||||
# pylint: disable=redefined-outer-name
|
||||
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from lettuce import step, world
|
||||
from lettuce.django import django_url
|
||||
from nose.tools import assert_equal, assert_not_equal
|
||||
|
||||
from student.models import CourseEnrollment
|
||||
|
||||
TEST_ROOT = settings.COMMON_TEST_DATA_ROOT
|
||||
ASSET_NAMES_CSS = 'td.name-col > span.title > a.filename'
|
||||
|
||||
|
||||
@step(u'I go to the files and uploads page$')
|
||||
def go_to_uploads(_step):
|
||||
menu_css = 'li.nav-course-courseware'
|
||||
uploads_css = 'li.nav-course-courseware-uploads a'
|
||||
world.css_click(menu_css)
|
||||
world.css_click(uploads_css)
|
||||
|
||||
|
||||
@step(u'I upload the( test)? file "([^"]*)"$')
|
||||
def upload_file(_step, is_test_file, file_name, button_text=None):
|
||||
if button_text:
|
||||
world.click_link(button_text)
|
||||
else:
|
||||
world.click_link('Upload New File')
|
||||
|
||||
if not is_test_file:
|
||||
_write_test_file(file_name, "test file")
|
||||
|
||||
# uploading the file itself
|
||||
path = os.path.join(TEST_ROOT, 'uploads/', file_name)
|
||||
world.browser.execute_script("$('input.file-input').css('display', 'block')")
|
||||
world.browser.attach_file('file', os.path.abspath(path))
|
||||
close_css = 'a.close-button'
|
||||
world.css_click(close_css)
|
||||
|
||||
|
||||
@step(u'I upload the file "([^"]*)" by clicking "([^"]*)"')
|
||||
def upload_file_on_button_press(_step, file_name, button_text=None):
|
||||
upload_file(_step, '', file_name, button_text)
|
||||
|
||||
|
||||
@step(u'I upload the files "([^"]*)"$')
|
||||
def upload_files(_step, files_string):
|
||||
# files_string should be comma separated with no spaces.
|
||||
files = files_string.split(",")
|
||||
upload_css = 'a.upload-button'
|
||||
world.css_click(upload_css)
|
||||
|
||||
# uploading the files
|
||||
for filename in files:
|
||||
_write_test_file(filename, "test file")
|
||||
path = os.path.join(TEST_ROOT, 'uploads/', filename)
|
||||
world.browser.execute_script("$('input.file-input').css('display', 'block')")
|
||||
world.browser.attach_file('file', os.path.abspath(path))
|
||||
|
||||
close_css = 'a.close-button'
|
||||
world.css_click(close_css)
|
||||
|
||||
|
||||
@step(u'I should not see the file "([^"]*)" was uploaded$')
|
||||
def check_not_there(_step, file_name):
|
||||
# Either there are no files, or there are files but
|
||||
# not the one I expect not to exist.
|
||||
|
||||
# Since our only test for deletion right now deletes
|
||||
# the only file that was uploaded, our success criteria
|
||||
# will be that there are no files.
|
||||
# In the future we can refactor if necessary.
|
||||
assert world.is_css_not_present(ASSET_NAMES_CSS)
|
||||
|
||||
|
||||
@step(u'I should see the file "([^"]*)" was uploaded$')
|
||||
def check_upload(_step, file_name):
|
||||
index = get_index(file_name)
|
||||
assert_not_equal(index, -1)
|
||||
|
||||
|
||||
@step(u'The url for the file "([^"]*)" is valid$')
|
||||
def check_url(_step, file_name):
|
||||
r = get_file(file_name)
|
||||
assert_equal(r.status_code, 200)
|
||||
|
||||
|
||||
@step(u'I delete the file "([^"]*)"$')
|
||||
def delete_file(_step, file_name):
|
||||
index = get_index(file_name)
|
||||
assert index != -1
|
||||
delete_css = "a.remove-asset-button"
|
||||
world.css_click(delete_css, index=index)
|
||||
world.confirm_studio_prompt()
|
||||
|
||||
|
||||
@step(u'I should see only one "([^"]*)"$')
|
||||
def no_duplicate(_step, file_name):
|
||||
all_names = world.css_find(ASSET_NAMES_CSS)
|
||||
only_one = False
|
||||
for i in range(len(all_names)):
|
||||
if file_name == world.css_html(ASSET_NAMES_CSS, index=i):
|
||||
only_one = not only_one
|
||||
assert only_one
|
||||
|
||||
|
||||
@step(u'I can download the correct "([^"]*)" file$')
|
||||
def check_download(_step, file_name):
|
||||
path = os.path.join(TEST_ROOT, 'uploads/', file_name)
|
||||
with open(os.path.abspath(path), 'r') as cur_file:
|
||||
cur_text = cur_file.read()
|
||||
r = get_file(file_name)
|
||||
downloaded_text = r.text
|
||||
assert cur_text == downloaded_text
|
||||
# resetting the file back to its original state
|
||||
_write_test_file(file_name, "This is an arbitrary file for testing uploads")
|
||||
|
||||
|
||||
def _write_test_file(file_name, text):
|
||||
path = os.path.join(TEST_ROOT, 'uploads/', file_name)
|
||||
# resetting the file back to its original state
|
||||
with open(os.path.abspath(path), 'w') as cur_file:
|
||||
cur_file.write(text)
|
||||
|
||||
|
||||
@step(u'I modify "([^"]*)"$')
|
||||
def modify_upload(_step, file_name):
|
||||
new_text = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(10))
|
||||
_write_test_file(file_name, new_text)
|
||||
|
||||
|
||||
@step(u'I upload an asset$')
|
||||
def upload_an_asset(step):
|
||||
step.given('I upload the file "asset.html"')
|
||||
|
||||
|
||||
@step(u'I (lock|unlock) the asset$')
|
||||
def lock_unlock_file(_step, _lock_state):
|
||||
index = get_index('asset.html')
|
||||
assert index != -1, 'Expected to find an asset but could not.'
|
||||
|
||||
# Warning: this is a misnomer, it really only toggles the
|
||||
# lock state. TODO: fix it.
|
||||
lock_css = "input.lock-checkbox"
|
||||
world.css_find(lock_css)[index].click()
|
||||
|
||||
|
||||
@step(u'the user "([^"]*)" is enrolled in the course$')
|
||||
def user_foo_is_enrolled_in_the_course(step, name):
|
||||
world.create_user(name, 'test')
|
||||
user = User.objects.get(username=name)
|
||||
|
||||
course_id = world.scenario_dict['COURSE'].id
|
||||
CourseEnrollment.enroll(user, course_id)
|
||||
|
||||
|
||||
@step(u'Then the asset is (locked|unlocked)$')
|
||||
def verify_lock_unlock_file(_step, lock_state):
|
||||
index = get_index('asset.html')
|
||||
assert index != -1, 'Expected to find an asset but could not.'
|
||||
lock_css = "input.lock-checkbox"
|
||||
checked = world.css_find(lock_css)[index]._element.get_attribute('checked')
|
||||
assert_equal(lock_state == "locked", bool(checked))
|
||||
|
||||
|
||||
@step(u'I am at the files and upload page of a Studio course')
|
||||
def at_upload_page(step):
|
||||
step.given('I have opened a new course in studio')
|
||||
step.given('I go to the files and uploads page')
|
||||
|
||||
|
||||
@step(u'I have created a course with a (locked|unlocked) asset$')
|
||||
def open_course_with_locked(step, lock_state):
|
||||
step.given('I am at the files and upload page of a Studio course')
|
||||
step.given('I upload the file "asset.html"')
|
||||
|
||||
if lock_state == "locked":
|
||||
step.given('I lock the asset')
|
||||
step.given('I reload the page')
|
||||
|
||||
|
||||
@step(u'Then the asset is (viewable|protected)$')
|
||||
def view_asset(_step, status):
|
||||
asset_loc = world.scenario_dict['COURSE'].id.make_asset_key(asset_type='asset', path='asset.html')
|
||||
svr_loc = django_url()
|
||||
asset_url = unicode(asset_loc)
|
||||
divider = '/'
|
||||
if asset_url[0] == '/':
|
||||
divider = ''
|
||||
url = '{}{}{}'.format(svr_loc, divider, asset_url)
|
||||
if status == 'viewable':
|
||||
expected_text = 'test file'
|
||||
else:
|
||||
expected_text = 'Unauthorized'
|
||||
|
||||
# Note that world.visit would trigger a 403 error instead of displaying "Unauthorized"
|
||||
# Instead, we can drop back into the selenium driver get command.
|
||||
world.browser.driver.get(url)
|
||||
assert_equal(world.css_text('body'), expected_text)
|
||||
|
||||
|
||||
@step('I see a confirmation that the file was deleted$')
|
||||
def i_see_a_delete_confirmation(_step):
|
||||
alert_css = '#notification-confirmation'
|
||||
assert world.is_css_present(alert_css)
|
||||
|
||||
|
||||
def get_index(file_name):
|
||||
all_names = world.css_find(ASSET_NAMES_CSS)
|
||||
for i in range(len(all_names)):
|
||||
if file_name == world.css_html(ASSET_NAMES_CSS, index=i):
|
||||
return i
|
||||
return -1
|
||||
|
||||
|
||||
def get_file(file_name):
|
||||
index = get_index(file_name)
|
||||
assert index != -1
|
||||
url_css = 'a.filename'
|
||||
|
||||
def get_url():
|
||||
return world.css_find(url_css)[index]._element.get_attribute('href')
|
||||
url = world.retry_on_exception(get_url)
|
||||
return requests.get(url)
|
||||
@@ -1,67 +0,0 @@
|
||||
# pylint: disable=missing-docstring
|
||||
|
||||
from lettuce import step, world
|
||||
|
||||
SELECTORS = {
|
||||
'spinner': '.video-wrapper .spinner',
|
||||
'controls': '.video-controls',
|
||||
}
|
||||
|
||||
# We should wait 300 ms for event handler invocation + 200ms for safety.
|
||||
DELAY = 0.5
|
||||
|
||||
|
||||
@step('I have uploaded subtitles "([^"]*)"$')
|
||||
def i_have_uploaded_subtitles(_step, sub_id):
|
||||
_step.given('I go to the files and uploads page')
|
||||
_step.given('I upload the test file "subs_{}.srt.sjson"'.format(sub_id.strip()))
|
||||
|
||||
|
||||
@step('I have created a Video component$')
|
||||
def i_created_a_video_component(step):
|
||||
step.given('I am in Studio editing a new unit')
|
||||
world.create_component_instance(
|
||||
step=step,
|
||||
category='video',
|
||||
)
|
||||
|
||||
world.wait_for_xmodule()
|
||||
world.disable_jquery_animations()
|
||||
|
||||
world.wait_for_present('.is-initialized')
|
||||
world.wait(DELAY)
|
||||
world.wait_for_invisible(SELECTORS['spinner'])
|
||||
if not world.youtube.config.get('youtube_api_blocked'):
|
||||
world.wait_for_visible(SELECTORS['controls'])
|
||||
|
||||
|
||||
@step('I have created a Video component with subtitles$')
|
||||
def i_created_a_video_with_subs(_step):
|
||||
_step.given('I have created a Video component with subtitles "3_yD_cEKoCk"')
|
||||
|
||||
|
||||
@step('I have created a Video component with subtitles "([^"]*)"$')
|
||||
def i_created_a_video_with_subs_with_name(_step, sub_id):
|
||||
_step.given('I have created a Video component')
|
||||
|
||||
# Store the current URL so we can return here
|
||||
video_url = world.browser.url
|
||||
|
||||
# Upload subtitles for the video using the upload interface
|
||||
_step.given('I have uploaded subtitles "{}"'.format(sub_id))
|
||||
|
||||
# Return to the video
|
||||
world.visit(video_url)
|
||||
|
||||
world.wait_for_xmodule()
|
||||
|
||||
# update .sub filed with proper subs name (which mimics real Studio/XML behavior)
|
||||
# this is needed only for that videos which are created in acceptance tests.
|
||||
_step.given('I edit the component')
|
||||
world.wait_for_ajax_complete()
|
||||
_step.given('I save changes')
|
||||
|
||||
world.disable_jquery_animations()
|
||||
|
||||
world.wait_for_present('.is-initialized')
|
||||
world.wait_for_invisible(SELECTORS['spinner'])
|
||||
@@ -20,7 +20,6 @@ from xmodule.exceptions import NotFoundError
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
from contentstore.config.models import NewAssetsPageFlag
|
||||
from contentstore.utils import reverse_course_url
|
||||
from contentstore.views.exception import AssetNotFoundException, AssetSizeTooLargeException
|
||||
from edxmako.shortcuts import render_to_response
|
||||
@@ -102,7 +101,6 @@ def _asset_index(request, course_key):
|
||||
|
||||
return render_to_response('asset_index.html', {
|
||||
'language_code': request.LANGUAGE_CODE,
|
||||
'waffle_flag_enabled': NewAssetsPageFlag.feature_enabled(course_key),
|
||||
'context_course': course_module,
|
||||
'max_file_size_in_mbs': settings.MAX_ASSET_UPLOAD_FILE_SIZE_IN_MB,
|
||||
'chunk_size_in_mbs': settings.UPLOAD_CHUNK_SIZE_IN_MB,
|
||||
|
||||
@@ -14,31 +14,8 @@
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
<%block name="header_extras">
|
||||
% if waffle_flag_enabled:
|
||||
% if not settings.STUDIO_FRONTEND_CONTAINER_URL:
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('common/css/vendor/vendor.min.css')}" />
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('common/css/vendor/assets.min.css')}" />
|
||||
% endif
|
||||
% else:
|
||||
% for template_name in ["asset"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="js/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
% endif
|
||||
</%block>
|
||||
|
||||
<%block name="requirejs">
|
||||
% if not waffle_flag_enabled:
|
||||
require(["js/factories/asset_index"], function (AssetIndexFactory) {
|
||||
AssetIndexFactory({
|
||||
assetCallbackUrl: "${asset_callback_url|n, js_escaped_string}",
|
||||
uploadChunkSizeInMBs: ${chunk_size_in_mbs|n, dump_js_escaped_json},
|
||||
maxFileSizeInMBs: ${max_file_size_in_mbs|n, dump_js_escaped_json},
|
||||
maxFileSizeRedirectUrl: "${max_file_size_redirect_url|n, js_escaped_string}"
|
||||
});
|
||||
});
|
||||
% endif
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('common/css/vendor/vendor.min.css')}" />
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('common/css/vendor/assets.min.css')}" />
|
||||
</%block>
|
||||
|
||||
<%block name="content">
|
||||
@@ -49,122 +26,36 @@
|
||||
<small class="subtitle">${_("Content")}</small>
|
||||
<span class="sr">- </span>${_("Files & Uploads")}
|
||||
</h3>
|
||||
|
||||
% if not waffle_flag_enabled:
|
||||
<div class="nav-actions">
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="button upload-button new-button"><span class="icon fa fa-plus" aria-hidden="true"></span> ${_("Upload New File")}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
% endif
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<div class="content">
|
||||
<%static:optional_include_mako file="asset_index_content_header.html" />
|
||||
% if waffle_flag_enabled:
|
||||
<%static:studiofrontend entry="assets">
|
||||
{
|
||||
"lang": "${language_code | n, js_escaped_string}",
|
||||
"course": {
|
||||
"id": "${context_course.id | n, js_escaped_string}",
|
||||
"name": "${context_course.display_name_with_default | n, js_escaped_string}",
|
||||
"url_name": "${context_course.location.name | n, js_escaped_string}",
|
||||
"org": "${context_course.location.org | n, js_escaped_string}",
|
||||
"num": "${context_course.location.course | n, js_escaped_string}",
|
||||
"display_course_number": "${context_course.display_coursenumber | n, js_escaped_string}",
|
||||
"revision": "${context_course.location.revision | n, js_escaped_string}"
|
||||
},
|
||||
"help_tokens": {
|
||||
"files": "${get_online_help_info(online_help_token())['doc_url'] | n, js_escaped_string}"
|
||||
},
|
||||
"upload_settings": {
|
||||
"max_file_size_in_mbs": ${max_file_size_in_mbs|n, dump_js_escaped_json}
|
||||
},
|
||||
"search_settings": {
|
||||
"enabled": ${waffle().is_enabled(ENABLE_ASSETS_SEARCH) | n, dump_js_escaped_json}
|
||||
}
|
||||
<%static:studiofrontend entry="assets">
|
||||
{
|
||||
"lang": "${language_code | n, js_escaped_string}",
|
||||
"course": {
|
||||
"id": "${context_course.id | n, js_escaped_string}",
|
||||
"name": "${context_course.display_name_with_default | n, js_escaped_string}",
|
||||
"url_name": "${context_course.location.name | n, js_escaped_string}",
|
||||
"org": "${context_course.location.org | n, js_escaped_string}",
|
||||
"num": "${context_course.location.course | n, js_escaped_string}",
|
||||
"display_course_number": "${context_course.display_coursenumber | n, js_escaped_string}",
|
||||
"revision": "${context_course.location.revision | n, js_escaped_string}"
|
||||
},
|
||||
"help_tokens": {
|
||||
"files": "${get_online_help_info(online_help_token())['doc_url'] | n, js_escaped_string}"
|
||||
},
|
||||
"upload_settings": {
|
||||
"max_file_size_in_mbs": ${max_file_size_in_mbs|n, dump_js_escaped_json}
|
||||
},
|
||||
"search_settings": {
|
||||
"enabled": ${waffle().is_enabled(ENABLE_ASSETS_SEARCH) | n, dump_js_escaped_json}
|
||||
}
|
||||
</%static:studiofrontend>
|
||||
% else:
|
||||
<div class="content-primary">
|
||||
<div class="wrapper-assets"></div>
|
||||
<div class="ui-loading">
|
||||
<p><span class="spin"><span class="icon fa fa-refresh" aria-hidden="true"></span></span> <span class="copy">${_("Loading")}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
% if not waffle_flag_enabled:
|
||||
<aside class="content-supplementary" role="complementary" aria-label="${_("Help adding Files and Uploads")}">
|
||||
<div class="bit">
|
||||
<h3 class="title-3">${_("Adding Files for Your Course")}</h3>
|
||||
|
||||
<p>${Text(_("To add files to use in your course, click {em_start}Upload New File{em_end}. Then follow the prompts to upload a file from your computer.")).format(em_start=HTML("<strong>"), em_end=HTML("</strong>"))}</p>
|
||||
|
||||
<p>${Text(_("{em_start}Caution{em_end}: {platform_name} recommends that you limit the file size to {em_start}10 MB{em_end}. In addition, do not upload video or audio files. You should use a third party service to host multimedia files.")).format(em_start=HTML("<strong>"), em_end=HTML("</strong>"), platform_name=settings.PLATFORM_NAME)}</p>
|
||||
|
||||
<p>${_("The course image, textbook chapters, and files that appear on your Course Handouts sidebar also appear in this list.")}</p>
|
||||
</div>
|
||||
<div class="bit">
|
||||
<h3 class="title-3">${_("Using File URLs")}</h3>
|
||||
|
||||
<p>${Text(_("Use the {em_start}{studio_name} URL{em_end} value to link to the file or image from a component, a course update, or a course handout.")).format(studio_name=settings.STUDIO_SHORT_NAME, em_start=HTML("<strong>"), em_end=HTML("</strong>"))}</p>
|
||||
|
||||
<p>${Text(_("Use the {em_start}Web URL{em_end} value to reference the file or image only from outside of your course. {em_start}Note:{em_end} If you lock a file, the Web URL no longer works for external access to a file.")).format(em_start=HTML("<strong>"), em_end=HTML("</strong>"))}</p>
|
||||
|
||||
<p>${_("To copy a URL, double click the value in the URL column, then copy the selected text.")}</p>
|
||||
</div>
|
||||
<div class="bit external-help">
|
||||
<a href="${get_online_help_info(online_help_token())['doc_url']}" target="_blank" class="button external-help-button">${_("Learn more about managing files")}</a>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
% endif
|
||||
}
|
||||
</%static:studiofrontend>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="upload-modal modal">
|
||||
<a href="#" class="close-button"><span class="icon fa fa-times-circle" aria-hidden="true"></span> <span class="sr">${_('close')}</span></a>
|
||||
<div class="modal-body">
|
||||
<h2 class="title">${_("Upload New File")}</h2>
|
||||
<p>${_("Max per-file size: {max_filesize}MB").format(max_filesize=max_file_size_in_mbs)}</p>
|
||||
<p class="file-name">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill"></div>
|
||||
</div>
|
||||
<div class="embeddable">
|
||||
<label>${_('URL:')}</label>
|
||||
<input type="text" class="embeddable-xml-input" value='' readonly>
|
||||
</div>
|
||||
<form class="file-chooser" action="${asset_callback_url}"
|
||||
method="post" enctype="multipart/form-data">
|
||||
<a href="#" class="choose-file-button">${_("Choose File")}</a>
|
||||
<input type="file" class="file-input" name="file" multiple>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</%block>
|
||||
|
||||
<%block name="view_alerts">
|
||||
<!-- alert: save confirmed with close -->
|
||||
<div class="wrapper wrapper-alert wrapper-alert-confirmation" role="status">
|
||||
<div class="alert confirmation">
|
||||
<span class="icon fa fa-check" aria-hidden="true"></span>
|
||||
|
||||
<div class="copy">
|
||||
<h2 class="title title-3">${_('Your file has been deleted.')}</h2>
|
||||
</div>
|
||||
|
||||
<a href="" rel="view" class="action action-alert-close">
|
||||
<span class="icon fa fa-times-circle" aria-hidden="true"></span>
|
||||
<span class="label">${_('close alert')}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</%block>
|
||||
|
||||
@@ -5,14 +5,12 @@ from django.contrib.admin import autodiscover as django_autodiscover
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import contentstore.views
|
||||
from contentstore.config.models import NewAssetsPageFlag
|
||||
from cms.djangoapps.contentstore.views.organization import OrganizationListView
|
||||
import openedx.core.djangoapps.common_views.xblock
|
||||
import openedx.core.djangoapps.debug.views
|
||||
import openedx.core.djangoapps.external_auth.views
|
||||
import openedx.core.djangoapps.lang_pref.views
|
||||
|
||||
from config_models.views import ConfigurationModelCurrentAPIView
|
||||
from ratelimitbackend import admin
|
||||
|
||||
django_autodiscover()
|
||||
@@ -250,10 +248,6 @@ if 'debug_toolbar' in settings.INSTALLED_APPS:
|
||||
urlpatterns.append(url(r'^template/(?P<template>.+)$', openedx.core.djangoapps.debug.views.show_reference_template,
|
||||
name='openedx.core.djangoapps.debug.views.show_reference_template'))
|
||||
|
||||
urlpatterns += [
|
||||
url(r'config/assets', ConfigurationModelCurrentAPIView.as_view(model=NewAssetsPageFlag)),
|
||||
]
|
||||
|
||||
# Custom error pages
|
||||
# These are used by Django to render these error codes. Do not remove.
|
||||
# pylint: disable=invalid-name
|
||||
|
||||
@@ -18,83 +18,6 @@ UPLOAD_SUFFIX = '/data/uploads/studio-uploads/'
|
||||
UPLOAD_FILE_DIR = Path(__file__).abspath().dirname().dirname().dirname().dirname() + UPLOAD_SUFFIX # pylint: disable=no-value-for-parameter
|
||||
|
||||
|
||||
class AssetIndexPage(CoursePage):
|
||||
"""
|
||||
The Files and Uploads page for a course in Studio
|
||||
"""
|
||||
|
||||
url_path = "assets"
|
||||
type_filter_element = '#js-asset-type-col'
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""
|
||||
Construct a URL to the page within the course.
|
||||
"""
|
||||
# TODO - is there a better way to make this agnostic to the underlying default module store?
|
||||
default_store = os.environ.get('DEFAULT_STORE', 'draft')
|
||||
course_key = CourseLocator(
|
||||
self.course_info['course_org'],
|
||||
self.course_info['course_num'],
|
||||
self.course_info['course_run'],
|
||||
deprecated=(default_store == 'draft')
|
||||
)
|
||||
url = "/".join([BASE_URL, self.url_path, urllib.quote_plus(unicode(course_key))])
|
||||
return url if url[-1] == '/' else url + '/'
|
||||
|
||||
@wait_for_js
|
||||
def is_browser_on_page(self):
|
||||
return all([
|
||||
self.q(css='body.view-uploads').present,
|
||||
self.q(css='.page-header').present,
|
||||
not self.q(css='div.ui-loading').visible,
|
||||
])
|
||||
|
||||
@wait_for_js
|
||||
def type_filter_on_page(self):
|
||||
"""
|
||||
Checks that type filter is in table header.
|
||||
"""
|
||||
return self.q(css=self.type_filter_element).present
|
||||
|
||||
@wait_for_js
|
||||
def type_filter_header_label_visible(self):
|
||||
"""
|
||||
Checks type filter label is added and visible in the pagination header.
|
||||
"""
|
||||
return self.q(css='span.filter-column').visible
|
||||
|
||||
@wait_for_js
|
||||
def click_type_filter(self):
|
||||
"""
|
||||
Clicks type filter menu.
|
||||
"""
|
||||
self.q(css=".filterable-column .nav-item").click()
|
||||
|
||||
@wait_for_js
|
||||
def select_type_filter(self, filter_number):
|
||||
"""
|
||||
Selects Type filter from dropdown which filters the results.
|
||||
Returns False if no filter.
|
||||
"""
|
||||
self.wait_for_ajax()
|
||||
if self.q(css=".filterable-column .nav-item").is_present():
|
||||
if not self.q(css=self.type_filter_element + " .wrapper-nav-sub").visible:
|
||||
self.q(css=".filterable-column > .nav-item").first.click()
|
||||
self.wait_for_element_visibility(
|
||||
self.type_filter_element + " .wrapper-nav-sub", "Type Filter promise satisfied.")
|
||||
self.q(css=self.type_filter_element + " .column-filter-link").nth(filter_number).click()
|
||||
self.wait_for_ajax()
|
||||
return True
|
||||
return False
|
||||
|
||||
def return_results_set(self):
|
||||
"""
|
||||
Returns the asset set from the page
|
||||
"""
|
||||
return self.q(css="#asset-table-body tr").results
|
||||
|
||||
|
||||
class AssetIndexPageStudioFrontend(CoursePage):
|
||||
"""The Files and Uploads page for a course in Studio"""
|
||||
|
||||
|
||||
@@ -3,59 +3,11 @@ Acceptance tests for Studio related to the asset index page.
|
||||
"""
|
||||
import os
|
||||
|
||||
from common.test.acceptance.fixtures.base import StudioApiLoginError
|
||||
from common.test.acceptance.fixtures.config import ConfigModelFixture
|
||||
from common.test.acceptance.pages.studio.asset_index import AssetIndexPage, AssetIndexPageStudioFrontend
|
||||
from common.test.acceptance.tests.helpers import skip_if_browser
|
||||
from common.test.acceptance.pages.studio.asset_index import AssetIndexPageStudioFrontend
|
||||
from common.test.acceptance.tests.studio.base_studio_test import StudioCourseTest
|
||||
from common.test.acceptance.pages.studio.asset_index import UPLOAD_FILE_DIR
|
||||
|
||||
|
||||
class AssetIndexTest(StudioCourseTest):
|
||||
"""
|
||||
Tests for the Asset index page.
|
||||
"""
|
||||
|
||||
def setUp(self, is_staff=False): # pylint: disable=arguments-differ
|
||||
super(AssetIndexTest, self).setUp()
|
||||
self.asset_page = AssetIndexPage(
|
||||
self.browser,
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
self.course_info['run']
|
||||
)
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
"""
|
||||
Populate the children of the test course fixture.
|
||||
"""
|
||||
ConfigModelFixture('/config/assets', {'enabled_for_all_courses': False, 'enabled': False}, 'cms').install()
|
||||
self.course_fixture.add_asset(['image.jpg', 'textbook.pdf'])
|
||||
|
||||
@skip_if_browser('chrome') # TODO Need to fix test_page_existance for this for chrome browser
|
||||
def test_type_filter_exists(self):
|
||||
"""
|
||||
Make sure type filter is on the page.
|
||||
"""
|
||||
self.asset_page.visit()
|
||||
assert self.asset_page.type_filter_on_page() is True
|
||||
|
||||
@skip_if_browser('chrome') # TODO Need to fix test_page_existance for this for chrome browser
|
||||
def test_filter_results(self):
|
||||
"""
|
||||
Make sure type filter actually filters the results.
|
||||
"""
|
||||
self.asset_page.visit()
|
||||
all_results = len(self.asset_page.return_results_set())
|
||||
if self.asset_page.select_type_filter(1):
|
||||
filtered_results = len(self.asset_page.return_results_set())
|
||||
assert self.asset_page.type_filter_header_label_visible()
|
||||
assert all_results > filtered_results
|
||||
else:
|
||||
msg = "Could not open select Type filter"
|
||||
raise StudioApiLoginError(msg)
|
||||
|
||||
|
||||
class AssetIndexTestStudioFrontend(StudioCourseTest):
|
||||
"""Tests for the Asset index page."""
|
||||
|
||||
@@ -70,7 +22,6 @@ class AssetIndexTestStudioFrontend(StudioCourseTest):
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
"""Populate the children of the test course fixture."""
|
||||
ConfigModelFixture('/config/assets', {'enabled_for_all_courses': True, 'enabled': True}, 'cms').install()
|
||||
self.course_fixture.add_asset(['image.jpg', 'textbook.pdf'])
|
||||
|
||||
def test_page_with_assets_elements_load(self):
|
||||
@@ -233,7 +184,6 @@ class AssetIndexTestStudioFrontendPagination(StudioCourseTest):
|
||||
|
||||
def populate_course_fixture(self, course_fixture):
|
||||
"""Populate the children of the test course fixture and upload 49 files."""
|
||||
ConfigModelFixture('/config/assets', {'enabled_for_all_courses': True, 'enabled': True}, 'cms').install()
|
||||
files = []
|
||||
|
||||
for file_name in os.listdir(UPLOAD_FILE_DIR):
|
||||
|
||||
@@ -6,7 +6,7 @@ import uuid
|
||||
from base_studio_test import StudioCourseTest
|
||||
from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc
|
||||
from common.test.acceptance.pages.common.auto_auth import AutoAuthPage
|
||||
from common.test.acceptance.pages.studio.asset_index import AssetIndexPage
|
||||
from common.test.acceptance.pages.studio.asset_index import AssetIndexPageStudioFrontend
|
||||
from common.test.acceptance.pages.studio.course_info import CourseUpdatesPage
|
||||
from common.test.acceptance.pages.studio.edit_tabs import PagesPage
|
||||
from common.test.acceptance.pages.studio.import_export import ExportCoursePage, ImportCoursePage
|
||||
@@ -227,7 +227,7 @@ class CoursePagesTest(StudioCourseTest):
|
||||
self.pages = [
|
||||
clz(self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'])
|
||||
for clz in [
|
||||
AssetIndexPage,
|
||||
AssetIndexPageStudioFrontend,
|
||||
CourseUpdatesPage,
|
||||
PagesPage, ExportCoursePage, ImportCoursePage, CourseTeamPage, CourseOutlinePage, SettingsPage,
|
||||
AdvancedSettingsPage, GradingPage, TextbookUploadPage
|
||||
|
||||
@@ -8,7 +8,7 @@ from nose.plugins.attrib import attr
|
||||
|
||||
from common.test.acceptance.fixtures.course import XBlockFixtureDesc
|
||||
from common.test.acceptance.pages.common.auto_auth import AutoAuthPage
|
||||
from common.test.acceptance.pages.studio.asset_index import AssetIndexPage
|
||||
from common.test.acceptance.pages.studio.asset_index import AssetIndexPageStudioFrontend
|
||||
from common.test.acceptance.pages.studio.course_info import CourseUpdatesPage
|
||||
from common.test.acceptance.pages.studio.edit_tabs import PagesPage
|
||||
from common.test.acceptance.pages.studio.import_export import (
|
||||
@@ -600,7 +600,7 @@ class AssetIndexHelpTest(StudioCourseTest):
|
||||
"""
|
||||
def setUp(self): # pylint: disable=arguments-differ
|
||||
super(AssetIndexHelpTest, self).setUp()
|
||||
self.course_asset_index_page = AssetIndexPage(
|
||||
self.course_asset_index_page = AssetIndexPageStudioFrontend(
|
||||
self.browser,
|
||||
self.course_info['org'],
|
||||
self.course_info['number'],
|
||||
@@ -626,25 +626,6 @@ class AssetIndexHelpTest(StudioCourseTest):
|
||||
href=expected_url,
|
||||
)
|
||||
|
||||
def test_asset_index_side_bar_help(self):
|
||||
"""
|
||||
Scenario: Help link in sidebar links is working on 'Files & Uploads' page
|
||||
Given that I am on the 'Files & Uploads' page.
|
||||
And I want help about the process
|
||||
And I click the 'Learn more about managing files' in the sidebar links
|
||||
Then Help link should open.
|
||||
And help url should be correct
|
||||
"""
|
||||
expected_url = _get_expected_documentation_url('/course_assets/course_files.html')
|
||||
|
||||
# Assert that help link is correct.
|
||||
assert_side_bar_help_link(
|
||||
test=self,
|
||||
page=self.course_asset_index_page,
|
||||
href=expected_url,
|
||||
help_text='Learn more about managing files'
|
||||
)
|
||||
|
||||
|
||||
@attr(shard=10)
|
||||
class CoursePagesHelpTest(StudioCourseTest):
|
||||
|
||||
Reference in New Issue
Block a user