Commit 98b663dc by cewing

MIT: CCX. Implement mechanism for switching POC and MOOC views

Ensure there is a consistent key for the session dict that will store the value of the poc_id if one is active.  If none is active, the mooc is assumed to be active.

Add mooc_name, mooc_url and active to the dict for each poc membership.

Update the navigation template to show link to mooc if poc is active, or to poc if not.

Ensure the switch_active_pocs view is covered well with tests.
parent 591c75a3
......@@ -7,7 +7,9 @@ import threading
from contextlib import contextmanager
from courseware.courses import get_request_for_thread
from courseware.field_overrides import FieldOverrideProvider
from pocs import ACTIVE_POC_KEY
from .models import PocMembership, PocFieldOverride
......@@ -52,7 +54,11 @@ def poc_context(poc):
def get_current_poc(user):
"""
TODO Needs to look in user's session
Return the poc that is active for this user
The user's session data is used to look up the active poc by id
If no poc is active, None is returned and MOOC view will take precedence
Active poc can be overridden by context manager (see `poc_context`)
"""
# If poc context is explicitly set, that takes precedence over the user's
# session.
......@@ -60,16 +66,22 @@ def get_current_poc(user):
if poc:
return poc
# Temporary implementation. Final implementation will need to look in
# user's session so user can switch between (potentially multiple) POC and
# MOOC views. See courseware.courses.get_request_for_thread for idea to
# get at the request object.
try:
membership = PocMembership.objects.get(student=user, active=True)
return membership.poc
except PocMembership.DoesNotExist:
request = get_request_for_thread()
if request is None:
return None
poc = None
poc_id = request.session.get(ACTIVE_POC_KEY, None)
if poc_id is not None:
try:
membership = PocMembership.objects.get(
student=user, active=True, poc__id__exact=poc_id
)
poc = membership.poc
except PocMembership.DoesNotExist:
pass
return poc
def get_override_for_poc(poc, block, name, default=None):
"""
......
......@@ -23,6 +23,7 @@ from xmodule.modulestore.tests.factories import (
CourseFactory,
ItemFactory,
)
from pocs import ACTIVE_POC_KEY
from ..models import (
PersonalOnlineCourse,
PocMembership,
......@@ -539,6 +540,154 @@ class TestPocGrades(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.assertEqual(len(grades['section_breakdown']), 4)
class TestSwitchActivePoc(ModuleStoreTestCase, LoginEnrollmentTestCase):
"""Verify the view for switching which POC is active, if any
"""
def setUp(self):
self.course = course = CourseFactory.create()
coach = AdminFactory.create()
role = CoursePocCoachRole(course.id)
role.add_users(coach)
self.poc = PocFactory(course_id=course.id, coach=coach)
enrollment = CourseEnrollmentFactory.create(course_id=course.id)
self.user = enrollment.user
self.target_url = reverse(
'course_root', args=[course.id.to_deprecated_string()]
)
def register_user_in_poc(self, active=False):
"""create registration of self.user in self.poc
registration will be inactive unless active=True
"""
PocMembershipFactory(poc=self.poc, student=self.user, active=active)
def verify_active_poc(self, request, id=None):
if id:
id = str(id)
self.assertEqual(id, request.session.get(ACTIVE_POC_KEY, None))
def test_unauthorized_cannot_switch_to_poc(self):
switch_url = reverse(
'switch_active_poc',
args=[self.course.id.to_deprecated_string(), self.poc.id]
)
response = self.client.get(switch_url)
self.assertEqual(response.status_code, 302)
def test_unauthorized_cannot_switch_to_mooc(self):
switch_url = reverse(
'switch_active_poc',
args=[self.course.id.to_deprecated_string()]
)
response = self.client.get(switch_url)
self.assertEqual(response.status_code, 302)
def test_enrolled_inactive_user_cannot_select_poc(self):
self.register_user_in_poc(active=False)
self.client.login(username=self.user.username, password="test")
switch_url = reverse(
'switch_active_poc',
args=[self.course.id.to_deprecated_string(), self.poc.id]
)
response = self.client.get(switch_url)
self.assertEqual(response.status_code, 302)
self.assertTrue(response.get('Location', '').endswith(self.target_url))
# if the poc were active, we'd need to pass the ID of the poc here.
self.verify_active_poc(self.client)
def test_enrolled_user_can_select_poc(self):
self.register_user_in_poc(active=True)
self.client.login(username=self.user.username, password="test")
switch_url = reverse(
'switch_active_poc',
args=[self.course.id.to_deprecated_string(), self.poc.id]
)
response = self.client.get(switch_url)
self.assertEqual(response.status_code, 302)
self.assertTrue(response.get('Location', '').endswith(self.target_url))
self.verify_active_poc(self.client, self.poc.id)
def test_enrolled_user_can_select_mooc(self):
self.register_user_in_poc(active=True)
self.client.login(username=self.user.username, password="test")
# pre-seed the session with the poc id
session = self.client.session
session[ACTIVE_POC_KEY] = str(self.poc.id)
session.save()
switch_url = reverse(
'switch_active_poc',
args=[self.course.id.to_deprecated_string()]
)
response = self.client.get(switch_url)
self.assertEqual(response.status_code, 302)
self.assertTrue(response.get('Location', '').endswith(self.target_url))
self.verify_active_poc(self.client)
def test_unenrolled_user_cannot_select_poc(self):
self.client.login(username=self.user.username, password="test")
switch_url = reverse(
'switch_active_poc',
args=[self.course.id.to_deprecated_string(), self.poc.id]
)
response = self.client.get(switch_url)
self.assertEqual(response.status_code, 302)
self.assertTrue(response.get('Location', '').endswith(self.target_url))
# if the poc were active, we'd need to pass the ID of the poc here.
self.verify_active_poc(self.client)
def test_unenrolled_user_switched_to_mooc(self):
self.client.login(username=self.user.username, password="test")
# pre-seed the session with the poc id
session = self.client.session
session[ACTIVE_POC_KEY] = str(self.poc.id)
session.save()
switch_url = reverse(
'switch_active_poc',
args=[self.course.id.to_deprecated_string(), self.poc.id]
)
response = self.client.get(switch_url)
self.assertEqual(response.status_code, 302)
self.assertTrue(response.get('Location', '').endswith(self.target_url))
# we tried to select the poc but are not registered, so we are switched
# back to the mooc view
self.verify_active_poc(self.client)
def test_unassociated_course_and_poc_not_selected(self):
new_course = CourseFactory.create()
self.client.login(username=self.user.username, password="test")
expected_url = reverse(
'course_root', args=[new_course.id.to_deprecated_string()]
)
# the poc and the course are not related.
switch_url = reverse(
'switch_active_poc',
args=[new_course.id.to_deprecated_string(), self.poc.id]
)
response = self.client.get(switch_url)
self.assertEqual(response.status_code, 302)
self.assertTrue(response.get('Location', '').endswith(expected_url))
# the mooc should be active
self.verify_active_poc(self.client)
def test_missing_poc_cannot_be_selected(self):
self.register_user_in_poc()
self.client.login(username=self.user.username, password="test")
switch_url = reverse(
'switch_active_poc',
args=[self.course.id.to_deprecated_string(), self.poc.id]
)
# delete the poc
self.poc.delete()
response = self.client.get(switch_url)
self.assertEqual(response.status_code, 302)
self.assertTrue(response.get('Location', '').endswith(self.target_url))
# we tried to select the poc it doesn't exist anymore, so we are
# switched back to the mooc view
self.verify_active_poc(self.client)
def flatten(seq):
"""
For [[1, 2], [3, 4]] returns [1, 2, 3, 4]. Does not recurse.
......
......@@ -17,12 +17,12 @@ from opaque_keys.edx.locations import SlashSeparatedCourseKey
from microsite_configuration import microsite
from pocs.models import (
from .models import (
PersonalOnlineCourse,
PocMembership,
PocFutureMembership,
)
from .overrides import get_current_poc
class EmailEnrollmentState(object):
""" Store the complete enrollment state of an email in a class """
......@@ -225,8 +225,12 @@ def get_all_pocs_for_user(user):
Returns a list of dicts: {
poc_name: <formatted title of POC course>
poc_url: <url to view this POC>
poc_active: True if this poc is currently the 'active' one
mooc_name: <formatted title of the MOOC course for this POC>
mooc_url: <url to view this MOOC>
}
"""
current_active_poc = get_current_poc(user)
if user.is_anonymous():
return []
active_poc_memberships = PocMembership.objects.filter(
......@@ -235,13 +239,22 @@ def get_all_pocs_for_user(user):
memberships = []
for membership in active_poc_memberships:
course = get_course_by_id(membership.poc.course_id)
title = 'POC: {}'.format(get_course_about_section(course, 'title'))
course_title = get_course_about_section(course, 'title')
poc_title = 'POC: {}'.format(course_title)
mooc_title = 'MOOC: {}'.format(course_title)
url = reverse(
'switch_active_poc',
args=[course.id.to_deprecated_string(), membership.poc.id]
)
mooc_url = reverse(
'switch_active_poc',
args=[course.id.to_deprecated_string(),]
)
memberships.append({
'poc_name': title,
'poc_url': url
'poc_name': poc_title,
'poc_url': url,
'active': membership.poc == current_active_poc,
'mooc_name': mooc_title,
'mooc_url': mooc_url,
})
return memberships
......@@ -12,15 +12,21 @@ from copy import deepcopy
from cStringIO import StringIO
from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponseForbidden
from django.http import (
HttpResponse,
HttpResponseForbidden,
HttpResponseRedirect,
)
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
from django.views.decorators.cache import cache_control
from django_future.csrf import ensure_csrf_cookie
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from courseware.courses import get_course
from courseware.courses import get_course_by_id
from courseware.field_overrides import disable_overrides
from courseware.grades import iterate_grades_for
......@@ -41,7 +47,13 @@ from .overrides import (
override_field_for_poc,
poc_context,
)
from .utils import enroll_email, unenroll_email
from .utils import (
enroll_email,
unenroll_email,
get_all_pocs_for_user,
)
from pocs import ACTIVE_POC_KEY
log = logging.getLogger(__name__)
TODAY = datetime.datetime.today # for patching in tests
......@@ -403,7 +415,6 @@ def poc_grades_csv(request, course):
course.id, request.user, course, depth=2)
course = get_module_for_descriptor(
request.user, request, course, field_data_cache, course.id)
poc = get_poc_for_coach(course, request.user)
with poc_context(poc):
# The grading policy for the MOOC is probably already cached. We need
......@@ -446,3 +457,38 @@ def poc_grades_csv(request, course):
writer.writerow(row)
return HttpResponse(buf.getvalue(), content_type='text/plain')
@login_required
def swich_active_poc(request, course_id, poc_id=None):
"""set the active POC for the logged-in user
"""
user = request.user
if not user.is_authenticated():
return HttpResponseForbidden(
_('Only registered students may change POC views.')
)
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
# will raise Http404 if course_id is bad
course = get_course_by_id(course_key)
course_url = reverse(
'course_root', args=[course.id.to_deprecated_string()]
)
if poc_id is not None:
try:
requested_poc = PersonalOnlineCourse.objects.get(pk=poc_id)
assert requested_poc.course_id.to_deprecated_string() == course_id
if not PocMembership.objects.filter(
poc=requested_poc, student=request.user, active=True
).exists():
poc_id = None
except PersonalOnlineCourse.DoesNotExist:
# what to do here? Log the failure? Do we care?
poc_id = None
except AssertionError:
# what to do here? Log the failure? Do we care?
poc_id = None
request.session[ACTIVE_POC_KEY] = poc_id
return HttpResponseRedirect(course_url)
......@@ -13,6 +13,7 @@ from status.status import get_site_status_msg
<%! from microsite_configuration import microsite %>
<%! from microsite_configuration.templatetags.microsite import platform_name %>
<%! from pocs.utils import get_all_pocs_for_user %>
## Provide a hook for themes to inject branding on top.
<%block name="navigation_top" />
......@@ -82,6 +83,15 @@ site_status_msg = get_site_status_msg(course_id)
% if settings.MKTG_URL_LINK_MAP.get('FAQ'):
<li><a href="${marketing_link('FAQ')}">${_("Help")}</a></li>
% endif
% if settings.FEATURES.get('PERSONAL_ONLINE_COURSES', False):
%for poc in get_all_pocs_for_user(user):
% if poc['active']:
<li><a href="${poc['mooc_url']}" role="menuitem">${poc['mooc_name']}</a></li>
% else:
<li><a href="${poc['poc_url']}" role="menuitem">${poc['poc_name']}</a></li>
% endif
%endfor
% endif
</%block>
<li><a href="${reverse('logout')}" role="menuitem">${_("Sign out")}</a></li>
</ul>
......
......@@ -359,6 +359,8 @@ if settings.COURSEWARE_ENABLED:
'pocs.views.poc_grades_csv', name='poc_grades_csv'),
url(r'^courses/{}/poc_set_grading_policy$'.format(settings.COURSE_ID_PATTERN),
'pocs.views.set_grading_policy', name='poc_set_grading_policy'),
url(r'^courses/{}/swich_poc(?:/(?P<poc_id>[\d]+))?$'.format(settings.COURSE_ID_PATTERN),
'pocs.views.swich_active_poc', name='switch_active_poc'),
url(r'^courses/{}/set_course_mode_price$'.format(settings.COURSE_ID_PATTERN),
'instructor.views.instructor_dashboard.set_course_mode_price', name="set_course_mode_price"),
url(r'^courses/{}/instructor/api/'.format(settings.COURSE_ID_PATTERN),
......
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