* API endpoint for certificate generation, an authenticated post with course
id requests that grading be carried out and a cert generated for
request.user in that course, using the usual grading and certificate
machinery (ie, it does not imply whitelisting, though whitelists and
blacklists will be respected)
- Logs each request as it comes in
- Calls xq.add_cert() and consequently, does grading synchronously on
this app host and then queues request for certificate agent.
- example usage:
```
curl --data "student_id=9999&course_id=Stanford/2013/Some_Class" http://127.0.0.1:8000/request_certificate
```
* Studio advanced setting added, "certificates_show_before_end", which
determines whether a course should permit certificates to be downloadable
by students before the coures's end date has passed.
- Modifications to dashboard view and templates to allow display of
certificate download links before course has ended.
(XXX: may declare failing students as failing before the course has ended.)
- To test, turn the setting on in a course which hasn't ended yet, and
force certificate generation for a student, then check their
dashboard.
118 lines
4.9 KiB
Python
118 lines
4.9 KiB
Python
"""URL handlers related to certificate handling by LMS"""
|
|
from dogapi import dog_stats_api
|
|
import json
|
|
import logging
|
|
|
|
from django.contrib.auth.models import User
|
|
from django.http import HttpResponse
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
|
|
from capa.xqueue_interface import XQUEUE_METRIC_NAME
|
|
from certificates.models import certificate_status_for_student, CertificateStatuses, GeneratedCertificate
|
|
from certificates.queue import XQueueCertInterface
|
|
from xmodule.course_module import CourseDescriptor
|
|
from xmodule.modulestore.django import modulestore
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@csrf_exempt
|
|
def request_certificate(request):
|
|
"""Request the on-demand creation of a certificate for some user, course.
|
|
|
|
A request doesn't imply a guarantee that such a creation will take place.
|
|
We intentionally use the same machinery as is used for doing certification
|
|
at the end of a course run, so that we can be sure users get graded and
|
|
then if and only if they pass, do they get a certificate issued.
|
|
"""
|
|
if request.method == "POST":
|
|
if request.user.is_authenticated():
|
|
xqci = XQueueCertInterface()
|
|
username = request.user.username
|
|
student = User.objects.get(username=username)
|
|
course_id = request.POST.get('course_id')
|
|
course = modulestore().get_instance(course_id, CourseDescriptor.id_to_location(course_id), depth=2)
|
|
|
|
status = certificate_status_for_student(student, course_id)['status']
|
|
if status in [CertificateStatuses.unavailable, CertificateStatuses.notpassing, CertificateStatuses.error]:
|
|
logger.info('Grading and certification requested for user {} in course {} via /request_certificate call'.format(username, course_id))
|
|
status = xqci.add_cert(student, course_id, course=course)
|
|
return HttpResponse(json.dumps({'add_status': status}), mimetype='application/json')
|
|
return HttpResponse(json.dumps({'add_status': 'ERRORANONYMOUSUSER'}), mimetype='application/json')
|
|
|
|
|
|
@csrf_exempt
|
|
def update_certificate(request):
|
|
"""
|
|
Will update GeneratedCertificate for a new certificate or
|
|
modify an existing certificate entry.
|
|
|
|
See models.py for a state diagram of certificate states
|
|
|
|
This view should only ever be accessed by the xqueue server
|
|
"""
|
|
|
|
status = CertificateStatuses
|
|
if request.method == "POST":
|
|
|
|
xqueue_body = json.loads(request.POST.get('xqueue_body'))
|
|
xqueue_header = json.loads(request.POST.get('xqueue_header'))
|
|
|
|
try:
|
|
cert = GeneratedCertificate.objects.get(
|
|
user__username=xqueue_body['username'],
|
|
course_id=xqueue_body['course_id'],
|
|
key=xqueue_header['lms_key'])
|
|
|
|
except GeneratedCertificate.DoesNotExist:
|
|
logger.critical('Unable to lookup certificate\n'
|
|
'xqueue_body: {0}\n'
|
|
'xqueue_header: {1}'.format(
|
|
xqueue_body, xqueue_header))
|
|
|
|
return HttpResponse(json.dumps({
|
|
'return_code': 1,
|
|
'content': 'unable to lookup key'}),
|
|
mimetype='application/json')
|
|
|
|
if 'error' in xqueue_body:
|
|
cert.status = status.error
|
|
if 'error_reason' in xqueue_body:
|
|
|
|
# Hopefully we will record a meaningful error
|
|
# here if something bad happened during the
|
|
# certificate generation process
|
|
#
|
|
# example:
|
|
# (aamorm BerkeleyX/CS169.1x/2012_Fall)
|
|
# <class 'simples3.bucket.S3Error'>:
|
|
# HTTP error (reason=error(32, 'Broken pipe'), filename=None) :
|
|
# certificate_agent.py:175
|
|
|
|
|
|
cert.error_reason = xqueue_body['error_reason']
|
|
else:
|
|
if cert.status in [status.generating, status.regenerating]:
|
|
cert.download_uuid = xqueue_body['download_uuid']
|
|
cert.verify_uuid = xqueue_body['verify_uuid']
|
|
cert.download_url = xqueue_body['url']
|
|
cert.status = status.downloadable
|
|
elif cert.status in [status.deleting]:
|
|
cert.status = status.deleted
|
|
else:
|
|
logger.critical('Invalid state for cert update: {0}'.format(
|
|
cert.status))
|
|
return HttpResponse(json.dumps({
|
|
'return_code': 1,
|
|
'content': 'invalid cert status'}),
|
|
mimetype='application/json')
|
|
|
|
dog_stats_api.increment(XQUEUE_METRIC_NAME, tags=[
|
|
u'action:update_certificate',
|
|
u'course_id:{}'.format(cert.course_id)
|
|
])
|
|
|
|
cert.save()
|
|
return HttpResponse(json.dumps({'return_code': 0}),
|
|
mimetype='application/json')
|