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
OpenEdx
edx-platform
Commits
5d799f51
Commit
5d799f51
authored
Sep 27, 2017
by
Bill DeRusha
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
EXPERIMENT: Physical Certificates
parent
a7888b78
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
333 additions
and
5 deletions
+333
-5
lms/djangoapps/certificates/signals.py
+66
-4
lms/djangoapps/certificates/tasks.py
+46
-0
lms/djangoapps/certificates/templates/certificates/edx_ace/passedcourse/email/body.html
+49
-0
lms/djangoapps/certificates/templates/certificates/edx_ace/passedcourse/email/body.txt
+17
-0
lms/djangoapps/certificates/templates/certificates/edx_ace/passedcourse/email/from_name.txt
+1
-0
lms/djangoapps/certificates/templates/certificates/edx_ace/passedcourse/email/head.html
+1
-0
lms/djangoapps/certificates/templates/certificates/edx_ace/passedcourse/email/subject.txt
+3
-0
lms/djangoapps/certificates/urls.py
+2
-0
lms/djangoapps/certificates/views/shipping_information.py
+66
-0
lms/djangoapps/instructor_task/tests/test_tasks_helper.py
+1
-1
lms/templates/certificates/shipping_information.html
+81
-0
No files found.
lms/djangoapps/certificates/signals.py
View file @
5d799f51
"""
"""
Signal handler for enabling/disabling self-generated certificates based on the course-pacing.
Signal handler for enabling/disabling self-generated certificates based on the course-pacing.
"""
"""
import
datetime
import
logging
import
logging
from
django.db.models.signals
import
post_save
from
django.db.models.signals
import
post_save
from
django.dispatch
import
receiver
from
django.dispatch
import
receiver
import
pytz
from
certificates.models
import
(
from
certificates.models
import
(
CertificateWhitelist
,
CertificateWhitelist
,
CertificateStatuses
,
GeneratedCertificate
GeneratedCertificate
)
)
from
certificates.tasks
import
generate_certificate
from
certificates.tasks
import
generate_certificate
,
send_passing_learner_message
from
certificates.views.shipping_information
import
PHYSICAL_CERTIFICATE_EXPERIMENT_ID
,
\
PHYSICAL_CERTIFICATE_EXPERIMENT_KEY
from
experiments.models
import
ExperimentData
,
ExperimentKeyValue
from
lms.djangoapps.grades.course_grade_factory
import
CourseGradeFactory
from
lms.djangoapps.grades.course_grade_factory
import
CourseGradeFactory
from
lms.djangoapps.verify_student.models
import
SoftwareSecurePhotoVerification
from
lms.djangoapps.verify_student.models
import
SoftwareSecurePhotoVerification
from
openedx.core.djangoapps.certificates.api
import
auto_certificate_generation_enabled
from
openedx.core.djangoapps.certificates.api
import
auto_certificate_generation_enabled
from
openedx.core.djangoapps.certificates.config
import
waffle
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
openedx.core.djangoapps.signals.signals
import
COURSE_GRADE_NOW_PASSED
,
LEARNER_NOW_VERIFIED
from
openedx.core.djangoapps.signals.signals
import
COURSE_GRADE_NOW_PASSED
,
LEARNER_NOW_VERIFIED
,
COURSE_CERT_AWARDED
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
...
@@ -102,3 +105,62 @@ def fire_ungenerated_certificate_task(user, course_key, expected_verification_st
...
@@ -102,3 +105,62 @@ def fire_ungenerated_certificate_task(user, course_key, expected_verification_st
kwargs
[
'expected_verification_status'
]
=
unicode
(
expected_verification_status
)
kwargs
[
'expected_verification_status'
]
=
unicode
(
expected_verification_status
)
generate_certificate
.
apply_async
(
countdown
=
CERTIFICATE_DELAY_SECONDS
,
kwargs
=
kwargs
)
generate_certificate
.
apply_async
(
countdown
=
CERTIFICATE_DELAY_SECONDS
,
kwargs
=
kwargs
)
return
True
return
True
@receiver
(
COURSE_CERT_AWARDED
)
def
handle_course_cert_awarded
(
sender
,
user
,
course_key
,
mode
,
status
,
**
kwargs
):
# pylint: disable=unused-argument
log
.
warn
(
'handle cert award'
)
try
:
exp_data
=
ExperimentData
.
objects
.
get
(
user
=
user
,
experiment_id
=
PHYSICAL_CERTIFICATE_EXPERIMENT_ID
,
key
=
'ship_cert_{0}'
.
format
(
str
(
course_key
)),
)
except
ExperimentData
.
DoesNotExist
:
return
if
exp_data
.
value
!=
'1'
:
return
send_passing_learner_message
.
apply_async
((
user
.
id
,
str
(
course_key
)),
retry
=
False
)
@receiver
(
post_save
,
sender
=
CourseEnrollment
,
dispatch_uid
=
'check_verified_upgrade'
)
def
create_schedule
(
sender
,
**
kwargs
):
enrollment
=
kwargs
[
'instance'
]
try
:
exp_data
=
ExperimentData
.
objects
.
get
(
user
=
enrollment
.
user
,
experiment_id
=
PHYSICAL_CERTIFICATE_EXPERIMENT_ID
,
key
=
'showed_interest_{0}'
.
format
(
str
(
enrollment
.
course_id
)),
)
except
ExperimentData
.
DoesNotExist
:
return
if
exp_data
.
value
!=
'1'
:
return
if
enrollment
.
mode
not
in
GeneratedCertificate
.
VERIFIED_CERTS_MODES
:
return
try
:
end_time_str
=
ExperimentKeyValue
.
objects
.
get
(
experiment_id
=
PHYSICAL_CERTIFICATE_EXPERIMENT_ID
,
key
=
'end_time'
)
except
ExperimentKeyValue
.
DoesNotExist
:
return
end_time
=
datetime
.
datetime
.
strptime
(
end_time_str
,
"
%
Y-
%
m-
%
dT
%
H:
%
M:
%
S.
%
fZ"
)
if
datetime
.
datetime
.
now
(
pytz
.
utc
)
>=
end_time
:
return
ship_exp_data
=
ExperimentData
.
objects
.
get_or_create
(
user
=
enrollment
.
user
,
experiment_id
=
PHYSICAL_CERTIFICATE_EXPERIMENT_ID
,
key
=
'ship_cert_{0}'
.
format
(
str
(
enrollment
.
course_id
)),
defaults
=
{
'value'
:
'1'
},
)
ship_exp_data
.
value
=
'1'
ship_exp_data
.
save
()
lms/djangoapps/certificates/tasks.py
View file @
5d799f51
from
celery
import
task
from
celery
import
task
from
logging
import
getLogger
from
logging
import
getLogger
import
logging
from
celery_utils.logged_task
import
LoggedTask
from
celery_utils.logged_task
import
LoggedTask
from
celery_utils.persist_on_failure
import
PersistOnFailureTask
from
celery_utils.persist_on_failure
import
PersistOnFailureTask
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
from
django.utils.http
import
urlquote
from
edx_ace
import
ace
from
edx_ace.message
import
MessageType
from
edx_ace.recipient
import
Recipient
from
lms.djangoapps.verify_student.models
import
SoftwareSecurePhotoVerification
from
lms.djangoapps.verify_student.models
import
SoftwareSecurePhotoVerification
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
from
.api
import
generate_user_certificates
from
.api
import
generate_user_certificates
from
certificates.views.shipping_information
import
shipping_information
logger
=
getLogger
(
__name__
)
logger
=
getLogger
(
__name__
)
...
@@ -43,3 +53,39 @@ def generate_certificate(self, **kwargs):
...
@@ -43,3 +53,39 @@ def generate_certificate(self, **kwargs):
if
expected_verification_status
!=
actual_verification_status
:
if
expected_verification_status
!=
actual_verification_status
:
raise
self
.
retry
(
kwargs
=
original_kwargs
)
raise
self
.
retry
(
kwargs
=
original_kwargs
)
generate_user_certificates
(
student
=
student
,
course_key
=
course_key
,
**
kwargs
)
generate_user_certificates
(
student
=
student
,
course_key
=
course_key
,
**
kwargs
)
ACE_ROUTING_KEY
=
getattr
(
settings
,
'ACE_ROUTING_KEY'
,
None
)
class
PassedCourse
(
MessageType
):
pass
@task
(
ignore_result
=
True
,
routing_key
=
ACE_ROUTING_KEY
)
def
send_passing_learner_message
(
user_id
,
course_key_str
):
try
:
user
=
User
.
objects
.
get
(
id
=
user_id
)
course_key
=
CourseKey
.
from_string
(
course_key_str
)
course
=
CourseOverview
.
get_from_id
(
course_key
)
def
absolute_url
(
relative_path
):
return
u'{}{}'
.
format
(
settings
.
LMS_ROOT_URL
,
urlquote
(
relative_path
))
context
=
{
'shipping_address_form_url'
:
absolute_url
(
reverse
(
'certificates:shipping_information'
)),
'course_name'
:
course
.
display_name
,
}
msg
=
PassedCourse
()
.
personalize
(
Recipient
(
user
.
username
,
user
.
email
,
),
course
.
language
,
context
,
)
ace
.
send
(
msg
)
except
:
logger
.
exception
(
''
)
lms/djangoapps/certificates/templates/certificates/edx_ace/passedcourse/email/body.html
0 → 100644
View file @
5d799f51
{% extends 'schedules/edx_ace/common/base_body.html' %}
{% load i18n %}
{% block preview_text %}
{% blocktrans trimmed %}
Congratulations on passing {{course_name}}!
{% endblocktrans %}
{% endblock %}
{% block content %}
<table
width=
"100%"
align=
"left"
border=
"0"
cellpadding=
"0"
cellspacing=
"0"
role=
"presentation"
>
<tr>
<td>
<h1>
{% trans "Congratulations!" %}
</h1>
<p>
{% blocktrans trimmed %}
You've passed
<strong>
{{ course_name }}
</strong>
! Your digital certificate is now available to
share on your LinkedIn profile. If you would like us to ship you your official printed certificate
you will need to tell us where to send it using the link below.
{% endblocktrans %}
</p>
<p>
{# email client support for style sheets is pretty spotty, so we have to inline all of these styles #}
<a
href=
"{{ shipping_address_form_url }}"
style=
"
color: #ffffff;
text-decoration: none;
border-radius: 4px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
background-color: #005686;
border-top: 10px solid #005686;
border-bottom: 10px solid #005686;
border-right: 16px solid #005686;
border-left: 16px solid #005686;
display: inline-block;
"
>
{# old email clients require the use of the font tag :( #}
<font
color=
"#ffffff"
><b>
{% trans "Ship an official certificate to me" %}
</b></font>
</a>
</p>
</td>
</tr>
</table>
{% endblock %}
lms/djangoapps/certificates/templates/certificates/edx_ace/passedcourse/email/body.txt
0 → 100644
View file @
5d799f51
{% load i18n %}
{% if course_ids|length > 1 %}
{% blocktrans trimmed %}
Remember when you enrolled in {{ course_name }}, and other courses on edX.org? We do, and we’re glad
to have you! Come see what everyone is learning.
{% endblocktrans %}
{% trans "Start learning now" %} <{{ dashboard_url }}>
{% else %}
{% blocktrans trimmed %}
Remember when you enrolled in {{ course_name }} on edX.org? We do, and we’re glad
to have you! Come see what everyone is learning.
{% endblocktrans %}
{% trans "Start learning now" %} <{{ course_url }}>
{% endif %}
lms/djangoapps/certificates/templates/certificates/edx_ace/passedcourse/email/from_name.txt
0 → 100644
View file @
5d799f51
{{ course_name }}
lms/djangoapps/certificates/templates/certificates/edx_ace/passedcourse/email/head.html
0 → 100644
View file @
5d799f51
{% extends 'schedules/edx_ace/common/base_head.html' %}
lms/djangoapps/certificates/templates/certificates/edx_ace/passedcourse/email/subject.txt
0 → 100644
View file @
5d799f51
{% load i18n %}
{% blocktrans %}You've passed {{course_name}}!{% endblocktrans %}
\ No newline at end of file
lms/djangoapps/certificates/urls.py
View file @
5d799f51
...
@@ -6,6 +6,7 @@ from django.conf import settings
...
@@ -6,6 +6,7 @@ from django.conf import settings
from
django.conf.urls
import
patterns
,
url
from
django.conf.urls
import
patterns
,
url
from
certificates
import
views
from
certificates
import
views
from
certificates.views.shipping_information
import
shipping_information
urlpatterns
=
patterns
(
urlpatterns
=
patterns
(
''
,
''
,
...
@@ -30,4 +31,5 @@ urlpatterns = patterns(
...
@@ -30,4 +31,5 @@ urlpatterns = patterns(
url
(
r'search'
,
views
.
search_certificates
,
name
=
"search"
),
url
(
r'search'
,
views
.
search_certificates
,
name
=
"search"
),
url
(
r'regenerate'
,
views
.
regenerate_certificate_for_user
,
name
=
"regenerate_certificate_for_user"
),
url
(
r'regenerate'
,
views
.
regenerate_certificate_for_user
,
name
=
"regenerate_certificate_for_user"
),
url
(
r'generate'
,
views
.
generate_certificate_for_user
,
name
=
"generate_certificate_for_user"
),
url
(
r'generate'
,
views
.
generate_certificate_for_user
,
name
=
"generate_certificate_for_user"
),
url
(
r'shipping_information'
,
shipping_information
,
name
=
"shipping_information"
),
)
)
lms/djangoapps/certificates/views/shipping_information.py
0 → 100644
View file @
5d799f51
# pylint: disable=bad-continuation
"""
Certificate Shipping Information view.
"""
import
json
import
logging
from
django.contrib.auth.decorators
import
login_required
from
django.core.urlresolvers
import
reverse
from
django.shortcuts
import
redirect
from
edxmako.shortcuts
import
render_to_response
from
experiments.models
import
ExperimentData
log
=
logging
.
getLogger
(
__name__
)
PHYSICAL_CERTIFICATE_EXPERIMENT_ID
=
100
PHYSICAL_CERTIFICATE_EXPERIMENT_KEY
=
"shipping_information"
@login_required
def
shipping_information
(
request
,
template_name
=
'certificates/shipping_information.html'
):
default_shipping_json
=
{
'first_name'
:
""
,
'last_name'
:
""
,
'address'
:
""
,
'city'
:
""
,
'state'
:
""
,
'zip_code'
:
""
}
if
request
.
method
==
'GET'
:
shipping_information
,
created
=
ExperimentData
.
objects
.
get_or_create
(
user
=
request
.
user
,
experiment_id
=
PHYSICAL_CERTIFICATE_EXPERIMENT_ID
,
key
=
PHYSICAL_CERTIFICATE_EXPERIMENT_KEY
,
defaults
=
{
'value'
:
json
.
dumps
(
default_shipping_json
)},
)
shipping_json
=
json
.
loads
(
shipping_information
.
value
)
if
request
.
method
==
'POST'
:
first_name
=
request
.
POST
.
get
(
'first_name'
)
last_name
=
request
.
POST
.
get
(
'last_name'
)
address
=
request
.
POST
.
get
(
'address'
)
city
=
request
.
POST
.
get
(
'city'
)
state
=
request
.
POST
.
get
(
'state'
)
zip_code
=
request
.
POST
.
get
(
'zip_code'
)
shipping_json
=
{
'first_name'
:
first_name
,
'last_name'
:
last_name
,
'address'
:
address
,
'city'
:
city
,
'state'
:
state
,
'zip_code'
:
zip_code
}
shipping_information
=
ExperimentData
.
objects
.
get
(
user
=
request
.
user
,
experiment_id
=
PHYSICAL_CERTIFICATE_EXPERIMENT_ID
,
key
=
PHYSICAL_CERTIFICATE_EXPERIMENT_KEY
)
shipping_information
.
value
=
json
.
dumps
(
shipping_json
)
shipping_information
.
save
()
return
render_to_response
(
template_name
,
{
'object'
:
shipping_json
})
lms/djangoapps/instructor_task/tests/test_tasks_helper.py
View file @
5d799f51
...
@@ -1989,7 +1989,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
...
@@ -1989,7 +1989,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase):
'failed'
:
3
,
'failed'
:
3
,
'skipped'
:
2
'skipped'
:
2
}
}
with
self
.
assertNumQueries
(
1
0
6
):
with
self
.
assertNumQueries
(
1
1
6
):
self
.
assertCertificatesGenerated
(
task_input
,
expected_results
)
self
.
assertCertificatesGenerated
(
task_input
,
expected_results
)
expected_results
=
{
expected_results
=
{
...
...
lms/templates/certificates/shipping_information.html
0 → 100644
View file @
5d799f51
<
%
page
expression_filter=
"h"
/>
<
%
inherit
file=
"../main.html"
/>
<
%
block
name=
"pagetitle"
>
Certificate Shipping Information
</
%
block>
<script
type=
"text/javascript"
>
function
editMode
(){
var
$editable
=
$
(
'.editable'
);
var
$readOnly
=
$
(
'.read-only'
);
$readOnly
.
addClass
(
'hidden'
);
$editable
.
removeClass
(
'hidden'
);
console
.
log
(
'called'
);
};
</script>
<style>
.read-only
{
line-height
:
25px
;
}
#shipping_form
{
margin
:
25px
;
}
label
{
display
:
block
;
float
:
left
;
width
:
100px
;
font-style
:
normal
;
font-weight
:
bold
;
cursor
:
default
;
}
.clearfix
{
margin-bottom
:
10px
;
}
</style>
<h1>
Course Certificate Shipping Information
</h1>
<div
id=
"shipping_form"
>
<form
method=
"POST"
>
<input
type=
"hidden"
id=
"csrf_token"
name=
"csrfmiddlewaretoken"
value=
"${csrf_token}"
>
<label
style=
""
>
First Name
</label>
<span
class=
"read-only"
>
${object['first_name']}
</span>
<input
class=
"editable hidden"
type=
"text"
name=
"first_name"
value=
"${object['first_name']}"
/>
<div
class=
"clearfix"
></div>
<label>
Last Name
</label>
<span
class=
"read-only"
>
${object['last_name']}
</span>
<input
class=
"editable hidden"
type=
"text"
name=
"last_name"
value=
"${object['last_name']}"
/>
<div
class=
"clearfix"
></div>
<label>
Address
</label>
<span
class=
"read-only"
>
${object['address']}
</span>
<input
class=
"editable hidden"
type=
"text"
name=
"address"
value=
"${object['address']}"
/>
<div
class=
"clearfix"
></div>
<label>
City
</label>
<span
class=
"read-only"
>
${object['city']}
</span>
<input
class=
"editable hidden"
type=
"text"
name=
"city"
value=
"${object['city']}"
/>
<div
class=
"clearfix"
></div>
<label>
State
</label>
<span
class=
"read-only"
>
${object['state']}
</span>
<input
class=
"editable hidden"
type=
"text"
name=
"state"
value=
"${object['state']}"
/>
<div
class=
"clearfix"
></div>
<label>
Zip Code
</label>
<span
class=
"read-only"
>
${object['zip_code']}
</span>
<input
class=
"editable hidden"
type=
"text"
name=
"zip_code"
value=
"${object['zip_code']}"
/>
<div
class=
"clearfix"
></div>
<br
/><br
/>
<button
type=
"button"
class=
"read-only"
id=
"edit-btn"
onclick=
"editMode(); return false;"
>
edit
</button>
<input
class=
"editable hidden"
type=
"submit"
name=
"submit"
value=
"submit"
/>
</form>
</div>
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