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
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
"""
|
||||
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 (
|
||||
@@ -1156,6 +1157,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 """
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
""" 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}.'
|
||||
|
||||
Reference in New Issue
Block a user