tabs.py 11.1 KB
Newer Older
1 2 3 4
"""
This module is essentially a broker to xmodule/tabs.py -- it was originally introduced to
perform some LMS-specific tab display gymnastics for the Entrance Exams feature
"""
5 6
from courseware.access import has_access
from courseware.entrance_exams import user_can_skip_entrance_exam
7
from django.conf import settings
8 9
from django.utils.translation import ugettext as _
from django.utils.translation import ugettext_noop
10
from openedx.core.lib.course_tabs import CourseTabPluginManager
11
from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, default_course_url_name
12
from student.models import CourseEnrollment
13
from xmodule.tabs import CourseTab, CourseTabList, course_reverse_func_from_name_func, key_checker
14 15


16
class EnrolledTab(CourseTab):
17 18 19 20 21
    """
    A base class for any view types that require a user to be enrolled.
    """
    @classmethod
    def is_enabled(cls, course, user=None):
22 23
        return user and user.is_authenticated() and \
            bool(CourseEnrollment.is_enrolled(user, course.id) or has_access(user, 'staff', course, course.id))
24 25


26
class CoursewareTab(EnrolledTab):
27 28 29
    """
    The main courseware view.
    """
30
    type = 'courseware'
31
    title = ugettext_noop('Course')
32 33 34
    priority = 10
    view_name = 'courseware'
    is_movable = False
35
    is_default = False
36
    supports_preview_menu = True
37

38 39 40 41 42 43 44 45 46 47
    @classmethod
    def is_enabled(cls, course, user=None):
        """
        Returns true if this tab is enabled.
        """
        # If this is the unified course tab then it is always enabled
        if UNIFIED_COURSE_TAB_FLAG.is_enabled(course.id):
            return True
        return super(CoursewareTab, cls).is_enabled(course, user)

48 49 50
    @property
    def link_func(self):
        """
51 52
        Returns a function that takes a course and reverse function and will
        compute the course URL for this tab.
53
        """
54 55
        reverse_name_func = lambda course: default_course_url_name(course.id)
        return course_reverse_func_from_name_func(reverse_name_func)
56

57

58
class CourseInfoTab(CourseTab):
59 60 61
    """
    The course info view.
    """
62
    type = 'course_info'
63
    title = ugettext_noop('Home')
64 65 66 67
    priority = 20
    view_name = 'info'
    tab_id = 'info'
    is_movable = False
68
    is_default = False
69 70 71

    @classmethod
    def is_enabled(cls, course, user=None):
72
        return True
73 74


75
class SyllabusTab(EnrolledTab):
76 77 78
    """
    A tab for the course syllabus.
    """
79
    type = 'syllabus'
80
    title = ugettext_noop('Syllabus')
81 82
    priority = 30
    view_name = 'syllabus'
83
    allow_multiple = True
84
    is_default = False
85 86

    @classmethod
87
    def is_enabled(cls, course, user=None):
88
        if not super(SyllabusTab, cls).is_enabled(course, user=user):
89 90 91 92
            return False
        return getattr(course, 'syllabus_present', False)


93
class ProgressTab(EnrolledTab):
94 95 96
    """
    The course progress view.
    """
97
    type = 'progress'
98
    title = ugettext_noop('Progress')
99
    priority = 40
100
    view_name = 'progress'
101
    is_hideable = True
102
    is_default = False
103 104

    @classmethod
105
    def is_enabled(cls, course, user=None):
106
        if not super(ProgressTab, cls).is_enabled(course, user=user):
107 108 109 110
            return False
        return not course.hide_progress_tab


111
class TextbookTabsBase(CourseTab):
112 113 114 115
    """
    Abstract class for textbook collection tabs classes.
    """
    # Translators: 'Textbooks' refers to the tab in the course that leads to the course' textbooks
116
    title = ugettext_noop("Textbooks")
117
    is_collection = True
118
    is_default = False
119 120

    @classmethod
121
    def is_enabled(cls, course, user=None):
