Instead, we use XModule field default values when creating an empty XModule. Driven by this use case, we also allow for XModules to be created in memory without being persisted to the database at all. This necessitates a change to the Modulestore api, replacing clone_item with create_draft and save_xmodule.
261 lines
9.5 KiB
Python
261 lines
9.5 KiB
Python
#pylint: disable=E1103, E1101
|
|
|
|
from django.conf import settings
|
|
from xmodule.modulestore import Location
|
|
from xmodule.modulestore.django import modulestore
|
|
from xmodule.modulestore.exceptions import ItemNotFoundError
|
|
from django.core.urlresolvers import reverse
|
|
import copy
|
|
import logging
|
|
import re
|
|
from xmodule.modulestore.draft import DIRECT_ONLY_CATEGORIES
|
|
from django.utils.translation import ugettext as _
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
# In order to instantiate an open ended tab automatically, need to have this data
|
|
OPEN_ENDED_PANEL = {"name": _("Open Ended Panel"), "type": "open_ended"}
|
|
NOTES_PANEL = {"name": _("My Notes"), "type": "notes"}
|
|
EXTRA_TAB_PANELS = dict([(p['type'], p) for p in [OPEN_ENDED_PANEL, NOTES_PANEL]])
|
|
|
|
|
|
def get_modulestore(category_or_location):
|
|
"""
|
|
Returns the correct modulestore to use for modifying the specified location
|
|
"""
|
|
if isinstance(category_or_location, Location):
|
|
category_or_location = category_or_location.category
|
|
|
|
if category_or_location in DIRECT_ONLY_CATEGORIES:
|
|
return modulestore('direct')
|
|
else:
|
|
return modulestore()
|
|
|
|
|
|
def get_course_location_for_item(location):
|
|
'''
|
|
cdodge: for a given Xmodule, return the course that it belongs to
|
|
NOTE: This makes a lot of assumptions about the format of the course location
|
|
Also we have to assert that this module maps to only one course item - it'll throw an
|
|
assert if not
|
|
'''
|
|
item_loc = Location(location)
|
|
|
|
# check to see if item is already a course, if so we can skip this
|
|
if item_loc.category != 'course':
|
|
# @hack! We need to find the course location however, we don't
|
|
# know the 'name' parameter in this context, so we have
|
|
# to assume there's only one item in this query even though we are not specifying a name
|
|
course_search_location = ['i4x', item_loc.org, item_loc.course, 'course', None]
|
|
courses = modulestore().get_items(course_search_location)
|
|
|
|
# make sure we found exactly one match on this above course search
|
|
found_cnt = len(courses)
|
|
if found_cnt == 0:
|
|
raise Exception('Could not find course at {0}'.format(course_search_location))
|
|
|
|
if found_cnt > 1:
|
|
raise Exception('Found more than one course at {0}. There should only be one!!! Dump = {1}'.format(course_search_location, courses))
|
|
|
|
location = courses[0].location
|
|
|
|
return location
|
|
|
|
|
|
def get_course_for_item(location):
|
|
'''
|
|
cdodge: for a given Xmodule, return the course that it belongs to
|
|
NOTE: This makes a lot of assumptions about the format of the course location
|
|
Also we have to assert that this module maps to only one course item - it'll throw an
|
|
assert if not
|
|
'''
|
|
item_loc = Location(location)
|
|
|
|
# @hack! We need to find the course location however, we don't
|
|
# know the 'name' parameter in this context, so we have
|
|
# to assume there's only one item in this query even though we are not specifying a name
|
|
course_search_location = ['i4x', item_loc.org, item_loc.course, 'course', None]
|
|
courses = modulestore().get_items(course_search_location)
|
|
|
|
# make sure we found exactly one match on this above course search
|
|
found_cnt = len(courses)
|
|
if found_cnt == 0:
|
|
raise BaseException('Could not find course at {0}'.format(course_search_location))
|
|
|
|
if found_cnt > 1:
|
|
raise BaseException('Found more than one course at {0}. There should only be one!!! Dump = {1}'.format(course_search_location, courses))
|
|
|
|
return courses[0]
|
|
|
|
|
|
def get_lms_link_for_item(location, preview=False, course_id=None):
|
|
if course_id is None:
|
|
course_id = get_course_id(location)
|
|
|
|
if settings.LMS_BASE is not None:
|
|
if preview:
|
|
lms_base = settings.MITX_FEATURES.get('PREVIEW_LMS_BASE')
|
|
else:
|
|
lms_base = settings.LMS_BASE
|
|
|
|
lms_link = "//{lms_base}/courses/{course_id}/jump_to/{location}".format(
|
|
lms_base=lms_base,
|
|
course_id=course_id,
|
|
location=Location(location)
|
|
)
|
|
else:
|
|
lms_link = None
|
|
|
|
return lms_link
|
|
|
|
|
|
def get_lms_link_for_about_page(location):
|
|
"""
|
|
Returns the url to the course about page from the location tuple.
|
|
"""
|
|
if settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False):
|
|
if not hasattr(settings, 'MKTG_URLS'):
|
|
log.exception("ENABLE_MKTG_SITE is True, but MKTG_URLS is not defined.")
|
|
about_base = None
|
|
else:
|
|
marketing_urls = settings.MKTG_URLS
|
|
if marketing_urls.get('ROOT', None) is None:
|
|
log.exception('There is no ROOT defined in MKTG_URLS')
|
|
about_base = None
|
|
else:
|
|
# Root will be "https://www.edx.org". The complete URL will still not be exactly correct,
|
|
# but redirects exist from www.edx.org to get to the Drupal course about page URL.
|
|
about_base = marketing_urls.get('ROOT')
|
|
# Strip off https:// (or http://) to be consistent with the formatting of LMS_BASE.
|
|
about_base = re.sub(r"^https?://", "", about_base)
|
|
elif settings.LMS_BASE is not None:
|
|
about_base = settings.LMS_BASE
|
|
else:
|
|
about_base = None
|
|
|
|
if about_base is not None:
|
|
lms_link = "//{about_base_url}/courses/{course_id}/about".format(
|
|
about_base_url=about_base,
|
|
course_id=get_course_id(location)
|
|
)
|
|
else:
|
|
lms_link = None
|
|
|
|
return lms_link
|
|
|
|
|
|
def get_course_id(location):
|
|
"""
|
|
Returns the course_id from a given the location tuple.
|
|
"""
|
|
# TODO: These will need to be changed to point to the particular instance of this problem in the particular course
|
|
return modulestore().get_containing_courses(Location(location))[0].id
|
|
|
|
|
|
class UnitState(object):
|
|
draft = 'draft'
|
|
private = 'private'
|
|
public = 'public'
|
|
|
|
|
|
def compute_unit_state(unit):
|
|
"""
|
|
Returns whether this unit is 'draft', 'public', or 'private'.
|
|
|
|
'draft' content is in the process of being edited, but still has a previous
|
|
version visible in the LMS
|
|
'public' content is locked and visible in the LMS
|
|
'private' content is editabled and not visible in the LMS
|
|
"""
|
|
|
|
if getattr(unit, 'is_draft', False):
|
|
try:
|
|
modulestore('direct').get_item(unit.location)
|
|
return UnitState.draft
|
|
except ItemNotFoundError:
|
|
return UnitState.private
|
|
else:
|
|
return UnitState.public
|
|
|
|
|
|
def update_item(location, value):
|
|
"""
|
|
If value is None, delete the db entry. Otherwise, update it using the correct modulestore.
|
|
"""
|
|
if value is None:
|
|
get_modulestore(location).delete_item(location)
|
|
else:
|
|
get_modulestore(location).update_item(location, value)
|
|
|
|
|
|
def get_url_reverse(course_page_name, course_module):
|
|
"""
|
|
Returns the course URL link to the specified location. This value is suitable to use as an href link.
|
|
|
|
course_page_name should correspond to an attribute in CoursePageNames (for example, 'ManageUsers'
|
|
or 'SettingsDetails'), or else it will simply be returned. This method passes back unknown values of
|
|
course_page_names so that it can also be used for absolute (known) URLs.
|
|
|
|
course_module is used to obtain the location, org, course, and name properties for a course, if
|
|
course_page_name corresponds to an attribute in CoursePageNames.
|
|
"""
|
|
url_name = getattr(CoursePageNames, course_page_name, None)
|
|
ctx_loc = course_module.location
|
|
|
|
if CoursePageNames.ManageUsers == url_name:
|
|
return reverse(url_name, kwargs={"location": ctx_loc})
|
|
elif url_name in [CoursePageNames.SettingsDetails, CoursePageNames.SettingsGrading,
|
|
CoursePageNames.CourseOutline, CoursePageNames.Checklists]:
|
|
return reverse(url_name, kwargs={'org': ctx_loc.org, 'course': ctx_loc.course, 'name': ctx_loc.name})
|
|
else:
|
|
return course_page_name
|
|
|
|
|
|
class CoursePageNames:
|
|
""" Constants for pages that are recognized by get_url_reverse method. """
|
|
ManageUsers = "manage_users"
|
|
SettingsDetails = "settings_details"
|
|
SettingsGrading = "settings_grading"
|
|
CourseOutline = "course_index"
|
|
Checklists = "checklists"
|
|
|
|
|
|
def add_extra_panel_tab(tab_type, course):
|
|
"""
|
|
Used to add the panel tab to a course if it does not exist.
|
|
@param tab_type: A string representing the tab type.
|
|
@param course: A course object from the modulestore.
|
|
@return: Boolean indicating whether or not a tab was added and a list of tabs for the course.
|
|
"""
|
|
# Copy course tabs
|
|
course_tabs = copy.copy(course.tabs)
|
|
changed = False
|
|
# Check to see if open ended panel is defined in the course
|
|
|
|
tab_panel = EXTRA_TAB_PANELS.get(tab_type)
|
|
if tab_panel not in course_tabs:
|
|
# Add panel to the tabs if it is not defined
|
|
course_tabs.append(tab_panel)
|
|
changed = True
|
|
return changed, course_tabs
|
|
|
|
|
|
def remove_extra_panel_tab(tab_type, course):
|
|
"""
|
|
Used to remove the panel tab from a course if it exists.
|
|
@param tab_type: A string representing the tab type.
|
|
@param course: A course object from the modulestore.
|
|
@return: Boolean indicating whether or not a tab was added and a list of tabs for the course.
|
|
"""
|
|
# Copy course tabs
|
|
course_tabs = copy.copy(course.tabs)
|
|
changed = False
|
|
# Check to see if open ended panel is defined in the course
|
|
|
|
tab_panel = EXTRA_TAB_PANELS.get(tab_type)
|
|
if tab_panel in course_tabs:
|
|
# Add panel to the tabs if it is not defined
|
|
course_tabs = [ct for ct in course_tabs if ct != tab_panel]
|
|
changed = True
|
|
return changed, course_tabs
|