From 1676fc31a5eb6c6de8c5efb93bda32e0b5b8955c Mon Sep 17 00:00:00 2001 From: David Ormsbee <dave@edx.org> Date: Wed, 21 Aug 2013 16:21:18 -0400 Subject: [PATCH] Halfway state for course enrollment by mode --- common/djangoapps/course_modes/models.py | 5 ++ common/djangoapps/course_modes/views.py | 28 +++++++- common/djangoapps/student/views.py | 17 +++-- .../verify_student/tests/test_models.py | 2 +- .../verify_student/tests/test_ssencrypt.py | 7 +- .../verify_student/tests/test_views.py | 3 +- lms/djangoapps/verify_student/views.py | 65 ++++++++++++++++++- lms/templates/courseware/course_about.html | 7 +- lms/urls.py | 5 +- 9 files changed, 119 insertions(+), 20 deletions(-) diff --git a/common/djangoapps/course_modes/models.py b/common/djangoapps/course_modes/models.py index 561c078b3b1..721b587603e 100644 --- a/common/djangoapps/course_modes/models.py +++ b/common/djangoapps/course_modes/models.py @@ -51,3 +51,8 @@ class CourseMode(models.Model): if not modes: modes = [cls.DEFAULT_MODE] return modes + + def __unicode__(self): + return u"{} : {}, min={}, prices={}".format( + self.course_id, self.mode_slug, self.min_price, self.suggested_prices + ) diff --git a/common/djangoapps/course_modes/views.py b/common/djangoapps/course_modes/views.py index 60f00ef0ef3..2f7356a1a73 100644 --- a/common/djangoapps/course_modes/views.py +++ b/common/djangoapps/course_modes/views.py @@ -1 +1,27 @@ -# Create your views here. +from django.http import HttpResponse +from django.views.generic.base import View + +from mitxmako.shortcuts import render_to_response + +from course_modes.models import CourseMode + +class ChooseModeView(View): + + def get(self, request): + course_id = request.GET.get("course_id") + context = { + "course_id" : course_id, + "available_modes" : CourseMode.modes_for_course(course_id) + } + return render_to_response("course_modes/choose.html", context) + + def post(self, request): + course_id = request.GET.get("course_id") + mode_slug = request.POST.get("mode_slug") + user = request.user + + # This is a bit redundant with logic in student.views.change_enrollement, + # but I don't really have the time to refactor it more nicely and test. + course = course_from_id(course_id) + if has_access(user, course, 'enroll'): + pass diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 4d59b5cc668..b3f296080a2 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -27,18 +27,19 @@ from django_future.csrf import ensure_csrf_cookie from django.utils.http import cookie_date from django.utils.http import base36_to_int from django.utils.translation import ugettext as _ +from django.views.decorators.http import require_POST from ratelimitbackend.exceptions import RateLimitException from mitxmako.shortcuts import render_to_response, render_to_string from bs4 import BeautifulSoup +from course_modes.models import CourseMode from student.models import (Registration, UserProfile, TestCenterUser, TestCenterUserForm, TestCenterRegistration, TestCenterRegistrationForm, PendingNameChange, PendingEmailChange, CourseEnrollment, unique_id_for_user, get_testcenter_registration, CourseEnrollmentAllowed) - from student.forms import PasswordResetFormNoActive from certificates.models import CertificateStatuses, certificate_status_for_student @@ -328,7 +329,8 @@ def try_change_enrollment(request): except Exception, e: log.exception("Exception automatically enrolling after login: {0}".format(str(e))) - +@login_required +@require_POST def change_enrollment(request): """ Modify the enrollment status for the logged-in user. @@ -346,12 +348,7 @@ def change_enrollment(request): as a post-login/registration helper, so the error messages in the responses should never actually be user-visible. """ - if request.method != "POST": - return HttpResponseNotAllowed(["POST"]) - user = request.user - if not user.is_authenticated(): - return HttpResponseForbidden() action = request.POST.get("enrollment_action") course_id = request.POST.get("course_id") @@ -371,6 +368,12 @@ def change_enrollment(request): if not has_access(user, course, 'enroll'): return HttpResponseBadRequest(_("Enrollment is closed")) + # If this course is available in multiple modes, redirect them to a page + # where they can choose which mode they want. + available_modes = CourseMode.modes_for_course(course_id) + if len(available_modes) > 1: + return HttpResponse(reverse("course_modes.views.choose")) + org, course_num, run = course_id.split("/") statsd.increment("common.student.enrollment", tags=["org:{0}".format(org), diff --git a/lms/djangoapps/verify_student/tests/test_models.py b/lms/djangoapps/verify_student/tests/test_models.py index 838da26fbad..249e1f26539 100644 --- a/lms/djangoapps/verify_student/tests/test_models.py +++ b/lms/djangoapps/verify_student/tests/test_models.py @@ -7,7 +7,7 @@ from student.tests.factories import UserFactory from verify_student.models import SoftwareSecurePhotoVerification, VerificationException -class TestPhotoVerification(object): +class TestPhotoVerification(TestCase): def test_state_transitions(self): """Make sure we can't make unexpected status transitions. diff --git a/lms/djangoapps/verify_student/tests/test_ssencrypt.py b/lms/djangoapps/verify_student/tests/test_ssencrypt.py index f2d063daf4c..1e9978be7c1 100644 --- a/lms/djangoapps/verify_student/tests/test_ssencrypt.py +++ b/lms/djangoapps/verify_student/tests/test_ssencrypt.py @@ -4,7 +4,7 @@ from nose.tools import assert_equals from verify_student.ssencrypt import ( aes_decrypt, aes_encrypt, encrypt_and_encode, decode_and_decrypt, - rsa_decrypt, rsa_encrypt + rsa_decrypt, rsa_encrypt, random_aes_key ) def test_aes(): @@ -76,8 +76,3 @@ l8N6+LEIVTMAytPk+/bImHvGHKZkCz5rEMSuYJWOmqKI92rUtI6fz5DUb3XSbrwT # Even though our AES key is only 32 bytes, RSA encryption will make it 256 # bytes, and base64 encoding will blow that up to 344 assert_equals(len(base64.urlsafe_b64encode(encrypted_aes_key)), 344) - - # Software Secure would decrypt our photo_id image by doing: - #rsa_encrypted_aes_key = base64.urlsafe_b64decode(encoded_photo_id_key) - #photo_id_aes_key = rsa_decrypt(rsa_encrypted_aes_key, priv_key_str) - diff --git a/lms/djangoapps/verify_student/tests/test_views.py b/lms/djangoapps/verify_student/tests/test_views.py index 47b08f7b357..22426c811d3 100644 --- a/lms/djangoapps/verify_student/tests/test_views.py +++ b/lms/djangoapps/verify_student/tests/test_views.py @@ -31,7 +31,8 @@ class StartView(TestCase): user = UserFactory.create(username="rusty", password="test") self.client.login(username="rusty", password="test") + + def must_be_logged_in(self): self.assertHttpForbidden(self.client.get(self.start_url())) - \ No newline at end of file diff --git a/lms/djangoapps/verify_student/views.py b/lms/djangoapps/verify_student/views.py index 3da2dd3bbe4..5d113c68b12 100644 --- a/lms/djangoapps/verify_student/views.py +++ b/lms/djangoapps/verify_student/views.py @@ -6,6 +6,8 @@ from mitxmako.shortcuts import render_to_response from verify_student.models import SoftwareSecurePhotoVerification +from course_modes.models import CourseMode + # @login_required def start_or_resume_attempt(request, course_id): """ @@ -15,8 +17,8 @@ def start_or_resume_attempt(request, course_id): """ # If the user has already been verified within the given time period, # redirect straight to the payment -- no need to verify again. - #if SoftwareSecurePhotoVerification.user_is_verified(user): - # pass + if SoftwareSecurePhotoVerification.user_is_verified(user): + pass attempt = SoftwareSecurePhotoVerification.active_for_user(request.user) if not attempt: @@ -47,3 +49,62 @@ def final_verification(request): def show_verification_page(request): pass + + +def enroll(user, course_id, mode_slug): + """ + Enroll the user in a course for a certain mode. + + This is the view you send folks to when they click on the enroll button. + This does NOT cover changing enrollment modes -- it's intended for new + enrollments only, and will just redirect to the dashboard if it detects + that an enrollment already exists. + """ + # If the user is already enrolled, jump to the dashboard. Yeah, we could + # do upgrades here, but this method is complicated enough. + if CourseEnrollment.is_enrolled(user, course_id): + return HttpResponseRedirect(reverse('dashboard')) + + available_modes = CourseModes.modes_for_course(course_id) + + # If they haven't chosen a mode... + if not mode_slug: + # Does this course support multiple modes of Enrollment? If so, redirect + # to a page that lets them choose which mode they want. + if len(available_modes) > 1: + return HttpResponseRedirect( + reverse('choose_enroll_mode', course_id=course_id) + ) + # Otherwise, we use the only mode that's supported... + else: + mode_slug = available_modes[0].slug + + # If the mode is one of the simple, non-payment ones, do the enrollment and + # send them to their dashboard. + if mode_slug in ("honor", "audit"): + CourseEnrollment.enroll(user, course_id, mode=mode_slug) + return HttpResponseRedirect(reverse('dashboard')) + + if mode_slug == "verify": + if SoftwareSecureVerification.has_submitted_recent_request(user): + # Capture payment info + # Create an order + # Create a VerifiedCertificate order item + return HttpResponse.Redirect(reverse('payment')) + + + # There's always at least one mode available (default is "honor"). If they + # haven't specified a mode, we just assume it's + if not mode: + mode = available_modes[0] + + elif len(available_modes) == 1: + if mode != available_modes[0]: + raise Exception() + + mode = available_modes[0] + + if mode == "honor": + CourseEnrollment.enroll(user, course_id) + return HttpResponseRedirect(reverse('dashboard')) + diff --git a/lms/templates/courseware/course_about.html b/lms/templates/courseware/course_about.html index 39cf04e72c3..ae3bb4e07ca 100644 --- a/lms/templates/courseware/course_about.html +++ b/lms/templates/courseware/course_about.html @@ -47,7 +47,12 @@ $('#class_enroll_form').on('ajax:complete', function(event, xhr) { if(xhr.status == 200) { - location.href = "${reverse('dashboard')}"; + if (xhr.responseText == "") { + location.href = "${reverse('dashboard')}"; + } + else { + location.href = xhr.responseText; + } } else if (xhr.status == 403) { location.href = "${reverse('register_user')}?course_id=${course.id}&enrollment_action=enroll"; } else { diff --git a/lms/urls.py b/lms/urls.py index 58c2cd3b550..1318fc6f74e 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -65,7 +65,10 @@ urlpatterns = ('', # nopep8 ) if settings.MITX_FEATURES.get("MULTIPLE_ENROLLMENT_ROLES"): - urlpatterns += (url(r'^verify_student/', include('verify_student.urls')),) + urlpatterns += ( + url(r'^verify_student/', include('verify_student.urls')), + url(r'^course_modes/', include('course_modes.urls')), + ) js_info_dict = { -- GitLab