New Feature: Certificates Web View

- SOL-465: Initial implementation of certificates web view and signatories (names/titles)

- SOL-718 Close button is working properly

- SOL-801 Backbone Signatories Modeling

- SOL-803 Underscore template: Editor (Add)

- SOL-802 Signatories: Underscore template - Details

- SOL-804 Signatories: Underscore template: Editor (Edit)

- Add signatory delete Django view

- SOL-805 Signatory editor (Delete)

- Add Coffeescript router

- SOL-716 Jasmine Tests

- Added missing minified JS library

- client side validation of signatory fields

- SOL-390 signatories names

- Remove obsolete extends Sass files

- input maxlength limiting for signatory information

- SOL-389: Course title override

- SOL-466: Add capability to upload digitized signatures in Studio

- ziafazal: fixed css for upload signature image

- ziafazal: completed deletion of signature images

- UX-1741: Add initial static rendering/styling for Open edX web certs
  * creating new global static dir
  * adding static version of edX UX pattern library assets
  * adding web certificates static assets
  * adding static (+abstracted) web certificates rendering
  * creating two tiers of rendering (base + distinguished)
  * providing sample assets for certificate rendering
  * supporting RTL layouts
  * adding certifcates assests to edX static asset pipeline
  * temporarily hiding the mozilla open badges share action
  * wiring print button to print view/page
  * fixup! addressing conflict artifact in valid cert template
  * fixup! adding missing %hd-subsection sass extend + components comment clean up
  * fixup! correcting pattern library .hd-4 font-weight value

- SOL-468 Linked Student View for Web View Credential

- SOL-467: Add capability to upload organization logos for certificates

- SOL-391 / SOL-387: Signatory related info (assets) in certificates web view

- kelketek: Fixes for static asset collection in certificate HTML view.

- SOL-398 Web View: Public Access

- mattdrayer: Post-merge branch stabilization

- catong: Initial changes to Studio template and Help config file

- ziafazal: Branch stabilizations

- SOL-387: Display organization logo on LMS web view

- talbs/mattdrayer: Branch Stabilizations

- talbs: converting backpack action to use a button HTML element

- talbs: revising placeholder assets + their rendering in cert view

- mattdrayer: Username web view wireup

- SOL-386 Certificate Mode Previews

- SOL-905: Make organization logo and signatory signature uneditable

- SOL-922: Improve test coverage

- SOL-765: Add LinkedIn sharing

- [marco] temporary styling adjustment to account for smaller linkedin share image / fake button

- SOL-921: Address hardcoded template items

- SOL-927: Deleting certificate should delete org logo image also
  * updated invalid template
  * removed hr
  * fix invalid certificate error

- clrux: Add i18n to certificate templates and partials

- mattdrayer: Pylint violations

- SOL-920 Certificate Activation/Deactivation

- mattdrayer: Added LMS support

- SOL-932: Fix preview mode support in certificate view

- SOL-934: Fixed bug reported and broken tests

- SOL-935 removed the 'valid' word from web view title

- talbs: RTL support updates/fixes
  * revising certificate type icon/name vertical alignment
  * removing unused older certificate template
  * revising styling for message/banner actions
  * abstracting accomplishment type to use course mode + adding in honor/verified-specific placeholders

- mattdrayer: JSHint violations
This commit is contained in:
Matt Drayer
2015-04-07 10:04:39 -04:00
parent f81d09cbdb
commit af7277cdd9
396 changed files with 30915 additions and 7493 deletions

View File

