tabs.py 7.79 KB
Newer Older
David Baumgold committed
1 2 3
"""
Views related to course tabs
"""
Steve Strassmann committed
4 5
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
6
from django.http import HttpResponseNotFound
7
from django.views.decorators.csrf import ensure_csrf_cookie
cahrens committed
8
from django.views.decorators.http import require_http_methods
9
from opaque_keys.edx.keys import CourseKey, UsageKey
10

David Baumgold committed
11
from edxmako.shortcuts import render_to_response
12 13
from student.auth import has_course_author_access
from util.json_request import JsonResponse, expect_json
14
from xmodule.modulestore import ModuleStoreEnum
15 16
from xmodule.modulestore.django import modulestore
from xmodule.tabs import CourseTab, CourseTabList, InvalidTabsException, StaticTab
17

18
from ..utils import get_lms_link_for_item
Steve Strassmann committed
19

cahrens committed
20
__all__ = ['tabs_handler']
Steve Strassmann committed
21

22

Steve Strassmann committed
23 24 25
@expect_json
@login_required
@ensure_csrf_cookie
cahrens committed
26
@require_http_methods(("GET", "POST", "PUT"))
27
def tabs_handler(request, course_key_string):
cahrens committed
28 29
    """
    The restful handler for static tabs.
Steve Strassmann committed
30

cahrens committed
31 32 33 34 35 36
    GET
        html: return page for editing static tabs
        json: not supported
    PUT or POST
        json: update the tab order. It is expected that the request body contains a JSON-encoded dict with entry "tabs".
        The value for "tabs" is an array of tab locators, indicating the desired order of the tabs.
Steve Strassmann committed
37

cahrens committed
38 39 40
    Creating a tab, deleting a tab, or changing its contents is not supported through this method.
    Instead use the general xblock URL (see item.xblock_handler).
    """
41
    course_key = CourseKey.from_string(course_key_string)
42
    if not has_course_author_access(request.user, course_key):
cahrens committed
43
        raise PermissionDenied()
Steve Strassmann committed
44

45
    course_item = modulestore().get_course(course_key)
46

cahrens committed
47 48 49 50 51
    if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
        if request.method == 'GET':
            raise NotImplementedError('coming soon')
        else:
            if 'tabs' in request.json:
52 53 54
                return reorder_tabs_handler(course_item, request)
            elif 'tab_id_locator' in request.json:
                return edit_tab_handler(course_item, request)
cahrens committed
55 56
            else:
                raise NotImplementedError('Creating or changing tab content is not supported.')
57

cahrens committed
58
    elif request.method == 'GET':  # assume html
59
        # get all tabs from the tabs list: static tabs (a.k.a. user-created tabs) and built-in tabs
60
        # present in the same order they are displayed in LMS
cahrens committed
61

62
        tabs_to_render = []
63 64
        for tab in CourseTabList.iterate_displayable(course_item, user=request.user, inline_collections=False,
                                                     include_hidden=True):
65
            if isinstance(tab, StaticTab):
66
                # static tab needs its locator information to render itself as an xmodule
67 68
                static_tab_loc = course_key.make_usage_key('static_tab', tab.url_slug)
                tab.locator = static_tab_loc
69
            tabs_to_render.append(tab)
cahrens committed
70 71 72

        return render_to_response('edit-tabs.html', {
            'context_course': course_item,
73
            'tabs_to_render': tabs_to_render,
74
            'lms_link': get_lms_link_for_item(course_item.location),
cahrens committed
75 76 77
        })
    else:
        return HttpResponseNotFound()
78 79


80 81 82 83 84 85 86 87 88
def reorder_tabs_handler(course_item, request):
    """
    Helper function for handling reorder of tabs request
    """

    # Tabs are identified by tab_id or locators.
    # The locators are used to identify static tabs since they are xmodules.
    # Although all tabs have tab_ids, newly created static tabs do not know
    # their tab_ids since the xmodule editor uses only locators to identify new objects.
89
    requested_tab_id_locators = request.json['tabs']
90 91 92 93 94 95

    # original tab list in original order
    old_tab_list = course_item.tabs

    # create a new list in the new order
    new_tab_list = []
