Merge pull request #12190 from edx/aj/sust-24-globalstaff-course-access
Aj/sust 24 globalstaff course access
This commit is contained in:
@@ -39,7 +39,7 @@ from course_modes.tests.factories import CourseModeFactory
|
||||
from courseware.model_data import set_score
|
||||
from courseware.module_render import toc_for_course
|
||||
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.user_state_client import DjangoXBlockUserStateClient
|
||||
from courseware.views.index import render_accordion, CoursewareIndex
|
||||
@@ -196,7 +196,7 @@ class ViewsTestCase(ModuleStoreTestCase):
|
||||
|
||||
def setUp(self):
|
||||
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(
|
||||
category='chapter',
|
||||
parent_location=self.course.location,
|
||||
@@ -323,6 +323,105 @@ class ViewsTestCase(ModuleStoreTestCase):
|
||||
self.assertNotIn('Problem 1', 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")
|
||||
@patch.dict(settings.FEATURES, {'ENABLE_PAID_COURSE_REGISTRATION': True})
|
||||
def test_course_about_in_cart(self):
|
||||
|
||||
@@ -42,11 +42,23 @@ def get_redirect_url(course_key, usage_key):
|
||||
# Here we use the navigation_index from the position returned from
|
||||
# path_to_location - we can only navigate to the topmost vertical at the
|
||||
# moment
|
||||
|
||||
redirect_url = reverse(
|
||||
'courseware_position',
|
||||
args=(unicode(course_key), chapter, section, navigation_index(position))
|
||||
)
|
||||
|
||||
redirect_url += "?{}".format(urlencode({'activate_block_id': unicode(final_target_id)}))
|
||||
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
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.views.generic import View
|
||||
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
|
||||
import logging
|
||||
import newrelic.agent
|
||||
@@ -26,7 +28,9 @@ from opaque_keys.edx.keys import CourseKey
|
||||
from openedx.core.lib.gating import api as gating_api
|
||||
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
|
||||
from shoppingcart.models import CourseRegistrationCode
|
||||
from student.models import CourseEnrollment
|
||||
from student.views import is_course_blocked
|
||||
from student.roles import GlobalStaff
|
||||
from util.views import ensure_valid_course_key
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.x_module import STUDENT_VIEW
|
||||
@@ -89,6 +93,7 @@ class CoursewareIndex(View):
|
||||
self.section_url_name = section
|
||||
self.position = position
|
||||
self.chapter, self.section = None, None
|
||||
self.url = request.path
|
||||
|
||||
try:
|
||||
self._init_new_relic()
|
||||
@@ -221,6 +226,11 @@ class CoursewareIndex(View):
|
||||
self.effective_user,
|
||||
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)]))
|
||||
|
||||
def _redirect_if_needed_for_prereqs(self):
|
||||
|
||||
@@ -14,15 +14,18 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.models import User, AnonymousUser
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.context_processors import csrf
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.timezone import UTC
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.cache import cache_control
|
||||
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.generic import View
|
||||
from eventtracking import tracker
|
||||
from ipware.ip import get_ip
|
||||
from markupsafe import escape
|
||||
@@ -30,6 +33,7 @@ from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
from rest_framework import status
|
||||
from instructor.views.api import require_global_staff
|
||||
|
||||
import shoppingcart
|
||||
import survey.utils
|
||||
@@ -37,6 +41,7 @@ import survey.views
|
||||
from certificates import api as certs_api
|
||||
from openedx.core.djangoapps.models.course_details import CourseDetails
|
||||
from commerce.utils import EcommerceService
|
||||
from enrollment.api import add_enrollment
|
||||
from course_modes.models import CourseMode
|
||||
from courseware import grades
|
||||
from courseware.access import has_access, has_ccx_coach_role, _adjust_start_date_for_beta_testers
|
||||
@@ -57,7 +62,7 @@ from courseware.courses import (
|
||||
from courseware.masquerade import setup_masquerade
|
||||
from courseware.model_data import FieldDataCache, ScoresClient
|
||||
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 edxmako.shortcuts import render_to_response, render_to_string, marketing_link
|
||||
from instructor.enrollment import uses_shib
|
||||
@@ -72,6 +77,7 @@ from openedx.core.djangoapps.theming import helpers as theming_helpers
|
||||
from shoppingcart.utils import is_shopping_cart_enabled
|
||||
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
|
||||
from student.models import UserTestGroup, CourseEnrollment
|
||||
from student.roles import GlobalStaff
|
||||
from util.cache import cache, cache_if_anonymous
|
||||
from util.date_utils import strftime_localized
|
||||
from util.db import outer_atomic
|
||||
@@ -239,6 +245,11 @@ def jump_to(_request, course_id, location):
|
||||
raise Http404(u"Invalid course_key or usage_key")
|
||||
try:
|
||||
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:
|
||||
raise Http404(u"No data at this location: {0}".format(usage_key))
|
||||
except NoPathToItem:
|
||||
@@ -444,6 +455,63 @@ def get_cosmetic_display_price(course, registration_price):
|
||||
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
|
||||
@cache_if_anonymous()
|
||||
def course_about(request, course_id):
|
||||
|
||||
45
lms/templates/enroll_staff.html
Normal file
45
lms/templates/enroll_staff.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<%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
lms/urls.py
10
lms/urls.py
@@ -10,7 +10,7 @@ from django.conf.urls.static import static
|
||||
|
||||
from microsite_configuration import microsite
|
||||
import auth_exchange.views
|
||||
|
||||
from courseware.views.views import EnrollStaffView
|
||||
from config_models.views import ConfigurationModelCurrentAPIView
|
||||
from courseware.views.index import CoursewareIndex
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
@@ -366,6 +366,14 @@ urlpatterns += (
|
||||
name='about_course',
|
||||
),
|
||||
|
||||
url(
|
||||
r'^courses/{}/enroll_staff$'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
EnrollStaffView.as_view(),
|
||||
name='enroll_staff',
|
||||
),
|
||||
|
||||
#Inside the course
|
||||
url(
|
||||
r'^courses/{}/$'.format(
|
||||
|
||||
Reference in New Issue
Block a user