diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 93f867549c..28b1742621 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -37,6 +37,7 @@ from xmodule.error_module import ErrorDescriptor from xmodule.errortracker import exc_info_to_str from github_sync import export_to_github from static_replace import replace_urls +from external_auth.views import ssl_login_shortcut from mitxmako.shortcuts import render_to_response, render_to_string from xmodule.modulestore.django import modulestore @@ -88,7 +89,7 @@ def signup(request): csrf_token = csrf(request)['csrf_token'] return render_to_response('signup.html', {'csrf': csrf_token}) - +@ssl_login_shortcut @ensure_csrf_cookie def login_page(request): """ @@ -109,7 +110,7 @@ def index(request): courses = modulestore().get_items(['i4x', None, None, 'course', None]) # filter out courses that we don't have access to - courses = filter(lambda course: has_access(request.user, course.location) and course.location.course != 'templates', courses) + courses = filter(lambda course: has_access(request.user, course.location) and course.location.course != 'templates' and course.location.org!='' and course.location.course!='' and course.location.name!='', courses) return render_to_response('index.html', { 'new_course_template' : Location('i4x', 'edx', 'templates', 'course', 'Empty'), @@ -118,7 +119,8 @@ def index(request): course.location.org, course.location.course, course.location.name])) - for course in courses] + for course in courses], + 'user': request.user }) @@ -272,6 +274,26 @@ def edit_unit(request, location): containing_section_locs = modulestore().get_parent_locations(containing_subsection.location) containing_section = modulestore().get_item(containing_section_locs[0]) + # cdodge hack. We're having trouble previewing drafts via jump_to redirect + # so let's generate the link url here + + # need to figure out where this item is in the list of children as the preview will need this + index =1 + for child in containing_subsection.get_children(): + if child.location == item.location: + break + index = index + 1 + + preview_lms_link = '//{preview}{lms_base}/courses/{org}/{course}/{course_name}/courseware/{section}/{subsection}/{index}'.format( + preview='preview.', + lms_base=settings.LMS_BASE, + org=course.location.org, + course=course.location.course, + course_name=course.location.name, + section=containing_section.location.name, + subsection=containing_subsection.location.name, + index=index) + unit_state = compute_unit_state(item) try: @@ -697,8 +719,18 @@ def upload_asset(request, org, course, coursename): #then commit the content contentstore().save(content) del_cached_content(content.location) + + # readback the saved content - we need the database timestamp + readback = contentstore().find(content.location) - response = HttpResponse('Upload completed') + response_payload = {'displayname' : content.name, + 'uploadDate' : get_date_display(readback.last_modified_at), + 'url' : StaticContent.get_url_path_from_location(content.location), + 'thumb_url' : StaticContent.get_url_path_from_location(thumbnail_content.location) if thumbnail_content is not None else None, + 'msg' : 'Upload completed' + } + + response = HttpResponse(json.dumps(response_payload)) response['asset_url'] = StaticContent.get_url_path_from_location(content.location) return response @@ -710,7 +742,7 @@ This view will return all CMS users who are editors for the specified course def manage_users(request, location): # check that logged in user has permissions to this item - if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME): + if not has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME) and not has_access(request.user, location, role=EDITOR_ROLE_NAME): raise PermissionDenied() course_module = modulestore().get_item(location) @@ -720,7 +752,9 @@ def manage_users(request, location): 'context_course': course_module, 'staff': get_users_in_course_group_by_role(location, STAFF_ROLE_NAME), 'add_user_postback_url' : reverse('add_user', args=[location]).rstrip('/'), - 'remove_user_postback_url' : reverse('remove_user', args=[location]).rstrip('/') + 'remove_user_postback_url' : reverse('remove_user', args=[location]).rstrip('/'), + 'allow_actions' : has_access(request.user, location, role=INSTRUCTOR_ROLE_NAME), + 'request_user_id' : request.user.id }) @@ -845,6 +879,10 @@ def asset_index(request, org, course, name): course_reference = StaticContent.compute_location(org, course, name) assets = contentstore().get_all_content_for_course(course_reference) + + # sort in reverse upload date order + assets = sorted(assets, key=lambda asset: asset['uploadDate'], reverse=True) + thumbnails = contentstore().get_all_content_thumbnails_for_course(course_reference) asset_display = [] for asset in assets: diff --git a/cms/envs/common.py b/cms/envs/common.py index 6f7a462da2..8b5e6c7655 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -32,7 +32,8 @@ from xmodule.static_content import write_descriptor_styles, write_descriptor_js, MITX_FEATURES = { 'USE_DJANGO_PIPELINE': True, 'GITHUB_PUSH': False, - 'ENABLE_DISCUSSION_SERVICE': False + 'ENABLE_DISCUSSION_SERVICE': False, + 'AUTH_USE_MIT_CERTIFICATES' : False, } # needed to use lms student app diff --git a/cms/envs/dev_ike.py b/cms/envs/dev_ike.py new file mode 100644 index 0000000000..5fb120854b --- /dev/null +++ b/cms/envs/dev_ike.py @@ -0,0 +1,16 @@ +# dev environment for ichuang/mit + +# FORCE_SCRIPT_NAME = '/cms' + +from .common import * +from logsettings import get_logger_config +from .dev import * +import socket + +MITX_FEATURES['AUTH_USE_MIT_CERTIFICATES'] = True + +MITX_FEATURES['USE_DJANGO_PIPELINE']=False # don't recompile scss + +SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # django 1.4 for nginx ssl proxy + + diff --git a/cms/static/coffee/src/views/module_edit.coffee b/cms/static/coffee/src/views/module_edit.coffee index 85c099ab9a..c39c908b47 100644 --- a/cms/static/coffee/src/views/module_edit.coffee +++ b/cms/static/coffee/src/views/module_edit.coffee @@ -57,12 +57,12 @@ class CMS.Views.ModuleEdit extends Backbone.View data = @module.save() data.metadata = @metadata() @model.save(data).done( => - alert("Your changes have been saved.") + showToastMessage("Your changes have been saved.", null, 3) @module = null @render() @$el.removeClass('editing') ).fail( -> - alert("There was an error saving your changes. Please try again.") + showToastMessage("There was an error saving your changes. Please try again.", null, 3) ) clickCancelButton: (event) -> diff --git a/cms/static/coffee/src/views/unit.coffee b/cms/static/coffee/src/views/unit.coffee index 2b0f7668a2..6f9b939af2 100644 --- a/cms/static/coffee/src/views/unit.coffee +++ b/cms/static/coffee/src/views/unit.coffee @@ -117,6 +117,8 @@ class CMS.Views.UnitEdit extends Backbone.View @model.save() deleteComponent: (event) => + if not confirm 'Are you sure you want to delete this component? This action cannot be undone.' + return $component = $(event.currentTarget).parents('.component') $.post('/delete_item', { id: $component.data('id') @@ -183,6 +185,7 @@ class CMS.Views.UnitEdit.NameEdit extends Backbone.View # Treat the metadata dictionary as immutable metadata = $.extend({}, @model.get('metadata')) metadata.display_name = @$('.unit-display-name-input').val() + $('.unit-location .editing .unit-name').html(metadata.display_name) @model.set('metadata', metadata) class CMS.Views.UnitEdit.LocationState extends Backbone.View diff --git a/cms/static/js/base.js b/cms/static/js/base.js index b0cb73cb50..dd9065dd07 100644 --- a/cms/static/js/base.js +++ b/cms/static/js/base.js @@ -80,11 +80,17 @@ $(document).ready(function() { }); function showImportSubmit(e) { - $('.file-name').html($(this).val()) - $('.file-name-block').show(); - $('.import .choose-file-button').hide(); - $('.submit-button').show(); - $('.progress').show(); + var filepath = $(this).val(); + if(filepath.substr(filepath.length - 6, 6) == 'tar.gz') { + $('.error-block').hide(); + $('.file-name').html($(this).val()); + $('.file-name-block').show(); + $('.import .choose-file-button').hide(); + $('.submit-button').show(); + $('.progress').show(); + } else { + $('.error-block').html('File format not supported. Please upload a file with a tar.gz extension.').show(); + } } function syncReleaseDate(e) { @@ -321,7 +327,7 @@ function startUpload(e) { function resetUploadBar(){ var percentVal = '0%'; - $('.upload-modal .progress-fill').width(percentVal) + $('.upload-modal .progress-fill').width(percentVal); $('.upload-modal .progress-fill').html(percentVal); } @@ -335,9 +341,21 @@ function displayFinishedUpload(xhr) { if(xhr.status = 200){ markAsLoaded(); } + var resp = JSON.parse(xhr.responseText); $('.upload-modal .copy-button').attr('href', xhr.getResponseHeader('asset_url')); - $('.upload-modal .progress-fill').html(xhr.responseText); + $('.upload-modal .progress-fill').html(resp.msg); $('.upload-modal .choose-file-button').html('Load Another File').show(); + $('.upload-modal .progress-fill').width('100%'); + + // see if this id already exists, if so, then user must have updated an existing piece of content + $("tr[data-id='" + resp.url + "']").remove(); + + var template = $('#new-asset-element').html(); + var html = Mustache.to_html(template, resp); + $('table > tbody > tr:first').before(html); + + $("tr[data-id='" + resp.url + "'] a.show-xml").toggle(showEmbeddableXML, hideEmbeddableXML); + } function markAsLoaded() { @@ -483,7 +501,7 @@ function addNewCourse(e) { e.preventDefault(); var $newCourse = $($('#new-course-template').html()); $('.new-course-button').after($newCourse); - $newCourse.find('.new-course-org').focus().select(); + $newCourse.find('.new-course-name').focus().select(); $newCourse.find('.new-course-save').bind('click', saveNewCourse); $newCourse.find('.new-course-cancel').bind('click', cancelNewCourse); } @@ -491,14 +509,17 @@ function addNewCourse(e) { function saveNewCourse(e) { e.preventDefault(); + var $newCourse = $(this).closest('.new-course'); + template = $(this).data('template'); - org = $(this).prevAll('.new-course-org').val(); - number = $(this).prevAll('.new-course-number').val(); - display_name = $(this).prevAll('.new-course-name').val(); + org = $newCourse.find('.new-course-org').val(); + number = $newCourse.find('.new-course-number').val(); + display_name = $newCourse.find('.new-course-name').val(); if (org == '' || number == '' || display_name == ''){ - alert('You must specify all fields in order to create a new course.') + alert('You must specify all fields in order to create a new course.'); + return; } $.post('/create_new_course', diff --git a/cms/static/sass/_courseware.scss b/cms/static/sass/_courseware.scss index 4251f25f90..40e34375f6 100644 --- a/cms/static/sass/_courseware.scss +++ b/cms/static/sass/_courseware.scss @@ -45,7 +45,7 @@ input.courseware-unit-search-input { } header { - height: 67px; + height: 47px; .item-details { float: left; @@ -80,6 +80,7 @@ input.courseware-unit-search-input { } h4 { + display: none; font-size: 12px; color: #878e9d; @@ -102,6 +103,17 @@ input.courseware-unit-search-input { border-top-width: 0; } } + + &.new-section { + header { + height: auto; + @include clearfix; + } + + .expand-collapse-icon { + visibility: hidden; + } + } } .new-section-name, @@ -114,12 +126,14 @@ input.courseware-unit-search-input { @include blue-button; padding: 2px 20px 5px; margin: 0 5px; + color: #fff !important; } .new-section-name-cancel, .new-subsection-name-cancel { @include white-button; padding: 2px 20px 5px; + color: #8891a1 !important; } .dummy-calendar { diff --git a/cms/static/sass/_dashboard.scss b/cms/static/sass/_dashboard.scss index 652d7f9d66..8763927bdb 100644 --- a/cms/static/sass/_dashboard.scss +++ b/cms/static/sass/_dashboard.scss @@ -41,4 +41,57 @@ display: block; padding: 20px; text-align: center; +} + +.new-course { + padding: 15px 25px; + margin-top: 20px; + border-radius: 3px; + border: 1px solid $darkGrey; + background: #fff; + box-shadow: 0 1px 2px rgba(0, 0, 0, .1); + @include clearfix; + + .row { + margin-bottom: 15px; + @include clearfix; + } + + .column { + float: left; + width: 48%; + } + + .column:first-child { + margin-right: 4%; + } + + .course-info { + width: 600px; + } + + label { + display: block; + font-size: 13px; + font-weight: 700; + } + + .new-course-org, + .new-course-number, + .new-course-name { + width: 100%; + } + + .new-course-name { + font-size: 19px; + font-weight: 300; + } + + .new-course-save { + @include blue-button; + } + + .new-course-cancel { + @include white-button; + } } \ No newline at end of file diff --git a/cms/static/sass/_import.scss b/cms/static/sass/_import.scss index f9480a6d46..a0a1f5e512 100644 --- a/cms/static/sass/_import.scss +++ b/cms/static/sass/_import.scss @@ -11,11 +11,14 @@ margin-right: 3%; font-size: 14px; - h3 { - margin-bottom: 20px; - font-size: 18px; + h2 { + font-weight: 700; + font-size: 19px; + margin-bottom: 20px; + } + + strong { font-weight: 700; - color: $error-red; } p + p { @@ -39,12 +42,17 @@ font-weight: 300; } - .file-name-block { + .file-name-block, + .error-block { display: none; margin-bottom: 15px; font-size: 13px; } + .error-block { + color: $error-red; + } + .choose-file-button { @include blue-button; padding: 10px 50px 11px; @@ -67,4 +75,28 @@ white-space: normal; } } + + .progress-bar { + display: none; + width: 350px; + height: 30px; + margin: 30px auto 10px; + border: 1px solid $blue; + + &.loaded { + border-color: #66b93d; + + .progress-fill { + background: #66b93d; + } + } + } + + .progress-fill { + width: 0%; + height: 30px; + background: $blue; + color: #fff; + line-height: 48px; + } } \ No newline at end of file diff --git a/cms/static/sass/_unit.scss b/cms/static/sass/_unit.scss index 0b893e1965..71d21e6c89 100644 --- a/cms/static/sass/_unit.scss +++ b/cms/static/sass/_unit.scss @@ -57,7 +57,7 @@ border: 1px solid #d1ddec; border-radius: 3px; background: #fff; - @include transition(border-color .15s); + @include transition(none); &:hover { border-color: #6696d7; @@ -253,6 +253,8 @@ .CodeMirror { border: 1px solid #3c3c3c; + background: #fff; + color: #3c3c3c; } h3 { diff --git a/cms/static/sass/base-style.scss b/cms/static/sass/base-style.scss index 0824cc380d..6a6105c109 100644 --- a/cms/static/sass/base-style.scss +++ b/cms/static/sass/base-style.scss @@ -5,6 +5,7 @@ @import 'reset'; @import 'mixins'; +@import "fonts"; @import "variables"; @import "cms_mixins"; @import "base"; diff --git a/cms/templates/asset_index.html b/cms/templates/asset_index.html index 1b3ffb6d5d..4244bd2542 100644 --- a/cms/templates/asset_index.html +++ b/cms/templates/asset_index.html @@ -1,10 +1,38 @@ <%inherit file="base.html" /> <%! from django.core.urlresolvers import reverse %> <%block name="bodyclass">assets -<%block name="title">CMS Courseware Overview +<%block name="title">Courseware Assets + +<%namespace name='static' file='static_content.html'/> + +<%block name="jsextra"> + + <%block name="content"> + +

Asset Library

@@ -22,9 +50,9 @@ Embed - + % for asset in assets: - +
% if asset['thumb_url'] is not None: diff --git a/cms/templates/import.html b/cms/templates/import.html index 42def2d512..84e136fef9 100644 --- a/cms/templates/import.html +++ b/cms/templates/import.html @@ -11,21 +11,23 @@

Import

-

Importing a new course will delete all course content currently associated with your course - and replace it with the contents of the uploaded file.

+

Please read the documentation before attempting an import!

+

Importing a new course will delete all content currently associated with your course + and replace it with the contents of the uploaded file.

File uploads must be zip files containing, at a minimum, a course.xml file.

Please note that if your course has any problems with auto-generated url_name nodes, re-importing your course could cause the loss of student data associated with those problems.

Course to import:

- Choose File +

+ Choose File

change

-
@@ -37,29 +39,35 @@ @@ -23,6 +36,7 @@

My Courses

+ % if user.is_active: New Course
    %for course, url in courses: @@ -35,8 +49,15 @@ --> - %endfor + %endfor
+ % else: +
+

+ In order to start authoring courses using edX studio, please click on the activation link in your email. +

+
+ % endif
diff --git a/cms/templates/login.html b/cms/templates/login.html index a604024804..3c834bb299 100644 --- a/cms/templates/login.html +++ b/cms/templates/login.html @@ -55,10 +55,11 @@ if(json.success) { location.href = "${reverse('index')}"; } else if($('#login_error').length == 0) { - $('#login_form').prepend('
Email or password is incorrect.
'); + $('#login_form').prepend('
' + json.value + '
'); $('#login_error').slideDown(150); } else { $('#login_error').stop().slideDown(150); + $('#login_error').html(json.value); } } ); diff --git a/cms/templates/manage_users.html b/cms/templates/manage_users.html index 3adfa42a16..80cb1256fb 100644 --- a/cms/templates/manage_users.html +++ b/cms/templates/manage_users.html @@ -16,20 +16,26 @@ New User
+ %if allow_actions:
save cancel
+ %endif
    % for user in staff:
  1. ${user.username} ${user.email} + %if allow_actions :
    - + %if request_user_id != user.id: + + %endif
    + %endif
  2. % endfor
diff --git a/cms/templates/registration/activation_complete.html b/cms/templates/registration/activation_complete.html index 30e731e8cc..8cc3dc8c56 100644 --- a/cms/templates/registration/activation_complete.html +++ b/cms/templates/registration/activation_complete.html @@ -3,28 +3,24 @@ <%namespace name='static' file='../static_content.html'/> +<%block name="content">
- %if not already_active: -

Activation Complete!

- %else: -

Account already active!

- %endif -
- -

+ +

%if not already_active: - Thanks for activating your account. + Thanks for activating your account. %else: This account has already been activated. %endif - + %if user_logged_in: - Visit your dashboard to see your courses. + Visit your dashboard to see your courses. %else: You can now login. %endif

+ diff --git a/cms/templates/signup.html b/cms/templates/signup.html index 93acdfad71..150658b75e 100644 --- a/cms/templates/signup.html +++ b/cms/templates/signup.html @@ -1,4 +1,6 @@ <%inherit file="base.html" /> +<%! from django.core.urlresolvers import reverse %> + <%block name="title">Sign up <%block name="bodyclass">no-header @@ -82,7 +84,7 @@ submit_data, function(json) { if(json.success) { - $('#register').html(json.value); + location.href = "${reverse('index')}"; } else { $('#register_error').html(json.value).stop().slideDown(150); } diff --git a/cms/templates/unit.html b/cms/templates/unit.html index 0f4f999599..3a184d65ba 100644 --- a/cms/templates/unit.html +++ b/cms/templates/unit.html @@ -108,9 +108,7 @@ ${subsection.display_name} -
    - ${units.enum_units(subsection, actions=False, selected=unit.location)} -
+ ${units.enum_units(subsection, actions=False, selected=unit.location)} diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 6e56c4f591..2b9b2c7884 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -10,7 +10,7 @@ ${context_course.display_name}