From cf33d85ef9dc6b59cd805c13e57000e529be5631 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 9 Nov 2012 12:02:01 -0500 Subject: [PATCH] Wire up cert status --- common/djangoapps/student/views.py | 45 ++++++----- common/lib/xmodule/xmodule/course_module.py | 10 +++ lms/djangoapps/certificates/models.py | 7 +- lms/templates/dashboard.html | 83 ++++++++++++++------- 4 files changed, 97 insertions(+), 48 deletions(-) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 8810c8609b..5d5dff81d2 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -29,6 +29,9 @@ from django_future.csrf import ensure_csrf_cookie, csrf_exempt from student.models import (Registration, UserProfile, PendingNameChange, PendingEmailChange, CourseEnrollment) + +from certificates.models import CertificateStatuses, certificate_status_for_student + from xmodule.course_module import CourseDescriptor from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.django import modulestore @@ -143,11 +146,15 @@ def dashboard(request): show_courseware_links_for = frozenset(course.id for course in courses if has_access(request.user, course, 'load')) + cert_statuses = [certificate_status_for_student(request.user, course.id) for course in courses] + context = {'courses': courses, 'message': message, 'staff_access': staff_access, 'errored_courses': errored_courses, - 'show_courseware_links_for' : show_courseware_links_for} + 'show_courseware_links_for' : show_courseware_links_for, + 'cert_statuses': cert_statuses, + } return render_to_response('dashboard.html', context) @@ -206,13 +213,13 @@ def change_enrollment(request): return {'success': False, 'error': 'enrollment in {} not allowed at this time' .format(course.display_name)} - - org, course_num, run=course_id.split("/") + + org, course_num, run=course_id.split("/") statsd.increment("common.student.enrollment", tags=["org:{0}".format(org), "course:{0}".format(course_num), "run:{0}".format(run)]) - + enrollment, created = CourseEnrollment.objects.get_or_create(user=user, course_id=course.id) return {'success': True} @@ -220,13 +227,13 @@ def change_enrollment(request): try: enrollment = CourseEnrollment.objects.get(user=user, course_id=course_id) enrollment.delete() - - org, course_num, run=course_id.split("/") + + org, course_num, run=course_id.split("/") statsd.increment("common.student.unenrollment", tags=["org:{0}".format(org), "course:{0}".format(course_num), "run:{0}".format(run)]) - + return {'success': True} except CourseEnrollment.DoesNotExist: return {'success': False, 'error': 'You are not enrolled for this course.'} @@ -275,13 +282,13 @@ def login_user(request, error=""): log.info("Login success - {0} ({1})".format(username, email)) try_change_enrollment(request) - + statsd.increment("common.student.successful_login") - + return HttpResponse(json.dumps({'success': True})) - + log.warning("Login failed - Account not active for user {0}, resending activation".format(username)) - + reactivation_email_for_user(user) not_activated_msg = "This account has not been activated. We have " + \ "sent another activation message. Please check your " + \ @@ -483,9 +490,9 @@ def create_account(request, post_override=None): log.debug('bypassing activation email') login_user.is_active = True login_user.save() - + statsd.increment("common.student.account_created") - + js = {'success': True} return HttpResponse(json.dumps(js), mimetype="application/json") @@ -541,9 +548,9 @@ def password_reset(request): ''' Attempts to send a password reset e-mail. ''' if request.method != "POST": raise Http404 - + # By default, Django doesn't allow Users with is_active = False to reset their passwords, - # but this bites people who signed up a long time ago, never activated, and forgot their + # but this bites people who signed up a long time ago, never activated, and forgot their # password. So for their sake, we'll auto-activate a user for whome password_reset is called. try: user = User.objects.get(email=request.POST['email']) @@ -551,7 +558,7 @@ def password_reset(request): user.save() except: log.exception("Tried to auto-activate user to enable password reset, but failed.") - + form = PasswordResetForm(request.POST) if form.is_valid(): form.save(use_https = request.is_secure(), @@ -589,7 +596,7 @@ def reactivation_email_for_user(user): res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL) return HttpResponse(json.dumps({'success': True})) - + @ensure_csrf_cookie def change_email_request(request): @@ -764,8 +771,8 @@ def accept_name_change_by_id(id): @ensure_csrf_cookie def accept_name_change(request): - ''' JSON: Name change process. Course staff clicks 'accept' on a given name change - + ''' JSON: Name change process. Course staff clicks 'accept' on a given name change + We used this during the prototype but now we simply record name changes instead of manually approving them. Still keeping this around in case we want to go back to this approval method. diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index fc27a692ea..46e8e145df 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -298,6 +298,16 @@ class CourseDescriptor(SequenceDescriptor): # Explicit comparison to True because we always want to return a bool. return self.metadata.get('hide_progress_tab') == True + @property + def end_of_course_survey_url(self): + """ + Pull from policy. Once we have our own survey module set up, can change this to point to an automatically + created survey for each class. + + Returns None if no url specified. + """ + return self.metadata.get('end_of_course_survey_url') + @property def title(self): return self.display_name diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 98b76d8fdc..1434510920 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -2,7 +2,7 @@ from django.contrib.auth.models import User from django.db import models from datetime import datetime -''' +""" Certificates are created for a student and an offering of a course. When a certificate is generated, a unique ID is generated so that @@ -35,8 +35,7 @@ State diagram: v v v [downloadable] [downloadable] [deleted] -''' - +""" class CertificateStatuses(object): unavailable = 'unavailable' @@ -88,6 +87,7 @@ def certificate_status_for_student(student, course_id): If the status is "downloadable", the dictionary also contains "download_url". + If the student has been graded, the dictionary also contains their grade for the course. ''' try: @@ -97,6 +97,7 @@ def certificate_status_for_student(student, course_id): return { 'status': CertificateStatuses.downloadable, 'download_url': generated_certificate.download_url, + 'grade': generated_certificate.grade, } else: return {'status': generated_certificate.status} diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index ad2d66f8be..c6c71017b3 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -2,6 +2,7 @@ from django.core.urlresolvers import reverse from courseware.courses import course_image_url, get_course_about_section from courseware.access import has_access + from certificates.models import CertificateStatuses %> <%inherit file="main.html" /> @@ -114,7 +115,7 @@ % if len(courses) > 0: - % for course in courses: + % for course, cert_status in zip(courses, cert_statuses):
<% @@ -145,35 +146,65 @@
-
-

