xmodule_modifiers.py 6.74 KB
Newer Older
1
import json
2
import logging
3
import static_replace
4

5 6 7
from django.conf import settings
from functools import wraps
from mitxmako.shortcuts import render_to_string
8 9
from xmodule.seq_module import SequenceModule
from xmodule.vertical_module import VerticalModule
10 11
import datetime
from django.utils.timezone import UTC
12

13
log = logging.getLogger("mitx.xmodule_modifiers")
14

Calen Pennington committed
15

16
def wrap_xmodule(get_html, module, template, context=None):
17 18 19 20 21 22 23 24
    """
    Wraps the results of get_html in a standard <section> with identifying
    data so that the appropriate javascript module can be loaded onto it.

    get_html: An XModule.get_html method or an XModuleDescriptor.get_html method
    module: An XModule
    template: A template that takes the variables:
        content: the results of get_html,
25
        display_name: the display name of the xmodule, if available (None otherwise)
26
        class_: the module class name
27 28
        module_name: the js_module_name of the module
    """
29 30
    if context is None:
        context = {}
31 32 33

    @wraps(get_html)
    def _get_html():
34
        context.update({
35
            'content': get_html(),
36
            'display_name': module.display_name,
37
            'class_': module.__class__.__name__,
38 39
            'module_name': module.js_module_name
        })
40 41

        return render_to_string(template, context)
42 43 44
    return _get_html


45
def replace_course_urls(get_html, course_id):
46 47 48 49 50 51 52
    """
    Updates the supplied module with a new get_html function that wraps
    the old get_html function and substitutes urls of the form /course/...
    with urls that are /courses/<course_id>/...
    """
    @wraps(get_html)
    def _get_html():
53
        return static_replace.replace_course_urls(get_html(), course_id)
54 55
    return _get_html

Calen Pennington committed
56

57
def replace_static_urls(get_html, data_dir, course_namespace=None):
58 59 60 61 62 63
    """
    Updates the supplied module with a new get_html function that wraps
    the old get_html function and substitutes urls of the form /static/...
    with urls that are /static/<prefix>/...
    """

64 65
    @wraps(get_html)
    def _get_html():
66
        return static_replace.replace_static_urls(get_html(), data_dir, course_namespace)
67
    return _get_html
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85


def grade_histogram(module_id):
    ''' Print out a histogram of grades on a given problem.
        Part of staff member debug info.
    '''
    from django.db import connection
    cursor = connection.cursor()

    q = """SELECT courseware_studentmodule.grade,
                  COUNT(courseware_studentmodule.student_id)
    FROM courseware_studentmodule
    WHERE courseware_studentmodule.module_id=%s
    GROUP BY courseware_studentmodule.grade"""
    # Passing module_id this way prevents sql-injection.
    cursor.execute(q, [module_id])

    grades = list(cursor.fetchall())
86
    grades.sort(key=lambda x: x[0])  # Add ORDER BY to sql query?
87
    if len(grades) >= 1 and grades[0][0] is None:
88 89 90 91
        return []
    return grades


92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
def save_module(get_html, module):
    """
    Updates the given get_html function for the given module to save the fields
    after rendering.
    """
    @wraps(get_html)
    def _get_html():
        """Cache the rendered output, save, then return the output."""
        rendered_html = get_html()
        module.save()
        return rendered_html

    return _get_html


107
def add_histogram(get_html, module, user):
108 109 110 111 112
    """
    Updates the supplied module with a new get_html function that wraps
    the output of the old get_html function with additional information
    for admin users only, including a histogram of student answers and the
    definition of the xmodule
113

114
    Does nothing if module is a SequenceModule or a VerticalModule.
115
    """
116 117
    @wraps(get_html)
    def _get_html():
118

119
        if type(module) in [SequenceModule, VerticalModule]:  # TODO: make this more general, eg use an XModule attribute instead
120 121
            return get_html()

122
        module_id = module.id
123 124 125 126 127 128
        if module.descriptor.has_score:
            histogram = grade_histogram(module_id)
            render_histogram = len(histogram) > 0
        else:
            histogram = None
            render_histogram = False
129

130
        if settings.MITX_FEATURES.get('ENABLE_LMS_MIGRATION'):
131
            [filepath, filename] = getattr(module.descriptor, 'xml_attributes', {}).get('filename', ['', None])
132 133 134 135 136 137
            osfs = module.system.filestore
            if filename is not None and osfs.exists(filename):
                # if original, unmangled filename exists then use it (github
                # doesn't like symlinks)
                filepath = filename
            data_dir = osfs.root_path.rsplit('/')[-1]
138
            giturl = module.lms.giturl or 'https://github.com/MITx'
139 140 141 142 143 144 145
            edit_link = "%s/%s/tree/master/%s" % (giturl, data_dir, filepath)
        else:
            edit_link = False
            # Need to define all the variables that are about to be used
            giturl = ""
            data_dir = ""

146
        source_file = module.lms.source_file  # source used to generate the problem XML, eg latex or word
147

ichuang committed
148
        # useful to indicate to staff if problem has been released or not
149
        # TODO (ichuang): use _has_access_descriptor.can_load in lms.courseware.access, instead of now>mstart comparison here
150
        now = datetime.datetime.now(UTC())
ichuang committed
151
        is_released = "unknown"
152 153
        mstart = module.descriptor.lms.start

154 155
        if mstart is not None:
            is_released = "<font color='red'>Yes!</font>" if (now > mstart) else "<font color='green'>Not yet</font>"
156

157
        staff_context = {'fields': [(field.name, getattr(module, field.name)) for field in module.fields],
158
                         'lms_fields': [(field.name, getattr(module.lms, field.name)) for field in module.lms.fields],
159
                         'xml_attributes' : getattr(module.descriptor, 'xml_attributes', {}),
160
                         'location': module.location,
161
                         'xqa_key': module.lms.xqa_key,
Calen Pennington committed
162
                         'source_file': source_file,
163
                         'source_url': '%s/%s/tree/master/%s' % (giturl, data_dir, source_file),
164
                         'category': str(module.__class__.__name__),
165
                         # Template uses element_id in js function names, so can't allow dashes
Calen Pennington committed
166
                         'element_id': module.location.html_id().replace('-', '_'),
167
                         'edit_link': edit_link,
168
                         'user': user,
Calen Pennington committed
169
                         'xqa_server': settings.MITX_FEATURES.get('USE_XQA_SERVER', 'http://xqa:server@content-qa.mitx.mit.edu/xqa'),
170 171
                         'histogram': json.dumps(histogram),
                         'render_histogram': render_histogram,
ichuang committed
172 173 174
                         'module_content': get_html(),
                         'is_released': is_released,
                         }
175 176
        return render_to_string("staff_problem_info.html", staff_context)

177
    return _get_html