Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
3204c8b3
Commit
3204c8b3
authored
Jun 10, 2015
by
zubair-arbi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
show credit eligibility requirements in studio
ECOM-1591
parent
233aba74
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
324 additions
and
45 deletions
+324
-45
cms/djangoapps/contentstore/views/course.py
+33
-0
cms/djangoapps/contentstore/views/tests/test_credit_eligibility.py
+59
-0
cms/djangoapps/models/settings/course_grading.py
+22
-0
cms/djangoapps/models/settings/course_metadata.py
+2
-1
cms/envs/common.py
+2
-0
cms/static/js/factories/settings.js
+3
-2
cms/static/js/models/settings/course_grading_policy.js
+26
-1
cms/static/js/views/settings/grading.js
+19
-1
cms/static/js/views/settings/main.js
+13
-3
cms/static/sass/views/_settings.scss
+24
-2
cms/templates/settings.html
+50
-3
cms/templates/settings_graders.html
+18
-2
common/test/acceptance/pages/studio/settings_advanced.py
+0
-1
openedx/core/djangoapps/credit/api.py
+49
-25
openedx/core/djangoapps/credit/signals.py
+2
-2
openedx/core/djangoapps/credit/tasks.py
+2
-2
No files found.
cms/djangoapps/contentstore/views/course.py
View file @
3204c8b3
...
...
@@ -24,6 +24,8 @@ from xmodule.modulestore.django import modulestore
from
xmodule.contentstore.content
import
StaticContent
from
xmodule.tabs
import
CourseTab
from
openedx.core.lib.course_tabs
import
CourseTabPluginManager
from
openedx.core.djangoapps.credit.api
import
is_credit_course
,
get_credit_requirements
from
openedx.core.djangoapps.credit.tasks
import
update_credit_course_requirements
from
xmodule.modulestore
import
EdxJSONEncoder
from
xmodule.modulestore.exceptions
import
ItemNotFoundError
,
DuplicateCourseError
from
opaque_keys
import
InvalidKeyError
...
...
@@ -842,6 +844,7 @@ def settings_handler(request, course_key_string):
"""
course_key
=
CourseKey
.
from_string
(
course_key_string
)
prerequisite_course_enabled
=
settings
.
FEATURES
.
get
(
'ENABLE_PREREQUISITE_COURSES'
,
False
)
credit_eligibility_enabled
=
settings
.
FEATURES
.
get
(
'ENABLE_CREDIT_ELIGIBILITY'
,
False
)
with
modulestore
()
.
bulk_operations
(
course_key
):
course_module
=
get_course_and_check_access
(
course_key
,
request
.
user
)
if
'text/html'
in
request
.
META
.
get
(
'HTTP_ACCEPT'
,
''
)
and
request
.
method
==
'GET'
:
...
...
@@ -867,6 +870,9 @@ def settings_handler(request, course_key_string):
'upload_asset_url'
:
upload_asset_url
,
'course_handler_url'
:
reverse_course_url
(
'course_handler'
,
course_key
),
'language_options'
:
settings
.
ALL_LANGUAGES
,
'credit_eligibility_enabled'
:
credit_eligibility_enabled
,
'is_credit_course'
:
False
,
'show_min_grade_warning'
:
False
,
}
if
prerequisite_course_enabled
:
courses
,
in_process_course_actions
=
get_courses_accessible_to_user
(
request
)
...
...
@@ -876,6 +882,27 @@ def settings_handler(request, course_key_string):
courses
=
_remove_in_process_courses
(
courses
,
in_process_course_actions
)
settings_context
.
update
({
'possible_pre_requisite_courses'
:
courses
})
if
credit_eligibility_enabled
:
if
is_credit_course
(
course_key
):
# get and all credit eligibility requirements
credit_requirements
=
get_credit_requirements
(
course_key
)
# pair together requirements with same 'namespace' values
paired_requirements
=
{}
for
requirement
in
credit_requirements
:
namespace
=
requirement
.
pop
(
"namespace"
)
paired_requirements
.
setdefault
(
namespace
,
[])
.
append
(
requirement
)
# if 'minimum_grade_credit' of a course is not set or 0 then
# show warning message to course author.
show_min_grade_warning
=
False
if
course_module
.
minimum_grade_credit
>
0
else
True
settings_context
.
update
(
{
'is_credit_course'
:
True
,
'credit_requirements'
:
paired_requirements
,
'show_min_grade_warning'
:
show_min_grade_warning
,
}
)
return
render_to_response
(
'settings.html'
,
settings_context
)
elif
'application/json'
in
request
.
META
.
get
(
'HTTP_ACCEPT'
,
''
):
if
request
.
method
==
'GET'
:
...
...
@@ -961,6 +988,7 @@ def grading_handler(request, course_key_string, grader_index=None):
'course_locator'
:
course_key
,
'course_details'
:
json
.
dumps
(
course_details
,
cls
=
CourseSettingsEncoder
),
'grading_url'
:
reverse_course_url
(
'grading_handler'
,
course_key
),
'is_credit_course'
:
is_credit_course
(
course_key
),
})
elif
'application/json'
in
request
.
META
.
get
(
'HTTP_ACCEPT'
,
''
):
if
request
.
method
==
'GET'
:
...
...
@@ -973,6 +1001,11 @@ def grading_handler(request, course_key_string, grader_index=None):
else
:
return
JsonResponse
(
CourseGradingModel
.
fetch_grader
(
course_key
,
grader_index
))
elif
request
.
method
in
(
'POST'
,
'PUT'
):
# post or put, doesn't matter.
# update credit course requirements if 'minimum_grade_credit'
# field value is changed
if
'minimum_grade_credit'
in
request
.
json
:
update_credit_course_requirements
.
delay
(
unicode
(
course_key
))
# None implies update the whole model (cutoffs, graceperiod, and graders) not a specific grader
if
grader_index
is
None
:
return
JsonResponse
(
...
...
cms/djangoapps/contentstore/views/tests/test_credit_eligibility.py
0 → 100644
View file @
3204c8b3
"""
Unit tests for credit eligibility UI in Studio.
"""
import
mock
from
contentstore.tests.utils
import
CourseTestCase
from
contentstore.utils
import
reverse_course_url
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
openedx.core.djangoapps.credit.api
import
get_credit_requirements
from
openedx.core.djangoapps.credit.models
import
CreditCourse
from
openedx.core.djangoapps.credit.signals
import
listen_for_course_publish
class
CreditEligibilityTest
(
CourseTestCase
):
"""Base class to test the course settings details view in Studio for credit
eligibility requirements.
"""
def
setUp
(
self
):
super
(
CreditEligibilityTest
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
(
org
=
'edX'
,
number
=
'dummy'
,
display_name
=
'Credit Course'
)
self
.
course_details_url
=
reverse_course_url
(
'settings_handler'
,
unicode
(
self
.
course
.
id
))
@mock.patch.dict
(
"django.conf.settings.FEATURES"
,
{
'ENABLE_CREDIT_ELIGIBILITY'
:
False
})
def
test_course_details_with_disabled_setting
(
self
):
"""Test that user don't see credit eligibility requirements in response
if the feature flag 'ENABLE_CREDIT_ELIGIBILITY' is not enabled.
"""
response
=
self
.
client
.
get_html
(
self
.
course_details_url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertNotContains
(
response
,
"Credit Eligibility Requirements"
)
self
.
assertNotContains
(
response
,
"Steps needed for credit eligibility"
)
@mock.patch.dict
(
"django.conf.settings.FEATURES"
,
{
'ENABLE_CREDIT_ELIGIBILITY'
:
True
})
def
test_course_details_with_enabled_setting
(
self
):
"""Test that credit eligibility requirements are present in
response if the feature flag 'ENABLE_CREDIT_ELIGIBILITY' is enabled.
"""
# verify that credit eligibility requirements block don't show if the
# course is not set as credit course
response
=
self
.
client
.
get_html
(
self
.
course_details_url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertNotContains
(
response
,
"Credit Eligibility Requirements"
)
self
.
assertNotContains
(
response
,
"Steps needed for credit eligibility"
)
# verify that credit eligibility requirements block shows if the
# course is set as credit course and it has eligibility requirements
credit_course
=
CreditCourse
(
course_key
=
unicode
(
self
.
course
.
id
),
enabled
=
True
)
credit_course
.
save
()
self
.
assertEqual
(
len
(
get_credit_requirements
(
self
.
course
.
id
)),
0
)
# test that after publishing course, minimum grade requirement is added
listen_for_course_publish
(
self
,
self
.
course
.
id
)
self
.
assertEqual
(
len
(
get_credit_requirements
(
self
.
course
.
id
)),
1
)
response
=
self
.
client
.
get_html
(
self
.
course_details_url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertContains
(
response
,
"Credit Eligibility Requirements"
)
self
.
assertContains
(
response
,
"Steps needed for credit eligibility"
)
cms/djangoapps/models/settings/course_grading.py
View file @
3204c8b3
...
...
@@ -14,6 +14,7 @@ class CourseGradingModel(object):
]
# weights transformed to ints [0..100]
self
.
grade_cutoffs
=
course_descriptor
.
grade_cutoffs
self
.
grace_period
=
CourseGradingModel
.
convert_set_grace_period
(
course_descriptor
)
self
.
minimum_grade_credit
=
course_descriptor
.
minimum_grade_credit
@classmethod
def
fetch
(
cls
,
course_key
):
...
...
@@ -62,6 +63,8 @@ class CourseGradingModel(object):
CourseGradingModel
.
update_grace_period_from_json
(
course_key
,
jsondict
[
'grace_period'
],
user
)
CourseGradingModel
.
update_minimum_grade_credit_from_json
(
course_key
,
jsondict
[
'minimum_grade_credit'
],
user
)
return
CourseGradingModel
.
fetch
(
course_key
)
@staticmethod
...
...
@@ -119,6 +122,25 @@ class CourseGradingModel(object):
modulestore
()
.
update_item
(
descriptor
,
user
.
id
)
@staticmethod
def
update_minimum_grade_credit_from_json
(
course_key
,
minimum_grade_credit
,
user
):
"""Update the course's default minimum grade requirement for credit.
Args:
course_key(CourseKey): The course identifier
minimum_grade_json(Float): Minimum grade value
user(User): The user object
"""
descriptor
=
modulestore
()
.
get_course
(
course_key
)
# 'minimum_grade_credit' cannot be set to None
if
minimum_grade_credit
is
not
None
:
minimum_grade_credit
=
minimum_grade_credit
descriptor
.
minimum_grade_credit
=
minimum_grade_credit
modulestore
()
.
update_item
(
descriptor
,
user
.
id
)
@staticmethod
def
delete_grader
(
course_key
,
index
,
user
):
"""
Delete the grader of the given type from the given course.
...
...
cms/djangoapps/models/settings/course_metadata.py
View file @
3204c8b3
...
...
@@ -44,7 +44,8 @@ class CourseMetadata(object):
'is_entrance_exam'
,
'in_entrance_exam'
,
'language'
,
'certificates'
'certificates'
,
'minimum_grade_credit'
,
]
@classmethod
...
...
cms/envs/common.py
View file @
3204c8b3
...
...
@@ -173,6 +173,8 @@ FEATURES = {
# How many seconds to show the bumper again, default is 7 days:
'SHOW_BUMPER_PERIODICITY'
:
7
*
24
*
3600
,
# Enable credit eligibility feature
'ENABLE_CREDIT_ELIGIBILITY'
:
False
,
}
ENABLE_JASMINE
=
False
...
...
cms/static/js/factories/settings.js
View file @
3204c8b3
...
...
@@ -2,7 +2,7 @@ define([
'jquery'
,
'js/models/settings/course_details'
,
'js/views/settings/main'
],
function
(
$
,
CourseDetailsModel
,
MainView
)
{
'use strict'
;
return
function
(
detailsUrl
)
{
return
function
(
detailsUrl
,
showMinGradeWarning
)
{
var
model
;
// highlighting labels when fields are focused in
$
(
'form :input'
)
...
...
@@ -19,7 +19,8 @@ define([
success
:
function
(
model
)
{
var
editor
=
new
MainView
({
el
:
$
(
'.settings-details'
),
model
:
model
model
:
model
,
showMinGradeWarning
:
showMinGradeWarning
});
editor
.
render
();
},
...
...
cms/static/js/models/settings/course_grading_policy.js
View file @
3204c8b3
...
...
@@ -5,7 +5,8 @@ var CourseGradingPolicy = Backbone.Model.extend({
defaults
:
{
graders
:
null
,
// CourseGraderCollection
grade_cutoffs
:
null
,
// CourseGradeCutoff model
grace_period
:
null
// either null or { hours: n, minutes: m, ...}
grace_period
:
null
,
// either null or { hours: n, minutes: m, ...}
minimum_grade_credit
:
null
// either null or percentage
},
parse
:
function
(
attributes
)
{
if
(
attributes
[
'graders'
])
{
...
...
@@ -28,6 +29,11 @@ var CourseGradingPolicy = Backbone.Model.extend({
minutes
:
0
}
}
// If minimum_grade_credit is unset or equal to 0 on the server,
// it's received as 0
if
(
attributes
.
minimum_grade_credit
===
null
)
{
attributes
.
minimum_grade_credit
=
0
;
}
return
attributes
;
},
gracePeriodToDate
:
function
()
{
...
...
@@ -55,6 +61,13 @@ var CourseGradingPolicy = Backbone.Model.extend({
minutes
:
parseInt
(
pieces
[
1
],
10
)
}
},
parseMinimumGradeCredit
:
function
(
minimum_grade_credit
)
{
// get the value of minimum grade credit value in percentage
if
(
isNaN
(
minimum_grade_credit
))
{
return
0
;
}
return
parseInt
(
minimum_grade_credit
);
},
validate
:
function
(
attrs
)
{
if
(
_
.
has
(
attrs
,
'grace_period'
))
{
if
(
attrs
[
'grace_period'
]
===
null
)
{
...
...
@@ -63,6 +76,18 @@ var CourseGradingPolicy = Backbone.Model.extend({
}
}
}
if
(
_
.
has
(
attrs
,
'minimum_grade_credit'
))
{
var
minimum_grade_cutoff
=
_
.
values
(
attrs
.
grade_cutoffs
).
pop
();
if
(
isNaN
(
attrs
.
minimum_grade_credit
)
||
attrs
.
minimum_grade_credit
===
null
||
attrs
.
minimum_grade_credit
<
minimum_grade_cutoff
)
{
return
{
'minimum_grade_credit'
:
interpolate
(
gettext
(
'Not able to set passing grade to less than %(minimum_grade_cutoff)s%.'
),
{
minimum_grade_cutoff
:
minimum_grade_cutoff
*
100
},
true
)
};
}
}
}
});
...
...
cms/static/js/views/settings/grading.js
View file @
3204c8b3
...
...
@@ -42,6 +42,7 @@ var GradingView = ValidatingView.extend({
this
.
clearValidationErrors
();
this
.
renderGracePeriod
();
this
.
renderMinimumGradeCredit
();
// Create and render the grading type subs
var
self
=
this
;
...
...
@@ -86,7 +87,8 @@ var GradingView = ValidatingView.extend({
this
.
model
.
get
(
'graders'
).
push
({});
},
fieldToSelectorMap
:
{
'grace_period'
:
'course-grading-graceperiod'
'grace_period'
:
'course-grading-graceperiod'
,
'minimum_grade_credit'
:
'course-minimum_grade_credit'
},
renderGracePeriod
:
function
()
{
var
format
=
function
(
time
)
{
...
...
@@ -97,11 +99,23 @@ var GradingView = ValidatingView.extend({
format
(
grace_period
.
hours
)
+
':'
+
format
(
grace_period
.
minutes
)
);
},
renderMinimumGradeCredit
:
function
()
{
var
minimum_grade_credit
=
this
.
model
.
get
(
'minimum_grade_credit'
);
this
.
$el
.
find
(
'#course-minimum_grade_credit'
).
val
(
parseFloat
(
minimum_grade_credit
)
*
100
+
'%'
);
},
setGracePeriod
:
function
(
event
)
{
this
.
clearValidationErrors
();
var
newVal
=
this
.
model
.
parseGracePeriod
(
$
(
event
.
currentTarget
).
val
());
this
.
model
.
set
(
'grace_period'
,
newVal
,
{
validate
:
true
});
},
setMinimumGradeCredit
:
function
(
event
)
{
this
.
clearValidationErrors
();
// get field value in float
var
newVal
=
this
.
model
.
parseMinimumGradeCredit
(
$
(
event
.
currentTarget
).
val
())
/
100
;
this
.
model
.
set
(
'minimum_grade_credit'
,
newVal
,
{
validate
:
true
});
},
updateModel
:
function
(
event
)
{
if
(
!
this
.
selectorToField
[
event
.
currentTarget
.
id
])
return
;
...
...
@@ -110,6 +124,10 @@ var GradingView = ValidatingView.extend({
this
.
setGracePeriod
(
event
);
break
;
case
'minimum_grade_credit'
:
this
.
setMinimumGradeCredit
(
event
);
break
;
default
:
this
.
setField
(
event
);
break
;
...
...
cms/static/js/views/settings/main.js
View file @
3204c8b3
define
([
"js/views/validation"
,
"codemirror"
,
"underscore"
,
"jquery"
,
"jquery.ui"
,
"js/utils/date_utils"
,
"js/models/uploads"
,
"js/views/uploads"
,
"js/utils/change_on_enter"
,
"js/views/license"
,
"js/models/license"
,
"jquery.timepicker"
,
"date"
],
"js/views/uploads"
,
"js/utils/change_on_enter"
,
"js/views/license"
,
"js/models/license"
,
"js/views/feedback_notification"
,
"jquery.timepicker"
,
"date"
],
function
(
ValidatingView
,
CodeMirror
,
_
,
$
,
ui
,
DateUtils
,
FileUploadModel
,
FileUploadDialog
,
TriggerChangeEventOnEnter
,
LicenseView
,
LicenseModel
)
{
FileUploadDialog
,
TriggerChangeEventOnEnter
,
LicenseView
,
LicenseModel
,
NotificationView
)
{
var
DetailsView
=
ValidatingView
.
extend
({
// Model class is CMS.Models.Settings.CourseDetails
...
...
@@ -21,7 +22,8 @@ var DetailsView = ValidatingView.extend({
'click .action-upload-image'
:
"uploadImage"
},
initialize
:
function
()
{
initialize
:
function
(
options
)
{
options
=
options
||
{};
this
.
fileAnchorTemplate
=
_
.
template
(
'<a href="<%= fullpath %>"> <i class="icon fa fa-file"></i><%= filename %></a>'
);
// fill in fields
this
.
$el
.
find
(
"#course-language"
).
val
(
this
.
model
.
get
(
'language'
));
...
...
@@ -49,6 +51,14 @@ var DetailsView = ValidatingView.extend({
showPreview
:
true
});
this
.
listenTo
(
this
.
licenseModel
,
'change'
,
this
.
handleLicenseChange
);
if
(
options
.
showMinGradeWarning
||
false
)
{
new
NotificationView
.
Warning
({
title
:
gettext
(
"Credit Eligibility Requirements"
),
message
:
gettext
(
"Minimum passing grade for credit is not set."
),
closeIcon
:
true
}).
show
();
}
},
render
:
function
()
{
...
...
cms/static/sass/views/_settings.scss
View file @
3204c8b3
...
...
@@ -326,6 +326,24 @@
width
:
flex-grid
(
5
,
9
);
}
// Credit eligibility requirements
#credit-minimum-passing-grade
{
float
:
left
;
width
:
flex-grid
(
3
,
9
);
margin-right
:
flex-gutter
();
}
#credit-proctoring-requirements
{
float
:
left
;
width
:
flex-grid
(
3
,
9
);
margin-right
:
flex-gutter
();
}
#credit-reverification-requirements
{
float
:
left
;
width
:
flex-grid
(
3
,
9
);
}
// course link note
.note-promotion-courseURL
{
box-shadow
:
0
2px
1px
$shadow-l1
;
...
...
@@ -731,6 +749,11 @@
#field-course-grading-graceperiod
{
width
:
flex-grid
(
3
,
9
);
}
#field-course-minimum_grade_credit
{
width
:
flex-grid
(
4
,
9
);
}
}
&
.assignment-types
{
...
...
@@ -985,4 +1008,4 @@
}
}
}
}
\ No newline at end of file
}
cms/templates/settings.html
View file @
3204c8b3
...
...
@@ -5,9 +5,10 @@
<
%
namespace
name=
'static'
file=
'static_content.html'
/>
<
%!
import
json
import
urllib
from
django
.
utils
.
translation
import
ugettext
as
_
from
contentstore
import
utils
import
urllib
%
>
<
%
block
name=
"header_extras"
>
...
...
@@ -27,9 +28,10 @@ CMS.URL = CMS.URL || {};
CMS
.
URL
.
UPLOAD_ASSET
=
'${upload_asset_url}'
;
</script>
</
%
block>
<
%
block
name=
"requirejs"
>
require(["js/factories/settings"], function(SettingsFactory) {
SettingsFactory("${details_url}");
SettingsFactory("${details_url}"
, ${json.dumps(show_min_grade_warning)}
);
});
</
%
block>
...
...
@@ -116,9 +118,54 @@ CMS.URL.UPLOAD_ASSET = '${upload_asset_url}';
</div>
% endif
</section>
<hr
class=
"divide"
/>
% if credit_eligibility_enabled and is_credit_course:
<section
class=
"group-settings basic"
>
<header>
<h2
class=
"title-2"
>
${_("Credit Eligibility Requirements")}
</h2>
<span
class=
"tip"
>
${_("Steps needed for credit eligibility")}
</span>
</header>
% if credit_requirements:
<ol
class=
"list-input"
>
% if 'grade' in credit_requirements:
<li
class=
"field text is-not-editable"
id=
"credit-minimum-passing-grade"
>
<label
for=
"minimum-passing-grade"
>
${_("Minimum Passing Grade")}
</label>
% for requirement in credit_requirements['grade']:
<input
title=
"${_('This field is disabled: this information cannot be changed.')}"
type=
"text"
class=
"long"
id=
"${requirement['name']}"
value=
"${'{0:.0f}%'.format(float(requirement['criteria']['min_grade'] or 0)*100)}"
readonly
/>
% endfor
</li>
% endif
% if 'proctored_exam' in credit_requirements:
<li
class=
"field text is-not-editable"
id=
"credit-proctoring-requirements"
>
<label
for=
"proctoring-requirements"
>
${_("Successful Proctored Exam")}
</label>
% for requirement in credit_requirements['proctored_exam']:
<input
title=
"${_('This field is disabled: this information cannot be changed.')}"
type=
"text"
class=
"long"
id=
"${requirement['name']}"
value=
"${requirement['display_name']}"
readonly
/>
% endfor
</li>
% endif
% if 'reverification' in credit_requirements:
<li
class=
"field text is-not-editable"
id=
"credit-reverification-requirements"
>
<label
for=
"reverification-requirements"
>
${_("Successful In Course Reverification")}
</label>
% for requirement in credit_requirements['reverification']:
## Translators: 'Access to Assessment 1' means the access for a requirement with name 'Assessment 1'
<input
title=
"${_('This field is disabled: this information cannot be changed.')}"
type=
"text"
class=
"long"
id=
"${requirement['name']}"
value=
"${_('Access to {display_name}').format(display_name=requirement['display_name'])}"
readonly
/>
% endfor
</li>
% endif
</ol>
% else:
<p>
No credit requirements found.
</p>
% endif
</section>
<hr
class=
"divide"
/>
% endif
<section
class=
"group-settings schedule"
>
<header>
<h2
class=
"title-2"
>
${_('Course Schedule')}
</h2>
...
...
cms/templates/settings_graders.html
View file @
3204c8b3
...
...
@@ -73,9 +73,26 @@
</li>
</ol>
</section>
<hr
class=
"divide"
/>
% if settings.FEATURES.get("ENABLE_CREDIT_ELIGIBILITY", False) and is_credit_course:
<section
class=
"group-settings grade-rules"
>
<header>
<h2
class=
"title-2"
>
${_("Credit Grade
&
Eligibility")}
</h2>
<span
class=
"tip"
>
${_("Settings for credit eligibility")}
</span>
</header>
<ol
class=
"list-input"
>
<li
class=
"field text"
id=
"field-course-minimum_grade_credit"
>
<label
for=
"course-minimum_grade_credit"
>
${_("Minimum Passing Grade to Earn Credit:")}
</label>
<input
type=
"text"
class=
"short time"
id=
"course-minimum_grade_credit"
value=
"0"
placeholder=
"80%"
autocomplete=
"off"
/>
<span
class=
"tip tip-inline"
>
${_("Must be greater than or equal to passing grade")}
</span>
</li>
</ol>
</section>
<hr
class=
"divide"
/>
% endif
<section
class=
"group-settings grade-rules"
>
<header>
<h2
class=
"title-2"
>
${_("Grading Rules
&
Policies")}
</h2>
...
...
@@ -90,7 +107,6 @@
</li>
</ol>
</section>
<hr
class=
"divide"
/>
<section
class=
"group-settings assignment-types"
>
...
...
common/test/acceptance/pages/studio/settings_advanced.py
View file @
3204c8b3
...
...
@@ -200,6 +200,5 @@ class AdvancedSettingsPage(CoursePage):
'annotation_storage_url'
,
'social_sharing_url'
,
'teams_configuration'
,
'minimum_grade_credit'
,
'video_bumper'
,
]
openedx/core/djangoapps/credit/api.py
View file @
3204c8b3
""" Contains the APIs for course credit requirements """
"""
Contains the APIs for course credit requirements.
"""
import
logging
import
uuid
from
django.db
import
transaction
from
student.models
import
User
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
student.models
import
User
from
.exceptions
import
(
InvalidCreditRequirements
,
InvalidCreditCourse
,
...
...
@@ -96,32 +101,33 @@ def get_credit_requirements(course_key, namespace=None):
Example:
>>> get_credit_requirements("course-v1-edX-DemoX-1T2015")
{
requirements =
[
{
"namespace": "reverification",
"name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid",
"display_name": "Assessment 1",
"criteria": {},
},
{
"namespace": "proctored_exam",
"name": "i4x://edX/DemoX/proctoring-block/final_uuid",
"display_name": "Final Exam",
"criteria": {},
},
{
"namespace": "grade",
"name": "grade",
"display_name": "Grade",
"criteria": {"min_grade": 0.8},
},
]
}
{
requirements =
[
{
"namespace": "reverification",
"name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid",
"display_name": "Assessment 1",
"criteria": {},
},
{
"namespace": "proctored_exam",
"name": "i4x://edX/DemoX/proctoring-block/final_uuid",
"display_name": "Final Exam",
"criteria": {},
},
{
"namespace": "grade",
"name": "grade",
"display_name": "Grade",
"criteria": {"min_grade": 0.8},
},
]
}
Returns:
Dict of requirements in the given namespace
"""
requirements
=
CreditRequirement
.
get_course_requirements
(
course_key
,
namespace
)
...
...
@@ -455,3 +461,21 @@ def _validate_requirements(requirements):
)
)
return
invalid_requirements
def
is_credit_course
(
course_key
):
"""API method to check if course is credit or not.
Args:
course_key(CourseKey): The course identifier string or CourseKey object
Returns:
Bool True if the course is marked credit else False
"""
try
:
course_key
=
CourseKey
.
from_string
(
unicode
(
course_key
))
except
InvalidKeyError
:
return
False
return
CreditCourse
.
is_credit_course
(
course_key
=
course_key
)
openedx/core/djangoapps/credit/signals.py
View file @
3204c8b3
...
...
@@ -15,6 +15,6 @@ def listen_for_course_publish(sender, course_key, **kwargs): # pylint: disable=
# Import here, because signal is registered at startup, but items in tasks
# are not yet able to be loaded
from
.tasks
import
update_course_requirements
from
.tasks
import
update_c
redit_c
ourse_requirements
update_course_requirements
.
delay
(
unicode
(
course_key
))
update_c
redit_c
ourse_requirements
.
delay
(
unicode
(
course_key
))
openedx/core/djangoapps/credit/tasks.py
View file @
3204c8b3
...
...
@@ -20,7 +20,7 @@ LOGGER = get_task_logger(__name__)
# pylint: disable=not-callable
@task
(
default_retry_delay
=
settings
.
CREDIT_TASK_DEFAULT_RETRY_DELAY
,
max_retries
=
settings
.
CREDIT_TASK_MAX_RETRIES
)
def
update_c
ourse_requirements
(
course_id
):
def
update_c
redit_course_requirements
(
course_id
):
# pylint: disable=invalid-name
"""Updates course requirements table for a course.
Args:
...
...
@@ -39,7 +39,7 @@ def update_course_requirements(course_id):
set_credit_requirements
(
course_key
,
requirements
)
except
(
InvalidKeyError
,
ItemNotFoundError
,
InvalidCreditRequirements
)
as
exc
:
LOGGER
.
error
(
'Error on adding the requirements for course
%
s -
%
s'
,
course_id
,
unicode
(
exc
))
raise
update_course_requirements
.
retry
(
args
=
[
course_id
],
exc
=
exc
)
raise
update_c
redit_c
ourse_requirements
.
retry
(
args
=
[
course_id
],
exc
=
exc
)
else
:
LOGGER
.
info
(
'Requirements added for course
%
s'
,
course_id
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment