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

3
import copy
cahrens committed
4
import logging
cahrens committed
5
import re
6 7
from datetime import datetime
from pytz import UTC
8 9

from django.conf import settings
David Baumgold committed
10
from django.utils.translation import ugettext as _
11
from django.core.urlresolvers import reverse
12 13
from django_comment_common.models import assign_default_role
from django_comment_common.utils import seed_permissions_roles
14 15

from xmodule.contentstore.content import StaticContent
16
from xmodule.modulestore import ModuleStoreEnum
17
from xmodule.modulestore.django import modulestore
18
from xmodule.modulestore.exceptions import ItemNotFoundError
19
from opaque_keys.edx.keys import UsageKey, CourseKey
20
from student.roles import CourseInstructorRole, CourseStaffRole
21 22
from student.models import CourseEnrollment
from student import auth
23

cahrens committed
24 25

log = logging.getLogger(__name__)
26

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

32

33
def add_instructor(course_key, requesting_user, new_instructor):
34
    """
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
    Adds given user as instructor and staff to the given course,
    after verifying that the requesting_user has permission to do so.
    """
    # can't use auth.add_users here b/c it requires user to already have Instructor perms in this course
    CourseInstructorRole(course_key).add_users(new_instructor)
    auth.add_users(requesting_user, CourseStaffRole(course_key), new_instructor)


def initialize_permissions(course_key, user_who_created_course):
    """
    Initializes a new course by enrolling the course creator as a student,
    and initializing Forum by seeding its permissions and assigning default roles.
    """
    # seed the forums
    seed_permissions_roles(course_key)

    # auto-enroll the course creator in the course so that "View Live" will work.
    CourseEnrollment.enroll(user_who_created_course, course_key)

    # set default forum roles (assign 'Student' role)
    assign_default_role(course_key, user_who_created_course)


def remove_all_instructors(course_key):
    """
60
    Removes all instructor and staff users from the given course.
61 62 63 64 65 66 67 68 69 70
    """
    staff_role = CourseStaffRole(course_key)
    staff_role.remove_users(*staff_role.users_with_role())
    instructor_role = CourseInstructorRole(course_key)
    instructor_role.remove_users(*instructor_role.users_with_role())


def delete_course_and_groups(course_key, user_id):
    """
    This deletes the courseware associated with a course_key as well as cleaning update_item
71 72
    the various user table stuff (groups, permissions, etc.)
    """
73
    module_store = modulestore()
74

75
    with module_store.bulk_operations(course_key):
76
        module_store.delete_course(course_key, user_id)
77

78 79 80
        print 'removing User permissions from course....'
        # in the django layer, we need to remove all the user permissions groups associated with this course
        try:
81
            remove_all_instructors(course_key)
82
        except Exception as err:
83
            log.error("Error in deleting course groups for {0}: {1}".format(course_key, err))
84

Calen Pennington committed
85

86
def get_lms_link_for_item(location, preview=False):
87 88 89 90 91 92
    """
    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.
    """
93
    assert(isinstance(location, UsageKey))
94 95 96 97 98 99

    if settings.LMS_BASE is None:
        return None

    if preview:
        lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE')
100
    else:
101
        lms_base = settings.LMS_BASE
102

103
    return u"//{lms_base}/courses/{course_key}/jump_to/{location}".format(
104
        lms_base=lms_base,
105
        course_key=location.course_key.to_deprecated_string(),
106 107
        location=location.to_deprecated_string(),
    )
108

Calen Pennington committed
109

110
def get_lms_link_for_about_page(course_key):
cahrens committed
111 112 113
    """
    Returns the url to the course about page from the location tuple.
    """
114

115
    assert(isinstance(course_key, CourseKey))
116

117
    if settings.FEATURES.get('ENABLE_MKTG_SITE', False):
cahrens committed
118 119
        if not hasattr(settings, 'MKTG_URLS'):
            log.exception("ENABLE_MKTG_SITE is True, but MKTG_URLS is not defined.")
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
            return None

        marketing_urls = settings.MKTG_URLS

        # 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', None)

        if about_base is None:
            log.exception('There is no ROOT defined in MKTG_URLS')
            return None

        # Strip off https:// (or http://) to be consistent with the formatting of LMS_BASE.
        about_base = re.sub(r"^https?://", "", about_base)

135 136 137
    elif settings.LMS_BASE is not None:
        about_base = settings.LMS_BASE
    else:
138
        return None
cahrens committed
139

140
    return u"//{about_base_url}/courses/{course_key}/about".format(
141
        about_base_url=about_base,
142
        course_key=course_key.to_deprecated_string()
143
    )
cahrens committed
144

