299 lines
12 KiB
Python
299 lines
12 KiB
Python
""" API v0 views. """
|
|
|
|
|
|
import logging
|
|
|
|
import edx_api_doc_tools as apidocs
|
|
from django.contrib.auth import get_user_model
|
|
from edx_rest_framework_extensions import permissions
|
|
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
|
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
|
|
from opaque_keys import InvalidKeyError
|
|
from opaque_keys.edx.keys import CourseKey
|
|
from rest_condition import C
|
|
from rest_framework.permissions import IsAuthenticated
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
|
|
from lms.djangoapps.certificates.api import get_certificate_for_user, get_certificates_for_user
|
|
from lms.djangoapps.certificates.apis.v0.permissions import IsOwnerOrPublicCertificates
|
|
from openedx.core.djangoapps.catalog.utils import get_course_run_details
|
|
from openedx.core.djangoapps.certificates.api import certificates_viewable_for_course
|
|
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
|
from openedx.core.djangoapps.user_api.accounts.api import visible_fields
|
|
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
|
|
|
|
log = logging.getLogger(__name__)
|
|
User = get_user_model()
|
|
|
|
|
|
class CertificatesDetailView(APIView):
|
|
"""
|
|
**Use Case**
|
|
|
|
* Get the details of a certificate for a specific user in a course.
|
|
|
|
**Example Request**
|
|
|
|
GET /api/certificates/v0/certificates/{username}/courses/{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
|
|
|
|
* created_date: Date/time the certificate was created, in ISO-8661 format.
|
|
|
|
* status: A string representation of the certificate status.
|
|
|
|
* is_passing: True if the certificate has a passing status, False if not.
|
|
|
|
* 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",
|
|
"created_date": "2015-12-03T13:14:28+0000",
|
|
"status": "downloadable",
|
|
"is_passing": true,
|
|
"download_url": "http://www.example.com/cert.pdf",
|
|
"grade": "0.98"
|
|
}
|
|
"""
|
|
|
|
authentication_classes = (
|
|
JwtAuthentication,
|
|
BearerAuthenticationAllowInactiveUser,
|
|
SessionAuthenticationAllowInactiveUser,
|
|
)
|
|
|
|
permission_classes = (permissions.JWT_RESTRICTED_APPLICATION_OR_USER_ACCESS,)
|
|
|
|
required_scopes = ['certificates:read']
|
|
|
|
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'}
|
|
)
|
|
|
|
course_overview = CourseOverview.get_from_id(course_id)
|
|
# return 404 if it's not a PDF certificates and there is no active certificate configuration.
|
|
if not user_cert['is_pdf_certificate'] and not course_overview.has_any_active_web_certificate:
|
|
return Response(
|
|
status=404,
|
|
data={'error_code': 'no_certificate_configuration_for_course'}
|
|
)
|
|
|
|
return Response(
|
|
{
|
|
"username": user_cert.get('username'),
|
|
"course_id": str(user_cert.get('course_key')),
|
|
"certificate_type": user_cert.get('type'),
|
|
"created_date": user_cert.get('created'),
|
|
"status": user_cert.get('status'),
|
|
"is_passing": user_cert.get('is_passing'),
|
|
"download_url": user_cert.get('download_url'),
|
|
"grade": user_cert.get('grade')
|
|
}
|
|
)
|
|
|
|
|
|
class CertificatesListView(APIView):
|
|
"""REST API endpoints for listing certificates."""
|
|
authentication_classes = (
|
|
JwtAuthentication,
|
|
BearerAuthenticationAllowInactiveUser,
|
|
SessionAuthenticationAllowInactiveUser,
|
|
)
|
|
permission_classes = (
|
|
C(IsAuthenticated) & (
|
|
C(permissions.NotJwtRestrictedApplication) |
|
|
(
|
|
C(permissions.JwtRestrictedApplication) &
|
|
permissions.JwtHasScope &
|
|
permissions.JwtHasUserFilterForRequestedUser
|
|
)
|
|
),
|
|
(C(permissions.IsStaff) | IsOwnerOrPublicCertificates), # pylint: disable=unsupported-binary-operation
|
|
)
|
|
|
|
required_scopes = ['certificates:read']
|
|
|
|
@apidocs.schema(parameters=[
|
|
apidocs.string_parameter(
|
|
'username',
|
|
apidocs.ParameterLocation.PATH,
|
|
description="The users to get certificates for",
|
|
)
|
|
])
|
|
def get(self, request, username):
|
|
"""Get a paginated list of bookmarks for a user.
|
|
|
|
**Use Case**
|
|
|
|
Get the list of viewable course certificates for a specific user.
|
|
|
|
**Example Request**
|
|
|
|
GET /api/certificates/v0/certificates/{username}
|
|
|
|
**GET Response Values**
|
|
|
|
If the request for information about the user's certificates is successful,
|
|
an HTTP 200 "OK" response is returned.
|
|
|
|
The HTTP 200 response contains a list of dicts with the following keys/values.
|
|
|
|
* username: A string representation of an user's username passed in the request.
|
|
|
|
* course_id: A string representation of a Course ID.
|
|
|
|
* course_display_name: A string representation of the Course name.
|
|
|
|
* course_organization: A string representation of the organization associated with the Course.
|
|
|
|
* certificate_type: A string representation of the certificate type.
|
|
Can be honor|verified|professional
|
|
|
|
* created_date: Date/time the certificate was created, in ISO-8661 format.
|
|
|
|
* status: A string representation of the certificate status.
|
|
|
|
* is_passing: True if the certificate has a passing status, False if not.
|
|
|
|
* 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",
|
|
"created_date": "2015-12-03T13:14:28+0000",
|
|
"status": "downloadable",
|
|
"is_passing": true,
|
|
"download_url": "http://www.example.com/cert.pdf",
|
|
"grade": "0.98"
|
|
}]
|
|
"""
|
|
user_certs = []
|
|
if self._viewable_by_requestor(request, username):
|
|
for user_cert in self._get_certificates_for_user(username):
|
|
user_certs.append({
|
|
'username': user_cert.get('username'),
|
|
'course_id': str(user_cert.get('course_key')),
|
|
'course_display_name': user_cert.get('course_display_name'),
|
|
'course_organization': user_cert.get('course_organization'),
|
|
'certificate_type': user_cert.get('type'),
|
|
'created_date': user_cert.get('created'),
|
|
'modified_date': user_cert.get('modified'),
|
|
'status': user_cert.get('status'),
|
|
'is_passing': user_cert.get('is_passing'),
|
|
'download_url': user_cert.get('download_url'),
|
|
'grade': user_cert.get('grade'),
|
|
})
|
|
return Response(user_certs)
|
|
|
|
def _viewable_by_requestor(self, request, username):
|
|
"""
|
|
Returns whether or not the requesting user is allowed to view the given user's certificates.
|
|
"""
|
|
try:
|
|
user = User.objects.select_related('profile').get(username=username)
|
|
except User.DoesNotExist:
|
|
return False
|
|
|
|
is_owner = request.user.username == username
|
|
is_staff = request.user.is_staff
|
|
certificates_viewable = 'course_certificates' in visible_fields(user.profile, user)
|
|
|
|
return is_owner or is_staff or certificates_viewable
|
|
|
|
def _get_certificates_for_user(self, username):
|
|
"""
|
|
Returns a user's viewable certificates sorted by course name.
|
|
"""
|
|
course_certificates = get_certificates_for_user(username)
|
|
passing_certificates = {}
|
|
for course_certificate in course_certificates:
|
|
if course_certificate.get('is_passing', False):
|
|
course_key = course_certificate['course_key']
|
|
passing_certificates[course_key] = course_certificate
|
|
|
|
viewable_certificates = []
|
|
for course_key, course_overview in CourseOverview.get_from_ids(
|
|
list(passing_certificates.keys())
|
|
).items():
|
|
if not course_overview:
|
|
# For deleted XML courses in which learners have a valid certificate.
|
|
# i.e. MITx/7.00x/2013_Spring
|
|
course_overview = self._get_pseudo_course_overview(course_key)
|
|
if certificates_viewable_for_course(course_overview):
|
|
course_certificate = passing_certificates[course_key]
|
|
# add certificate into viewable certificate list only if it's a PDF certificate
|
|
# or there is an active certificate configuration.
|
|
if course_certificate['is_pdf_certificate'] or course_overview.has_any_active_web_certificate:
|
|
course_certificate['course_display_name'] = course_overview.display_name_with_default
|
|
course_certificate['course_organization'] = course_overview.display_org_with_default
|
|
viewable_certificates.append(course_certificate)
|
|
|
|
viewable_certificates.sort(key=lambda certificate: certificate['created'])
|
|
return viewable_certificates
|
|
|
|
def _get_pseudo_course_overview(self, course_key):
|
|
"""
|
|
Returns a pseudo course overview object for deleted courses.
|
|
"""
|
|
course_run = get_course_run_details(course_key, ['title'])
|
|
return CourseOverview(
|
|
display_name=course_run.get('title'),
|
|
display_org_with_default=course_key.org,
|
|
certificates_show_before_end=True
|
|
)
|