Commit 2c5319e4 by attiyaIshaque

Merge pull request #12190 from edx/aj/sust-24-globalstaff-course-access

Aj/sust 24 globalstaff course access
parents 7b34f431 48e7fc81
...@@ -39,7 +39,7 @@ from course_modes.tests.factories import CourseModeFactory ...@@ -39,7 +39,7 @@ from course_modes.tests.factories import CourseModeFactory
from courseware.model_data import set_score from courseware.model_data import set_score
from courseware.module_render import toc_for_course from courseware.module_render import toc_for_course
from courseware.testutils import RenderXBlockTestMixin from courseware.testutils import RenderXBlockTestMixin
from courseware.tests.factories import StudentModuleFactory from courseware.tests.factories import StudentModuleFactory, GlobalStaffFactory
from courseware.url_helpers import get_redirect_url from courseware.url_helpers import get_redirect_url
from courseware.user_state_client import DjangoXBlockUserStateClient from courseware.user_state_client import DjangoXBlockUserStateClient
from courseware.views.index import render_accordion, CoursewareIndex from courseware.views.index import render_accordion, CoursewareIndex
...@@ -196,7 +196,7 @@ class ViewsTestCase(ModuleStoreTestCase): ...@@ -196,7 +196,7 @@ class ViewsTestCase(ModuleStoreTestCase):
def setUp(self): def setUp(self):
super(ViewsTestCase, self).setUp() super(ViewsTestCase, self).setUp()
self.course = CourseFactory.create(display_name=u'teꜱᴛ course') self.course = CourseFactory.create(display_name=u'teꜱᴛ course', run="Testing_course")
self.chapter = ItemFactory.create( self.chapter = ItemFactory.create(
category='chapter', category='chapter',
parent_location=self.course.location, parent_location=self.course.location,
...@@ -323,6 +323,105 @@ class ViewsTestCase(ModuleStoreTestCase): ...@@ -323,6 +323,105 @@ class ViewsTestCase(ModuleStoreTestCase):
self.assertNotIn('Problem 1', response.content) self.assertNotIn('Problem 1', response.content)
self.assertNotIn('Problem 2', response.content) self.assertNotIn('Problem 2', response.content)
def _create_global_staff_user(self):
"""
Create global staff user and log them in
"""
self.global_staff = GlobalStaffFactory.create() # pylint: disable=attribute-defined-outside-init
self.client.login(username=self.global_staff.username, password='test')
def _create_url_for_enroll_staff(self):
"""
creates the courseware url and enroll staff url
"""
# create the _next parameter
courseware_url = reverse(
'courseware_section',
kwargs={
'course_id': unicode(self.course_key),
'chapter': unicode(self.chapter.location.name),
'section': unicode(self.section.location.name),
}
)
# create the url for enroll_staff view
enroll_url = "{enroll_url}?next={courseware_url}".format(
enroll_url=reverse('enroll_staff', kwargs={'course_id': unicode(self.course.id)}),
courseware_url=courseware_url
)
return courseware_url, enroll_url
@ddt.data(
({'enroll': "Enroll"}, True),
({'dont_enroll': "Don't enroll"}, False))
@ddt.unpack
def test_enroll_staff_redirection(self, data, enrollment):
"""
Verify unenrolled staff is redirected to correct url.
"""
self._create_global_staff_user()
courseware_url, enroll_url = self._create_url_for_enroll_staff()
response = self.client.post(enroll_url, data=data, follow=True)
self.assertEqual(response.status_code, 200)
# we were redirected to our current location
self.assertIn(302, response.redirect_chain[0])
self.assertEqual(len(response.redirect_chain), 1)
if enrollment:
self.assertRedirects(response, courseware_url)
else:
self.assertRedirects(response, '/courses/{}/about'.format(unicode(self.course_key)))
def test_enroll_staff_with_invalid_data(self):
"""
If we try to post with an invalid data pattern, then we'll redirected to
course about page.
"""
self._create_global_staff_user()
__, enroll_url = self._create_url_for_enroll_staff()
response = self.client.post(enroll_url, data={'test': "test"})
self.assertEqual(response.status_code, 302)
self.assertRedirects(response, '/courses/{}/about'.format(unicode(self.course_key)))
def test_courseware_redirection(self):
"""
Tests that a global staff member is redirected to the staff enrollment page.
Un-enrolled Staff user should be redirected to the staff enrollment page accessing courseware,
user chooses to enroll in the course. User is enrolled and redirected to the requested url.
Scenario:
1. Un-enrolled staff tries to access any course vertical (courseware url).
2. User is redirected to the staff enrollment page.
3. User chooses to enroll in the course.
4. User is enrolled in the course and redirected to the requested courseware url.
"""
self._create_global_staff_user()
courseware_url, enroll_url = self._create_url_for_enroll_staff()
# Accessing the courseware url in which not enrolled & redirected to staff enrollment page
response = self.client.get(courseware_url, follow=True)
self.assertEqual(response.status_code, 200)
self.assertIn(302, response.redirect_chain[0])
self.assertEqual(len(response.redirect_chain), 1)
self.assertRedirects(response, enroll_url)
# Accessing the enroll staff url and verify the correct url
response = self.client.get(enroll_url)
self.assertEqual(response.status_code, 200)
response_content = response.content
self.assertIn('Enroll', response_content)
self.assertIn("dont_enroll", response_content)
# Post the valid data to enroll the staff in the course
response = self.client.post(enroll_url, data={'enroll': "Enroll"}, follow=True)
self.assertEqual(response.status_code, 200)
self.assertIn(302, response.redirect_chain[0])
self.assertEqual(len(response.redirect_chain), 1)
self.assertRedirects(response, courseware_url)
# Verify staff has been enrolled to the given course
self.assertTrue(CourseEnrollment.is_enrolled(self.global_staff, self.course.id))
@unittest.skipUnless(settings.FEATURES.get('ENABLE_SHOPPING_CART'), "Shopping Cart not enabled in settings") @unittest.skipUnless(settings.FEATURES.get('ENABLE_SHOPPING_CART'), "Shopping Cart not enabled in settings")
@patch.dict(settings.FEATURES, {'ENABLE_PAID_COURSE_REGISTRATION': True}) @patch.dict(settings.FEATURES, {'ENABLE_PAID_COURSE_REGISTRATION': True})
def test_course_about_in_cart(self): def test_course_about_in_cart(self):
......
...@@ -42,11 +42,23 @@ def get_redirect_url(course_key, usage_key): ...@@ -42,11 +42,23 @@ def get_redirect_url(course_key, usage_key):
# Here we use the navigation_index from the position returned from # Here we use the navigation_index from the position returned from
# path_to_location - we can only navigate to the topmost vertical at the # path_to_location - we can only navigate to the topmost vertical at the
# moment # moment
redirect_url = reverse( redirect_url = reverse(
'courseware_position', 'courseware_position',
args=(unicode(course_key), chapter, section, navigation_index(position)) args=(unicode(course_key), chapter, section, navigation_index(position))
) )
redirect_url += "?{}".format(urlencode({'activate_block_id': unicode(final_target_id)})) redirect_url += "?{}".format(urlencode({'activate_block_id': unicode(final_target_id)}))
return redirect_url return redirect_url
def get_redirect_url_for_global_staff(course_key, _next):
"""
Returns the redirect url for staff enrollment
Args:
course_key(str): Course key string
_next(str): Redirect url of course component
"""
redirect_url = ("{url}?next={redirect}".format(
url=reverse('enroll_staff', args=[unicode(course_key)]),
redirect=_next))
return redirect_url
...@@ -15,6 +15,8 @@ from django.views.decorators.cache import cache_control ...@@ -15,6 +15,8 @@ from django.views.decorators.cache import cache_control
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.generic import View from django.views.generic import View
from django.shortcuts import redirect from django.shortcuts import redirect
from courseware.url_helpers import get_redirect_url_for_global_staff
from edxmako.shortcuts import render_to_response, render_to_string from edxmako.shortcuts import render_to_response, render_to_string
import logging import logging
import newrelic.agent import newrelic.agent
...@@ -26,7 +28,9 @@ from opaque_keys.edx.keys import CourseKey ...@@ -26,7 +28,9 @@ from opaque_keys.edx.keys import CourseKey
from openedx.core.lib.gating import api as gating_api from openedx.core.lib.gating import api as gating_api
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
from shoppingcart.models import CourseRegistrationCode from shoppingcart.models import CourseRegistrationCode
from student.models import CourseEnrollment
from student.views import is_course_blocked from student.views import is_course_blocked
from student.roles import GlobalStaff
from util.views import ensure_valid_course_key from util.views import ensure_valid_course_key
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.x_module import STUDENT_VIEW from xmodule.x_module import STUDENT_VIEW
...@@ -89,6 +93,7 @@ class CoursewareIndex(View): ...@@ -89,6 +93,7 @@ class CoursewareIndex(View):
self.section_url_name = section self.section_url_name = section
self.position = position self.position = position
self.chapter, self.section = None, None self.chapter, self.section = None, None
self.url = request.path
try: try:
self._init_new_relic() self._init_new_relic()
...@@ -221,6 +226,11 @@ class CoursewareIndex(View): ...@@ -221,6 +226,11 @@ class CoursewareIndex(View):
self.effective_user, self.effective_user,
unicode(self.course.id) unicode(self.course.id)
) )
user_is_global_staff = GlobalStaff().has_user(self.effective_user)
user_is_enrolled = CourseEnrollment.is_enrolled(self.effective_user, self.course_key)
if user_is_global_staff and not user_is_enrolled:
redirect_url = get_redirect_url_for_global_staff(self.course_key, _next=self.url)
raise Redirect(redirect_url)
raise Redirect(reverse('about_course', args=[unicode(self.course.id)])) raise Redirect(reverse('about_course', args=[unicode(self.course.id)]))
def _redirect_if_needed_for_prereqs(self): def _redirect_if_needed_for_prereqs(self):
......
...@@ -14,15 +14,18 @@ from django.contrib.auth.decorators import login_required ...@@ -14,15 +14,18 @@ from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User, AnonymousUser from django.contrib.auth.models import User, AnonymousUser
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.context_processors import csrf
from django.db import transaction from django.db import transaction
from django.db.models import Q from django.db.models import Q
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import redirect from django.shortcuts import redirect
from django.utils.decorators import method_decorator
from django.utils.timezone import UTC from django.utils.timezone import UTC
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views.decorators.cache import cache_control from django.views.decorators.cache import cache_control
from django.views.decorators.csrf import ensure_csrf_cookie from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.http import require_GET, require_POST, require_http_methods from django.views.decorators.http import require_GET, require_POST, require_http_methods
from django.views.generic import View
from eventtracking import tracker from eventtracking import tracker
from ipware.ip import get_ip from ipware.ip import get_ip
from markupsafe import escape from markupsafe import escape
...@@ -30,6 +33,7 @@ from opaque_keys import InvalidKeyError ...@@ -30,6 +33,7 @@ from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey, UsageKey from opaque_keys.edx.keys import CourseKey, UsageKey
from opaque_keys.edx.locations import SlashSeparatedCourseKey from opaque_keys.edx.locations import SlashSeparatedCourseKey
from rest_framework import status from rest_framework import status
from instructor.views.api import require_global_staff
import shoppingcart import shoppingcart
import survey.utils import survey.utils
...@@ -37,6 +41,7 @@ import survey.views ...@@ -37,6 +41,7 @@ import survey.views
from certificates import api as certs_api from certificates import api as certs_api
from openedx.core.djangoapps.models.course_details import CourseDetails from openedx.core.djangoapps.models.course_details import CourseDetails
from commerce.utils import EcommerceService from commerce.utils import EcommerceService
from enrollment.api import add_enrollment
from course_modes.models import CourseMode from course_modes.models import CourseMode
from courseware import grades from courseware import grades
from courseware.access import has_access, has_ccx_coach_role, _adjust_start_date_for_beta_testers from courseware.access import has_access, has_ccx_coach_role, _adjust_start_date_for_beta_testers
...@@ -57,7 +62,7 @@ from courseware.courses import ( ...@@ -57,7 +62,7 @@ from courseware.courses import (
from courseware.masquerade import setup_masquerade from courseware.masquerade import setup_masquerade
from courseware.model_data import FieldDataCache, ScoresClient from courseware.model_data import FieldDataCache, ScoresClient
from courseware.models import StudentModule, BaseStudentModuleHistory from courseware.models import StudentModule, BaseStudentModuleHistory
from courseware.url_helpers import get_redirect_url from courseware.url_helpers import get_redirect_url, get_redirect_url_for_global_staff
from courseware.user_state_client import DjangoXBlockUserStateClient from courseware.user_state_client import DjangoXBlockUserStateClient
from edxmako.shortcuts import render_to_response, render_to_string, marketing_link from edxmako.shortcuts import render_to_response, render_to_string, marketing_link
from instructor.enrollment import uses_shib from instructor.enrollment import uses_shib
...@@ -72,6 +77,7 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers ...@@ -72,6 +77,7 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers
from shoppingcart.utils import is_shopping_cart_enabled from shoppingcart.utils import is_shopping_cart_enabled
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
from student.models import UserTestGroup, CourseEnrollment from student.models import UserTestGroup, CourseEnrollment
from student.roles import GlobalStaff
from util.cache import cache, cache_if_anonymous from util.cache import cache, cache_if_anonymous
from util.date_utils import strftime_localized from util.date_utils import strftime_localized
from util.db import outer_atomic from util.db import outer_atomic
...@@ -239,6 +245,11 @@ def jump_to(_request, course_id, location): ...@@ -239,6 +245,11 @@ def jump_to(_request, course_id, location):
raise Http404(u"Invalid course_key or usage_key") raise Http404(u"Invalid course_key or usage_key")
try: try:
redirect_url = get_redirect_url(course_key, usage_key) redirect_url = get_redirect_url(course_key, usage_key)
user = _request.user
user_is_global_staff = GlobalStaff().has_user(user)
user_is_enrolled = CourseEnrollment.is_enrolled(user, course_key)
if user_is_global_staff and not user_is_enrolled:
redirect_url = get_redirect_url_for_global_staff(course_key, _next=redirect_url)
except ItemNotFoundError: except ItemNotFoundError:
raise Http404(u"No data at this location: {0}".format(usage_key)) raise Http404(u"No data at this location: {0}".format(usage_key))
except NoPathToItem: except NoPathToItem:
...@@ -444,6 +455,63 @@ def get_cosmetic_display_price(course, registration_price): ...@@ -444,6 +455,63 @@ def get_cosmetic_display_price(course, registration_price):
return _('Free') return _('Free')
class EnrollStaffView(View):
"""
Displays view for registering in the course to a global staff user.
User can either choose to 'Enroll' or 'Don't Enroll' in the course.
Enroll: Enrolls user in course and redirects to the courseware.
Don't Enroll: Redirects user to course about page.
Arguments:
- request : HTTP request
- course_id : course id
Returns:
- RedirectResponse
"""
template_name = 'enroll_staff.html'
@method_decorator(require_global_staff)
@method_decorator(ensure_valid_course_key)
def get(self, request, course_id):
"""
Display enroll staff view to global staff user with `Enroll` and `Don't Enroll` options.
"""
user = request.user
course_key = CourseKey.from_string(course_id)
with modulestore().bulk_operations(course_key):
course = get_course_with_access(user, 'load', course_key)
if not registered_for_course(course, user):
context = {
'course': course,
'csrftoken': csrf(request)["csrf_token"]
}
return render_to_response(self.template_name, context)
@method_decorator(require_global_staff)
@method_decorator(ensure_valid_course_key)
def post(self, request, course_id):
"""
Either enrolls the user in course or redirects user to course about page
depending upon the option (Enroll, Don't Enroll) chosen by the user.
"""
_next = urllib.quote_plus(request.GET.get('next', 'info'), safe='/:?=')
course_key = CourseKey.from_string(course_id)
enroll = 'enroll' in request.POST
if enroll:
add_enrollment(request.user.username, course_id)
log.info(
u"User %s enrolled in %s via `enroll_staff` view",
request.user.username,
course_id
)
return redirect(_next)
# In any other case redirect to the course about page.
return redirect(reverse('about_course', args=[unicode(course_key)]))
@ensure_csrf_cookie @ensure_csrf_cookie
@cache_if_anonymous() @cache_if_anonymous()
def course_about(request, course_id): def course_about(request, course_id):
......
<%page expression_filter="h" />
<%inherit file="main.html" />
<%namespace name='static' file='static_content.html'/>
<%!
from django.utils.translation import ugettext as _
from courseware.courses import get_course_about_section
%>
<%block name="headextra">
<meta property="og:title" content="${course.display_name_with_default}"/>
<meta property="og:description" content="${get_course_about_section(request, course, 'short_description')}"/>
</%block>
<%block name="pagetitle">${course.display_name_with_default}</%block>
<main id="main" aria-label="Content" tabindex="-1">
<div class="course-info">
<header class="course-profile">
<div class="intro-inner-wrapper">
<div class="table">
<div class="intro">
<div class="heading-group">
<h3> ${_("You should Register before trying to access the Unit")}</h3>
</div>
<div class="heading-group">
<p>
${course.display_name_with_default}
</p>
</div>
<form role="form" id="enroll_staff_form" method="post" action="">
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrftoken }"/>
<div class="main-cta">
<button class="register" name="enroll" type="submit"
value="${_('Enroll')}">${_('Enroll')}<span
class="sr">${course.display_name_with_default}</span></button>
<button class="register" name="dont_enroll" type="submit"
value="${_('Don\'t enroll')}">${_('Don\'t enroll')}<span
class="sr">${course.display_name_with_default}</span></button>
</div>
</form>
</div>
</div>
</div>
</header>
</div>
</main>
...@@ -10,7 +10,7 @@ from django.conf.urls.static import static ...@@ -10,7 +10,7 @@ from django.conf.urls.static import static
from microsite_configuration import microsite from microsite_configuration import microsite
import auth_exchange.views import auth_exchange.views
from courseware.views.views import EnrollStaffView
from config_models.views import ConfigurationModelCurrentAPIView from config_models.views import ConfigurationModelCurrentAPIView
from courseware.views.index import CoursewareIndex from courseware.views.index import CoursewareIndex
from openedx.core.djangoapps.programs.models import ProgramsApiConfig from openedx.core.djangoapps.programs.models import ProgramsApiConfig
...@@ -366,6 +366,14 @@ urlpatterns += ( ...@@ -366,6 +366,14 @@ urlpatterns += (
name='about_course', name='about_course',
), ),
url(
r'^courses/{}/enroll_staff$'.format(
settings.COURSE_ID_PATTERN,
),
EnrollStaffView.as_view(),
name='enroll_staff',
),
#Inside the course #Inside the course
url( url(
r'^courses/{}/$'.format( r'^courses/{}/$'.format(
......
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