utils.py 10.1 KB
Newer Older
David Baumgold committed
1 2
#pylint: disable=E1103, E1101

3
import copy
cahrens committed
4
import logging
cahrens committed
5
import re
6 7

from django.conf import settings
David Baumgold committed
8
from django.utils.translation import ugettext as _
9 10 11 12 13 14

from xmodule.contentstore.content import StaticContent
from xmodule.contentstore.django import contentstore
from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
15 16 17
from django_comment_common.utils import unseed_permissions_roles
from xmodule.modulestore.store_utilities import delete_course
from xmodule.course_module import CourseDescriptor
18
from xmodule.modulestore.draft import DIRECT_ONLY_CATEGORIES
19
from student.roles import CourseInstructorRole, CourseStaffRole
20

cahrens committed
21 22

log = logging.getLogger(__name__)
23

24
# In order to instantiate an open ended tab automatically, need to have this data
David Baumgold committed
25 26
OPEN_ENDED_PANEL = {"name": _("Open Ended Panel"), "type": "open_ended"}
NOTES_PANEL = {"name": _("My Notes"), "type": "notes"}
27
EXTRA_TAB_PANELS = dict([(p['type'], p) for p in [OPEN_ENDED_PANEL, NOTES_PANEL]])
Calen Pennington committed
28

29

30 31 32 33 34 35 36 37
def delete_course_and_groups(course_id, commit=False):
    """
    This deletes the courseware associated with a course_id as well as cleaning update_item
    the various user table stuff (groups, permissions, etc.)
    """
    module_store = modulestore('direct')
    content_store = contentstore()

38
    org, course_num, _ = course_id.split("/")
39 40 41 42 43 44 45 46 47 48 49
    module_store.ignore_write_events_on_courses.append('{0}/{1}'.format(org, course_num))

    loc = CourseDescriptor.id_to_location(course_id)
    if delete_course(module_store, content_store, loc, commit):
        print 'removing forums permissions and roles...'
        unseed_permissions_roles(course_id)

        print 'removing User permissions from course....'
        # in the django layer, we need to remove all the user permissions groups associated with this course
        if commit:
            try:
50 51 52 53
                staff_role = CourseStaffRole(loc)
                staff_role.remove_users(*staff_role.users_with_role())
                instructor_role = CourseInstructorRole(loc)
                instructor_role.remove_users(*instructor_role.users_with_role())
54 55 56 57
            except Exception as err:
                log.error("Error in deleting course groups for {0}: {1}".format(loc, err))


58
def get_modulestore(category_or_location):
Don Mitchell committed
59 60 61
    """
    Returns the correct modulestore to use for modifying the specified location
    """
62 63
    if isinstance(category_or_location, Location):
        category_or_location = category_or_location.category
Calen Pennington committed
64

65
    if category_or_location in DIRECT_ONLY_CATEGORIES:
Don Mitchell committed
66 67 68
        return modulestore('direct')
    else:
        return modulestore()
69

Calen Pennington committed
70

71
def get_course_location_for_item(location):
72 73 74 75 76 77
    '''
    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
    '''
78 79 80 81 82 83 84
    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
cahrens committed
85
        course_search_location = Location('i4x', item_loc.org, item_loc.course, 'course', None)
86 87 88 89 90
        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:
91
            raise Exception('Could not find course at {0}'.format(course_search_location))
92 93

        if found_cnt > 1:
94
            raise Exception('Found more than one course at {0}. There should only be one!!! Dump = {1}'.format(course_search_location, courses))
95 96 97 98

        location = courses[0].location

    return location
99

Calen Pennington committed
100

101 102 103 104 105 106 107 108 109 110 111 112
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
cahrens committed
113
    course_search_location = Location('i4x', item_loc.org, item_loc.course, 'course', None)
114 115 116 117 118 119 120 121 122 123 124 125
    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]

126

