courses.py 8.01 KB
Newer Older
1
from collections import defaultdict
2
from fs.errors import ResourceNotFoundError
3
from functools import wraps
4
import logging
5

6
from path import path
7
from django.conf import settings
8
from django.core.urlresolvers import reverse
9
from django.http import Http404
10

11
from xmodule.course_module import CourseDescriptor
12
from xmodule.modulestore import Location
13
from xmodule.modulestore.django import modulestore
14
from xmodule.modulestore.exceptions import ItemNotFoundError
15
from static_replace import replace_urls, try_staticfiles_lookup
16
from courseware.access import has_access
17
import branding
18

19
log = logging.getLogger(__name__)
20

21

22
def get_course_by_id(course_id):
23
    """
24
    Given a course id, return the corresponding course descriptor.
25

26
    If course_id is not valid, raises a 404.
27
    """
28 29
    try:
        course_loc = CourseDescriptor.id_to_location(course_id)
30
        return modulestore().get_instance(course_id, course_loc)
31 32
    except (KeyError, ItemNotFoundError):
        raise Http404("Course not found.")
Victor Shnayder committed
33

34

35 36 37 38 39
def get_course_with_access(user, course_id, action):
    """
    Given a course_id, look up the corresponding course descriptor,
    check that the user has the access to perform the specified action
    on the course, and return the descriptor.
40

41 42 43 44 45 46 47
    Raises a 404 if the course_id is invalid, or the user doesn't have access.
    """
    course = get_course_by_id(course_id)
    if not has_access(user, course, action):
        # Deliberately return a non-specific error message to avoid
        # leaking info about access control settings
        raise Http404("Course not found.")
48
    return course
49

50

51 52 53 54 55 56 57 58 59 60
def get_opt_course_with_access(user, course_id, action):
    """
    Same as get_course_with_access, except that if course_id is None,
    return None without performing any access checks.
    """
    if course_id is None:
        return None
    return get_course_with_access(user, course_id, action)


61
def course_image_url(course):
62 63 64 65
    """Try to look up the image url for the course.  If it's not found,
    log an error and return the dead link"""
    path = course.metadata['data_dir'] + "/images/course_image.jpg"
    return try_staticfiles_lookup(path)
66

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
def find_file(fs, dirs, filename):
    """
    Looks for a filename in a list of dirs on a filesystem, in the specified order.

    fs: an OSFS filesystem
    dirs: a list of path objects
    filename: a string

    Returns d / filename if found in dir d, else raises ResourceNotFoundError.
    """
    for d in dirs:
        filepath = path(d) / filename
        if fs.exists(filepath):
            return filepath
    raise ResourceNotFoundError("Could not find {0}".format(filename))

83 84
def get_course_about_section(course, section_key):
    """
Victor Shnayder committed
85 86 87
    This returns the snippet of html to be rendered on the course about page,
    given the key for the section.

88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
    Valid keys:
    - overview
    - title
    - university
    - number
    - short_description
    - description
    - key_dates (includes start, end, exams, etc)
    - video
    - course_staff_short
    - course_staff_extended
    - requirements
    - syllabus
    - textbook
    - faq
    - more_info
104
    - ocw_links
105 106
    """

Victor Shnayder committed
107 108 109
    # Many of these are stored as html files instead of some semantic
    # markup. This can change without effecting this interface when we find a
    # good format for defining so many snippets of text/html.
110 111

# TODO: Remove number, instructors from this list
Victor Shnayder committed
112 113 114 115
    if section_key in ['short_description', 'description', 'key_dates', 'video',
                       'course_staff_short', 'course_staff_extended',
                       'requirements', 'syllabus', 'textbook', 'faq', 'more_info',
                       'number', 'instructors', 'overview',
116
                       'effort', 'end_date', 'prerequisites', 'ocw_links']:
117

118
        try:
119 120 121 122 123
            fs = course.system.resources_fs
            # first look for a run-specific version
            dirs = [path("about") / course.url_name, path("about")]
            filepath = find_file(fs, dirs, section_key + ".html")
            with fs.open(filepath) as htmlFile:
