SUST-24 Initial commit
Add bokchoy test and changes in html
This commit is contained in:
committed by
attiyaishaque
parent
7b34f4317d
commit
3fb6592443
@@ -286,3 +286,23 @@ class CoursewareSequentialTabPage(CoursePage):
|
||||
return the body of the sequential currently selected
|
||||
"""
|
||||
return self.q(css='#seq_content .xblock').text[0]
|
||||
|
||||
|
||||
class AboutPage(CoursePage):
|
||||
"""
|
||||
Course about.
|
||||
"""
|
||||
|
||||
url_path = "about/"
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='.intro').present
|
||||
|
||||
|
||||
@property
|
||||
def is_register_button_present(self):
|
||||
"""
|
||||
Returns True if the timed/proctored exam timer bar is visible on the courseware.
|
||||
"""
|
||||
from nose.tools import set_trace; set_trace()
|
||||
return self.q(css=".register").is_present()
|
||||
|
||||
@@ -5,6 +5,7 @@ Studio Home page
|
||||
from bok_choy.page_object import PageObject
|
||||
from . import BASE_URL
|
||||
from selenium.webdriver import ActionChains
|
||||
from ..common.utils import click_css
|
||||
|
||||
|
||||
class DashboardPage(PageObject):
|
||||
@@ -12,11 +13,24 @@ class DashboardPage(PageObject):
|
||||
Studio Home page
|
||||
"""
|
||||
|
||||
def __init__(self, browser):
|
||||
"""
|
||||
Initialize the page.
|
||||
"""
|
||||
super(DashboardPage, self).__init__(browser)
|
||||
url = BASE_URL + "/course/"
|
||||
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='.content-primary').visible
|
||||
|
||||
def mouse_hover(self, element):
|
||||
"""
|
||||
Mouse over on given element.
|
||||
"""
|
||||
mouse_hover_action = ActionChains(self.browser).move_to_element(element)
|
||||
mouse_hover_action.perform()
|
||||
|
||||
@property
|
||||
def course_runs(self):
|
||||
"""
|
||||
@@ -48,6 +62,16 @@ class DashboardPage(PageObject):
|
||||
# Clicking on course with run will trigger an ajax event
|
||||
self.wait_for_ajax()
|
||||
|
||||
|
||||
def view_live(self, element):
|
||||
"""
|
||||
Clicks the "View Live" link and switches to the new tab
|
||||
"""
|
||||
self.mouse_hover(self.browser.find_element_by_css_selector('.view-button'))
|
||||
click_css(self, '.view-button', require_notification=False)
|
||||
self.browser.switch_to_window(self.browser.window_handles[-1])
|
||||
click_css(self, element, require_notification=False)
|
||||
|
||||
def has_new_library_button(self):
|
||||
"""
|
||||
(bool) is the "New Library" button present?
|
||||
|
||||
@@ -11,6 +11,7 @@ from ...pages.studio.overview import CourseOutlinePage
|
||||
from ...pages.studio.utils import verify_ordering
|
||||
|
||||
|
||||
|
||||
class StudioCourseTest(UniqueCourseTest):
|
||||
"""
|
||||
Base class for all Studio course tests.
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
"""
|
||||
Acceptance tests for Home Page (My Courses / My Libraries).
|
||||
"""
|
||||
import os
|
||||
import uuid
|
||||
|
||||
from bok_choy.web_app_test import WebAppTest
|
||||
from common.test.acceptance.pages.common.logout import LogoutPage
|
||||
from common.test.acceptance.pages.lms.courseware import AboutPage, CoursewarePage
|
||||
from flaky import flaky
|
||||
from opaque_keys.edx.locator import LibraryLocator
|
||||
from opaque_keys.edx.locator import LibraryLocator, CourseLocator
|
||||
from uuid import uuid4
|
||||
|
||||
from ...fixtures import PROGRAMS_STUB_URL
|
||||
@@ -173,3 +178,74 @@ class StudioLanguageTest(WebAppTest):
|
||||
get_selected_option_text(language_selector),
|
||||
u'Dummy Language (Esperanto)'
|
||||
)
|
||||
|
||||
class CourseNotEnrollTest(WebAppTest):
|
||||
"""
|
||||
Test that we can create a new content library on the studio home page.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Load the helper for the home page (dashboard page)
|
||||
"""
|
||||
super(CourseNotEnrollTest, self).setUp()
|
||||
|
||||
self.auth_page = AutoAuthPage(self.browser, staff=True)
|
||||
self.dashboard_page = DashboardPage(self.browser)
|
||||
self.course_name = "New Course Name"
|
||||
self.course_org = "orgX"
|
||||
self.course_number = str(uuid.uuid4().get_hex().upper()[0:6])
|
||||
self.course_run = "2015_T2"
|
||||
|
||||
def course_id(self):
|
||||
"""
|
||||
Returns the serialized course_key for the test
|
||||
"""
|
||||
# TODO - is there a better way to make this agnostic to the underlying default module store?
|
||||
default_store = os.environ.get('DEFAULT_STORE', 'draft')
|
||||
course_key = CourseLocator(
|
||||
self.course_org,
|
||||
self.course_number,
|
||||
self.course_run,
|
||||
deprecated=(default_store == 'draft')
|
||||
)
|
||||
return unicode(course_key)
|
||||
|
||||
def test_unenroll_course(self):
|
||||
"""
|
||||
From the home page:
|
||||
Click "New Course" ,Fill out the form
|
||||
Submit the form
|
||||
Return to the home page and logout
|
||||
Login with the staff user which is not enrolled in the course
|
||||
click the view live button of the course
|
||||
Here are two scenario:
|
||||
First click the continue button
|
||||
Second click the Enroll button and see the response.
|
||||
"""
|
||||
self.auth_page.visit()
|
||||
self.dashboard_page.visit()
|
||||
self.assertTrue(self.dashboard_page.new_course_button.present)
|
||||
|
||||
self.dashboard_page.click_new_course_button()
|
||||
self.assertTrue(self.dashboard_page.is_new_course_form_visible())
|
||||
self.dashboard_page.fill_new_course_form(
|
||||
self.course_name, self.course_org, self.course_number, self.course_run
|
||||
)
|
||||
self.assertTrue(self.dashboard_page.is_new_course_form_valid())
|
||||
self.dashboard_page.submit_new_course_form()
|
||||
|
||||
LogoutPage(self.browser).visit()
|
||||
AutoAuthPage(self.browser, course_id=None, staff=True).visit()
|
||||
|
||||
self.dashboard_page.visit()
|
||||
self.dashboard_page.view_live('.submit>input:last-child')
|
||||
about_page = AboutPage(self.browser, self.course_id)
|
||||
about_page.wait_for_page()
|
||||
self.assertTrue(about_page.is_register_button_present)
|
||||
|
||||
self.dashboard_page.visit()
|
||||
self.dashboard_page.view_live('.submit>input:first-child')
|
||||
courseware = CoursewarePage(self.browser, self.course_id)
|
||||
courseware.wait_for_page()
|
||||
self.assertTrue(courseware.is_browser_on_page())
|
||||
@@ -1449,6 +1449,14 @@ class DefaultStatesContentTest(CourseOutlineTest):
|
||||
self.assertEqual(courseware.xblock_component_type(1), 'html')
|
||||
self.assertEqual(courseware.xblock_component_type(2), 'discussion')
|
||||
|
||||
def test_unenroll_course(self):
|
||||
from nose.tools import set_trace; set_trace()
|
||||
self.course_outline_page.visit()
|
||||
self.course_outline_page.view_live()
|
||||
courseware = CoursewarePage(self.browser, self.course_id)
|
||||
courseware.wait_for_page()
|
||||
self.assertEqual(courseware.num_xblock_components, 3)
|
||||
|
||||
|
||||
@attr('shard_3')
|
||||
class UnitNavigationTest(CourseOutlineTest):
|
||||
|
||||
@@ -222,7 +222,6 @@ class GeneratedCertificate(models.Model):
|
||||
MODES = Choices('verified', 'honor', 'audit', 'professional', 'no-id-professional')
|
||||
|
||||
VERIFIED_CERTS_MODES = [CourseMode.VERIFIED, CourseMode.CREDIT_MODE]
|
||||
|
||||
user = models.ForeignKey(User)
|
||||
course_id = CourseKeyField(max_length=255, blank=True, default=None)
|
||||
verify_uuid = models.CharField(max_length=32, blank=True, default='', db_index=True)
|
||||
|
||||
@@ -30,6 +30,8 @@ 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 xblock.fragment import Fragment
|
||||
from instructor.views.api import require_global_staff
|
||||
|
||||
import shoppingcart
|
||||
import survey.utils
|
||||
@@ -72,6 +74,10 @@ 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 student.views import is_course_blocked
|
||||
|
||||
from util.cache import cache, cache_if_anonymous
|
||||
from util.date_utils import strftime_localized
|
||||
from util.db import outer_atomic
|
||||
@@ -195,6 +201,389 @@ def get_current_child(xmodule, min_depth=None, requested_child=None):
|
||||
return child
|
||||
|
||||
|
||||
def redirect_to_course_position(course_module, content_depth):
|
||||
"""
|
||||
Return a redirect to the user's current place in the course.
|
||||
|
||||
If this is the user's first time, redirects to COURSE/CHAPTER/SECTION.
|
||||
If this isn't the users's first time, redirects to COURSE/CHAPTER,
|
||||
and the view will find the current section and display a message
|
||||
about reusing the stored position.
|
||||
|
||||
If there is no current position in the course or chapter, then selects
|
||||
the first child.
|
||||
|
||||
"""
|
||||
urlargs = {'course_id': course_module.id.to_deprecated_string()}
|
||||
chapter = get_current_child(course_module, min_depth=content_depth)
|
||||
if chapter is None:
|
||||
# oops. Something bad has happened.
|
||||
raise Http404("No chapter found when loading current position in course")
|
||||
|
||||
urlargs['chapter'] = chapter.url_name
|
||||
if course_module.position is not None:
|
||||
return redirect(reverse('courseware_chapter', kwargs=urlargs))
|
||||
|
||||
# Relying on default of returning first child
|
||||
section = get_current_child(chapter, min_depth=content_depth - 1)
|
||||
if section is None:
|
||||
raise Http404("No section found when loading current position in course")
|
||||
|
||||
urlargs['section'] = section.url_name
|
||||
return redirect(reverse('courseware_section', kwargs=urlargs))
|
||||
|
||||
|
||||
def save_child_position(seq_module, child_name):
|
||||
"""
|
||||
child_name: url_name of the child
|
||||
"""
|
||||
for position, c in enumerate(seq_module.get_display_items(), start=1):
|
||||
if c.location.name == child_name:
|
||||
# Only save if position changed
|
||||
if position != seq_module.position:
|
||||
seq_module.position = position
|
||||
# Save this new position to the underlying KeyValueStore
|
||||
seq_module.save()
|
||||
|
||||
|
||||
def save_positions_recursively_up(user, request, field_data_cache, xmodule, course=None):
|
||||
"""
|
||||
Recurses up the course tree starting from a leaf
|
||||
Saving the position property based on the previous node as it goes
|
||||
"""
|
||||
current_module = xmodule
|
||||
|
||||
while current_module:
|
||||
parent_location = modulestore().get_parent_location(current_module.location)
|
||||
parent = None
|
||||
if parent_location:
|
||||
parent_descriptor = modulestore().get_item(parent_location)
|
||||
parent = get_module_for_descriptor(
|
||||
user,
|
||||
request,
|
||||
parent_descriptor,
|
||||
field_data_cache,
|
||||
current_module.location.course_key,
|
||||
course=course
|
||||
)
|
||||
|
||||
if parent and hasattr(parent, 'position'):
|
||||
save_child_position(parent, current_module.location.name)
|
||||
|
||||
current_module = parent
|
||||
|
||||
|
||||
@transaction.non_atomic_requests
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@ensure_valid_course_key
|
||||
@outer_atomic(read_committed=True)
|
||||
def index(request, course_id, chapter=None, section=None,
|
||||
position=None):
|
||||
"""
|
||||
Displays courseware accordion and associated content. If course, chapter,
|
||||
and section are all specified, renders the page, or returns an error if they
|
||||
are invalid.
|
||||
|
||||
If section is not specified, displays the accordion opened to the right chapter.
|
||||
|
||||
If neither chapter or section are specified, redirects to user's most recent
|
||||
chapter, or the first chapter if this is the user's first visit.
|
||||
|
||||
Arguments:
|
||||
|
||||
- request : HTTP request
|
||||
- course_id : course id (str: ORG/course/URL_NAME)
|
||||
- chapter : chapter url_name (str)
|
||||
- section : section url_name (str)
|
||||
- position : position in module, eg of <sequential> module (str)
|
||||
|
||||
Returns:
|
||||
|
||||
- HTTPresponse
|
||||
"""
|
||||
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
|
||||
# Gather metrics for New Relic so we can slice data in New Relic Insights
|
||||
newrelic.agent.add_custom_parameter('course_id', unicode(course_key))
|
||||
newrelic.agent.add_custom_parameter('org', unicode(course_key.org))
|
||||
|
||||
user = User.objects.prefetch_related("groups").get(id=request.user.id)
|
||||
|
||||
redeemed_registration_codes = CourseRegistrationCode.objects.filter(
|
||||
course_id=course_key,
|
||||
registrationcoderedemption__redeemed_by=request.user
|
||||
)
|
||||
|
||||
# Redirect to dashboard if the course is blocked due to non-payment.
|
||||
if is_course_blocked(request, redeemed_registration_codes, course_key):
|
||||
# registration codes may be generated via Bulk Purchase Scenario
|
||||
# we have to check only for the invoice generated registration codes
|
||||
# that their invoice is valid or not
|
||||
log.warning(
|
||||
u'User %s cannot access the course %s because payment has not yet been received',
|
||||
user,
|
||||
course_key.to_deprecated_string()
|
||||
)
|
||||
return redirect(reverse('dashboard'))
|
||||
|
||||
request.user = user # keep just one instance of User
|
||||
with modulestore().bulk_operations(course_key):
|
||||
return _index_bulk_op(request, course_key, chapter, section, position)
|
||||
|
||||
|
||||
# pylint: disable=too-many-statements
|
||||
def _index_bulk_op(request, course_key, chapter, section, position):
|
||||
"""
|
||||
Render the index page for the specified course.
|
||||
"""
|
||||
# Verify that position a string is in fact an int
|
||||
if position is not None:
|
||||
try:
|
||||
int(position)
|
||||
except ValueError:
|
||||
raise Http404(u"Position {} is not an integer!".format(position))
|
||||
|
||||
course = get_course_with_access(request.user, 'load', course_key, depth=2)
|
||||
staff_access = has_access(request.user, 'staff', course)
|
||||
masquerade, user = setup_masquerade(request, course_key, staff_access, reset_masquerade_data=True)
|
||||
|
||||
registered = registered_for_course(course, user)
|
||||
if not registered:
|
||||
# TODO (vshnayder): do course instructors need to be registered to see course?
|
||||
log.debug(u'User %s tried to view course %s but is not enrolled', user, course.location.to_deprecated_string())
|
||||
if bool(staff_access) == False:
|
||||
return redirect("{url}?{redirect}".format(
|
||||
url=reverse(enroll_staff, args=[course_key.to_deprecated_string()]),
|
||||
redirect=request.GET.urlencode()
|
||||
)
|
||||
)
|
||||
return redirect(reverse('about_course', args=[course_key.to_deprecated_string()]))
|
||||
|
||||
# see if all pre-requisites (as per the milestones app feature) have been fulfilled
|
||||
# Note that if the pre-requisite feature flag has been turned off (default) then this check will
|
||||
# always pass
|
||||
if not has_access(user, 'view_courseware_with_prerequisites', course):
|
||||
# prerequisites have not been fulfilled therefore redirect to the Dashboard
|
||||
log.info(
|
||||
u'User %d tried to view course %s '
|
||||
u'without fulfilling prerequisites',
|
||||
user.id, unicode(course.id))
|
||||
return redirect(reverse('dashboard'))
|
||||
|
||||
# Entrance Exam Check
|
||||
# If the course has an entrance exam and the requested chapter is NOT the entrance exam, and
|
||||
# the user hasn't yet met the criteria to bypass the entrance exam, redirect them to the exam.
|
||||
if chapter and course_has_entrance_exam(course):
|
||||
chapter_descriptor = course.get_child_by(lambda m: m.location.name == chapter)
|
||||
if chapter_descriptor and not getattr(chapter_descriptor, 'is_entrance_exam', False) \
|
||||
and user_must_complete_entrance_exam(request, user, course):
|
||||
log.info(u'User %d tried to view course %s without passing entrance exam', user.id, unicode(course.id))
|
||||
return redirect(reverse('courseware', args=[unicode(course.id)]))
|
||||
|
||||
# Gated Content Check
|
||||
gated_content = gating_api.get_gated_content(course, user)
|
||||
if section and gated_content:
|
||||
for usage_key in gated_content:
|
||||
if section in usage_key:
|
||||
raise Http404
|
||||
|
||||
# check to see if there is a required survey that must be taken before
|
||||
# the user can access the course.
|
||||
if survey.utils.must_answer_survey(course, user):
|
||||
return redirect(reverse('course_survey', args=[unicode(course.id)]))
|
||||
|
||||
bookmarks_api_url = reverse('bookmarks')
|
||||
|
||||
try:
|
||||
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
|
||||
course_key, user, course, depth=2)
|
||||
|
||||
studio_url = get_studio_url(course, 'course')
|
||||
|
||||
language_preference = get_user_preference(request.user, LANGUAGE_KEY)
|
||||
if not language_preference:
|
||||
language_preference = settings.LANGUAGE_CODE
|
||||
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'COURSE_TITLE': course.display_name_with_default_escaped,
|
||||
'course': course,
|
||||
'init': '',
|
||||
'fragment': Fragment(),
|
||||
'staff_access': staff_access,
|
||||
'studio_url': studio_url,
|
||||
'masquerade': masquerade,
|
||||
'xqa_server': settings.FEATURES.get('XQA_SERVER', "http://your_xqa_server.com"),
|
||||
'bookmarks_api_url': bookmarks_api_url,
|
||||
'language_preference': language_preference,
|
||||
'disable_optimizely': True,
|
||||
}
|
||||
table_of_contents, __, __ = toc_for_course(user, request, course, chapter, section, field_data_cache)
|
||||
context['accordion'] = render_accordion(request, course, table_of_contents)
|
||||
|
||||
now = datetime.now(UTC())
|
||||
effective_start = _adjust_start_date_for_beta_testers(user, course, course_key)
|
||||
if not in_preview_mode() and staff_access and now < effective_start:
|
||||
# Disable student view button if user is staff and
|
||||
# course is not yet visible to students.
|
||||
context['disable_student_access'] = True
|
||||
|
||||
has_content = course.has_children_at_depth(CONTENT_DEPTH)
|
||||
if not has_content:
|
||||
# Show empty courseware for a course with no units
|
||||
return render_to_response('courseware/courseware.html', context)
|
||||
elif chapter is None:
|
||||
# Check first to see if we should instead redirect the user to an Entrance Exam
|
||||
if course_has_entrance_exam(course):
|
||||
exam_chapter = get_entrance_exam_content(request, course)
|
||||
if exam_chapter:
|
||||
if exam_chapter.get_children():
|
||||
exam_section = exam_chapter.get_children()[0]
|
||||
if exam_section:
|
||||
return redirect('courseware_section',
|
||||
course_id=unicode(course_key),
|
||||
chapter=exam_chapter.url_name,
|
||||
section=exam_section.url_name)
|
||||
|
||||
# passing CONTENT_DEPTH avoids returning 404 for a course with an
|
||||
# empty first section and a second section with content
|
||||
return redirect_to_course_position(course, CONTENT_DEPTH)
|
||||
|
||||
chapter_descriptor = course.get_child_by(lambda m: m.location.name == chapter)
|
||||
if chapter_descriptor is not None:
|
||||
save_child_position(course, chapter)
|
||||
else:
|
||||
# User may be trying to access a chapter that isn't live yet
|
||||
if masquerade and masquerade.role == 'student': # if staff is masquerading as student be kinder, don't 404
|
||||
log.debug('staff masquerading as student: no chapter %s', chapter)
|
||||
return redirect(reverse('courseware', args=[course.id.to_deprecated_string()]))
|
||||
raise Http404('No chapter descriptor found with name {}'.format(chapter))
|
||||
|
||||
if course_has_entrance_exam(course):
|
||||
# Message should not appear outside the context of entrance exam subsection.
|
||||
# if section is none then we don't need to show message on welcome back screen also.
|
||||
if getattr(chapter_descriptor, 'is_entrance_exam', False) and section is not None:
|
||||
context['entrance_exam_current_score'] = get_entrance_exam_score(request, course)
|
||||
context['entrance_exam_passed'] = user_has_passed_entrance_exam(request, course)
|
||||
|
||||
if section is None:
|
||||
section_descriptor = get_current_child(chapter_descriptor, requested_child=request.GET.get("child"))
|
||||
if section_descriptor:
|
||||
section = section_descriptor.url_name
|
||||
else:
|
||||
# Something went wrong -- perhaps this chapter has no sections visible to the user.
|
||||
# Clearing out the last-visited state and showing "first-time" view by redirecting
|
||||
# to courseware.
|
||||
course.position = None
|
||||
course.save()
|
||||
return redirect(reverse('courseware', args=[course.id.to_deprecated_string()]))
|
||||
else:
|
||||
section_descriptor = chapter_descriptor.get_child_by(lambda m: m.location.name == section)
|
||||
|
||||
if section_descriptor is None:
|
||||
# Specifically asked-for section doesn't exist
|
||||
if masquerade and masquerade.role == 'student': # don't 404 if staff is masquerading as student
|
||||
log.debug('staff masquerading as student: no section %s', section)
|
||||
return redirect(reverse('courseware', args=[course.id.to_deprecated_string()]))
|
||||
raise Http404
|
||||
|
||||
# Allow chromeless operation
|
||||
if section_descriptor.chrome:
|
||||
chrome = [s.strip() for s in section_descriptor.chrome.lower().split(",")]
|
||||
if 'accordion' not in chrome:
|
||||
context['disable_accordion'] = True
|
||||
if 'tabs' not in chrome:
|
||||
context['disable_tabs'] = True
|
||||
|
||||
if section_descriptor.default_tab:
|
||||
context['default_tab'] = section_descriptor.default_tab
|
||||
|
||||
# cdodge: this looks silly, but let's refetch the section_descriptor with depth=None
|
||||
# which will prefetch the children more efficiently than doing a recursive load
|
||||
section_descriptor = modulestore().get_item(section_descriptor.location, depth=None)
|
||||
|
||||
# Load all descendants of the section, because we're going to display its
|
||||
# html, which in general will need all of its children
|
||||
field_data_cache.add_descriptor_descendents(
|
||||
section_descriptor, depth=None
|
||||
)
|
||||
|
||||
section_module = get_module_for_descriptor(
|
||||
user,
|
||||
request,
|
||||
section_descriptor,
|
||||
field_data_cache,
|
||||
course_key,
|
||||
position,
|
||||
course=course
|
||||
)
|
||||
|
||||
# Save where we are in the chapter.
|
||||
save_child_position(chapter_descriptor, section)
|
||||
|
||||
table_of_contents, prev_section_info, next_section_info = toc_for_course(
|
||||
user, request, course, chapter, section, field_data_cache
|
||||
)
|
||||
context['accordion'] = render_accordion(request, course, table_of_contents)
|
||||
|
||||
def _compute_section_url(section_info, requested_child):
|
||||
"""
|
||||
Returns the section URL for the given section_info with the given child parameter.
|
||||
"""
|
||||
return "{url}?child={requested_child}".format(
|
||||
url=reverse(
|
||||
'courseware_section',
|
||||
args=[unicode(course.id), section_info['chapter_url_name'], section_info['url_name']],
|
||||
),
|
||||
requested_child=requested_child,
|
||||
)
|
||||
|
||||
section_render_context = {
|
||||
'activate_block_id': request.GET.get('activate_block_id'),
|
||||
'requested_child': request.GET.get("child"),
|
||||
'prev_url': _compute_section_url(prev_section_info, 'last') if prev_section_info else None,
|
||||
'next_url': _compute_section_url(next_section_info, 'first') if next_section_info else None,
|
||||
}
|
||||
context['fragment'] = section_module.render(STUDENT_VIEW, section_render_context)
|
||||
context['section_title'] = section_descriptor.display_name_with_default_escaped
|
||||
result = render_to_response('courseware/courseware.html', context)
|
||||
except Exception as e:
|
||||
|
||||
# Doesn't bar Unicode characters from URL, but if Unicode characters do
|
||||
# cause an error it is a graceful failure.
|
||||
if isinstance(e, UnicodeEncodeError):
|
||||
raise Http404("URL contains Unicode characters")
|
||||
|
||||
if isinstance(e, Http404):
|
||||
# let it propagate
|
||||
raise
|
||||
|
||||
# In production, don't want to let a 500 out for any reason
|
||||
if settings.DEBUG:
|
||||
raise
|
||||
else:
|
||||
log.exception(
|
||||
u"Error in index view: user=%s, effective_user=%s, course=%s, chapter=%s section=%s position=%s",
|
||||
request.user, user, course, chapter, section, position
|
||||
)
|
||||
try:
|
||||
result = render_to_response('courseware/courseware-error.html', {
|
||||
'staff_access': staff_access,
|
||||
'course': course
|
||||
})
|
||||
except:
|
||||
# Let the exception propagate, relying on global config to at
|
||||
# at least return a nice error message
|
||||
log.exception("Error while rendering courseware-error page")
|
||||
raise
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@ensure_valid_course_key
|
||||
def jump_to_id(request, course_id, module_id):
|
||||
@@ -238,7 +627,13 @@ def jump_to(_request, course_id, location):
|
||||
except InvalidKeyError:
|
||||
raise Http404(u"Invalid course_key or usage_key")
|
||||
try:
|
||||
user = _request.user
|
||||
redirect_url = get_redirect_url(course_key, usage_key)
|
||||
if GlobalStaff().has_user(user) and not CourseEnrollment.is_enrolled(user, course_key):
|
||||
redirect_url = "{url}?next={redirect}".format(
|
||||
url=reverse(enroll_staff, args=[course_key.to_deprecated_string()]),
|
||||
redirect=redirect_url
|
||||
)
|
||||
except ItemNotFoundError:
|
||||
raise Http404(u"No data at this location: {0}".format(usage_key))
|
||||
except NoPathToItem:
|
||||
@@ -443,6 +838,54 @@ def get_cosmetic_display_price(course, registration_price):
|
||||
# Translators: This refers to the cost of the course. In this case, the course costs nothing so it is free.
|
||||
return _('Free')
|
||||
|
||||
@require_global_staff
|
||||
@require_http_methods(['POST', 'GET'])
|
||||
def enroll_staff(request, course_id):
|
||||
'''
|
||||
1. Should be staff
|
||||
2. should be a valid course_id
|
||||
3. shouldn't be enrolled before
|
||||
4. The requested view url to redirect
|
||||
|
||||
URL-ABC-GOTO-HERE-
|
||||
|
||||
1. You want to register for this course?
|
||||
Confirm:
|
||||
1. User is valid staff user who wants to enroll.
|
||||
2. Course is valid course
|
||||
2. Yes
|
||||
3. Post request, enroll the user and redirect him to the requested view
|
||||
|
||||
:param request:
|
||||
:param course_id:
|
||||
:return:
|
||||
'''
|
||||
user = request.user
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
_next = urllib.quote_plus(request.GET.get('next', 'info'), safe='/:?=')
|
||||
|
||||
if request.method == 'GET':
|
||||
with modulestore().bulk_operations(course_key):
|
||||
course = get_course_with_access(user, 'load', course_key, depth=2)
|
||||
|
||||
# Prompt for enrollment if Globalstaff is not enrolled in the course
|
||||
if not registered_for_course(course, user):
|
||||
return render_to_response('enroll_staff.html', {
|
||||
'course': course,
|
||||
'csrftoken': csrf(request)["csrf_token"]
|
||||
})
|
||||
|
||||
elif request.method == 'POST' and 'enroll' in request.POST.dict():
|
||||
enrollment = CourseEnrollment.get_or_create_enrollment(request.user, course_key)
|
||||
enrollment.update_enrollment(is_active=True)
|
||||
log.info(
|
||||
u"User %s enrolled in %s via `enroll_staff` view",
|
||||
user.username,
|
||||
course_id
|
||||
)
|
||||
|
||||
return redirect(_next)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_if_anonymous()
|
||||
|
||||
@@ -3289,7 +3289,6 @@ def validate_request_data_and_get_certificate(certificate_invalidation, course_k
|
||||
)
|
||||
|
||||
student = get_student(user, course_key)
|
||||
|
||||
certificate = GeneratedCertificate.certificate_for_student(student, course_key)
|
||||
if not certificate:
|
||||
raise ValueError(_(
|
||||
|
||||
@@ -446,6 +446,7 @@ def submit_export_ora2_data(request, course_key):
|
||||
|
||||
def generate_certificates_for_students(request, course_key, student_set=None, specific_student_id=None): # pylint: disable=invalid-name
|
||||
"""
|
||||
<<<<<<< HEAD
|
||||
Submits a task to generate certificates for given students enrolled in the course.
|
||||
|
||||
Arguments:
|
||||
|
||||
37
lms/templates/enroll_staff.html
Normal file
37
lms/templates/enroll_staff.html
Normal file
@@ -0,0 +1,37 @@
|
||||
<%inherit file="main.html" />
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
from courseware.courses import get_course_info_section, get_course_date_summary
|
||||
|
||||
|
||||
%>
|
||||
|
||||
<%block name="pagetitle">${_("{course_number} Course Info").format(course_number=course.display_number_with_default)}</%block>
|
||||
|
||||
<%block name="headextra">
|
||||
<%static:css group='style-course-vendor'/>
|
||||
<%static:css group='style-course'/>
|
||||
</%block>
|
||||
|
||||
<%block name="bodyclass">view-in-course view-course-info ${course.css_class or ''}</%block>
|
||||
<div class="login-register">
|
||||
<section class="form-type">
|
||||
<section role="main" class="content">
|
||||
<div id="unenroll_error" class="modal-form-error"></div>
|
||||
<div id="${course.id.to_deprecated_string()}" >
|
||||
<h3> ${_("You should Register before trying to access and Unit")}</h3>
|
||||
<form role="form" id="enroll_staff_form" method="post" action="">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrftoken }"/>
|
||||
<div class="submit ">
|
||||
<input name="enroll" type="submit" value="${_(" Enroll")}" />
|
||||
<input name="continue" type="submit" value="${_(" Continue")}" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@@ -366,6 +366,14 @@ urlpatterns += (
|
||||
name='about_course',
|
||||
),
|
||||
|
||||
url(
|
||||
r'^courses/{}/enroll_staff$'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
'courseware.views.enroll_staff',
|
||||
name='enroll_staff',
|
||||
),
|
||||
|
||||
#Inside the course
|
||||
url(
|
||||
r'^courses/{}/$'.format(
|
||||
|
||||
Reference in New Issue
Block a user