Commit 090cefcf by Giovanni Di Milia Committed by Jim Abramson

Added REST API for certificates in LMS (#12055)

parent 0d27b5ed
......@@ -8,28 +8,28 @@ import logging
from django.conf import settings
from django.core.urlresolvers import reverse
from eventtracking import tracker
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from branding import api as branding_api
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from xmodule.modulestore.django import modulestore
from xmodule_django.models import CourseKeyField
from util.organizations_helpers import get_course_organizations
from certificates.models import (
CertificateStatuses,
certificate_status_for_student,
CertificateGenerationCourseSetting,
CertificateGenerationConfiguration,
ExampleCertificateSet,
GeneratedCertificate,
CertificateGenerationCourseSetting,
CertificateStatuses,
CertificateTemplate,
CertificateTemplateAsset,
ExampleCertificateSet,
GeneratedCertificate,
certificate_status_for_student,
)
from certificates.queue import XQueueCertInterface
from branding import api as branding_api
log = logging.getLogger("edx.certificate")
......@@ -43,6 +43,36 @@ def is_passing_status(cert_status):
return CertificateStatuses.is_passing_status(cert_status)
def format_certificate_for_user(username, cert):
"""
Helper function to serialize an user certificate.
Arguments:
username (unicode): The identifier of the user.
cert (GeneratedCertificate): a user certificate
Returns: dict
"""
return {
"username": username,
"course_key": cert.course_id,
"type": cert.mode,
"status": cert.status,
"grade": cert.grade,
"created": cert.created_date,
"modified": cert.modified_date,
# NOTE: the download URL is not currently being set for webview certificates.
# In the future, we can update this to construct a URL to the webview certificate
# for courses that have this feature enabled.
"download_url": (
cert.download_url or get_certificate_url(cert.user.id, cert.course_id)
if cert.status == CertificateStatuses.downloadable
else None
),
}
def get_certificates_for_user(username):
"""
Retrieve certificate information for a particular user.
......@@ -57,7 +87,7 @@ def get_certificates_for_user(username):
[
{
"username": "bob",
"course_key": "edX/DemoX/Demo_Course",
"course_key": CourseLocator('edX', 'DemoX', 'Demo_Course', None, None),
"type": "verified",
"status": "downloadable",
"download_url": "http://www.example.com/cert.pdf",
......@@ -69,28 +99,30 @@ def get_certificates_for_user(username):
"""
return [
{
"username": username,
"course_key": cert.course_id,
"type": cert.mode,
"status": cert.status,
"grade": cert.grade,
"created": cert.created_date,
"modified": cert.modified_date,
# NOTE: the download URL is not currently being set for webview certificates.
# In the future, we can update this to construct a URL to the webview certificate
# for courses that have this feature enabled.
"download_url": (
cert.download_url or get_certificate_url(cert.user.id, cert.course_id)
if cert.status == CertificateStatuses.downloadable
else None
),
}
format_certificate_for_user(username, cert)
for cert in GeneratedCertificate.eligible_certificates.filter(user__username=username).order_by("course_id")
]
def get_certificate_for_user(username, course_key):
"""
Retrieve certificate information for a particular user for a specific course.
Arguments:
username (unicode): The identifier of the user.
course_key (CourseKey): A Course Key.
Returns: dict
"""
try:
cert = GeneratedCertificate.eligible_certificates.get(
user__username=username,
course_id=course_key
)
except GeneratedCertificate.DoesNotExist:
return None
return format_certificate_for_user(username, cert)
def generate_user_certificates(student, course_key, course=None, insecure=False, generation_mode='batch',
forced_grade=None):
"""
......
""" Certificates API URLs. """
from django.conf.urls import (
include,
patterns,
url,
)
urlpatterns = patterns(
'',
url(r'^v0/', include('lms.djangoapps.certificates.apis.v0.urls', namespace='v0')),
)
"""
Tests for the Certificate REST APIs.
"""
from django.core.urlresolvers import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from certificates.models import CertificateStatuses
from certificates.tests.factories import GeneratedCertificateFactory
from course_modes.models import CourseMode
from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
class CertificatesRestApiTest(SharedModuleStoreTestCase, APITestCase):
"""
Test for the Certificates REST APIs
"""
@classmethod
def setUpClass(cls):
super(CertificatesRestApiTest, cls).setUpClass()
cls.course = CourseFactory.create(
org='edx',
number='verified',
display_name='Verified Course'
)
def setUp(self):
super(CertificatesRestApiTest, self).setUp()
self.student = UserFactory.create(password='test')
self.student_no_cert = UserFactory.create(password='test')
self.staff_user = UserFactory.create(password='test', is_staff=True)
GeneratedCertificateFactory.create(
user=self.student,
course_id=self.course.id,
status=CertificateStatuses.downloadable,
mode='verified',
download_url='www.google.com',
grade="0.88"
)
self.namespaced_url = 'certificates_api:v0:certificates:detail'
def get_url(self, username):
"""
Helper function to create the url for certificates
"""
return reverse(
self.namespaced_url,
kwargs={
'course_id': self.course.id,
'username': username
}
)
def test_permissions(self):
"""
Test that only the owner of the certificate can access the url
"""
# anonymous user
resp = self.client.get(self.get_url(self.student.username))
self.assertEqual(resp.status_code, status.HTTP_401_UNAUTHORIZED)
# another student
self.client.login(username=self.student_no_cert.username, password='test')
resp = self.client.get(self.get_url(self.student.username))
# gets 404 instead of 403 for security reasons
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(resp.data, {u'detail': u'Not found.'}) # pylint: disable=no-member
self.client.logout()
# same student of the certificate
self.client.login(username=self.student.username, password='test')
resp = self.client.get(self.get_url(self.student.username))
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.client.logout()
# staff user
self.client.login(username=self.staff_user.username, password='test')
resp = self.client.get(self.get_url(self.student.username))
self.assertEqual(resp.status_code, status.HTTP_200_OK)
def test_no_certificate_for_user(self):
"""
Test for case with no certificate available
"""
self.client.login(username=self.student_no_cert.username, password='test')
resp = self.client.get(self.get_url(self.student_no_cert.username))
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
self.assertIn('error_code', resp.data) # pylint: disable=no-member
self.assertEqual(
resp.data['error_code'], # pylint: disable=no-member
'no_certificate_for_user'
)
def test_certificate_for_user(self):
"""
Tests case user that pulls her own certificate
"""
self.client.login(username=self.student.username, password='test')
resp = self.client.get(self.get_url(self.student.username))
self.assertEqual(resp.status_code, status.HTTP_200_OK)
self.assertEqual(
resp.data, # pylint: disable=no-member
{
'username': self.student.username,
'status': CertificateStatuses.downloadable,
'grade': '0.88',
'download_url': 'www.google.com',
'certificate_type': CourseMode.VERIFIED,
'course_id': unicode(self.course.id),
}
)
""" Certificates API v0 URLs. """
from django.conf import settings
from django.conf.urls import (
include,
patterns,
url,
)
from lms.djangoapps.certificates.apis.v0 import views
CERTIFICATES_URLS = patterns(
'',
url(
r'^{username}/courses/{course_id}/$'.format(
username=settings.USERNAME_PATTERN,
course_id=settings.COURSE_ID_PATTERN
),
views.CertificatesDetailView.as_view(), name='detail'
),
)
urlpatterns = patterns(
'',
url(r'^certificates/', include(CERTIFICATES_URLS, namespace='certificates')),
)
""" API v0 views. """
import logging
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from rest_framework.authentication import SessionAuthentication
from rest_framework.generics import GenericAPIView
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework_oauth.authentication import OAuth2Authentication
from lms.djangoapps.certificates.api import get_certificate_for_user
from openedx.core.lib.api import permissions
log = logging.getLogger(__name__)
class CertificatesDetailView(GenericAPIView):
"""
**Use Case**
* Get the details of a certificate for a specific user in a course.
**Example Request**
GET /api/certificates/v0/certificates/{username}/{course_id}
**GET Parameters**
A GET request must include the following parameters.
* username: A string representation of an user's username.
* course_id: A string representation of a Course ID.
**GET Response Values**
If the request for information about the Certificate is successful, an HTTP 200 "OK" response
is returned.
The HTTP 200 response has the following values.
* username: A string representation of an user's username passed in the request.
* course_id: A string representation of a Course ID.
* certificate_type: A string representation of the certificate type.
Can be honor|verified|professional
* status: A string representation of the certificate status.
* download_url: A string representation of the certificate url.
* grade: A string representation of a float for the user's course grade.
**Example GET Response**
{
"username": "bob",
"course_id": "edX/DemoX/Demo_Course",
"certificate_type": "verified",
"status": "downloadable",
"download_url": "http://www.example.com/cert.pdf",
"grade": "0.98"
}
"""
authentication_classes = (OAuth2Authentication, SessionAuthentication,)
permission_classes = (IsAuthenticated, permissions.IsUserInUrlOrStaff)
def get(self, request, username, course_id):
"""
Gets a certificate information.
Args:
request (Request): Django request object.
username (string): URI element specifying the user's username.
course_id (string): URI element specifying the course location.
Return:
A JSON serialized representation of the certificate.
"""
try:
course_key = CourseKey.from_string(course_id)
except InvalidKeyError:
log.warning('Course ID string "%s" is not valid', course_id)
return Response(
status=404,
data={'error_code': 'course_id_not_valid'}
)
user_cert = get_certificate_for_user(username=username, course_key=course_key)
if user_cert is None:
return Response(
status=404,
data={'error_code': 'no_certificate_for_user'}
)
return Response(
{
"username": user_cert.get('username'),
"course_id": unicode(user_cert.get('course_key')),
"certificate_type": user_cert.get('type'),
"status": user_cert.get('status'),
"download_url": user_cert.get('download_url'),
"grade": user_cert.get('grade')
}
)
......@@ -8,16 +8,20 @@ from django.test.utils import override_settings
from django.conf import settings
from mock import patch
from nose.plugins.attrib import attr
from opaque_keys.edx.locator import CourseLocator
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from config_models.models import cache
from course_modes.models import CourseMode
from course_modes.tests.factories import CourseModeFactory
from config_models.models import cache
from microsite_configuration import microsite
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
from util.testing import EventTestMixin
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase,
SharedModuleStoreTestCase,
)
from certificates import api as certs_api
from certificates.models import (
......@@ -30,7 +34,6 @@ from certificates.models import (
from certificates.queue import XQueueCertInterface, XQueueAddToQueueError
from certificates.tests.factories import GeneratedCertificateFactory
from microsite_configuration import microsite
FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
......@@ -201,6 +204,93 @@ class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTes
@attr('shard_1')
class CertificateGetTests(SharedModuleStoreTestCase):
"""Tests for the `test_get_certificate_for_user` helper function. """
@classmethod
def setUpClass(cls):
super(CertificateGetTests, cls).setUpClass()
cls.student = UserFactory()
cls.student_no_cert = UserFactory()
cls.course_1 = CourseFactory.create(
org='edx',
number='verified_1',
display_name='Verified Course 1'
)
cls.course_2 = CourseFactory.create(
org='edx',
number='verified_2',
display_name='Verified Course 2'
)
# certificate for the first course
GeneratedCertificateFactory.create(
user=cls.student,
course_id=cls.course_1.id,
status=CertificateStatuses.downloadable,
mode='verified',
download_url='www.google.com',
grade="0.88",
)
# certificate for the second course
GeneratedCertificateFactory.create(
user=cls.student,
course_id=cls.course_2.id,
status=CertificateStatuses.downloadable,
mode='honor',
download_url='www.gmail.com',
grade="0.99",
)
def test_get_certificate_for_user(self):
"""
Test to get a certificate for a user for a specific course.
"""
cert = certs_api.get_certificate_for_user(self.student.username, self.course_1.id)
self.assertEqual(cert['username'], self.student.username)
self.assertEqual(cert['course_key'], self.course_1.id)
self.assertEqual(cert['type'], CourseMode.VERIFIED)
self.assertEqual(cert['status'], CertificateStatuses.downloadable)
self.assertEqual(cert['grade'], "0.88")
self.assertEqual(cert['download_url'], 'www.google.com')
def test_get_certificates_for_user(self):
"""
Test to get all the certificates for a user
"""
certs = certs_api.get_certificates_for_user(self.student.username)
self.assertEqual(len(certs), 2)
self.assertEqual(certs[0]['username'], self.student.username)
self.assertEqual(certs[1]['username'], self.student.username)
self.assertEqual(certs[0]['course_key'], self.course_1.id)
self.assertEqual(certs[1]['course_key'], self.course_2.id)
self.assertEqual(certs[0]['type'], CourseMode.VERIFIED)
self.assertEqual(certs[1]['type'], CourseMode.HONOR)
self.assertEqual(certs[0]['status'], CertificateStatuses.downloadable)
self.assertEqual(certs[1]['status'], CertificateStatuses.downloadable)
self.assertEqual(certs[0]['grade'], '0.88')
self.assertEqual(certs[1]['grade'], '0.99')
self.assertEqual(certs[0]['download_url'], 'www.google.com')
self.assertEqual(certs[1]['download_url'], 'www.gmail.com')
def test_no_certificate_for_user(self):
"""
Test the case when there is no certificate for a user for a specific course.
"""
self.assertIsNone(
certs_api.get_certificate_for_user(self.student_no_cert.username, self.course_1.id)
)
def test_no_certificates_for_user(self):
"""
Test the case when there are no certificates for a user.
"""
self.assertEqual(
certs_api.get_certificates_for_user(self.student_no_cert.username),
[]
)
@attr('shard_1')
@override_settings(CERT_QUEUE='certificates')
class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, ModuleStoreTestCase):
"""Tests for generating certificates for students. """
......
......@@ -925,6 +925,10 @@ urlpatterns += (
url(r'^update_certificate$', 'certificates.views.update_certificate'),
url(r'^update_example_certificate$', 'certificates.views.update_example_certificate'),
url(r'^request_certificate$', 'certificates.views.request_certificate'),
# REST APIs
url(r'^api/certificates/',
include('lms.djangoapps.certificates.apis.urls', namespace='certificates_api')),
)
# XDomain proxy
......
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