@@ -0,0 +1,97 @@
<%inherit file="base.html" />
<%def name="online_help_token()"><% return "certificates" %></%def>
<%namespace name='static' file='static_content.html'/>
<%! import json %>
<%!
from contentstore import utils
from django.utils.translation import ugettext as _
%>
<%block name="title">${_("Course Certificates")}</%block>
<%block name="bodyclass">is-signedin course view-certificates</%block>
<%block name="header_extras">
% for template_name in ["certificate-details", "certificate-editor", "signatory-editor", "signatory-details", "basic-modal", "modal-button", "list", "upload-dialog", "certificate-web-preview"]:
<script type="text/template" id="${template_name}-tpl">
<%static:include path="js/${template_name}.underscore" />
</script>
% endfor
</%block>
<%block name="jsextra">
<script type="text/javascript">
window.CMS = window.CMS || {};
CMS.URL = CMS.URL || {};
CMS.URL.UPLOAD_ASSET = '${upload_asset_url}';
</script>
</%block>
<%block name="requirejs">
require(["js/certificates/factories/certificates_page_factory"], function(CertificatesPageFactory) {
CertificatesPageFactory(${json.dumps(certificates)}, "${certificate_url}", "${course_outline_url}", ${json.dumps(course_modes)}, ${json.dumps(certificate_web_view_url)}, ${json.dumps(is_active)}, ${json.dumps(certificate_activation_handler_url)} );
});
</%block>
<%block name="content">
<div class="wrapper-mast wrapper">
<header class="mast has-actions has-subtitle">
<h1 class="page-header">
<small class="subtitle">${_("Settings")}</small>
<span class="sr">&gt; </span>${_("Certificates")}
</h1>
<div class="preview-certificate nav-actions"></div>
</header>
</div>
<div class="wrapper-content wrapper">
<section class="content">
<article class="content-primary" role="main">
<div class="wrapper-certificates certificates-list">
<h2 class="sr title">${_("Certificates")}</h2>
% if certificates is None:
<div class="notice notice-incontext notice-moduledisabled">
<p class="copy">
${_("This module is not enabled.")}
</p>
</div>
% else:
<div class="ui-loading">
<p><span class="spin"><i class="icon fa fa-refresh" aria-hidden="true"></i></span> <span class="copy">${_("Loading")}</span></p>
</div>
% endif
</div>
</article>
<aside class="content-supplementary" role="complementary">
<div class="bit">
<div class="certificates-doc">
<h2 class="title-3">${_("Certificates")}</h2>
<p>${_("Upon successful completion of your course, learners receive a certificate to acknowledge their accomplishment. Course team members with the Admin role in Studio can create course certificates based on templates that exist for your organization.")}</p>
<p>${_("Course team members with the Admin role can also add signatory names for a certificate, and upload assets including signature image files for signatories. {em_start}Note:{em_end} Signature images are used only for verified certificates.").format(em_start='<strong>', em_end="</strong>")}</p>
<p>${_("Click {em_start}New Certificate{em_end} to add a new certificate. To edit a certficate, hover over its box and click {em_start}Edit{em_end}. You can delete a certificate only if it has not been issued to a learner. To delete a certificate, hover over its box and click the delete icon.").format(em_start="<strong>", em_end="</strong>")}</p>
<p><a href="${get_online_help_info(online_help_token())['doc_url']}" target="_blank" class="button external-help-button">${_("Learn More")}</a></p>
</div>
</div>
<div class="bit">
% if context_course:
<%
details_url = utils.reverse_course_url('settings_handler', context_course.id)
grading_url = utils.reverse_course_url('grading_handler', context_course.id)
course_team_url = utils.reverse_course_url('course_team_handler', context_course.id)
advanced_settings_url = utils.reverse_course_url('advanced_settings_handler', context_course.id)
%>
<h2 class="title-3">${_("Other Course Settings")}</h2>
<nav class="nav-related" aria-label="${_('Other Course Settings')}">
<ul>
<li class="nav-item"><a href="${details_url}">${_("Details &amp; Schedule")}</a></li>
<li class="nav-item"><a href="${grading_url}">${_("Grading")}</a></li>
<li class="nav-item"><a href="${course_team_url}">${_("Course Team")}</a></li>
<li class="nav-item"><a href="${advanced_settings_url}">${_("Advanced Settings")}</a></li>
<li class="nav-item"><a href="${utils.reverse_course_url('group_configurations_list_handler', context_course.id)}">${_("Group Configurations")}</a></li>
</ul>
</nav>
% endif
</div>
</aside>
</section>
</div>
</%block>

View File

@@ -0,0 +1,40 @@
<div class="collection-details wrapper-certificate">
<header class="collection-header">
<h3 class="sr title">
<%= name %>
</h3>
</header>
<ol class="collection-info certificate-info certificate-info-<% if(showDetails){ print('block'); } else { print('inline'); } %>">
<% if (!_.isUndefined(id)) { %>
<li class="sr certificate-id">
<span class="certificate-label"><%= gettext('ID') %>: </span>
<span class="certificate-value"><%= id %></span>
</li>
<% } %>
<% if (showDetails) { %>
<header>
<span class="title"><%= gettext("Certificate Details") %></span>
</header>
<% if (course_title) { %>
<div class="course-title-override">
<span class="certificate-label"><%= gettext('Course Title Override') %>: </span>
<span class="certificate-value"><%= course_title %></span>
</div>
<% } %>
<header style='margin-top: 30px;'>
<span class="title"><%= gettext("Certificate Signatories") %></span>
</header>
<div class="signatory-details-list"></div>
<% } %>
</ol>
<ul class="actions certificate-actions">
<li class="action action-edit">
<button class="edit"><i class="icon fa fa-pencil" aria-hidden="true"></i> <%= gettext("Edit") %></button>
</li>
<li class="action action-delete wrapper-delete-button" data-tooltip="<%= gettext('Delete') %>">
<button class="delete action-icon"><i class="icon fa fa-trash-o" aria-hidden="true"></i><span><%= gettext("Delete") %></span></button>
</li>
</ul>
</div>

View File

@@ -0,0 +1,57 @@
<form class="collection-edit-form certificate-edit-form">
<div aria-live="polite">
<% if (error && error.message) { %>
<div class="certificate-edit-error message message-status message-status error is-shown" name="certificate-edit-error">
<%= gettext(error.message) %>
</div>
<% } %>
</div>
<div class="wrapper-form">
<fieldset class="collection-fields certificate-fields">
<legend class="sr"><%= gettext("Certificate Information") %></legend>
<div class="sr input-wrap field text required add-collection-name add-certificate-name <% if(error && error.attributes && error.attributes.name) { print('error'); } %>">
<label for="certificate-name-<%= uniqueId %>"><%= gettext("Certificate Name") %></label>
<input id="certificate-name-<%= uniqueId %>" class="collection-name-input input-text" name="certificate-name" type="text" placeholder="<%= gettext("Name of the certificate") %>" value="<%= name %>" aria-describedby="certificate-name-<%=uniqueId %>-tip" />
<span id="certificate-name-<%= uniqueId %>-tip" class="tip tip-stacked"><%= gettext("Name of the certificate") %></span>
</div>
<div class="sr input-wrap field text add-certificate-description">
<label for="certificate-description-<%= uniqueId %>"><%= gettext("Description") %></label>
<textarea id="certificate-description-<%= uniqueId %>" class="certificate-description-input text input-text" name="certificate-description" placeholder="<%= gettext("Description of the certificate") %>" aria-describedby="certificate-description-<%=uniqueId %>-tip"><%= description %></textarea>
<span id="certificate-description-<%= uniqueId %>-tip" class="tip tip-stacked"><%= gettext("Description of the certificate") %></span>
</div>
<div class="input-wrap field text add-certificate-course-title">
<label for="certificate-course-title-<%= uniqueId %>"><%= gettext("Course Title Override") %></label>
<input id="certificate-course-title-<%= uniqueId %>" class="certificate-course-title-input input-text" name="certificate-course-title" type="text" placeholder="<%= gettext("Course title") %>" value="<%= course_title %>" aria-describedby="certificate-course-title-<%=uniqueId %>-tip" />
<span id="certificate-course-title-<%= uniqueId %>-tip" class="tip tip-stacked"><%= gettext("Title of the course") %></span>
</div>
<div class="input-wrap field text add-org-logo">
<label for="certificate-org-logo-<%= uniqueId %>"><%= gettext("Organization Logo") %></label>
<div class="current-org-logo" <% if (org_logo_path == "") { print('style="display:none"'); } %>><span class="wrapper-org-logo"><img class="org-logo" src="<%= org_logo_path %>" alt="Organization Logo"></span></div>
<div class="org-logo-upload-wrapper">
<div class="org-logo-upload-input-wrapper">
<input id="certificate-org-logo-<%= uniqueId %>" class="collection-name-input input-text org-logo-input" name="org-logo-path" type="text" placeholder="<%= gettext("Path to organization logo") %>" value="<%= org_logo_path %>" aria-describedby="certificate-org-logo-<%=uniqueId %>-tip" readonly />
<span id="certificate-org-logo-<%=uniqueId %>-tip" class="tip tip-stacked"><%= gettext("Maximum logo height 125px, width variable") %></span>
</div>
<button type="button" class="action action-upload-org-logo">Upload Organization Logo</button>
</div>
</div>
</fieldset>
<header>
<span class="title"><%= gettext("Certificate Signatories") %></legend>
</header>
<div class="signatory-edit-list"> </div>
<span>
<button class="action action-add-signatory" type="button"><%= gettext("Add Signatory") %></button>
<span class="tip tip-stacked"><%= gettext("(Up to 4 signatories are allowed for a certificate)") %></span>
</span>
</div>
<div class="actions">
<button class="action action-primary" type="submit"><% if (isNew) { print(gettext("Create")) } else { print(gettext("Save")) } %></button>
<button class="action action-secondary action-cancel"><%= gettext("Cancel") %></button>
<% if (!isNew) { %>
<span class="wrapper-delete-button">
<a class="button action-delete delete" href="#"><%= gettext("Delete") %></a>
</span>
<% } %>
</div>
</form>

