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
9cc30d09
Commit
9cc30d09
authored
May 02, 2014
by
Joe Blaylock
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2533 from edx/jrbl/certs_request_cert_endpoint
/request_cert AJAX endpoint
parents
3ff3973b
09745079
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
152 additions
and
27 deletions
+152
-27
common/djangoapps/student/views.py
+10
-10
common/lib/xmodule/xmodule/course_module.py
+9
-0
common/lib/xmodule/xmodule/templates/html/grade_me.yaml
+26
-0
common/lib/xmodule/xmodule/tests/test_course_module.py
+48
-4
lms/djangoapps/certificates/queue.py
+16
-6
lms/djangoapps/certificates/views.py
+37
-5
lms/templates/dashboard/_dashboard_certificate_information.html
+4
-1
lms/templates/dashboard/_dashboard_course_listing.html
+1
-1
lms/urls.py
+1
-0
No files found.
common/djangoapps/student/views.py
View file @
9cc30d09
...
...
@@ -200,7 +200,7 @@ def cert_info(user, course):
'survey_url': url, only if show_survey_button is True
'grade': if status is not 'processing'
"""
if
not
course
.
has_ended
():
if
not
course
.
may_certify
():
return
{}
return
_cert_info
(
user
,
course
,
certificate_status_for_student
(
user
,
course
.
id
))
...
...
@@ -291,6 +291,15 @@ def _cert_info(user, course, cert_status):
"""
Implements the logic for cert_info -- split out for testing.
"""
# simplify the status for the template using this lookup table
template_state
=
{
CertificateStatuses
.
generating
:
'generating'
,
CertificateStatuses
.
regenerating
:
'generating'
,
CertificateStatuses
.
downloadable
:
'ready'
,
CertificateStatuses
.
notpassing
:
'notpassing'
,
CertificateStatuses
.
restricted
:
'restricted'
,
}
default_status
=
'processing'
default_info
=
{
'status'
:
default_status
,
...
...
@@ -302,15 +311,6 @@ def _cert_info(user, course, cert_status):
if
cert_status
is
None
:
return
default_info
# simplify the status for the template using this lookup table
template_state
=
{
CertificateStatuses
.
generating
:
'generating'
,
CertificateStatuses
.
regenerating
:
'generating'
,
CertificateStatuses
.
downloadable
:
'ready'
,
CertificateStatuses
.
notpassing
:
'notpassing'
,
CertificateStatuses
.
restricted
:
'restricted'
,
}
status
=
template_state
.
get
(
cert_status
[
'status'
],
default_status
)
d
=
{
'status'
:
status
,
...
...
common/lib/xmodule/xmodule/course_module.py
View file @
9cc30d09
...
...
@@ -369,6 +369,9 @@ class CourseFields(object):
)
enrollment_domain
=
String
(
help
=
"External login method associated with user accounts allowed to register in course"
,
scope
=
Scope
.
settings
)
certificates_show_before_end
=
Boolean
(
help
=
"True if students may download certificates before course end"
,
scope
=
Scope
.
settings
,
default
=
False
)
course_image
=
String
(
help
=
"Filename of the course image"
,
scope
=
Scope
.
settings
,
...
...
@@ -592,6 +595,12 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
return
datetime
.
now
(
UTC
())
>
self
.
end
def
may_certify
(
self
):
"""
Return True if it is acceptable to show the student a certificate download link
"""
return
self
.
certificates_show_before_end
or
self
.
has_ended
()
def
has_started
(
self
):
return
datetime
.
now
(
UTC
())
>
self
.
start
...
...
common/lib/xmodule/xmodule/templates/html/grade_me.yaml
0 → 100644
View file @
9cc30d09
---
metadata
:
display_name
:
(Grade Me!) Button
data
:
|
<p>By clicking the button below, you assert that you have completed the course in its entirety.</p>
<input type=button value="Yes, I Agree." id="User_Verify_Button" style="margin-bottom: 20px;" />
<p class="verify-button-success-text" style="font-weight: bold; color: #008200;"></p>
<script type="text/javascript">
var success_message = "Your grading and certification request has been received, <br />if you have passed, your certificate should be available in the next 20 minutes.";
document.getElementById('User_Verify_Button').addEventListener("click",
function(event) {
(function(event) {
var linkcontents = $('a.user-link').contents();
$.ajax({
type: 'POST',
url: '/request_certificate',
data: {'course_id': $$course_id},
success: function(data) {
$('.verify-button-success-text').html(success_message);
}
});
}).call(document.getElementById('User_Verify_Button'), event);
});
</script>
common/lib/xmodule/xmodule/tests/test_course_module.py
View file @
9cc30d09
import
unittest
from
datetime
import
datetime
from
datetime
import
datetime
,
timedelta
from
fs.memoryfs
import
MemoryFS
...
...
@@ -49,7 +49,7 @@ class DummySystem(ImportSystem):
)
def
get_dummy_course
(
start
,
announcement
=
None
,
is_new
=
None
,
advertised_start
=
None
,
end
=
None
):
def
get_dummy_course
(
start
,
announcement
=
None
,
is_new
=
None
,
advertised_start
=
None
,
end
=
None
,
certs
=
False
):
"""Get a dummy course"""
system
=
DummySystem
(
load_error_modules
=
True
)
...
...
@@ -69,17 +69,61 @@ def get_dummy_course(start, announcement=None, is_new=None, advertised_start=Non
{announcement}
{is_new}
{advertised_start}
{end}>
{end}
certificates_show_before_end="{certs}">
<chapter url="hi" url_name="ch" display_name="CH">
<html url_name="h" display_name="H">Two houses, ...</html>
</chapter>
</course>
'''
.
format
(
org
=
ORG
,
course
=
COURSE
,
start
=
start
,
is_new
=
is_new
,
announcement
=
announcement
,
advertised_start
=
advertised_start
,
end
=
end
)
announcement
=
announcement
,
advertised_start
=
advertised_start
,
end
=
end
,
certs
=
certs
)
return
system
.
process_xml
(
start_xml
)
class
HasEndedMayCertifyTestCase
(
unittest
.
TestCase
):
"""Double check the semantics around when to finalize courses."""
def
setUp
(
self
):
system
=
DummySystem
(
load_error_modules
=
True
)
#sample_xml = """
# <course org="{org}" course="{course}" display_organization="{org}_display" display_coursenumber="{course}_display"
# graceperiod="1 day" url_name="test"
# start="2012-01-01T12:00"
# {end}
# certificates_show_before_end={cert}>
# <chapter url="hi" url_name="ch" display_name="CH">
# <html url_name="h" display_name="H">Two houses, ...</html>
# </chapter>
# </course>
#""".format(org=ORG, course=COURSE)
past_end
=
(
datetime
.
now
()
-
timedelta
(
days
=
12
))
.
strftime
(
"
%
Y-
%
m-
%
dT
%
H:
%
M:00"
)
future_end
=
(
datetime
.
now
()
+
timedelta
(
days
=
12
))
.
strftime
(
"
%
Y-
%
m-
%
dT
%
H:
%
M:00"
)
self
.
past_show_certs
=
get_dummy_course
(
"2012-01-01T12:00"
,
end
=
past_end
,
certs
=
True
)
self
.
past_noshow_certs
=
get_dummy_course
(
"2012-01-01T12:00"
,
end
=
past_end
,
certs
=
False
)
self
.
future_show_certs
=
get_dummy_course
(
"2012-01-01T12:00"
,
end
=
future_end
,
certs
=
True
)
self
.
future_noshow_certs
=
get_dummy_course
(
"2012-01-01T12:00"
,
end
=
future_end
,
certs
=
False
)
#self.past_show_certs = system.process_xml(sample_xml.format(end=past_end, cert=True))
#self.past_noshow_certs = system.process_xml(sample_xml.format(end=past_end, cert=False))
#self.future_show_certs = system.process_xml(sample_xml.format(end=future_end, cert=True))
#self.future_noshow_certs = system.process_xml(sample_xml.format(end=future_end, cert=False))
def
test_has_ended
(
self
):
"""Check that has_ended correctly tells us when a course is over."""
self
.
assertTrue
(
self
.
past_show_certs
.
has_ended
())
self
.
assertTrue
(
self
.
past_noshow_certs
.
has_ended
())
self
.
assertFalse
(
self
.
future_show_certs
.
has_ended
())
self
.
assertFalse
(
self
.
future_noshow_certs
.
has_ended
())
def
test_may_certify
(
self
):
"""Check that may_certify correctly tells us when a course may wrap."""
self
.
assertTrue
(
self
.
past_show_certs
.
may_certify
())
self
.
assertTrue
(
self
.
past_noshow_certs
.
may_certify
())
self
.
assertTrue
(
self
.
future_show_certs
.
may_certify
())
self
.
assertFalse
(
self
.
future_noshow_certs
.
may_certify
())
class
IsNewCourseTestCase
(
unittest
.
TestCase
):
"""Make sure the property is_new works on courses"""
...
...
lms/djangoapps/certificates/queue.py
View file @
9cc30d09
...
...
@@ -15,6 +15,8 @@ from verify_student.models import SoftwareSecurePhotoVerification
import
json
import
random
import
logging
import
lxml
from
lxml.etree
import
XMLSyntaxError
,
ParserError
from
xmodule.modulestore
import
Location
...
...
@@ -170,6 +172,7 @@ class XQueueCertInterface(object):
if
course
is
None
:
course
=
courses
.
get_course_by_id
(
course_id
)
profile
=
UserProfile
.
objects
.
get
(
user
=
student
)
profile_name
=
profile
.
name
# Needed
self
.
request
.
user
=
student
...
...
@@ -201,9 +204,16 @@ class XQueueCertInterface(object):
cert
.
user
=
student
cert
.
grade
=
grade
[
'percent'
]
cert
.
course_id
=
course_id
cert
.
name
=
profile
.
name
cert
.
name
=
profile_name
# Strip HTML from grade range label
grade_contents
=
grade
.
get
(
'grade'
,
None
)
try
:
grade_contents
=
lxml
.
html
.
fromstring
(
grade_contents
)
.
text_content
()
except
(
TypeError
,
XMLSyntaxError
,
ParserError
)
as
e
:
# Despite blowing up the xml parser, bad values here are fine
grade_contents
=
None
if
is_whitelisted
or
grade
[
'grade'
]
is
not
None
:
if
is_whitelisted
or
grade
_contents
is
not
None
:
# check to see whether the student is on the
# the embargoed country restricted list
...
...
@@ -222,8 +232,8 @@ class XQueueCertInterface(object):
'username'
:
student
.
username
,
'course_id'
:
course_id
,
'course_name'
:
course_name
,
'name'
:
profile
.
name
,
'grade'
:
grade
[
'grade'
]
,
'name'
:
profile
_
name
,
'grade'
:
grade
_contents
,
'template_pdf'
:
template_pdf
,
}
if
template_file
:
...
...
@@ -233,8 +243,8 @@ class XQueueCertInterface(object):
cert
.
save
()
self
.
_send_to_xqueue
(
contents
,
key
)
else
:
new
_status
=
status
.
notpassing
cert
.
status
=
new
_status
cert
_status
=
status
.
notpassing
cert
.
status
=
cert
_status
cert
.
save
()
return
new_status
...
...
lms/djangoapps/certificates/views.py
View file @
9cc30d09
"""URL handlers related to certificate handling by LMS"""
from
dogapi
import
dog_stats_api
import
json
import
logging
from
certificates.models
import
GeneratedCertificate
from
certificates.models
import
CertificateStatuses
as
status
from
django.views.decorators.csrf
import
csrf_exempt
from
django.contrib.auth.models
import
User
from
django.http
import
HttpResponse
import
json
from
dogapi
import
dog_stats_api
from
django.views.decorators.csrf
import
csrf_exempt
from
capa.xqueue_interface
import
XQUEUE_METRIC_NAME
from
certificates.models
import
certificate_status_for_student
,
CertificateStatuses
,
GeneratedCertificate
from
certificates.queue
import
XQueueCertInterface
from
xmodule.course_module
import
CourseDescriptor
from
xmodule.modulestore.django
import
modulestore
logger
=
logging
.
getLogger
(
__name__
)
@csrf_exempt
def
request_certificate
(
request
):
"""Request the on-demand creation of a certificate for some user, course.
A request doesn't imply a guarantee that such a creation will take place.
We intentionally use the same machinery as is used for doing certification
at the end of a course run, so that we can be sure users get graded and
then if and only if they pass, do they get a certificate issued.
"""
if
request
.
method
==
"POST"
:
if
request
.
user
.
is_authenticated
():
xqci
=
XQueueCertInterface
()
username
=
request
.
user
.
username
student
=
User
.
objects
.
get
(
username
=
username
)
course_id
=
request
.
POST
.
get
(
'course_id'
)
course
=
modulestore
()
.
get_instance
(
course_id
,
CourseDescriptor
.
id_to_location
(
course_id
),
depth
=
2
)
status
=
certificate_status_for_student
(
student
,
course_id
)[
'status'
]
if
status
in
[
CertificateStatuses
.
unavailable
,
CertificateStatuses
.
notpassing
,
CertificateStatuses
.
error
]:
logger
.
info
(
'Grading and certification requested for user {} in course {} via /request_certificate call'
.
format
(
username
,
course_id
))
status
=
xqci
.
add_cert
(
student
,
course_id
,
course
=
course
)
return
HttpResponse
(
json
.
dumps
({
'add_status'
:
status
}),
mimetype
=
'application/json'
)
return
HttpResponse
(
json
.
dumps
({
'add_status'
:
'ERRORANONYMOUSUSER'
}),
mimetype
=
'application/json'
)
@csrf_exempt
def
update_certificate
(
request
):
"""
Will update GeneratedCertificate for a new certificate or
...
...
@@ -21,6 +52,7 @@ def update_certificate(request):
This view should only ever be accessed by the xqueue server
"""
status
=
CertificateStatuses
if
request
.
method
==
"POST"
:
xqueue_body
=
json
.
loads
(
request
.
POST
.
get
(
'xqueue_body'
))
...
...
lms/templates/dashboard/_dashboard_certificate_information.html
View file @
9cc30d09
...
...
@@ -24,8 +24,11 @@ else:
%
>
<div
class=
"message message-status ${status_css_class} is-shown"
>
% if cert_status['status'] == 'processing':
% if cert_status['status'] == 'processing'
and not course.may_certify()
:
<p
class=
"message-copy"
>
${_("Final course details are being wrapped up at this time. Your final standing will be available shortly.")}
</p>
% elif course.may_certify() and cert_status['status'] == 'processing':
<!-- Certification is allowed but no cert requested, or cert unearned -->
<!-- <p class="message-copy">${_("Your final standing is unrequested or unavailable at this time.")}</p> -->
% elif cert_status['status'] in ('generating', 'ready', 'notpassing', 'restricted'):
<p
class=
"message-copy"
>
${_("Your final grade:")}
<span
class=
"grade-value"
>
${"{0:.0f}%".format(float(cert_status['grade'])*100)}
</span>
.
...
...
lms/templates/dashboard/_dashboard_course_listing.html
View file @
9cc30d09
...
...
@@ -67,7 +67,7 @@
</h3>
</hgroup>
% if course.
has_ended
() and cert_status and not enrollment.mode == 'audit':
% if course.
may_certify
() and cert_status and not enrollment.mode == 'audit':
<
%
include
file=
'_dashboard_certificate_information.html'
args=
'cert_status=cert_status,course=course, enrollment=enrollment'
/>
% endif
...
...
lms/urls.py
View file @
9cc30d09
...
...
@@ -12,6 +12,7 @@ if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'):
urlpatterns
=
(
''
,
# nopep8
# certificate view
url
(
r'^update_certificate$'
,
'certificates.views.update_certificate'
),
url
(
r'^request_certificate$'
,
'certificates.views.request_certificate'
),
url
(
r'^$'
,
'branding.views.index'
,
name
=
"root"
),
# Main marketing page, or redirect to courseware
url
(
r'^dashboard$'
,
'student.views.dashboard'
,
name
=
"dashboard"
),
url
(
r'^token$'
,
'student.views.token'
,
name
=
"token"
),
...
...
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