122 123 124 125 126 127 128 129 130 131 132
        return user is None or user.is_authenticated()

    @classmethod
    def items(cls, course):
        """
        A generator for iterating through all the SingleTextbookTab book objects associated with this
        collection of textbooks.
        """
        raise NotImplementedError()


133
class TextbookTabs(TextbookTabsBase):
134 135 136
    """
    A tab representing the collection of all textbook tabs.
    """
137
    type = 'textbooks'
138 139 140 141
    priority = None
    view_name = 'book'

    @classmethod
142
    def is_enabled(cls, course, user=None):
143
        parent_is_enabled = super(TextbookTabs, cls).is_enabled(course, user)
144 145 146 147 148 149 150 151 152 153 154 155 156
        return settings.FEATURES.get('ENABLE_TEXTBOOK') and parent_is_enabled

    @classmethod
    def items(cls, course):
        for index, textbook in enumerate(course.textbooks):
            yield SingleTextbookTab(
                name=textbook.title,
                tab_id='textbook/{0}'.format(index),
                view_name=cls.view_name,
                index=index
            )


157
class PDFTextbookTabs(TextbookTabsBase):
158 159 160
    """
    A tab representing the collection of all PDF textbook tabs.
    """
161
    type = 'pdf_textbooks'
162 163 164 165 166 167 168 169 170 171 172 173 174 175
    priority = None
    view_name = 'pdf_book'

    @classmethod
    def items(cls, course):
        for index, textbook in enumerate(course.pdf_textbooks):
            yield SingleTextbookTab(
                name=textbook['tab_title'],
                tab_id='pdftextbook/{0}'.format(index),
                view_name=cls.view_name,
                index=index
            )


176
class HtmlTextbookTabs(TextbookTabsBase):
177 178 179
    """
    A tab representing the collection of all Html textbook tabs.
    """
180
    type = 'html_textbooks'
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
    priority = None
    view_name = 'html_book'

    @classmethod
    def items(cls, course):
        for index, textbook in enumerate(course.html_textbooks):
            yield SingleTextbookTab(
                name=textbook['tab_title'],
                tab_id='htmltextbook/{0}'.format(index),
                view_name=cls.view_name,
                index=index
            )


class LinkTab(CourseTab):
    """
    Abstract class for tabs that contain external links.
    """
    link_value = ''

    def __init__(self, tab_dict=None, name=None, link=None):
        self.link_value = tab_dict['link'] if tab_dict else link

        def link_value_func(_course, _reverse_func):
            """ Returns the link_value as the link. """
            return self.link_value

        self.type = tab_dict['type']

210 211 212
        tab_dict['link_func'] = link_value_func

        super(LinkTab, self).__init__(tab_dict)
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235

    def __getitem__(self, key):
        if key == 'link':
            return self.link_value
        else:
            return super(LinkTab, self).__getitem__(key)

    def __setitem__(self, key, value):
        if key == 'link':
            self.link_value = value
        else:
            super(LinkTab, self).__setitem__(key, value)

    def to_json(self):
        to_json_val = super(LinkTab, self).to_json()
        to_json_val.update({'link': self.link_value})
        return to_json_val

    def __eq__(self, other):
        if not super(LinkTab, self).__eq__(other):
            return False
        return self.link_value == other.get('link')

236
    @classmethod
237
    def is_enabled(cls, course, user=None):
238 239 240 241 242 243 244 245 246 247
        return True


class ExternalDiscussionCourseTab(LinkTab):
    """
    A course tab that links to an external discussion service.
    """

    type = 'external_discussion'
    # Translators: 'Discussion' refers to the tab in the courseware that leads to the discussion forums
248
    title = ugettext_noop('Discussion')
249
    priority = None
250
    is_default = False
251 252 253 254 255 256 257 258

    @classmethod
    def validate(cls, tab_dict, raise_error=True):
        """ Validate that the tab_dict for this course tab has the necessary information to render. """
        return (super(ExternalDiscussionCourseTab, cls).validate(tab_dict, raise_error) and
                key_checker(['link'])(tab_dict, raise_error))

    @classmethod
259
    def is_enabled(cls, course, user=None):
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
        if not super(ExternalDiscussionCourseTab, cls).is_enabled(course, user=user):
            return False
        return course.discussion_link


class ExternalLinkCourseTab(LinkTab):
    """
    A course tab containing an external link.
    """
    type = 'external_link'
    priority = None
    is_default = False    # An external link tab is not added to a course by default
    allow_multiple = True

    @classmethod
    def validate(cls, tab_dict, raise_error=True):
        """ Validate that the tab_dict for this course tab has the necessary information to render. """
        return (super(ExternalLinkCourseTab, cls).validate(tab_dict, raise_error) and
                key_checker(['link', 'name'])(tab_dict, raise_error))

280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295

class SingleTextbookTab(CourseTab):
    """
    A tab representing a single textbook.  It is created temporarily when enumerating all textbooks within a
    Textbook collection tab.  It should not be serialized or persisted.
    """
    type = 'single_textbook'
    is_movable = False
    is_collection_item = True
    priority = None

    def __init__(self, name, tab_id, view_name, index):
        def link_func(course, reverse_func, index=index):
            """ Constructs a link for textbooks from a view name, a course, and an index. """
            return reverse_func(view_name, args=[unicode(course.id), index])

296 297 298 299 300
        tab_dict = dict()
        tab_dict['name'] = name
        tab_dict['tab_id'] = tab_id
        tab_dict['link_func'] = link_func
        super(SingleTextbookTab, self).__init__(tab_dict)
301 302 303

    def to_json(self):
        raise NotImplementedError('SingleTextbookTab should not be serialized.')
304 305


306
def get_course_tab_list(request, course):
307 308 309
    """
    Retrieves the course tab list from xmodule.tabs and manipulates the set as necessary
    """
310
    user = request.user
311
    xmodule_tab_list = CourseTabList.iterate_displayable(course, user=user)
312

313 314 315
    # Now that we've loaded the tabs for this course, perform the Entrance Exam work.
    # If the user has to take an entrance exam, we'll need to hide away all but the
    # "Courseware" tab. The tab is then renamed as "Entrance Exam".
316
    course_tab_list = []
317
    must_complete_ee = not user_can_skip_entrance_exam(user, course)
318
    for tab in xmodule_tab_list:
319
        if must_complete_ee:
320
            # Hide all of the tabs except for 'Courseware'
321
            # Rename 'Courseware' tab to 'Entrance Exam'
322
            if tab.type != 'courseware':
323
                continue
324
            tab.name = _("Entrance Exam")
325 326 327
        # TODO: LEARNER-611 - once the course_info tab is removed, remove this code
        if UNIFIED_COURSE_TAB_FLAG.is_enabled(course.id) and tab.type == 'course_info':
                continue
328
        if tab.type == 'static_tab' and tab.course_staff_only and \
Dmitry Viskov committed
329
                not bool(user and has_access(user, 'staff', course, course.id)):
330
            continue
331
        course_tab_list.append(tab)
332 333 334

    # Add in any dynamic tabs, i.e. those that are not persisted
    course_tab_list += _get_dynamic_tabs(course, user)
335
    return course_tab_list
336 337 338 339 340 341 342 343 344 345


def _get_dynamic_tabs(course, user):
    """
    Returns the dynamic tab types for the current user.

    Note: dynamic tabs are those that are not persisted in the course, but are
    instead added dynamically based upon the user's role.
    """
    dynamic_tabs = list()
346
    for tab_type in CourseTabPluginManager.get_tab_types():
347
        if getattr(tab_type, "is_dynamic", False):
348
            tab = tab_type(dict())
349
            if tab.is_enabled(course, user=user):
350 351 352
                dynamic_tabs.append(tab)
    dynamic_tabs.sort(key=lambda dynamic_tab: dynamic_tab.name)
    return dynamic_tabs