Contentstore views pylint fixes
This commit is contained in:
@@ -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
|
||||
Also, since we're unifying the user database between LMS and CAS,
|
||||
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
|
||||
has all the rights that STAFF do
|
||||
will not be in both INSTRUCTOR and STAFF groups, so we have to cascade our
|
||||
queries here as INSTRUCTOR has all the rights that STAFF do
|
||||
'''
|
||||
course_location = get_course_location_for_item(location)
|
||||
_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 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
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
import tarfile
|
||||
import shutil
|
||||
import cgi
|
||||
from functools import partial
|
||||
from tempfile import mkdtemp
|
||||
from path import path
|
||||
|
||||
@@ -34,7 +35,8 @@ from .access import get_location_and_verify_access
|
||||
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):
|
||||
@@ -58,13 +60,14 @@ def assets_to_json_dict(assets):
|
||||
obj["thumbnail"] = thumbnail
|
||||
id_info = asset.get("_id")
|
||||
if id_info:
|
||||
obj["id"] = "/{tag}/{org}/{course}/{revision}/{category}/{name}".format(
|
||||
org=id_info.get("org", ""),
|
||||
course=id_info.get("course", ""),
|
||||
revision=id_info.get("revision", ""),
|
||||
tag=id_info.get("tag", ""),
|
||||
category=id_info.get("category", ""),
|
||||
name=id_info.get("name", ""),
|
||||
obj["id"] = "/{tag}/{org}/{course}/{revision}/{category}/{name}" \
|
||||
.format(
|
||||
org=id_info.get("org", ""),
|
||||
course=id_info.get("course", ""),
|
||||
revision=id_info.get("revision", ""),
|
||||
tag=id_info.get("tag", ""),
|
||||
category=id_info.get("category", ""),
|
||||
name=id_info.get("name", ""),
|
||||
)
|
||||
ret.append(obj)
|
||||
return ret
|
||||
@@ -132,14 +135,14 @@ def asset_index(request, org, course, name):
|
||||
@login_required
|
||||
def upload_asset(request, org, course, coursename):
|
||||
'''
|
||||
This method allows for POST uploading of files into the course asset library, which will
|
||||
be supported by GridFS in MongoDB.
|
||||
This method allows for POST uploading of files into the course asset
|
||||
library, which will be supported by GridFS in MongoDB.
|
||||
'''
|
||||
# construct a location from the passed in path
|
||||
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:
|
||||
modulestore().get_item(location)
|
||||
except:
|
||||
@@ -150,9 +153,10 @@ def upload_asset(request, org, course, coursename):
|
||||
if 'file' not in request.FILES:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
# compute a 'filename' which is similar to the location formatting, we're using the 'filename'
|
||||
# nomenclature since we're using a FileSystem paradigm here. We're just imposing
|
||||
# the Location string formatting expectations to keep things a bit more consistent
|
||||
# compute a 'filename' which is similar to the location formatting, we're
|
||||
# using the 'filename' nomenclature since we're using a FileSystem paradigm
|
||||
# here. We're just imposing the Location string formatting expectations to
|
||||
# keep things a bit more consistent
|
||||
upload_file = request.FILES['file']
|
||||
filename = upload_file.name
|
||||
mime_type = upload_file.content_type
|
||||
@@ -160,20 +164,25 @@ def upload_asset(request, org, course, coursename):
|
||||
content_loc = StaticContent.compute_location(org, course, filename)
|
||||
|
||||
chunked = upload_file.multiple_chunks()
|
||||
sc_partial = partial(StaticContent, content_loc, filename, mime_type)
|
||||
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:
|
||||
content = StaticContent(content_loc, filename, mime_type, upload_file.read())
|
||||
content = sc_partial(upload_file.read())
|
||||
tempfile_path = None
|
||||
|
||||
thumbnail_content = None
|
||||
thumbnail_location = None
|
||||
|
||||
# first let's see if a thumbnail can be created
|
||||
(thumbnail_content, thumbnail_location) = contentstore().generate_thumbnail(content,
|
||||
tempfile_path=None if not chunked else
|
||||
upload_file.temporary_file_path())
|
||||
(thumbnail_content, thumbnail_location) = contentstore().generate_thumbnail(
|
||||
content,
|
||||
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)
|
||||
# now store thumbnail location only if we could create it
|
||||
if thumbnail_content is not None:
|
||||
@@ -186,13 +195,15 @@ def upload_asset(request, org, course, coursename):
|
||||
# readback the saved content - we need the database timestamp
|
||||
readback = contentstore().find(content.location)
|
||||
|
||||
response_payload = {'displayname': content.name,
|
||||
'uploadDate': get_default_time_display(readback.last_modified_at),
|
||||
'url': StaticContent.get_url_path_from_location(content.location),
|
||||
'portable_url': StaticContent.get_static_path_from_location(content.location),
|
||||
'thumb_url': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_content is not None else None,
|
||||
'msg': 'Upload completed'
|
||||
}
|
||||
response_payload = {
|
||||
'displayname': content.name,
|
||||
'uploadDate': get_default_time_display(readback.last_modified_at),
|
||||
'url': StaticContent.get_url_path_from_location(content.location),
|
||||
'portable_url': StaticContent.get_static_path_from_location(content.location),
|
||||
'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)
|
||||
return response
|
||||
@@ -202,8 +213,8 @@ def upload_asset(request, org, course, coursename):
|
||||
@login_required
|
||||
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
|
||||
the main GridFS collection and into a Trashcan
|
||||
This method will perform a 'soft-delete' of an asset, which is basically to
|
||||
copy the asset from the main GridFS collection and into a Trashcan
|
||||
'''
|
||||
get_location_and_verify_access(request, org, course, name)
|
||||
|
||||
|
||||
@@ -30,7 +30,8 @@ def get_checklists(request, org, course, name):
|
||||
modulestore = get_modulestore(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
|
||||
if not course_module.checklists:
|
||||
course_module.checklists = CourseDescriptor.checklists.default
|
||||
@@ -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):
|
||||
index = int(checklist_index)
|
||||
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
|
||||
checklists, _ = expand_checklist_action_urls(course_module)
|
||||
course_module.save()
|
||||
@@ -76,10 +78,13 @@ def update_checklist(request, org, course, name, checklist_index=None):
|
||||
return JsonResponse(checklists[index])
|
||||
else:
|
||||
return HttpResponseBadRequest(
|
||||
"Could not save checklist state because the checklist index was out of range or unspecified.",
|
||||
content_type="text/plain")
|
||||
( "Could not save checklist state because the checklist index "
|
||||
"was out of range or unspecified."),
|
||||
content_type="text/plain"
|
||||
)
|
||||
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)
|
||||
if modified:
|
||||
course_module.save()
|
||||
|
||||
@@ -2,7 +2,8 @@ import json
|
||||
import logging
|
||||
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.views.decorators.http import require_http_methods
|
||||
from django.core.exceptions import PermissionDenied
|
||||
@@ -72,10 +73,15 @@ def edit_subsection(request, location):
|
||||
except ItemNotFoundError:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
lms_link = get_lms_link_for_item(location, course_id=course.location.course_id)
|
||||
preview_link = get_lms_link_for_item(location, course_id=course.location.course_id, preview=True)
|
||||
lms_link = get_lms_link_for_item(
|
||||
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':
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
@@ -83,18 +89,22 @@ def edit_subsection(request, location):
|
||||
|
||||
# we're for now assuming a single parent
|
||||
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
|
||||
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(
|
||||
(field.name, field.read_from(item))
|
||||
for field
|
||||
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
|
||||
@@ -105,19 +115,22 @@ def edit_subsection(request, location):
|
||||
can_view_live = True
|
||||
break
|
||||
|
||||
return render_to_response('edit_subsection.html',
|
||||
{'subsection': item,
|
||||
'context_course': course,
|
||||
'new_unit_category': 'vertical',
|
||||
'lms_link': lms_link,
|
||||
'preview_link': preview_link,
|
||||
'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders),
|
||||
'parent_location': course.location,
|
||||
'parent_item': parent,
|
||||
'policy_metadata': policy_metadata,
|
||||
'subsection_units': subsection_units,
|
||||
'can_view_live': can_view_live
|
||||
})
|
||||
return render_to_response(
|
||||
'edit_subsection.html',
|
||||
{
|
||||
'subsection': item,
|
||||
'context_course': course,
|
||||
'new_unit_category': 'vertical',
|
||||
'lms_link': lms_link,
|
||||
'preview_link': preview_link,
|
||||
'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders),
|
||||
'parent_location': course.location,
|
||||
'parent_item': parent,
|
||||
'policy_metadata': policy_metadata,
|
||||
'subsection_units': subsection_units,
|
||||
'can_view_live': can_view_live
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -125,7 +138,7 @@ def edit_unit(request, location):
|
||||
"""
|
||||
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
|
||||
"""
|
||||
@@ -141,7 +154,10 @@ def edit_unit(request, location):
|
||||
item = modulestore().get_item(location, depth=1)
|
||||
except ItemNotFoundError:
|
||||
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)
|
||||
for category in COMPONENT_TYPES:
|
||||
@@ -162,17 +178,19 @@ def edit_unit(request, location):
|
||||
template.get('template_id')
|
||||
))
|
||||
|
||||
# Check if there are any advanced modules specified in the course policy. These modules
|
||||
# should be specified as a list of strings, where the strings are the names of the modules
|
||||
# in ADVANCED_COMPONENT_TYPES that should be enabled for the course.
|
||||
# Check if there are any advanced modules specified in the course policy.
|
||||
# These modules should be specified as a list of strings, where the strings
|
||||
# are the names of the modules in ADVANCED_COMPONENT_TYPES that should be
|
||||
# enabled for the course.
|
||||
course_advanced_keys = course.advanced_modules
|
||||
|
||||
# Set component types according to course policy file
|
||||
if isinstance(course_advanced_keys, list):
|
||||
for category in course_advanced_keys:
|
||||
if category in ADVANCED_COMPONENT_TYPES:
|
||||
# Do I need to allow for boilerplates or just defaults on the class? i.e., can an advanced
|
||||
# have more than one entry in the menu? one for default and others for prefilled boilerplates?
|
||||
# Do I need to allow for boilerplates or just defaults on the
|
||||
# class? i.e., can an advanced have more than one entry in the
|
||||
# menu? one for default and others for prefilled boilerplates?
|
||||
try:
|
||||
component_class = XModuleDescriptor.load_class(category)
|
||||
|
||||
@@ -183,13 +201,16 @@ def edit_unit(request, location):
|
||||
None # don't override default data
|
||||
))
|
||||
except PluginMissingError:
|
||||
# dhm: I got this once but it can happen any time the course author configures
|
||||
# an advanced component which does not exist on the server. This code here merely
|
||||
# prevents any authors from trying to instantiate the non-existent component type
|
||||
# by not showing it in the menu
|
||||
# dhm: I got this once but it can happen any time the
|
||||
# course author configures an advanced component which does
|
||||
# not exist on the server. This code here merely
|
||||
# prevents any authors from trying to instantiate the
|
||||
# non-existent component type by not showing it in the menu
|
||||
pass
|
||||
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 = [
|
||||
component.location.url()
|
||||
@@ -201,16 +222,20 @@ def edit_unit(request, location):
|
||||
# this will need to change to check permissions correctly so as
|
||||
# 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_section_locs = modulestore().get_parent_locations(containing_subsection.location, None)
|
||||
containing_section_locs = modulestore().get_parent_locations(
|
||||
containing_subsection.location, None
|
||||
)
|
||||
containing_section = modulestore().get_item(containing_section_locs[0])
|
||||
|
||||
# cdodge hack. We're having trouble previewing drafts via jump_to redirect
|
||||
# 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
|
||||
for child in containing_subsection.get_children():
|
||||
if child.location == item.location:
|
||||
@@ -219,15 +244,19 @@ def edit_unit(request, location):
|
||||
|
||||
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_base=preview_lms_base,
|
||||
lms_base=settings.LMS_BASE,
|
||||
org=course.location.org,
|
||||
course=course.location.course,
|
||||
course_name=course.location.name,
|
||||
section=containing_section.location.name,
|
||||
subsection=containing_subsection.location.name,
|
||||
index=index)
|
||||
preview_lms_link = (
|
||||
'//{preview_lms_base}/courses/{org}/{course}/'
|
||||
'{course_name}/courseware/{section}/{subsection}/{index}'
|
||||
).format(
|
||||
preview_lms_base=preview_lms_base,
|
||||
lms_base=settings.LMS_BASE,
|
||||
org=course.location.org,
|
||||
course=course.location.course,
|
||||
course_name=course.location.name,
|
||||
section=containing_section.location.name,
|
||||
subsection=containing_subsection.location.name,
|
||||
index=index
|
||||
)
|
||||
|
||||
unit_state = compute_unit_state(item)
|
||||
|
||||
@@ -240,11 +269,13 @@ def edit_unit(request, location):
|
||||
'draft_preview_link': preview_lms_link,
|
||||
'published_preview_link': lms_link,
|
||||
'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,
|
||||
'new_unit_category': 'vertical',
|
||||
'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):
|
||||
@require_http_methods(("GET", "POST", "PUT"))
|
||||
@ensure_csrf_cookie
|
||||
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])
|
||||
if not has_access(request.user, location):
|
||||
return HttpResponseForbidden()
|
||||
@@ -263,7 +295,9 @@ def assignment_type_update(request, org, course, category, name):
|
||||
if request.method == 'GET':
|
||||
return JsonResponse(CourseGradingModel.get_section_grader_type(location))
|
||||
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
|
||||
@@ -276,8 +310,8 @@ def create_draft(request):
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
# This clones the existing item location to a draft location (the draft is implicit,
|
||||
# because modulestore is a Draft modulestore)
|
||||
# This clones the existing item location to a draft location (the draft is
|
||||
# implicit, because modulestore is a Draft modulestore)
|
||||
modulestore().convert_to_draft(location)
|
||||
|
||||
return HttpResponse()
|
||||
@@ -286,7 +320,9 @@ def create_draft(request):
|
||||
@login_required
|
||||
@expect_json
|
||||
def publish_draft(request):
|
||||
"Publish a draft"
|
||||
"""
|
||||
Publish a draft
|
||||
"""
|
||||
location = request.POST['id']
|
||||
|
||||
# check permissions for this user within this course
|
||||
@@ -294,7 +330,10 @@ def publish_draft(request):
|
||||
raise PermissionDenied()
|
||||
|
||||
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()
|
||||
|
||||
@@ -328,13 +367,23 @@ def module_info(request, module_location):
|
||||
raise PermissionDenied()
|
||||
|
||||
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
|
||||
if not has_access(request.user, location):
|
||||
raise PermissionDenied()
|
||||
|
||||
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"):
|
||||
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):
|
||||
'context_course': course,
|
||||
'lms_link': lms_link,
|
||||
'sections': sections,
|
||||
'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders),
|
||||
'course_graders': json.dumps(
|
||||
CourseGradingModel.fetch(course.location).graders
|
||||
),
|
||||
'parent_location': course.location,
|
||||
'new_section_category': 'chapter',
|
||||
'new_subsection_category': 'sequential',
|
||||
@@ -120,24 +122,31 @@ def create_new_course(request):
|
||||
except ItemNotFoundError:
|
||||
pass
|
||||
if existing_course is not None:
|
||||
return JsonResponse(
|
||||
{
|
||||
'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.'),
|
||||
'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.'),
|
||||
}
|
||||
)
|
||||
return JsonResponse({
|
||||
'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.')),
|
||||
'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)
|
||||
if len(courses) > 0:
|
||||
return JsonResponse(
|
||||
{
|
||||
'ErrMsg': _('There is already a course defined with the same organization and course number. Please change at least one field to be 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.'),
|
||||
}
|
||||
)
|
||||
return JsonResponse({
|
||||
'ErrMsg': _(('There is already a course defined with the same '
|
||||
'organization and course number. Please '
|
||||
'change at least one field to be 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
|
||||
# note: no system to pass
|
||||
@@ -145,11 +154,17 @@ def create_new_course(request):
|
||||
metadata = {}
|
||||
else:
|
||||
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)
|
||||
|
||||
# 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')
|
||||
modulestore('direct').create_and_save_xmodule(
|
||||
dest_about_location,
|
||||
@@ -164,7 +179,8 @@ def create_new_course(request):
|
||||
# seed the forums
|
||||
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)
|
||||
|
||||
return JsonResponse({'id': new_course.location.url()})
|
||||
@@ -174,7 +190,8 @@ def create_new_course(request):
|
||||
@ensure_csrf_cookie
|
||||
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
|
||||
"""
|
||||
@@ -189,7 +206,8 @@ def course_info(request, org, course, name, provided_id=None):
|
||||
'context_course': course_module,
|
||||
'url_base': "/" + org + "/" + course + "/",
|
||||
'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):
|
||||
restful CRUD operations on course_info updates.
|
||||
|
||||
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
|
||||
# get current 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 :-(
|
||||
# Possibly due to my removing the seemingly redundant pattern in urls.py
|
||||
# Hmmm, provided_id is coming as empty string on create whereas I believe
|
||||
# it used to be None :-( Possibly due to my removing the seemingly
|
||||
# redundant pattern in urls.py
|
||||
if provided_id == '':
|
||||
provided_id = None
|
||||
|
||||
@@ -221,13 +241,19 @@ def course_info_updates(request, org, course, provided_id=None):
|
||||
return JsonResponse(get_course_updates(location))
|
||||
elif request.method == 'DELETE':
|
||||
try:
|
||||
return JsonResponse(delete_course_update(location, request.POST, provided_id))
|
||||
return JsonResponse(delete_course_update(location, request.POST,
|
||||
provided_id
|
||||
))
|
||||
except:
|
||||
return HttpResponseBadRequest("Failed to delete",
|
||||
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:
|
||||
return JsonResponse(update_course_updates(location, request.POST, provided_id))
|
||||
return JsonResponse(update_course_updates(location, request.POST,
|
||||
provided_id
|
||||
))
|
||||
except:
|
||||
return HttpResponseBadRequest("Failed to save",
|
||||
content_type="text/plain")
|
||||
@@ -237,7 +263,8 @@ def course_info_updates(request, org, course, provided_id=None):
|
||||
@ensure_csrf_cookie
|
||||
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
|
||||
"""
|
||||
@@ -253,7 +280,9 @@ def get_course_settings(request, org, course, name):
|
||||
"course": course,
|
||||
"name": name,
|
||||
"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):
|
||||
@ensure_csrf_cookie
|
||||
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
|
||||
"""
|
||||
@@ -281,7 +311,8 @@ def course_config_graders_page(request, org, course, name):
|
||||
@ensure_csrf_cookie
|
||||
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
|
||||
"""
|
||||
@@ -301,8 +332,9 @@ def course_config_advanced_page(request, org, course, name):
|
||||
@ensure_csrf_cookie
|
||||
def course_settings_updates(request, org, course, name, section):
|
||||
"""
|
||||
restful CRUD operations on course settings. This differs from get_course_settings by communicating purely
|
||||
through json (not rendering any html) and handles section level operations rather than whole page.
|
||||
Restful CRUD operations on course settings. This differs from
|
||||
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
|
||||
section: one of details, faculty, grading, problems, discussions
|
||||
@@ -318,9 +350,15 @@ def course_settings_updates(request, org, course, name, section):
|
||||
|
||||
if request.method == 'GET':
|
||||
# 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.
|
||||
return JsonResponse(manager.update_from_json(request.POST), encoder=CourseSettingsEncoder)
|
||||
return JsonResponse(
|
||||
manager.update_from_json(request.POST),
|
||||
encoder=CourseSettingsEncoder
|
||||
)
|
||||
|
||||
|
||||
@expect_json
|
||||
@@ -329,8 +367,9 @@ def course_settings_updates(request, org, course, name, section):
|
||||
@ensure_csrf_cookie
|
||||
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
|
||||
through json (not rendering any html) and handles section level operations rather than whole page.
|
||||
Restful CRUD operations on course_info updates. This differs from
|
||||
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
|
||||
"""
|
||||
@@ -339,13 +378,18 @@ def course_grader_updates(request, org, course, name, grader_index=None):
|
||||
|
||||
if request.method == 'GET':
|
||||
# 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":
|
||||
# ??? Should this return anything? Perhaps success fail?
|
||||
CourseGradingModel.delete_grader(Location(location), grader_index)
|
||||
return JsonResponse()
|
||||
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
|
||||
@@ -354,8 +398,9 @@ def course_grader_updates(request, org, course, name, grader_index=None):
|
||||
@ensure_csrf_cookie
|
||||
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,
|
||||
the payload is either a key or a list of keys to delete.
|
||||
Restful CRUD operations on metadata. The payload is a json rep of the
|
||||
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
|
||||
"""
|
||||
@@ -364,20 +409,26 @@ def course_advanced_updates(request, org, course, name):
|
||||
if request.method == 'GET':
|
||||
return JsonResponse(CourseMetadata.fetch(location))
|
||||
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:
|
||||
# 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)
|
||||
# Whether or not to filter the tabs key out of the settings metadata
|
||||
filter_tabs = True
|
||||
|
||||
# Check to see if the user instantiated any advanced components. This is a hack
|
||||
# that does the following :
|
||||
# 1) adds/removes the open ended panel tab to a course automatically if the user
|
||||
# has indicated that they want to edit the combinedopendended or peergrading module
|
||||
# 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
|
||||
# Check to see if the user instantiated any advanced components. This
|
||||
# is a hack that does the following :
|
||||
# 1) adds/removes the open ended panel tab to a course automatically
|
||||
# if the user has indicated that they want to edit the
|
||||
# combinedopendended or peergrading module
|
||||
# 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
|
||||
if ADVANCED_COMPONENT_POLICY_KEY in request_body:
|
||||
# Get the course so that we can scrape current tabs
|
||||
@@ -389,19 +440,25 @@ def course_advanced_updates(request, org, course, name):
|
||||
'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():
|
||||
component_types = tab_component_map.get(tab_type)
|
||||
found_ac_type = False
|
||||
for ac_type in component_types:
|
||||
if ac_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]:
|
||||
# Add tab to the course if needed
|
||||
changed, new_tabs = add_extra_panel_tab(tab_type, course_module)
|
||||
# If a tab has been added to the course, then send the metadata along to CourseMetadata.update_from_json
|
||||
changed, new_tabs = add_extra_panel_tab(
|
||||
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:
|
||||
course_module.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
|
||||
# Set this flag to avoid the tab removal code below.
|
||||
found_ac_type = True
|
||||
@@ -410,18 +467,26 @@ def course_advanced_updates(request, org, course, name):
|
||||
# we may need to remove the tab from the course.
|
||||
if not found_ac_type:
|
||||
# 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:
|
||||
course_module.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
|
||||
try:
|
||||
return JsonResponse(CourseMetadata.update_from_json(location,
|
||||
request_body,
|
||||
filter_tabs=filter_tabs))
|
||||
return JsonResponse(CourseMetadata.update_from_json(
|
||||
location,
|
||||
request_body,
|
||||
filter_tabs=filter_tabs
|
||||
))
|
||||
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):
|
||||
@@ -498,7 +563,8 @@ def textbook_index(request, org, course, name):
|
||||
if request.is_ajax():
|
||||
if request.method == 'GET':
|
||||
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:
|
||||
textbooks = validate_textbooks_json(request.body)
|
||||
except TextbookValidationError as err:
|
||||
@@ -517,7 +583,10 @@ def textbook_index(request, org, course, name):
|
||||
# Save the data that we've just changed to the underlying
|
||||
# MongoKeyValueStore before we update the mongo datastore.
|
||||
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)
|
||||
else:
|
||||
upload_asset_url = reverse('upload_asset', kwargs={
|
||||
@@ -599,7 +668,8 @@ def textbook_by_id(request, org, course, name, tid):
|
||||
if not textbook:
|
||||
return JsonResponse(status=404)
|
||||
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:
|
||||
new_textbook = validate_textbook_json(request.body)
|
||||
except TextbookValidationError as err:
|
||||
@@ -616,7 +686,10 @@ def textbook_by_id(request, org, course, name, tid):
|
||||
# Save the data that we've just changed to the underlying
|
||||
# MongoKeyValueStore before we update the mongo datastore.
|
||||
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)
|
||||
elif request.method == 'DELETE':
|
||||
if not textbook:
|
||||
@@ -626,5 +699,8 @@ def textbook_by_id(request, org, course, name, tid):
|
||||
new_textbooks.extend(course_module.pdf_textbooks[i + 1:])
|
||||
course_module.pdf_textbooks = new_textbooks
|
||||
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()
|
||||
|
||||
Reference in New Issue
Block a user