From 1abea0b406a08f808f1850871a62fcb6504dcccc Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Mon, 5 Aug 2013 14:16:26 -0400 Subject: [PATCH 01/16] Check that content-type starts with application/json When Chrome sends the AJAX request to add a user to the course team, it sets the Content-type to "application/json". However, when Firefox sends the same request, it sets the Content-type to "application/json; charset=UTF-8". This commit only checks that the Content-type begins with "application/json", not is identical to it; that way, Firefox can play, too. --- cms/djangoapps/contentstore/views/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cms/djangoapps/contentstore/views/user.py b/cms/djangoapps/contentstore/views/user.py index e1c75bad0f..5b38d47452 100644 --- a/cms/djangoapps/contentstore/views/user.py +++ b/cms/djangoapps/contentstore/views/user.py @@ -179,7 +179,7 @@ def course_team_user(request, org, course, name, email): return JsonResponse() # all other operations require the requesting user to specify a role - if request.META.get("CONTENT_TYPE", "") == "application/json" and request.body: + if request.META.get("CONTENT_TYPE", "").startswith("application/json") and request.body: try: payload = json.loads(request.body) except: From 73b9e261e424c22a856790cae764b94421dc76bb Mon Sep 17 00:00:00 2001 From: Adam Palay Date: Mon, 5 Aug 2013 13:23:45 -0400 Subject: [PATCH 02/16] redirects lms landing page to student.views.index if there is no marketing site resets the cms edge redirect to '/' --- cms/djangoapps/contentstore/views/requests.py | 2 +- lms/djangoapps/branding/views.py | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/cms/djangoapps/contentstore/views/requests.py b/cms/djangoapps/contentstore/views/requests.py index dc1b7871ab..abbf84755e 100644 --- a/cms/djangoapps/contentstore/views/requests.py +++ b/cms/djangoapps/contentstore/views/requests.py @@ -12,7 +12,7 @@ def landing(request, org, course, coursename): # points to the temporary edge page def edge(request): - return redirect('/dashboard') + return redirect('/') def event(request): diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index 985dfa52d0..531b61f3ff 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -4,7 +4,6 @@ from django.shortcuts import redirect from django_future.csrf import ensure_csrf_cookie import student.views -import branding import courseware.views from mitxmako.shortcuts import marketing_link from util.cache import cache_if_anonymous @@ -26,11 +25,7 @@ def index(request): if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE'): return redirect(settings.MKTG_URLS.get('ROOT')) - university = branding.get_university(request.META.get('HTTP_HOST')) - if university is None: - return student.views.index(request, user=request.user) - - return redirect('/') + return student.views.index(request, user=request.user) @ensure_csrf_cookie @@ -44,8 +39,4 @@ def courses(request): if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False): return redirect(marketing_link('COURSES'), permanent=True) - university = branding.get_university(request.META.get('HTTP_HOST')) - if university is None: - return courseware.views.courses(request) - - return redirect('/') + return courseware.views.courses(request) From bf5af6c8cfb67e698ce14ba5217808d96df3b75c Mon Sep 17 00:00:00 2001 From: Adam Palay Date: Mon, 5 Aug 2013 17:11:33 -0400 Subject: [PATCH 03/16] add university_profile/edge.html for edge landing page --- lms/djangoapps/branding/views.py | 14 ++++- lms/templates/university_profile/edge.html | 65 ++++++++++++++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 lms/templates/university_profile/edge.html diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index 531b61f3ff..42c57e3090 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -2,8 +2,10 @@ from django.conf import settings from django.core.urlresolvers import reverse from django.shortcuts import redirect from django_future.csrf import ensure_csrf_cookie +from mitxmako.shortcuts import render_to_response import student.views +import branding import courseware.views from mitxmako.shortcuts import marketing_link from util.cache import cache_if_anonymous @@ -25,7 +27,11 @@ def index(request): if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE'): return redirect(settings.MKTG_URLS.get('ROOT')) - return student.views.index(request, user=request.user) + university = branding.get_university(request.META.get('HTTP_HOST')) + if university is None: + return student.views.index(request, user=request.user) + + return render_to_response('university_profile/edge.html', {}) @ensure_csrf_cookie @@ -39,4 +45,8 @@ def courses(request): if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False): return redirect(marketing_link('COURSES'), permanent=True) - return courseware.views.courses(request) + university = branding.get_university(request.META.get('HTTP_HOST')) + if university is None: + return courseware.views.courses(request) + + return render_to_response('university_profile/edge.html', {}) diff --git a/lms/templates/university_profile/edge.html b/lms/templates/university_profile/edge.html new file mode 100644 index 0000000000..a3e115ddd8 --- /dev/null +++ b/lms/templates/university_profile/edge.html @@ -0,0 +1,65 @@ +<%inherit file="../stripped-main.html" /> +<%! from django.core.urlresolvers import reverse %> +<%block name="title">edX edge +<%block name="bodyclass">no-header edge-landing + +<%block name="content"> +
+
edX edge
+
+ + +
+
+ + + +<%block name="js_extra"> + + + +<%include file="../signup_modal.html" /> +<%include file="../forgot_password_modal.html" /> \ No newline at end of file From 17b5ccf13a6c271532ca53431240c1d33fd194ec Mon Sep 17 00:00:00 2001 From: Adam Palay Date: Mon, 5 Aug 2013 18:08:32 -0400 Subject: [PATCH 04/16] creates "edge" case --- lms/djangoapps/branding/views.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index 42c57e3090..6a8ef8056b 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -28,10 +28,13 @@ def index(request): return redirect(settings.MKTG_URLS.get('ROOT')) university = branding.get_university(request.META.get('HTTP_HOST')) - if university is None: - return student.views.index(request, user=request.user) + if university == 'edge': + return render_to_response('university_profile/edge.html', {}) + + # we do not expect this case to be reached in cases where + # marketing and edge are enabled + return student.views.index(request, user=request.user) - return render_to_response('university_profile/edge.html', {}) @ensure_csrf_cookie @@ -46,7 +49,9 @@ def courses(request): return redirect(marketing_link('COURSES'), permanent=True) university = branding.get_university(request.META.get('HTTP_HOST')) - if university is None: - return courseware.views.courses(request) + if university == 'edge': + return render_to_response('university_profile/edge.html', {}) - return render_to_response('university_profile/edge.html', {}) + # we do not expect this case to be reached in cases where + # marketing and edge are enabled + return courseware.views.courses(request) From 5dd202e5926e84fa4ee54783447c0df72a8007b0 Mon Sep 17 00:00:00 2001 From: Adam Palay Date: Mon, 5 Aug 2013 18:45:54 -0400 Subject: [PATCH 05/16] update edge.html --- lms/templates/university_profile/edge.html | 23 +++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lms/templates/university_profile/edge.html b/lms/templates/university_profile/edge.html index a3e115ddd8..166a95a106 100644 --- a/lms/templates/university_profile/edge.html +++ b/lms/templates/university_profile/edge.html @@ -1,33 +1,34 @@ +<%! from django.utils.translation import ugettext as _ %> <%inherit file="../stripped-main.html" /> <%! from django.core.urlresolvers import reverse %> -<%block name="title">edX edge +<%block name="title">${_("edX edge")} <%block name="bodyclass">no-header edge-landing <%block name="content">
-
edX edge
+
${_("edX edge")}
From 9728dceeb0da512f50e1e8efcac634bb6db82e2f Mon Sep 17 00:00:00 2001 From: Alexander Kryklia Date: Tue, 6 Aug 2013 11:29:51 +0300 Subject: [PATCH 06/16] Adds RawDescriptro for VideoAlpha --- common/lib/xmodule/xmodule/videoalpha_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/videoalpha_module.py b/common/lib/xmodule/xmodule/videoalpha_module.py index 084389411a..3ac3d5157c 100644 --- a/common/lib/xmodule/xmodule/videoalpha_module.py +++ b/common/lib/xmodule/xmodule/videoalpha_module.py @@ -21,6 +21,7 @@ from django.conf import settings from xmodule.x_module import XModule from xmodule.editing_module import TabsEditingDescriptor +from xmodule.raw_module import RawDescriptor from xmodule.modulestore.mongo import MongoModuleStore from xmodule.modulestore.django import modulestore from xmodule.contentstore.content import StaticContent @@ -187,7 +188,7 @@ class VideoAlphaModule(VideoAlphaFields, XModule): }) -class VideoAlphaDescriptor(VideoAlphaFields, TabsEditingDescriptor): +class VideoAlphaDescriptor(VideoAlphaFields, TabsEditingDescriptor, RawDescriptor): """Descriptor for `VideoAlphaModule`.""" module_class = VideoAlphaModule From e8e09afa0a3b00fe88ea4978d7647cf14dc9b20b Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Tue, 6 Aug 2013 10:45:14 -0400 Subject: [PATCH 07/16] Make sure that we properly parse and save section release times Firefox wasn't saving section release times, due to issues with JS Date() parsing. I've modified the code to make it more explicit around what it should do and how it should work, which also makes it work better with both Firefox and Chrome. --- cms/static/js/base.js | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index de0fd955dc..7f82e67431 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -253,6 +253,12 @@ function syncReleaseDate(e) { $("#start_time").val(""); } +function pad2(number) { + // pad a number to two places: useful for formatting months, days, hours, etc + // when displaying a date/time + return (number < 10 ? '0' : '') + number; +} + function getEdxTimeFromDateTimeVals(date_val, time_val) { if (date_val != '') { if (time_val == '') time_val = '00:00'; @@ -772,21 +778,23 @@ function cancelSetSectionScheduleDate(e) { function saveSetSectionScheduleDate(e) { e.preventDefault(); - var input_date = $('.edit-subsection-publish-settings .start-date').val(); - var input_time = $('.edit-subsection-publish-settings .start-time').val(); - - var start = getEdxTimeFromDateTimeVals(input_date, input_time); + var date = $('.edit-subsection-publish-settings .start-date').datepicker("getDate"); + var time = $('.edit-subsection-publish-settings .start-time').timepicker("getTime"); + var datetime = new Date(Date.UTC( + date.getFullYear(), date.getMonth(), date.getDate(), + time.getHours(), time.getMinutes() + )); var id = $modal.attr('data-id'); analytics.track('Edited Section Release Date', { 'course': course_location_analytics, 'id': id, - 'start': start + 'start': datetime }); var saving = new CMS.Views.Notification.Mini({ - title: gettext("Saving") + "…", + title: gettext("Saving") + "…" }); saving.show(); // call into server to commit the new order @@ -798,7 +806,7 @@ function saveSetSectionScheduleDate(e) { data: JSON.stringify({ 'id': id, 'metadata': { - 'start': start + 'start': datetime } }) }).success(function() { @@ -806,12 +814,15 @@ function saveSetSectionScheduleDate(e) { var html = _.template( '' + '' + gettext("Will Release:") + ' ' + - gettext("<%= date %> at <%= time %> UTC") + + gettext("{month}/{day}/{year} at {hour}:{minute} UTC") + '' + - '' + + '' + gettext("Edit") + '', - {date: input_date, time: input_time, id: id}); + {year: datetime.getUTCFullYear(), month: pad2(datetime.getUTCMonth() + 1), day: pad2(datetime.getUTCDate()), + hour: pad2(datetime.getUTCHours()), minute: pad2(datetime.getUTCMinutes()), + id: id}, + {interpolate: /\{(.+?)\}/g}); $thisSection.find('.section-published-date').html(html); hideModal(); saving.hide(); From 87ce16c70e19c22c36177f8021a56e7b584e08ca Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Tue, 6 Aug 2013 11:28:35 -0400 Subject: [PATCH 08/16] Cleanup/reformatting --- cms/static/js/views/overview.js | 2 +- cms/templates/edit_subsection.html | 9 ++++----- cms/templates/overview.html | 8 ++++---- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/cms/static/js/views/overview.js b/cms/static/js/views/overview.js index e41a6971a6..4ce0d3688e 100644 --- a/cms/static/js/views/overview.js +++ b/cms/static/js/views/overview.js @@ -148,7 +148,7 @@ function generateCheckHoverState(selectorsToOpen, selectorsToShove) { } }); - } + }; } function removeHesitate(event, ui) { diff --git a/cms/templates/edit_subsection.html b/cms/templates/edit_subsection.html index bdcaf18015..5b03643f3b 100644 --- a/cms/templates/edit_subsection.html +++ b/cms/templates/edit_subsection.html @@ -1,11 +1,10 @@ -<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> <%! - import logging - from xmodule.util.date_utils import get_default_time_display, almost_same_datetime + import logging + from xmodule.util.date_utils import get_default_time_display, almost_same_datetime + from django.utils.translation import ugettext as _ + from django.core.urlresolvers import reverse %> - -<%! from django.core.urlresolvers import reverse %> <%block name="title">${_("CMS Subsection")} <%block name="bodyclass">is-signedin course subsection diff --git a/cms/templates/overview.html b/cms/templates/overview.html index 3795e9d09b..2c42df187a 100644 --- a/cms/templates/overview.html +++ b/cms/templates/overview.html @@ -1,10 +1,10 @@ -<%! from django.utils.translation import ugettext as _ %> <%inherit file="base.html" /> <%! - import logging - from xmodule.util import date_utils + import logging + from xmodule.util import date_utils + from django.utils.translation import ugettext as _ + from django.core.urlresolvers import reverse %> -<%! from django.core.urlresolvers import reverse %> <%block name="title">${_("Course Outline")} <%block name="bodyclass">is-signedin course outline From 9c09323b0d1bd6215dd8b880f534aae245b64993 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Tue, 6 Aug 2013 11:28:57 -0400 Subject: [PATCH 09/16] Properly parse and save section release times --- cms/static/js/base.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 7f82e67431..b2fcec84ae 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -313,9 +313,18 @@ function saveSubsection() { metadata[$(el).data("metadata-name")] = el.value; } - // Piece back together the date/time UI elements into one date/time string - metadata['start'] = getEdxTimeFromDateTimeInputs('start_date', 'start_time'); - metadata['due'] = getEdxTimeFromDateTimeInputs('due_date', 'due_time'); + // get datetimes for start and due, stick into metadata + _(["start", "due"]).each(function(name) { + var date, time; + date = $("#"+name+"_date").datepicker("getDate"); + time = $("#"+name+"_time").timepicker("getTime"); + if (date && time) { + metadata[name] = new Date(Date.UTC( + date.getFullYear(), date.getMonth(), date.getDate(), + time.getHours(), time.getMinutes() + )); + } + }); $.ajax({ url: "/save_item", From d2c4ac2597d6c97d9f29629111699f5ec4fc27d2 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Tue, 6 Aug 2013 14:30:19 -0400 Subject: [PATCH 10/16] Fixed Jasmine tests --- .../coffee/spec/views/overview_spec.coffee | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/cms/static/coffee/spec/views/overview_spec.coffee b/cms/static/coffee/spec/views/overview_spec.coffee index d900e4bfb1..8bda96da66 100644 --- a/cms/static/coffee/spec/views/overview_spec.coffee +++ b/cms/static/coffee/spec/views/overview_spec.coffee @@ -1,22 +1,19 @@ describe "Course Overview", -> beforeEach -> - appendSetFixtures """ - - """ - - appendSetFixtures """ - - """ + _.each ["/static/js/vendor/date.js", "/static/js/vendor/timepicker/jquery.timepicker.js", "/jsi18n/"], (path) -> + appendSetFixtures """ + + """ appendSetFixtures """ - """#" + """ appendSetFixtures """
@@ -38,7 +35,7 @@ describe "Course Overview", -> SaveCancel
- """#" + """ appendSetFixtures """
@@ -46,12 +43,13 @@ describe "Course Overview", ->
- """#" + """ spyOn(window, 'saveSetSectionScheduleDate').andCallThrough() # Have to do this here, as it normally gets bound in document.ready() $('a.save-button').click(saveSetSectionScheduleDate) $('a.delete-section-button').click(deleteSection) + $(".edit-subsection-publish-settings .start-date").datepicker() @notificationSpy = spyOn(CMS.Views.Notification.Mini.prototype, 'show').andCallThrough() window.analytics = jasmine.createSpyObj('analytics', ['track']) From c0bd7db2936c89f3125e10ac562a6bcfcbf00645 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Tue, 6 Aug 2013 16:47:23 -0400 Subject: [PATCH 11/16] Removed unused JS functions --- cms/static/js/base.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index b2fcec84ae..7db66a525d 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -259,23 +259,6 @@ function pad2(number) { return (number < 10 ? '0' : '') + number; } -function getEdxTimeFromDateTimeVals(date_val, time_val) { - if (date_val != '') { - if (time_val == '') time_val = '00:00'; - - return new Date(date_val + " " + time_val + "Z"); - } - - else return null; -} - -function getEdxTimeFromDateTimeInputs(date_id, time_id) { - var input_date = $('#' + date_id).val(); - var input_time = $('#' + time_id).val(); - - return getEdxTimeFromDateTimeVals(input_date, input_time); -} - function autosaveInput(e) { var self = this; if (this.saveTimer) { From aab9661fe962012f0d7f983e94d19302162be6b6 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Tue, 6 Aug 2013 16:48:09 -0400 Subject: [PATCH 12/16] Scoped pad2 function to the one place that it's called --- cms/static/js/base.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 7db66a525d..4a5fc2b182 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -253,12 +253,6 @@ function syncReleaseDate(e) { $("#start_time").val(""); } -function pad2(number) { - // pad a number to two places: useful for formatting months, days, hours, etc - // when displaying a date/time - return (number < 10 ? '0' : '') + number; -} - function autosaveInput(e) { var self = this; if (this.saveTimer) { @@ -802,6 +796,12 @@ function saveSetSectionScheduleDate(e) { } }) }).success(function() { + var pad2 = function(number) { + // pad a number to two places: useful for formatting months, days, hours, etc + // when displaying a date/time + return (number < 10 ? '0' : '') + number; + }; + var $thisSection = $('.courseware-section[data-id="' + id + '"]'); var html = _.template( '' + From ae019ef8c9f4d0ea67574ee3edf4a2b5c7f3d393 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Tue, 6 Aug 2013 17:09:12 -0400 Subject: [PATCH 13/16] Abstracted functionality to get datetime into separate JS function --- cms/static/js/base.js | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index 4a5fc2b182..bb772da02b 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -253,6 +253,22 @@ function syncReleaseDate(e) { $("#start_time").val(""); } +function getDatetime(datepickerInput, timepickerInput) { + // given a pair of inputs (datepicker and timepicker), return a JS Date + // object that corresponds to the datetime that they represent. Assume + // UTC timezone, NOT the timezone of the user's browser. + var date = $(datepickerInput).datepicker("getDate"); + var time = $(timepickerInput).timepicker("getTime"); + if(date && time) { + return new Date(Date.UTC( + date.getFullYear(), date.getMonth(), date.getDate(), + time.getHours(), time.getMinutes() + )); + } else { + return null; + } +} + function autosaveInput(e) { var self = this; if (this.saveTimer) { @@ -292,14 +308,13 @@ function saveSubsection() { // get datetimes for start and due, stick into metadata _(["start", "due"]).each(function(name) { - var date, time; - date = $("#"+name+"_date").datepicker("getDate"); - time = $("#"+name+"_time").timepicker("getTime"); - if (date && time) { - metadata[name] = new Date(Date.UTC( - date.getFullYear(), date.getMonth(), date.getDate(), - time.getHours(), time.getMinutes() - )); + + var datetime = getDatetime( + document.getElementById(name+"_date"), + document.getElementById(name+"_time") + ); + if (datetime) { + metadata[name] = datetime; } }); @@ -764,12 +779,10 @@ function cancelSetSectionScheduleDate(e) { function saveSetSectionScheduleDate(e) { e.preventDefault(); - var date = $('.edit-subsection-publish-settings .start-date').datepicker("getDate"); - var time = $('.edit-subsection-publish-settings .start-time').timepicker("getTime"); - var datetime = new Date(Date.UTC( - date.getFullYear(), date.getMonth(), date.getDate(), - time.getHours(), time.getMinutes() - )); + var datetime = getDatetime( + $('.edit-subsection-publish-settings .start-date'), + $('.edit-subsection-publish-settings .start-time') + ); var id = $modal.attr('data-id'); From ee3ce7b6c20861509b6ae72287ccd03eb20fb2e1 Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Wed, 7 Aug 2013 09:45:44 -0400 Subject: [PATCH 14/16] Don't ignore null datetimes on subsection settings Clicking "Sync to " should send an AJAX request with the datetimes set to null, so that the server resets them. --- cms/static/js/base.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cms/static/js/base.js b/cms/static/js/base.js index bb772da02b..80b24776da 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -313,9 +313,9 @@ function saveSubsection() { document.getElementById(name+"_date"), document.getElementById(name+"_time") ); - if (datetime) { - metadata[name] = datetime; - } + // if datetime is null, we want to set that in metadata anyway; + // its an indication to the server to clear the datetime in the DB + metadata[name] = datetime; }); $.ajax({ From e30a9a6fe33877a79a6047e3ff5a6cf9e858133d Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Wed, 7 Aug 2013 11:03:19 -0400 Subject: [PATCH 15/16] Created a new lettuce test to catch bug, updated other lettuce tests --- .../contentstore/features/section.feature | 2 +- .../contentstore/features/section.py | 13 ++-- .../contentstore/features/subsection.feature | 24 ++++++-- .../contentstore/features/subsection.py | 60 ++++++++++++++----- 4 files changed, 75 insertions(+), 24 deletions(-) diff --git a/cms/djangoapps/contentstore/features/section.feature b/cms/djangoapps/contentstore/features/section.feature index a08b490c6d..d9dd6f9398 100644 --- a/cms/djangoapps/contentstore/features/section.feature +++ b/cms/djangoapps/contentstore/features/section.feature @@ -24,7 +24,7 @@ Feature: Create Section Given I have opened a new course in Studio And I have added a new section When I click the Edit link for the release date - And I save a new section release date + And I set the section release date to 12/25/2013 Then the section release date is updated And I see a "saving" notification diff --git a/cms/djangoapps/contentstore/features/section.py b/cms/djangoapps/contentstore/features/section.py index 955c6a8f4e..3ca8e1676d 100644 --- a/cms/djangoapps/contentstore/features/section.py +++ b/cms/djangoapps/contentstore/features/section.py @@ -35,10 +35,15 @@ def i_click_the_edit_link_for_the_release_date(_step): world.css_click(button_css) -@step('I save a new section release date$') -def i_save_a_new_section_release_date(_step): - set_date_and_time('input.start-date.date.hasDatepicker', '12/25/2013', - 'input.start-time.time.ui-timepicker-input', '00:00') +@step('I set the section release date to ([0-9/-]+)( [0-9:]+)?') +def set_section_release_date(_step, datestring, timestring): + if hasattr(timestring, "strip"): + timestring = timestring.strip() + if not timestring: + timestring = "00:00" + set_date_and_time( + 'input.start-date.date.hasDatepicker', datestring, + 'input.start-time.time.ui-timepicker-input', timestring) world.browser.click_link_by_text('Save') diff --git a/cms/djangoapps/contentstore/features/subsection.feature b/cms/djangoapps/contentstore/features/subsection.feature index 9f5793dbe7..84755b3644 100644 --- a/cms/djangoapps/contentstore/features/subsection.feature +++ b/cms/djangoapps/contentstore/features/subsection.feature @@ -14,7 +14,7 @@ Feature: Create Subsection When I click the New Subsection link And I enter a subsection name with a quote and click save Then I see my subsection name with a quote on the Courseware page - And I click to edit the subsection name + And I click on the subsection Then I see the complete subsection name with a quote in the editor Scenario: Assign grading type to a subsection and verify it is still shown after refresh (bug #258) @@ -27,10 +27,13 @@ Feature: Create Subsection Scenario: Set a due date in a different year (bug #256) Given I have opened a new subsection in Studio - And I have set a release date and due date in different years - Then I see the correct dates + And I set the subsection release date to 12/25/2011 03:00 + And I set the subsection due date to 01/02/2012 04:00 + Then I see the subsection release date is 12/25/2011 03:00 + And I see the subsection due date is 01/02/2012 04:00 And I reload the page - Then I see the correct dates + Then I see the subsection release date is 12/25/2011 03:00 + And I see the subsection due date is 01/02/2012 04:00 Scenario: Delete a subsection Given I have opened a new course section in Studio @@ -40,3 +43,16 @@ Feature: Create Subsection And I press the "subsection" delete icon And I confirm the prompt Then the subsection does not exist + + Scenario: Sync to Section + Given I have opened a new course section in Studio + And I click the Edit link for the release date + And I set the section release date to 01/02/2103 + And I have added a new subsection + And I click on the subsection + And I set the subsection release date to 01/20/2103 + And I reload the page + And I click the link to sync release date to section + And I wait for "1" second + And I reload the page + Then I see the subsection release date is 01/02/2103 diff --git a/cms/djangoapps/contentstore/features/subsection.py b/cms/djangoapps/contentstore/features/subsection.py index e280ec615d..60a325f550 100644 --- a/cms/djangoapps/contentstore/features/subsection.py +++ b/cms/djangoapps/contentstore/features/subsection.py @@ -41,8 +41,8 @@ def i_save_subsection_name_with_quote(step): save_subsection_name('Subsection With "Quote"') -@step('I click to edit the subsection name$') -def i_click_to_edit_subsection_name(step): +@step('I click on the subsection$') +def click_on_subsection(step): world.css_click('span.subsection-name-value') @@ -53,12 +53,28 @@ def i_see_complete_subsection_name_with_quote_in_editor(step): assert_equal(world.css_value(css), 'Subsection With "Quote"') -@step('I have set a release date and due date in different years$') -def test_have_set_dates_in_different_years(step): - set_date_and_time('input#start_date', '12/25/2011', 'input#start_time', '03:00') - world.css_click('.set-date') - # Use a year in the past so that current year will always be different. - set_date_and_time('input#due_date', '01/02/2012', 'input#due_time', '04:00') +@step('I set the subsection release date to ([0-9/-]+)( [0-9:]+)?') +def set_subsection_release_date(_step, datestring, timestring): + if hasattr(timestring, "strip"): + timestring = timestring.strip() + if not timestring: + timestring = "00:00" + set_date_and_time( + 'input#start_date', datestring, + 'input#start_time', timestring) + + +@step('I set the subsection due date to ([0-9/-]+)( [0-9:]+)?') +def set_subsection_due_date(_step, datestring, timestring): + if hasattr(timestring, "strip"): + timestring = timestring.strip() + if not timestring: + timestring = "00:00" + if not world.css_visible('input#due_date'): + world.css_click('.due-date-input .set-date') + set_date_and_time( + 'input#due_date', datestring, + 'input#due_time', timestring) @step('I mark it as Homework$') @@ -72,6 +88,11 @@ def i_see_it_marked__as_homework(step): assert_equal(world.css_value(".status-label"), 'Homework') +@step('I click the link to sync release date to section') +def click_sync_release_date(step): + world.css_click('.sync-date') + + ############ ASSERTIONS ################### @@ -91,16 +112,25 @@ def the_subsection_does_not_exist(step): assert world.browser.is_element_not_present_by_css(css) -@step('I see the correct dates$') -def i_see_the_correct_dates(step): - assert_equal('12/25/2011', get_date('input#start_date')) - assert_equal('03:00', get_date('input#start_time')) - assert_equal('01/02/2012', get_date('input#due_date')) - assert_equal('04:00', get_date('input#due_time')) +@step('I see the subsection release date is ([0-9/-]+)( [0-9:]+)?') +def i_see_subsection_release(_step, datestring, timestring): + if hasattr(timestring, "strip"): + timestring = timestring.strip() + assert_equal(datestring, get_date('input#start_date')) + if timestring: + assert_equal(timestring, get_date('input#start_time')) + + +@step('I see the subsection due date is ([0-9/-]+)( [0-9:]+)?') +def i_see_subsection_due(_step, datestring, timestring): + if hasattr(timestring, "strip"): + timestring = timestring.strip() + assert_equal(datestring, get_date('input#due_date')) + if timestring: + assert_equal(timestring, get_date('input#due_time')) ############ HELPER METHODS ################### - def get_date(css): return world.css_find(css).first.value.strip() From df25770fa714d7349ff1d87e32bcaffb837071ba Mon Sep 17 00:00:00 2001 From: David Baumgold Date: Tue, 6 Aug 2013 16:39:02 -0400 Subject: [PATCH 16/16] Assign isExternal JS function to window object When JS functions are defined with names, they are local variables, and inaccessible if defined inside a closure. Django-Pipeline concatenates all of our JS into one big closure. This function explicitly assings the function to a property of the `window` object, so that it is accessible to other JS functions. --- common/static/js/utility.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/common/static/js/utility.js b/common/static/js/utility.js index 6407faad48..a983535b46 100644 --- a/common/static/js/utility.js +++ b/common/static/js/utility.js @@ -1,20 +1,20 @@ // checks whether or not the url is external to the local site. // generously provided by StackOverflow: http://stackoverflow.com/questions/6238351/fastest-way-to-detect-external-urls -function isExternal(url) { +window.isExternal = function (url) { // parse the url into protocol, host, path, query, and fragment. More information can be found here: http://tools.ietf.org/html/rfc3986#appendix-B var match = url.match(/^([^:\/?#]+:)?(?:\/\/([^\/?#]*))?([^?#]+)?(\?[^#]*)?(#.*)?/); // match[1] matches a protocol if one exists in the url // if the protocol in the url does not match the protocol in the window's location, this url is considered external - if (typeof match[1] === "string" && - match[1].length > 0 - && match[1].toLowerCase() !== location.protocol) + if (typeof match[1] === "string" && + match[1].length > 0 && + match[1].toLowerCase() !== location.protocol) return true; // match[2] matches the host if one exists in the url // if the host in the url does not match the host of the window location, this url is considered external - if (typeof match[2] === "string" && - match[2].length > 0 && + if (typeof match[2] === "string" && + match[2].length > 0 && // this regex removes the port number if it patches the current location's protocol - match[2].replace(new RegExp(":("+{"http:":80,"https:":443}[location.protocol]+")?$"), "") !== location.host) + match[2].replace(new RegExp(":("+{"http:":80,"https:":443}[location.protocol]+")?$"), "") !== location.host) return true; return false; -} +};