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
37aebaa7
Commit
37aebaa7
authored
Dec 16, 2015
by
asadiqbal
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
SOL-1492
parent
7f6e8b88
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
446 additions
and
92 deletions
+446
-92
lms/djangoapps/certificates/tests/test_support_views.py
+139
-10
lms/djangoapps/certificates/urls.py
+2
-1
lms/djangoapps/certificates/views/support.py
+88
-12
lms/djangoapps/support/static/support/js/collections/certificate.js
+13
-4
lms/djangoapps/support/static/support/js/spec/views/certificates_spec.js
+92
-28
lms/djangoapps/support/static/support/js/views/certificates.js
+64
-22
lms/djangoapps/support/static/support/templates/certificates.underscore
+9
-2
lms/djangoapps/support/static/support/templates/certificates_results.underscore
+9
-0
lms/djangoapps/support/tests/test_views.py
+17
-9
lms/djangoapps/support/views/certificate.py
+2
-1
lms/static/sass/views/_support.scss
+9
-2
lms/templates/support/certificates.html
+2
-1
No files found.
lms/djangoapps/certificates/tests/test_support_views.py
View file @
37aebaa7
...
@@ -7,7 +7,6 @@ import json
...
@@ -7,7 +7,6 @@ import json
import
ddt
import
ddt
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
django.test
import
TestCase
from
django.test.utils
import
override_settings
from
django.test.utils
import
override_settings
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
...
@@ -22,7 +21,7 @@ FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
...
@@ -22,7 +21,7 @@ FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
FEATURES_WITH_CERTS_ENABLED
[
'CERTIFICATES_HTML_VIEW'
]
=
True
FEATURES_WITH_CERTS_ENABLED
[
'CERTIFICATES_HTML_VIEW'
]
=
True
class
CertificateSupportTestCase
(
TestCase
):
class
CertificateSupportTestCase
(
ModuleStore
TestCase
):
"""
"""
Base class for tests of the certificate support views.
Base class for tests of the certificate support views.
"""
"""
...
@@ -36,6 +35,9 @@ class CertificateSupportTestCase(TestCase):
...
@@ -36,6 +35,9 @@ class CertificateSupportTestCase(TestCase):
STUDENT_PASSWORD
=
"student"
STUDENT_PASSWORD
=
"student"
CERT_COURSE_KEY
=
CourseKey
.
from_string
(
"edX/DemoX/Demo_Course"
)
CERT_COURSE_KEY
=
CourseKey
.
from_string
(
"edX/DemoX/Demo_Course"
)
COURSE_NOT_EXIST_KEY
=
CourseKey
.
from_string
(
"test/TestX/Test_Course_Not_Exist"
)
EXISTED_COURSE_KEY_1
=
CourseKey
.
from_string
(
"test1/Test1X/Test_Course_Exist_1"
)
EXISTED_COURSE_KEY_2
=
CourseKey
.
from_string
(
"test2/Test2X/Test_Course_Exist_2"
)
CERT_GRADE
=
0.89
CERT_GRADE
=
0.89
CERT_STATUS
=
CertificateStatuses
.
downloadable
CERT_STATUS
=
CertificateStatuses
.
downloadable
CERT_MODE
=
"verified"
CERT_MODE
=
"verified"
...
@@ -47,6 +49,11 @@ class CertificateSupportTestCase(TestCase):
...
@@ -47,6 +49,11 @@ class CertificateSupportTestCase(TestCase):
Log in as the support team member.
Log in as the support team member.
"""
"""
super
(
CertificateSupportTestCase
,
self
)
.
setUp
()
super
(
CertificateSupportTestCase
,
self
)
.
setUp
()
CourseFactory
(
org
=
CertificateSupportTestCase
.
EXISTED_COURSE_KEY_1
.
org
,
course
=
CertificateSupportTestCase
.
EXISTED_COURSE_KEY_1
.
course
,
run
=
CertificateSupportTestCase
.
EXISTED_COURSE_KEY_1
.
run
,
)
# Create the support staff user
# Create the support staff user
self
.
support
=
UserFactory
(
self
.
support
=
UserFactory
(
...
@@ -79,7 +86,7 @@ class CertificateSupportTestCase(TestCase):
...
@@ -79,7 +86,7 @@ class CertificateSupportTestCase(TestCase):
@ddt.ddt
@ddt.ddt
class
CertificateSearchTests
(
ModuleStoreTestCase
,
CertificateSupportTestCase
):
class
CertificateSearchTests
(
CertificateSupportTestCase
):
"""
"""
Tests for the certificate search end-point used by the support team.
Tests for the certificate search end-point used by the support team.
"""
"""
...
@@ -137,14 +144,20 @@ class CertificateSearchTests(ModuleStoreTestCase, CertificateSupportTestCase):
...
@@ -137,14 +144,20 @@ class CertificateSearchTests(ModuleStoreTestCase, CertificateSupportTestCase):
(
CertificateSupportTestCase
.
STUDENT_EMAIL
,
True
),
(
CertificateSupportTestCase
.
STUDENT_EMAIL
,
True
),
(
"bar"
,
False
),
(
"bar"
,
False
),
(
"bar@example.com"
,
False
),
(
"bar@example.com"
,
False
),
(
""
,
False
),
(
CertificateSupportTestCase
.
STUDENT_USERNAME
,
False
,
'invalid_key'
),
(
CertificateSupportTestCase
.
STUDENT_USERNAME
,
False
,
unicode
(
CertificateSupportTestCase
.
COURSE_NOT_EXIST_KEY
)),
(
CertificateSupportTestCase
.
STUDENT_USERNAME
,
True
,
unicode
(
CertificateSupportTestCase
.
EXISTED_COURSE_KEY_1
)),
)
)
@ddt.unpack
@ddt.unpack
def
test_search
(
self
,
query
,
expect_result
):
def
test_search
(
self
,
user_filter
,
expect_result
,
course_filter
=
None
):
response
=
self
.
_search
(
query
)
response
=
self
.
_search
(
user_filter
,
course_filter
)
if
expect_result
:
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
results
=
json
.
loads
(
response
.
content
)
results
=
json
.
loads
(
response
.
content
)
self
.
assertEqual
(
len
(
results
),
1
if
expect_result
else
0
)
self
.
assertEqual
(
len
(
results
),
1
)
else
:
self
.
assertEqual
(
response
.
status_code
,
400
)
def
test_results
(
self
):
def
test_results
(
self
):
response
=
self
.
_search
(
self
.
STUDENT_USERNAME
)
response
=
self
.
_search
(
self
.
STUDENT_USERNAME
)
...
@@ -184,14 +197,16 @@ class CertificateSearchTests(ModuleStoreTestCase, CertificateSupportTestCase):
...
@@ -184,14 +197,16 @@ class CertificateSearchTests(ModuleStoreTestCase, CertificateSupportTestCase):
)
)
)
)
def
_search
(
self
,
query
):
def
_search
(
self
,
user_filter
,
course_filter
=
None
):
"""Execute a search and return the response. """
"""Execute a search and return the response. """
url
=
reverse
(
"certificates:search"
)
+
"?query="
+
query
url
=
reverse
(
"certificates:search"
)
+
"?user="
+
user_filter
if
course_filter
:
url
+=
'&course_id='
+
course_filter
return
self
.
client
.
get
(
url
)
return
self
.
client
.
get
(
url
)
@ddt.ddt
@ddt.ddt
class
CertificateRegenerateTests
(
ModuleStoreTestCase
,
CertificateSupportTestCase
):
class
CertificateRegenerateTests
(
CertificateSupportTestCase
):
"""
"""
Tests for the certificate regeneration end-point used by the support team.
Tests for the certificate regeneration end-point used by the support team.
"""
"""
...
@@ -308,3 +323,117 @@ class CertificateRegenerateTests(ModuleStoreTestCase, CertificateSupportTestCase
...
@@ -308,3 +323,117 @@ class CertificateRegenerateTests(ModuleStoreTestCase, CertificateSupportTestCase
params
[
"username"
]
=
username
params
[
"username"
]
=
username
return
self
.
client
.
post
(
url
,
params
)
return
self
.
client
.
post
(
url
,
params
)
@ddt.ddt
class
CertificateGenerateTests
(
CertificateSupportTestCase
):
"""
Tests for the certificate generation end-point used by the support team.
"""
def
setUp
(
self
):
"""
Create a course and enroll the student in the course.
"""
super
(
CertificateGenerateTests
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
(
org
=
self
.
EXISTED_COURSE_KEY_2
.
org
,
course
=
self
.
EXISTED_COURSE_KEY_2
.
course
,
run
=
self
.
EXISTED_COURSE_KEY_2
.
run
)
CourseEnrollment
.
enroll
(
self
.
student
,
self
.
EXISTED_COURSE_KEY_2
,
self
.
CERT_MODE
)
@ddt.data
(
(
GlobalStaff
,
True
),
(
SupportStaffRole
,
True
),
(
None
,
False
),
)
@ddt.unpack
def
test_access_control
(
self
,
role
,
has_access
):
# Create a user and log in
user
=
UserFactory
(
username
=
"foo"
,
password
=
"foo"
)
success
=
self
.
client
.
login
(
username
=
"foo"
,
password
=
"foo"
)
self
.
assertTrue
(
success
,
msg
=
"Could not log in"
)
# Assign the user to the role
if
role
is
not
None
:
role
()
.
add_users
(
user
)
# Make a POST request
# Since we're not passing valid parameters, we'll get an error response
# but at least we'll know we have access
response
=
self
.
_generate
()
if
has_access
:
self
.
assertEqual
(
response
.
status_code
,
400
)
else
:
self
.
assertEqual
(
response
.
status_code
,
403
)
def
test_generate_certificate
(
self
):
response
=
self
.
_generate
(
course_key
=
self
.
course
.
id
,
# pylint: disable=no-member
username
=
self
.
STUDENT_USERNAME
,
)
self
.
assertEqual
(
response
.
status_code
,
200
)
def
test_generate_certificate_missing_params
(
self
):
# Missing username
response
=
self
.
_generate
(
course_key
=
self
.
EXISTED_COURSE_KEY_2
)
self
.
assertEqual
(
response
.
status_code
,
400
)
# Missing course key
response
=
self
.
_generate
(
username
=
self
.
STUDENT_USERNAME
)
self
.
assertEqual
(
response
.
status_code
,
400
)
def
test_generate_no_such_user
(
self
):
response
=
self
.
_generate
(
course_key
=
unicode
(
self
.
EXISTED_COURSE_KEY_2
),
username
=
"invalid_username"
,
)
self
.
assertEqual
(
response
.
status_code
,
400
)
def
test_generate_no_such_course
(
self
):
response
=
self
.
_generate
(
course_key
=
CourseKey
.
from_string
(
"edx/invalid/course"
),
username
=
self
.
STUDENT_USERNAME
)
self
.
assertEqual
(
response
.
status_code
,
400
)
def
test_generate_user_is_not_enrolled
(
self
):
# Unenroll the user
CourseEnrollment
.
unenroll
(
self
.
student
,
self
.
EXISTED_COURSE_KEY_2
)
# Can no longer regenerate certificates for the user
response
=
self
.
_generate
(
course_key
=
self
.
EXISTED_COURSE_KEY_2
,
username
=
self
.
STUDENT_USERNAME
)
self
.
assertEqual
(
response
.
status_code
,
400
)
def
test_generate_user_has_no_certificate
(
self
):
# Delete the user's certificate
GeneratedCertificate
.
objects
.
all
()
.
delete
()
# Should be able to generate
response
=
self
.
_generate
(
course_key
=
self
.
EXISTED_COURSE_KEY_2
,
username
=
self
.
STUDENT_USERNAME
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# A new certificate is created
num_certs
=
GeneratedCertificate
.
objects
.
filter
(
user
=
self
.
student
)
.
count
()
self
.
assertEqual
(
num_certs
,
1
)
def
_generate
(
self
,
course_key
=
None
,
username
=
None
):
"""Call the generation end-point and return the response. """
url
=
reverse
(
"certificates:generate_certificate_for_user"
)
params
=
{}
if
course_key
is
not
None
:
params
[
"course_key"
]
=
course_key
if
username
is
not
None
:
params
[
"username"
]
=
username
return
self
.
client
.
post
(
url
,
params
)
lms/djangoapps/certificates/urls.py
View file @
37aebaa7
...
@@ -27,8 +27,9 @@ urlpatterns = patterns(
...
@@ -27,8 +27,9 @@ urlpatterns = patterns(
# End-points used by student support
# End-points used by student support
# The views in the lms/djangoapps/support use these end-points
# The views in the lms/djangoapps/support use these end-points
# to retrieve certificate information and regenerate certificates.
# to retrieve certificate information and regenerate certificates.
url
(
r'search'
,
views
.
search_
by_user
,
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"
),
)
)
...
...
lms/djangoapps/certificates/views/support.py
View file @
37aebaa7
...
@@ -5,6 +5,7 @@ See lms/djangoapps/support for more details.
...
@@ -5,6 +5,7 @@ See lms/djangoapps/support for more details.
"""
"""
import
logging
import
logging
import
urllib
from
functools
import
wraps
from
functools
import
wraps
from
django.http
import
(
from
django.http
import
(
...
@@ -25,6 +26,8 @@ from student.models import User, CourseEnrollment
...
@@ -25,6 +26,8 @@ from student.models import User, CourseEnrollment
from
courseware.access
import
has_access
from
courseware.access
import
has_access
from
util.json_request
import
JsonResponse
from
util.json_request
import
JsonResponse
from
certificates
import
api
from
certificates
import
api
from
instructor_task.api
import
generate_certificates_for_students
from
openedx.core.djangoapps.content.course_overviews.models
import
CourseOverview
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -46,11 +49,15 @@ def require_certificate_permission(func):
...
@@ -46,11 +49,15 @@ def require_certificate_permission(func):
@require_GET
@require_GET
@require_certificate_permission
@require_certificate_permission
def
search_
by_user
(
request
):
def
search_
certificates
(
request
):
"""
"""
Search for certificates for a particular user.
Search for certificates for a particular user
OR along with the given course
.
Supports search by either username or email address.
Supports search by either username or email address along with course id.
First filter the records for the given username/email and then filter against the given course id (if given).
Show the 'Regenerate' button if a record found in 'generatedcertificate' model otherwise it will show the Generate
button.
Arguments:
Arguments:
request (HttpRequest): The request object.
request (HttpRequest): The request object.
...
@@ -59,7 +66,8 @@ def search_by_user(request):
...
@@ -59,7 +66,8 @@ def search_by_user(request):
JsonResponse
JsonResponse
Example Usage:
Example Usage:
GET /certificates/search?query=bob@example.com
GET /certificates/search?user=bob@example.com
GET /certificates/search?user=bob@example.com&course_id=xyz
Response: 200 OK
Response: 200 OK
Content-Type: application/json
Content-Type: application/json
...
@@ -77,27 +85,46 @@ def search_by_user(request):
...
@@ -77,27 +85,46 @@ def search_by_user(request):
]
]
"""
"""
query
=
request
.
GET
.
get
(
"query"
)
user_filter
=
request
.
GET
.
get
(
"user"
,
""
)
if
not
query
:
if
not
user_filter
:
return
JsonResponse
([])
msg
=
_
(
"user is not given."
)
return
HttpResponseBadRequest
(
msg
)
try
:
try
:
user
=
User
.
objects
.
get
(
Q
(
email
=
query
)
|
Q
(
username
=
query
))
user
=
User
.
objects
.
get
(
Q
(
email
=
user_filter
)
|
Q
(
username
=
user_filter
))
except
User
.
DoesNotExist
:
except
User
.
DoesNotExist
:
return
JsonResponse
([]
)
return
HttpResponseBadRequest
(
_
(
"user '{user}' does not exist"
)
.
format
(
user
=
user_filter
)
)
certificates
=
api
.
get_certificates_for_user
(
user
.
username
)
certificates
=
api
.
get_certificates_for_user
(
user
.
username
)
for
cert
in
certificates
:
for
cert
in
certificates
:
cert
[
"course_key"
]
=
unicode
(
cert
[
"course_key"
])
cert
[
"course_key"
]
=
unicode
(
cert
[
"course_key"
])
cert
[
"created"
]
=
cert
[
"created"
]
.
isoformat
()
cert
[
"created"
]
=
cert
[
"created"
]
.
isoformat
()
cert
[
"modified"
]
=
cert
[
"modified"
]
.
isoformat
()
cert
[
"modified"
]
=
cert
[
"modified"
]
.
isoformat
()
cert
[
"regenerate"
]
=
True
course_id
=
urllib
.
quote_plus
(
request
.
GET
.
get
(
"course_id"
,
""
),
safe
=
':/'
)
if
course_id
:
try
:
course_key
=
CourseKey
.
from_string
(
course_id
)
except
InvalidKeyError
:
return
HttpResponseBadRequest
(
_
(
"Course id '{course_id}' is not valid"
)
.
format
(
course_id
=
course_id
))
else
:
try
:
if
CourseOverview
.
get_from_id
(
course_key
):
certificates
=
[
certificate
for
certificate
in
certificates
if
certificate
[
'course_key'
]
==
course_id
]
if
not
certificates
:
return
JsonResponse
([{
'username'
:
user
.
username
,
'course_key'
:
course_id
,
'regenerate'
:
False
}])
except
CourseOverview
.
DoesNotExist
:
msg
=
_
(
"The course does not exist against the given key '{course_key}'"
)
.
format
(
course_key
=
course_key
)
return
HttpResponseBadRequest
(
msg
)
return
JsonResponse
(
certificates
)
return
JsonResponse
(
certificates
)
def
_validate_
regen_
post_params
(
params
):
def
_validate_post_params
(
params
):
"""
"""
Validate request POST parameters to the regenerate certificates end-point.
Validate request POST parameters to the
generate and
regenerate certificates end-point.
Arguments:
Arguments:
params (QueryDict): Request parameters.
params (QueryDict): Request parameters.
...
@@ -149,7 +176,7 @@ def regenerate_certificate_for_user(request):
...
@@ -149,7 +176,7 @@ def regenerate_certificate_for_user(request):
"""
"""
# Check the POST parameters, returning a 400 response if they're not valid.
# Check the POST parameters, returning a 400 response if they're not valid.
params
,
response
=
_validate_
regen_
post_params
(
request
.
POST
)
params
,
response
=
_validate_post_params
(
request
.
POST
)
if
response
is
not
None
:
if
response
is
not
None
:
return
response
return
response
...
@@ -186,3 +213,52 @@ def regenerate_certificate_for_user(request):
...
@@ -186,3 +213,52 @@ def regenerate_certificate_for_user(request):
params
[
"user"
]
.
id
,
params
[
"course_key"
]
params
[
"user"
]
.
id
,
params
[
"course_key"
]
)
)
return
HttpResponse
(
200
)
return
HttpResponse
(
200
)
@transaction.non_atomic_requests
@require_POST
@require_certificate_permission
def
generate_certificate_for_user
(
request
):
"""
Generate certificates for a user.
This is meant to be used by support staff through the UI in lms/djangoapps/support
Arguments:
request (HttpRequest): The request object
Returns:
HttpResponse
Example Usage:
POST /certificates/generate
* username: "bob"
* course_key: "edX/DemoX/Demo_Course"
Response: 200 OK
"""
# Check the POST parameters, returning a 400 response if they're not valid.
params
,
response
=
_validate_post_params
(
request
.
POST
)
if
response
is
not
None
:
return
response
try
:
# Check that the course exists
CourseOverview
.
get_from_id
(
params
[
"course_key"
])
except
CourseOverview
.
DoesNotExist
:
msg
=
_
(
"The course {course_key} does not exist"
)
.
format
(
course_key
=
params
[
"course_key"
])
return
HttpResponseBadRequest
(
msg
)
else
:
# Check that the user is enrolled in the course
if
not
CourseEnrollment
.
is_enrolled
(
params
[
"user"
],
params
[
"course_key"
]):
msg
=
_
(
"User {username} is not enrolled in the course {course_key}"
)
.
format
(
username
=
params
[
"user"
]
.
username
,
course_key
=
params
[
"course_key"
]
)
return
HttpResponseBadRequest
(
msg
)
# Attempt to generate certificate
generate_certificates_for_students
(
request
,
params
[
"course_key"
],
students
=
[
params
[
"user"
]])
return
HttpResponse
(
200
)
lms/djangoapps/support/static/support/js/collections/certificate.js
View file @
37aebaa7
...
@@ -6,15 +6,24 @@
...
@@ -6,15 +6,24 @@
model
:
CertModel
,
model
:
CertModel
,
initialize
:
function
(
options
)
{
initialize
:
function
(
options
)
{
this
.
userQuery
=
options
.
userQuery
||
''
;
this
.
userFilter
=
options
.
userFilter
||
''
;
this
.
courseFilter
=
options
.
courseFilter
||
''
;
},
},
setUserQuery
:
function
(
userQuery
)
{
setUserFilter
:
function
(
userFilter
)
{
this
.
userQuery
=
userQuery
;
this
.
userFilter
=
userFilter
;
},
setCourseFilter
:
function
(
courseFilter
)
{
this
.
courseFilter
=
courseFilter
;
},
},
url
:
function
()
{
url
:
function
()
{
return
'/certificates/search?query='
+
this
.
userQuery
;
var
url
=
'/certificates/search?user='
+
this
.
userFilter
;
if
(
this
.
courseFilter
)
{
url
+=
'&course_id='
+
this
.
courseFilter
;
}
return
url
;
}
}
});
});
});
});
...
...
lms/djangoapps/support/static/support/js/spec/views/certificates_spec.js
View file @
37aebaa7
...
@@ -9,7 +9,7 @@ define([
...
@@ -9,7 +9,7 @@ define([
var
view
=
null
,
var
view
=
null
,
SEARCH_RESULTS
=
[
REGENERATE_
SEARCH_RESULTS
=
[
{
{
'username'
:
'student'
,
'username'
:
'student'
,
'status'
:
'notpassing'
,
'status'
:
'notpassing'
,
...
@@ -18,7 +18,8 @@ define([
...
@@ -18,7 +18,8 @@ define([
'type'
:
'honor'
,
'type'
:
'honor'
,
'course_key'
:
'course-v1:edX+DemoX+Demo_Course'
,
'course_key'
:
'course-v1:edX+DemoX+Demo_Course'
,
'download_url'
:
null
,
'download_url'
:
null
,
'modified'
:
'2015-08-06T19:47:07+00:00'
'modified'
:
'2015-08-06T19:47:07+00:00'
,
'regenerate'
:
true
},
},
{
{
'username'
:
'student'
,
'username'
:
'student'
,
...
@@ -28,8 +29,23 @@ define([
...
@@ -28,8 +29,23 @@ define([
'type'
:
'verified'
,
'type'
:
'verified'
,
'course_key'
:
'edx/test/2015'
,
'course_key'
:
'edx/test/2015'
,
'download_url'
:
'http://www.example.com/certificate.pdf'
,
'download_url'
:
'http://www.example.com/certificate.pdf'
,
'modified'
:
'2015-08-06T19:47:05+00:00'
'modified'
:
'2015-08-06T19:47:05+00:00'
,
},
'regenerate'
:
true
}
],
GENERATE_SEARCH_RESULTS
=
[
{
'username'
:
'student'
,
'status'
:
''
,
'created'
:
''
,
'grade'
:
''
,
'type'
:
''
,
'course_key'
:
'edx/test1/2016'
,
'download_url'
:
null
,
'modified'
:
''
,
'regenerate'
:
false
}
],
],
getSearchResults
=
function
()
{
getSearchResults
=
function
()
{
...
@@ -49,19 +65,29 @@ define([
...
@@ -49,19 +65,29 @@ define([
return
results
;
return
results
;
},
},
searchFor
=
function
(
query
,
requests
,
response
)
{
searchFor
=
function
(
user_filter
,
course_filter
,
requests
,
response
)
{
// Enter the search term and submit
// Enter the search term and submit
view
.
setUserQuery
(
query
);
var
url
=
'/certificates/search?user='
+
user_filter
;
view
.
setUserFilter
(
user_filter
);
if
(
course_filter
)
{
view
.
setCourseFilter
(
course_filter
);
url
+=
'&course_id='
+
course_filter
;
}
view
.
triggerSearch
();
view
.
triggerSearch
();
// Simulate a response from the server
// Simulate a response from the server
AjaxHelpers
.
expectJsonRequest
(
requests
,
'GET'
,
'/certificates/search?query=student@example.com'
);
AjaxHelpers
.
expectJsonRequest
(
requests
,
'GET'
,
url
);
AjaxHelpers
.
respondWithJson
(
requests
,
response
);
AjaxHelpers
.
respondWithJson
(
requests
,
response
);
},
},
regenerateCerts
=
function
(
username
,
courseKey
)
{
regenerateCerts
=
function
(
username
,
courseKey
)
{
var
sel
=
'.btn-cert-regenerate[data-course-key="'
+
courseKey
+
'"]'
;
var
sel
=
'.btn-cert-regenerate[data-course-key="'
+
courseKey
+
'"]'
;
$
(
sel
).
click
();
$
(
sel
).
click
();
},
generateCerts
=
function
(
username
,
courseKey
)
{
var
sel
=
'.btn-cert-generate[data-course-key="'
+
courseKey
+
'"]'
;
$
(
sel
).
click
();
};
};
beforeEach
(
function
()
{
beforeEach
(
function
()
{
...
@@ -80,35 +106,49 @@ define([
...
@@ -80,35 +106,49 @@ define([
var
requests
=
AjaxHelpers
.
requests
(
this
),
var
requests
=
AjaxHelpers
.
requests
(
this
),
results
=
[];
results
=
[];
searchFor
(
'student@example.com'
,
requests
,
SEARCH_RESULTS
);
searchFor
(
'student@example.com'
,
''
,
requests
,
REGENERATE_
SEARCH_RESULTS
);
results
=
getSearchResults
();
results
=
getSearchResults
();
// Expect that the results displayed on the page match the results
// Expect that the results displayed on the page match the results
// returned by the server.
// returned by the server.
expect
(
results
.
length
).
toEqual
(
SEARCH_RESULTS
.
length
);
expect
(
results
.
length
).
toEqual
(
REGENERATE_
SEARCH_RESULTS
.
length
);
// Check the first row of results
// Check the first row of results
expect
(
results
[
0
][
0
]).
toEqual
(
SEARCH_RESULTS
[
0
].
course_key
);
expect
(
results
[
0
][
0
]).
toEqual
(
REGENERATE_
SEARCH_RESULTS
[
0
].
course_key
);
expect
(
results
[
0
][
1
]).
toEqual
(
SEARCH_RESULTS
[
0
].
type
);
expect
(
results
[
0
][
1
]).
toEqual
(
REGENERATE_
SEARCH_RESULTS
[
0
].
type
);
expect
(
results
[
0
][
2
]).
toEqual
(
SEARCH_RESULTS
[
0
].
status
);
expect
(
results
[
0
][
2
]).
toEqual
(
REGENERATE_
SEARCH_RESULTS
[
0
].
status
);
expect
(
results
[
0
][
3
]).
toContain
(
'Not available'
);
expect
(
results
[
0
][
3
]).
toContain
(
'Not available'
);
expect
(
results
[
0
][
4
]).
toEqual
(
SEARCH_RESULTS
[
0
].
grade
);
expect
(
results
[
0
][
4
]).
toEqual
(
REGENERATE_
SEARCH_RESULTS
[
0
].
grade
);
expect
(
results
[
0
][
5
]).
toEqual
(
SEARCH_RESULTS
[
0
].
modified
);
expect
(
results
[
0
][
5
]).
toEqual
(
REGENERATE_
SEARCH_RESULTS
[
0
].
modified
);
// Check the second row of results
// Check the second row of results
expect
(
results
[
1
][
0
]).
toEqual
(
SEARCH_RESULTS
[
1
].
course_key
);
expect
(
results
[
1
][
0
]).
toEqual
(
REGENERATE_SEARCH_RESULTS
[
1
].
course_key
);
expect
(
results
[
1
][
1
]).
toEqual
(
SEARCH_RESULTS
[
1
].
type
);
expect
(
results
[
1
][
1
]).
toEqual
(
REGENERATE_SEARCH_RESULTS
[
1
].
type
);
expect
(
results
[
1
][
2
]).
toEqual
(
SEARCH_RESULTS
[
1
].
status
);
expect
(
results
[
1
][
2
]).
toEqual
(
REGENERATE_SEARCH_RESULTS
[
1
].
status
);
expect
(
results
[
1
][
3
]).
toContain
(
SEARCH_RESULTS
[
1
].
download_url
);
expect
(
results
[
1
][
3
]).
toContain
(
REGENERATE_SEARCH_RESULTS
[
1
].
download_url
);
expect
(
results
[
1
][
4
]).
toEqual
(
SEARCH_RESULTS
[
1
].
grade
);
expect
(
results
[
1
][
4
]).
toEqual
(
REGENERATE_SEARCH_RESULTS
[
1
].
grade
);
expect
(
results
[
1
][
5
]).
toEqual
(
SEARCH_RESULTS
[
1
].
modified
);
expect
(
results
[
1
][
5
]).
toEqual
(
REGENERATE_SEARCH_RESULTS
[
1
].
modified
);
searchFor
(
'student@example.com'
,
'edx/test1/2016'
,
requests
,
GENERATE_SEARCH_RESULTS
);
results
=
getSearchResults
();
expect
(
results
.
length
).
toEqual
(
GENERATE_SEARCH_RESULTS
.
length
);
// Check the first row of results
expect
(
results
[
0
][
0
]).
toEqual
(
GENERATE_SEARCH_RESULTS
[
0
].
course_key
);
expect
(
results
[
0
][
1
]).
toEqual
(
GENERATE_SEARCH_RESULTS
[
0
].
type
);
expect
(
results
[
0
][
2
]).
toEqual
(
GENERATE_SEARCH_RESULTS
[
0
].
status
);
expect
(
results
[
0
][
3
]).
toContain
(
'Not available'
);
expect
(
results
[
0
][
4
]).
toEqual
(
GENERATE_SEARCH_RESULTS
[
0
].
grade
);
expect
(
results
[
0
][
5
]).
toEqual
(
GENERATE_SEARCH_RESULTS
[
0
].
modified
);
});
});
it
(
'searches for certificates and displays a message when there are no results'
,
function
()
{
it
(
'searches for certificates and displays a message when there are no results'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
),
var
requests
=
AjaxHelpers
.
requests
(
this
),
results
=
[];
results
=
[];
searchFor
(
'student@example.com'
,
requests
,
[]);
searchFor
(
'student@example.com'
,
''
,
requests
,
[]);
results
=
getSearchResults
();
results
=
getSearchResults
();
// Expect that no results are found
// Expect that no results are found
...
@@ -118,30 +158,30 @@ define([
...
@@ -118,30 +158,30 @@ define([
expect
(
$
(
'.certificates-results'
).
text
()).
toContain
(
'No results'
);
expect
(
$
(
'.certificates-results'
).
text
()).
toContain
(
'No results'
);
});
});
it
(
'automatically searches for an initial
query
if one is provided'
,
function
()
{
it
(
'automatically searches for an initial
filter
if one is provided'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
),
var
requests
=
AjaxHelpers
.
requests
(
this
),
results
=
[];
results
=
[];
// Re-render the view, this time providing an initial
query
.
// Re-render the view, this time providing an initial
filter
.
view
=
new
CertificatesView
({
view
=
new
CertificatesView
({
el
:
$
(
'.certificates-content'
),
el
:
$
(
'.certificates-content'
),
user
Query
:
'student@example.com'
user
Filter
:
'student@example.com'
}).
render
();
}).
render
();
// Simulate a response from the server
// Simulate a response from the server
AjaxHelpers
.
expectJsonRequest
(
requests
,
'GET'
,
'/certificates/search?
query
=student@example.com'
);
AjaxHelpers
.
expectJsonRequest
(
requests
,
'GET'
,
'/certificates/search?
user
=student@example.com'
);
AjaxHelpers
.
respondWithJson
(
requests
,
SEARCH_RESULTS
);
AjaxHelpers
.
respondWithJson
(
requests
,
REGENERATE_
SEARCH_RESULTS
);
// Check the search results
// Check the search results
results
=
getSearchResults
();
results
=
getSearchResults
();
expect
(
results
.
length
).
toEqual
(
SEARCH_RESULTS
.
length
);
expect
(
results
.
length
).
toEqual
(
REGENERATE_
SEARCH_RESULTS
.
length
);
});
});
it
(
'regenerates a certificate for a student'
,
function
()
{
it
(
'regenerates a certificate for a student'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
var
requests
=
AjaxHelpers
.
requests
(
this
);
// Trigger a search
// Trigger a search
searchFor
(
'student@example.com'
,
requests
,
SEARCH_RESULTS
);
searchFor
(
'student@example.com'
,
''
,
requests
,
REGENERATE_
SEARCH_RESULTS
);
// Click the button to regenerate certificates for a user
// Click the button to regenerate certificates for a user
regenerateCerts
(
'student'
,
'course-v1:edX+DemoX+Demo_Course'
);
regenerateCerts
(
'student'
,
'course-v1:edX+DemoX+Demo_Course'
);
...
@@ -159,5 +199,29 @@ define([
...
@@ -159,5 +199,29 @@ define([
// Respond with success
// Respond with success
AjaxHelpers
.
respondWithJson
(
requests
,
''
);
AjaxHelpers
.
respondWithJson
(
requests
,
''
);
});
});
it
(
'generate a certificate for a student'
,
function
()
{
var
requests
=
AjaxHelpers
.
requests
(
this
);
// Trigger a search
searchFor
(
'student@example.com'
,
'edx/test1/2016'
,
requests
,
GENERATE_SEARCH_RESULTS
);
// Click the button to generate certificates for a user
generateCerts
(
'student'
,
'edx/test1/2016'
);
// Expect a request to the server
AjaxHelpers
.
expectPostRequest
(
requests
,
'/certificates/generate'
,
$
.
param
({
username
:
'student'
,
course_key
:
'edx/test1/2016'
})
);
// Respond with success
AjaxHelpers
.
respondWithJson
(
requests
,
''
);
});
});
});
});
});
lms/djangoapps/support/static/support/js/views/certificates.js
View file @
37aebaa7
...
@@ -12,24 +12,27 @@
...
@@ -12,24 +12,27 @@
return
Backbone
.
View
.
extend
({
return
Backbone
.
View
.
extend
({
events
:
{
events
:
{
'submit .certificates-form'
:
'search'
,
'submit .certificates-form'
:
'search'
,
'click .btn-cert-regenerate'
:
'regenerateCertificate'
'click .btn-cert-regenerate'
:
'regenerateCertificate'
,
'click .btn-cert-generate'
:
'generateCertificate'
},
},
initialize
:
function
(
options
)
{
initialize
:
function
(
options
)
{
_
.
bindAll
(
this
,
'search'
,
'updateCertificates'
,
'regenerateCertificate'
,
'handleSearchError'
);
_
.
bindAll
(
this
,
'search'
,
'updateCertificates'
,
'regenerateCertificate'
,
'handleSearchError'
);
this
.
certificates
=
new
CertCollection
({});
this
.
certificates
=
new
CertCollection
({});
this
.
initialQuery
=
options
.
userQuery
||
null
;
this
.
initialFilter
=
options
.
userFilter
||
null
;
this
.
courseFilter
=
options
.
courseFilter
||
null
;
},
},
render
:
function
()
{
render
:
function
()
{
this
.
$el
.
html
(
_
.
template
(
certificatesTpl
));
this
.
$el
.
html
(
_
.
template
(
certificatesTpl
));
// If there is an initial
query
, then immediately trigger a search.
// If there is an initial
filter
, then immediately trigger a search.
// This is useful because it allows users to share search results:
// This is useful because it allows users to share search results:
// if the URL contains ?query="foo" then anyone who loads that URL
// if the URL contains ?user_filter="foo"&course_id="xyz" then anyone who loads that URL
// will automatically search for "foo".
// will automatically search for "foo" and course "xyz".
if
(
this
.
initialQuery
)
{
if
(
this
.
initialFilter
)
{
this
.
setUserQuery
(
this
.
initialQuery
);
this
.
setUserFilter
(
this
.
initialFilter
);
this
.
setCourseFilter
(
this
.
courseFilter
);
this
.
triggerSearch
();
this
.
triggerSearch
();
}
}
...
@@ -38,7 +41,7 @@
...
@@ -38,7 +41,7 @@
renderResults
:
function
()
{
renderResults
:
function
()
{
var
context
=
{
var
context
=
{
certificates
:
this
.
certificates
,
certificates
:
this
.
certificates
};
};
this
.
setResults
(
_
.
template
(
resultsTpl
,
context
));
this
.
setResults
(
_
.
template
(
resultsTpl
,
context
));
...
@@ -52,25 +55,56 @@
...
@@ -52,25 +55,56 @@
search
:
function
(
event
)
{
search
:
function
(
event
)
{
// Fetch the certificate collection for the given user
// Fetch the certificate collection for the given user
var
query
=
this
.
getUserQuery
(),
var
url
=
'/support/certificates?user='
+
this
.
getUserFilter
();
url
=
'/support/certificates?query='
+
query
;
//course id is optional.
if
(
this
.
getCourseFilter
())
{
url
+=
'&course_id='
+
this
.
getCourseFilter
();
}
// Prevent form submission, since we're handling it ourselves.
// Prevent form submission, since we're handling it ourselves.
event
.
preventDefault
();
event
.
preventDefault
();
// Push a URL into history with the search
query
as a GET parameter.
// Push a URL into history with the search
filter
as a GET parameter.
// That way, if the user reloads the page or sends someone the link
// That way, if the user reloads the page or sends someone the link
// then the same search will be performed on page load.
// then the same search will be performed on page load.
window
.
history
.
pushState
({},
window
.
document
.
title
,
url
);
window
.
history
.
pushState
({},
window
.
document
.
title
,
url
);
// Perform a search for the user's certificates.
// Perform a search for the user's certificates.
this
.
disableButtons
();
this
.
disableButtons
();
this
.
certificates
.
setUserQuery
(
query
);
this
.
certificates
.
setUserFilter
(
this
.
getUserFilter
());
this
.
certificates
.
setCourseFilter
(
this
.
getCourseFilter
());
this
.
certificates
.
fetch
({
success
:
this
.
updateCertificates
,
error
:
this
.
handleSearchError
});
},
generateCertificate
:
function
(
event
)
{
var
$button
=
$
(
event
.
target
);
// Generate certificates for a particular user and course.
// If this is successful, reload the certificate results so they show
// the updated status.
this
.
disableButtons
();
$
.
ajax
({
url
:
'/certificates/generate'
,
type
:
'POST'
,
data
:
{
username
:
$button
.
data
(
'username'
),
course_key
:
$button
.
data
(
'course-key'
)
},
context
:
this
,
success
:
function
()
{
this
.
certificates
.
fetch
({
this
.
certificates
.
fetch
({
success
:
this
.
updateCertificates
,
success
:
this
.
updateCertificates
,
error
:
this
.
handleSearchError
error
:
this
.
handleSearchError
});
});
},
},
error
:
this
.
handleGenerationsError
});
},
regenerateCertificate
:
function
(
event
)
{
regenerateCertificate
:
function
(
event
)
{
var
$button
=
$
(
event
.
target
);
var
$button
=
$
(
event
.
target
);
...
@@ -84,16 +118,16 @@
...
@@ -84,16 +118,16 @@
type
:
'POST'
,
type
:
'POST'
,
data
:
{
data
:
{
username
:
$button
.
data
(
'username'
),
username
:
$button
.
data
(
'username'
),
course_key
:
$button
.
data
(
'course-key'
)
,
course_key
:
$button
.
data
(
'course-key'
)
},
},
context
:
this
,
context
:
this
,
success
:
function
()
{
success
:
function
()
{
this
.
certificates
.
fetch
({
this
.
certificates
.
fetch
({
success
:
this
.
updateCertificates
,
success
:
this
.
updateCertificates
,
error
:
this
.
handleSearchError
,
error
:
this
.
handleSearchError
});
});
},
},
error
:
this
.
handle
Regenerate
Error
error
:
this
.
handle
Generations
Error
});
});
},
},
...
@@ -102,12 +136,12 @@
...
@@ -102,12 +136,12 @@
this
.
enableButtons
();
this
.
enableButtons
();
},
},
handleSearchError
:
function
(
jqxhr
)
{
handleSearchError
:
function
(
jqxhr
,
response
)
{
this
.
renderError
(
jqxhr
.
responseText
);
this
.
renderError
(
response
.
responseText
);
this
.
enableButtons
();
this
.
enableButtons
();
},
},
handle
Regenerate
Error
:
function
(
jqxhr
)
{
handle
Generations
Error
:
function
(
jqxhr
)
{
// Since there are multiple "regenerate" buttons on the page,
// Since there are multiple "regenerate" buttons on the page,
// it's difficult to show the error message in the UI.
// it's difficult to show the error message in the UI.
// Since this page is used only by internal staff, I think the
// Since this page is used only by internal staff, I think the
...
@@ -120,12 +154,20 @@
...
@@ -120,12 +154,20 @@
$
(
'.certificates-form'
).
submit
();
$
(
'.certificates-form'
).
submit
();
},
},
getUserQuery
:
function
()
{
getUserFilter
:
function
()
{
return
$
(
'.certificates-form input[name="query"]'
).
val
();
return
$
(
'.certificates-form > #certificate-user-filter-input'
).
val
();
},
setUserFilter
:
function
(
filter
)
{
$
(
'.certificates-form > #certificate-user-filter-input'
).
val
(
filter
);
},
getCourseFilter
:
function
()
{
return
$
(
'.certificates-form > #certificate-course-filter-input'
).
val
();
},
},
set
UserQuery
:
function
(
query
)
{
set
CourseFilter
:
function
(
course_id
)
{
$
(
'.certificates-form
input[name="query"]'
).
val
(
query
);
$
(
'.certificates-form
> #certificate-course-filter-input'
).
val
(
course_id
);
},
},
setResults
:
function
(
html
)
{
setResults
:
function
(
html
)
{
...
...
lms/djangoapps/support/static/support/templates/certificates.underscore
View file @
37aebaa7
<div class="certificates-search">
<div class="certificates-search">
<form class="certificates-form">
<form class="certificates-form">
<label class="sr" for="certificate-
query
-input"><%- gettext("Search") %></label>
<label class="sr" for="certificate-
user-filter
-input"><%- gettext("Search") %></label>
<input
<input
id="certificate-
query
-input"
id="certificate-
user-filter
-input"
type="text"
type="text"
name="query"
name="query"
value=""
value=""
placeholder="<%- gettext("username or email") %>">
placeholder="<%- gettext("username or email") %>">
</input>
</input>
<input
id="certificate-course-filter-input"
type="text"
name="query"
value=""
placeholder="<%- gettext("course id") %>">
</input>
<input type="submit" value="<%- gettext("Search") %>" class="btn-disable-on-submit"></input>
<input type="submit" value="<%- gettext("Search") %>" class="btn-disable-on-submit"></input>
</form>
</form>
</div>
</div>
...
...
lms/djangoapps/support/static/support/templates/certificates_results.underscore
View file @
37aebaa7
...
@@ -29,12 +29,21 @@
...
@@ -29,12 +29,21 @@
<td><%- cert.get("grade") %></td>
<td><%- cert.get("grade") %></td>
<td><%- cert.get("modified") %></td>
<td><%- cert.get("modified") %></td>
<td>
<td>
<% if (cert.get("regenerate")) { %>
<button
<button
class="btn-cert-regenerate btn-disable-on-submit"
class="btn-cert-regenerate btn-disable-on-submit"
data-username="<%- cert.get("username") %>"
data-username="<%- cert.get("username") %>"
data-course-key="<%- cert.get("course_key") %>"
data-course-key="<%- cert.get("course_key") %>"
><%- gettext("Regenerate") %></button>
><%- gettext("Regenerate") %></button>
<span class="sr"><%- gettext("Regenerate the user's certificate") %></span>
<span class="sr"><%- gettext("Regenerate the user's certificate") %></span>
<% } else { %>
<button
class="btn-cert-generate btn-disable-on-submit"
data-username="<%- cert.get("username") %>"
data-course-key="<%- cert.get("course_key") %>"
><%- gettext("Generate") %></button>
<span class="sr"><%- gettext("Generate the user's certificate") %></span>
<% } %>
</td>
</td>
</tr>
</tr>
<% } %>
<% } %>
...
...
lms/djangoapps/support/tests/test_views.py
View file @
37aebaa7
...
@@ -9,7 +9,6 @@ import json
...
@@ -9,7 +9,6 @@ import json
import
re
import
re
import
ddt
import
ddt
from
django.test
import
TestCase
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
pytz
import
UTC
from
pytz
import
UTC
...
@@ -21,9 +20,10 @@ from student.roles import GlobalStaff, SupportStaffRole
...
@@ -21,9 +20,10 @@ from student.roles import GlobalStaff, SupportStaffRole
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
class
SupportViewTestCase
(
TestCase
):
class
SupportViewTestCase
(
ModuleStore
TestCase
):
"""
"""
Base class for support view tests.
Base class for support view tests.
"""
"""
...
@@ -36,6 +36,7 @@ class SupportViewTestCase(TestCase):
...
@@ -36,6 +36,7 @@ class SupportViewTestCase(TestCase):
"""Create a user and log in. """
"""Create a user and log in. """
super
(
SupportViewTestCase
,
self
)
.
setUp
()
super
(
SupportViewTestCase
,
self
)
.
setUp
()
self
.
user
=
UserFactory
(
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
password
=
self
.
PASSWORD
)
self
.
user
=
UserFactory
(
username
=
self
.
USERNAME
,
email
=
self
.
EMAIL
,
password
=
self
.
PASSWORD
)
self
.
course
=
CourseFactory
.
create
()
success
=
self
.
client
.
login
(
username
=
self
.
USERNAME
,
password
=
self
.
PASSWORD
)
success
=
self
.
client
.
login
(
username
=
self
.
USERNAME
,
password
=
self
.
PASSWORD
)
self
.
assertTrue
(
success
,
msg
=
"Could not log in"
)
self
.
assertTrue
(
success
,
msg
=
"Could not log in"
)
...
@@ -129,16 +130,23 @@ class SupportViewCertificatesTests(SupportViewTestCase):
...
@@ -129,16 +130,23 @@ class SupportViewCertificatesTests(SupportViewTestCase):
super
(
SupportViewCertificatesTests
,
self
)
.
setUp
()
super
(
SupportViewCertificatesTests
,
self
)
.
setUp
()
SupportStaffRole
()
.
add_users
(
self
.
user
)
SupportStaffRole
()
.
add_users
(
self
.
user
)
def
test_certificates_no_
query
(
self
):
def
test_certificates_no_
filter
(
self
):
# Check that an empty initial
query
is passed to the JavaScript client correctly.
# Check that an empty initial
filter
is passed to the JavaScript client correctly.
response
=
self
.
client
.
get
(
reverse
(
"support:certificates"
))
response
=
self
.
client
.
get
(
reverse
(
"support:certificates"
))
self
.
assertContains
(
response
,
"user
Query
: ''"
)
self
.
assertContains
(
response
,
"user
Filter
: ''"
)
def
test_certificates_with_
query
(
self
):
def
test_certificates_with_
user_filter
(
self
):
# Check that an initial
query
is passed to the JavaScript client.
# Check that an initial
filter
is passed to the JavaScript client.
url
=
reverse
(
"support:certificates"
)
+
"?
query
=student@example.com"
url
=
reverse
(
"support:certificates"
)
+
"?
user
=student@example.com"
response
=
self
.
client
.
get
(
url
)
response
=
self
.
client
.
get
(
url
)
self
.
assertContains
(
response
,
"userQuery: 'student@example.com'"
)
self
.
assertContains
(
response
,
"userFilter: 'student@example.com'"
)
def
test_certificates_along_with_course_filter
(
self
):
# Check that an initial filter is passed to the JavaScript client.
url
=
reverse
(
"support:certificates"
)
+
"?user=student@example.com&course_id="
+
unicode
(
self
.
course
.
id
)
response
=
self
.
client
.
get
(
url
)
self
.
assertContains
(
response
,
"userFilter: 'student@example.com'"
)
self
.
assertContains
(
response
,
"courseFilter: '"
+
unicode
(
self
.
course
.
id
)
+
"'"
)
@ddt.ddt
@ddt.ddt
...
...
lms/djangoapps/support/views/certificate.py
View file @
37aebaa7
...
@@ -30,6 +30,7 @@ class CertificatesSupportView(View):
...
@@ -30,6 +30,7 @@ class CertificatesSupportView(View):
def
get
(
self
,
request
):
def
get
(
self
,
request
):
"""Render the certificates support view. """
"""Render the certificates support view. """
context
=
{
context
=
{
"user_query"
:
request
.
GET
.
get
(
"query"
,
""
)
"user_filter"
:
request
.
GET
.
get
(
"user"
,
""
),
"course_filter"
:
request
.
GET
.
get
(
"course_id"
,
""
)
}
}
return
render_to_response
(
"support/certificates.html"
,
context
)
return
render_to_response
(
"support/certificates.html"
,
context
)
lms/static/sass/views/_support.scss
View file @
37aebaa7
...
@@ -4,9 +4,12 @@
...
@@ -4,9 +4,12 @@
.certificates-search
,
.enrollment-search
{
.certificates-search
,
.enrollment-search
{
margin
:
40px
0
;
margin
:
40px
0
;
input
[
name
=
"query"
]
{
input
[
name
=
"query"
]
{
width
:
476px
;
width
:
350px
;
}
.certificates-form
{
max-width
:
850px
;
margin
:
0
auto
;
}
}
}
}
...
@@ -31,6 +34,10 @@
...
@@ -31,6 +34,10 @@
font-size
:
12px
;
font-size
:
12px
;
}
}
.btn-cert-generate
{
font-size
:
12px
;
}
.enrollment-modal-wrapper.is-shown
{
.enrollment-modal-wrapper.is-shown
{
position
:
fixed
;
position
:
fixed
;
top
:
0
;
top
:
0
;
...
...
lms/templates/support/certificates.html
View file @
37aebaa7
...
@@ -9,7 +9,8 @@ from django.utils.translation import ugettext as _
...
@@ -9,7 +9,8 @@ from django.utils.translation import ugettext as _
<
%
block
name=
"js_extra"
>
<
%
block
name=
"js_extra"
>
<
%
static:require_module
module_name=
"support/js/certificates_factory"
class_name=
"CertificatesFactory"
>
<
%
static:require_module
module_name=
"support/js/certificates_factory"
class_name=
"CertificatesFactory"
>
new CertificatesFactory({
new CertificatesFactory({
userQuery: '${ user_query }'
userFilter: '${ user_filter }',
courseFilter: '${course_filter}'
});
});
</
%
static:require
_module
>
</
%
static:require
_module
>
</
%
block>
</
%
block>
...
...
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