Calen Pennington committed
145

146 147
def course_image_url(course):
    """Returns the image url for the course."""
148
    loc = StaticContent.compute_location(course.location.course_key, course.course_image)
149
    path = StaticContent.serialize_asset_key_with_slash(loc)
150 151
    return path

cahrens committed
152

153
# pylint: disable=invalid-name
154
def is_currently_visible_to_students(xblock):
155
    """
156 157
    Returns true if there is a published version of the xblock that is currently visible to students.
    This means that it has a release date in the past, and the xblock has not been set to staff only.
158 159 160
    """

    try:
161
        published = modulestore().get_item(xblock.location, revision=ModuleStoreEnum.RevisionOption.published_only)
162 163 164 165
    # If there's no published version then the xblock is clearly not visible
    except ItemNotFoundError:
        return False

166 167 168 169
    # If visible_to_staff_only is True, this xblock is not visible to students regardless of start date.
    if published.visible_to_staff_only:
        return False

170 171 172 173 174 175 176 177
    # Check start date
    if 'detached' not in published._class_tags and published.start is not None:
        return datetime.now(UTC) > published.start

    # No start date, so it's always visible
    return True


178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
def find_release_date_source(xblock):
    """
    Finds the ancestor of xblock that set its release date.
    """

    # Stop searching at the section level
    if xblock.category == 'chapter':
        return xblock

    parent_location = modulestore().get_parent_location(xblock.location,
                                                        revision=ModuleStoreEnum.RevisionOption.draft_preferred)
    # Orphaned xblocks set their own release date
    if not parent_location:
        return xblock

    parent = modulestore().get_item(parent_location)
    if parent.start != xblock.start:
        return xblock
    else:
        return find_release_date_source(parent)


200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
def find_staff_lock_source(xblock):
    """
    Returns the xblock responsible for setting this xblock's staff lock, or None if the xblock is not staff locked.
    If this xblock is explicitly locked, return it, otherwise find the ancestor which sets this xblock's staff lock.
    """

    # Stop searching if this xblock has explicitly set its own staff lock
    if xblock.fields['visible_to_staff_only'].is_set_on(xblock):
        return xblock

    # Stop searching at the section level
    if xblock.category == 'chapter':
        return None

    parent_location = modulestore().get_parent_location(xblock.location,
                                                        revision=ModuleStoreEnum.RevisionOption.draft_preferred)
    # Orphaned xblocks set their own staff lock
    if not parent_location:
        return None

    parent = modulestore().get_item(parent_location)
    return find_staff_lock_source(parent)


def ancestor_has_staff_lock(xblock, parent_xblock=None):
    """
    Returns True iff one of xblock's ancestors has staff lock.
    Can avoid mongo query by passing in parent_xblock.
    """
    if parent_xblock is None:
        parent_location = modulestore().get_parent_location(xblock.location,
                                                            revision=ModuleStoreEnum.RevisionOption.draft_preferred)
        if not parent_location:
            return False
        parent_xblock = modulestore().get_item(parent_location)
    return parent_xblock.visible_to_staff_only


238
def add_extra_panel_tab(tab_type, course):
Vik Paruchuri committed
239
    """
240 241
    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
242 243 244
    @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
245
    # Copy course tabs
246 247
    course_tabs = copy.copy(course.tabs)
    changed = False
Don Mitchell committed
248
    # Check to see if open ended panel is defined in the course
249

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

Chris Dodge committed
257

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

    tab_panel = EXTRA_TAB_PANELS.get(tab_type)
    if tab_panel in course_tabs:
Don Mitchell committed
272
        # Add panel to the tabs if it is not defined
273
        course_tabs = [ct for ct in course_tabs if ct != tab_panel]
274 275
        changed = True
    return changed, course_tabs
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300


def reverse_url(handler_name, key_name=None, key_value=None, kwargs=None):
    """
    Creates the URL for the given handler.
    The optional key_name and key_value are passed in as kwargs to the handler.
    """
    kwargs_for_reverse = {key_name: unicode(key_value)} if key_name else None
    if kwargs:
        kwargs_for_reverse.update(kwargs)
    return reverse('contentstore.views.' + handler_name, kwargs=kwargs_for_reverse)


def reverse_course_url(handler_name, course_key, kwargs=None):
    """
    Creates the URL for handlers that use course_keys as URL parameters.
    """
    return reverse_url(handler_name, 'course_key_string', course_key, kwargs)


def reverse_usage_url(handler_name, usage_key, kwargs=None):
    """
    Creates the URL for handlers that use usage_keys as URL parameters.
    """
    return reverse_url(handler_name, 'usage_key_string', usage_key, kwargs)