Add extensible course view types for edX platform
This commit is contained in:
committed by
Diana Huang
parent
9008548c25
commit
94e1c42314
@@ -1171,6 +1171,8 @@ class CourseEnrollment(models.Model):
|
||||
|
||||
`course_id` is our usual course_id string (e.g. "edX/Test101/2013_Fall)
|
||||
"""
|
||||
if not user.is_authenticated():
|
||||
return False
|
||||
try:
|
||||
record = CourseEnrollment.objects.get(user=user, course_id=course_key)
|
||||
return record.is_active
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
Implement CourseTab
|
||||
"""
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from xblock.fields import List
|
||||
|
||||
# We should only scrape strings for i18n in this file, since the target language is known only when
|
||||
@@ -51,11 +52,11 @@ class CourseTab(object):
|
||||
|
||||
self.link_func = link_func
|
||||
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff, is_user_enrolled): # pylint: disable=unused-argument
|
||||
def is_enabled(self, course, settings, user=None): # pylint: disable=unused-argument
|
||||
"""
|
||||
Determines whether the tab should be displayed in the UI for the given course and a particular user.
|
||||
This method is to be overridden by subclasses when applicable. The base class implementation
|
||||
always returns True.
|
||||
Determines whether the tab is enabled for the given course and a particular user.
|
||||
This method is to be overridden by subclasses when applicable. The base class
|
||||
implementation always returns True.
|
||||
|
||||
Args:
|
||||
course: An xModule CourseDescriptor
|
||||
@@ -67,17 +68,11 @@ class CourseTab(object):
|
||||
FEATURES['ENABLE_STUDENT_NOTES']
|
||||
FEATURES['ENABLE_TEXTBOOK']
|
||||
|
||||
is_user_authenticated: Indicates whether the user is authenticated. If the tab is of
|
||||
type AuthenticatedCourseTab and this value is False, then can_display will return False.
|
||||
|
||||
is_user_staff: Indicates whether the user has staff access to the course. If the tab is of
|
||||
type StaffTab and this value is False, then can_display will return False.
|
||||
|
||||
is_user_enrolled: Indicates whether the user is enrolled in the course
|
||||
user: An optional user for whom the tab will be displayed. If none,
|
||||
then the code should assume a staff user or an author.
|
||||
|
||||
Returns:
|
||||
A boolean value to indicate whether this instance of the tab should be displayed to a
|
||||
given user for the given course.
|
||||
A boolean value to indicate whether this instance of the tab is enabled.
|
||||
"""
|
||||
return True
|
||||
|
||||
@@ -174,62 +169,104 @@ class CourseTab(object):
|
||||
Raises:
|
||||
InvalidTabsException if the given tab doesn't have the right keys.
|
||||
"""
|
||||
sub_class_types = {
|
||||
'courseware': CoursewareTab,
|
||||
'course_info': CourseInfoTab,
|
||||
'wiki': WikiTab,
|
||||
'discussion': DiscussionTab,
|
||||
'external_discussion': ExternalDiscussionTab,
|
||||
'external_link': ExternalLinkTab,
|
||||
'textbooks': TextbookTabs,
|
||||
'pdf_textbooks': PDFTextbookTabs,
|
||||
'html_textbooks': HtmlTextbookTabs,
|
||||
'progress': ProgressTab,
|
||||
'static_tab': StaticTab,
|
||||
'peer_grading': PeerGradingTab,
|
||||
'staff_grading': StaffGradingTab,
|
||||
'open_ended': OpenEndedGradingTab,
|
||||
'notes': NotesTab,
|
||||
'edxnotes': EdxNotesTab,
|
||||
'syllabus': SyllabusTab,
|
||||
'instructor': InstructorTab, # not persisted
|
||||
'ccx_coach': CcxCoachTab, # not persisted
|
||||
}
|
||||
|
||||
tab_type = tab_dict.get('type')
|
||||
if tab_type not in sub_class_types:
|
||||
available_tab_types = CourseTabManager.get_tab_types()
|
||||
tab_type_name = tab_dict.get('type')
|
||||
if tab_type_name not in available_tab_types:
|
||||
raise InvalidTabsException(
|
||||
'Unknown tab type {0}. Known types: {1}'.format(tab_type, sub_class_types)
|
||||
'Unknown tab type {0}. Known types: {1}'.format(tab_type_name, available_tab_types)
|
||||
)
|
||||
tab_type = available_tab_types[tab_dict['type']]
|
||||
tab_type.validate(tab_dict)
|
||||
# TODO: don't import openedx capabilities from common
|
||||
from openedx.core.lib.plugins.api import CourseViewType
|
||||
if issubclass(tab_type, CourseViewType):
|
||||
return CourseViewTab(tab_type, tab_dict=tab_dict)
|
||||
else:
|
||||
return tab_type(tab_dict=tab_dict)
|
||||
|
||||
tab_class = sub_class_types[tab_dict['type']]
|
||||
tab_class.validate(tab_dict)
|
||||
return tab_class(tab_dict=tab_dict)
|
||||
|
||||
class CourseTabManager(object):
|
||||
"""
|
||||
A manager that handles the set of available course tabs.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_tab_types(cls):
|
||||
"""
|
||||
Returns the list of available tab types.
|
||||
"""
|
||||
if not hasattr(cls, "_tab_types"):
|
||||
tab_types = {
|
||||
'courseware': CoursewareTab,
|
||||
'course_info': CourseInfoTab,
|
||||
'wiki': WikiTab,
|
||||
'discussion': DiscussionTab,
|
||||
'external_discussion': ExternalDiscussionTab,
|
||||
'external_link': ExternalLinkTab,
|
||||
'textbooks': TextbookTabs,
|
||||
'pdf_textbooks': PDFTextbookTabs,
|
||||
'html_textbooks': HtmlTextbookTabs,
|
||||
'progress': ProgressTab,
|
||||
'static_tab': StaticTab,
|
||||
'peer_grading': PeerGradingTab,
|
||||
'staff_grading': StaffGradingTab,
|
||||
'open_ended': OpenEndedGradingTab,
|
||||
'notes': NotesTab,
|
||||
'syllabus': SyllabusTab,
|
||||
}
|
||||
|
||||
# Add any registered course views
|
||||
# TODO: don't import openedx capabilities from common
|
||||
from openedx.core.lib.plugins.api import CourseViewTypeManager
|
||||
for course_view_type in CourseViewTypeManager.get_available_plugins().values():
|
||||
tab_types[course_view_type.name] = course_view_type
|
||||
|
||||
cls._tab_types = tab_types
|
||||
return cls._tab_types
|
||||
|
||||
|
||||
class AuthenticatedCourseTab(CourseTab):
|
||||
"""
|
||||
Abstract class for tabs that can be accessed by only authenticated users.
|
||||
"""
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff, is_user_enrolled):
|
||||
return is_user_authenticated
|
||||
def is_enabled(self, course, settings, user=None):
|
||||
return not user or user.is_authenticated()
|
||||
|
||||
|
||||
class StaffTab(AuthenticatedCourseTab):
|
||||
"""
|
||||
Abstract class for tabs that can be accessed by only users with staff access.
|
||||
"""
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff, is_user_enrolled): # pylint: disable=unused-argument
|
||||
return is_user_staff
|
||||
def is_enabled(self, course, settings, user=None): # pylint: disable=unused-argument
|
||||
return not user or is_user_staff(course, user)
|
||||
|
||||
|
||||
class EnrolledOrStaffTab(CourseTab):
|
||||
def is_user_staff(course, user):
|
||||
"""
|
||||
Returns true if the user is staff in the specified course, or globally.
|
||||
"""
|
||||
from courseware.access import has_access # pylint: disable=import-error
|
||||
return has_access(user, 'staff', course, course.id)
|
||||
|
||||
|
||||
def is_user_enrolled_or_staff(course, user):
|
||||
"""
|
||||
Returns true if the user is enrolled in the specified course,
|
||||
or if the user is staff.
|
||||
"""
|
||||
from student.models import CourseEnrollment # pylint: disable=import-error
|
||||
return is_user_staff(course, user) or CourseEnrollment.is_enrolled(user, course.id)
|
||||
|
||||
|
||||
class EnrolledOrStaffTab(AuthenticatedCourseTab):
|
||||
"""
|
||||
Abstract class for tabs that can be accessed by only users with staff access
|
||||
or users enrolled in the course.
|
||||
"""
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff, is_user_enrolled): # pylint: disable=unused-argument
|
||||
return is_user_authenticated and (is_user_staff or is_user_enrolled)
|
||||
def is_enabled(self, course, settings, user=None): # pylint: disable=unused-argument
|
||||
if not user:
|
||||
return True
|
||||
return is_user_enrolled_or_staff(course, user)
|
||||
|
||||
|
||||
class HideableTab(CourseTab):
|
||||
@@ -323,10 +360,8 @@ class ProgressTab(EnrolledOrStaffTab):
|
||||
link_func=link_reverse_func(self.type),
|
||||
)
|
||||
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff, is_user_enrolled):
|
||||
super_can_display = super(ProgressTab, self).can_display(
|
||||
course, settings, is_user_authenticated, is_user_staff, is_user_enrolled
|
||||
)
|
||||
def is_enabled(self, course, settings, user=None):
|
||||
super_can_display = super(ProgressTab, self).is_enabled(course, settings, user=user)
|
||||
return super_can_display and not course.hide_progress_tab
|
||||
|
||||
@classmethod
|
||||
@@ -350,10 +385,12 @@ class WikiTab(HideableTab):
|
||||
tab_dict=tab_dict,
|
||||
)
|
||||
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff, is_user_enrolled):
|
||||
return settings.WIKI_ENABLED and (
|
||||
course.allow_public_wiki_access or is_user_enrolled or is_user_staff
|
||||
)
|
||||
def is_enabled(self, course, settings, user=None):
|
||||
if not settings.WIKI_ENABLED:
|
||||
return False
|
||||
if not user or course.allow_public_wiki_access:
|
||||
return True
|
||||
return is_user_enrolled_or_staff(course, user)
|
||||
|
||||
@classmethod
|
||||
def validate(cls, tab_dict, raise_error=True):
|
||||
@@ -375,14 +412,12 @@ class DiscussionTab(EnrolledOrStaffTab):
|
||||
link_func=link_reverse_func('django_comment_client.forum.views.forum_form_discussion'),
|
||||
)
|
||||
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff, is_user_enrolled):
|
||||
def is_enabled(self, course, settings, user=None):
|
||||
if settings.FEATURES.get('CUSTOM_COURSES_EDX', False):
|
||||
from ccx.overrides import get_current_ccx # pylint: disable=import-error
|
||||
if get_current_ccx():
|
||||
return False
|
||||
super_can_display = super(DiscussionTab, self).can_display(
|
||||
course, settings, is_user_authenticated, is_user_staff, is_user_enrolled
|
||||
)
|
||||
super_can_display = super(DiscussionTab, self).is_enabled(course, settings, user=user)
|
||||
return settings.FEATURES.get('ENABLE_DISCUSSION_SERVICE') and super_can_display
|
||||
|
||||
@classmethod
|
||||
@@ -549,7 +584,7 @@ class TextbookTabs(TextbookTabsBase):
|
||||
tab_id=self.type,
|
||||
)
|
||||
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff, is_user_enrolled):
|
||||
def is_enabled(self, course, settings, user=None):
|
||||
return settings.FEATURES.get('ENABLE_TEXTBOOK')
|
||||
|
||||
def items(self, course):
|
||||
@@ -668,7 +703,7 @@ class SyllabusTab(CourseTab):
|
||||
"""
|
||||
type = 'syllabus'
|
||||
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff, is_user_enrolled):
|
||||
def is_enabled(self, course, settings, user=None):
|
||||
return hasattr(course, 'syllabus_present') and course.syllabus_present
|
||||
|
||||
def __init__(self, tab_dict=None): # pylint: disable=unused-argument
|
||||
@@ -686,7 +721,7 @@ class NotesTab(AuthenticatedCourseTab):
|
||||
"""
|
||||
type = 'notes'
|
||||
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff, is_user_enrolled):
|
||||
def is_enabled(self, course, settings, user=None):
|
||||
return settings.FEATURES.get('ENABLE_STUDENT_NOTES')
|
||||
|
||||
def __init__(self, tab_dict=None):
|
||||
@@ -701,77 +736,24 @@ class NotesTab(AuthenticatedCourseTab):
|
||||
return super(NotesTab, cls).validate(tab_dict, raise_error) and need_name(tab_dict, raise_error)
|
||||
|
||||
|
||||
class EdxNotesTab(AuthenticatedCourseTab):
|
||||
class CourseViewTab(AuthenticatedCourseTab):
|
||||
"""
|
||||
A tab for the course student notes.
|
||||
A tab that renders a course view.
|
||||
"""
|
||||
type = 'edxnotes'
|
||||
|
||||
def can_display(self, course, settings, is_user_authenticated, is_user_staff, is_user_enrolled):
|
||||
return settings.FEATURES.get('ENABLE_EDXNOTES')
|
||||
|
||||
def __init__(self, tab_dict=None):
|
||||
super(EdxNotesTab, self).__init__(
|
||||
name=tab_dict['name'] if tab_dict else _('Notes'),
|
||||
tab_id=self.type,
|
||||
link_func=link_reverse_func(self.type),
|
||||
def __init__(self, course_view_type, tab_dict=None):
|
||||
super(CourseViewTab, self).__init__(
|
||||
name=tab_dict['name'] if tab_dict else course_view_type.title,
|
||||
tab_id=course_view_type.name,
|
||||
link_func=link_reverse_func(course_view_type.view_name),
|
||||
)
|
||||
self.type = course_view_type.name
|
||||
self.course_view_type = course_view_type
|
||||
|
||||
@classmethod
|
||||
def validate(cls, tab_dict, raise_error=True):
|
||||
return super(EdxNotesTab, cls).validate(tab_dict, raise_error) and need_name(tab_dict, raise_error)
|
||||
|
||||
|
||||
class InstructorTab(StaffTab):
|
||||
"""
|
||||
A tab for the course instructors.
|
||||
"""
|
||||
type = 'instructor'
|
||||
|
||||
def __init__(self, tab_dict=None): # pylint: disable=unused-argument
|
||||
super(InstructorTab, self).__init__(
|
||||
# Translators: 'Instructor' appears on the tab that leads to the instructor dashboard, which is
|
||||
# a portal where an instructor can get data and perform various actions on their course
|
||||
name=_('Instructor'),
|
||||
tab_id=self.type,
|
||||
link_func=link_reverse_func('instructor_dashboard'),
|
||||
)
|
||||
|
||||
|
||||
class CcxCoachTab(CourseTab):
|
||||
"""
|
||||
A tab for the custom course coaches.
|
||||
"""
|
||||
type = 'ccx_coach'
|
||||
|
||||
def __init__(self, tab_dict=None): # pylint: disable=unused-argument
|
||||
super(CcxCoachTab, self).__init__(
|
||||
name=_('CCX Coach'),
|
||||
tab_id=self.type,
|
||||
link_func=link_reverse_func('ccx_coach_dashboard'),
|
||||
)
|
||||
|
||||
def can_display(self, course, settings, *args, **kw):
|
||||
"""
|
||||
Since we don't get the user here, we use a thread local defined in the ccx
|
||||
overrides to get it, then use the course to get the coach role and find out if
|
||||
the user is one.
|
||||
"""
|
||||
user_is_coach = False
|
||||
if settings.FEATURES.get('CUSTOM_COURSES_EDX', False) and course.enable_ccx:
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
from student.roles import CourseCcxCoachRole # pylint: disable=import-error
|
||||
from ccx.overrides import get_current_request # pylint: disable=import-error
|
||||
course_id = course.id.to_deprecated_string()
|
||||
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
|
||||
role = CourseCcxCoachRole(course_key)
|
||||
request = get_current_request()
|
||||
if request is not None:
|
||||
user_is_coach = role.has_user(request.user)
|
||||
super_can_display = super(CcxCoachTab, self).can_display(
|
||||
course, settings, *args, **kw
|
||||
)
|
||||
return user_is_coach and super_can_display
|
||||
def is_enabled(self, course, settings, user=None):
|
||||
if not super(CourseViewTab, self).is_enabled(course, settings, user=user):
|
||||
return False
|
||||
return self.course_view_type.is_enabled(course, settings, user=user)
|
||||
|
||||
|
||||
class CourseTabList(List):
|
||||
@@ -851,48 +833,23 @@ class CourseTabList(List):
|
||||
return next((tab for tab in tab_list if tab.tab_id == tab_id), None)
|
||||
|
||||
@staticmethod
|
||||
def iterate_displayable(
|
||||
course,
|
||||
settings,
|
||||
is_user_authenticated=True,
|
||||
is_user_staff=True,
|
||||
is_user_enrolled=False
|
||||
):
|
||||
def iterate_displayable(course, settings, user=None, inline_collections=True):
|
||||
"""
|
||||
Generator method for iterating through all tabs that can be displayed for the given course and
|
||||
the given user with the provided access settings.
|
||||
"""
|
||||
for tab in course.tabs:
|
||||
if tab.can_display(
|
||||
course, settings, is_user_authenticated, is_user_staff, is_user_enrolled
|
||||
) and (not tab.is_hideable or not tab.is_hidden):
|
||||
if tab.is_enabled(course, settings, user=user) and (not user or not tab.is_hideable or not tab.is_hidden):
|
||||
if tab.is_collection:
|
||||
for item in tab.items(course):
|
||||
yield item
|
||||
# If rendering inline that add each item in the collection,
|
||||
# else just show the tab itself as long as it is not empty.
|
||||
if inline_collections:
|
||||
for item in tab.items(course):
|
||||
yield item
|
||||
elif len(list(tab.items(course))) > 0:
|
||||
yield tab
|
||||
else:
|
||||
yield tab
|
||||
instructor_tab = InstructorTab()
|
||||
if instructor_tab.can_display(course, settings, is_user_authenticated, is_user_staff, is_user_enrolled):
|
||||
yield instructor_tab
|
||||
ccx_coach_tab = CcxCoachTab()
|
||||
if ccx_coach_tab.can_display(course, settings, is_user_authenticated, is_user_staff, is_user_enrolled):
|
||||
yield ccx_coach_tab
|
||||
|
||||
@staticmethod
|
||||
def iterate_displayable_cms(
|
||||
course,
|
||||
settings
|
||||
):
|
||||
"""
|
||||
Generator method for iterating through all tabs that can be displayed for the given course
|
||||
with the provided settings.
|
||||
"""
|
||||
for tab in course.tabs:
|
||||
if tab.can_display(course, settings, is_user_authenticated=True, is_user_staff=True, is_user_enrolled=True):
|
||||
if tab.is_collection and not len(list(tab.items(course))):
|
||||
# do not yield collections that have no items
|
||||
continue
|
||||
yield tab
|
||||
|
||||
@classmethod
|
||||
def validate_tabs(cls, tabs):
|
||||
@@ -927,7 +884,8 @@ class CourseTabList(List):
|
||||
TextbookTabs.type,
|
||||
PDFTextbookTabs.type,
|
||||
HtmlTextbookTabs.type,
|
||||
EdxNotesTab.type]:
|
||||
CourseViewTab.type
|
||||
]:
|
||||
cls._validate_num_tabs_of_type(tabs, tab_type, 1)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""Tests for Tab classes"""
|
||||
from mock import MagicMock
|
||||
from mock import MagicMock, patch
|
||||
import xmodule.tabs as tabs
|
||||
import unittest
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
@@ -18,6 +18,28 @@ class TabTestCase(unittest.TestCase):
|
||||
self.reverse = lambda name, args: "name/{0}/args/{1}".format(name, ",".join(str(a) for a in args))
|
||||
self.books = None
|
||||
|
||||
def create_mock_user(self, is_authenticated=True, is_staff=True, is_enrolled=True):
|
||||
"""
|
||||
Creates a mock user with the specified properties.
|
||||
"""
|
||||
user = MagicMock()
|
||||
user.name = 'mock_user'
|
||||
user.is_staff = is_staff
|
||||
user.is_enrolled = is_enrolled
|
||||
user.is_authenticated = lambda: is_authenticated
|
||||
return user
|
||||
|
||||
@patch('xmodule.tabs.is_user_enrolled_or_staff')
|
||||
@patch('xmodule.tabs.is_user_staff')
|
||||
def is_tab_enabled(self, tab, course, settings, user, is_staff_mock=None, is_enrolled_or_staff_mock=None):
|
||||
"""
|
||||
Returns true if the specified tab is enabled.
|
||||
"""
|
||||
is_staff_mock.return_value = user.is_staff
|
||||
is_enrolled_or_staff_mock.return_value = user.is_enrolled or user.is_staff
|
||||
|
||||
return tab.is_enabled(course, settings, user=user)
|
||||
|
||||
def set_up_books(self, num_books):
|
||||
"""Initializes the textbooks in the course and adds the given number of books to each textbook"""
|
||||
self.books = [MagicMock() for _ in range(num_books)]
|
||||
@@ -101,33 +123,17 @@ class TabTestCase(unittest.TestCase):
|
||||
):
|
||||
"""Checks can display results for various users"""
|
||||
if for_staff_only:
|
||||
self.assertEquals(
|
||||
expected_value,
|
||||
tab.can_display(
|
||||
self.course, self.settings, is_user_authenticated=True, is_user_staff=True, is_user_enrolled=True
|
||||
)
|
||||
)
|
||||
user = self.create_mock_user(is_authenticated=True, is_staff=True, is_enrolled=True)
|
||||
self.assertEquals(expected_value, self.is_tab_enabled(tab, self.course, self.settings, user))
|
||||
if for_authenticated_users_only:
|
||||
self.assertEquals(
|
||||
expected_value,
|
||||
tab.can_display(
|
||||
self.course, self.settings, is_user_authenticated=True, is_user_staff=False, is_user_enrolled=False
|
||||
)
|
||||
)
|
||||
user = self.create_mock_user(is_authenticated=True, is_staff=False, is_enrolled=False)
|
||||
self.assertEquals(expected_value, self.is_tab_enabled(tab, self.course, self.settings, user))
|
||||
if not for_staff_only and not for_authenticated_users_only and not for_enrolled_users_only:
|
||||
self.assertEquals(
|
||||
expected_value,
|
||||
tab.can_display(
|
||||
self.course, self.settings, is_user_authenticated=False, is_user_staff=False, is_user_enrolled=False
|
||||
)
|
||||
)
|
||||
user = self.create_mock_user(is_authenticated=False, is_staff=False, is_enrolled=False)
|
||||
self.assertEquals(expected_value, self.is_tab_enabled(tab, self.course, self.settings, user))
|
||||
if for_enrolled_users_only:
|
||||
self.assertEquals(
|
||||
expected_value,
|
||||
tab.can_display(
|
||||
self.course, self.settings, is_user_authenticated=True, is_user_staff=False, is_user_enrolled=True
|
||||
)
|
||||
)
|
||||
user = self.create_mock_user(is_authenticated=True, is_staff=False, is_enrolled=True)
|
||||
self.assertEquals(expected_value, self.is_tab_enabled(tab, self.course, self.settings, user))
|
||||
|
||||
def check_get_and_set_methods(self, tab):
|
||||
"""Test __getitem__ and __setitem__ calls"""
|
||||
@@ -285,13 +291,16 @@ class TextbooksTestCase(TabTestCase):
|
||||
self.num_textbook_tabs = sum(1 for tab in self.course.tabs if isinstance(tab, tabs.TextbookTabsBase))
|
||||
self.num_textbooks = self.num_textbook_tabs * len(self.books)
|
||||
|
||||
def test_textbooks_enabled(self):
|
||||
@patch('xmodule.tabs.is_user_enrolled_or_staff')
|
||||
def test_textbooks_enabled(self, is_enrolled_or_staff_mock):
|
||||
is_enrolled_or_staff_mock.return_value = True
|
||||
|
||||
type_to_reverse_name = {'textbook': 'book', 'pdftextbook': 'pdf_book', 'htmltextbook': 'html_book'}
|
||||
|
||||
self.settings.FEATURES['ENABLE_TEXTBOOK'] = True
|
||||
num_textbooks_found = 0
|
||||
for tab in tabs.CourseTabList.iterate_displayable(self.course, self.settings):
|
||||
user = self.create_mock_user(is_authenticated=True, is_staff=False, is_enrolled=True)
|
||||
for tab in tabs.CourseTabList.iterate_displayable(self.course, self.settings, user=user):
|
||||
# verify all textbook type tabs
|
||||
if isinstance(tab, tabs.SingleTextbookTab):
|
||||
book_type, book_index = tab.tab_id.split("/", 1)
|
||||
@@ -397,56 +406,6 @@ class SyllabusTestCase(TabTestCase):
|
||||
self.check_syllabus_tab(False)
|
||||
|
||||
|
||||
class InstructorTestCase(TabTestCase):
|
||||
"""Test cases for Instructor Tab."""
|
||||
|
||||
def test_instructor_tab(self):
|
||||
name = 'Instructor'
|
||||
tab = self.check_tab(
|
||||
tab_class=tabs.InstructorTab,
|
||||
dict_tab={'type': tabs.InstructorTab.type, 'name': name},
|
||||
expected_name=name,
|
||||
expected_link=self.reverse('instructor_dashboard', args=[self.course.id.to_deprecated_string()]),
|
||||
expected_tab_id=tabs.InstructorTab.type,
|
||||
invalid_dict_tab=None,
|
||||
)
|
||||
self.check_can_display_results(tab, for_staff_only=True)
|
||||
|
||||
|
||||
class EdxNotesTestCase(TabTestCase):
|
||||
"""
|
||||
Test cases for Notes Tab.
|
||||
"""
|
||||
|
||||
def check_edxnotes_tab(self):
|
||||
"""
|
||||
Helper function for verifying the edxnotes tab.
|
||||
"""
|
||||
return self.check_tab(
|
||||
tab_class=tabs.EdxNotesTab,
|
||||
dict_tab={'type': tabs.EdxNotesTab.type, 'name': 'same'},
|
||||
expected_link=self.reverse('edxnotes', args=[self.course.id.to_deprecated_string()]),
|
||||
expected_tab_id=tabs.EdxNotesTab.type,
|
||||
invalid_dict_tab=self.fake_dict_tab,
|
||||
)
|
||||
|
||||
def test_edxnotes_tabs_enabled(self):
|
||||
"""
|
||||
Tests that edxnotes tab is shown when feature is enabled.
|
||||
"""
|
||||
self.settings.FEATURES['ENABLE_EDXNOTES'] = True
|
||||
tab = self.check_edxnotes_tab()
|
||||
self.check_can_display_results(tab, for_authenticated_users_only=True)
|
||||
|
||||
def test_edxnotes_tabs_disabled(self):
|
||||
"""
|
||||
Tests that edxnotes tab is not shown when feature is disabled.
|
||||
"""
|
||||
self.settings.FEATURES['ENABLE_EDXNOTES'] = False
|
||||
tab = self.check_edxnotes_tab()
|
||||
self.check_can_display_results(tab, expected_value=False)
|
||||
|
||||
|
||||
class KeyCheckerTestCase(unittest.TestCase):
|
||||
"""Test cases for KeyChecker class"""
|
||||
|
||||
@@ -510,7 +469,6 @@ class TabListTestCase(TabTestCase):
|
||||
tabs.TextbookTabs.type,
|
||||
tabs.PDFTextbookTabs.type,
|
||||
tabs.HtmlTextbookTabs.type,
|
||||
tabs.EdxNotesTab.type,
|
||||
]
|
||||
|
||||
for unique_tab_type in unique_tab_types:
|
||||
@@ -543,7 +501,6 @@ class TabListTestCase(TabTestCase):
|
||||
{'type': tabs.OpenEndedGradingTab.type},
|
||||
{'type': tabs.NotesTab.type, 'name': 'fake_name'},
|
||||
{'type': tabs.SyllabusTab.type},
|
||||
{'type': tabs.EdxNotesTab.type, 'name': 'fake_name'},
|
||||
],
|
||||
# with external discussion
|
||||
[
|
||||
@@ -599,7 +556,11 @@ class CourseTabListTestCase(TabListTestCase):
|
||||
self.assertTrue(tabs.ExternalDiscussionTab() not in self.course.tabs)
|
||||
self.assertTrue(tabs.DiscussionTab() in self.course.tabs)
|
||||
|
||||
def test_iterate_displayable(self):
|
||||
@patch('xmodule.tabs.is_user_enrolled_or_staff')
|
||||
@patch('xmodule.tabs.is_user_staff')
|
||||
def test_iterate_displayable(self, is_staff_mock, is_enrolled_or_staff_mock):
|
||||
is_staff_mock.return_value = True
|
||||
is_enrolled_or_staff_mock.return_value = True
|
||||
# enable all tab types
|
||||
self.settings.FEATURES['ENABLE_TEXTBOOK'] = True
|
||||
self.settings.FEATURES['ENABLE_DISCUSSION_SERVICE'] = True
|
||||
@@ -613,24 +574,24 @@ class CourseTabListTestCase(TabListTestCase):
|
||||
# initialize the course tabs to a list of all valid tabs
|
||||
self.course.tabs = self.all_valid_tab_list
|
||||
|
||||
# enumerate the tabs using the CMS call
|
||||
for i, tab in enumerate(tabs.CourseTabList.iterate_displayable_cms(
|
||||
self.course,
|
||||
self.settings,
|
||||
# enumerate the tabs with no user
|
||||
for i, tab in enumerate(tabs.CourseTabList.iterate_displayable(
|
||||
self.course,
|
||||
self.settings,
|
||||
inline_collections=False
|
||||
)):
|
||||
self.assertEquals(tab.type, self.course.tabs[i].type)
|
||||
|
||||
# enumerate the tabs and verify textbooks and the instructor tab
|
||||
# enumerate the tabs with a staff user
|
||||
user = self.create_mock_user(is_authenticated=True, is_staff=True, is_enrolled=True)
|
||||
for i, tab in enumerate(tabs.CourseTabList.iterate_displayable(
|
||||
self.course,
|
||||
self.settings,
|
||||
self.course,
|
||||
self.settings,
|
||||
user=user
|
||||
)):
|
||||
if getattr(tab, 'is_collection_item', False):
|
||||
# a collection item was found as a result of a collection tab
|
||||
self.assertTrue(getattr(self.course.tabs[i], 'is_collection', False))
|
||||
elif i == len(self.course.tabs):
|
||||
# the last tab must be the Instructor tab
|
||||
self.assertEquals(tab.type, tabs.InstructorTab.type)
|
||||
else:
|
||||
# all other tabs must match the expected type
|
||||
self.assertEquals(tab.type, self.course.tabs[i].type)
|
||||
@@ -638,14 +599,14 @@ class CourseTabListTestCase(TabListTestCase):
|
||||
# test including non-empty collections
|
||||
self.assertIn(
|
||||
tabs.HtmlTextbookTabs(),
|
||||
list(tabs.CourseTabList.iterate_displayable_cms(self.course, self.settings)),
|
||||
list(tabs.CourseTabList.iterate_displayable(self.course, self.settings, inline_collections=False)),
|
||||
)
|
||||
|
||||
# test not including empty collections
|
||||
self.course.html_textbooks = []
|
||||
self.assertNotIn(
|
||||
tabs.HtmlTextbookTabs(),
|
||||
list(tabs.CourseTabList.iterate_displayable_cms(self.course, self.settings)),
|
||||
list(tabs.CourseTabList.iterate_displayable(self.course, self.settings, inline_collections=False)),
|
||||
)
|
||||
|
||||
def test_get_tab_by_methods(self):
|
||||
@@ -698,12 +659,13 @@ class DiscussionLinkTestCase(TabTestCase):
|
||||
"""Helper function to verify whether the discussion tab exists and can be displayed"""
|
||||
self.course.tabs = tab_list
|
||||
self.course.discussion_link = discussion_link_in_course
|
||||
discussion = tabs.CourseTabList.get_discussion(self.course)
|
||||
discussion_tab = tabs.CourseTabList.get_discussion(self.course)
|
||||
user = self.create_mock_user(is_authenticated=True, is_staff=is_staff, is_enrolled=is_enrolled)
|
||||
self.assertEquals(
|
||||
(
|
||||
discussion is not None and
|
||||
discussion.can_display(self.course, self.settings, True, is_staff, is_enrolled) and
|
||||
(discussion.link_func(self.course, self._reverse(self.course)) == expected_discussion_link)
|
||||
discussion_tab is not None and
|
||||
self.is_tab_enabled(discussion_tab, self.course, self.settings, user) and
|
||||
(discussion_tab.link_func(self.course, self._reverse(self.course)) == expected_discussion_link)
|
||||
),
|
||||
expected_can_display_value
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user