From 82d7e25f3e358787e2c5d69a96c05426643336a1 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Mon, 2 Feb 2015 09:31:13 -0500 Subject: [PATCH] Add views to the embargo app to render messages explaining why students are blocked. --- common/djangoapps/embargo/messages.py | 25 +++++-- .../migrations/0004_migrate_embargo_config.py | 2 + common/djangoapps/embargo/models.py | 8 +-- common/djangoapps/embargo/tests/test_views.py | 67 ++++++++++++++++++ common/djangoapps/embargo/urls.py | 15 ++++ common/djangoapps/embargo/views.py | 68 +++++++++++++++++++ lms/envs/common.py | 4 ++ .../courseware_access_block.html | 12 ++++ .../enrollment_access_block.html | 12 ++++ lms/urls.py | 6 ++ 10 files changed, 209 insertions(+), 10 deletions(-) create mode 100644 common/djangoapps/embargo/tests/test_views.py create mode 100644 common/djangoapps/embargo/urls.py create mode 100644 common/djangoapps/embargo/views.py create mode 100644 lms/templates/static_templates/courseware_access_block.html create mode 100644 lms/templates/static_templates/enrollment_access_block.html diff --git a/common/djangoapps/embargo/messages.py b/common/djangoapps/embargo/messages.py index 61d7e8b2d9..dd2764ad25 100644 --- a/common/djangoapps/embargo/messages.py +++ b/common/djangoapps/embargo/messages.py @@ -10,18 +10,31 @@ from collections import namedtuple BlockedMessage = namedtuple('BlockedMessage', [ # A user-facing description of the message 'description', + + # The mako template used to render the message + 'template', ]) ENROLL_MESSAGES = { 'default': BlockedMessage( description='Default', + template='static_templates/enrollment_access_block.html' ), -} - - -ACCESS_MESSAGES = { - 'default': BlockedMessage( - description='Default', + 'embargo': BlockedMessage( + description='Embargo', + template='static_templates/embargo.html' + ) +} + + +COURSEWARE_MESSAGES = { + 'default': BlockedMessage( + description='Default', + template='static_templates/courseware_access_block.html' + ), + 'embargo': BlockedMessage( + description='Embargo', + template='static_templates/embargo.html' ) } diff --git a/common/djangoapps/embargo/migrations/0004_migrate_embargo_config.py b/common/djangoapps/embargo/migrations/0004_migrate_embargo_config.py index 9ddcc3b4e0..0e1f7fb03a 100644 --- a/common/djangoapps/embargo/migrations/0004_migrate_embargo_config.py +++ b/common/djangoapps/embargo/migrations/0004_migrate_embargo_config.py @@ -16,6 +16,8 @@ class Migration(DataMigration): orm.CountryAccessRule.objects.get_or_create( country=country_model, rule_type='blacklist', + enroll_msg_key='embargo', + access_msg_key='embargo', restricted_course=new_course ) diff --git a/common/djangoapps/embargo/models.py b/common/djangoapps/embargo/models.py index f8a56d339a..7a3a63ae3b 100644 --- a/common/djangoapps/embargo/models.py +++ b/common/djangoapps/embargo/models.py @@ -21,7 +21,7 @@ from django_countries.fields import CountryField from config_models.models import ConfigurationModel from xmodule_django.models import CourseKeyField, NoneToEmptyManager -from embargo.messages import ENROLL_MESSAGES, ACCESS_MESSAGES +from embargo.messages import ENROLL_MESSAGES, COURSEWARE_MESSAGES class EmbargoedCourse(models.Model): @@ -99,9 +99,9 @@ class RestrictedCourse(models.Model): for msg_key, msg in ENROLL_MESSAGES.iteritems() ]) - ACCESS_MSG_KEY_CHOICES = tuple([ + COURSEWARE_MSG_KEY_CHOICES = tuple([ (msg_key, msg.description) - for msg_key, msg in ACCESS_MESSAGES.iteritems() + for msg_key, msg in COURSEWARE_MESSAGES.iteritems() ]) course_key = CourseKeyField( @@ -118,7 +118,7 @@ class RestrictedCourse(models.Model): access_msg_key = models.CharField( max_length=255, - choices=ACCESS_MSG_KEY_CHOICES, + choices=COURSEWARE_MSG_KEY_CHOICES, default='default', help_text=ugettext_lazy(u"The message to show when a user is blocked from accessing a course.") ) diff --git a/common/djangoapps/embargo/tests/test_views.py b/common/djangoapps/embargo/tests/test_views.py new file mode 100644 index 0000000000..1af91517aa --- /dev/null +++ b/common/djangoapps/embargo/tests/test_views.py @@ -0,0 +1,67 @@ +"""Tests for embargo app views. """ + +import unittest +from mock import patch +from django.test import TestCase +from django.core.urlresolvers import reverse +from django.conf import settings +import ddt + +from util.testing import UrlResetMixin +from embargo import messages + + +@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') +@ddt.ddt +class CourseAccessMessageViewTest(UrlResetMixin, TestCase): + """Tests for the courseware access message view. + + These end-points serve static content. + While we *could* check the text on each page, + this will require changes to the test every time + the text on the page changes. + + Instead, we load each page we expect to be available + (based on the configuration in `embargo.messages`) + and verify that we get the correct status code. + + This will catch errors in the message configuration + (for example, moving a template and forgetting to + update the configuration appropriately). + + """ + + @patch.dict(settings.FEATURES, {'ENABLE_COUNTRY_ACCESS': True}) + def setUp(self): + super(CourseAccessMessageViewTest, self).setUp('embargo') + + @ddt.data(*messages.ENROLL_MESSAGES.keys()) + def test_enrollment_messages(self, msg_key): + self._load_page('enrollment', msg_key) + + @ddt.data(*messages.COURSEWARE_MESSAGES.keys()) + def test_courseware_messages(self, msg_key): + self._load_page('courseware', msg_key) + + @ddt.data('enrollment', 'courseware') + def test_invalid_message_key(self, access_point): + self._load_page(access_point, 'invalid', expected_status=404) + + def _load_page(self, access_point, message_key, expected_status=200): + """Load the message page and check the status code. """ + url = reverse('embargo_blocked_message', kwargs={ + 'access_point': access_point, + 'message_key': message_key + }) + response = self.client.get(url) + self.assertEqual( + response.status_code, expected_status, + msg=( + u"Unexpected status code when loading '{url}': " + u"expected {expected} but got {actual}" + ).format( + url=url, + expected=expected_status, + actual=response.status_code + ) + ) diff --git a/common/djangoapps/embargo/urls.py b/common/djangoapps/embargo/urls.py new file mode 100644 index 0000000000..4f67b7da31 --- /dev/null +++ b/common/djangoapps/embargo/urls.py @@ -0,0 +1,15 @@ +"""URLs served by the embargo app. """ + +from django.conf.urls import patterns, url + +from embargo.views import CourseAccessMessageView + + +urlpatterns = patterns( + 'embargo.views', + url( + r'^blocked-message/(?Penrollment|courseware)/(?P.+)/$', + CourseAccessMessageView.as_view(), + name='embargo_blocked_message', + ), +) diff --git a/common/djangoapps/embargo/views.py b/common/djangoapps/embargo/views.py new file mode 100644 index 0000000000..d18ec4e1cd --- /dev/null +++ b/common/djangoapps/embargo/views.py @@ -0,0 +1,68 @@ +"""Views served by the embargo app. """ + +from django.http import Http404 +from django.views.generic.base import View + +from edxmako.shortcuts import render_to_response + +from embargo import messages + + +class CourseAccessMessageView(View): + """Show a message explaining that the user was blocked from a course. """ + + ENROLLMENT_ACCESS_POINT = 'enrollment' + COURSEWARE_ACCESS_POINT = 'courseware' + + def get(self, request, access_point=None, message_key=None): + """Show a message explaining that the user was blocked. + + Arguments: + request (HttpRequest) + + Keyword Arguments: + access_point (str): Either 'enrollment' or 'courseware', + indicating how the user is trying to access the restricted + content. + + message_key (str): An identifier for which message to show. + See `embargo.messages` for more information. + + Returns: + HttpResponse + + Raises: + Http404: If no message is configured for the specified message key. + + """ + blocked_message = self._message(access_point, message_key) + + if blocked_message is None: + raise Http404 + + return render_to_response(blocked_message.template, {}) + + def _message(self, access_point, message_key): + """Retrieve message information. + + Arguments: + access_point (str): Either 'enrollment' or 'courseware' + message_key (str): The identifier for which message to show. + + Returns: + embargo.messages.BlockedMessage or None + + """ + message_dict = dict() + + # The access point determines which set of messages to use. + # This allows us to show different messages to students who + # are enrolling in a course than we show to students + # who are enrolled and accessing courseware. + if access_point == self.ENROLLMENT_ACCESS_POINT: + message_dict = messages.ENROLL_MESSAGES + elif access_point == self.COURSEWARE_ACCESS_POINT: + message_dict = messages.COURSEWARE_MESSAGES + + # Return the message corresponding to the given key if one is available. + return message_dict.get(message_key) diff --git a/lms/envs/common.py b/lms/envs/common.py index 5d6af295df..6f65fd182e 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -252,6 +252,10 @@ FEATURES = { # Toggles the embargo site functionality, which enable embargoing for the whole site 'SITE_EMBARGOED': False, + # Toggle whether to replace the current embargo implementation with + # the more flexible "country access" feature. + 'ENABLE_COUNTRY_ACCESS': False, + # Whether the Wiki subsystem should be accessible via the direct /wiki/ paths. Setting this to True means # that people can submit content and modify the Wiki in any arbitrary manner. We're leaving this as True in the # defaults, so that we maintain current behavior diff --git a/lms/templates/static_templates/courseware_access_block.html b/lms/templates/static_templates/courseware_access_block.html new file mode 100644 index 0000000000..9533bc722d --- /dev/null +++ b/lms/templates/static_templates/courseware_access_block.html @@ -0,0 +1,12 @@ +<%! from django.utils.translation import ugettext as _ %> +<%inherit file="../main.html" /> + +<%block name="pagetitle">${_("This Course Unavailable In Your Country")} + +
+

+${_("Our system indicates that you are trying to access this {platform_name} " + "course from a country or region in which it is not currently available." + ).format(platform_name=settings.PLATFORM_NAME)} +

+
diff --git a/lms/templates/static_templates/enrollment_access_block.html b/lms/templates/static_templates/enrollment_access_block.html new file mode 100644 index 0000000000..168cad5304 --- /dev/null +++ b/lms/templates/static_templates/enrollment_access_block.html @@ -0,0 +1,12 @@ +<%! from django.utils.translation import ugettext as _ %> +<%inherit file="../main.html" /> + +<%block name="pagetitle">${_("This Course Unavailable In Your Country")} + +
+

+${_("Our system indicates that you are trying to enroll in this {platform_name} " + "course from a country or region in which it is not currently available." + ).format(platform_name=settings.PLATFORM_NAME)} +

+
diff --git a/lms/urls.py b/lms/urls.py index 3f70e22d72..6a41de2399 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -484,6 +484,12 @@ urlpatterns += ( url(r'^shoppingcart/', include('shoppingcart.urls')), ) +# Country access (embargo) +if settings.FEATURES.get('ENABLE_COUNTRY_ACCESS'): + urlpatterns += ( + url(r'^embargo/', include('embargo.urls')), + ) + # Survey Djangoapp urlpatterns += ( url(r'^survey/', include('survey.urls')),