View File

@@ -0,0 +1,17 @@
<label for="course-modes"><%= gettext("Choose mode") %></label>
<select id="course-modes">
<% _.each(course_modes, function(course_mode) { %>
<option value= "<%= course_mode %>"><%= course_mode %></option>
<% }); %>
</select>
<a href=<%= certificate_web_view_url %> class="button preview-certificate-link" target="_blank">
<%= gettext("Preview Certificate") %>
</a>
<button class="button activate-cert">
<span>
<% if(!is_active) { %>
<%= gettext("Activate") %></span>
<% } else { %>
<%= gettext("Deactivate") %></span>
<% } %>
</button>

View File

@@ -11,7 +11,7 @@
<% } else { %>
<div class="list-items"></div>
<% if (!isEditing) { %>
<button class="action action-add">
<button class="action action-add <% if(!canCreateNewItem) {%> action-add-hidden <% }%>" >
<i class="icon fa fa-plus"></i>
<%- interpolate(
gettext('New %(item_type)s'), {item_type: itemCategoryDisplayName}, true

View File

@@ -0,0 +1,22 @@
<div class="signatory-panel-default">
<div class="actions certificate-actions signatory-panel-edit">
<span class="action action-edit-signatory">
<a href="javascript:void(0);" class="edit-signatory"><i class="icon fa fa-pencil" aria-hidden="true"></i> <%= gettext("Edit") %></a>
</span>
</div>
<div class="signatory-panel-header">Signatory <%= signatory_number %>&nbsp;</div>
<div class="signatory-panel-body">
<div>
<span class="signatory-name-label"><%= gettext("Name") %>:&nbsp;</span>
<span class="signatory-name-value"><%= name %></span>
</div>
<div>
<span class="signatory-title-label"><%= gettext("Title") %>:&nbsp;</span>
<span class="signatory-title-value"><%= title %></span>
</div>
<div>
<span class="signatory-organization-label"><%= gettext("Organization") %>:&nbsp;</span>
<span class="signatory-organization-value"><%= organization %></span>
</div>
</div>
</div>

View File

@@ -0,0 +1,61 @@
<div class="signatory-panel-default">
<% if (!is_editing_all_collections) { %>
<a class="signatory-panel-close" href="javascript:void(0);" data-tooltip="Close">
<i class="icon fa fa-close" aria-hidden="true"></i>
<span class="sr action-button-text"><%= gettext("Close") %></span>
</a>
<a class="signatory-panel-save" href="javascript:void(0);" data-tooltip="Save">
<i class="icon fa fa-save" aria-hidden="true"></i>
<span class="sr action-button-text"><%= gettext("Save") %></span>
</a>
<% } else if (signatories_count > 1 && (total_saved_signatories > 1 || isNew) ) { %>
<a class="signatory-panel-delete" href="#" data-tooltip="Delete">
<i class="icon fa fa-trash-o" aria-hidden="true"></i>
<span class="sr action-button-text"><%= gettext("Delete") %></span>
</a>
<% } %>
<div class="signatory-panel-header">Signatory <%= signatory_number %></div>
<div class="signatory-panel-body">
<fieldset class="collection-fields signatory-fields">
<legend class="sr"><%= gettext("Certificate Signatory Configuration") %></legend>
<div class="input-wrap field text add-signatory-name <% if(error && error.name) { print('error'); } %>">
<label for="signatory-name-<%= signatory_number %>"><%= gettext("Name ") %></label>
<input id="signatory-name-<%= signatory_number %>" class="collection-name-input input-text signatory-name-input" name="signatory-name" type="text" placeholder="<%= gettext("Name of the signatory") %>" value="<%= name %>" aria-describedby="signatory-name-<%= signatory_number %>-tip" maxlength="40" />
<span id="signatory-name-<%= signatory_number %>-tip" class="tip tip-stacked"><%= gettext("Maximum 40 characters") %></span>
<% if(error && error.name) { %>
<span class="message-error"><%= error.name %></span>
<% } %>
</div>
<div class="input-wrap field text add-signatory-title <% if(error && error.title) { print('error'); } %>">
<label for="signatory-title-<%= signatory_number %>"><%= gettext("Title ") %></label>
<textarea id="signatory-title-<%= signatory_number %>" class="collection-name-input text input-text signatory-title-input" name="signatory-title" cols="40" rows="2" placeholder="<%= gettext("Title of the signatory") %>" aria-describedby="signatory-title-<%= signatory_number %>-tip" maxlength="80"><%= title %></textarea>
<span id="signatory-title-<%= signatory_number %>-tip" class="tip tip-stacked"><%= gettext("2 Lines, 40 characters each") %></span>
<% if(error && error.title) { %>
<span class="message-error"><%= error.title %></span>
<% } %>
</div>
<div class="input-wrap field text add-signatory-organization <% if(error && error.organization) { print('error'); } %>">
<label for="signatory-organization-<%= signatory_number %>"><%= gettext("Organization ") %></label>
<input id="signatory-organization-<%= signatory_number %>" class="collection-name-input input-text signatory-organization-input" name="signatory-organization" type="text" placeholder="<%= gettext("Organization of the signatory") %>" value="<%= organization %>" aria-describedby="signatory-organization-<%= signatory_number %>-tip" maxlength="40" />
<span id="signatory-organization-<%= signatory_number %>-tip" class="tip tip-stacked"><%= gettext("Maximum 40 characters") %></span>
<% if(error && error.organization) { %>
<span class="message-error"><%= error.organization %></span>
<% } %>
</div>
<div class="input-wrap field text add-signatory-signature">
<label for="signatory-signature-<%= signatory_number %>"><%= gettext("Signature Image") %></label>
<% if (signature_image_path != "") { %>
<div class="current-signature-image"><span class="wrapper-signature-image"><img class="signature-image" src="<%= signature_image_path %>" alt="Signature Image"></span></div>
<% } %>
<div class="signature-upload-wrapper">
<div class="signature-upload-input-wrapper">
<input id="signatory-signature-<%= signatory_number %>" class="collection-name-input input-text signatory-signature-input" name="signatory-signature-url" type="text" placeholder="<%= gettext("Path to Signature Image") %>" value="<%= signature_image_path %>" aria-describedby="signatory-signature-<%= signatory_number %>-tip" readonly />
<span id="signatory-signature-<%= signatory_number %>-tip" class="tip tip-stacked"><%= gettext("Image must be 450px X 150px transparent PNG") %></span>
</div>
<button type="button" class="action action-upload-signature">Upload Signature Image</button>
</div>
</div>
</fieldset>
</div>
</div>

View File

@@ -3,6 +3,7 @@
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from contentstore.context_processors import doc_url
from student.roles import CourseCreatorRole, CourseInstructorRole, CourseStaffRole
%>
<%page args="online_help_token"/>
@@ -34,6 +35,7 @@
grading_url = reverse('contentstore.views.grading_handler', kwargs={'course_key_string': unicode(course_key)})
advanced_settings_url = reverse('contentstore.views.advanced_settings_handler', kwargs={'course_key_string': unicode(course_key)})
tabs_url = reverse('contentstore.views.tabs_handler', kwargs={'course_key_string': unicode(course_key)})
certificates_url = reverse('contentstore.views.certificates.certificates_list_handler', kwargs={'course_key_string': unicode(course_key)})
%>
<h2 class="info-course">
<span class="sr">${_("Current Course:")}</span>
@@ -98,6 +100,15 @@
<li class="nav-item nav-course-settings-advanced">
<a href="${advanced_settings_url}">${_("Advanced Settings")}</a>
</li>
% if settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False) and \
(user.is_superuser or user.is_staff or \
CourseCreatorRole(course_key).has_user(user) or \
CourseInstructorRole(course_key).has_user(user) or \
CourseStaffRole(course_key).has_user(user)):
<li class="nav-item nav-course-settings-certificates">
<a href="${certificates_url}">${_("Certificates")}</a>
</li>
% endif
</ul>
</div>
</div>