127
def get_lms_link_for_item(location, preview=False, course_id=None):
128 129 130 131 132 133 134 135 136
    """
    Returns an LMS link to the course with a jump_to to the provided location.

    :param location: the location to jump to
    :param preview: True if the preview version of LMS should be returned. Default value is false.
    :param course_id: the course_id within which the location lives. If not specified, the course_id is obtained
           by calling Location(location).course_id; note that this only works for locations representing courses
           instead of elements within courses.
    """
137
    if course_id is None:
138
        course_id = Location(location).course_id
139

140
    if settings.LMS_BASE is not None:
141
        if preview:
142
            lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE')
143 144
        else:
            lms_base = settings.LMS_BASE
Chris Dodge committed
145

146 147 148
        lms_link = "//{lms_base}/courses/{course_id}/jump_to/{location}".format(
            lms_base=lms_base,
            course_id=course_id,
cahrens committed
149
            location=Location(location)
150 151 152 153 154 155
        )
    else:
        lms_link = None

    return lms_link

Calen Pennington committed
156

cahrens committed
157 158 159 160
def get_lms_link_for_about_page(location):
    """
    Returns the url to the course about page from the location tuple.
    """
161
    if settings.FEATURES.get('ENABLE_MKTG_SITE', False):
cahrens committed
162 163 164 165 166 167 168 169 170
        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:
cahrens committed
171 172
                # 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.
cahrens committed
173
                about_base = marketing_urls.get('ROOT')
cahrens committed
174 175
                # Strip off https:// (or http://) to be consistent with the formatting of LMS_BASE.
                about_base = re.sub(r"^https?://", "", about_base)
176 177 178 179 180 181 182 183
    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,
184
            course_id=Location(location).course_id
cahrens committed
185 186 187 188 189 190
        )
    else:
        lms_link = None

    return lms_link

Calen Pennington committed
191

192 193 194 195 196 197 198
def course_image_url(course):
    """Returns the image url for the course."""
    loc = course.location._replace(tag='c4x', category='asset', name=course.course_image)
    path = StaticContent.get_url_path_from_location(loc)
    return path


199 200 201 202 203 204
class UnitState(object):
    draft = 'draft'
    private = 'private'
    public = 'public'


205 206 207 208 209 210 211 212 213 214
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
    """

Chris Dodge committed
215
    if getattr(unit, 'is_draft', False):
216
        try:
217
            modulestore('direct').get_item(unit.location)
218
            return UnitState.draft
219
        except ItemNotFoundError:
220
            return UnitState.private
221
    else:
222
        return UnitState.public
Lyla Fischer committed
223 224


225 226 227 228 229 230 231
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:
232
        get_modulestore(location).update_item(location, value)
cahrens committed
233 234


235
def add_extra_panel_tab(tab_type, course):
Vik Paruchuri committed
236
    """
237 238
    Used to add the panel tab to a course if it does not exist.
    @param tab_type: A string representing the tab type.
Vik Paruchuri committed
239 240 241
    @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.
    """
Don Mitchell committed
242
    # Copy course tabs
243 244
    course_tabs = copy.copy(course.tabs)
    changed = False
Don Mitchell committed
245
    # Check to see if open ended panel is defined in the course
246

247 248
    tab_panel = EXTRA_TAB_PANELS.get(tab_type)
    if tab_panel not in course_tabs:
Don Mitchell committed
249
        # Add panel to the tabs if it is not defined
250
        course_tabs.append(tab_panel)
251 252
        changed = True
    return changed, course_tabs
253

Chris Dodge committed
254

255
def remove_extra_panel_tab(tab_type, course):
256
    """
257 258
    Used to remove the panel tab from a course if it exists.
    @param tab_type: A string representing the tab type.
259 260 261
    @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.
    """
Don Mitchell committed
262
    # Copy course tabs
263 264
    course_tabs = copy.copy(course.tabs)
    changed = False
Don Mitchell committed
265
    # Check to see if open ended panel is defined in the course
266 267 268

    tab_panel = EXTRA_TAB_PANELS.get(tab_type)
    if tab_panel in course_tabs:
Don Mitchell committed
269
        # Add panel to the tabs if it is not defined
270
        course_tabs = [ct for ct in course_tabs if ct != tab_panel]
271 272
        changed = True
    return changed, course_tabs