__init__.py 7.52 KB
Newer Older
1 2 3
import logging
import re

4
from staticfiles.storage import staticfiles_storage
5 6
from staticfiles import finders
from django.conf import settings
7

8
from xmodule.modulestore.django import modulestore
9
from xmodule.modulestore import ModuleStoreEnum
10 11
from xmodule.contentstore.content import StaticContent

12 13
from opaque_keys.edx.locator import AssetLocator

14 15
log = logging.getLogger(__name__)

16 17

def _url_replace_regex(prefix):
18 19 20
    """
    Match static urls in quotes that don't end in '?raw'.

21 22
    To anyone contemplating making this more complicated:
    http://xkcd.com/1171/
23
    """
24
    return ur"""
25 26 27
        (?x)                      # flags=re.VERBOSE
        (?P<quote>\\?['"])        # the opening quotes
        (?P<prefix>{prefix})      # the prefix
28
        (?P<rest>.*?)             # everything else in the url
29
        (?P=quote)                # the first matching closing quote
30 31
        """.format(prefix=prefix)

Calen Pennington committed
32

33 34 35 36 37 38 39 40
def try_staticfiles_lookup(path):
    """
    Try to lookup a path in staticfiles_storage.  If it fails, return
    a dead link instead of raising an exception.
    """
    try:
        url = staticfiles_storage.url(path)
    except Exception as err:
41
        log.warning("staticfiles_storage couldn't find path {0}: {1}".format(
42
            path, str(err)))
43 44
        # Just return the original path; don't kill everything.
        url = path
45
    return url
46 47


48 49 50 51 52 53 54 55 56 57
def replace_jump_to_id_urls(text, course_id, jump_to_id_base_url):
    """
    This will replace a link to another piece of courseware to a 'jump_to'
    URL that will redirect to the right place in the courseware

    NOTE: This is similar to replace_course_urls in terms of functionality
    but it is intended to be used when we only have a 'id' that the
    course author provides. This is much more helpful when using
    Studio authored courses since they don't need to know the path. This
    is also durable with respect to item moves.
58 59 60

    text: The content over which to perform the subtitutions
    course_id: The course_id in which this rewrite happens
61
    jump_to_id_base_url:
62 63 64 65 66
        A app-tier (e.g. LMS) absolute path to the base of the handler that will perform the
        redirect. e.g. /courses/<org>/<course>/<run>/jump_to_id. NOTE the <id> will be appended to
        the end of this URL at re-write time

    output: <text> after the link rewriting rules are applied
67 68 69 70 71 72 73 74 75 76
    """

    def replace_jump_to_id_url(match):
        quote = match.group('quote')
        rest = match.group('rest')
        return "".join([quote, jump_to_id_base_url + rest, quote])

    return re.sub(_url_replace_regex('/jump_to_id/'), replace_jump_to_id_url, text)


77
def replace_course_urls(text, course_key):
78 79
    """
    Replace /course/$stuff urls with /courses/$course_id/$stuff urls
80

81 82
    text: The text to replace
    course_module: A CourseDescriptor
83

84 85
    returns: text with the links replaced
    """
86

87 88
    course_id = course_key.to_deprecated_string()

89 90 91 92
    def replace_course_url(match):
        quote = match.group('quote')
        rest = match.group('rest')
        return "".join([quote, '/courses/' + course_id + '/', rest, quote])
93

94
    return re.sub(_url_replace_regex('/course/'), replace_course_url, text)
95

96

97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
def process_static_urls(text, replacement_function, data_dir=None):
    """
    Run an arbitrary replacement function on any urls matching the static file
    directory
    """
    def wrap_part_extraction(match):
        """
        Unwraps a match group for the captures specified in _url_replace_regex
        and forward them on as function arguments
        """
        original = match.group(0)
        prefix = match.group('prefix')
        quote = match.group('quote')
        rest = match.group('rest')
        return replacement_function(original, prefix, quote, rest)

    return re.sub(
        _url_replace_regex(u'(?:{static_url}|/static/)(?!{data_dir})'.format(
            static_url=settings.STATIC_URL,
            data_dir=data_dir
        )),
        wrap_part_extraction,
        text
    )


def make_static_urls_absolute(request, html):
    """
    Converts relative URLs referencing static assets to absolute URLs
    """
    def replace(__, prefix, quote, rest):
        """
        Function to actually do a single relative -> absolute url replacement
        """
        processed = request.build_absolute_uri(prefix + rest)
        return quote + processed + quote

    return process_static_urls(
        html,
        replace
    )


def replace_static_urls(text, data_directory=None, course_id=None, static_asset_path=''):
141 142 143 144
    """
    Replace /static/$stuff urls either with their correct url as generated by collectstatic,
    (/static/$md5_hashed_stuff) or by the course-specific content static url
    /static/$course_data_dir/$stuff, or, if course_namespace is not None, by the
145
    correct url in the contentstore (/c4x/.. or /asset-loc:..)
146

147 148
    text: The source text to do the substitution in
    data_directory: The directory in which course data is stored
149
    course_id: The course identifier used to distinguish static content for this course in studio
150
    static_asset_path: Path for static assets, which overrides data_directory and course_namespace, if nonempty
151
    """
152

153 154 155 156
    def replace_static_url(original, prefix, quote, rest):
        """
        Replace a single matched url.
        """
157 158 159 160
        # Don't mess with things that end in '?raw'
        if rest.endswith('?raw'):
            return original

161
        # In debug mode, if we can find the url as is,
162
        if settings.DEBUG and finders.find(rest, True):
163
            return original
Chris Dodge committed
164
        # if we're running with a MongoBacked store course_namespace is not None, then use studio style urls
165 166 167
        elif (not static_asset_path) \
                and course_id \
                and modulestore().get_modulestore_type(course_id) != ModuleStoreEnum.Type.xml:
Chris Dodge committed
168
            # first look in the static file pipeline and see if we are trying to reference
169
            # a piece of static content which is in the edx-platform repo (e.g. JS associated with an xmodule)
170 171 172 173 174 175 176 177 178

            exists_in_staticfiles_storage = False
            try:
                exists_in_staticfiles_storage = staticfiles_storage.exists(rest)
            except Exception as err:
                log.warning("staticfiles_storage couldn't find path {0}: {1}".format(
                    rest, str(err)))

            if exists_in_staticfiles_storage:
179 180
                url = staticfiles_storage.url(rest)
            else:
Chris Dodge committed
181 182
                # if not, then assume it's courseware specific content and then look in the
                # Mongo-backed database
Chris Dodge committed
183
                url = StaticContent.convert_legacy_static_url_with_course_id(rest, course_id)
184 185 186 187

                if AssetLocator.CANONICAL_NAMESPACE in url:
                    url = url.replace('block@', 'block/', 1)

188
        # Otherwise, look the file up in staticfiles_storage, and append the data directory if needed
189
        else:
190
            course_path = "/".join((static_asset_path or data_directory, rest))
191

192
            try:
193 194 195 196
                if staticfiles_storage.exists(rest):
                    url = staticfiles_storage.url(rest)
                else:
                    url = staticfiles_storage.url(course_path)
197
            # And if that fails, assume that it's course content, and add manually data directory
198 199
            except Exception as err:
                log.warning("staticfiles_storage couldn't find path {0}: {1}".format(
200
                    rest, str(err)))
201
                url = "".join([prefix, course_path])
202

203 204
        return "".join([quote, url, quote])

205
    return process_static_urls(text, replace_static_url, data_dir=static_asset_path or data_directory)