Commit a3887e95 by David Baumgold Committed by Sarina Canelake

Courseware license (Creative Commons): FED

Use native checkboxes for courseware license options
In Studio settings editor for video module, don't show license if feature-flagged off
Don't let Scope.contents fields leak to Studio editor
JS gettext() must all be on the same line for i18n
Add docstrings for bok-choy tests
Remove LicenseMixin from HTMLDescriptor
Responding to UX review feedback
Add aria-pressed attribute
Use https links instead of protocol-relative links for links to creativecommons.org
Remove license from course outline page in Studio
parent 8fbaa66d
......@@ -339,3 +339,4 @@ if FEATURES['ENABLE_COURSEWARE_INDEX'] or FEATURES['ENABLE_LIBRARY_INDEX']:
SEARCH_ENGINE = "search.elastic.ElasticSearchEngine"
XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
XBLOCK_SETTINGS.setdefault("VideoDescriptor", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
......@@ -143,6 +143,9 @@ FEATURES = {
# Toggle course entrance exams feature
'ENTRANCE_EXAMS': False,
# Toggle platform-wide course licensing
'LICENSING': False,
# Enable the courseware search functionality
'ENABLE_COURSEWARE_INDEX': False,
......@@ -945,3 +948,9 @@ ELASTIC_FIELD_MAPPINGS = {
"type": "date"
}
}
XBLOCK_SETTINGS = {
"VideoDescriptor": {
"licensing_enabled": FEATURES.get("LICENSING", False)
}
}
......@@ -78,6 +78,9 @@ FEATURES['MILESTONES_APP'] = True
################################ ENTRANCE EXAMS ################################
FEATURES['ENTRANCE_EXAMS'] = True
################################ COURSE LICENSES ################################
FEATURES['LICENSING'] = True
################################ SEARCH INDEX ################################
FEATURES['ENABLE_COURSEWARE_INDEX'] = True
FEATURES['ENABLE_LIBRARY_INDEX'] = True
......
......@@ -138,19 +138,19 @@ define(["js/views/license", "js/models/license", "js/common_helpers/template_hel
it("has no preview by default", function () {
this.view.render();
expect(this.view.$("#license-preview").length).toEqual(0)
expect(this.view.$(".license-preview").length).toEqual(0)
this.view.$("li[data-license=creative-commons] button").click();
expect(this.view.$("#license-preview").length).toEqual(0)
expect(this.view.$(".license-preview").length).toEqual(0)
});
it("displays a preview if showPreview is true", function() {
this.view = new LicenseView({model: this.model, showPreview: true});
this.view.render()
expect(this.view.$("#license-preview").length).toEqual(1)
expect(this.view.$("#license-preview")).toHaveText("");
expect(this.view.$(".license-preview").length).toEqual(1)
expect(this.view.$(".license-preview")).toHaveText("");
this.view.$("li[data-license=creative-commons] button").click();
expect(this.view.$("#license-preview").length).toEqual(1)
expect(this.view.$("#license-preview")).toContainText("Some Rights Reserved");
expect(this.view.$(".license-preview").length).toEqual(1)
expect(this.view.$(".license-preview")).toContainText("Some Rights Reserved");
});
})
......
......@@ -7,7 +7,7 @@ define(["js/views/baseview", "underscore"], function(BaseView, _) {
"creative-commons": {
"name": gettext("Creative Commons"),
"tooltip": gettext("You waive some rights for your work, such that others can use it too"),
"url": "//creativecommons.org/about",
"url": "https://creativecommons.org/about",
"options": {
"ver": {
"name": gettext("Version"),
......@@ -18,31 +18,27 @@ define(["js/views/baseview", "underscore"], function(BaseView, _) {
"name": gettext("Attribution"),
"type": "boolean",
"default": true,
"help": gettext("Allow others to copy, distribute, display and perform " +
"your copyrighted work but only if they give credit the way you request."),
"help": gettext("Allow others to copy, distribute, display and perform your copyrighted work but only if they give credit the way you request. Currently, this option is required."),
"disabled": true,
},
"NC": {
"name": gettext("Noncommercial"),
"type": "boolean",
"default": true,
"help": gettext("Allow others to copy, distribute, display and perform " +
"your work - and derivative works based upon it - but for noncommercial purposes only."),
"help": gettext("Allow others to copy, distribute, display and perform your work - and derivative works based upon it - but for noncommercial purposes only."),
},
"ND": {
"name": gettext("No Derivatives"),
"type": "boolean",
"default": true,
"help": gettext("Allow others to copy, distribute, display and perform " +
"only verbatim copies of your work, not derivative works based upon it."),
"help": gettext("Allow others to copy, distribute, display and perform only verbatim copies of your work, not derivative works based upon it. This option is incompatible with \"Share Alike\"."),
"conflictsWith": ["SA"]
},
"SA": {
"name": gettext("Share Alike"),
"type": "boolean",
"default": false,
"help": gettext("Allow others to distribute derivative works only under " +
"a license identical to the license that governs your work."),
"help": gettext("Allow others to distribute derivative works only under a license identical to the license that governs your work. This option is incompatible with \"No Derivatives\"."),
"conflictsWith": ["ND"]
}
},
......@@ -132,6 +128,7 @@ define(["js/views/baseview", "underscore"], function(BaseView, _) {
// fire the change event manually.
this.model.trigger("change change:options")
}
e.preventDefault();
}
});
......
......@@ -276,12 +276,13 @@ var DetailsView = ValidatingView.extend({
this.model.fetch({
success: function() {
self.render();
_.each(self.codeMirrors,
function(mirror) {
var ele = mirror.getTextArea();
var field = self.selectorToField[ele.id];
mirror.setValue(self.model.get(field));
});
_.each(self.codeMirrors, function(mirror) {
var ele = mirror.getTextArea();
var field = self.selectorToField[ele.id];
mirror.setValue(self.model.get(field));
});
self.licenseModel.setFromString(self.model.get("license"), {silent: true});
self.licenseView.render()
},
reset: true,
silent: true});
......
......@@ -172,107 +172,6 @@ form {
}
}
// +Forms - License selector UI
// ====================
.license-img {
padding: 4px;
}
ul.license-types {
text-align: middle;
vertical-align: middle;
display: inline-block;
li {
display: inline-block;
}
.action.license-button {
@include grey-button;
@extend %t-action2;
display: inline-block;
text-align: center;
width: 220px;
height: 40px;
cursor: pointer;
&.is-selected {
@include blue-button;
}
}
.tip {
@extend %t-copy-sub2;
}
}
.wrapper-license-options {
margin-bottom: 10px;
.tip {
@extend %t-copy-sub2;
}
#list-license-options {
padding-bottom: 10px;
li {
line-height: 1.5;
border-bottom: 1px solid #EEE;
padding: ($baseline / 2) 0 ($baseline * 0.4);
&.is-clickable {
cursor: pointer;
}
&.last {
border-bottom: none;
}
.fa {
vertical-align: top;
}
.fa-square-o {
display: inline-block;
margin: ($baseline * 0.15) 15px 0px;
}
.fa-square-o.is-disabled {
color: $gray;
}
.fa-check-square-o {
color: $blue;
display: none;
margin: ($baseline * 0.15) 14px 0px 16px;
}
.fa-check-square-o.is-disabled {
color: $gray;
}
&.is-selected {
.fa-check-square-o {
display: inline-block;
}
.fa-square-o {
display: none;
}
}
.option-name {
@extend %t-action3;
@extend %t-strong;
display: inline-block;
width: 15%;
vertical-align: top;
}
.explanation {
@extend %t-action4;
display: inline-block;
width: 75%;
vertical-align: top;
color: $gray;
}
}
}
}
// +Form - Create New
// ====================
// form styling for creating a new content item (course, user, textbook)
......
// studio - elements - xblock rendering
// ==========================
// Table of Contents
// * +Layout - Xblocks
// * +Licensing - Xblocks
// * +Pagination - Xblocks
// * +Messaging - Xblocks
// * +Case: Page Level
// * +Case: Nesting Level
// * +Case: Element / Component Level
// * +Case: Experiment Groups - Edited
// * +Editing - Xblocks
// * +Case - Special Xblock Type Overrides
// +Layout - Xblocks
// ====================
// styling for xblocks at various levels of nesting: page level,
// extends - UI archetypes - xblock rendering
.wrapper-xblock {
margin: ($baseline/2);
border: 1px solid $gray-l4;
......@@ -45,7 +58,7 @@
}
}
// secondary header for meta-information and associated actions
// UI: secondary header for meta-information and associated actions
.xblock-header-secondary {
overflow: hidden;
border-top: 1px solid $gray-l3;
......@@ -103,28 +116,48 @@
}
}
// +Licensing - Xblocks
// ====================
.xblock-license,
.xmodule_display.xmodule_HtmlModule .xblock-license {
text-align: $bi-app-right;
.xmodule_display.xmodule_HtmlModule .xblock-license,
.xmodule_VideoModule .xblock-license {
@include text-align(right);
@extend %t-title7;
display: block;
width: auto;
border-top: 1px solid $gray-l3;
margin: 0 15px;
padding: 5px;
font-size: 80%;
color: $gray-l3;
padding: ($baseline/4) 0;
color: $gray;
text-align: $bi-app-right;
.license-label,
.license-value,
.license-actions {
display: inline-block;
vertical-align: middle;
margin-bottom: 0;
}
a {
color: $gray-l3;
color: $gray;
&:hover {
color: $ui-link-color;
}
}
span {
color: inherit;
i {
font-style: normal;
}
}
// CASE: xblocks video
.xmodule_VideoModule .xblock-license {
border: 0;
}
// +Pagination - Xblocks
.container-paging-header {
.meta-wrap {
margin: $baseline ($baseline/2);
......@@ -154,12 +187,9 @@
}
}
// ====================
//UI: default internal xblock content styles
// ====================
// TO-DO: clean-up / remove this reset
// internal headings for problems and video components
h2 {
@extend %t-title5;
......@@ -220,6 +250,8 @@
}
}
// +Messaging - Xblocks
// ====================
// xblock message area, for general information as well as validation
.wrapper-xblock-message {
......@@ -281,7 +313,8 @@
}
}
// CASE: page level - outer most level
// +Case: Page Level
// ====================
&.level-page {
margin: 0;
box-shadow: none;
......@@ -325,7 +358,9 @@
}
// CASE: nesting level - element wrapper level
// +Case: Nesting Level
// ====================
// element wrapper level
&.level-nesting {
@include transition(all $tmg-f2 linear 0s);
border: 1px solid $gray-l3;
......@@ -359,7 +394,8 @@
}
}
// CASE: element/component level
// +Case: Element / Component Level
// ====================
&.level-element {
@include transition(all $tmg-f2 linear 0s);
box-shadow: none;
......@@ -416,7 +452,9 @@
}
}
// CASE: edited experiment groups: active and inactive
// +Case: Experiment Groups - Edited
// ====================
// edited experiment groups: active and inactive
.wrapper-groups {
.title {
......@@ -456,8 +494,8 @@
}
}
// +Editing - Xblocks
// ====================
// XBlock editing
// xblock Editor tab wrapper
.wrapper-comp-editor {
......@@ -807,7 +845,10 @@
}
}
// CASE: special xblock type overrides
// +Case - Special Xblock Type Overrides
// ====================
// TO-DO - remove this reset styling from base _xblocks.scss file
// Latex Compiler
// make room for the launch compiler button
......
......@@ -217,27 +217,7 @@
}
}
// course content license
// --------------------
.course-license {
@extend .content-primary;
@include text-align(right);
display: block;
float: $bi-app-right;
width: auto;
.license-label,
.license-value,
.license-actions {
display: inline-block;
vertical-align: middle;
margin-bottom: 0;
}
img {
display: inline;
}
}
// REMOVE BEFORE MERGE - removed outline styling here from cms
.wrapper-dnd {
clear: both;
......
// studio - views - course settings
// ====================
// Table of Contents
// * +Settings - Base / All
// * +Settings - Licenses
// +Settings - Base / All
// ====================
.view-settings {
@include text-align(left);
@include direction();
......@@ -104,10 +109,11 @@
margin-top: ($baseline/4);
color: $gray-d1;
}
.tip-inline{
display: inline;
margin-left: 5px;
}
.tip-inline {
display: inline;
@include margin-left($baseline/4);
}
.message-error {
@extend %t-copy-sub1;
......@@ -149,6 +155,7 @@
}
}
}
#heading-entrance-exam{
font-weight: 600;
}
......@@ -156,6 +163,7 @@
label[for="entrance-exam-enabled"] {
font-size: 14px;
}
.field {
margin: 0 0 ($baseline*2) 0;
......@@ -978,3 +986,100 @@
}
}
}
// +Settings - Licenses
// ====================
.view-settings {
.license-img {
padding: ($baseline/5);
}
.license-types {
text-align: center;
vertical-align: middle;
display: inline-block;
.license-type {
display: inline-block;
}
.action.license-button {
@include grey-button;
@extend %t-action2;
display: inline-block;
text-align: center;
width: 220px;
height: 40px;
cursor: pointer;
&.is-selected {
@include blue-button;
}
}
.tip {
@extend %t-copy-sub2;
}
}
.wrapper-license-options {
margin-bottom: ($baseline/2);
.tip {
@extend %t-copy-sub2;
}
.license-options {
padding-bottom: ($baseline/2);
.license-option {
line-height: 1.5;
border-bottom: 1px solid $gray-l4;
padding: ($baseline/2) 0 ($baseline*0.4);
&.is-clickable {
cursor: pointer;
}
&:last-child {
border-bottom: none;
}
input[type=checkbox] {
vertical-align: top;
width: auto;
min-width: auto;
height: auto;
border: 0;
margin: ($baseline*0.15) 15px 0px;
}
.option-name {
@extend %t-action3;
@extend %t-strong;
display: inline-block;
width: 15%;
vertical-align: top;
cursor: pointer;
}
.explanation {
@extend %t-action4;
display: inline-block;
width: 75%;
vertical-align: top;
color: $gray;
}
}
}
}
.license-preview a {
color: $gray;
&:hover {
color: $ui-link-color;
}
}
.list-input.settings-list ul.license-options li {
// to make sure the padding is correctly overridden
padding: ($baseline / 2) 0 ($baseline * 0.4);
}
}
\ No newline at end of file
......@@ -109,13 +109,6 @@ from contentstore.utils import reverse_usage_url
</div>
</div>
% if settings.FEATURES.get("LICENSING", False):
<div class="course-license">
<h2 class="license-label">${_("License:")}</h2>
<p class="license-value"><%include file="license.html" args="license=context_course.license" /></p>
</div>
% endif
<div class="wrapper-dnd">
<%
course_locator = context_course.location
......
<div class="license-wrapper">
<div class="wrapper-license">
<h3 class="label setting-label">
<%= gettext("License Type") %>
</h3>
<ul class="license-types">
<% var link_start_tpl = '<a href="{url}" target="_blank">'; %>
<% _.each(licenseInfo, function(license, licenseType) { %>
<li data-license="<%- licenseType %>">
<li class="license-type" data-license="<%- licenseType %>">
<button name="license-<%- licenseType %>"
class="action license-button <% if(model.type === licenseType) { print("is-selected"); } %>"
aria-pressed="<%- (model.type === licenseType).toString() %>"
<% if (license.tooltip) { %>data-tooltip="<%- license.tooltip %>"<% } %>>
<%- license.name %>
</button>
......@@ -45,31 +46,23 @@
(optionInfo.conflictsWith && _.any(optionInfo.conflictsWith, function(key) {return model.options[key];}));
%>
<li data-option="<%- optionKey %>"
class="action-item license-button
<% if (optionSelected) { print("is-selected"); } %>
<% if (optionDisabled) { print("is-disabled"); } else { print("is-clickable"); } %>"
class="action-item license-option
<% if (optionSelected) { print("is-selected"); } %>
<% if (optionDisabled) { print("is-disabled"); } else { print("is-clickable"); } %>"
>
<i aria-hidden="true"
class="fa fa-fw
<% if(optionSelected) { print("fa-check-square-o"); } else { print("fa-square-o"); } %>
<% if(optionDisabled) { print("is-disabled"); } %>
"></i>
<h4 class="option-name">
<input type="checkbox"
id="<%- model.type %>-<%- optionKey %>"
name="<%- model.type %>-<%- optionKey %>"
aria-describedby="<%- optionKey %>-explanation"
<% if(optionSelected) { print('checked="checked"'); } %>
<% if(optionDisabled) { print('disabled="disabled"'); } %>
/>
<label for="<%- model.type %>-<%- optionKey %>" class="option-name">
<%- optionInfo.name %>
<%
var states = [];
if (optionSelected) {
states.push(gettext("selected"));
}
if (optionDisabled) {
states.push(gettext("disabled"));
}
if (states) {
%>
<span class="sr">, <%- states.join(", ") %></span>
<% } %>
</h4>
<div class="explanation"><%- optionInfo.help %></div>
</label>
<div id="<%- optionKey %>-explanation" class="explanation">
<%- optionInfo.help %>
</div>
</li>
<% } // could implement other types here %>
<% }) %>
......@@ -78,7 +71,7 @@
<% } %>
<% if (showPreview) { %>
<div class="license-preview-wrapper">
<div class="wrapper-license-preview">
<h4 class="label setting-label">
<%= gettext("License Display") %>
</h4>
......@@ -100,7 +93,7 @@
version = model.options.ver || "1.0";
}
%>
<a rel="license" href="//creativecommons.org/licenses/<%- enabled.join("-") %>/<%- version %>/">
<a rel="license" href="https://creativecommons.org/licenses/<%- enabled.join("-") %>/<%- version %>/">
<% if (previewButton) { %>
<img src="https://licensebuttons.net/l/<%- enabled.join("-") %>/<%- version %>/<%- typeof buttonSize == "string" ? buttonSize : "88x31" %>.png"
alt="<%- typeof licenseString == "string" ? licenseString : "" %>"
......
......@@ -20,7 +20,6 @@ from xmodule.x_module import XModule, DEPRECATION_VSCOMPAT_EVENT
from xmodule.xml_module import XmlDescriptor, name_to_pathname
from xblock.core import XBlock
from xblock.fields import Scope, String, Boolean, List
from xmodule.mixin import LicenseMixin
log = logging.getLogger("edx.courseware")
......@@ -88,7 +87,7 @@ class HtmlModule(HtmlModuleMixin):
pass
class HtmlDescriptor(HtmlFields, LicenseMixin, XmlDescriptor, EditingDescriptor): # pylint: disable=abstract-method
class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor): # pylint: disable=abstract-method
"""
Module for putting raw html in a course
"""
......
......@@ -49,6 +49,7 @@ from xmodule.modulestore.edit_info import EditInfoRuntimeMixin
from xmodule.modulestore.exceptions import ItemNotFoundError, DuplicateCourseError, ReferentialIntegrityError
from xmodule.modulestore.inheritance import InheritanceMixin, inherit_metadata, InheritanceKeyValueStore
from xmodule.modulestore.xml import CourseLocationManager
from xmodule.services import SettingsService
log = logging.getLogger(__name__)
......@@ -900,6 +901,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase, Mongo
if self.user_service:
services["user"] = self.user_service
services["settings"] = SettingsService()
system = CachingDescriptorSystem(
modulestore=self,
......
......@@ -181,6 +181,15 @@ class SequenceDescriptor(SequenceFields, MakoModuleDescriptor, XmlDescriptor):
self.runtime.add_block_as_child_node(child, xml_object)
return xml_object
@property
def non_editable_metadata_fields(self):
"""
`is_entrance_exam` should not be editable in the Studio settings editor.
"""
non_editable_fields = super(SequenceDescriptor, self).non_editable_metadata_fields
non_editable_fields.append(self.fields['is_entrance_exam'])
return non_editable_fields
def index_dictionary(self):
"""
Return dictionary prepared with module content and type for indexing.
......
......@@ -473,7 +473,8 @@ class SplitTestDescriptor(SplitTestFields, SequenceDescriptor, StudioEditableDes
non_editable_fields = super(SplitTestDescriptor, self).non_editable_metadata_fields
non_editable_fields.extend([
SplitTestDescriptor.due,
SplitTestDescriptor.user_partitions
SplitTestDescriptor.user_partitions,
SplitTestDescriptor.group_id_to_child,
])
return non_editable_fields
......
......@@ -25,6 +25,7 @@ from pkg_resources import resource_string
from django.conf import settings
from xblock.core import XBlock
from xblock.fields import ScopeIds
from xblock.runtime import KvsFieldData
......@@ -287,6 +288,7 @@ class VideoModule(VideoFields, VideoTranscriptsMixin, VideoStudentViewHandlers,
})
@XBlock.wants("settings")
class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandlers,
TabsEditingDescriptor, EmptyDataRawDescriptor):
"""
......@@ -384,6 +386,12 @@ class VideoDescriptor(VideoFields, VideoTranscriptsMixin, VideoStudioViewHandler
def editable_metadata_fields(self):
editable_fields = super(VideoDescriptor, self).editable_metadata_fields
settings_service = self.runtime.service(self, 'settings')
if settings_service:
xb_settings = settings_service.get_settings_bucket(self)
if not xb_settings.get("licensing_enabled", False) and "license" in editable_fields:
del editable_fields["license"]
if self.source_visible:
editable_fields['source']['non_editable'] = True
else:
......
[class^="icon-cc"], [class*=" icon-cc"] {
font-family: 'edx-cc';
font-style: normal;
font-weight: normal;
/* fix buttons height */
line-height: 1em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
}
.icon-cc { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
.icon-cc-by { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
.icon-cc-nc { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
.icon-cc-nc-eu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
.icon-cc-nc-jp { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
.icon-cc-sa { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
.icon-cc-nd { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
.icon-cc-pd { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
.icon-cc-zero { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }
.icon-cc-share { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe809;&nbsp;'); }
.icon-cc-remix { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80a;&nbsp;'); }
......@@ -8,46 +8,9 @@
font-weight: normal;
font-style: normal;
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
/*
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'edx-cc';
src: url('../fonts/edx-cc/edx-cc.svg?52318265#edx-cc') format('svg');
}
}
*/
[class^="icon-cc"]:before, [class*=" icon-cc"]:before {
[class^="icon-cc"]:before, [class*=" icon-cc"]:before {
font-family: "edx-cc";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: .2em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.icon-cc:before { content: '\e800'; } /* '' */
......
// studio - utilities - mixins and extends
// common - utilities - mixins and extends
// ====================
// Table of Contents
......@@ -427,3 +427,4 @@
white-space: nowrap;
text-overflow: ellipsis;
}
......@@ -36,7 +36,7 @@ def parse_license(lic):
enabled = ["zero"]
version = license_options.get("ver", "1.0")
%>
<a rel="license" href="//creativecommons.org/licenses/${'-'.join(enabled)}/${version}/">
<a rel="license" href="https://creativecommons.org/licenses/${'-'.join(enabled)}/${version}/">
% if button:
<img src="https://licensebuttons.net/l/${'-'.join(enabled)}/${version}/${button_size}.png"
alt="${license}"
......
......@@ -19,6 +19,13 @@ class SettingsPage(CoursePage):
def is_browser_on_page(self):
return self.q(css='body.view-settings').present
def refresh_and_wait_for_load(self):
"""
Refresh the page and wait for all resources to load.
"""
self.browser.refresh()
self.wait_for_page()
def get_elements(self, css_selector):
self.wait_for_element_presence(
css_selector,
......@@ -72,16 +79,35 @@ class SettingsPage(CoursePage):
'Entrance exam minimum score percent is invisible'
)
def set_course_license(self, license_type):
css_selector = (
"section.license ul.license-types "
"li[data-license={license_type}] button"
).format(license_type=license_type)
@property
def course_license(self):
license_types_css = "section.license ul.license-types li.license-type"
self.wait_for_element_presence(
css_selector,
'{license_type} button is present'.format(license_type=license_type)
license_types_css,
"license type buttons are present",
)
selected = self.q(css=license_types_css + " button.is-selected")
if selected.is_present():
return selected.text[0]
return None
@course_license.setter
def course_license(self, license_name):
license_types_css = "section.license ul.license-types li.license-type"
self.wait_for_element_presence(
license_types_css,
"license type buttons are present",
)
self.q(css=css_selector).click()
button_xpath = (
"//section[contains(@class, 'license')]"
"//ul[contains(@class, 'license-types')]"
"//li[contains(@class, 'license-type')]"
"//button[contains(text(),'{license_name}')]"
).format(license_name=license_name)
button = self.q(xpath=button_xpath)
if not button.present:
raise Exception("Invalid license name: {name}".format(name=license_name))
button.click()
def save_changes(self, wait_for_confirmation=True):
"""
......
......@@ -424,27 +424,49 @@ class ContentLicenseTest(StudioCourseTest):
self.browser,
self.course_id,
)
self.outline_page.visit()
self.settings_page.visit()
def test_empty_license(self):
self.assertEqual(self.outline_page.license, "None")
"""
When I visit the Studio settings page,
I see that the course license is "None" by default.
Then I visit the LMS courseware page,
and I see that there is no course license displayed.
"""
self.assertIsNone(self.settings_page.course_license)
self.lms_courseware.visit()
self.assertIsNone(self.lms_courseware.course_license)
def test_arr_license(self):
self.outline_page.edit_course_start_date()
self.settings_page.set_course_license("all-rights-reserved")
"""
When I visit the Studio settings page,
and I set the course license to "All Rights Reserved",
and I refresh the page,
I see that the course license is "All Rights Reserved".
Then I visit the LMS courseware page,
and I see that the course license is "All Rights Reserved".
"""
self.settings_page.course_license = "All Rights Reserved"
self.settings_page.save_changes()
self.outline_page.visit()
self.assertEqual(self.outline_page.license, "© All Rights Reserved")
self.settings_page.refresh_and_wait_for_load()
self.assertEqual(self.settings_page.course_license, "All Rights Reserved")
self.lms_courseware.visit()
self.assertEqual(self.lms_courseware.course_license, "© All Rights Reserved")
def test_cc_license(self):
self.outline_page.edit_course_start_date()
self.settings_page.set_course_license("creative-commons")
"""
When I visit the Studio settings page,
and I set the course license to "Creative Commons",
and I refresh the page,
I see that the course license is "Creative Commons".
Then I visit the LMS courseware page,
and I see that the course license is "Some Rights Reserved".
"""
self.settings_page.course_license = "Creative Commons"
self.settings_page.save_changes()
self.outline_page.visit()
self.assertEqual(self.outline_page.license, "Some Rights Reserved")
self.settings_page.refresh_and_wait_for_load()
self.assertEqual(self.settings_page.course_license, "Creative Commons")
self.lms_courseware.visit()
self.assertEqual(self.lms_courseware.course_license, "Some Rights Reserved")
......@@ -28,6 +28,12 @@ class VideoLicenseTest(StudioCourseTest):
# used by StudioCourseTest.setUp()
def populate_course_fixture(self, course_fixture):
"""
Create a course with a single chapter.
That chapter has a single section.
That section has a single vertical.
That vertical has a single video element.
"""
video_block = XBlockFixtureDesc('video', "Test Video")
vertical = XBlockFixtureDesc('vertical', "Test Vertical")
vertical.add_children(video_block)
......@@ -38,6 +44,11 @@ class VideoLicenseTest(StudioCourseTest):
self.course_fixture.add_children(chapter)
def test_empty_license(self):
"""
When I visit the LMS courseware,
I can see that the video is present
but it has no license displayed by default.
"""
self.lms_courseware.visit()
video = self.lms_courseware.q(css=".vert .xblock .video")
self.assertTrue(video.is_present())
......@@ -45,6 +56,13 @@ class VideoLicenseTest(StudioCourseTest):
self.assertFalse(video_license.is_present())
def test_arr_license(self):
"""
When I edit a video element in Studio,
I can set an "All Rights Reserved" license on that video element.
When I visit the LMS courseware,
I can see that the video is present
and that it has "All Rights Reserved" displayed for the license.
"""
self.studio_course_outline.visit()
subsection = self.studio_course_outline.section_at(0).subsection_at(0)
subsection.expand_subsection()
......@@ -65,6 +83,13 @@ class VideoLicenseTest(StudioCourseTest):
self.assertEqual(video_license.text[0], "© All Rights Reserved")
def test_cc_license(self):
"""
When I edit a video element in Studio,
I can set a "Creative Commons" license on that video element.
When I visit the LMS courseware,
I can see that the video is present
and that it has "Some Rights Reserved" displayed for the license.
"""
self.studio_course_outline.visit()
subsection = self.studio_course_outline.section_at(0).subsection_at(0)
subsection.expand_subsection()
......
......@@ -576,6 +576,7 @@ FACEBOOK_APP_SECRET = AUTH_TOKENS.get("FACEBOOK_APP_SECRET")
FACEBOOK_APP_ID = AUTH_TOKENS.get("FACEBOOK_APP_ID")
XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
XBLOCK_SETTINGS.setdefault("VideoDescriptor", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
##### CDN EXPERIMENT/MONITORING FLAGS #####
CDN_VIDEO_URLS = ENV_TOKENS.get('CDN_VIDEO_URLS', CDN_VIDEO_URLS)
......
......@@ -1277,7 +1277,6 @@ PIPELINE_CSS = {
'source_filenames': [
'sass/lms-main.css',
'css/edx-cc.css',
'css/edx-cc-ie7.css',
],
'output_filename': 'css/lms-main.css',
},
......
......@@ -137,25 +137,6 @@ a:focus {
min-width: 760px;
width: flex-grid(12);
}
.container-footer {
max-width: grid-width(12);
min-width: 760px;
width: flex-grid(12);
margin: 0 auto;
text-align: $bi-app-right;
color: $gray-l2;
span {
color: inherit;
}
a:link, a:visited {
color: $gray-l2;
}
a:active, a:hover {
color: $blue;
}
}
span.edx {
text-transform: none;
......
// lms - course - base
// ====================
// Table of Contents
// * +Containers
// * +Resets - Old, Body
// * +Resets - Old, Forms
// * +Resets - Old, Images
// * +Resets - Old, Misc
// +Containers
// ====================
.content-wrapper {
background: none;
border: none;
}
.container {
padding: 0;
> div {
display: table;
table-layout: fixed;
width: 100%;
border-radius: 3px;
border: 1px solid $outer-border-color;
background: $container-bg;
box-shadow: 0 1px 2px $shadow-l2;
}
}
// +Resets - Old, Body
// ====================
body {
min-width: 980px;
min-height: 100%;
......@@ -26,25 +60,8 @@ a {
}
}
.content-wrapper {
background: none;
border: none;
}
.container {
padding: 0;
> div {
display: table;
table-layout: fixed;
width: 100%;
border-radius: 3px;
border: 1px solid $outer-border-color;
background: $container-bg;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
}
// +Resets - Old, Forms
// ====================
form {
label {
display: block;
......@@ -102,6 +119,8 @@ button,
}
// +Resets - Old, Images
// ====================
img {
max-width: 100%;
}
......@@ -134,6 +153,9 @@ img {
}
}
// +Resets - Old, Misc
// ====================
.test-class {
border: 1px solid #f00;
}
......
......@@ -41,61 +41,63 @@ html.video-fullscreen{
}
}
div.course-wrapper {
position: relative;
.content-wrapper {
.container-footer {
margin: 0 auto;
max-width: grid-width(12);
min-width: 760px;
width: flex-grid(12);
color: $gray;
text-align: $bi-app-right;
}
}
.course-license {
position: absolute;
margin: 5px auto;
bottom: 0;
text-align: center;
width: 100%;
color: $gray-l3;
.content-wrapper {
.course-license, .xblock-license {
@include text-align(right);
@extend %t-title7;
display: block;
width: auto;
padding: ($baseline/4) 0;
span {
color: inherit;
}
a:link, a:visited {
color: $gray-l3;
color: $gray;
}
a:active, a:hover {
color: $link-hover;
}
}
.xblock-license {
text-align: $bi-app-right;
border-top: 1px solid $gray-l3;
margin: 0 15px;
padding: 5px;
font-size: 80%;
span {
color: inherit;
.license-label,
.license-value,
.license-actions {
display: inline-block;
vertical-align: middle;
margin-bottom: 0;
}
&, a {
color: $gray-l3;
&:hover {
color: $link-hover;
}
i {
font-style: normal;
}
}
.xmodule_display .xblock-license,
.xmodule_display .xblock-license a {
color: $gray-l3;
&:hover {
color: $link-hover;
img {
display: inline;
}
}
}
.xmodule_VideoModule .xblock-license {
border: 0;
}
// TO-DO should this be content wrapper?
div.course-wrapper {
position: relative;
section.course-content {
@extend .content;
padding: 40px;
padding: ($baseline*2);
line-height: 1.6;
h1 {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment