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