Victor Shnayder committed
124 125
                return replace_urls(htmlFile.read().decode('utf-8'),
                                    course.metadata['data_dir'])
126
        except ResourceNotFoundError:
Victor Shnayder committed
127 128
            log.warning("Missing about section {key} in course {url}".format(
                key=section_key, url=course.location.url()))
129 130
            return None
    elif section_key == "title":
131
        return course.metadata.get('display_name', course.url_name)
132 133 134 135 136 137 138
    elif section_key == "university":
        return course.location.org
    elif section_key == "number":
        return course.number

    raise KeyError("Invalid about key " + str(section_key))

139

140 141
def get_course_info_section(course, section_key):
    """
Victor Shnayder committed
142 143 144
    This returns the snippet of html to be rendered on the course info page,
    given the key for the section.

145 146 147 148 149 150 151
    Valid keys:
    - handouts
    - guest_handouts
    - updates
    - guest_updates
    """

Victor Shnayder committed
152 153 154
    # Many of these are stored as html files instead of some semantic
    # markup. This can change without effecting this interface when we find a
    # good format for defining so many snippets of text/html.
155 156 157

    if section_key in ['handouts', 'guest_handouts', 'updates', 'guest_updates']:
        try:
158 159 160 161 162 163
            fs = course.system.resources_fs
            # first look for a run-specific version
            dirs = [path("info") / course.url_name, path("info")]
            filepath = find_file(fs, dirs, section_key + ".html")

            with fs.open(filepath) as htmlFile:
164 165 166 167
                # Replace '/static/' urls
                info_html = replace_urls(htmlFile.read().decode('utf-8'), course.metadata['data_dir'])

                # Replace '/course/' urls
168
                course_root = reverse('course_root', args=[course.id])[:-1] # Remove trailing slash
169 170
                info_html = replace_urls(info_html, course_root, '/course/')
                return info_html
171
        except ResourceNotFoundError:
Victor Shnayder committed
172 173
            log.exception("Missing info section {key} in course {url}".format(
                key=section_key, url=course.location.url()))
174
            return "! Info section missing !"
175

176
    raise KeyError("Invalid about key " + str(section_key))
177

178

179 180
# TODO: Fix this such that these are pulled in as extra course-specific tabs.
#       arjun will address this by the end of October if no one does so prior to
181
#       then.
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
def get_course_syllabus_section(course, section_key):
    """
    This returns the snippet of html to be rendered on the syllabus page,
    given the key for the section.

    Valid keys:
    - syllabus
    - guest_syllabus
    """

    # Many of these are stored as html files instead of some semantic
    # markup. This can change without effecting this interface when we find a
    # good format for defining so many snippets of text/html.

    if section_key in ['syllabus', 'guest_syllabus']:
        try:
198 199 200 201 202
            fs = course.system.resources_fs
            # first look for a run-specific version
            dirs = [path("syllabus") / course.url_name, path("syllabus")]
            filepath = find_file(fs, dirs, section_key + ".html")
            with fs.open(filepath) as htmlFile:
203 204 205 206 207 208 209 210 211
                return replace_urls(htmlFile.read().decode('utf-8'),
                                    course.metadata['data_dir'])
        except ResourceNotFoundError:
            log.exception("Missing syllabus section {key} in course {url}".format(
                key=section_key, url=course.location.url()))
            return "! Syllabus missing !"

    raise KeyError("Invalid about key " + str(section_key))

212 213

def get_courses_by_university(user, domain=None):
214 215 216 217 218 219
    '''
    Returns dict of lists of courses available, keyed by course.org (ie university).
    Courses are sorted by course.number.
    '''
    # TODO: Clean up how 'error' is done.
    # filter out any courses that errored.
220
    visible_courses = branding.get_visible_courses(domain)
221

222
    universities = defaultdict(list)
223
    for course in visible_courses:
David Ormsbee committed
224 225
        if not has_access(user, course, 'see_exists'):
            continue
226
        universities[course.org].append(course)
227
    return universities