134 lines
5.6 KiB
Python
134 lines
5.6 KiB
Python
import json
|
|
import copy
|
|
|
|
from util.json_request import JsonResponse
|
|
from django.http import HttpResponseBadRequest
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.views.decorators.http import require_http_methods
|
|
from django_future.csrf import ensure_csrf_cookie
|
|
from mitxmako.shortcuts import render_to_response
|
|
from django.http import HttpResponseNotFound
|
|
from django.core.exceptions import PermissionDenied
|
|
from xmodule.modulestore.django import loc_mapper
|
|
|
|
from xmodule.modulestore.inheritance import own_metadata
|
|
|
|
|
|
from ..utils import get_modulestore
|
|
from .access import has_access
|
|
from xmodule.course_module import CourseDescriptor
|
|
from xmodule.modulestore.locator import BlockUsageLocator
|
|
|
|
__all__ = ['checklists_handler']
|
|
|
|
|
|
# pylint: disable=unused-argument
|
|
@require_http_methods(("GET", "POST", "PUT"))
|
|
@login_required
|
|
@ensure_csrf_cookie
|
|
def checklists_handler(request, tag=None, course_id=None, branch=None, version_guid=None, block=None, checklist_index=None):
|
|
"""
|
|
The restful handler for checklists.
|
|
|
|
GET
|
|
html: return html page for all checklists
|
|
json: return json representing all checklists. checklist_index is not supported for GET at this time.
|
|
POST or PUT
|
|
json: updates the checked state for items within a particular checklist. checklist_index is required.
|
|
"""
|
|
location = BlockUsageLocator(course_id=course_id, branch=branch, version_guid=version_guid, usage_id=block)
|
|
if not has_access(request.user, location):
|
|
raise PermissionDenied()
|
|
|
|
old_location = loc_mapper().translate_locator_to_location(location)
|
|
|
|
modulestore = get_modulestore(old_location)
|
|
course_module = modulestore.get_item(old_location)
|
|
|
|
json_request = 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json')
|
|
if request.method == 'GET':
|
|
# If course was created before checklists were introduced, copy them over
|
|
# from the template.
|
|
if not course_module.checklists:
|
|
course_module.checklists = CourseDescriptor.checklists.default
|
|
course_module.save()
|
|
modulestore.update_metadata(old_location, own_metadata(course_module))
|
|
|
|
expanded_checklists = expand_all_action_urls(course_module)
|
|
if json_request:
|
|
return JsonResponse(expanded_checklists)
|
|
else:
|
|
handler_url = location.url_reverse('checklists/', '')
|
|
return render_to_response('checklists.html',
|
|
{
|
|
'handler_url': handler_url,
|
|
# context_course is used by analytics
|
|
'context_course': course_module,
|
|
'checklists': expanded_checklists
|
|
})
|
|
elif json_request:
|
|
# Can now assume POST or PUT because GET handled above.
|
|
if checklist_index is not None and 0 <= int(checklist_index) < len(course_module.checklists):
|
|
index = int(checklist_index)
|
|
persisted_checklist = course_module.checklists[index]
|
|
modified_checklist = json.loads(request.body)
|
|
# Only thing the user can modify is the "checked" state.
|
|
# We don't want to persist what comes back from the client because it will
|
|
# include the expanded action URLs (which are non-portable).
|
|
for item_index, item in enumerate(modified_checklist.get('items')):
|
|
persisted_checklist['items'][item_index]['is_checked'] = item['is_checked']
|
|
# seeming noop which triggers kvs to record that the metadata is
|
|
# not default
|
|
course_module.checklists = course_module.checklists
|
|
course_module.save()
|
|
modulestore.update_metadata(old_location, own_metadata(course_module))
|
|
expanded_checklist = expand_checklist_action_url(course_module, persisted_checklist)
|
|
return JsonResponse(expanded_checklist)
|
|
else:
|
|
return HttpResponseBadRequest(
|
|
("Could not save checklist state because the checklist index "
|
|
"was out of range or unspecified."),
|
|
content_type="text/plain"
|
|
)
|
|
else:
|
|
return HttpResponseNotFound()
|
|
|
|
|
|
def expand_all_action_urls(course_module):
|
|
"""
|
|
Gets the checklists out of the course module and expands their action urls.
|
|
|
|
Returns a copy of the checklists with modified urls, without modifying the persisted version
|
|
of the checklists.
|
|
"""
|
|
expanded_checklists = []
|
|
for checklist in course_module.checklists:
|
|
expanded_checklists.append(expand_checklist_action_url(course_module, checklist))
|
|
return expanded_checklists
|
|
|
|
|
|
def expand_checklist_action_url(course_module, checklist):
|
|
"""
|
|
Expands the action URLs for a given checklist and returns the modified version.
|
|
|
|
The method does a copy of the input checklist and does not modify the input argument.
|
|
"""
|
|
expanded_checklist = copy.deepcopy(checklist)
|
|
|
|
urlconf_map = {
|
|
"ManageUsers": "course_team",
|
|
"CourseOutline": "course",
|
|
"SettingsDetails": "settings/details",
|
|
"SettingsGrading": "settings/grading",
|
|
}
|
|
|
|
for item in expanded_checklist.get('items'):
|
|
action_url = item.get('action_url')
|
|
if action_url in urlconf_map:
|
|
url_prefix = urlconf_map[action_url]
|
|
ctx_loc = course_module.location
|
|
location = loc_mapper().translate_location(ctx_loc.course_id, ctx_loc, False, True)
|
|
item['action_url'] = location.url_reverse(url_prefix, '')
|
|
|
|
return expanded_checklist
|