Final course details are being wrapped up at this time. Your final standing will be available shortly.

-
-
-

You have received a grade of B in this course.

+ % if course.has_ended: + <% + passing_grade = False + cert_button = False + survey_button = False + if cert_status['status'] in [CertificateStatuses.generating, CertificateStatuses.regenerating]: + status_css_class = 'course-status-certrendering' + cert_button = True + survey_button = True + passing_grade = True + elif cert_status['status'] == CertificateStatuses.downloadable: + status_css_class = 'course-status-certavailable' + cert_button = True + survey_button = True + passing_grade = True + elif cert_status['status'] == CertificateStatuses.notpassing: + status_css_class = 'course-status-certnotavailable' + survey_button = True + else cert_status['status']: + # This is primarily the 'unavailable' state, but also 'error', 'deleted', etc. + status_css_class = 'course-status-processing' - -
+ if survey_button and not course.end_of_course_survey_url: + survey_button = False + %> +
-
-

You have received a grade of B in this course.

+ % if cert_status == CertificateStatuses.unavailable: +

Final course details are being wrapped up at this time. + Your final standing will be available shortly.

+ % elif passing_grade: +

You have received a grade of + ${cert_status['grade']} + in this course.

+ % elif cert_status == CertificateStatuses.notpassing: +

You did not complete the necessary requirements for + completion of this course. Your grade was ${cert_status['grade']} +

+ % endif + % if cert_button or survey_button: + + % endif +
- -
- -
-

You did not complete the necessary requirements for completion of this course.

- - -
+ % endif Unregister