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.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
ACTIVE_POC_KEY = '_poc_id'
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user