Commit fd2f3b69 by Peter Fogg

Prevent ordering closed courses, and give a good user message.

When Drupal attempts to enroll a user in a closed course, a 406 will
be returned. This causes the marketing site to redirect to the track
selection page for that course, which will then redirect to the
dashboard with a nice message.

ECOM-2317
parent e6fcfae9
"""
Tests for course_modes views.
"""
from datetime import datetime
import unittest
import decimal
import ddt
import freezegun
from mock import patch
from django.conf import settings
from django.core.urlresolvers import reverse
......@@ -9,6 +15,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from util.testing import UrlResetMixin
from embargo.test_utils import restrict_course
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.tests.factories import CourseFactory
from course_modes.tests.factories import CourseModeFactory
from student.tests.factories import CourseEnrollmentFactory, UserFactory
......@@ -340,6 +347,21 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
self.assertNotContains(response, "Find courses")
self.assertNotContains(response, "Schools & Partners")
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@freezegun.freeze_time('2015-01-02')
def test_course_closed(self):
for mode in ["honor", "verified"]:
CourseModeFactory(mode_slug=mode, course_id=self.course.id)
self.course.enrollment_end = datetime(2015, 01, 01)
modulestore().update_item(self.course, self.user.id)
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
response = self.client.get(url)
# URL-encoded version of 1/1/15, 12:00 AM
redirect_url = reverse('dashboard') + '?course_closed=1%2F1%2F15%2C+12%3A00+AM'
self.assertRedirects(response, redirect_url)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
......
......@@ -3,14 +3,16 @@ Views for the course_mode module
"""
import decimal
import urllib
from babel.dates import format_datetime
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.db import transaction
from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import redirect
from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _
from django.utils.translation import get_language, to_locale, ugettext as _
from django.views.generic.base import View
from ipware.ip import get_ip
from opaque_keys.edx.keys import CourseKey
......@@ -108,6 +110,11 @@ class ChooseModeView(View):
chosen_price = donation_for_course.get(unicode(course_key), None)
course = modulestore().get_course(course_key)
if CourseEnrollment.is_enrollment_closed(request.user, course):
locale = to_locale(get_language())
enrollment_end_date = format_datetime(course.enrollment_end, 'short', locale=locale)
params = urllib.urlencode({'course_closed': enrollment_end_date})
return redirect('{0}?{1}'.format(reverse('dashboard'), params))
# When a credit mode is available, students will be given the option
# to upgrade from a verified mode to a credit mode at the end of the course.
......
......@@ -701,6 +701,10 @@ def dashboard(request):
redirect_message = _("The course you are looking for does not start until {date}.").format(
date=request.GET['notlive']
)
elif 'course_closed' in request.GET:
redirect_message = _("The course you are looking for is closed for enrollment as of {date}.").format(
date=request.GET['course_closed']
)
else:
redirect_message = ''
......
......@@ -2,12 +2,13 @@
"""
End-to-end tests for the LMS.
"""
from datetime import datetime
from datetime import datetime, timedelta
from flaky import flaky
from textwrap import dedent
from unittest import skip
from nose.plugins.attrib import attr
import pytz
import urllib
from bok_choy.promise import EmptyPromise
from ..helpers import (
......@@ -1157,6 +1158,95 @@ class NotLiveRedirectTest(UniqueCourseTest):
@attr('shard_1')
class EnrollmentClosedRedirectTest(UniqueCourseTest):
"""
Test that a banner is shown when the user is redirected to the
dashboard after trying to view the track selection page for a
course after enrollment has ended.
"""
def setUp(self):
"""Create a course that is closed for enrollment, and sign in as a user."""
super(EnrollmentClosedRedirectTest, self).setUp()
course = CourseFixture(
self.course_info['org'], self.course_info['number'],
self.course_info['run'], self.course_info['display_name']
)
now = datetime.now(pytz.UTC)
course.add_course_details({
'enrollment_start': (now - timedelta(days=30)).isoformat(),
'enrollment_end': (now - timedelta(days=1)).isoformat()
})
course.install()
# Add an honor mode to the course
ModeCreationPage(self.browser, self.course_id).visit()
# Add a verified mode to the course
ModeCreationPage(
self.browser,
self.course_id,
mode_slug=u'verified',
mode_display_name=u'Verified Certificate',
min_price=10,
suggested_prices='10,20'
).visit()
def _assert_dashboard_message(self):
"""
Assert that the 'closed for enrollment' text is present on the
dashboard.
"""
page = DashboardPage(self.browser)
page.wait_for_page()
self.assertIn(
'The course you are looking for is closed for enrollment',
page.banner_text
)
def test_redirect_banner(self):
"""
Navigate to the course info page, then check that we're on the
dashboard page with the appropriate message.
"""
AutoAuthPage(self.browser).visit()
url = BASE_URL + "/course_modes/choose/" + self.course_id
self.browser.get(url)
self._assert_dashboard_message()
def test_login_redirect(self):
"""
Test that the user is correctly redirected after logistration when
attempting to enroll in a closed course.
"""
url = '{base_url}/register?{params}'.format(
base_url=BASE_URL,
params=urllib.urlencode({
'course_id': self.course_id,
'enrollment_action': 'enroll',
'email_opt_in': 'false'
})
)
self.browser.get(url)
register_page = CombinedLoginAndRegisterPage(
self.browser,
start_page="register",
course_id=self.course_id
)
register_page.wait_for_page()
register_page.register(
email="email@example.com",
password="password",
username="username",
full_name="Test User",
country="US",
favorite_movie="Mad Max: Fury Road",
terms_of_service=True
)
self._assert_dashboard_message()
@attr('shard_1')
class LMSLanguageTest(UniqueCourseTest):
""" Test suite for the LMS Language """
def setUp(self):
......
""" Commerce API v0 view tests. """
from datetime import datetime, timedelta
import json
import itertools
from uuid import uuid4
......@@ -10,6 +11,7 @@ from django.test import TestCase
from django.test.utils import override_settings
import mock
from nose.plugins.attrib import attr
import pytz
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
......@@ -25,6 +27,7 @@ from openedx.core.lib.django_test_client_utils import get_absolute_url
from student.models import CourseEnrollment
from student.tests.factories import CourseModeFactory
from student.tests.tests import EnrollmentEventTestMixin
from xmodule.modulestore.django import modulestore
@attr('shard_1')
......@@ -345,6 +348,16 @@ class BasketsViewTests(EnrollmentEventTestMixin, UserMixin, ModuleStoreTestCase)
self.assertEqual(mock_update.called, is_opt_in)
self.assertEqual(response.status_code, 200)
def test_closed_course(self):
"""
Ensure that the view does not attempt to create a basket for closed
courses.
"""
self.course.enrollment_end = datetime.now(pytz.UTC) - timedelta(days=1)
modulestore().update_item(self.course, self.user.id) # pylint:disable=no-member
with mock_create_basket(expect_called=False):
self.assertEqual(self._post_to_view().status_code, 406)
@attr('shard_1')
@override_settings(ECOMMERCE_API_URL=TEST_API_URL, ECOMMERCE_API_SIGNING_KEY=TEST_API_SIGNING_KEY)
......
......@@ -100,6 +100,13 @@ class BasketsView(APIView):
msg = Messages.ENROLLMENT_EXISTS.format(course_id=course_id, username=user.username)
return DetailResponse(msg, status=HTTP_409_CONFLICT)
# Check to see if enrollment for this course is closed.
course = courses.get_course(course_key)
if CourseEnrollment.is_enrollment_closed(user, course):
msg = Messages.ENROLLMENT_CLOSED.format(course_id=course_id)
log.info(u'Unable to enroll user %s in closed course %s.', user.id, course_id)
return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE)
# If there is no audit or honor course mode, this most likely
# a Prof-Ed course. Return an error so that the JS redirects
# to track selection.
......
......@@ -17,3 +17,4 @@ class Messages(object):
NO_HONOR_MODE = u'Course {course_id} does not have an honor mode.'
NO_DEFAULT_ENROLLMENT_MODE = u'Course {course_id} does not have an honor or audit mode.'
ENROLLMENT_EXISTS = u'User {username} is already enrolled in {course_id}.'
ENROLLMENT_CLOSED = u'Enrollment is closed for {course_id}.'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment