Commit 37aebaa7 by asadiqbal

SOL-1492

parent 7f6e8b88
......@@ -7,7 +7,6 @@ import json
import ddt
from django.conf import settings
from django.core.urlresolvers import reverse
from django.test import TestCase
from django.test.utils import override_settings
from opaque_keys.edx.keys import CourseKey
......@@ -22,7 +21,7 @@ FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
class CertificateSupportTestCase(TestCase):
class CertificateSupportTestCase(ModuleStoreTestCase):
"""
Base class for tests of the certificate support views.
"""
......@@ -36,6 +35,9 @@ class CertificateSupportTestCase(TestCase):
STUDENT_PASSWORD = "student"
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_STATUS = CertificateStatuses.downloadable
CERT_MODE = "verified"
......@@ -47,6 +49,11 @@ class CertificateSupportTestCase(TestCase):
Log in as the support team member.
"""
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
self.support = UserFactory(
......@@ -79,7 +86,7 @@ class CertificateSupportTestCase(TestCase):
@ddt.ddt
class CertificateSearchTests(ModuleStoreTestCase, CertificateSupportTestCase):
class CertificateSearchTests(CertificateSupportTestCase):
"""
Tests for the certificate search end-point used by the support team.
"""
......@@ -137,14 +144,20 @@ class CertificateSearchTests(ModuleStoreTestCase, CertificateSupportTestCase):
(CertificateSupportTestCase.STUDENT_EMAIL, True),
("bar", 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
def test_search(self, query, expect_result):
response = self._search(query)
self.assertEqual(response.status_code, 200)
results = json.loads(response.content)
self.assertEqual(len(results), 1 if expect_result else 0)
def test_search(self, user_filter, expect_result, course_filter=None):
response = self._search(user_filter, course_filter)
if expect_result:
self.assertEqual(response.status_code, 200)
results = json.loads(response.content)
self.assertEqual(len(results), 1)
else:
self.assertEqual(response.status_code, 400)
def test_results(self):
response = self._search(self.STUDENT_USERNAME)
......@@ -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. """
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)
@ddt.ddt
class CertificateRegenerateTests(ModuleStoreTestCase, CertificateSupportTestCase):
class CertificateRegenerateTests(CertificateSupportTestCase):
"""
Tests for the certificate regeneration end-point used by the support team.
"""
......@@ -308,3 +323,117 @@ class CertificateRegenerateTests(ModuleStoreTestCase, CertificateSupportTestCase
params["username"] = username
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)
......@@ -27,8 +27,9 @@ urlpatterns = patterns(
# End-points used by student support
# The views in the lms/djangoapps/support use these end-points
# 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'generate', views.generate_certificate_for_user, name="generate_certificate_for_user"),
)
......
......@@ -5,6 +5,7 @@ See lms/djangoapps/support for more details.
"""
import logging
import urllib
from functools import wraps
from django.http import (
......@@ -25,6 +26,8 @@ from student.models import User, CourseEnrollment
from courseware.access import has_access
from util.json_request import JsonResponse
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__)
......@@ -46,11 +49,15 @@ def require_certificate_permission(func):
@require_GET
@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:
request (HttpRequest): The request object.
......@@ -59,7 +66,8 @@ def search_by_user(request):
JsonResponse
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
Content-Type: application/json
......@@ -77,27 +85,46 @@ def search_by_user(request):
]
"""
query = request.GET.get("query")
if not query:
return JsonResponse([])
user_filter = request.GET.get("user", "")
if not user_filter:
msg = _("user is not given.")
return HttpResponseBadRequest(msg)
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:
return JsonResponse([])
return HttpResponseBadRequest(_("user '{user}' does not exist").format(user=user_filter))
certificates = api.get_certificates_for_user(user.username)
for cert in certificates:
cert["course_key"] = unicode(cert["course_key"])
cert["created"] = cert["created"].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)
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:
params (QueryDict): Request parameters.
......@@ -149,7 +176,7 @@ def regenerate_certificate_for_user(request):
"""
# 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:
return response
......@@ -186,3 +213,52 @@ def regenerate_certificate_for_user(request):
params["user"].id, params["course_key"]
)
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)
......@@ -6,15 +6,24 @@
model: CertModel,
initialize: function(options) {
this.userQuery = options.userQuery || '';
this.userFilter = options.userFilter || '';
this.courseFilter = options.courseFilter || '';
},
setUserQuery: function(userQuery) {
this.userQuery = userQuery;
setUserFilter: function(userFilter) {
this.userFilter = userFilter;
},
setCourseFilter: function(courseFilter) {
this.courseFilter = courseFilter;
},
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;
}
});
});
......
......@@ -9,7 +9,7 @@ define([
var view = null,
SEARCH_RESULTS = [
REGENERATE_SEARCH_RESULTS = [
{
'username': 'student',
'status': 'notpassing',
......@@ -18,7 +18,8 @@ define([
'type': 'honor',
'course_key': 'course-v1:edX+DemoX+Demo_Course',
'download_url': null,
'modified': '2015-08-06T19:47:07+00:00'
'modified': '2015-08-06T19:47:07+00:00',
'regenerate': true
},
{
'username': 'student',
......@@ -28,8 +29,23 @@ define([
'type': 'verified',
'course_key': 'edx/test/2015',
'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() {
......@@ -49,19 +65,29 @@ define([
return results;
},
searchFor = function(query, requests, response) {
searchFor = function(user_filter, course_filter, requests, response) {
// 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();
// 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);
},
regenerateCerts = function(username, courseKey) {
var sel = '.btn-cert-regenerate[data-course-key="' + courseKey + '"]';
$(sel).click();
},
generateCerts = function(username, courseKey) {
var sel = '.btn-cert-generate[data-course-key="' + courseKey + '"]';
$(sel).click();
};
beforeEach(function () {
......@@ -80,35 +106,49 @@ define([
var requests = AjaxHelpers.requests(this),
results = [];
searchFor('student@example.com', requests, SEARCH_RESULTS);
searchFor('student@example.com', '', requests, REGENERATE_SEARCH_RESULTS);
results = getSearchResults();
// Expect that the results displayed on the page match the results
// 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
expect(results[0][0]).toEqual(SEARCH_RESULTS[0].course_key);
expect(results[0][1]).toEqual(SEARCH_RESULTS[0].type);
expect(results[0][2]).toEqual(SEARCH_RESULTS[0].status);
expect(results[0][0]).toEqual(REGENERATE_SEARCH_RESULTS[0].course_key);
expect(results[0][1]).toEqual(REGENERATE_SEARCH_RESULTS[0].type);
expect(results[0][2]).toEqual(REGENERATE_SEARCH_RESULTS[0].status);
expect(results[0][3]).toContain('Not available');
expect(results[0][4]).toEqual(SEARCH_RESULTS[0].grade);
expect(results[0][5]).toEqual(SEARCH_RESULTS[0].modified);
expect(results[0][4]).toEqual(REGENERATE_SEARCH_RESULTS[0].grade);
expect(results[0][5]).toEqual(REGENERATE_SEARCH_RESULTS[0].modified);
// Check the second row of results
expect(results[1][0]).toEqual(SEARCH_RESULTS[1].course_key);
expect(results[1][1]).toEqual(SEARCH_RESULTS[1].type);
expect(results[1][2]).toEqual(SEARCH_RESULTS[1].status);
expect(results[1][3]).toContain(SEARCH_RESULTS[1].download_url);
expect(results[1][4]).toEqual(SEARCH_RESULTS[1].grade);
expect(results[1][5]).toEqual(SEARCH_RESULTS[1].modified);
expect(results[1][0]).toEqual(REGENERATE_SEARCH_RESULTS[1].course_key);
expect(results[1][1]).toEqual(REGENERATE_SEARCH_RESULTS[1].type);
expect(results[1][2]).toEqual(REGENERATE_SEARCH_RESULTS[1].status);
expect(results[1][3]).toContain(REGENERATE_SEARCH_RESULTS[1].download_url);
expect(results[1][4]).toEqual(REGENERATE_SEARCH_RESULTS[1].grade);
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() {
var requests = AjaxHelpers.requests(this),
results = [];
searchFor('student@example.com', requests, []);
searchFor('student@example.com', '', requests, []);
results = getSearchResults();
// Expect that no results are found
......@@ -118,30 +158,30 @@ define([
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),
results = [];
// Re-render the view, this time providing an initial query.
// Re-render the view, this time providing an initial filter.
view = new CertificatesView({
el: $('.certificates-content'),
userQuery: 'student@example.com'
userFilter: 'student@example.com'
}).render();
// Simulate a response from the server
AjaxHelpers.expectJsonRequest(requests, 'GET', '/certificates/search?query=student@example.com');
AjaxHelpers.respondWithJson(requests, SEARCH_RESULTS);
AjaxHelpers.expectJsonRequest(requests, 'GET', '/certificates/search?user=student@example.com');
AjaxHelpers.respondWithJson(requests, REGENERATE_SEARCH_RESULTS);
// Check the search results
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() {
var requests = AjaxHelpers.requests(this);
// 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
regenerateCerts('student', 'course-v1:edX+DemoX+Demo_Course');
......@@ -159,5 +199,29 @@ define([
// Respond with success
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, '');
});
});
});
......@@ -12,24 +12,27 @@
return Backbone.View.extend({
events: {
'submit .certificates-form': 'search',
'click .btn-cert-regenerate': 'regenerateCertificate'
'click .btn-cert-regenerate': 'regenerateCertificate',
'click .btn-cert-generate': 'generateCertificate'
},
initialize: function(options) {
_.bindAll(this, 'search', 'updateCertificates', 'regenerateCertificate', 'handleSearchError');
this.certificates = new CertCollection({});
this.initialQuery = options.userQuery || null;
this.initialFilter = options.userFilter || null;
this.courseFilter = options.courseFilter || null;
},
render: function() {
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:
// if the URL contains ?query="foo" then anyone who loads that URL
// will automatically search for "foo".
if (this.initialQuery) {
this.setUserQuery(this.initialQuery);
// if the URL contains ?user_filter="foo"&course_id="xyz" then anyone who loads that URL
// will automatically search for "foo" and course "xyz".
if (this.initialFilter) {
this.setUserFilter(this.initialFilter);
this.setCourseFilter(this.courseFilter);
this.triggerSearch();
}
......@@ -38,7 +41,7 @@
renderResults: function() {
var context = {
certificates: this.certificates,
certificates: this.certificates
};
this.setResults(_.template(resultsTpl, context));
......@@ -52,26 +55,57 @@
search: function(event) {
// Fetch the certificate collection for the given user
var query = this.getUserQuery(),
url = '/support/certificates?query=' + query;
var url = '/support/certificates?user=' + this.getUserFilter();
//course id is optional.
if (this.getCourseFilter()) {
url += '&course_id=' + this.getCourseFilter();
}
// Prevent form submission, since we're handling it ourselves.
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
// then the same search will be performed on page load.
window.history.pushState({}, window.document.title, url);
// Perform a search for the user's certificates.
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({
success: this.updateCertificates,
error: this.handleSearchError
});
},
error: this.handleGenerationsError
});
},
regenerateCertificate: function(event) {
var $button = $(event.target);
......@@ -84,16 +118,16 @@
type: 'POST',
data: {
username: $button.data('username'),
course_key: $button.data('course-key'),
course_key: $button.data('course-key')
},
context: this,
success: function() {
this.certificates.fetch({
success: this.updateCertificates,
error: this.handleSearchError,
error: this.handleSearchError
});
},
error: this.handleRegenerateError
error: this.handleGenerationsError
});
},
......@@ -102,12 +136,12 @@
this.enableButtons();
},
handleSearchError: function(jqxhr) {
this.renderError(jqxhr.responseText);
handleSearchError: function(jqxhr, response) {
this.renderError(response.responseText);
this.enableButtons();
},
handleRegenerateError: function(jqxhr) {
handleGenerationsError: function(jqxhr) {
// Since there are multiple "regenerate" buttons on the page,
// it's difficult to show the error message in the UI.
// Since this page is used only by internal staff, I think the
......@@ -120,12 +154,20 @@
$('.certificates-form').submit();
},
getUserQuery: function() {
return $('.certificates-form input[name="query"]').val();
getUserFilter: function() {
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();
},
setUserQuery: function(query) {
$('.certificates-form input[name="query"]').val(query);
setCourseFilter: function(course_id) {
$('.certificates-form > #certificate-course-filter-input').val(course_id);
},
setResults: function(html) {
......
<div class="certificates-search">
<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
id="certificate-query-input"
id="certificate-user-filter-input"
type="text"
name="query"
value=""
placeholder="<%- gettext("username or email") %>">
</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>
</form>
</div>
......
......@@ -29,12 +29,21 @@
<td><%- cert.get("grade") %></td>
<td><%- cert.get("modified") %></td>
<td>
<% if (cert.get("regenerate")) { %>
<button
class="btn-cert-regenerate btn-disable-on-submit"
data-username="<%- cert.get("username") %>"
data-course-key="<%- cert.get("course_key") %>"
><%- gettext("Regenerate") %></button>
<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>
</tr>
<% } %>
......
......@@ -9,7 +9,6 @@ import json
import re
import ddt
from django.test import TestCase
from django.core.urlresolvers import reverse
from pytz import UTC
......@@ -21,9 +20,10 @@ from student.roles import GlobalStaff, SupportStaffRole
from student.tests.factories import UserFactory, CourseEnrollmentFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
class SupportViewTestCase(TestCase):
class SupportViewTestCase(ModuleStoreTestCase):
"""
Base class for support view tests.
"""
......@@ -36,6 +36,7 @@ class SupportViewTestCase(TestCase):
"""Create a user and log in. """
super(SupportViewTestCase, self).setUp()
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)
self.assertTrue(success, msg="Could not log in")
......@@ -129,16 +130,23 @@ class SupportViewCertificatesTests(SupportViewTestCase):
super(SupportViewCertificatesTests, self).setUp()
SupportStaffRole().add_users(self.user)
def test_certificates_no_query(self):
# Check that an empty initial query is passed to the JavaScript client correctly.
def test_certificates_no_filter(self):
# Check that an empty initial filter is passed to the JavaScript client correctly.
response = self.client.get(reverse("support:certificates"))
self.assertContains(response, "userQuery: ''")
self.assertContains(response, "userFilter: ''")
def test_certificates_with_query(self):
# Check that an initial query is passed to the JavaScript client.
url = reverse("support:certificates") + "?query=student@example.com"
def test_certificates_with_user_filter(self):
# Check that an initial filter is passed to the JavaScript client.
url = reverse("support:certificates") + "?user=student@example.com"
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
......
......@@ -30,6 +30,7 @@ class CertificatesSupportView(View):
def get(self, request):
"""Render the certificates support view. """
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)
......@@ -3,11 +3,14 @@
// ===================================================================
.certificates-search, .enrollment-search {
margin: 40px 0;
input[name="query"] {
width: 476px;
}
margin: 40px 0;
input[name="query"] {
width: 350px;
}
.certificates-form {
max-width: 850px;
margin: 0 auto;
}
}
......@@ -31,6 +34,10 @@
font-size: 12px;
}
.btn-cert-generate {
font-size: 12px;
}
.enrollment-modal-wrapper.is-shown {
position: fixed;
top: 0;
......
......@@ -9,7 +9,8 @@ from django.utils.translation import ugettext as _
<%block name="js_extra">
<%static:require_module module_name="support/js/certificates_factory" class_name="CertificatesFactory">
new CertificatesFactory({
userQuery: '${ user_query }'
userFilter: '${ user_filter }',
courseFilter: '${course_filter}'
});
</%static:require_module>
</%block>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment