Commit 3ed41982 by Julian Arni

Contentstore views pylint fixes

parent c65c9eb0
...@@ -26,12 +26,16 @@ def has_access(user, location, role=STAFF_ROLE_NAME): ...@@ -26,12 +26,16 @@ def has_access(user, location, role=STAFF_ROLE_NAME):
There is a super-admin permissions if user.is_staff is set There is a super-admin permissions if user.is_staff is set
Also, since we're unifying the user database between LMS and CAS, Also, since we're unifying the user database between LMS and CAS,
I'm presuming that the course instructor (formally known as admin) I'm presuming that the course instructor (formally known as admin)
will not be in both INSTRUCTOR and STAFF groups, so we have to cascade our queries here as INSTRUCTOR will not be in both INSTRUCTOR and STAFF groups, so we have to cascade our
has all the rights that STAFF do queries here as INSTRUCTOR has all the rights that STAFF do
''' '''
course_location = get_course_location_for_item(location) course_location = get_course_location_for_item(location)
_has_access = is_user_in_course_group_role(user, course_location, role) _has_access = is_user_in_course_group_role(user, course_location, role)
# if we're not in STAFF, perhaps we're in INSTRUCTOR groups # if we're not in STAFF, perhaps we're in INSTRUCTOR groups
if not _has_access and role == STAFF_ROLE_NAME: if not _has_access and role == STAFF_ROLE_NAME:
_has_access = is_user_in_course_group_role(user, course_location, INSTRUCTOR_ROLE_NAME) _has_access = is_user_in_course_group_role(
user,
course_location,
INSTRUCTOR_ROLE_NAME
)
return _has_access return _has_access
...@@ -4,6 +4,7 @@ import os ...@@ -4,6 +4,7 @@ import os
import tarfile import tarfile
import shutil import shutil
import cgi import cgi
from functools import partial
from tempfile import mkdtemp from tempfile import mkdtemp
from path import path from path import path
...@@ -34,7 +35,8 @@ from .access import get_location_and_verify_access ...@@ -34,7 +35,8 @@ from .access import get_location_and_verify_access
from util.json_request import JsonResponse from util.json_request import JsonResponse
__all__ = ['asset_index', 'upload_asset', 'import_course', 'generate_export_course', 'export_course'] __all__ = ['asset_index', 'upload_asset', 'import_course',
'generate_export_course', 'export_course']
def assets_to_json_dict(assets): def assets_to_json_dict(assets):
...@@ -58,13 +60,14 @@ def assets_to_json_dict(assets): ...@@ -58,13 +60,14 @@ def assets_to_json_dict(assets):
obj["thumbnail"] = thumbnail obj["thumbnail"] = thumbnail
id_info = asset.get("_id") id_info = asset.get("_id")
if id_info: if id_info:
obj["id"] = "/{tag}/{org}/{course}/{revision}/{category}/{name}".format( obj["id"] = "/{tag}/{org}/{course}/{revision}/{category}/{name}" \
org=id_info.get("org", ""), .format(
course=id_info.get("course", ""), org=id_info.get("org", ""),
revision=id_info.get("revision", ""), course=id_info.get("course", ""),
tag=id_info.get("tag", ""), revision=id_info.get("revision", ""),
category=id_info.get("category", ""), tag=id_info.get("tag", ""),
name=id_info.get("name", ""), category=id_info.get("category", ""),
name=id_info.get("name", ""),
) )
ret.append(obj) ret.append(obj)
return ret return ret
...@@ -132,14 +135,14 @@ def asset_index(request, org, course, name): ...@@ -132,14 +135,14 @@ def asset_index(request, org, course, name):
@login_required @login_required
def upload_asset(request, org, course, coursename): def upload_asset(request, org, course, coursename):
''' '''
This method allows for POST uploading of files into the course asset library, which will This method allows for POST uploading of files into the course asset
be supported by GridFS in MongoDB. library, which will be supported by GridFS in MongoDB.
''' '''
# construct a location from the passed in path # construct a location from the passed in path
location = get_location_and_verify_access(request, org, course, coursename) location = get_location_and_verify_access(request, org, course, coursename)
# Does the course actually exist?!? Get anything from it to prove its existance # Does the course actually exist?!? Get anything from it to prove its
# existence
try: try:
modulestore().get_item(location) modulestore().get_item(location)
except: except:
...@@ -150,9 +153,10 @@ def upload_asset(request, org, course, coursename): ...@@ -150,9 +153,10 @@ def upload_asset(request, org, course, coursename):
if 'file' not in request.FILES: if 'file' not in request.FILES:
return HttpResponseBadRequest() return HttpResponseBadRequest()
# compute a 'filename' which is similar to the location formatting, we're using the 'filename' # compute a 'filename' which is similar to the location formatting, we're
# nomenclature since we're using a FileSystem paradigm here. We're just imposing # using the 'filename' nomenclature since we're using a FileSystem paradigm
# the Location string formatting expectations to keep things a bit more consistent # here. We're just imposing the Location string formatting expectations to
# keep things a bit more consistent
upload_file = request.FILES['file'] upload_file = request.FILES['file']
filename = upload_file.name filename = upload_file.name
mime_type = upload_file.content_type mime_type = upload_file.content_type
...@@ -160,20 +164,25 @@ def upload_asset(request, org, course, coursename): ...@@ -160,20 +164,25 @@ def upload_asset(request, org, course, coursename):
content_loc = StaticContent.compute_location(org, course, filename) content_loc = StaticContent.compute_location(org, course, filename)
chunked = upload_file.multiple_chunks() chunked = upload_file.multiple_chunks()
sc_partial = partial(StaticContent, content_loc, filename, mime_type)
if chunked: if chunked:
content = StaticContent(content_loc, filename, mime_type, upload_file.chunks()) content = sc_partial(upload_file.chunks())
temp_filepath = upload_file.temporary_file_path()
else: else:
content = StaticContent(content_loc, filename, mime_type, upload_file.read()) content = sc_partial(upload_file.read())
tempfile_path = None
thumbnail_content = None thumbnail_content = None
thumbnail_location = None thumbnail_location = None
# first let's see if a thumbnail can be created # first let's see if a thumbnail can be created
(thumbnail_content, thumbnail_location) = contentstore().generate_thumbnail(content, (thumbnail_content, thumbnail_location) = contentstore().generate_thumbnail(
tempfile_path=None if not chunked else content,
upload_file.temporary_file_path()) tempfile_path=tempfile_path
)
# delete cached thumbnail even if one couldn't be created this time (else the old thumbnail will continue to show) # delete cached thumbnail even if one couldn't be created this time (else
# the old thumbnail will continue to show)
del_cached_content(thumbnail_location) del_cached_content(thumbnail_location)
# now store thumbnail location only if we could create it # now store thumbnail location only if we could create it
if thumbnail_content is not None: if thumbnail_content is not None:
...@@ -186,13 +195,15 @@ def upload_asset(request, org, course, coursename): ...@@ -186,13 +195,15 @@ def upload_asset(request, org, course, coursename):
# readback the saved content - we need the database timestamp # readback the saved content - we need the database timestamp
readback = contentstore().find(content.location) readback = contentstore().find(content.location)
response_payload = {'displayname': content.name, response_payload = {
'uploadDate': get_default_time_display(readback.last_modified_at), 'displayname': content.name,
'url': StaticContent.get_url_path_from_location(content.location), 'uploadDate': get_default_time_display(readback.last_modified_at),
'portable_url': StaticContent.get_static_path_from_location(content.location), 'url': StaticContent.get_url_path_from_location(content.location),
'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None, 'portable_url': StaticContent.get_static_path_from_location(content.location),
'msg': 'Upload completed' 'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location)
} if thumbnail_content is not None else None,
'msg': 'Upload completed'
}
response = JsonResponse(response_payload) response = JsonResponse(response_payload)
return response return response
...@@ -202,8 +213,8 @@ def upload_asset(request, org, course, coursename): ...@@ -202,8 +213,8 @@ def upload_asset(request, org, course, coursename):
@login_required @login_required
def remove_asset(request, org, course, name): def remove_asset(request, org, course, name):
''' '''
This method will perform a 'soft-delete' of an asset, which is basically to copy the asset from This method will perform a 'soft-delete' of an asset, which is basically to
the main GridFS collection and into a Trashcan copy the asset from the main GridFS collection and into a Trashcan
''' '''
get_location_and_verify_access(request, org, course, name) get_location_and_verify_access(request, org, course, name)
......
...@@ -30,7 +30,8 @@ def get_checklists(request, org, course, name): ...@@ -30,7 +30,8 @@ def get_checklists(request, org, course, name):
modulestore = get_modulestore(location) modulestore = get_modulestore(location)
course_module = modulestore.get_item(location) course_module = modulestore.get_item(location)
# If course was created before checklists were introduced, copy them over from the template. # If course was created before checklists were introduced, copy them over
# from the template.
copied = False copied = False
if not course_module.checklists: if not course_module.checklists:
course_module.checklists = CourseDescriptor.checklists.default course_module.checklists = CourseDescriptor.checklists.default
...@@ -68,7 +69,8 @@ def update_checklist(request, org, course, name, checklist_index=None): ...@@ -68,7 +69,8 @@ def update_checklist(request, org, course, name, checklist_index=None):
if checklist_index is not None and 0 <= int(checklist_index) < len(course_module.checklists): if checklist_index is not None and 0 <= int(checklist_index) < len(course_module.checklists):
index = int(checklist_index) index = int(checklist_index)
course_module.checklists[index] = json.loads(request.body) course_module.checklists[index] = json.loads(request.body)
# seeming noop which triggers kvs to record that the metadata is not default # seeming noop which triggers kvs to record that the metadata is
# not default
course_module.checklists = course_module.checklists course_module.checklists = course_module.checklists
checklists, _ = expand_checklist_action_urls(course_module) checklists, _ = expand_checklist_action_urls(course_module)
course_module.save() course_module.save()
...@@ -76,10 +78,13 @@ def update_checklist(request, org, course, name, checklist_index=None): ...@@ -76,10 +78,13 @@ def update_checklist(request, org, course, name, checklist_index=None):
return JsonResponse(checklists[index]) return JsonResponse(checklists[index])
else: else:
return HttpResponseBadRequest( return HttpResponseBadRequest(
"Could not save checklist state because the checklist index was out of range or unspecified.", ( "Could not save checklist state because the checklist index "
content_type="text/plain") "was out of range or unspecified."),
content_type="text/plain"
)
elif request.method == 'GET': elif request.method == 'GET':
# In the JavaScript view initialize method, we do a fetch to get all the checklists. # In the JavaScript view initialize method, we do a fetch to get all
# the checklists.
checklists, modified = expand_checklist_action_urls(course_module) checklists, modified = expand_checklist_action_urls(course_module)
if modified: if modified:
course_module.save() course_module.save()
......
...@@ -2,7 +2,8 @@ import json ...@@ -2,7 +2,8 @@ import json
import logging import logging
from collections import defaultdict from collections import defaultdict
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden from django.http import HttpResponse, HttpResponseBadRequest, \
HttpResponseForbidden
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods from django.views.decorators.http import require_http_methods
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
...@@ -72,10 +73,15 @@ def edit_subsection(request, location): ...@@ -72,10 +73,15 @@ def edit_subsection(request, location):
except ItemNotFoundError: except ItemNotFoundError:
return HttpResponseBadRequest() return HttpResponseBadRequest()
lms_link = get_lms_link_for_item(location, course_id=course.location.course_id) lms_link = get_lms_link_for_item(
preview_link = get_lms_link_for_item(location, course_id=course.location.course_id, preview=True) location, course_id=course.location.course_id
)
preview_link = get_lms_link_for_item(
location, course_id=course.location.course_id, preview=True
)
# make sure that location references a 'sequential', otherwise return BadRequest # make sure that location references a 'sequential', otherwise return
# BadRequest
if item.location.category != 'sequential': if item.location.category != 'sequential':
return HttpResponseBadRequest() return HttpResponseBadRequest()
...@@ -83,18 +89,22 @@ def edit_subsection(request, location): ...@@ -83,18 +89,22 @@ def edit_subsection(request, location):
# we're for now assuming a single parent # we're for now assuming a single parent
if len(parent_locs) != 1: if len(parent_locs) != 1:
logging.error('Multiple (or none) parents have been found for {0}'.format(location)) logging.error(
'Multiple (or none) parents have been found for' + location
)
# this should blow up if we don't find any parents, which would be erroneous # this should blow up if we don't find any parents, which would be erroneous
parent = modulestore().get_item(parent_locs[0]) parent = modulestore().get_item(parent_locs[0])
# remove all metadata from the generic dictionary that is presented in a more normalized UI # remove all metadata from the generic dictionary that is presented in a
# more normalized UI
policy_metadata = dict( policy_metadata = dict(
(field.name, field.read_from(item)) (field.name, field.read_from(item))
for field for field
in item.fields in item.fields
if field.name not in ['display_name', 'start', 'due', 'format'] and field.scope == Scope.settings if field.name not in ['display_name', 'start', 'due', 'format']
and field.scope == Scope.settings
) )
can_view_live = False can_view_live = False
...@@ -105,19 +115,22 @@ def edit_subsection(request, location): ...@@ -105,19 +115,22 @@ def edit_subsection(request, location):
can_view_live = True can_view_live = True
break break
return render_to_response('edit_subsection.html', return render_to_response(
{'subsection': item, 'edit_subsection.html',
'context_course': course, {
'new_unit_category': 'vertical', 'subsection': item,
'lms_link': lms_link, 'context_course': course,
'preview_link': preview_link, 'new_unit_category': 'vertical',
'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders), 'lms_link': lms_link,
'parent_location': course.location, 'preview_link': preview_link,
'parent_item': parent, 'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders),
'policy_metadata': policy_metadata, 'parent_location': course.location,
'subsection_units': subsection_units, 'parent_item': parent,
'can_view_live': can_view_live 'policy_metadata': policy_metadata,
}) 'subsection_units': subsection_units,
'can_view_live': can_view_live
}
)
@login_required @login_required
...@@ -125,7 +138,7 @@ def edit_unit(request, location): ...@@ -125,7 +138,7 @@ def edit_unit(request, location):
""" """
Display an editing page for the specified module. Display an editing page for the specified module.
Expects a GET request with the parameter 'id'. Expects a GET request with the parameter `id`.
id: A Location URL id: A Location URL
""" """
...@@ -141,7 +154,10 @@ def edit_unit(request, location): ...@@ -141,7 +154,10 @@ def edit_unit(request, location):
item = modulestore().get_item(location, depth=1) item = modulestore().get_item(location, depth=1)
except ItemNotFoundError: except ItemNotFoundError:
return HttpResponseBadRequest() return HttpResponseBadRequest()
lms_link = get_lms_link_for_item(item.location, course_id=course.location.course_id) lms_link = get_lms_link_for_item(
item.location,
course_id=course.location.course_id
)
component_templates = defaultdict(list) component_templates = defaultdict(list)
for category in COMPONENT_TYPES: for category in COMPONENT_TYPES:
...@@ -162,17 +178,19 @@ def edit_unit(request, location): ...@@ -162,17 +178,19 @@ def edit_unit(request, location):
template.get('template_id') template.get('template_id')
)) ))
# Check if there are any advanced modules specified in the course policy. These modules # Check if there are any advanced modules specified in the course policy.
# should be specified as a list of strings, where the strings are the names of the modules # These modules should be specified as a list of strings, where the strings
# in ADVANCED_COMPONENT_TYPES that should be enabled for the course. # are the names of the modules in ADVANCED_COMPONENT_TYPES that should be
# enabled for the course.
course_advanced_keys = course.advanced_modules course_advanced_keys = course.advanced_modules
# Set component types according to course policy file # Set component types according to course policy file
if isinstance(course_advanced_keys, list): if isinstance(course_advanced_keys, list):
for category in course_advanced_keys: for category in course_advanced_keys:
if category in ADVANCED_COMPONENT_TYPES: if category in ADVANCED_COMPONENT_TYPES:
# Do I need to allow for boilerplates or just defaults on the class? i.e., can an advanced # Do I need to allow for boilerplates or just defaults on the
# have more than one entry in the menu? one for default and others for prefilled boilerplates? # class? i.e., can an advanced have more than one entry in the
# menu? one for default and others for prefilled boilerplates?
try: try:
component_class = XModuleDescriptor.load_class(category) component_class = XModuleDescriptor.load_class(category)
...@@ -183,13 +201,16 @@ def edit_unit(request, location): ...@@ -183,13 +201,16 @@ def edit_unit(request, location):
None # don't override default data None # don't override default data
)) ))
except PluginMissingError: except PluginMissingError:
# dhm: I got this once but it can happen any time the course author configures # dhm: I got this once but it can happen any time the
# an advanced component which does not exist on the server. This code here merely # course author configures an advanced component which does
# prevents any authors from trying to instantiate the non-existent component type # not exist on the server. This code here merely
# by not showing it in the menu # prevents any authors from trying to instantiate the
# non-existent component type by not showing it in the menu
pass pass
else: else:
log.error("Improper format for course advanced keys! {0}".format(course_advanced_keys)) log.error(
"Improper format for course advanced keys!" + course_advanced_keys
)
components = [ components = [
component.location.url() component.location.url()
...@@ -201,16 +222,20 @@ def edit_unit(request, location): ...@@ -201,16 +222,20 @@ def edit_unit(request, location):
# this will need to change to check permissions correctly so as # this will need to change to check permissions correctly so as
# to pick the correct parent subsection # to pick the correct parent subsection
containing_subsection_locs = modulestore().get_parent_locations(location, None) containing_subsection_locs = modulestore().get_parent_locations(
location, None
)
containing_subsection = modulestore().get_item(containing_subsection_locs[0]) containing_subsection = modulestore().get_item(containing_subsection_locs[0])
containing_section_locs = modulestore().get_parent_locations(
containing_section_locs = modulestore().get_parent_locations(containing_subsection.location, None) containing_subsection.location, None
)
containing_section = modulestore().get_item(containing_section_locs[0]) containing_section = modulestore().get_item(containing_section_locs[0])
# cdodge hack. We're having trouble previewing drafts via jump_to redirect # cdodge hack. We're having trouble previewing drafts via jump_to redirect
# so let's generate the link url here # so let's generate the link url here
# need to figure out where this item is in the list of children as the preview will need this # need to figure out where this item is in the list of children as the
# preview will need this
index = 1 index = 1
for child in containing_subsection.get_children(): for child in containing_subsection.get_children():
if child.location == item.location: if child.location == item.location:
...@@ -219,15 +244,19 @@ def edit_unit(request, location): ...@@ -219,15 +244,19 @@ def edit_unit(request, location):
preview_lms_base = settings.MITX_FEATURES.get('PREVIEW_LMS_BASE') preview_lms_base = settings.MITX_FEATURES.get('PREVIEW_LMS_BASE')
preview_lms_link = '//{preview_lms_base}/courses/{org}/{course}/{course_name}/courseware/{section}/{subsection}/{index}'.format( preview_lms_link = (
preview_lms_base=preview_lms_base, '//{preview_lms_base}/courses/{org}/{course}/'
lms_base=settings.LMS_BASE, '{course_name}/courseware/{section}/{subsection}/{index}'
org=course.location.org, ).format(
course=course.location.course, preview_lms_base=preview_lms_base,
course_name=course.location.name, lms_base=settings.LMS_BASE,
section=containing_section.location.name, org=course.location.org,
subsection=containing_subsection.location.name, course=course.location.course,
index=index) course_name=course.location.name,
section=containing_section.location.name,
subsection=containing_subsection.location.name,
index=index
)
unit_state = compute_unit_state(item) unit_state = compute_unit_state(item)
...@@ -240,11 +269,13 @@ def edit_unit(request, location): ...@@ -240,11 +269,13 @@ def edit_unit(request, location):
'draft_preview_link': preview_lms_link, 'draft_preview_link': preview_lms_link,
'published_preview_link': lms_link, 'published_preview_link': lms_link,
'subsection': containing_subsection, 'subsection': containing_subsection,
'release_date': get_default_time_display(containing_subsection.lms.start) if containing_subsection.lms.start is not None else None, 'release_date': get_default_time_display(containing_subsection.lms.start)
if containing_subsection.lms.start is not None else None,
'section': containing_section, 'section': containing_section,
'new_unit_category': 'vertical', 'new_unit_category': 'vertical',
'unit_state': unit_state, 'unit_state': unit_state,
'published_date': get_default_time_display(item.cms.published_date) if item.cms.published_date is not None else None 'published_date': get_default_time_display(item.cms.published_date)
if item.cms.published_date is not None else None
}) })
...@@ -253,9 +284,10 @@ def edit_unit(request, location): ...@@ -253,9 +284,10 @@ def edit_unit(request, location):
@require_http_methods(("GET", "POST", "PUT")) @require_http_methods(("GET", "POST", "PUT"))
@ensure_csrf_cookie @ensure_csrf_cookie
def assignment_type_update(request, org, course, category, name): def assignment_type_update(request, org, course, category, name):
''' """
CRUD operations on assignment types for sections and subsections and anything else gradable. CRUD operations on assignment types for sections and subsections and
''' anything else gradable.
"""
location = Location(['i4x', org, course, category, name]) location = Location(['i4x', org, course, category, name])
if not has_access(request.user, location): if not has_access(request.user, location):
return HttpResponseForbidden() return HttpResponseForbidden()
...@@ -263,7 +295,9 @@ def assignment_type_update(request, org, course, category, name): ...@@ -263,7 +295,9 @@ def assignment_type_update(request, org, course, category, name):
if request.method == 'GET': if request.method == 'GET':
return JsonResponse(CourseGradingModel.get_section_grader_type(location)) return JsonResponse(CourseGradingModel.get_section_grader_type(location))
elif request.method in ('POST', 'PUT'): # post or put, doesn't matter. elif request.method in ('POST', 'PUT'): # post or put, doesn't matter.
return JsonResponse(CourseGradingModel.update_section_grader_type(location, request.POST)) return JsonResponse(CourseGradingModel.update_section_grader_type(
location, request.POST
))
@login_required @login_required
...@@ -276,8 +310,8 @@ def create_draft(request): ...@@ -276,8 +310,8 @@ def create_draft(request):
if not has_access(request.user, location): if not has_access(request.user, location):
raise PermissionDenied() raise PermissionDenied()
# This clones the existing item location to a draft location (the draft is implicit, # This clones the existing item location to a draft location (the draft is
# because modulestore is a Draft modulestore) # implicit, because modulestore is a Draft modulestore)
modulestore().convert_to_draft(location) modulestore().convert_to_draft(location)
return HttpResponse() return HttpResponse()
...@@ -286,7 +320,9 @@ def create_draft(request): ...@@ -286,7 +320,9 @@ def create_draft(request):
@login_required @login_required
@expect_json @expect_json
def publish_draft(request): def publish_draft(request):
"Publish a draft" """
Publish a draft
"""
location = request.POST['id'] location = request.POST['id']
# check permissions for this user within this course # check permissions for this user within this course
...@@ -294,7 +330,10 @@ def publish_draft(request): ...@@ -294,7 +330,10 @@ def publish_draft(request):
raise PermissionDenied() raise PermissionDenied()
item = modulestore().get_item(location) item = modulestore().get_item(location)
_xmodule_recurse(item, lambda i: modulestore().publish(i.location, request.user.id)) _xmodule_recurse(
item,
lambda i: modulestore().publish(i.location, request.user.id)
)
return HttpResponse() return HttpResponse()
...@@ -328,13 +367,23 @@ def module_info(request, module_location): ...@@ -328,13 +367,23 @@ def module_info(request, module_location):
raise PermissionDenied() raise PermissionDenied()
rewrite_static_links = request.GET.get('rewrite_url_links', 'True') in ['True', 'true'] rewrite_static_links = request.GET.get('rewrite_url_links', 'True') in ['True', 'true']
logging.debug('rewrite_static_links = {0} {1}'.format(request.GET.get('rewrite_url_links', 'False'), rewrite_static_links)) logging.debug('rewrite_static_links = {0} {1}'.format(
request.GET.get('rewrite_url_links', 'False'),
rewrite_static_links)
)
# check that logged in user has permissions to this item # check that logged in user has permissions to this item
if not has_access(request.user, location): if not has_access(request.user, location):
raise PermissionDenied() raise PermissionDenied()
if request.method == 'GET': if request.method == 'GET':
return JsonResponse(get_module_info(get_modulestore(location), location, rewrite_static_links=rewrite_static_links)) return JsonResponse(get_module_info(
get_modulestore(location),
location,
rewrite_static_links=rewrite_static_links
))
elif request.method in ("POST", "PUT"): elif request.method in ("POST", "PUT"):
return JsonResponse(set_module_info(get_modulestore(location), location, request.POST)) return JsonResponse(set_module_info(
get_modulestore(location),
location, request.POST
))
...@@ -82,7 +82,9 @@ def course_index(request, org, course, name): ...@@ -82,7 +82,9 @@ def course_index(request, org, course, name):
'context_course': course, 'context_course': course,
'lms_link': lms_link, 'lms_link': lms_link,
'sections': sections, 'sections': sections,
'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders), 'course_graders': json.dumps(
CourseGradingModel.fetch(course.location).graders
),
'parent_location': course.location, 'parent_location': course.location,
'new_section_category': 'chapter', 'new_section_category': 'chapter',
'new_subsection_category': 'sequential', 'new_subsection_category': 'sequential',
...@@ -120,24 +122,31 @@ def create_new_course(request): ...@@ -120,24 +122,31 @@ def create_new_course(request):
except ItemNotFoundError: except ItemNotFoundError:
pass pass
if existing_course is not None: if existing_course is not None:
return JsonResponse( return JsonResponse({
{ 'ErrMsg': _(('There is already a course defined with the same '
'ErrMsg': _('There is already a course defined with the same organization, course number, and course run. Please change either organization or course number to be unique.'), 'organization, course number, and course run. Please '
'OrgErrMsg': _('Please change either the organization or course number so that it is unique.'), 'change either organization or course number to be '
'CourseErrMsg': _('Please change either the organization or course number so that it is unique.'), 'unique.')),
} 'OrgErrMsg': _(('Please change either the organization or '
) 'course number so that it is unique.')),
'CourseErrMsg': _(('Please change either the organization or '
'course number so that it is unique.')),
})
course_search_location = ['i4x', dest_location.org, dest_location.course, 'course', None] course_search_location = ['i4x', dest_location.org, dest_location.course,
'course', None
]
courses = modulestore().get_items(course_search_location) courses = modulestore().get_items(course_search_location)
if len(courses) > 0: if len(courses) > 0:
return JsonResponse( return JsonResponse({
{ 'ErrMsg': _(('There is already a course defined with the same '
'ErrMsg': _('There is already a course defined with the same organization and course number. Please change at least one field to be unique.'), 'organization and course number. Please '
'OrgErrMsg': _('Please change either the organization or course number so that it is unique.'), 'change at least one field to be unique.')),
'CourseErrMsg': _('Please change either the organization or course number so that it is unique.'), 'OrgErrMsg': _(('Please change either the organization or '
} 'course number so that it is unique.')),
) 'CourseErrMsg': _(('Please change either the organization or '
'course number so that it is unique.')),
})
# instantiate the CourseDescriptor and then persist it # instantiate the CourseDescriptor and then persist it
# note: no system to pass # note: no system to pass
...@@ -145,11 +154,17 @@ def create_new_course(request): ...@@ -145,11 +154,17 @@ def create_new_course(request):
metadata = {} metadata = {}
else: else:
metadata = {'display_name': display_name} metadata = {'display_name': display_name}
modulestore('direct').create_and_save_xmodule(dest_location, metadata=metadata) modulestore('direct').create_and_save_xmodule(
dest_location,
metadata=metadata
)
new_course = modulestore('direct').get_item(dest_location) new_course = modulestore('direct').get_item(dest_location)
# clone a default 'about' overview module as well # clone a default 'about' overview module as well
dest_about_location = dest_location.replace(category='about', name='overview') dest_about_location = dest_location.replace(
category='about',
name='overview'
)
overview_template = AboutDescriptor.get_template('overview.yaml') overview_template = AboutDescriptor.get_template('overview.yaml')
modulestore('direct').create_and_save_xmodule( modulestore('direct').create_and_save_xmodule(
dest_about_location, dest_about_location,
...@@ -164,7 +179,8 @@ def create_new_course(request): ...@@ -164,7 +179,8 @@ def create_new_course(request):
# seed the forums # seed the forums
seed_permissions_roles(new_course.location.course_id) seed_permissions_roles(new_course.location.course_id)
# auto-enroll the course creator in the course so that "View Live" will work. # auto-enroll the course creator in the course so that "View Live" will
# work.
CourseEnrollment.enroll(request.user, new_course.location.course_id) CourseEnrollment.enroll(request.user, new_course.location.course_id)
return JsonResponse({'id': new_course.location.url()}) return JsonResponse({'id': new_course.location.url()})
...@@ -174,7 +190,8 @@ def create_new_course(request): ...@@ -174,7 +190,8 @@ def create_new_course(request):
@ensure_csrf_cookie @ensure_csrf_cookie
def course_info(request, org, course, name, provided_id=None): def course_info(request, org, course, name, provided_id=None):
""" """
Send models and views as well as html for editing the course info to the client. Send models and views as well as html for editing the course info to the
client.
org, course, name: Attributes of the Location for the item to edit org, course, name: Attributes of the Location for the item to edit
""" """
...@@ -189,7 +206,8 @@ def course_info(request, org, course, name, provided_id=None): ...@@ -189,7 +206,8 @@ def course_info(request, org, course, name, provided_id=None):
'context_course': course_module, 'context_course': course_module,
'url_base': "/" + org + "/" + course + "/", 'url_base': "/" + org + "/" + course + "/",
'course_updates': json.dumps(get_course_updates(location)), 'course_updates': json.dumps(get_course_updates(location)),
'handouts_location': Location(['i4x', org, course, 'course_info', 'handouts']).url() 'handouts_location': Location(['i4x', org, course, 'course_info',
'handouts']).url()
}) })
...@@ -202,14 +220,16 @@ def course_info_updates(request, org, course, provided_id=None): ...@@ -202,14 +220,16 @@ def course_info_updates(request, org, course, provided_id=None):
restful CRUD operations on course_info updates. restful CRUD operations on course_info updates.
org, course: Attributes of the Location for the item to edit org, course: Attributes of the Location for the item to edit
provided_id should be none if it's new (create) and a composite of the update db id + index otherwise. provided_id should be none if it's new (create) and a composite of the
update db id + index otherwise.
""" """
# ??? No way to check for access permission afaik # ??? No way to check for access permission afaik
# get current updates # get current updates
location = ['i4x', org, course, 'course_info', "updates"] location = ['i4x', org, course, 'course_info', "updates"]
# Hmmm, provided_id is coming as empty string on create whereas I believe it used to be None :-( # Hmmm, provided_id is coming as empty string on create whereas I believe
# Possibly due to my removing the seemingly redundant pattern in urls.py # it used to be None :-( Possibly due to my removing the seemingly
# redundant pattern in urls.py
if provided_id == '': if provided_id == '':
provided_id = None provided_id = None
...@@ -221,13 +241,19 @@ def course_info_updates(request, org, course, provided_id=None): ...@@ -221,13 +241,19 @@ def course_info_updates(request, org, course, provided_id=None):
return JsonResponse(get_course_updates(location)) return JsonResponse(get_course_updates(location))
elif request.method == 'DELETE': elif request.method == 'DELETE':
try: try:
return JsonResponse(delete_course_update(location, request.POST, provided_id)) return JsonResponse(delete_course_update(location, request.POST,
provided_id
))
except: except:
return HttpResponseBadRequest("Failed to delete", return HttpResponseBadRequest("Failed to delete",
content_type="text/plain") content_type="text/plain")
elif request.method in ('POST', 'PUT'): # can be either and sometimes django is rewriting one to the other elif request.method in ('POST', 'PUT'): # can be either and sometimes
# django is rewriting one to the
# other
try: try:
return JsonResponse(update_course_updates(location, request.POST, provided_id)) return JsonResponse(update_course_updates(location, request.POST,
provided_id
))
except: except:
return HttpResponseBadRequest("Failed to save", return HttpResponseBadRequest("Failed to save",
content_type="text/plain") content_type="text/plain")
...@@ -237,7 +263,8 @@ def course_info_updates(request, org, course, provided_id=None): ...@@ -237,7 +263,8 @@ def course_info_updates(request, org, course, provided_id=None):
@ensure_csrf_cookie @ensure_csrf_cookie
def get_course_settings(request, org, course, name): def get_course_settings(request, org, course, name):
""" """
Send models and views as well as html for editing the course settings to the client. Send models and views as well as html for editing the course settings to
the client.
org, course, name: Attributes of the Location for the item to edit org, course, name: Attributes of the Location for the item to edit
""" """
...@@ -253,7 +280,9 @@ def get_course_settings(request, org, course, name): ...@@ -253,7 +280,9 @@ def get_course_settings(request, org, course, name):
"course": course, "course": course,
"name": name, "name": name,
"section": "details"}), "section": "details"}),
'about_page_editable': not settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False) 'about_page_editable': not settings.MITX_FEATURES.get(
'ENABLE_MKTG_SITE', False
)
}) })
...@@ -261,7 +290,8 @@ def get_course_settings(request, org, course, name): ...@@ -261,7 +290,8 @@ def get_course_settings(request, org, course, name):
@ensure_csrf_cookie @ensure_csrf_cookie
def course_config_graders_page(request, org, course, name): def course_config_graders_page(request, org, course, name):
""" """
Send models and views as well as html for editing the course settings to the client. Send models and views as well as html for editing the course settings to
the client.
org, course, name: Attributes of the Location for the item to edit org, course, name: Attributes of the Location for the item to edit
""" """
...@@ -281,7 +311,8 @@ def course_config_graders_page(request, org, course, name): ...@@ -281,7 +311,8 @@ def course_config_graders_page(request, org, course, name):
@ensure_csrf_cookie @ensure_csrf_cookie
def course_config_advanced_page(request, org, course, name): def course_config_advanced_page(request, org, course, name):
""" """
Send models and views as well as html for editing the advanced course settings to the client. Send models and views as well as html for editing the advanced course
settings to the client.
org, course, name: Attributes of the Location for the item to edit org, course, name: Attributes of the Location for the item to edit
""" """
...@@ -301,8 +332,9 @@ def course_config_advanced_page(request, org, course, name): ...@@ -301,8 +332,9 @@ def course_config_advanced_page(request, org, course, name):
@ensure_csrf_cookie @ensure_csrf_cookie
def course_settings_updates(request, org, course, name, section): def course_settings_updates(request, org, course, name, section):
""" """
restful CRUD operations on course settings. This differs from get_course_settings by communicating purely Restful CRUD operations on course settings. This differs from
through json (not rendering any html) and handles section level operations rather than whole page. get_course_settings by communicating purely through json (not rendering any
html) and handles section level operations rather than whole page.
org, course: Attributes of the Location for the item to edit org, course: Attributes of the Location for the item to edit
section: one of details, faculty, grading, problems, discussions section: one of details, faculty, grading, problems, discussions
...@@ -318,9 +350,15 @@ def course_settings_updates(request, org, course, name, section): ...@@ -318,9 +350,15 @@ def course_settings_updates(request, org, course, name, section):
if request.method == 'GET': if request.method == 'GET':
# Cannot just do a get w/o knowing the course name :-( # Cannot just do a get w/o knowing the course name :-(
return JsonResponse(manager.fetch(Location(['i4x', org, course, 'course', name])), encoder=CourseSettingsEncoder) return JsonResponse(
manager.fetch(Location(['i4x', org, course, 'course', name])),
encoder=CourseSettingsEncoder
)
elif request.method in ('POST', 'PUT'): # post or put, doesn't matter. elif request.method in ('POST', 'PUT'): # post or put, doesn't matter.
return JsonResponse(manager.update_from_json(request.POST), encoder=CourseSettingsEncoder) return JsonResponse(
manager.update_from_json(request.POST),
encoder=CourseSettingsEncoder
)
@expect_json @expect_json
...@@ -329,8 +367,9 @@ def course_settings_updates(request, org, course, name, section): ...@@ -329,8 +367,9 @@ def course_settings_updates(request, org, course, name, section):
@ensure_csrf_cookie @ensure_csrf_cookie
def course_grader_updates(request, org, course, name, grader_index=None): def course_grader_updates(request, org, course, name, grader_index=None):
""" """
restful CRUD operations on course_info updates. This differs from get_course_settings by communicating purely Restful CRUD operations on course_info updates. This differs from
through json (not rendering any html) and handles section level operations rather than whole page. get_course_settings by communicating purely through json (not rendering any
html) and handles section level operations rather than whole page.
org, course: Attributes of the Location for the item to edit org, course: Attributes of the Location for the item to edit
""" """
...@@ -339,13 +378,18 @@ def course_grader_updates(request, org, course, name, grader_index=None): ...@@ -339,13 +378,18 @@ def course_grader_updates(request, org, course, name, grader_index=None):
if request.method == 'GET': if request.method == 'GET':
# Cannot just do a get w/o knowing the course name :-( # Cannot just do a get w/o knowing the course name :-(
return JsonResponse(CourseGradingModel.fetch_grader(Location(location), grader_index)) return JsonResponse(CourseGradingModel.fetch_grader(
Location(location), grader_index
))
elif request.method == "DELETE": elif request.method == "DELETE":
# ??? Should this return anything? Perhaps success fail? # ??? Should this return anything? Perhaps success fail?
CourseGradingModel.delete_grader(Location(location), grader_index) CourseGradingModel.delete_grader(Location(location), grader_index)
return JsonResponse() return JsonResponse()
else: # post or put, doesn't matter. else: # post or put, doesn't matter.
return JsonResponse(CourseGradingModel.update_grader_from_json(Location(location), request.POST)) return JsonResponse(CourseGradingModel.update_grader_from_json(
Location(location),
request.POST
))
# # NB: expect_json failed on ["key", "key2"] and json payload # # NB: expect_json failed on ["key", "key2"] and json payload
...@@ -354,8 +398,9 @@ def course_grader_updates(request, org, course, name, grader_index=None): ...@@ -354,8 +398,9 @@ def course_grader_updates(request, org, course, name, grader_index=None):
@ensure_csrf_cookie @ensure_csrf_cookie
def course_advanced_updates(request, org, course, name): def course_advanced_updates(request, org, course, name):
""" """
restful CRUD operations on metadata. The payload is a json rep of the metadata dicts. For delete, otoh, Restful CRUD operations on metadata. The payload is a json rep of the
the payload is either a key or a list of keys to delete. metadata dicts. For delete, otoh, the payload is either a key or a list of
keys to delete.
org, course: Attributes of the Location for the item to edit org, course: Attributes of the Location for the item to edit
""" """
...@@ -364,20 +409,26 @@ def course_advanced_updates(request, org, course, name): ...@@ -364,20 +409,26 @@ def course_advanced_updates(request, org, course, name):
if request.method == 'GET': if request.method == 'GET':
return JsonResponse(CourseMetadata.fetch(location)) return JsonResponse(CourseMetadata.fetch(location))
elif request.method == 'DELETE': elif request.method == 'DELETE':
return JsonResponse(CourseMetadata.delete_key(location, json.loads(request.body))) return JsonResponse(CourseMetadata.delete_key(
location,
json.loads(request.body)
))
else: else:
# NOTE: request.POST is messed up because expect_json # NOTE: request.POST is messed up because expect_json
# cloned_request.POST.copy() is creating a defective entry w/ the whole payload as the key # cloned_request.POST.copy() is creating a defective entry w/ the whole
# payload as the key
request_body = json.loads(request.body) request_body = json.loads(request.body)
# Whether or not to filter the tabs key out of the settings metadata # Whether or not to filter the tabs key out of the settings metadata
filter_tabs = True filter_tabs = True
# Check to see if the user instantiated any advanced components. This is a hack # Check to see if the user instantiated any advanced components. This
# that does the following : # is a hack that does the following :
# 1) adds/removes the open ended panel tab to a course automatically if the user # 1) adds/removes the open ended panel tab to a course automatically
# has indicated that they want to edit the combinedopendended or peergrading module # if the user has indicated that they want to edit the
# 2) adds/removes the notes panel tab to a course automatically if the user has # combinedopendended or peergrading module
# indicated that they want the notes module enabled in their course # 2) adds/removes the notes panel tab to a course automatically if
# the user has indicated that they want the notes module enabled in
# their course
# TODO refactor the above into distinct advanced policy settings # TODO refactor the above into distinct advanced policy settings
if ADVANCED_COMPONENT_POLICY_KEY in request_body: if ADVANCED_COMPONENT_POLICY_KEY in request_body:
# Get the course so that we can scrape current tabs # Get the course so that we can scrape current tabs
...@@ -389,19 +440,25 @@ def course_advanced_updates(request, org, course, name): ...@@ -389,19 +440,25 @@ def course_advanced_updates(request, org, course, name):
'notes': NOTE_COMPONENT_TYPES, 'notes': NOTE_COMPONENT_TYPES,
} }
# Check to see if the user instantiated any notes or open ended components # Check to see if the user instantiated any notes or open ended
# components
for tab_type in tab_component_map.keys(): for tab_type in tab_component_map.keys():
component_types = tab_component_map.get(tab_type) component_types = tab_component_map.get(tab_type)
found_ac_type = False found_ac_type = False
for ac_type in component_types: for ac_type in component_types:
if ac_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]: if ac_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]:
# Add tab to the course if needed # Add tab to the course if needed
changed, new_tabs = add_extra_panel_tab(tab_type, course_module) changed, new_tabs = add_extra_panel_tab(
# If a tab has been added to the course, then send the metadata along to CourseMetadata.update_from_json tab_type,
course_module
)
# If a tab has been added to the course, then send the
# metadata along to CourseMetadata.update_from_json
if changed: if changed:
course_module.tabs = new_tabs course_module.tabs = new_tabs
request_body.update({'tabs': new_tabs}) request_body.update({'tabs': new_tabs})
# Indicate that tabs should not be filtered out of the metadata # Indicate that tabs should not be filtered out of
# the metadata
filter_tabs = False filter_tabs = False
# Set this flag to avoid the tab removal code below. # Set this flag to avoid the tab removal code below.
found_ac_type = True found_ac_type = True
...@@ -410,18 +467,26 @@ def course_advanced_updates(request, org, course, name): ...@@ -410,18 +467,26 @@ def course_advanced_updates(request, org, course, name):
# we may need to remove the tab from the course. # we may need to remove the tab from the course.
if not found_ac_type: if not found_ac_type:
# Remove tab from the course if needed # Remove tab from the course if needed
changed, new_tabs = remove_extra_panel_tab(tab_type, course_module) changed, new_tabs = remove_extra_panel_tab(
tab_type, course_module
)
if changed: if changed:
course_module.tabs = new_tabs course_module.tabs = new_tabs
request_body.update({'tabs': new_tabs}) request_body.update({'tabs': new_tabs})
# Indicate that tabs should *not* be filtered out of the metadata # Indicate that tabs should *not* be filtered out of
# the metadata
filter_tabs = False filter_tabs = False
try: try:
return JsonResponse(CourseMetadata.update_from_json(location, return JsonResponse(CourseMetadata.update_from_json(
request_body, location,
filter_tabs=filter_tabs)) request_body,
filter_tabs=filter_tabs
))
except (TypeError, ValueError) as err: except (TypeError, ValueError) as err:
return HttpResponseBadRequest("Incorrect setting format. " + str(err), content_type="text/plain") return HttpResponseBadRequest(
"Incorrect setting format. " + str(err),
content_type="text/plain"
)
class TextbookValidationError(Exception): class TextbookValidationError(Exception):
...@@ -498,7 +563,8 @@ def textbook_index(request, org, course, name): ...@@ -498,7 +563,8 @@ def textbook_index(request, org, course, name):
if request.is_ajax(): if request.is_ajax():
if request.method == 'GET': if request.method == 'GET':
return JsonResponse(course_module.pdf_textbooks) return JsonResponse(course_module.pdf_textbooks)
elif request.method in ('POST', 'PUT'): # can be either and sometimes django is rewriting one to the other elif request.method in ('POST', 'PUT'): # can be either and sometimes
# django is rewriting one to the other
try: try:
textbooks = validate_textbooks_json(request.body) textbooks = validate_textbooks_json(request.body)
except TextbookValidationError as err: except TextbookValidationError as err:
...@@ -517,7 +583,10 @@ def textbook_index(request, org, course, name): ...@@ -517,7 +583,10 @@ def textbook_index(request, org, course, name):
# Save the data that we've just changed to the underlying # Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore. # MongoKeyValueStore before we update the mongo datastore.
course_module.save() course_module.save()
store.update_metadata(course_module.location, own_metadata(course_module)) store.update_metadata(
course_module.location,
own_metadata(course_module)
)
return JsonResponse(course_module.pdf_textbooks) return JsonResponse(course_module.pdf_textbooks)
else: else:
upload_asset_url = reverse('upload_asset', kwargs={ upload_asset_url = reverse('upload_asset', kwargs={
...@@ -599,7 +668,8 @@ def textbook_by_id(request, org, course, name, tid): ...@@ -599,7 +668,8 @@ def textbook_by_id(request, org, course, name, tid):
if not textbook: if not textbook:
return JsonResponse(status=404) return JsonResponse(status=404)
return JsonResponse(textbook) return JsonResponse(textbook)
elif request.method in ('POST', 'PUT'): # can be either and sometimes django is rewriting one to the other elif request.method in ('POST', 'PUT'): # can be either and sometimes
# django is rewriting one to the other
try: try:
new_textbook = validate_textbook_json(request.body) new_textbook = validate_textbook_json(request.body)
except TextbookValidationError as err: except TextbookValidationError as err:
...@@ -616,7 +686,10 @@ def textbook_by_id(request, org, course, name, tid): ...@@ -616,7 +686,10 @@ def textbook_by_id(request, org, course, name, tid):
# Save the data that we've just changed to the underlying # Save the data that we've just changed to the underlying
# MongoKeyValueStore before we update the mongo datastore. # MongoKeyValueStore before we update the mongo datastore.
course_module.save() course_module.save()
store.update_metadata(course_module.location, own_metadata(course_module)) store.update_metadata(
course_module.location,
own_metadata(course_module)
)
return JsonResponse(new_textbook, status=201) return JsonResponse(new_textbook, status=201)
elif request.method == 'DELETE': elif request.method == 'DELETE':
if not textbook: if not textbook:
...@@ -626,5 +699,8 @@ def textbook_by_id(request, org, course, name, tid): ...@@ -626,5 +699,8 @@ def textbook_by_id(request, org, course, name, tid):
new_textbooks.extend(course_module.pdf_textbooks[i + 1:]) new_textbooks.extend(course_module.pdf_textbooks[i + 1:])
course_module.pdf_textbooks = new_textbooks course_module.pdf_textbooks = new_textbooks
course_module.save() course_module.save()
store.update_metadata(course_module.location, own_metadata(course_module)) store.update_metadata(
course_module.location,
own_metadata(course_module)
)
return JsonResponse() return JsonResponse()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment