Merge branch 'master' into feature/cas/manual-policy-merged
This commit is contained in:
@@ -59,4 +59,4 @@ def i_am_on_tab(step, tab_name):
|
||||
@step('I see a link for adding a new section$')
|
||||
def i_see_new_section_link(step):
|
||||
link_css = 'a.new-courseware-section-button'
|
||||
assert_css_with_text(link_css, 'New Section')
|
||||
assert_css_with_text(link_css, '+ New Section')
|
||||
|
||||
@@ -342,7 +342,7 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
# Create a course so there is something to view
|
||||
resp = self.client.get(reverse('index'))
|
||||
self.assertContains(resp,
|
||||
'<h1>My Courses</h1>',
|
||||
'<h1 class="title-1">My Courses</h1>',
|
||||
status_code=200,
|
||||
html=True)
|
||||
|
||||
|
||||
@@ -131,7 +131,8 @@ def index(request):
|
||||
reverse('course_index', args=[
|
||||
course.location.org,
|
||||
course.location.course,
|
||||
course.location.name]))
|
||||
course.location.name]),
|
||||
get_lms_link_for_item(course.location))
|
||||
for course in courses],
|
||||
'user': request.user,
|
||||
'disable_course_creation': settings.MITX_FEATURES.get('DISABLE_COURSE_CREATION', False) and not request.user.is_staff
|
||||
@@ -172,6 +173,8 @@ def course_index(request, org, course, name):
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
lms_link = get_lms_link_for_item(location)
|
||||
|
||||
upload_asset_callback_url = reverse('upload_asset', kwargs={
|
||||
'org': org,
|
||||
'course': course,
|
||||
@@ -184,6 +187,7 @@ def course_index(request, org, course, name):
|
||||
return render_to_response('overview.html', {
|
||||
'active_tab': 'courseware',
|
||||
'context_course': course,
|
||||
'lms_link': lms_link,
|
||||
'sections': sections,
|
||||
'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders),
|
||||
'parent_location': course.location,
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
class CMS.Views.TabsEdit extends Backbone.View
|
||||
events:
|
||||
'click .new-tab': 'addNewTab'
|
||||
|
||||
initialize: =>
|
||||
@$('.component').each((idx, element) =>
|
||||
@@ -13,6 +11,7 @@ class CMS.Views.TabsEdit extends Backbone.View
|
||||
)
|
||||
)
|
||||
|
||||
@options.mast.find('.new-tab').on('click', @addNewTab)
|
||||
@$('.components').sortable(
|
||||
handle: '.drag-handle'
|
||||
update: @tabMoved
|
||||
|
||||
BIN
cms/static/img/preview-lms-staticpages.png
Normal file
BIN
cms/static/img/preview-lms-staticpages.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
@@ -43,6 +43,12 @@ $(document).ready(function () {
|
||||
|
||||
$('body').addClass('js');
|
||||
|
||||
// lean/simple modal
|
||||
$('a[rel*=modal]').leanModal({overlay : 0.80, closeButton: '.action-modal-close' });
|
||||
$('a.action-modal-close').click(function(e){
|
||||
(e).preventDefault();
|
||||
});
|
||||
|
||||
// nav - dropdown related
|
||||
$body.click(function (e) {
|
||||
$('.nav-dropdown .nav-item .wrapper-nav-sub').removeClass('is-shown');
|
||||
@@ -638,7 +644,7 @@ function addNewCourse(e) {
|
||||
$(e.target).hide();
|
||||
var $newCourse = $($('#new-course-template').html());
|
||||
var $cancelButton = $newCourse.find('.new-course-cancel');
|
||||
$('.new-course-button').after($newCourse);
|
||||
$('.inner-wrapper').prepend($newCourse);
|
||||
$newCourse.find('.new-course-name').focus().select();
|
||||
$newCourse.find('form').bind('submit', saveNewCourse);
|
||||
$cancelButton.bind('click', cancelNewCourse);
|
||||
@@ -822,4 +828,4 @@ function saveSetSectionScheduleDate(e) {
|
||||
|
||||
hideModal();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ CMS.Views.CourseInfoEdit = Backbone.View.extend({
|
||||
render: function() {
|
||||
// instantiate the ClassInfoUpdateView and delegate the proper dom to it
|
||||
new CMS.Views.ClassInfoUpdateView({
|
||||
el: this.$('#course-update-view'),
|
||||
el: $('body.updates'),
|
||||
collection: this.model.get('updates')
|
||||
});
|
||||
|
||||
@@ -27,10 +27,10 @@ CMS.Views.ClassInfoUpdateView = Backbone.View.extend({
|
||||
// collection is CourseUpdateCollection
|
||||
events: {
|
||||
"click .new-update-button" : "onNew",
|
||||
"click .save-button" : "onSave",
|
||||
"click .cancel-button" : "onCancel",
|
||||
"click .edit-button" : "onEdit",
|
||||
"click .delete-button" : "onDelete"
|
||||
"click #course-update-view .save-button" : "onSave",
|
||||
"click #course-update-view .cancel-button" : "onCancel",
|
||||
"click .post-actions > .edit-button" : "onEdit",
|
||||
"click .post-actions > .delete-button" : "onDelete"
|
||||
},
|
||||
|
||||
initialize: function() {
|
||||
|
||||
@@ -50,7 +50,142 @@ h1 {
|
||||
|
||||
// ====================
|
||||
|
||||
// layout - basic
|
||||
// layout - basic page header
|
||||
.wrapper-mast {
|
||||
margin: 0;
|
||||
padding: 0 $baseline;
|
||||
position: relative;
|
||||
|
||||
.mast, .metadata {
|
||||
@include clearfix();
|
||||
@include font-size(16);
|
||||
position: relative;
|
||||
max-width: $fg-max-width;
|
||||
min-width: $fg-min-width;
|
||||
width: flex-grid(12);
|
||||
margin: 0 auto $baseline auto;
|
||||
color: $gray-d2;
|
||||
}
|
||||
|
||||
.mast {
|
||||
border-bottom: 1px solid $gray-l4;
|
||||
padding-bottom: ($baseline/2);
|
||||
|
||||
.title-sub {
|
||||
@include font-size(14);
|
||||
position: relative;
|
||||
top: ($baseline/4);
|
||||
display: block;
|
||||
margin: 0;
|
||||
color: $gray-l2;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.title, .title-1 {
|
||||
@include font-size(32);
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: 600;
|
||||
color: $gray-d3;
|
||||
}
|
||||
|
||||
.nav-hierarchy {
|
||||
@include font-size(14);
|
||||
display: block;
|
||||
margin: 0;
|
||||
color: $gray-l2;
|
||||
font-weight: 400;
|
||||
|
||||
.nav-item {
|
||||
display: inline;
|
||||
vertical-align: middle;
|
||||
margin-right: ($baseline/4);
|
||||
|
||||
&:after {
|
||||
content: ">>";
|
||||
margin-left: ($baseline/4);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
|
||||
&:after {
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// layout with actions
|
||||
.title {
|
||||
width: flex-grid(12);
|
||||
}
|
||||
|
||||
// layout with actions
|
||||
&.has-actions {
|
||||
@include clearfix();
|
||||
|
||||
.title {
|
||||
float: left;
|
||||
width: flex-grid(6,12);
|
||||
margin-right: flex-gutter();
|
||||
}
|
||||
|
||||
.nav-actions {
|
||||
position: relative;
|
||||
bottom: -($baseline*0.75);
|
||||
float: right;
|
||||
width: flex-grid(6,12);
|
||||
text-align: right;
|
||||
|
||||
.nav-item {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-right: ($baseline/2);
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// buttons
|
||||
.button {
|
||||
padding: ($baseline/4) ($baseline/2) ($baseline/3) ($baseline/2) !important;
|
||||
font-weight: 400 !important;
|
||||
}
|
||||
|
||||
.new-button {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
.view-button {
|
||||
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
.upload-button .icon-create {
|
||||
@include font-size(18);
|
||||
margin-top: ($baseline/4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// layout with actions
|
||||
&.has-subtitle {
|
||||
|
||||
.nav-actions {
|
||||
bottom: -($baseline*1.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// page metadata/action bar
|
||||
.metadata {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// layout - basic page content
|
||||
.wrapper-content {
|
||||
margin: 0;
|
||||
padding: 0 $baseline;
|
||||
@@ -89,8 +224,39 @@ h1 {
|
||||
}
|
||||
|
||||
.introduction {
|
||||
@include box-sizing(border-box);
|
||||
@include font-size(14);
|
||||
width: flex-grid(12);
|
||||
margin: 0 0 $baseline 0;
|
||||
|
||||
.copy strong {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
&.has-links {
|
||||
@include clearfix();
|
||||
|
||||
.copy {
|
||||
float: left;
|
||||
width: flex-grid(8,12);
|
||||
margin-right: flex-gutter();
|
||||
}
|
||||
|
||||
.nav-introduction-supplementary {
|
||||
@include font-size(13);
|
||||
float: right;
|
||||
width: flex-grid(4,12);
|
||||
display: block;
|
||||
text-align: right;
|
||||
|
||||
.icon {
|
||||
@include font-size(14);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: ($baseline/4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +264,7 @@ h1 {
|
||||
@include box-sizing(border-box);
|
||||
}
|
||||
|
||||
// layout - primary content
|
||||
.content-primary {
|
||||
|
||||
.title-1, .title-2, .title-3, .title-4, .title-5, .title-5 {
|
||||
@@ -129,6 +296,7 @@ h1 {
|
||||
}
|
||||
}
|
||||
|
||||
// layout - supplemental content
|
||||
.content-supplementary {
|
||||
|
||||
.bit {
|
||||
@@ -494,22 +662,49 @@ hr.divide {
|
||||
|
||||
.new-button {
|
||||
@include green-button;
|
||||
font-size: 13px;
|
||||
@include font-size(13);
|
||||
padding: 8px 20px 10px;
|
||||
text-align: center;
|
||||
|
||||
&.big {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.icon-create {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: ($baseline/4);
|
||||
margin-top: ($baseline/10);
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.view-button {
|
||||
@include blue-button;
|
||||
@include font-size(13);
|
||||
text-align: center;
|
||||
|
||||
&.big {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.icon-view {
|
||||
@include font-size(15);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: ($baseline/2);
|
||||
margin-top: ($baseline/5);
|
||||
line-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-button.standard,
|
||||
.delete-button.standard {
|
||||
float: left;
|
||||
@include font-size(12);
|
||||
@include white-button;
|
||||
float: left;
|
||||
padding: 3px 10px 4px;
|
||||
margin-left: 7px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
|
||||
.edit-icon,
|
||||
@@ -579,6 +774,72 @@ hr.divide {
|
||||
|
||||
// ====================
|
||||
|
||||
// js dependant
|
||||
body.js {
|
||||
|
||||
// lean/simple modal window
|
||||
.content-modal {
|
||||
@include border-bottom-radius(2px);
|
||||
@include box-sizing(border-box);
|
||||
@include box-shadow(0 2px 4px $shadow-d1);
|
||||
position: relative;
|
||||
display: none;
|
||||
width: 700px;
|
||||
overflow: hidden;
|
||||
border: 1px solid $gray-d1;
|
||||
padding: ($baseline);
|
||||
background: $white;
|
||||
|
||||
.action-modal-close {
|
||||
@include transition(top .25s ease-in-out);
|
||||
@include border-bottom-radius(3px);
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
right: $baseline;
|
||||
padding: ($baseline/4) ($baseline/2) 0 ($baseline/2);
|
||||
background: $gray-l3;
|
||||
text-align: center;
|
||||
|
||||
.label {
|
||||
@include text-sr();
|
||||
}
|
||||
|
||||
.ss-icon {
|
||||
@include font-size(18);
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $blue;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
@include box-sizing(border-box);
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
padding: ($baseline/10);
|
||||
border: 1px solid $gray-l4;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include font-size(18);
|
||||
margin: 0 0 ($baseline/2) 0;
|
||||
font-weight: 600;
|
||||
color: $gray-d3;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include font-size(13);
|
||||
margin-top: ($baseline/2);
|
||||
color: $gray-l1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ====================
|
||||
|
||||
// works in progress
|
||||
body.hide-wip {
|
||||
|
||||
|
||||
@@ -37,6 +37,11 @@
|
||||
padding: 34px 0 42px;
|
||||
border-top: 1px solid #cbd1db;
|
||||
|
||||
&:first-child {
|
||||
padding-top: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.editing {
|
||||
position: relative;
|
||||
z-index: 1001;
|
||||
|
||||
@@ -6,19 +6,27 @@
|
||||
@include box-shadow(0 1px 2px rgba(0, 0, 0, .1));
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
border-bottom: 1px solid $mediumGrey;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
padding: 20px 25px;
|
||||
line-height: 1.3;
|
||||
|
||||
&:hover {
|
||||
background: $paleYellow;
|
||||
.class-link {
|
||||
z-index: 100;
|
||||
display: block;
|
||||
padding: 20px 25px;
|
||||
line-height: 1.3;
|
||||
|
||||
&:hover {
|
||||
background: $paleYellow;
|
||||
|
||||
+ .view-live-button {
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +42,22 @@
|
||||
margin-right: 20px;
|
||||
color: #3c3c3c;
|
||||
}
|
||||
|
||||
// view live button
|
||||
.view-live-button {
|
||||
z-index: 10000;
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: $baseline;
|
||||
padding: ($baseline/4) ($baseline/2);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.new-course {
|
||||
|
||||
@@ -350,67 +350,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// js dependant
|
||||
&.js {
|
||||
|
||||
.content-modal {
|
||||
@include border-bottom-radius(2px);
|
||||
@include box-sizing(border-box);
|
||||
@include box-shadow(0 2px 4px $shadow-d1);
|
||||
position: relative;
|
||||
display: none;
|
||||
width: 700px;
|
||||
overflow: hidden;
|
||||
border: 1px solid $gray-d1;
|
||||
padding: ($baseline);
|
||||
background: $white;
|
||||
|
||||
.action-modal-close {
|
||||
@include transition(top .25s ease-in-out);
|
||||
@include border-bottom-radius(3px);
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
right: $baseline;
|
||||
padding: ($baseline/4) ($baseline/2) 0 ($baseline/2);
|
||||
background: $gray-l3;
|
||||
text-align: center;
|
||||
|
||||
.label {
|
||||
@include text-sr();
|
||||
}
|
||||
|
||||
.ss-icon {
|
||||
@include font-size(18);
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $blue;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
@include box-sizing(border-box);
|
||||
width: 100%;
|
||||
overflow-y: scroll;
|
||||
padding: ($baseline/10);
|
||||
border: 1px solid $gray-l4;
|
||||
}
|
||||
|
||||
.title {
|
||||
@include font-size(18);
|
||||
margin: 0 0 ($baseline/2) 0;
|
||||
font-weight: 600;
|
||||
color: $gray-d3;
|
||||
}
|
||||
|
||||
.description {
|
||||
@include font-size(13);
|
||||
margin-top: ($baseline/2);
|
||||
color: $gray-l1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,8 +28,9 @@
|
||||
border-radius: 0;
|
||||
|
||||
&.new-component-item {
|
||||
margin-top: 20px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
@include box-shadow(none);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,12 +33,27 @@
|
||||
</tr>
|
||||
</script>
|
||||
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-actions has-subtitle">
|
||||
<div class="title">
|
||||
<span class="title-sub">Course Content</span>
|
||||
<h1 class="title-1">Files & Uploads</h1>
|
||||
</div>
|
||||
|
||||
<nav class="nav-actions">
|
||||
<h3 class="sr">Page Actions</h3>
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="button upload-button new-button"><i class="ss-icon ss-symbolicons-standard icon icon-create"></i> Upload New File</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<div class="page-actions">
|
||||
<a href="#" class="upload-button new-button">
|
||||
<span class="upload-icon"></span>Upload New Asset
|
||||
</a>
|
||||
<input type="text" class="asset-search-input search wip-box" placeholder="search assets" style="display:none"/>
|
||||
</div>
|
||||
<article class="asset-library">
|
||||
|
||||
@@ -42,16 +42,38 @@
|
||||
</%block>
|
||||
|
||||
<%block name="content">
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-actions has-subtitle">
|
||||
<div class="title">
|
||||
<span class="title-sub">Course Content</span>
|
||||
<h1 class="title-1">Course Updates</h1>
|
||||
</div>
|
||||
|
||||
<nav class="nav-actions">
|
||||
<h3 class="sr">Page Actions</h3>
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<a href="#" class=" button new-button new-update-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">+</i> New Update</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<div class="introduction">
|
||||
<p clas="copy">Course updates are announcements or notifications you want to share with your class. Other course authors have used them for important exam/date reminders, change in schedules, and to call out any important steps students need to be aware of.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<h1>Course Info</h1>
|
||||
<div class="course-info-wrapper">
|
||||
<div class="main-column window">
|
||||
<article class="course-updates" id="course-update-view">
|
||||
<h2>Course Updates & News</h2>
|
||||
<a href="#" class="new-update-button">New Update</a>
|
||||
<ol class="update-list" id="course-update-list"></ol>
|
||||
<!-- probably replace w/ a vertical where each element of the vertical is a separate update w/ a date and html field -->
|
||||
</article>
|
||||
</div>
|
||||
<div class="sidebar window course-handouts" id="course-handouts-view"></div>
|
||||
|
||||
@@ -9,25 +9,49 @@
|
||||
el: $('.main-wrapper'),
|
||||
model: new CMS.Models.Module({
|
||||
id: '${context_course.location}'
|
||||
})
|
||||
}),
|
||||
mast: $('.wrapper-mast')
|
||||
});
|
||||
</script>
|
||||
</%block>
|
||||
|
||||
<%block name="content">
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-actions has-subtitle">
|
||||
<div class="title">
|
||||
<span class="title-sub">Course Content</span>
|
||||
<h1 class="title-1">Static Pages</h1>
|
||||
</div>
|
||||
|
||||
<nav class="nav-actions">
|
||||
<h3 class="sr">Page Actions</h3>
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="button new-button new-tab"><i class="ss-icon ss-symbolicons-standard icon icon-create">+</i> New Page</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<div class="introduction has-links">
|
||||
<p class="copy">Static Pages are additional pages that supplement your Courseware. Other course authors have used them to share a syllabus, calendar, handouts, and more.</p>
|
||||
<nav class="nav-introduction-supplementary">
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<a rel="modal" href="#preview-lms-staticpages"><i class="ss-icon ss-symbolicons-block icon icon-information">❓</i>How do Static Pages look to students in my course?</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<article class="unit-body">
|
||||
<div class="details">
|
||||
<h2>Here you can add and manage additional pages for your course</h2>
|
||||
<p>These pages will be added to the primary navigation menu alongside Courseware, Course Info, Discussion, etc.</p>
|
||||
</div>
|
||||
|
||||
<div class="page-actions">
|
||||
<a href="#" class="new-button new-tab">
|
||||
<span class="plus-icon white"></span>New Page
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="tab-list">
|
||||
<ol class='components'>
|
||||
@@ -43,4 +67,17 @@
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-modal" id="preview-lms-staticpages">
|
||||
<h3 class="title">How Static Pages are Used in Your Course</h3>
|
||||
<figure>
|
||||
<img src="/static/img/preview-lms-staticpages.png" alt="Preview of how Static Pages are used in your course" />
|
||||
<figcaption class="description">These pages will be presented in your course's main navigation alongside Courseware, Course Info, Discussion, etc.</figcaption>
|
||||
</figure>
|
||||
|
||||
<a href="#" rel="view" class="action action-modal-close">
|
||||
<i class="ss-icon ss-symbolicons-block icon-close icon">␡</i>
|
||||
<span class="label">close modal</span>
|
||||
</a>
|
||||
</div>
|
||||
</%block>
|
||||
@@ -6,6 +6,15 @@
|
||||
<%block name="bodyclass">is-signedin course tools export</%block>
|
||||
|
||||
<%block name="content">
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-subtitle">
|
||||
<div class="title">
|
||||
<span class="title-sub">Tools</span>
|
||||
<h1 class="title-1">Course Export</h1>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<article class="export-overview">
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<img src="/static/img/thumb-hiw-feature1.png" alt="Studio Helps You Keep Your Courses Organized" />
|
||||
<figcaption class="sr">Studio Helps You Keep Your Courses Organized</figcaption>
|
||||
<span class="action-zoom">
|
||||
<i class="ss-icon ss-symbolicons-block"></i>
|
||||
<i class="ss-icon ss-symbolicons-block icon icon-zoom"></i>
|
||||
</span>
|
||||
</a>
|
||||
</figure>
|
||||
@@ -62,7 +62,7 @@
|
||||
<img src="/static/img/thumb-hiw-feature2.png" alt="Learning is More than Just Lectures" />
|
||||
<figcaption class="sr">Learning is More than Just Lectures</figcaption>
|
||||
<span class="action-zoom">
|
||||
<i class="ss-icon ss-symbolicons-block"></i>
|
||||
<i class="ss-icon ss-symbolicons-block icon icon-zoom"></i>
|
||||
</span>
|
||||
</a>
|
||||
</figure>
|
||||
@@ -96,7 +96,7 @@
|
||||
<img src="/static/img/thumb-hiw-feature3.png" alt="Studio Gives You Simple, Fast, and Incremental Publishing. With Friends." />
|
||||
<figcaption class="sr">Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.</figcaption>
|
||||
<span class="action-zoom">
|
||||
<i class="ss-icon ss-symbolicons-block"></i>
|
||||
<i class="ss-icon ss-symbolicons-block icon icon-zoom"></i>
|
||||
</span>
|
||||
</a>
|
||||
</figure>
|
||||
@@ -152,7 +152,7 @@
|
||||
</figure>
|
||||
|
||||
<a href="#" rel="view" class="action action-modal-close">
|
||||
<i class="ss-icon ss-symbolicons-block">␡</i>
|
||||
<i class="ss-icon ss-symbolicons-block icon icon-close">␡</i>
|
||||
<span class="label">close modal</span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -165,7 +165,7 @@
|
||||
</figure>
|
||||
|
||||
<a href="#" rel="view" class="action action-modal-close">
|
||||
<i class="ss-icon ss-symbolicons-block">␡</i>
|
||||
<i class="ss-icon ss-symbolicons-block icon icon-close">␡</i>
|
||||
<span class="label">close modal</span>
|
||||
</a>
|
||||
</div>
|
||||
@@ -178,22 +178,8 @@
|
||||
</figure>
|
||||
|
||||
<a href="#" rel="view" class="action action-modal-close">
|
||||
<i class="ss-icon ss-symbolicons-block">␡</i>
|
||||
<i class="ss-icon ss-symbolicons-block icon icon-close">␡</i>
|
||||
<span class="label">close modal</span>
|
||||
</a>
|
||||
</div>
|
||||
</%block>
|
||||
|
||||
<%block name="jsextra">
|
||||
<script type="text/javascript">
|
||||
(function() {
|
||||
|
||||
// lean modal window
|
||||
$('a[rel*=modal]').leanModal({overlay : 0.50, closeButton: '.action-modal-close' });
|
||||
$('a.action-modal-close').click(function(e){
|
||||
(e).preventDefault();
|
||||
});
|
||||
|
||||
})(this)
|
||||
</script>
|
||||
</%block>
|
||||
@@ -6,6 +6,15 @@
|
||||
<%block name="bodyclass">is-signedin course tools import</%block>
|
||||
|
||||
<%block name="content">
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-subtitle">
|
||||
<div class="title">
|
||||
<span class="title-sub">Tools</span>
|
||||
<h1 class="title-1">Course Import</h1>
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<article class="import-overview">
|
||||
|
||||
@@ -33,35 +33,57 @@
|
||||
</%block>
|
||||
|
||||
<%block name="content">
|
||||
<div class="main-wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<h1>My Courses</h1>
|
||||
<article class="my-classes">
|
||||
% if user.is_active:
|
||||
% if not disable_course_creation:
|
||||
<a href="#" class="new-button new-course-button"><span class="plus-icon white"></span> New Course</a>
|
||||
%endif
|
||||
<ul class="class-list">
|
||||
%for course, url in courses:
|
||||
<li>
|
||||
<a href="${url}" class="class-name">
|
||||
<span class="class-name">${course}</span>
|
||||
<!--
|
||||
<span class="detail">Started: 9/21/2012</span>
|
||||
<span class="detail">Ends: 10/21/2012</span>
|
||||
-->
|
||||
</a>
|
||||
</li>
|
||||
%endfor
|
||||
</ul>
|
||||
% else:
|
||||
<div class='warn-msg'>
|
||||
<p>
|
||||
In order to start authoring courses using edX studio, please click on the activation link in your email.
|
||||
</p>
|
||||
</div>
|
||||
% endif
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</%block>
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-actions">
|
||||
<div class="title">
|
||||
<h1 class="title-1">My Courses</h1>
|
||||
</div>
|
||||
|
||||
% if user.is_active:
|
||||
<nav class="nav-actions">
|
||||
<h3 class="sr">Page Actions</h3>
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
% if not disable_course_creation:
|
||||
<a href="#" class="button new-button new-course-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">+</i> New Course</a>
|
||||
% endif
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
% endif
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<div class="introduction">
|
||||
<p class="copy"><strong>Welcome, ${ user.username }</strong>. Here are all of the courses you are currently authoring in Studio:</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<article class="my-classes">
|
||||
% if user.is_active:
|
||||
<ul class="class-list">
|
||||
%for course, url, lms_link in courses:
|
||||
<li>
|
||||
<a class="class-link" href="${url}" class="class-name">
|
||||
<span class="class-name">${course}</span>
|
||||
</a>
|
||||
<a href="${lms_link}" rel="external" class="button view-button view-live-button"><i class="ss-icon ss-symbolicons-block icon icon-view"></i>View Live</a>
|
||||
</li>
|
||||
%endfor
|
||||
</ul>
|
||||
% else:
|
||||
<div class='warn-msg'>
|
||||
<p>
|
||||
In order to start authoring courses using edX Studio, please click on the activation link in your email.
|
||||
</p>
|
||||
</div>
|
||||
% endif
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</%block>
|
||||
@@ -4,15 +4,28 @@
|
||||
|
||||
|
||||
<%block name="content">
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-actions has-subtitle">
|
||||
<div class="title">
|
||||
<span class="title-sub">Course Settings</span>
|
||||
<h1 class="title-1">Course Team</h1>
|
||||
</div>
|
||||
|
||||
<nav class="nav-actions">
|
||||
<h3 class="sr">Page Actions</h3>
|
||||
<ul>
|
||||
%if allow_actions:
|
||||
<li class="nav-item">
|
||||
<a href="#" class="button new-button new-user-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">+</i> New User</a>
|
||||
</li>
|
||||
%endif
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<div class="page-actions">
|
||||
%if allow_actions:
|
||||
<a href="#" class="new-button new-user-button">
|
||||
<span class="plus-icon white"></span>New User
|
||||
</a>
|
||||
%endif
|
||||
</div>
|
||||
|
||||
<div class="details">
|
||||
<p>The following list of users have been designated as course staff. This means that these users will have permissions to modify course content. You may add additional course staff below, if you are the course instructor. Please note that they must have already registered and verified their account.</p>
|
||||
|
||||
@@ -120,12 +120,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-actions has-subtitle">
|
||||
<div class="title">
|
||||
<span class="title-sub">Course Content</span>
|
||||
<h1 class="title-1">Course Outline</h1>
|
||||
</div>
|
||||
|
||||
<nav class="nav-actions">
|
||||
<h3 class="sr">Page Actions</h3>
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="toggle-button toggle-button-sections"><i class="ss-icon ss-symbolicons-block icon">up</i> <span class="label">Collapse All Sections</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="button new-button new-courseware-section-button"><i class="ss-icon ss-symbolicons-standard icon icon-create">+</i> New Section</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="${lms_link}" rel="external" class="button view-button view-live-button"><i class="ss-icon ss-symbolicons-block icon icon-view"></i>View Live</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<div class="page-actions">
|
||||
<a href="#" class="new-button new-courseware-section-button"><span class="plus-icon white"></span> New Section</a>
|
||||
<a href="#" class="toggle-button toggle-button-sections"><i class="ss-icon ss-symbolicons-block">up</i> <span class="label">Collapse All Sections</span></a>
|
||||
</div>
|
||||
<article class="courseware-overview" data-course-id="${context_course.location.url()}">
|
||||
% for section in sections:
|
||||
<section class="courseware-section branch" data-id="${section.location}">
|
||||
|
||||
@@ -43,17 +43,17 @@ from contentstore import utils
|
||||
</%block>
|
||||
|
||||
<%block name="content">
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<header class="page">
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-subtitle">
|
||||
<div class="title">
|
||||
<span class="title-sub">Settings</span>
|
||||
<h1 class="title-1">Schedule & Details</h1>
|
||||
</header>
|
||||
|
||||
<!-- <div class="introduction">
|
||||
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.</p>
|
||||
</div> -->
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<article class="content-primary" role="main">
|
||||
<form id="settings_details" class="settings-details" method="post" action="">
|
||||
<section class="group-settings basic">
|
||||
|
||||
@@ -39,17 +39,17 @@ from contentstore import utils
|
||||
</%block>
|
||||
|
||||
<%block name="content">
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<header class="page">
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-subtitle">
|
||||
<div class="title">
|
||||
<span class="title-sub">Settings</span>
|
||||
<h1 class="title-1">Grading</h1>
|
||||
</header>
|
||||
|
||||
<!-- <div class="introduction">
|
||||
<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.</p>
|
||||
</div> -->
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<article class="content-primary" role="main">
|
||||
<form id="settings_details" class="settings-grading" method="post" action="">
|
||||
<section class="group-settings grade-range">
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
|
||||
<nav class="nav-course primary nav-dropdown" role="navigation">
|
||||
<h2 class="sr">PH207x's Navigation:</h2>
|
||||
<h2 class="sr">${context_course.display_name}'s Navigation:</h2>
|
||||
|
||||
<ol>
|
||||
<li class="nav-item nav-course-courseware">
|
||||
|
||||
@@ -1,25 +1,13 @@
|
||||
import copy
|
||||
from fs.errors import ResourceNotFoundError
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
from lxml import etree
|
||||
from lxml.html import rewrite_links
|
||||
from path import path
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pkg_resources import resource_string
|
||||
|
||||
from .capa_module import only_one, ComplexEncoder
|
||||
from .editing_module import EditingDescriptor
|
||||
from .html_checker import check_html
|
||||
from progress import Progress
|
||||
from .stringify import stringify_children
|
||||
from .x_module import XModule
|
||||
from .xml_module import XmlDescriptor
|
||||
from xmodule.modulestore import Location
|
||||
from combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor
|
||||
from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor
|
||||
|
||||
log = logging.getLogger("mitx.courseware")
|
||||
|
||||
@@ -217,4 +205,4 @@ class CombinedOpenEndedDescriptor(XmlDescriptor, EditingDescriptor):
|
||||
for child in ['task']:
|
||||
add_child(child)
|
||||
|
||||
return elt
|
||||
return elt
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
__author__ = 'vik'
|
||||
@@ -1,33 +1,18 @@
|
||||
import copy
|
||||
from fs.errors import ResourceNotFoundError
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
from lxml import etree
|
||||
from lxml.html import rewrite_links
|
||||
from path import path
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
from pkg_resources import resource_string
|
||||
|
||||
from .capa_module import only_one, ComplexEncoder
|
||||
from .editing_module import EditingDescriptor
|
||||
from .html_checker import check_html
|
||||
from progress import Progress
|
||||
from .stringify import stringify_children
|
||||
from .x_module import XModule
|
||||
from .xml_module import XmlDescriptor
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.timeinfo import TimeInfo
|
||||
from xmodule.capa_module import only_one, ComplexEncoder
|
||||
from xmodule.editing_module import EditingDescriptor
|
||||
from xmodule.html_checker import check_html
|
||||
from xmodule.progress import Progress
|
||||
from xmodule.stringify import stringify_children
|
||||
from xmodule.x_module import XModule
|
||||
from xmodule.xml_module import XmlDescriptor
|
||||
import self_assessment_module
|
||||
import open_ended_module
|
||||
from combined_open_ended_rubric import CombinedOpenEndedRubric, RubricParsingError, GRADER_TYPE_IMAGE_DICT, HUMAN_GRADER_TYPE, LEGEND_LIST
|
||||
from .stringify import stringify_children
|
||||
import dateutil
|
||||
import dateutil.parser
|
||||
import datetime
|
||||
from timeparse import parse_timedelta
|
||||
from combined_open_ended_rubric import CombinedOpenEndedRubric, GRADER_TYPE_IMAGE_DICT, HUMAN_GRADER_TYPE, LEGEND_LIST
|
||||
|
||||
log = logging.getLogger("mitx.courseware")
|
||||
|
||||
@@ -59,6 +44,10 @@ HUMAN_TASK_TYPE = {
|
||||
'openended' : "edX Assessment",
|
||||
}
|
||||
|
||||
#Default value that controls whether or not to skip basic spelling checks in the controller
|
||||
#Metadata overrides this
|
||||
SKIP_BASIC_CHECKS = False
|
||||
|
||||
class CombinedOpenEndedV1Module():
|
||||
"""
|
||||
This is a module that encapsulates all open ended grading (self assessment, peer assessment, etc).
|
||||
@@ -73,7 +62,7 @@ class CombinedOpenEndedV1Module():
|
||||
'save_assessment' -- Saves the student assessment (or external grader assessment)
|
||||
'save_post_assessment' -- saves a post assessment (hint, feedback on feedback, etc)
|
||||
ajax actions implemented by combined open ended module are:
|
||||
'reset' -- resets the whole combined open ended module and returns to the first child module
|
||||
'reset' -- resets the whole combined open ended module and returns to the first child moduleresource_string
|
||||
'next_problem' -- moves to the next child module
|
||||
'get_results' -- gets results from a given child module
|
||||
|
||||
@@ -90,14 +79,6 @@ class CombinedOpenEndedV1Module():
|
||||
INTERMEDIATE_DONE = 'intermediate_done'
|
||||
DONE = 'done'
|
||||
|
||||
js = {'coffee': [resource_string(__name__, 'js/src/combinedopenended/display.coffee'),
|
||||
resource_string(__name__, 'js/src/collapsible.coffee'),
|
||||
resource_string(__name__, 'js/src/javascript_loader.coffee'),
|
||||
]}
|
||||
js_module_name = "CombinedOpenEnded"
|
||||
|
||||
css = {'scss': [resource_string(__name__, 'css/combinedopenended/display.scss')]}
|
||||
|
||||
def __init__(self, system, location, definition, descriptor,
|
||||
instance_state=None, shared_state=None, metadata = None, static_data = None, **kwargs):
|
||||
|
||||
@@ -165,28 +146,16 @@ class CombinedOpenEndedV1Module():
|
||||
self.max_attempts = int(self.metadata.get('attempts', MAX_ATTEMPTS))
|
||||
self.is_scored = self.metadata.get('is_graded', IS_SCORED) in TRUE_DICT
|
||||
self.accept_file_upload = self.metadata.get('accept_file_upload', ACCEPT_FILE_UPLOAD) in TRUE_DICT
|
||||
self.skip_basic_checks = self.metadata.get('skip_spelling_checks', SKIP_BASIC_CHECKS)
|
||||
|
||||
display_due_date_string = self.metadata.get('due', None)
|
||||
if display_due_date_string is not None:
|
||||
try:
|
||||
self.display_due_date = dateutil.parser.parse(display_due_date_string)
|
||||
except ValueError:
|
||||
log.error("Could not parse due date {0} for location {1}".format(display_due_date_string, location))
|
||||
raise
|
||||
else:
|
||||
self.display_due_date = None
|
||||
|
||||
grace_period_string = self.metadata.get('graceperiod', None)
|
||||
if grace_period_string is not None and self.display_due_date:
|
||||
try:
|
||||
self.grace_period = parse_timedelta(grace_period_string)
|
||||
self.close_date = self.display_due_date + self.grace_period
|
||||
except:
|
||||
log.error("Error parsing the grace period {0} for location {1}".format(grace_period_string, location))
|
||||
raise
|
||||
else:
|
||||
self.grace_period = None
|
||||
self.close_date = self.display_due_date
|
||||
try:
|
||||
self.timeinfo = TimeInfo(display_due_date_string, grace_period_string)
|
||||
except:
|
||||
log.error("Error parsing due date information in location {0}".format(location))
|
||||
raise
|
||||
self.display_due_date = self.timeinfo.display_due_date
|
||||
|
||||
# Used for progress / grading. Currently get credit just for
|
||||
# completion (doesn't matter if you self-assessed correct/incorrect).
|
||||
@@ -204,7 +173,9 @@ class CombinedOpenEndedV1Module():
|
||||
'rubric': definition['rubric'],
|
||||
'display_name': self.display_name,
|
||||
'accept_file_upload': self.accept_file_upload,
|
||||
'close_date' : self.close_date,
|
||||
'close_date' : self.timeinfo.close_date,
|
||||
's3_interface' : self.system.s3_interface,
|
||||
'skip_basic_checks' : self.skip_basic_checks,
|
||||
}
|
||||
|
||||
self.task_xml = definition['task_xml']
|
||||
@@ -798,9 +769,6 @@ class CombinedOpenEndedV1Descriptor(XmlDescriptor, EditingDescriptor):
|
||||
has_score = True
|
||||
template_dir_name = "combinedopenended"
|
||||
|
||||
js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]}
|
||||
js_module_name = "HTMLEditingDescriptor"
|
||||
|
||||
@classmethod
|
||||
def definition_from_xml(cls, xml_object, system):
|
||||
"""
|
||||
@@ -841,4 +809,4 @@ class CombinedOpenEndedV1Descriptor(XmlDescriptor, EditingDescriptor):
|
||||
for child in ['task']:
|
||||
add_child(child)
|
||||
|
||||
return elt
|
||||
return elt
|
||||
@@ -5,7 +5,7 @@ import requests
|
||||
from requests.exceptions import RequestException, ConnectionError, HTTPError
|
||||
import sys
|
||||
|
||||
from xmodule.combined_open_ended_rubric import CombinedOpenEndedRubric, RubricParsingError
|
||||
from combined_open_ended_rubric import CombinedOpenEndedRubric
|
||||
from lxml import etree
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
@@ -22,8 +22,6 @@ class GradingService(object):
|
||||
def __init__(self, config):
|
||||
self.username = config['username']
|
||||
self.password = config['password']
|
||||
self.url = config['url']
|
||||
self.login_url = self.url + '/login/'
|
||||
self.session = requests.session()
|
||||
self.system = config['system']
|
||||
|
||||
@@ -13,11 +13,6 @@ from urlparse import urlparse
|
||||
import requests
|
||||
from boto.s3.connection import S3Connection
|
||||
from boto.s3.key import Key
|
||||
#TODO: Settings import is needed now in order to specify the URL and keys for amazon s3 (to upload images).
|
||||
#Eventually, the goal is to replace the global django settings import with settings specifically
|
||||
#for this module. There is no easy way to do this now, so piggybacking on the django settings
|
||||
#makes sense.
|
||||
from django.conf import settings
|
||||
import pickle
|
||||
import logging
|
||||
import re
|
||||
@@ -221,7 +216,7 @@ def run_image_tests(image):
|
||||
return success
|
||||
|
||||
|
||||
def upload_to_s3(file_to_upload, keyname):
|
||||
def upload_to_s3(file_to_upload, keyname, s3_interface):
|
||||
'''
|
||||
Upload file to S3 using provided keyname.
|
||||
|
||||
@@ -237,8 +232,8 @@ def upload_to_s3(file_to_upload, keyname):
|
||||
#im.save(out_im, 'PNG')
|
||||
|
||||
try:
|
||||
conn = S3Connection(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY)
|
||||
bucketname = str(settings.AWS_STORAGE_BUCKET_NAME)
|
||||
conn = S3Connection(s3_interface['access_key'], s3_interface['secret_access_key'])
|
||||
bucketname = str(s3_interface['storage_bucket_name'])
|
||||
bucket = conn.create_bucket(bucketname.lower())
|
||||
|
||||
k = Key(bucket)
|
||||
@@ -5,28 +5,16 @@ hints, answers, and assessment judgment (currently only correct/incorrect).
|
||||
Parses xml definition file--see below for exact format.
|
||||
"""
|
||||
|
||||
import copy
|
||||
from fs.errors import ResourceNotFoundError
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
from lxml import etree
|
||||
from lxml.html import rewrite_links
|
||||
from path import path
|
||||
import os
|
||||
import sys
|
||||
import hashlib
|
||||
import capa.xqueue_interface as xqueue_interface
|
||||
|
||||
from pkg_resources import resource_string
|
||||
|
||||
from .capa_module import only_one, ComplexEncoder
|
||||
from .editing_module import EditingDescriptor
|
||||
from .html_checker import check_html
|
||||
from progress import Progress
|
||||
from .stringify import stringify_children
|
||||
from .xml_module import XmlDescriptor
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.capa_module import ComplexEncoder
|
||||
from xmodule.editing_module import EditingDescriptor
|
||||
from xmodule.progress import Progress
|
||||
from xmodule.stringify import stringify_children
|
||||
from xmodule.xml_module import XmlDescriptor
|
||||
from capa.util import *
|
||||
import openendedchild
|
||||
|
||||
@@ -122,7 +110,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
|
||||
'rubric': rubric_string,
|
||||
'initial_display': self.initial_display,
|
||||
'answer': self.answer,
|
||||
'problem_id': self.display_name
|
||||
'problem_id': self.display_name,
|
||||
'skip_basic_checks': self.skip_basic_checks,
|
||||
})
|
||||
updated_grader_payload = json.dumps(parsed_grader_payload)
|
||||
|
||||
@@ -689,9 +678,6 @@ class OpenEndedDescriptor(XmlDescriptor, EditingDescriptor):
|
||||
has_score = True
|
||||
template_dir_name = "openended"
|
||||
|
||||
js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]}
|
||||
js_module_name = "HTMLEditingDescriptor"
|
||||
|
||||
@classmethod
|
||||
def definition_from_xml(cls, xml_object, system):
|
||||
"""
|
||||
@@ -13,17 +13,15 @@ import hashlib
|
||||
import capa.xqueue_interface as xqueue_interface
|
||||
import re
|
||||
|
||||
from pkg_resources import resource_string
|
||||
|
||||
from .capa_module import only_one, ComplexEncoder
|
||||
from .editing_module import EditingDescriptor
|
||||
from .html_checker import check_html
|
||||
from progress import Progress
|
||||
from .stringify import stringify_children
|
||||
from .xml_module import XmlDescriptor
|
||||
from xmodule.capa_module import only_one, ComplexEncoder
|
||||
import open_ended_image_submission
|
||||
from xmodule.editing_module import EditingDescriptor
|
||||
from xmodule.html_checker import check_html
|
||||
from xmodule.progress import Progress
|
||||
from xmodule.stringify import stringify_children
|
||||
from xmodule.xml_module import XmlDescriptor
|
||||
from xmodule.modulestore import Location
|
||||
from capa.util import *
|
||||
import open_ended_image_submission
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
@@ -100,6 +98,8 @@ class OpenEndedChild(object):
|
||||
self.display_name = static_data['display_name']
|
||||
self.accept_file_upload = static_data['accept_file_upload']
|
||||
self.close_date = static_data['close_date']
|
||||
self.s3_interface = static_data['s3_interface']
|
||||
self.skip_basic_checks = static_data['skip_basic_checks']
|
||||
|
||||
# Used for progress / grading. Currently get credit just for
|
||||
# completion (doesn't matter if you self-assessed correct/incorrect).
|
||||
@@ -319,7 +319,7 @@ class OpenEndedChild(object):
|
||||
|
||||
try:
|
||||
image_data.seek(0)
|
||||
success, s3_public_url = open_ended_image_submission.upload_to_s3(image_data, image_key)
|
||||
success, s3_public_url = open_ended_image_submission.upload_to_s3(image_data, image_key, self.s3_interface)
|
||||
except:
|
||||
log.exception("Could not upload image to S3.")
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
from requests.exceptions import RequestException, ConnectionError, HTTPError
|
||||
import sys
|
||||
|
||||
#TODO: Settings import is needed now in order to specify the URL where to find the peer grading service.
|
||||
#Eventually, the goal is to replace the global django settings import with settings specifically
|
||||
#for this xmodule. There is no easy way to do this now, so piggybacking on the django settings
|
||||
#makes sense.
|
||||
from django.conf import settings
|
||||
|
||||
from combined_open_ended_rubric import CombinedOpenEndedRubric, RubricParsingError
|
||||
from lxml import etree
|
||||
from grading_service_module import GradingService, GradingServiceError
|
||||
from grading_service_module import GradingService
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -28,6 +17,8 @@ class PeerGradingService(GradingService):
|
||||
def __init__(self, config, system):
|
||||
config['system'] = system
|
||||
super(PeerGradingService, self).__init__(config)
|
||||
self.url = config['url'] + config['peer_grading']
|
||||
self.login_url = self.url + '/login/'
|
||||
self.get_next_submission_url = self.url + '/get_next_submission/'
|
||||
self.save_grade_url = self.url + '/save_grade/'
|
||||
self.is_student_calibrated_url = self.url + '/is_student_calibrated/'
|
||||
@@ -142,25 +133,3 @@ class MockPeerGradingService(object):
|
||||
json.dumps({'location': 'i4x://MITx/3.091x/problem/open_ended_demo2',
|
||||
'problem_name': "Problem 2", 'num_graded': 1, 'num_pending': 5})
|
||||
]})
|
||||
|
||||
_service = None
|
||||
|
||||
|
||||
def peer_grading_service(system):
|
||||
"""
|
||||
Return a peer grading service instance--if settings.MOCK_PEER_GRADING is True,
|
||||
returns a mock one, otherwise a real one.
|
||||
|
||||
Caches the result, so changing the setting after the first call to this
|
||||
function will have no effect.
|
||||
"""
|
||||
global _service
|
||||
if _service is not None:
|
||||
return _service
|
||||
|
||||
if settings.MOCK_PEER_GRADING:
|
||||
_service = MockPeerGradingService()
|
||||
else:
|
||||
_service = PeerGradingService(settings.PEER_GRADING_INTERFACE, system)
|
||||
|
||||
return _service
|
||||
@@ -1,24 +1,12 @@
|
||||
import copy
|
||||
from fs.errors import ResourceNotFoundError
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
from lxml import etree
|
||||
from lxml.html import rewrite_links
|
||||
from path import path
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pkg_resources import resource_string
|
||||
|
||||
from .capa_module import only_one, ComplexEncoder
|
||||
from .editing_module import EditingDescriptor
|
||||
from .html_checker import check_html
|
||||
from progress import Progress
|
||||
from .stringify import stringify_children
|
||||
from .x_module import XModule
|
||||
from .xml_module import XmlDescriptor
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.capa_module import ComplexEncoder
|
||||
from xmodule.editing_module import EditingDescriptor
|
||||
from xmodule.progress import Progress
|
||||
from xmodule.stringify import stringify_children
|
||||
from xmodule.xml_module import XmlDescriptor
|
||||
import openendedchild
|
||||
|
||||
from combined_open_ended_rubric import CombinedOpenEndedRubric
|
||||
@@ -285,10 +273,6 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
|
||||
has_score = True
|
||||
template_dir_name = "selfassessment"
|
||||
|
||||
js = {'coffee': [resource_string(__name__, 'js/src/html/edit.coffee')]}
|
||||
js_module_name = "HTMLEditingDescriptor"
|
||||
css = {'scss': [resource_string(__name__, 'css/editor/edit.scss'), resource_string(__name__, 'css/html/edit.scss')]}
|
||||
|
||||
@classmethod
|
||||
def definition_from_xml(cls, xml_object, system):
|
||||
"""
|
||||
@@ -1,39 +1,20 @@
|
||||
"""
|
||||
This module provides an interface on the grading-service backend
|
||||
for peer grading
|
||||
|
||||
Use peer_grading_service() to get the version specified
|
||||
in settings.PEER_GRADING_INTERFACE
|
||||
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
import sys
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from combined_open_ended_rubric import CombinedOpenEndedRubric
|
||||
from lxml import etree
|
||||
|
||||
import copy
|
||||
import itertools
|
||||
import json
|
||||
import logging
|
||||
from lxml.html import rewrite_links
|
||||
import os
|
||||
|
||||
from datetime import datetime
|
||||
from pkg_resources import resource_string
|
||||
from .capa_module import only_one, ComplexEncoder
|
||||
from .capa_module import ComplexEncoder
|
||||
from .editing_module import EditingDescriptor
|
||||
from .html_checker import check_html
|
||||
from progress import Progress
|
||||
from .stringify import stringify_children
|
||||
from .x_module import XModule
|
||||
from .xml_module import XmlDescriptor
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from timeinfo import TimeInfo
|
||||
|
||||
from peer_grading_service import peer_grading_service, GradingServiceError
|
||||
from xmodule.open_ended_grading_classes.peer_grading_service import PeerGradingService, GradingServiceError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -70,7 +51,8 @@ class PeerGradingModule(XModule):
|
||||
#We need to set the location here so the child modules can use it
|
||||
system.set('location', location)
|
||||
self.system = system
|
||||
self.peer_gs = peer_grading_service(self.system)
|
||||
self.peer_gs = PeerGradingService(self.system.open_ended_grading_interface, self.system)
|
||||
|
||||
|
||||
self.use_for_single_location = self.metadata.get('use_for_single_location', USE_FOR_SINGLE_LOCATION)
|
||||
if isinstance(self.use_for_single_location, basestring):
|
||||
@@ -80,10 +62,28 @@ class PeerGradingModule(XModule):
|
||||
if isinstance(self.is_graded, basestring):
|
||||
self.is_graded = (self.is_graded in TRUE_DICT)
|
||||
|
||||
display_due_date_string = self.metadata.get('due', None)
|
||||
grace_period_string = self.metadata.get('graceperiod', None)
|
||||
|
||||
try:
|
||||
self.timeinfo = TimeInfo(display_due_date_string, grace_period_string)
|
||||
except:
|
||||
log.error("Error parsing due date information in location {0}".format(location))
|
||||
raise
|
||||
|
||||
self.display_due_date = self.timeinfo.display_due_date
|
||||
|
||||
self.link_to_location = self.metadata.get('link_to_location', USE_FOR_SINGLE_LOCATION)
|
||||
if self.use_for_single_location == True:
|
||||
#This will raise an exception if the location is invalid
|
||||
link_to_location_object = Location(self.link_to_location)
|
||||
try:
|
||||
self.linked_problem = modulestore().get_instance(self.system.course_id, self.link_to_location)
|
||||
except:
|
||||
log.error("Linked location {0} for peer grading module {1} does not exist".format(
|
||||
self.link_to_location, self.location))
|
||||
raise
|
||||
due_date = self.linked_problem.metadata.get('peer_grading_due', None)
|
||||
if due_date:
|
||||
self.metadata['due'] = due_date
|
||||
|
||||
self.ajax_url = self.system.ajax_url
|
||||
if not self.ajax_url.endswith("/"):
|
||||
@@ -95,6 +95,15 @@ class PeerGradingModule(XModule):
|
||||
#This could result in an exception, but not wrapping in a try catch block so it moves up the stack
|
||||
self.max_grade = int(self.max_grade)
|
||||
|
||||
def closed(self):
|
||||
return self._closed(self.timeinfo)
|
||||
|
||||
def _closed(self, timeinfo):
|
||||
if timeinfo.close_date is not None and datetime.utcnow() > timeinfo.close_date:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _err_response(self, msg):
|
||||
"""
|
||||
Return a HttpResponse with a json dump with success=False, and the given error message.
|
||||
@@ -114,6 +123,8 @@ class PeerGradingModule(XModule):
|
||||
Needs to be implemented by inheritors. Renders the HTML that students see.
|
||||
@return:
|
||||
"""
|
||||
if self.closed():
|
||||
return self.peer_grading_closed()
|
||||
if not self.use_for_single_location:
|
||||
return self.peer_grading()
|
||||
else:
|
||||
@@ -142,7 +153,7 @@ class PeerGradingModule(XModule):
|
||||
|
||||
def query_data_for_location(self):
|
||||
student_id = self.system.anonymous_student_id
|
||||
location = self.system.location
|
||||
location = self.link_to_location
|
||||
success = False
|
||||
response = {}
|
||||
|
||||
@@ -171,7 +182,7 @@ class PeerGradingModule(XModule):
|
||||
success, response = self.query_data_for_location()
|
||||
if not success:
|
||||
log.exception("No instance data found and could not get data from controller for loc {0} student {1}".format(
|
||||
self.system.location, self.system.anonymous_student_id
|
||||
self.system.location.url(), self.system.anonymous_student_id
|
||||
))
|
||||
return None
|
||||
count_graded = response['count_graded']
|
||||
@@ -400,6 +411,16 @@ class PeerGradingModule(XModule):
|
||||
log.exception("Error saving calibration grade, location: {0}, submission_id: {1}, submission_key: {2}, grader_id: {3}".format(location, submission_id, submission_key, grader_id))
|
||||
return self._err_response('Could not connect to grading service')
|
||||
|
||||
def peer_grading_closed(self):
|
||||
'''
|
||||
Show the Peer grading closed template
|
||||
'''
|
||||
html = self.system.render_template('peer_grading/peer_grading_closed.html', {
|
||||
'use_for_single_location': self.use_for_single_location
|
||||
})
|
||||
return html
|
||||
|
||||
|
||||
def peer_grading(self, get=None):
|
||||
'''
|
||||
Show a peer grading interface
|
||||
@@ -426,6 +447,40 @@ class PeerGradingModule(XModule):
|
||||
error_text = "Could not get problem list"
|
||||
success = False
|
||||
|
||||
|
||||
def _find_corresponding_module_for_location(location):
|
||||
'''
|
||||
find the peer grading module that links to the given location
|
||||
'''
|
||||
try:
|
||||
return modulestore().get_instance(self.system.course_id, location)
|
||||
except:
|
||||
# the linked problem doesn't exist
|
||||
log.error("Problem {0} does not exist in this course".format(location))
|
||||
raise
|
||||
|
||||
|
||||
for problem in problem_list:
|
||||
problem_location = problem['location']
|
||||
descriptor = _find_corresponding_module_for_location(problem_location)
|
||||
if descriptor:
|
||||
problem['due'] = descriptor.metadata.get('peer_grading_due', None)
|
||||
grace_period_string = descriptor.metadata.get('graceperiod', None)
|
||||
try:
|
||||
problem_timeinfo = TimeInfo(problem['due'], grace_period_string)
|
||||
except:
|
||||
log.error("Malformed due date or grace period string for location {0}".format(problem_location))
|
||||
raise
|
||||
if self._closed(problem_timeinfo):
|
||||
problem['closed'] = True
|
||||
else:
|
||||
problem['closed'] = False
|
||||
else:
|
||||
# if we can't find the due date, assume that it doesn't have one
|
||||
problem['due'] = None
|
||||
problem['closed'] = False
|
||||
|
||||
|
||||
ajax_url = self.ajax_url
|
||||
html = self.system.render_template('peer_grading/peer_grading.html', {
|
||||
'course_id': self.system.course_id,
|
||||
|
||||
@@ -2,9 +2,9 @@ import json
|
||||
from mock import Mock, MagicMock, ANY
|
||||
import unittest
|
||||
|
||||
from xmodule.openendedchild import OpenEndedChild
|
||||
from xmodule.open_ended_module import OpenEndedModule
|
||||
from xmodule.combined_open_ended_modulev1 import CombinedOpenEndedV1Module
|
||||
from xmodule.open_ended_grading_classes.openendedchild import OpenEndedChild
|
||||
from xmodule.open_ended_grading_classes.open_ended_module import OpenEndedModule
|
||||
from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module
|
||||
|
||||
from xmodule.modulestore import Location
|
||||
from lxml import etree
|
||||
@@ -12,6 +12,8 @@ import capa.xqueue_interface as xqueue_interface
|
||||
from datetime import datetime
|
||||
|
||||
from . import test_system
|
||||
|
||||
import test_util_open_ended
|
||||
"""
|
||||
Tests for the various pieces of the CombinedOpenEndedGrading system
|
||||
|
||||
@@ -43,7 +45,10 @@ class OpenEndedChildTest(unittest.TestCase):
|
||||
'max_score': max_score,
|
||||
'display_name': 'Name',
|
||||
'accept_file_upload': False,
|
||||
'close_date': None
|
||||
'close_date': None,
|
||||
's3_interface' : "",
|
||||
'open_ended_grading_interface' : {},
|
||||
'skip_basic_checks' : False,
|
||||
}
|
||||
definition = Mock()
|
||||
descriptor = Mock()
|
||||
@@ -161,6 +166,9 @@ class OpenEndedModuleTest(unittest.TestCase):
|
||||
'accept_file_upload': False,
|
||||
'rewrite_content_links' : "",
|
||||
'close_date': None,
|
||||
's3_interface' : test_util_open_ended.S3_INTERFACE,
|
||||
'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
|
||||
'skip_basic_checks' : False,
|
||||
}
|
||||
|
||||
oeparam = etree.XML('''
|
||||
@@ -293,6 +301,9 @@ class CombinedOpenEndedModuleTest(unittest.TestCase):
|
||||
'accept_file_upload' : False,
|
||||
'rewrite_content_links' : "",
|
||||
'close_date' : "",
|
||||
's3_interface' : test_util_open_ended.S3_INTERFACE,
|
||||
'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
|
||||
'skip_basic_checks' : False,
|
||||
}
|
||||
|
||||
oeparam = etree.XML('''
|
||||
|
||||
@@ -2,13 +2,14 @@ import json
|
||||
from mock import Mock
|
||||
import unittest
|
||||
|
||||
from xmodule.self_assessment_module import SelfAssessmentModule
|
||||
from xmodule.open_ended_grading_classes.self_assessment_module import SelfAssessmentModule
|
||||
from xmodule.modulestore import Location
|
||||
from lxml import etree
|
||||
from nose.plugins.skip import SkipTest
|
||||
|
||||
from . import test_system
|
||||
|
||||
import test_util_open_ended
|
||||
|
||||
class SelfAssessmentTest(unittest.TestCase):
|
||||
|
||||
@@ -47,7 +48,10 @@ class SelfAssessmentTest(unittest.TestCase):
|
||||
'max_score': 1,
|
||||
'display_name': "Name",
|
||||
'accept_file_upload': False,
|
||||
'close_date': None
|
||||
'close_date': None,
|
||||
's3_interface' : test_util_open_ended.S3_INTERFACE,
|
||||
'open_ended_grading_interface' : test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE,
|
||||
'skip_basic_checks' : False,
|
||||
}
|
||||
|
||||
self.module = SelfAssessmentModule(test_system, self.location,
|
||||
|
||||
14
common/lib/xmodule/xmodule/tests/test_util_open_ended.py
Normal file
14
common/lib/xmodule/xmodule/tests/test_util_open_ended.py
Normal file
@@ -0,0 +1,14 @@
|
||||
OPEN_ENDED_GRADING_INTERFACE = {
|
||||
'url' : 'http://127.0.0.1:3033/',
|
||||
'username' : 'incorrect',
|
||||
'password' : 'incorrect',
|
||||
'staff_grading' : 'staff_grading',
|
||||
'peer_grading' : 'peer_grading',
|
||||
'grading_controller' : 'grading_controller'
|
||||
}
|
||||
|
||||
S3_INTERFACE = {
|
||||
'aws_access_key' : "",
|
||||
'aws_secret_key' : "",
|
||||
"aws_bucket_name" : "",
|
||||
}
|
||||
39
common/lib/xmodule/xmodule/timeinfo.py
Normal file
39
common/lib/xmodule/xmodule/timeinfo.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import dateutil
|
||||
import dateutil.parser
|
||||
import datetime
|
||||
from timeparse import parse_timedelta
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class TimeInfo(object):
|
||||
"""
|
||||
This is a simple object that calculates and stores datetime information for an XModule
|
||||
based on the due date string and the grace period string
|
||||
|
||||
So far it parses out three different pieces of time information:
|
||||
self.display_due_date - the 'official' due date that gets displayed to students
|
||||
self.grace_period - the length of the grace period
|
||||
self.close_date - the real due date
|
||||
|
||||
"""
|
||||
def __init__(self, display_due_date_string, grace_period_string):
|
||||
if display_due_date_string is not None:
|
||||
try:
|
||||
self.display_due_date = dateutil.parser.parse(display_due_date_string)
|
||||
except ValueError:
|
||||
log.error("Could not parse due date {0}".format(display_due_date_string))
|
||||
raise
|
||||
else:
|
||||
self.display_due_date = None
|
||||
|
||||
if grace_period_string is not None and self.display_due_date:
|
||||
try:
|
||||
self.grace_period = parse_timedelta(grace_period_string)
|
||||
self.close_date = self.display_due_date + self.grace_period
|
||||
except:
|
||||
log.error("Error parsing the grace period {0}".format(grace_period_string))
|
||||
raise
|
||||
else:
|
||||
self.grace_period = None
|
||||
self.close_date = self.display_due_date
|
||||
@@ -879,7 +879,9 @@ class ModuleSystem(object):
|
||||
xqueue=None,
|
||||
node_path="",
|
||||
anonymous_student_id='',
|
||||
course_id=None):
|
||||
course_id=None,
|
||||
open_ended_grading_interface=None,
|
||||
s3_interface=None):
|
||||
'''
|
||||
Create a closure around the system environment.
|
||||
|
||||
@@ -930,6 +932,8 @@ class ModuleSystem(object):
|
||||
self.anonymous_student_id = anonymous_student_id
|
||||
self.course_id = course_id
|
||||
self.user_is_staff = user is not None and user.is_staff
|
||||
self.open_ended_grading_interface = open_ended_grading_interface
|
||||
self.s3_interface = s3_interface
|
||||
|
||||
def get(self, attr):
|
||||
''' provide uniform access to attributes (like etree).'''
|
||||
|
||||
@@ -226,6 +226,30 @@ def _get_module(user, request, descriptor, student_module_cache, course_id,
|
||||
'waittime': settings.XQUEUE_WAITTIME_BETWEEN_REQUESTS
|
||||
}
|
||||
|
||||
def get_or_default(key, default):
|
||||
getattr(settings, key, default)
|
||||
|
||||
#This is a hacky way to pass settings to the combined open ended xmodule
|
||||
#It needs an S3 interface to upload images to S3
|
||||
#It needs the open ended grading interface in order to get peer grading to be done
|
||||
#TODO: refactor these settings into module-specific settings when possible.
|
||||
#this first checks to see if the descriptor is the correct one, and only sends settings if it is
|
||||
is_descriptor_combined_open_ended = (descriptor.__class__.__name__ == 'CombinedOpenEndedDescriptor')
|
||||
is_descriptor_peer_grading = (descriptor.__class__.__name__ == 'PeerGradingDescriptor')
|
||||
open_ended_grading_interface = None
|
||||
s3_interface = None
|
||||
if is_descriptor_combined_open_ended or is_descriptor_peer_grading:
|
||||
open_ended_grading_interface = settings.OPEN_ENDED_GRADING_INTERFACE
|
||||
open_ended_grading_interface['mock_peer_grading'] = settings.MOCK_PEER_GRADING
|
||||
open_ended_grading_interface['mock_staff_grading'] = settings.MOCK_STAFF_GRADING
|
||||
if is_descriptor_combined_open_ended:
|
||||
s3_interface = {
|
||||
'access_key' : get_or_default('AWS_ACCESS_KEY_ID',''),
|
||||
'secret_access_key' : get_or_default('AWS_SECRET_ACCESS_KEY',''),
|
||||
'storage_bucket_name' : get_or_default('AWS_STORAGE_BUCKET_NAME','')
|
||||
}
|
||||
|
||||
|
||||
def inner_get_module(descriptor):
|
||||
"""
|
||||
Delegate to get_module. It does an access check, so may return None
|
||||
@@ -255,6 +279,8 @@ def _get_module(user, request, descriptor, student_module_cache, course_id,
|
||||
node_path=settings.NODE_PATH,
|
||||
anonymous_student_id=unique_id_for_user(user),
|
||||
course_id=course_id,
|
||||
open_ended_grading_interface=open_ended_grading_interface,
|
||||
s3_interface=s3_interface,
|
||||
)
|
||||
# pass position specified in URL to module through ModuleSystem
|
||||
system.set('position', position)
|
||||
|
||||
@@ -413,7 +413,7 @@ def instructor_dashboard(request, course_id):
|
||||
smdat = StudentModule.objects.filter(course_id=course_id,
|
||||
module_state_key=module_state_key)
|
||||
smdat = smdat.order_by('student')
|
||||
msg+="Found module to reset. "
|
||||
msg += "Found %d records to dump " % len(smdat)
|
||||
except Exception as err:
|
||||
msg+="<font color='red'>Couldn't find module with that urlname. </font>"
|
||||
msg += "<pre>%s</pre>" % escape(err)
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
from requests.exceptions import RequestException, ConnectionError, HTTPError
|
||||
import sys
|
||||
from xmodule.grading_service_module import GradingService, GradingServiceError
|
||||
from xmodule.open_ended_grading_classes.grading_service_module import GradingService
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse, Http404
|
||||
from xmodule.x_module import ModuleSystem
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
|
||||
@@ -20,6 +14,8 @@ class ControllerQueryService(GradingService):
|
||||
def __init__(self, config):
|
||||
config['system'] = ModuleSystem(None, None, None, render_to_string, None)
|
||||
super(ControllerQueryService, self).__init__(config)
|
||||
self.url = config['url'] + config['grading_controller']
|
||||
self.login_url = self.url + '/login/'
|
||||
self.check_eta_url = self.url + '/get_submission_eta/'
|
||||
self.is_unique_url = self.url + '/is_name_unique/'
|
||||
self.combined_notifications_url = self.url + '/combined_notifications/'
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
from django.conf import settings
|
||||
from xmodule.open_ended_grading_classes import peer_grading_service
|
||||
from staff_grading_service import StaffGradingService
|
||||
from open_ended_grading.controller_query_service import ControllerQueryService
|
||||
from xmodule import peer_grading_service
|
||||
import json
|
||||
from student.models import unique_id_for_user
|
||||
import open_ended_util
|
||||
from courseware.models import StudentModule
|
||||
import logging
|
||||
from courseware.access import has_access
|
||||
from util.cache import cache
|
||||
import datetime
|
||||
from xmodule import peer_grading_service
|
||||
from xmodule.x_module import ModuleSystem
|
||||
from mitxmako.shortcuts import render_to_string
|
||||
|
||||
@@ -28,7 +26,7 @@ NOTIFICATION_TYPES = (
|
||||
|
||||
|
||||
def staff_grading_notifications(course, user):
|
||||
staff_gs = StaffGradingService(settings.STAFF_GRADING_INTERFACE)
|
||||
staff_gs = StaffGradingService(settings.OPEN_ENDED_GRADING_INTERFACE)
|
||||
pending_grading = False
|
||||
img_path = ""
|
||||
course_id = course.id
|
||||
@@ -61,7 +59,7 @@ def staff_grading_notifications(course, user):
|
||||
|
||||
def peer_grading_notifications(course, user):
|
||||
system = ModuleSystem(None, None, None, render_to_string, None)
|
||||
peer_gs = peer_grading_service.PeerGradingService(settings.PEER_GRADING_INTERFACE, system)
|
||||
peer_gs = peer_grading_service.PeerGradingService(settings.OPEN_ENDED_GRADING_INTERFACE, system)
|
||||
pending_grading = False
|
||||
img_path = ""
|
||||
course_id = course.id
|
||||
@@ -93,8 +91,7 @@ def peer_grading_notifications(course, user):
|
||||
|
||||
|
||||
def combined_notifications(course, user):
|
||||
controller_url = open_ended_util.get_controller_url()
|
||||
controller_qs = ControllerQueryService(controller_url)
|
||||
controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE)
|
||||
student_id = unique_id_for_user(user)
|
||||
user_is_staff = has_access(user, course, 'staff')
|
||||
course_id = course.id
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
from django.conf import settings
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_controller_url():
|
||||
peer_grading_url = settings.PEER_GRADING_INTERFACE['url']
|
||||
split_url = peer_grading_url.split("/")
|
||||
controller_url = "http://" + split_url[2] + "/grading_controller"
|
||||
controller_settings = settings.PEER_GRADING_INTERFACE.copy()
|
||||
controller_settings['url'] = controller_url
|
||||
return controller_settings
|
||||
@@ -4,10 +4,7 @@ This module provides views that proxy to the staff grading backend service.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import requests
|
||||
from requests.exceptions import RequestException, ConnectionError, HTTPError
|
||||
import sys
|
||||
from xmodule.grading_service_module import GradingService, GradingServiceError
|
||||
from xmodule.open_ended_grading_classes.grading_service_module import GradingService, GradingServiceError
|
||||
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse, Http404
|
||||
@@ -64,6 +61,8 @@ class StaffGradingService(GradingService):
|
||||
def __init__(self, config):
|
||||
config['system'] = ModuleSystem(None, None, None, render_to_string, None)
|
||||
super(StaffGradingService, self).__init__(config)
|
||||
self.url = config['url'] + config['staff_grading']
|
||||
self.login_url = self.url + '/login/'
|
||||
self.get_next_url = self.url + '/get_next_submission/'
|
||||
self.save_grade_url = self.url + '/save_grade/'
|
||||
self.get_problem_list_url = self.url + '/get_problem_list/'
|
||||
@@ -164,7 +163,7 @@ def staff_grading_service():
|
||||
if settings.MOCK_STAFF_GRADING:
|
||||
_service = MockStaffGradingService()
|
||||
else:
|
||||
_service = StaffGradingService(settings.STAFF_GRADING_INTERFACE)
|
||||
_service = StaffGradingService(settings.OPEN_ENDED_GRADING_INTERFACE)
|
||||
|
||||
return _service
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ django-admin.py test --settings=lms.envs.test --pythonpath=. lms/djangoapps/open
|
||||
|
||||
from django.test import TestCase
|
||||
from open_ended_grading import staff_grading_service
|
||||
from xmodule import peer_grading_service, peer_grading_module
|
||||
from xmodule.open_ended_grading_classes import peer_grading_service
|
||||
from xmodule import peer_grading_module
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
@@ -25,6 +26,8 @@ log = logging.getLogger(__name__)
|
||||
from django.test.utils import override_settings
|
||||
from django.http import QueryDict
|
||||
|
||||
from xmodule.tests import test_util_open_ended
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=ct.TEST_DATA_XML_MODULESTORE)
|
||||
class TestStaffGradingService(ct.PageLoader):
|
||||
@@ -144,9 +147,11 @@ class TestPeerGradingService(ct.PageLoader):
|
||||
location = "i4x://edX/toy/peergrading/init"
|
||||
|
||||
self.mock_service = peer_grading_service.MockPeerGradingService()
|
||||
self.system = ModuleSystem(location, None, None, render_to_string, None)
|
||||
self.system = ModuleSystem(location, None, None, render_to_string, None,
|
||||
s3_interface = test_util_open_ended.S3_INTERFACE,
|
||||
open_ended_grading_interface=test_util_open_ended.OPEN_ENDED_GRADING_INTERFACE
|
||||
)
|
||||
self.descriptor = peer_grading_module.PeerGradingDescriptor(self.system)
|
||||
|
||||
self.peer_module = peer_grading_module.PeerGradingModule(self.system, location, "<peergrading/>", self.descriptor)
|
||||
self.peer_module.peer_gs = self.mock_service
|
||||
self.logout()
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import logging
|
||||
import urllib
|
||||
import re
|
||||
|
||||
from django.conf import settings
|
||||
from django.views.decorators.cache import cache_control
|
||||
@@ -13,12 +12,10 @@ from student.models import unique_id_for_user
|
||||
from courseware.courses import get_course_with_access
|
||||
|
||||
from controller_query_service import ControllerQueryService
|
||||
from xmodule.grading_service_module import GradingServiceError
|
||||
from xmodule.open_ended_grading_classes.grading_service_module import GradingServiceError
|
||||
import json
|
||||
from .staff_grading import StaffGrading
|
||||
from student.models import unique_id_for_user
|
||||
|
||||
import open_ended_util
|
||||
import open_ended_notifications
|
||||
|
||||
from xmodule.modulestore.django import modulestore
|
||||
@@ -30,8 +27,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
template_imports = {'urllib': urllib}
|
||||
|
||||
controller_url = open_ended_util.get_controller_url()
|
||||
controller_qs = ControllerQueryService(controller_url)
|
||||
controller_qs = ControllerQueryService(settings.OPEN_ENDED_GRADING_INTERFACE)
|
||||
|
||||
"""
|
||||
Reverses the URL from the name and the course id, and then adds a trailing slash if
|
||||
|
||||
@@ -100,10 +100,7 @@ XQUEUE_INTERFACE = AUTH_TOKENS['XQUEUE_INTERFACE']
|
||||
MODULESTORE = AUTH_TOKENS.get('MODULESTORE', MODULESTORE)
|
||||
CONTENTSTORE = AUTH_TOKENS.get('CONTENTSTORE', CONTENTSTORE)
|
||||
|
||||
STAFF_GRADING_INTERFACE = AUTH_TOKENS.get('STAFF_GRADING_INTERFACE',
|
||||
STAFF_GRADING_INTERFACE)
|
||||
PEER_GRADING_INTERFACE = AUTH_TOKENS.get('PEER_GRADING_INTERFACE',
|
||||
PEER_GRADING_INTERFACE)
|
||||
OPEN_ENDED_GRADING_INTERFACE = AUTH_TOKENS.get('OPEN_ENDED_GRADING_INTERFACE', OPEN_ENDED_GRADING_INTERFACE)
|
||||
|
||||
PEARSON_TEST_USER = "pearsontest"
|
||||
PEARSON_TEST_PASSWORD = AUTH_TOKENS.get("PEARSON_TEST_PASSWORD")
|
||||
|
||||
@@ -310,37 +310,30 @@ WIKI_USE_BOOTSTRAP_SELECT_WIDGET = False
|
||||
WIKI_LINK_LIVE_LOOKUPS = False
|
||||
WIKI_LINK_DEFAULT_LEVEL = 2
|
||||
|
||||
################################# Staff grading config #####################
|
||||
|
||||
#By setting up the default settings with an incorrect user name and password,
|
||||
# will get an error when attempting to connect
|
||||
STAFF_GRADING_INTERFACE = {
|
||||
'url': 'http://sandbox-grader-001.m.edx.org/staff_grading',
|
||||
'username': 'incorrect_user',
|
||||
'password': 'incorrect_pass',
|
||||
}
|
||||
|
||||
# Used for testing, debugging
|
||||
MOCK_STAFF_GRADING = False
|
||||
|
||||
################################# Pearson TestCenter config ################
|
||||
|
||||
PEARSONVUE_SIGNINPAGE_URL = "https://www1.pearsonvue.com/testtaker/signin/SignInPage/EDX"
|
||||
# TESTCENTER_ACCOMMODATION_REQUEST_EMAIL = "exam-help@edx.org"
|
||||
|
||||
################################# Peer grading config #####################
|
||||
################################# open ended grading config #####################
|
||||
|
||||
#By setting up the default settings with an incorrect user name and password,
|
||||
# will get an error when attempting to connect
|
||||
PEER_GRADING_INTERFACE = {
|
||||
OPEN_ENDED_GRADING_INTERFACE = {
|
||||
'url': 'http://sandbox-grader-001.m.edx.org/peer_grading',
|
||||
'username': 'incorrect_user',
|
||||
'password': 'incorrect_pass',
|
||||
'staff_grading' : 'staff_grading',
|
||||
'peer_grading' : 'peer_grading',
|
||||
'grading_controller' : 'grading_controller'
|
||||
}
|
||||
|
||||
# Used for testing, debugging
|
||||
# Used for testing, debugging peer grading
|
||||
MOCK_PEER_GRADING = False
|
||||
|
||||
# Used for testing, debugging staff grading
|
||||
MOCK_STAFF_GRADING = False
|
||||
|
||||
################################# Jasmine ###################################
|
||||
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
|
||||
|
||||
|
||||
@@ -131,21 +131,17 @@ if os.path.isdir(DATA_DIR):
|
||||
|
||||
MITX_VERSION_STRING = os.popen('cd %s; git describe' % REPO_ROOT).read().strip()
|
||||
|
||||
################################# Staff grading config #####################
|
||||
################################# Open ended grading config #####################
|
||||
|
||||
STAFF_GRADING_INTERFACE = {
|
||||
'url': 'http://127.0.0.1:3033/staff_grading',
|
||||
'username': 'lms',
|
||||
'password': 'abcd',
|
||||
}
|
||||
OPEN_ENDED_GRADING_INTERFACE = {
|
||||
'url' : 'http://127.0.0.1:3033/',
|
||||
'username' : 'lms',
|
||||
'password' : 'abcd',
|
||||
'staff_grading' : 'staff_grading',
|
||||
'peer_grading' : 'peer_grading',
|
||||
'grading_controller' : 'grading_controller'
|
||||
}
|
||||
|
||||
################################# Peer grading config #####################
|
||||
|
||||
PEER_GRADING_INTERFACE = {
|
||||
'url': 'http://127.0.0.1:3033/peer_grading',
|
||||
'username': 'lms',
|
||||
'password': 'abcd',
|
||||
}
|
||||
################################ LMS Migration #################################
|
||||
MITX_FEATURES['ENABLE_LMS_MIGRATION'] = True
|
||||
MITX_FEATURES['ACCESS_REQUIRE_STAFF_FOR_COURSE'] = False # require that user be in the staff_* group to be able to enroll
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<table class="problem-list">
|
||||
<tr>
|
||||
<th>Problem Name</th>
|
||||
<th>Due date</th>
|
||||
<th>Graded</th>
|
||||
<th>Available</th>
|
||||
<th>Required</th>
|
||||
@@ -22,7 +23,18 @@
|
||||
%for problem in problem_list:
|
||||
<tr data-graded="${problem['num_graded']}" data-required="${problem['num_required']}">
|
||||
<td class="problem-name">
|
||||
<a href="#problem" data-location="${problem['location']}" class="problem-button">${problem['problem_name']}</a>
|
||||
%if problem['closed']:
|
||||
${problem['problem_name']}
|
||||
%else:
|
||||
<a href="#problem" data-location="${problem['location']}" class="problem-button">${problem['problem_name']}</a>
|
||||
%endif
|
||||
</td>
|
||||
<td>
|
||||
% if problem['due']:
|
||||
${problem['due']}
|
||||
% else:
|
||||
No due date
|
||||
% endif
|
||||
</td>
|
||||
<td>
|
||||
${problem['num_graded']}
|
||||
|
||||
10
lms/templates/peer_grading/peer_grading_closed.html
Normal file
10
lms/templates/peer_grading/peer_grading_closed.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<section class="container peer-grading-container">
|
||||
<h2>Peer Grading</h2>
|
||||
<p>The due date has passed, and
|
||||
% if use_for_single_location:
|
||||
peer grading for this problem is closed at this time.
|
||||
%else:
|
||||
peer grading is closed at this time.
|
||||
%endif
|
||||
</p>
|
||||
</section>
|
||||
Reference in New Issue
Block a user