96
    for tab_id_locator in requested_tab_id_locators:
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
        tab = get_tab_by_tab_id_locator(old_tab_list, tab_id_locator)
        if tab is None:
            return JsonResponse(
                {"error": "Tab with id_locator '{0}' does not exist.".format(tab_id_locator)}, status=400
            )
        new_tab_list.append(tab)

    # the old_tab_list may contain additional tabs that were not rendered in the UI because of
    # global or course settings.  so add those to the end of the list.
    non_displayed_tabs = set(old_tab_list) - set(new_tab_list)
    new_tab_list.extend(non_displayed_tabs)

    # validate the tabs to make sure everything is Ok (e.g., did the client try to reorder unmovable tabs?)
    try:
        CourseTabList.validate_tabs(new_tab_list)
    except InvalidTabsException, exception:
        return JsonResponse(
            {"error": "New list of tabs is not valid: {0}.".format(str(exception))}, status=400
        )

    # persist the new order of the tabs
    course_item.tabs = new_tab_list
119
    modulestore().update_item(course_item, request.user.id)
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141

    return JsonResponse()


def edit_tab_handler(course_item, request):
    """
    Helper function for handling requests to edit settings of a single tab
    """

    # Tabs are identified by tab_id or locator
    tab_id_locator = request.json['tab_id_locator']

    # Find the given tab in the course
    tab = get_tab_by_tab_id_locator(course_item.tabs, tab_id_locator)
    if tab is None:
        return JsonResponse(
            {"error": "Tab with id_locator '{0}' does not exist.".format(tab_id_locator)}, status=400
        )

    if 'is_hidden' in request.json:
        # set the is_hidden attribute on the requested tab
        tab.is_hidden = request.json['is_hidden']
142
        modulestore().update_item(course_item, request.user.id)
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
    else:
        raise NotImplementedError('Unsupported request to edit tab: {0}'.format(request.json))

    return JsonResponse()


def get_tab_by_tab_id_locator(tab_list, tab_id_locator):
    """
    Look for a tab with the specified tab_id or locator.  Returns the first matching tab.
    """
    if 'tab_id' in tab_id_locator:
        tab = CourseTabList.get_tab_by_id(tab_list, tab_id_locator['tab_id'])
    elif 'tab_locator' in tab_id_locator:
        tab = get_tab_by_locator(tab_list, tab_id_locator['tab_locator'])
    return tab


160
def get_tab_by_locator(tab_list, usage_key_string):
161 162 163
    """
    Look for a tab with the specified locator.  Returns the first matching tab.
    """
164
    tab_location = UsageKey.from_string(usage_key_string)
165
    item = modulestore().get_item(tab_location)
166 167 168 169 170 171 172
    static_tab = StaticTab(
        name=item.display_name,
        url_slug=item.location.name,
    )
    return CourseTabList.get_tab_by_id(tab_list, static_tab.tab_id)


173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
# "primitive" tab edit functions driven by the command line.
# These should be replaced/deleted by a more capable GUI someday.
# Note that the command line UI identifies the tabs with 1-based
# indexing, but this implementation code is standard 0-based.

def validate_args(num, tab_type):
    "Throws for the disallowed cases."
    if num <= 1:
        raise ValueError('Tabs 1 and 2 cannot be edited')
    if tab_type == 'static_tab':
        raise ValueError('Tabs of type static_tab cannot be edited here (use Studio)')


def primitive_delete(course, num):
    "Deletes the given tab number (0 based)."
    tabs = course.tabs
    validate_args(num, tabs[num].get('type', ''))
    del tabs[num]
    # Note for future implementations: if you delete a static_tab, then Chris Dodge
    # points out that there's other stuff to delete beyond this element.
    # This code happens to not delete static_tab so it doesn't come up.
194
    modulestore().update_item(course, ModuleStoreEnum.UserID.primitive_command)
195 196 197 198 199


def primitive_insert(course, num, tab_type, name):
    "Inserts a new tab at the given number (0 based)."
    validate_args(num, tab_type)
200
    new_tab = CourseTab.from_json({u'type': unicode(tab_type), u'name': unicode(name)})
201 202
    tabs = course.tabs
    tabs.insert(num, new_tab)
203
    modulestore().update_item(course, ModuleStoreEnum.UserID.primitive_command)