def get_ordered_updates(self, request, course):
        """
        Returns any course updates in reverse chronological order.
        """
        info_module = get_course_info_section_module(request, request.user, course, 'updates')

        updates = info_module.items if info_module else []
        info_block = getattr(info_module, '_xmodule', info_module) if info_module else None
        ordered_updates = [update for update in updates if update.get('status') == self.STATUS_VISIBLE]
        ordered_updates.sort(
            key=lambda item: (self.safe_parse_date(item['date']), item['id']),
            reverse=True
        )
        keyword_context = {
            'username': request.user.username,
            'user_id': request.user.id,
            'name': request.user.profile.name,
            'course_title': course.display_name,
            'course_id': course.id,
            'course_start_date': get_default_time_display(course.start),
            'course_end_date': get_default_time_display(course.end),
        }
        for update in ordered_updates:
            update['content'] = info_block.system.replace_urls(update['content'])
            update['content'] = info_block.system.substitute_keywords_with_data(update['content'], keyword_context)
        return ordered_updates
    def get_ordered_updates(self, request, course):
        """
        Returns any course updates in reverse chronological order.
        """
        info_module = get_course_info_section_module(request, request.user,
                                                     course, 'updates')

        updates = info_module.items if info_module else []
        info_block = getattr(info_module, '_xmodule',
                             info_module) if info_module else None
        ordered_updates = [
            update for update in updates
            if update.get('status') == self.STATUS_VISIBLE
        ]
        ordered_updates.sort(key=lambda item:
                             (self.safe_parse_date(item['date']), item['id']),
                             reverse=True)
        keyword_context = {
            'username': request.user.username,
            'user_id': request.user.id,
            'name': request.user.profile.name,
            'course_title': course.display_name,
            'course_id': course.id,
            'course_start_date': get_default_time_display(course.start),
            'course_end_date': get_default_time_display(course.end),
        }
        for update in ordered_updates:
            update['content'] = info_block.system.replace_urls(
                update['content'])
            update[
                'content'] = info_block.system.substitute_keywords_with_data(
                    update['content'], keyword_context)
        return ordered_updates
Exemple #3
0
def get_course_info_section(request, course, section_key):
    """
    This returns the snippet of html to be rendered on the course info page,
    given the key for the section.

    Valid keys:
    - handouts
    - guest_handouts
    - updates
    - guest_updates
    """
    info_module = get_course_info_section_module(request, course, section_key)

    html = ''
    if info_module is not None:
        try:
            html = info_module.render(STUDENT_VIEW).content
            context = {
                'username': request.user.username,
                'user_id': request.user.id,
                'name': request.user.profile.name,
                'course_title': course.display_name,
                'course_id': course.id,
                'course_start_date': get_default_time_display(course.start),
                'course_end_date': get_default_time_display(course.end),
            }
            html = substitute_keywords_with_data(html, context)
        except Exception:  # pylint: disable=broad-except
            html = render_to_string('courseware/error-message.html', None)
            log.exception(
                u"Error rendering course=%s, section_key=%s",
                course, section_key
            )

    return html
def _get_course_email_context(course):
    """
    Returns context arguments to apply to all emails, independent of recipient.
    """
    course_id = course.id.to_deprecated_string()
    course_title = course.display_name
    course_start_date = get_default_time_display(course.start)
    course_end_date = get_default_time_display(course.end)
    course_root = reverse('course_root', kwargs={'course_id': course_id})
    course_url = '{}{}'.format(
        settings.LMS_ROOT_URL,
        course_root
    )
    image_url = u'{}{}'.format(settings.LMS_ROOT_URL, course_image_url(course))
    email_context = {
        'course_title': course_title,
        'course_root': course_root,
        'course_language': course.language,
        'course_url': course_url,
        'course_image_url': image_url,
        'course_start_date': course_start_date,
        'course_end_date': course_end_date,
        'account_settings_url': '{}{}'.format(settings.LMS_ROOT_URL, reverse('account_settings')),
        'email_settings_url': '{}{}'.format(settings.LMS_ROOT_URL, reverse('dashboard')),
        'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME),
    }
    return email_context
Exemple #5
0
def _get_course_email_context(course):
    """
    Returns context arguments to apply to all emails, independent of recipient.
    """
    course_id = course.id.to_deprecated_string()
    course_title = course.display_name
    course_start_date = get_default_time_display(course.start)
    course_end_date = get_default_time_display(course.end)
    course_root = reverse('course_root', kwargs={'course_id': course_id})
    course_url = '{}{}'.format(settings.LMS_ROOT_URL, course_root)
    image_url = u'{}{}'.format(settings.LMS_ROOT_URL, course_image_url(course))
    email_context = {
        'course_title':
        course_title,
        'course_root':
        course_root,
        'course_language':
        course.language,
        'course_url':
        course_url,
        'course_image_url':
        image_url,
        'course_start_date':
        course_start_date,
        'course_end_date':
        course_end_date,
        'account_settings_url':
        '{}{}'.format(settings.LMS_ROOT_URL, reverse('account_settings')),
        'email_settings_url':
        '{}{}'.format(settings.LMS_ROOT_URL, reverse('dashboard')),
        'platform_name':
        configuration_helpers.get_value('PLATFORM_NAME',
                                        settings.PLATFORM_NAME),
    }
    return email_context
Exemple #6
0
def get_course_info_section(request, user, course, section_key):
    """
    This returns the snippet of html to be rendered on the course info page,
    given the key for the section.

    Valid keys:
    - handouts
    - guest_handouts
    - updates
    - guest_updates
    """
    info_module = get_course_info_section_module(request, user, course,
                                                 section_key)

    html = ''
    if info_module is not None:
        try:
            html = info_module.render(STUDENT_VIEW).content
            context = {
                'username': request.user.username,
                'user_id': request.user.id,
                'name': request.user.profile.name,
                'course_title': course.display_name,
                'course_id': course.id,
                'course_start_date': get_default_time_display(course.start),
                'course_end_date': get_default_time_display(course.end),
            }
            html = substitute_keywords_with_data(html, context)
        except Exception:  # pylint: disable=broad-except
            html = render_to_string('courseware/error-message.html', None)
            log.exception(u"Error rendering course_id=%s, section_key=%s",
                          unicode(course.id), section_key)

    return html
Exemple #7
0
 def get_html(self):
     if self.system.substitute_keywords_with_data:
         course = self.descriptor.runtime.modulestore.get_course(self.course_id)
         user = self.system.get_real_user(self.system.anonymous_student_id)
         context = {
             "user_id": user.id if user else None,
             "name": user.profile.name if user else "",
             "course_title": course.display_name,
             "course_id": self.course_id,
             "course_start_date": get_default_time_display(course.start),
             "course_end_date": get_default_time_display(course.end),
         }
         return self.system.substitute_keywords_with_data(self.data, context)
     return self.data
Exemple #8
0
def _get_asset_json_for_editorjs(display_name, content_type, date, location,
                                 thumbnail_location, locked):
    '''
    Helper method for formatting the asset information to send to client.
    '''
    asset_url = StaticContent.serialize_asset_key_with_slash(location)
    external_url = settings.LMS_BASE + asset_url
    return {
        "success": 1,
        "file": {
            'url':
            asset_url,
            'display_name':
            display_name,
            'content_type':
            content_type,
            'date_added':
            get_default_time_display(date),
            'asset_url':
            asset_url,
            'external_url':
            external_url,
            'portable_url':
            StaticContent.get_static_path_from_location(location),
            'thumbnail':
            StaticContent.serialize_asset_key_with_slash(thumbnail_location)
            if thumbnail_location else None,
            'locked':
            locked,
            'id':
            six.text_type(location)
        }
    }
    def test_start_date_on_page(self):
        """
        Verify that the course start date is included on the course outline page.
        """
        def _get_release_date(response):
            """Return the release date from the course page"""
            parsed_html = lxml.html.fromstring(response.content)
            return parsed_html.find_class('course-status')[0].find_class('status-release-value')[0].text_content()

        def _assert_settings_link_present(response):
            """
            Asserts there's a course settings link on the course page by the course release date.
            """
            parsed_html = lxml.html.fromstring(response.content)
            settings_link = parsed_html.find_class('course-status')[0].find_class('action-edit')[0].find('a')
            self.assertIsNotNone(settings_link)
            self.assertEqual(settings_link.get('href'), reverse_course_url('settings_handler', self.course.id))

        outline_url = reverse_course_url('course_handler', self.course.id)
        response = self.client.get(outline_url, {}, HTTP_ACCEPT='text/html')

        # A course with the default release date should display as "Unscheduled"
        self.assertEqual(_get_release_date(response), 'Unscheduled')
        _assert_settings_link_present(response)

        self.course.start = datetime.datetime(2014, 1, 1, tzinfo=pytz.utc)
        modulestore().update_item(self.course, ModuleStoreEnum.UserID.test)
        response = self.client.get(outline_url, {}, HTTP_ACCEPT='text/html')

        self.assertEqual(_get_release_date(response), get_default_time_display(self.course.start))
        _assert_settings_link_present(response)
Exemple #10
0
def _get_release_date(xblock):
    """
    Returns the release date for the xblock, or None if the release date has never been set.
    """
    # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
    return get_default_time_display(
        xblock.start) if xblock.start != DEFAULT_START_DATE else None
Exemple #11
0
    def test_start_date_on_page(self):
        """
        Verify that the course start date is included on the course outline page.
        """
        def _get_release_date(response):
            """Return the release date from the course page"""
            parsed_html = lxml.html.fromstring(response.content)
            return parsed_html.find_class('course-status')[0].find_class('status-release-value')[0].text_content()

        def _assert_settings_link_present(response):
            """
            Asserts there's a course settings link on the course page by the course release date.
            """
            parsed_html = lxml.html.fromstring(response.content)
            settings_link = parsed_html.find_class('course-status')[0].find_class('action-edit')[0].find('a')
            self.assertIsNotNone(settings_link)
            self.assertEqual(settings_link.get('href'), reverse_course_url('settings_handler', self.course.id))

        outline_url = reverse_course_url('course_handler', self.course.id)
        response = self.client.get(outline_url, {}, HTTP_ACCEPT='text/html')

        # A course with the default release date should display as "Unscheduled"
        self.assertEqual(_get_release_date(response), 'Unscheduled')
        _assert_settings_link_present(response)

        self.course.start = datetime.datetime(2014, 1, 1, tzinfo=pytz.utc)
        modulestore().update_item(self.course, ModuleStoreEnum.UserID.test)
        response = self.client.get(outline_url, {}, HTTP_ACCEPT='text/html')

        self.assertEqual(_get_release_date(response), get_default_time_display(self.course.start))
        _assert_settings_link_present(response)
Exemple #12
0
def _get_course_email_context(course):
    """
    Returns context arguments to apply to all emails, independent of recipient.
    """
    course_id = course.id.to_deprecated_string()
    course_title = course.display_name
    course_end_date = get_default_time_display(course.end)
    course_url = 'https://{}{}'.format(
        settings.SITE_NAME,
        reverse('course_root', kwargs={'course_id': course_id}))
    image_url = u'https://{}{}'.format(settings.SITE_NAME,
                                       course_image_url(course))
    email_context = {
        'course_title':
        course_title,
        'course_url':
        course_url,
        'course_image_url':
        image_url,
        'course_end_date':
        course_end_date,
        'account_settings_url':
        'https://{}{}'.format(settings.SITE_NAME, reverse('account_settings')),
        'email_settings_url':
        'https://{}{}'.format(settings.SITE_NAME, reverse('dashboard')),
        'platform_name':
        settings.PLATFORM_NAME,
    }
    return email_context
Exemple #13
0
def _get_asset_json(display_name, content_type, date, location,
                    thumbnail_location, locked):
    '''
    Helper method for formatting the asset information to send to client.
    '''

    # Colaraz Custom: overriding this to get site base web link of the asset(s)
    asset_url = StaticContent.serialize_asset_key_with_slash(location)
    external_url = 'https://' + configuration_helpers.get_value(
        'LMS_BASE') + asset_url

    return {
        'display_name':
        display_name,
        'content_type':
        content_type,
        'date_added':
        get_default_time_display(date),
        'url':
        asset_url,
        'external_url':
        external_url,
        'portable_url':
        StaticContent.get_static_path_from_location(location),
        'thumbnail':
        StaticContent.serialize_asset_key_with_slash(thumbnail_location)
        if thumbnail_location else None,
        'locked':
        locked,
        # needed for Backbone delete/update.
        'id':
        unicode(location)
    }
Exemple #14
0
def _get_asset_json(display_name, content_type, date, location,
                    thumbnail_location, locked):
    """
    Helper method for formatting the asset information to send to client.
    """
    asset_url = StaticContent.serialize_asset_key_with_slash(location)
    # eduNEXT 14.07.2016
    lms_base = microsite.get_value_for_org(location.org, 'SITE_NAME',
                                           settings.LMS_BASE)
    external_url = lms_base + asset_url
    return {
        'display_name':
        display_name,
        'content_type':
        content_type,
        'date_added':
        get_default_time_display(date),
        'url':
        asset_url,
        'external_url':
        external_url,
        'portable_url':
        StaticContent.get_static_path_from_location(location),
        'thumbnail':
        StaticContent.serialize_asset_key_with_slash(thumbnail_location)
        if thumbnail_location else None,
        'locked':
        locked,
        # Needed for Backbone delete/update.
        'id':
        unicode(location)
    }
Exemple #15
0
def _get_asset_json(display_name, content_type, date, location,
                    thumbnail_location, locked):
    '''
    Helper method for formatting the asset information to send to client.
    '''
    asset_url = StaticContent.serialize_asset_key_with_slash(location)
    external_url = settings.LMS_BASE + asset_url
    return {
        'display_name':
        display_name,
        'content_type':
        content_type,
        'date_added':
        get_default_time_display(date),
        'url':
        asset_url,
        'external_url':
        external_url,
        'portable_url':
        StaticContent.get_static_path_from_location(location),
        'thumbnail':
        StaticContent.serialize_asset_key_with_slash(thumbnail_location)
        if thumbnail_location else None,
        'locked':
        locked,
        # needed for Backbone delete/update.
        'id':
        unicode(location)
    }
Exemple #16
0
def _get_asset_json(display_name, date, location, thumbnail_location, locked):
    """
    Helper method for formatting the asset information to send to client.
    """
    asset_url = location.to_deprecated_string()
    external_url = settings.LMS_BASE + asset_url
    return {
        'display_name':
        display_name,
        'date_added':
        get_default_time_display(date),
        'url':
        asset_url,
        'external_url':
        external_url,
        'portable_url':
        StaticContent.get_static_path_from_location(location),
        'thumbnail':
        thumbnail_location.to_deprecated_string()
        if thumbnail_location is not None else None,
        'locked':
        locked,
        # Needed for Backbone delete/update.
        'id':
        unicode(location)
    }
Exemple #17
0
 def get_html(self):
     if self.system.substitute_keywords_with_data:
         course = self.descriptor.runtime.modulestore.get_course(
             self.course_id)
         user = self.system.get_real_user(self.system.anonymous_student_id)
         context = {
             'username': user.username if user else '',
             'user_id': user.id if user else None,
             'name': user.profile.name if user else '',
             'course_title': course.display_name,
             'course_id': self.course_id,
             'course_start_date': get_default_time_display(course.start),
             'course_end_date': get_default_time_display(course.end),
         }
         return self.system.substitute_keywords_with_data(
             self.data, context)
     return self.data
Exemple #18
0
def _section_course_info(course, access):
    """ Provide data for the corresponding dashboard section """
    course_key = course.id

    section_data = {
        'section_key': 'course_info',
        'section_display_name': _('Course Info'),
        'access': access,
        'course_id': course_key,
        'course_display_name': course.display_name,
        'has_started': course.has_started(),
        'has_ended': course.has_ended(),
        'start_date': get_default_time_display(course.start),
        'end_date': get_default_time_display(course.end) or _('No end date set'),
        'num_sections': len(course.children),
        'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': unicode(course_key)}),
    }

    if settings.FEATURES.get('DISPLAY_ANALYTICS_ENROLLMENTS'):
        section_data['enrollment_count'] = CourseEnrollment.objects.enrollment_counts(course_key)

    if settings.ANALYTICS_DASHBOARD_URL:
        #  dashboard_link is already made safe in _get_dashboard_link
        dashboard_link = _get_dashboard_link(course_key)
        #  so we can use Text() here so it's not double-escaped and rendering HTML on the front-end
        message = Text(_("Enrollment data is now available in {dashboard_link}.")).format(dashboard_link=dashboard_link)
        section_data['enrollment_message'] = message

    if settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD'):
        section_data['detailed_gitlogs_url'] = reverse('gitlogs_detail', kwargs={'course_id': unicode(course_key)})

    try:
        sorted_cutoffs = sorted(course.grade_cutoffs.items(), key=lambda i: i[1], reverse=True)
        advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo
        section_data['grade_cutoffs'] = reduce(advance, sorted_cutoffs, "")[:-2]
    except Exception:  # pylint: disable=broad-except
        section_data['grade_cutoffs'] = "Not Available"
    # section_data['offline_grades'] = offline_grades_available(course_key)

    try:
        section_data['course_errors'] = [(escape(a), '') for (a, _unused) in modulestore().get_course_errors(course.id)]
    except Exception:  # pylint: disable=broad-except
        section_data['course_errors'] = [('Error fetching errors', '')]

    return section_data
def _section_course_info(course, access):
    """ Provide data for the corresponding dashboard section """
    course_key = course.id

    section_data = {
        'section_key': 'course_info',
        'section_display_name': _('Course Info'),
        'access': access,
        'course_id': course_key,
        'course_display_name': course.display_name,
        'has_started': course.has_started(),
        'has_ended': course.has_ended(),
        'start_date': get_default_time_display(course.start),
        'end_date': get_default_time_display(course.end) or _('No end date set'),
        'num_sections': len(course.children),
        'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': unicode(course_key)}),
    }

    if settings.FEATURES.get('DISPLAY_ANALYTICS_ENROLLMENTS'):
        section_data['enrollment_count'] = CourseEnrollment.objects.enrollment_counts(course_key)

    if settings.ANALYTICS_DASHBOARD_URL:
        #  dashboard_link is already made safe in _get_dashboard_link
        dashboard_link = _get_dashboard_link(course_key)
        #  so we can use Text() here so it's not double-escaped and rendering HTML on the front-end
        message = Text(_("Enrollment data is now available in {dashboard_link}.")).format(dashboard_link=dashboard_link)
        section_data['enrollment_message'] = message

    if settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD'):
        section_data['detailed_gitlogs_url'] = reverse('gitlogs_detail', kwargs={'course_id': unicode(course_key)})

    try:
        sorted_cutoffs = sorted(course.grade_cutoffs.items(), key=lambda i: i[1], reverse=True)
        advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo
        section_data['grade_cutoffs'] = reduce(advance, sorted_cutoffs, "")[:-2]
    except Exception:  # pylint: disable=broad-except
        section_data['grade_cutoffs'] = "Not Available"
    # section_data['offline_grades'] = offline_grades_available(course_key)

    try:
        section_data['course_errors'] = [(escape(a), '') for (a, _unused) in modulestore().get_course_errors(course.id)]
    except Exception:  # pylint: disable=broad-except
        section_data['course_errors'] = [('Error fetching errors', '')]

    return section_data
Exemple #20
0
 def __init__(self, fake_email, num_sent):
     super(FakeEmailInfo, self).__init__()
     self.created = get_default_time_display(fake_email.created)
     self.number_sent = num_sent
     fake_email_dict = fake_email.to_dict()
     self.email = {
         feature: fake_email_dict[feature]
         for feature in self.EMAIL_FEATURES
     }
def extract_email_features(email_task):
    """
    From the given task, extract email content information

    Expects that the given task has the following attributes:
    * task_input (dict containing email_id and to_option)
    * task_output (optional, dict containing total emails sent)

    With this information, gets the corresponding email object from the
    bulk emails table, and loads up a dict containing the following:
    * created, the time the email was sent displayed in default time display
    * sent_to, the group the email was delivered to
    * email, dict containing the subject, id, and html_message of an email
    * number_sent, int number of emails sent
    If task_input cannot be loaded, then the email cannot be loaded
    and None is returned for these fields.
    """
    # Load the task input info to get email id
    try:
        task_input_information = json.loads(email_task.task_input)
    except ValueError:
        log.error("Could not parse task input as valid json; task input: %s",
                  email_task.task_input)
        return email_error_information()

    email = CourseEmail.objects.get(id=task_input_information['email_id'])

    creation_time = get_default_time_display(email.created)
    email_feature_dict = {
        'created': creation_time,
        'sent_to': task_input_information['to_option']
    }
    features = ['subject', 'html_message', 'id']
    email_info = {
        feature: unicode(getattr(email, feature))
        for feature in features
    }

    # Pass along email as an object with the information we desire
    email_feature_dict['email'] = email_info

    number_sent = None
    if hasattr(email_task,
               'task_output') and email_task.task_output is not None:
        try:
            task_output = json.loads(email_task.task_output)
        except ValueError:
            log.error(
                "Could not parse task output as valid json; task output: %s",
                email_task.task_output)
        else:
            if 'total' in task_output:
                number_sent = int(task_output['total'])
    email_feature_dict['number_sent'] = number_sent

    return email_feature_dict
def _section_course_info(course, access):
    """ Provide data for the corresponding dashboard section """
    course_key = course.id

    section_data = {
        "section_key": "course_info",
        "section_display_name": _("Course Info"),
        "access": access,
        "course_id": course_key,
        "course_display_name": course.display_name,
        "has_started": course.has_started(),
        "has_ended": course.has_ended(),
        "start_date": get_default_time_display(course.start),
        "end_date": get_default_time_display(course.end) or _("No end date set"),
        "num_sections": len(course.children),
        "list_instructor_tasks_url": reverse("list_instructor_tasks", kwargs={"course_id": unicode(course_key)}),
    }

    if settings.FEATURES.get("DISPLAY_ANALYTICS_ENROLLMENTS"):
        section_data["enrollment_count"] = CourseEnrollment.objects.enrollment_counts(course_key)

    if settings.ANALYTICS_DASHBOARD_URL:
        dashboard_link = _get_dashboard_link(course_key)
        message = _("Enrollment data is now available in {dashboard_link}.").format(dashboard_link=dashboard_link)
        section_data["enrollment_message"] = message

    if settings.FEATURES.get("ENABLE_SYSADMIN_DASHBOARD"):
        section_data["detailed_gitlogs_url"] = reverse("gitlogs_detail", kwargs={"course_id": unicode(course_key)})

    try:
        sorted_cutoffs = sorted(course.grade_cutoffs.items(), key=lambda i: i[1], reverse=True)
        advance = lambda memo, (letter, score): "{}: {}, ".format(letter, score) + memo
        section_data["grade_cutoffs"] = reduce(advance, sorted_cutoffs, "")[:-2]
    except Exception:  # pylint: disable=broad-except
        section_data["grade_cutoffs"] = "Not Available"
    # section_data['offline_grades'] = offline_grades_available(course_key)

    try:
        section_data["course_errors"] = [(escape(a), "") for (a, _unused) in modulestore().get_course_errors(course.id)]
    except Exception:  # pylint: disable=broad-except
        section_data["course_errors"] = [("Error fetching errors", "")]

    return section_data
 def get_plain_html_updates(self, request, course):
     """
     Returns any course updates in an html chunk. Used
     for older implementations and a few tests that store
     a single html object representing all the updates.
     """
     info_module = get_course_info_section_module(request, request.user, course, 'updates')
     info_block = getattr(info_module, '_xmodule', info_module)
     update_content = info_block.system.replace_urls(info_module.data) if info_module else ''
     keyword_context = {
         'username': request.user.username,
         'user_id': request.user.id,
         'name': request.user.profile.name,
         'course_title': course.display_name,
         'course_id': course.id,
         'course_start_date': get_default_time_display(course.start),
         'course_end_date': get_default_time_display(course.end),
     }
     update_content = info_block.system.substitute_keywords_with_data(update_content, keyword_context)
     return update_content
Exemple #24
0
def _get_release_date(xblock, user=None):
    """
    Returns the release date for the xblock, or None if the release date has never been set.
    """
    # If year of start date is less than 1900 then reset the start date to DEFAULT_START_DATE
    if xblock.start.year < 1900 and user:
        xblock.start = DEFAULT_START_DATE
        xblock = _update_with_callback(xblock, user)

    # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
    return get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None
Exemple #25
0
    def __init__(self, fake_email, num_sent, num_failed):
        super(FakeEmailInfo, self).__init__()
        self.created = get_default_time_display(fake_email.created)

        number_sent = str(num_sent) + ' sent'
        if num_failed > 0:
            number_sent += ', ' + str(num_failed) + " failed"

        self.number_sent = number_sent
        fake_email_dict = fake_email.to_dict()
        self.email = {feature: fake_email_dict[feature] for feature in self.EMAIL_FEATURES}
        self.requester = u'expected'
Exemple #26
0
def _get_course_email_context(course):
    """
    Returns context arguments to apply to all emails, independent of recipient.
    """
    course_id = course.id.to_deprecated_string()
    course_title = course.display_name
    course_url = 'https://{}{}'.format(
        settings.SITE_NAME,
        reverse('course_root', kwargs={'course_id': course_id})
    )
    image_url = 'https://{}{}'.format(settings.SITE_NAME, course_image_url(course))
    email_context = {
        'course_title': course_title,
        'course_url': course_url,
        'course_image_url': image_url,
        'course_start_date': get_default_time_display(course.start),
        'course_end_date': get_default_time_display(course.end),
        'account_settings_url': 'https://{}{}'.format(settings.SITE_NAME, reverse('dashboard')),
        'platform_name': settings.PLATFORM_NAME,
    }
    return email_context
    def test_sub_mutiltple_tags(self, test_info):
        """ Test that subbing works with multiple subtags """
        anon_id = '123456789'
        patched_dict = {
            '%%USER_ID%%': lambda x, y: anon_id,
            '%%USER_FULLNAME%%': lambda x, y: self.user.profile.name,
            '%%COURSE_DISPLAY_NAME': lambda x, y: self.course.display_name,
            '%%COURSE_END_DATE': lambda x, y: get_default_time_display(self.course.end)
        }

        with patch.dict(Ks.KEYWORD_FUNCTION_MAP, patched_dict):
            result = Ks.substitute_keywords_with_data(test_info['test_string'], self.user.id, self.course.id)
            self.assertEqual(result, test_info['expected'])
Exemple #28
0
 def studio_view(self, context=None):
     html = self.resource_string("static/html/studio.html")
     frag = Fragment()
     file_uploaded_date = get_default_time_display(
         self.file_uploaded_date) if self.file_uploaded_date else ''
     context = {'block': self, 'file_uploaded_date': file_uploaded_date}
     frag.add_content(MakoTemplate(text=html).render_unicode(**context))
     frag.add_css(self.resource_string("static/css/scormxblock.css"))
     frag.add_javascript(self.resource_string("static/js/src/studio.js"))
     frag.add_javascript_url(
         self.runtime.local_resource_url(self,
                                         'public/jquery.fileupload.js'))
     frag.initialize_js('ScormStudioXBlock')
     return frag
 def get_plain_html_updates(self, request, course):
     """
     Returns any course updates in an html chunk. Used
     for older implementations and a few tests that store
     a single html object representing all the updates.
     """
     info_module = get_course_info_section_module(request, request.user,
                                                  course, 'updates')
     info_block = getattr(info_module, '_xmodule', info_module)
     update_content = info_block.system.replace_urls(
         info_module.data) if info_module else ''
     keyword_context = {
         'username': request.user.username,
         'user_id': request.user.id,
         'name': request.user.profile.name,
         'course_title': course.display_name,
         'course_id': course.id,
         'course_start_date': get_default_time_display(course.start),
         'course_end_date': get_default_time_display(course.end),
     }
     update_content = info_block.system.substitute_keywords_with_data(
         update_content, keyword_context)
     return update_content
Exemple #30
0
def _show_receipt_json(order):
    """Render the receipt page as JSON.

    The included information is deliberately minimal:
    as much as possible, the included information should
    be common to *all* order items, so the client doesn't
    need to handle different item types differently.

    Arguments:
        request (HttpRequest): The request for the receipt.
        order (Order): The order model to display.

    Returns:
        HttpResponse

    """
    order_info = {
        'orderNum':
        order.id,
        'currency':
        order.currency,
        'status':
        order.status,
        'purchase_datetime':
        get_default_time_display(order.purchase_time)
        if order.purchase_time else None,
        'billed_to': {
            'first_name': order.bill_to_first,
            'last_name': order.bill_to_last,
            'street1': order.bill_to_street1,
            'street2': order.bill_to_street2,
            'city': order.bill_to_city,
            'state': order.bill_to_state,
            'postal_code': order.bill_to_postalcode,
            'country': order.bill_to_country,
        },
        'total_cost':
        order.total_cost,
        'items':
        [{
            'quantity': item.qty,
            'unit_cost': item.unit_cost,
            'line_cost': item.line_cost,
            'line_desc': item.line_desc,
            'course_key': six.text_type(item.course_id)
        }
         for item in OrderItem.objects.filter(order=order).select_subclasses()]
    }
    return JsonResponse(order_info)
    def setUp(self):
        super(KeywordSubTest, self).setUp()
        self.user = UserFactory.create(email="*****@*****.**",
                                       username="******",
                                       profile__name="Test User")
        self.course = CourseFactory.create(org='edx',
                                           course='999',
                                           display_name='test_course')

        self.context = {
            'user_id': self.user.id,
            'course_title': self.course.display_name,
            'name': self.user.profile.name,
            'course_end_date': get_default_time_display(self.course.end),
        }
Exemple #32
0
def _get_asset_json(display_name, date, location, thumbnail_location, locked):
    """
    Helper method for formatting the asset information to send to client.
    """
    asset_url = StaticContent.get_url_path_from_location(location)
    return {
        'display_name': display_name,
        'date_added': get_default_time_display(date),
        'url': asset_url,
        'portable_url': StaticContent.get_static_path_from_location(location),
        'thumbnail': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_location is not None else None,
        'locked': locked,
        # Needed for Backbone delete/update.
        'id': asset_url
    }
Exemple #33
0
    def __init__(self, fake_email, num_sent, num_failed):
        super(FakeEmailInfo, self).__init__()
        self.created = get_default_time_display(fake_email.created)

        number_sent = str(num_sent) + ' sent'
        if num_failed > 0:
            number_sent += ', ' + str(num_failed) + " failed"

        self.number_sent = number_sent
        fake_email_dict = fake_email.to_dict()
        self.email = {
            feature: fake_email_dict[feature]
            for feature in self.EMAIL_FEATURES
        }
        self.requester = u'expected'
Exemple #34
0
def _get_asset_json(display_name, content_type, date, location, thumbnail_location, locked,name_creater):
    """
    Helper method for formatting the asset information to send to client.
    """
    asset_url = StaticContent.serialize_asset_key_with_slash(location)
    external_url = 'localhost:8000' + asset_url
    return {
        'display_name': display_name,
        'content_type': content_type,
        'date_added': get_default_time_display(date),
        'url': asset_url,
        'external_url': external_url,
        'portable_url': StaticContent.get_static_path_from_location(location),
        'thumbnail': StaticContent.serialize_asset_key_with_slash(thumbnail_location) if thumbnail_location else None,
        'locked': locked,
        'name_creater':name_creater
    }
Exemple #35
0
def _get_asset_json(display_name, date, location, thumbnail_location, locked):
    """
    Helper method for formatting the asset information to send to client.
    """
    asset_url = _add_slash(location.to_deprecated_string())
    external_url = settings.LMS_BASE + asset_url
    return {
        'display_name': display_name,
        'date_added': get_default_time_display(date),
        'url': asset_url,
        'external_url': external_url,
        'portable_url': StaticContent.get_static_path_from_location(location),
        'thumbnail': _add_slash(unicode(thumbnail_location)) if thumbnail_location else None,
        'locked': locked,
        # Needed for Backbone delete/update.
        'id': unicode(location)
    }
def extract_email_features(email_task):
    """
    From the given task, extract email content information

    Expects that the given task has the following attributes:
    * task_input (dict containing email_id and to_option)
    * task_output (optional, dict containing total emails sent)

    With this information, gets the corresponding email object from the
    bulk emails table, and loads up a dict containing the following:
    * created, the time the email was sent displayed in default time display
    * sent_to, the group the email was delivered to
    * email, dict containing the subject, id, and html_message of an email
    * number_sent, int number of emails sent
    If task_input cannot be loaded, then the email cannot be loaded
    and None is returned for these fields.
    """
    # Load the task input info to get email id
    try:
        task_input_information = json.loads(email_task.task_input)
    except ValueError:
        log.error("Could not parse task input as valid json; task input: %s", email_task.task_input)
        return email_error_information()

    email = CourseEmail.objects.get(id=task_input_information['email_id'])

    creation_time = get_default_time_display(email.created)
    email_feature_dict = {'created': creation_time, 'sent_to': task_input_information['to_option']}
    features = ['subject', 'html_message', 'id']
    email_info = {feature: unicode(getattr(email, feature)) for feature in features}

    # Pass along email as an object with the information we desire
    email_feature_dict['email'] = email_info

    number_sent = None
    if hasattr(email_task, 'task_output') and email_task.task_output is not None:
        try:
            task_output = json.loads(email_task.task_output)
        except ValueError:
            log.error("Could not parse task output as valid json; task output: %s", email_task.task_output)
        else:
            if 'total' in task_output:
                number_sent = int(task_output['total'])
    email_feature_dict['number_sent'] = number_sent

    return email_feature_dict
Exemple #37
0
def _get_asset_json(display_name, content_type, date, location, thumbnail_location, locked):
    '''
    Helper method for formatting the asset information to send to client.
    '''
    asset_url = StaticContent.serialize_asset_key_with_slash(location)
    external_url = settings.LMS_BASE + asset_url
    return {
        'display_name': display_name,
        'content_type': content_type,
        'date_added': get_default_time_display(date),
        'url': asset_url,
        'external_url': external_url,
        'portable_url': StaticContent.get_static_path_from_location(location),
        'thumbnail': StaticContent.serialize_asset_key_with_slash(thumbnail_location) if thumbnail_location else None,
        'locked': locked,
        # needed for Backbone delete/update.
        'id': unicode(location)
    }
Exemple #38
0
def _show_receipt_json(order):
    """Render the receipt page as JSON.

    The included information is deliberately minimal:
    as much as possible, the included information should
    be common to *all* order items, so the client doesn't
    need to handle different item types differently.

    Arguments:
        request (HttpRequest): The request for the receipt.
        order (Order): The order model to display.

    Returns:
        HttpResponse

    """
    order_info = {
        "orderNum": order.id,
        "currency": order.currency,
        "status": order.status,
        "purchase_datetime": get_default_time_display(order.purchase_time) if order.purchase_time else None,
        "billed_to": {
            "first_name": order.bill_to_first,
            "last_name": order.bill_to_last,
            "street1": order.bill_to_street1,
            "street2": order.bill_to_street2,
            "city": order.bill_to_city,
            "state": order.bill_to_state,
            "postal_code": order.bill_to_postalcode,
            "country": order.bill_to_country,
        },
        "total_cost": order.total_cost,
        "items": [
            {
                "quantity": item.qty,
                "unit_cost": item.unit_cost,
                "line_cost": item.line_cost,
                "line_desc": item.line_desc,
                "course_key": unicode(item.course_id),
            }
            for item in OrderItem.objects.filter(order=order).select_subclasses()
        ],
    }
    return JsonResponse(order_info)
Exemple #39
0
def _show_receipt_json(order):
    """Render the receipt page as JSON.

    The included information is deliberately minimal:
    as much as possible, the included information should
    be common to *all* order items, so the client doesn't
    need to handle different item types differently.

    Arguments:
        request (HttpRequest): The request for the receipt.
        order (Order): The order model to display.

    Returns:
        HttpResponse

    """
    order_info = {
        'orderNum': order.id,
        'currency': order.currency,
        'status': order.status,
        'purchase_datetime': get_default_time_display(order.purchase_time) if order.purchase_time else None,
        'billed_to': {
            'first_name': order.bill_to_first,
            'last_name': order.bill_to_last,
            'street1': order.bill_to_street1,
            'street2': order.bill_to_street2,
            'city': order.bill_to_city,
            'state': order.bill_to_state,
            'postal_code': order.bill_to_postalcode,
            'country': order.bill_to_country,
        },
        'total_cost': order.total_cost,
        'items': [
            {
                'quantity': item.qty,
                'unit_cost': item.unit_cost,
                'line_cost': item.line_cost,
                'line_desc': item.line_desc,
                'course_key': unicode(item.course_id)
            }
            for item in OrderItem.objects.filter(order=order).select_subclasses()
        ]
    }
    return JsonResponse(order_info)
    def setUp(self):
        super(KeywordSubTest, self).setUp(create_user=False)
        self.user = UserFactory.create(
            email="*****@*****.**",
            username="******",
            profile__name="Test User"
        )
        self.course = CourseFactory.create(
            org='edx',
            course='999',
            display_name='test_course'
        )

        self.context = {
            'user_id': self.user.id,
            'course_title': self.course.display_name,
            'name': self.user.profile.name,
            'course_end_date': get_default_time_display(self.course.end),
        }
Exemple #41
0
def _get_release_date(xblock, user=None):
    """
    Returns the release date for the xblock, or None if the release date has never been set.
    """
    # If year of start date is less than 1900 then reset the start date to DEFAULT_START_DATE
    reset_to_default = False
    try:
        reset_to_default = xblock.start.year < 1900
    except ValueError:
        # For old mongo courses, accessing the start attribute calls `to_json()`,
        # which raises a `ValueError` for years < 1900.
        reset_to_default = True

    if reset_to_default and user:
        xblock.start = DEFAULT_START_DATE
        xblock = _update_with_callback(xblock, user)

    # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
    return get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None
Exemple #42
0
def _get_release_date(xblock, user=None):
    """
    Returns the release date for the xblock, or None if the release date has never been set.
    """
    # If year of start date is less than 1900 then reset the start date to DEFAULT_START_DATE
    reset_to_default = False
    try:
        reset_to_default = xblock.start.year < 1900
    except ValueError:
        # For old mongo courses, accessing the start attribute calls `to_json()`,
        # which raises a `ValueError` for years < 1900.
        reset_to_default = True

    if reset_to_default and user:
        xblock.start = DEFAULT_START_DATE
        xblock = _update_with_callback(xblock, user)

    # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
    return get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None
Exemple #43
0
def _get_course_email_context(course):
    """
    Returns context arguments to apply to all emails, independent of recipient.
    """
    course_id = course.id.to_deprecated_string()
    course_title = course.display_name
    course_end_date = get_default_time_display(course.end)
    scheme = u"https" if settings.HTTPS == "on" else u"http"
    course_url = '{}://{}{}'.format(
        scheme,
        settings.SITE_NAME,
        reverse('course_root', kwargs={'course_id': course_id})
    )
    image_url = u'{}://{}{}'.format(scheme, settings.SITE_NAME, course_image_url(course))
    email_context = {
        'course_title': course_title,
        'course_url': course_url,
        'course_image_url': image_url,
        'course_end_date': course_end_date,
        'account_settings_url': '{}://{}{}'.format(scheme, settings.SITE_NAME, reverse('account_settings')),
        'platform_name': microsite.get_value('platform_name', settings.PLATFORM_NAME),
    }
    return email_context
Exemple #44
0
def _get_asset_json(display_name, date, location, thumbnail_location, locked):
    """
    Helper method for formatting the asset information to send to client.
    """
    asset_url = StaticContent.get_url_path_from_location(location)
    return {
        'display_name':
        display_name,
        'date_added':
        get_default_time_display(date),
        'url':
        asset_url,
        'portable_url':
        StaticContent.get_static_path_from_location(location),
        'thumbnail':
        StaticContent.get_url_path_from_location(thumbnail_location)
        if thumbnail_location is not None else None,
        'locked':
        locked,
        # Needed for Backbone delete/update.
        'id':
        asset_url
    }
Exemple #45
0
 def default(self, obj):
     if isinstance(obj, datetime):
         return get_default_time_display(obj)
     return json.JSONEncoder.default(self, obj)
Exemple #46
0
def unit_handler(request, usage_key_string):
    """
    The restful handler for unit-specific requests.

    GET
        html: return html page for editing a unit
        json: not currently supported
    """
    if 'text/html' in request.META.get('HTTP_ACCEPT', 'text/html'):
        usage_key = UsageKey.from_string(usage_key_string)
        try:
            course, item, lms_link = _get_item_in_course(request, usage_key)
        except ItemNotFoundError:
            return HttpResponseBadRequest()

        component_templates = _get_component_templates(course)

        xblocks = item.get_children()

        # TODO (cpennington): If we share units between courses,
        # this will need to change to check permissions correctly so as
        # to pick the correct parent subsection
        containing_subsection = get_parent_xblock(item)
        containing_section = get_parent_xblock(containing_subsection)

        # cdodge hack. We're having trouble previewing drafts via jump_to redirect
        # so let's generate the link url here

        # need to figure out where this item is in the list of children as the
        # preview will need this
        index = 1
        for child in containing_subsection.get_children():
            if child.location == item.location:
                break
            index = index + 1

        preview_lms_base = settings.FEATURES.get('PREVIEW_LMS_BASE')

        preview_lms_link = (
            u'//{preview_lms_base}/courses/{org}/{course}/{course_name}/courseware/{section}/{subsection}/{index}'
        ).format(
            preview_lms_base=preview_lms_base,
            lms_base=settings.LMS_BASE,
            org=course.location.org,
            course=course.location.course,
            course_name=course.location.name,
            section=containing_section.location.name,
            subsection=containing_subsection.location.name,
            index=index
        )

        return render_to_response('unit.html', {
            'context_course': course,
            'unit': item,
            'unit_usage_key': usage_key,
            'child_usage_keys': [block.scope_ids.usage_id for block in xblocks],
            'component_templates': json.dumps(component_templates),
            'draft_preview_link': preview_lms_link,
            'published_preview_link': lms_link,
            'subsection': containing_subsection,
            'release_date': (
                get_default_time_display(containing_subsection.start)
                if containing_subsection.start is not None else None
            ),
            'section': containing_section,
            'new_unit_category': 'vertical',
            'unit_state': compute_publish_state(item),
            'published_date': (
                get_default_time_display(item.published_date)
                if item.published_date is not None else None
            ),
        })
    else:
        return HttpResponseBadRequest("Only supports html requests")
def unit_handler(request, tag=None, package_id=None, branch=None, version_guid=None, block=None):
    """
    The restful handler for unit-specific requests.

    GET
        html: return html page for editing a unit
        json: not currently supported
    """
    if "text/html" in request.META.get("HTTP_ACCEPT", "text/html"):
        locator = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
        try:
            old_location, course, item, lms_link = _get_item_in_course(request, locator)
        except ItemNotFoundError:
            return HttpResponseBadRequest()

        component_templates = defaultdict(list)
        for category in COMPONENT_TYPES:
            component_class = _load_mixed_class(category)
            # add the default template
            # TODO: Once mixins are defined per-application, rather than per-runtime,
            # this should use a cms mixed-in class. (cpennington)
            if hasattr(component_class, "display_name"):
                display_name = component_class.display_name.default or "Blank"
            else:
                display_name = "Blank"
            component_templates[category].append(
                (
                    display_name,
                    category,
                    False,  # No defaults have markdown (hardcoded current default)
                    None,  # no boilerplate for overrides
                )
            )
            # add boilerplates
            if hasattr(component_class, "templates"):
                for template in component_class.templates():
                    filter_templates = getattr(component_class, "filter_templates", None)
                    if not filter_templates or filter_templates(template, course):
                        component_templates[category].append(
                            (
                                template["metadata"].get("display_name"),
                                category,
                                template["metadata"].get("markdown") is not None,
                                template.get("template_id"),
                            )
                        )

        # Check if there are any advanced modules specified in the course policy.
        # These modules should be specified as a list of strings, where the strings
        # are the names of the modules in ADVANCED_COMPONENT_TYPES that should be
        # enabled for the course.
        course_advanced_keys = course.advanced_modules

        # Set component types according to course policy file
        if isinstance(course_advanced_keys, list):
            for category in course_advanced_keys:
                if category in ADVANCED_COMPONENT_TYPES:
                    # Do I need to allow for boilerplates or just defaults on the
                    # class? i.e., can an advanced have more than one entry in the
                    # menu? one for default and others for prefilled boilerplates?
                    try:
                        component_class = _load_mixed_class(category)

                        component_templates["advanced"].append(
                            (
                                component_class.display_name.default or category,
                                category,
                                False,
                                None,  # don't override default data
                            )
                        )
                    except PluginMissingError:
                        # dhm: I got this once but it can happen any time the
                        # course author configures an advanced component which does
                        # not exist on the server. This code here merely
                        # prevents any authors from trying to instantiate the
                        # non-existent component type by not showing it in the menu
                        pass
        else:
            log.error("Improper format for course advanced keys! %s", course_advanced_keys)

        xblocks = item.get_children()
        locators = [
            loc_mapper().translate_location(course.location.course_id, xblock.location, False, True)
            for xblock in xblocks
        ]

        # TODO (cpennington): If we share units between courses,
        # this will need to change to check permissions correctly so as
        # to pick the correct parent subsection
        containing_subsection = get_parent_xblock(item)
        containing_section = get_parent_xblock(containing_subsection)

        # cdodge hack. We're having trouble previewing drafts via jump_to redirect
        # so let's generate the link url here

        # need to figure out where this item is in the list of children as the
        # preview will need this
        index = 1
        for child in containing_subsection.get_children():
            if child.location == item.location:
                break
            index = index + 1

        preview_lms_base = settings.FEATURES.get("PREVIEW_LMS_BASE")

        preview_lms_link = (
            u"//{preview_lms_base}/courses/{org}/{course}/{course_name}/courseware/{section}/{subsection}/{index}"
        ).format(
            preview_lms_base=preview_lms_base,
            lms_base=settings.LMS_BASE,
            org=course.location.org,
            course=course.location.course,
            course_name=course.location.name,
            section=containing_section.location.name,
            subsection=containing_subsection.location.name,
            index=index,
        )

        return render_to_response(
            "unit.html",
            {
                "context_course": course,
                "unit": item,
                "unit_locator": locator,
                "locators": locators,
                "component_templates": component_templates,
                "draft_preview_link": preview_lms_link,
                "published_preview_link": lms_link,
                "subsection": containing_subsection,
                "release_date": (
                    get_default_time_display(containing_subsection.start)
                    if containing_subsection.start is not None
                    else None
                ),
                "section": containing_section,
                "new_unit_category": "vertical",
                "unit_state": compute_unit_state(item),
                "published_date": (
                    get_default_time_display(item.published_date) if item.published_date is not None else None
                ),
            },
        )
    else:
        return HttpResponseBadRequest("Only supports html requests")
Exemple #48
0
def create_xblock_info(xblock,
                       data=None,
                       metadata=None,
                       include_ancestor_info=False,
                       include_child_info=False,
                       course_outline=False,
                       include_children_predicate=NEVER,
                       parent_xblock=None,
                       graders=None):
    """
    Creates the information needed for client-side XBlockInfo.

    If data or metadata are not specified, their information will not be added
    (regardless of whether or not the xblock actually has data or metadata).

    There are three optional boolean parameters:
      include_ancestor_info - if true, ancestor info is added to the response
      include_child_info - if true, direct child info is included in the response
      course_outline - if true, the xblock is being rendered on behalf of the course outline.
        There are certain expensive computations that do not need to be included in this case.

    In addition, an optional include_children_predicate argument can be provided to define whether or
    not a particular xblock should have its children included.
    """
    def safe_get_username(user_id):
        """
        Guard against bad user_ids, like the infamous "**replace_user**".
        Note that this will ignore our special known IDs (ModuleStoreEnum.UserID).
        We should consider adding special handling for those values.

        :param user_id: the user id to get the username of
        :return: username, or None if the user does not exist or user_id is None
        """
        if user_id:
            try:
                return User.objects.get(id=user_id).username
            except:  # pylint: disable=bare-except
                pass

        return None

    is_xblock_unit = is_unit(xblock, parent_xblock)
    # this should not be calculated for Sections and Subsections on Unit page
    has_changes = modulestore().has_changes(xblock) if (
        is_xblock_unit or course_outline) else None

    if graders is None:
        graders = CourseGradingModel.fetch(xblock.location.course_key).graders

    # Compute the child info first so it can be included in aggregate information for the parent
    should_visit_children = include_child_info and (
        course_outline and not is_xblock_unit or not course_outline)
    if should_visit_children and xblock.has_children:
        child_info = _create_xblock_child_info(
            xblock,
            course_outline,
            graders,
            include_children_predicate=include_children_predicate,
        )
    else:
        child_info = None

    # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
    release_date = get_default_time_display(
        xblock.start) if xblock.start != DEFAULT_START_DATE else None
    if xblock.category != 'course':
        visibility_state = _compute_visibility_state(
            xblock, child_info, is_xblock_unit and has_changes)
    else:
        visibility_state = None
    published = modulestore().has_published_version(xblock)

    xblock_info = {
        "id":
        unicode(xblock.location),
        "display_name":
        xblock.display_name_with_default,
        "category":
        xblock.category,
        "edited_on":
        get_default_time_display(xblock.subtree_edited_on)
        if xblock.subtree_edited_on else None,
        "published":
        published,
        "published_on":
        get_default_time_display(xblock.published_on)
        if xblock.published_on else None,
        "studio_url":
        xblock_studio_url(xblock, parent_xblock),
        "released_to_students":
        datetime.now(UTC) > xblock.start,
        "release_date":
        release_date,
        "visibility_state":
        visibility_state,
        "lti_enabled":
        xblock.fields['lti_enabled'].is_set_on(xblock),
        "lti_url":
        "",
        "lti_key":
        "",
        "lti_secret":
        "",
        "has_explicit_staff_lock":
        xblock.fields['visible_to_staff_only'].is_set_on(xblock),
        "start":
        xblock.fields['start'].to_json(xblock.start),
        "graded":
        xblock.graded,
        "due_date":
        get_default_time_display(xblock.due),
        "due":
        xblock.fields['due'].to_json(xblock.due),
        "format":
        xblock.format,
        "course_graders":
        json.dumps([grader.get('type') for grader in graders]),
        "has_changes":
        has_changes,
    }

    if settings.FEATURES.get('ENABLE_AS_LTI_TOOL_PROVIDER', False):
        store = modulestore()
        course = store.get_course(xblock.location.course_key)
        if course.lti_enabled:
            course_id = xblock.location.course_key
            usr = int(xblock.subtree_edited_by)
            existing_components = LTIComponent.objects.filter(
                course_id=course_id, module_id=xblock.location, user_id=usr)
            if xblock.fields['lti_enabled'].is_set_on(xblock):
                if len(existing_components) == 0:
                    key = ''.join(
                        random.SystemRandom().choice(string.ascii_uppercase +
                                                     string.digits)
                        for _ in range(8))
                    secret = ''.join(
                        random.SystemRandom().choice(string.ascii_uppercase +
                                                     string.digits)
                        for _ in range(16))
                    lti_component = LTIComponent(user_id=usr,
                                                 course_id=course_id,
                                                 module_id=str(
                                                     xblock.location),
                                                 key=key,
                                                 secret=secret)
                    lti_component.save()
                else:
                    key = existing_components[0].key
                    secret = existing_components[0].secret
                xblock_info["lti_url"] = str(xblock.location)
                xblock_info["lti_key"] = key
                xblock_info["lti_secret"] = secret
            else:
                if len(existing_components) > 0:
                    for existing_component in existing_components:
                        existing_components.delete()
        else:
            xblock_info["lti_url"] = "disabled"
            xblock_info["lti_key"] = "disabled"
            xblock_info["lti_secret"] = "disabled"
    else:
        xblock_info["lti_url"] = "disabled"
        xblock_info["lti_key"] = "disabled"
        xblock_info["lti_secret"] = "disabled"

    if data is not None:
        xblock_info["data"] = data
    if metadata is not None:
        xblock_info["metadata"] = metadata
    if include_ancestor_info:
        xblock_info['ancestor_info'] = _create_xblock_ancestor_info(
            xblock, course_outline)
    if child_info:
        xblock_info['child_info'] = child_info
    if visibility_state == VisibilityState.staff_only:
        xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock(
            xblock, parent_xblock)
    else:
        xblock_info["ancestor_has_staff_lock"] = False

    # Currently, 'edited_by', 'published_by', and 'release_date_from' are only used by the
    # container page when rendering a unit. Since they are expensive to compute, only include them for units
    # that are not being rendered on the course outline.
    if is_xblock_unit and not course_outline:
        xblock_info["edited_by"] = safe_get_username(xblock.subtree_edited_by)
        xblock_info["published_by"] = safe_get_username(xblock.published_by)
        xblock_info[
            "currently_visible_to_students"] = is_currently_visible_to_students(
                xblock)
        if release_date:
            xblock_info["release_date_from"] = _get_release_date_from(xblock)
        if visibility_state == VisibilityState.staff_only:
            xblock_info["staff_lock_from"] = _get_staff_lock_from(xblock)
        else:
            xblock_info["staff_lock_from"] = None
    if course_outline:
        if xblock_info["has_explicit_staff_lock"]:
            xblock_info["staff_only_message"] = True
        elif child_info and child_info["children"]:
            xblock_info["staff_only_message"] = all([
                child["staff_only_message"] for child in child_info["children"]
            ])
        else:
            xblock_info["staff_only_message"] = False

    return xblock_info
def test_get_default_time_display_no_tzname():
    assert_equals("", get_default_time_display(None))
    test_time = datetime(1992, 3, 12, 15, 3, 30, tzinfo=NamelessTZ())
    assert_equals("Mar 12, 1992 at 15:03-0300",
                  get_default_time_display(test_time))
def test_get_dflt_time_disp_notz():
    test_time = datetime(1992, 3, 12, 15, 3, 30)
    assert_equals("Mar 12, 1992 at 15:03 UTC",
                  get_default_time_display(test_time))
Exemple #51
0
def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False,
                       course_outline=False, include_children_predicate=NEVER, parent_xblock=None, graders=None,
                       user=None):
    """
    Creates the information needed for client-side XBlockInfo.

    If data or metadata are not specified, their information will not be added
    (regardless of whether or not the xblock actually has data or metadata).

    There are three optional boolean parameters:
      include_ancestor_info - if true, ancestor info is added to the response
      include_child_info - if true, direct child info is included in the response
      course_outline - if true, the xblock is being rendered on behalf of the course outline.
        There are certain expensive computations that do not need to be included in this case.

    In addition, an optional include_children_predicate argument can be provided to define whether or
    not a particular xblock should have its children included.
    """
    is_library_block = isinstance(xblock.location, LibraryUsageLocator)
    is_xblock_unit = is_unit(xblock, parent_xblock)
    # this should not be calculated for Sections and Subsections on Unit page or for library blocks
    has_changes = None
    if (is_xblock_unit or course_outline) and not is_library_block:
        has_changes = modulestore().has_changes(xblock)

    if graders is None:
        if not is_library_block:
            graders = CourseGradingModel.fetch(xblock.location.course_key).graders
        else:
            graders = []

    # Filter the graders data as needed
    graders = _filter_entrance_exam_grader(graders)

    # Compute the child info first so it can be included in aggregate information for the parent
    should_visit_children = include_child_info and (course_outline and not is_xblock_unit or not course_outline)
    if should_visit_children and xblock.has_children:
        child_info = _create_xblock_child_info(
            xblock,
            course_outline,
            graders,
            include_children_predicate=include_children_predicate,
            user=user
        )
    else:
        child_info = None

    if xblock.category != 'course':
        visibility_state = _compute_visibility_state(xblock, child_info, is_xblock_unit and has_changes)
    else:
        visibility_state = None
    published = modulestore().has_published_version(xblock) if not is_library_block else None

    # defining the default value 'True' for delete, drag and add new child actions in xblock_actions for each xblock.
    xblock_actions = {'deletable': True, 'draggable': True, 'childAddable': True}
    explanatory_message = None
    # is_entrance_exam is inherited metadata.
    if xblock.category == 'chapter' and getattr(xblock, "is_entrance_exam", None):
        # Entrance exam section should not be deletable, draggable and not have 'New Subsection' button.
        xblock_actions['deletable'] = xblock_actions['childAddable'] = xblock_actions['draggable'] = False
        if parent_xblock is None:
            parent_xblock = get_parent_xblock(xblock)

        # Translators: The {pct_sign} here represents the percent sign, i.e., '%'
        # in many languages. This is used to avoid Transifex's misinterpreting of
        # '% o'. The percent sign is also translatable as a standalone string.
        explanatory_message = _('Students must score {score}{pct_sign} or higher to access course materials.').format(
            score=int(parent_xblock.entrance_exam_minimum_score_pct * 100),
            # Translators: This is the percent sign. It will be used to represent
            # a percent value out of 100, e.g. "58%" means "58/100".
            pct_sign=_('%'))

    xblock_info = {
        "id": unicode(xblock.location),
        "display_name": xblock.display_name_with_default,
        "category": xblock.category,
        "edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None,
        "published": published,
        "published_on": get_default_time_display(xblock.published_on) if published and xblock.published_on else None,
        "studio_url": xblock_studio_url(xblock, parent_xblock),
        "released_to_students": datetime.now(UTC) > xblock.start,
        "release_date": _get_release_date(xblock, user),
        "visibility_state": visibility_state,
        "has_explicit_staff_lock": xblock.fields['visible_to_staff_only'].is_set_on(xblock),
        "start": xblock.fields['start'].to_json(xblock.start),
        "graded": xblock.graded,
        "due_date": get_default_time_display(xblock.due),
        "due": xblock.fields['due'].to_json(xblock.due),
        "format": xblock.format,
        "course_graders": json.dumps([grader.get('type') for grader in graders]),
        "has_changes": has_changes,
        "actions": xblock_actions,
        "explanatory_message": explanatory_message
    }

    # Entrance exam subsection should be hidden. in_entrance_exam is inherited metadata, all children will have it.
    if xblock.category == 'sequential' and getattr(xblock, "in_entrance_exam", False):
        xblock_info["is_header_visible"] = False

    if data is not None:
        xblock_info["data"] = data
    if metadata is not None:
        xblock_info["metadata"] = metadata
    if include_ancestor_info:
        xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock, course_outline)
    if child_info:
        xblock_info['child_info'] = child_info
    if visibility_state == VisibilityState.staff_only:
        xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock(xblock, parent_xblock)
    else:
        xblock_info["ancestor_has_staff_lock"] = False

    if course_outline:
        if xblock_info["has_explicit_staff_lock"]:
            xblock_info["staff_only_message"] = True
        elif child_info and child_info["children"]:
            xblock_info["staff_only_message"] = all([child["staff_only_message"] for child in child_info["children"]])
        else:
            xblock_info["staff_only_message"] = False

    return xblock_info
Exemple #52
0
def _compose_message_reverification_email(
        course_key, user_id, related_assessment_location, status, request
):  # pylint: disable=invalid-name
    """
    Compose subject and message for photo reverification email.

    Args:
        course_key(CourseKey): CourseKey object
        user_id(str): User Id
        related_assessment_location(str): Location of reverification XBlock
        photo_verification(QuerySet): Queryset of SoftwareSecure objects
        status(str): Approval status
        is_secure(Bool): Is running on secure protocol or not

    Returns:
        None if any error occurred else Tuple of subject and message strings
    """
    try:
        usage_key = UsageKey.from_string(related_assessment_location)
        reverification_block = modulestore().get_item(usage_key)

        course = modulestore().get_course(course_key)
        redirect_url = get_redirect_url(course_key, usage_key.replace(course_key=course_key))

        subject = "Re-verification Status"
        context = {
            "status": status,
            "course_name": course.display_name_with_default,
            "assessment": reverification_block.related_assessment
        }

        # Allowed attempts is 1 if not set on verification block
        allowed_attempts = reverification_block.attempts + 1
        used_attempts = VerificationStatus.get_user_attempts(user_id, course_key, related_assessment_location)
        left_attempts = allowed_attempts - used_attempts
        is_attempt_allowed = left_attempts > 0
        verification_open = True
        if reverification_block.due:
            verification_open = timezone.now() <= reverification_block.due

        context["left_attempts"] = left_attempts
        context["is_attempt_allowed"] = is_attempt_allowed
        context["verification_open"] = verification_open
        context["due_date"] = get_default_time_display(reverification_block.due)

        context['platform_name'] = settings.PLATFORM_NAME
        context["used_attempts"] = used_attempts
        context["allowed_attempts"] = allowed_attempts
        context["support_link"] = microsite.get_value('email_from_address', settings.CONTACT_EMAIL)

        re_verification_link = reverse(
            'verify_student_incourse_reverify',
            args=(
                unicode(course_key),
                related_assessment_location
            )
        )

        context["course_link"] = request.build_absolute_uri(redirect_url)
        context["reverify_link"] = request.build_absolute_uri(re_verification_link)

        message = render_to_string('emails/reverification_processed.txt', context)
        log.info(
            "Sending email to User_Id=%s. Attempts left for this user are %s. "
            "Allowed attempts %s. "
            "Due Date %s",
            str(user_id), left_attempts, allowed_attempts, str(reverification_block.due)
        )
        return subject, message
    # Catch all exception to avoid raising back to view
    except:  # pylint: disable=bare-except
        log.exception("The email for re-verification sending failed for user_id %s", user_id)
Exemple #53
0
 def course_end_date_sub(user, course):
     """ Returns the course end date in the default display """
     return get_default_time_display(course.end)
Exemple #54
0
 def __init__(self, fake_email, num_sent):
     super(FakeEmailInfo, self).__init__()
     self.created = get_default_time_display(fake_email.created)
     self.number_sent = num_sent
     fake_email_dict = fake_email.to_dict()
     self.email = {feature: fake_email_dict[feature] for feature in self.EMAIL_FEATURES}
Exemple #55
0
def create_xblock_info(xblock,
                       data=None,
                       metadata=None,
                       include_ancestor_info=False,
                       include_child_info=False,
                       course_outline=False,
                       include_children_predicate=NEVER,
                       parent_xblock=None,
                       graders=None):
    """
    Creates the information needed for client-side XBlockInfo.

    If data or metadata are not specified, their information will not be added
    (regardless of whether or not the xblock actually has data or metadata).

    There are three optional boolean parameters:
      include_ancestor_info - if true, ancestor info is added to the response
      include_child_info - if true, direct child info is included in the response
      course_outline - if true, the xblock is being rendered on behalf of the course outline.
        There are certain expensive computations that do not need to be included in this case.

    In addition, an optional include_children_predicate argument can be provided to define whether or
    not a particular xblock should have its children included.
    """
    is_library_block = isinstance(xblock.location, LibraryUsageLocator)
    is_xblock_unit = is_unit(xblock, parent_xblock)
    # this should not be calculated for Sections and Subsections on Unit page or for library blocks
    has_changes = None
    if (is_xblock_unit or course_outline) and not is_library_block:
        has_changes = modulestore().has_changes(xblock)

    if graders is None:
        if not is_library_block:
            graders = CourseGradingModel.fetch(
                xblock.location.course_key).graders
        else:
            graders = []

    # Filter the graders data as needed
    graders = _filter_entrance_exam_grader(graders)

    # Compute the child info first so it can be included in aggregate information for the parent
    should_visit_children = include_child_info and (
        course_outline and not is_xblock_unit or not course_outline)
    if should_visit_children and xblock.has_children:
        child_info = _create_xblock_child_info(
            xblock,
            course_outline,
            graders,
            include_children_predicate=include_children_predicate,
        )
    else:
        child_info = None

    if xblock.category != 'course':
        visibility_state = _compute_visibility_state(
            xblock, child_info, is_xblock_unit and has_changes)
    else:
        visibility_state = None
    published = modulestore().has_published_version(
        xblock) if not is_library_block else None

    # defining the default value 'True' for delete, drag and add new child actions in xblock_actions for each xblock.
    xblock_actions = {
        'deletable': True,
        'draggable': True,
        'childAddable': True
    }
    explanatory_message = None
    # is_entrance_exam is inherited metadata.
    if xblock.category == 'chapter' and getattr(xblock, "is_entrance_exam",
                                                None):
        # Entrance exam section should not be deletable, draggable and not have 'New Subsection' button.
        xblock_actions['deletable'] = xblock_actions[
            'childAddable'] = xblock_actions['draggable'] = False
        if parent_xblock is None:
            parent_xblock = get_parent_xblock(xblock)

        explanatory_message = _(
            'Students must score {score}% or higher to access course materials.'
        ).format(score=int(parent_xblock.entrance_exam_minimum_score_pct *
                           100))

    xblock_info = {
        "id":
        unicode(xblock.location),
        "display_name":
        xblock.display_name_with_default,
        "category":
        xblock.category,
        "edited_on":
        get_default_time_display(xblock.subtree_edited_on)
        if xblock.subtree_edited_on else None,
        "published":
        published,
        "published_on":
        get_default_time_display(xblock.published_on)
        if published and xblock.published_on else None,
        "studio_url":
        xblock_studio_url(xblock, parent_xblock),
        "released_to_students":
        datetime.now(UTC) > xblock.start,
        "release_date":
        _get_release_date(xblock),
        "visibility_state":
        visibility_state,
        "has_explicit_staff_lock":
        xblock.fields['visible_to_staff_only'].is_set_on(xblock),
        "start":
        xblock.fields['start'].to_json(xblock.start),
        "graded":
        xblock.graded,
        "due_date":
        get_default_time_display(xblock.due),
        "due":
        xblock.fields['due'].to_json(xblock.due),
        "format":
        xblock.format,
        "course_graders":
        json.dumps([grader.get('type') for grader in graders]),
        "has_changes":
        has_changes,
        "actions":
        xblock_actions,
        "explanatory_message":
        explanatory_message
    }

    # Entrance exam subsection should be hidden. in_entrance_exam is inherited metadata, all children will have it.
    if xblock.category == 'sequential' and getattr(xblock, "in_entrance_exam",
                                                   False):
        xblock_info["is_header_visible"] = False

    if data is not None:
        xblock_info["data"] = data
    if metadata is not None:
        xblock_info["metadata"] = metadata
    if include_ancestor_info:
        xblock_info['ancestor_info'] = _create_xblock_ancestor_info(
            xblock, course_outline)
    if child_info:
        xblock_info['child_info'] = child_info
    if visibility_state == VisibilityState.staff_only:
        xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock(
            xblock, parent_xblock)
    else:
        xblock_info["ancestor_has_staff_lock"] = False

    if course_outline:
        if xblock_info["has_explicit_staff_lock"]:
            xblock_info["staff_only_message"] = True
        elif child_info and child_info["children"]:
            xblock_info["staff_only_message"] = all([
                child["staff_only_message"] for child in child_info["children"]
            ])
        else:
            xblock_info["staff_only_message"] = False

    return xblock_info
Exemple #56
0
 def default(self, obj):
     if isinstance(obj, datetime):
         return get_default_time_display(obj)
     return json.JSONEncoder.default(self, obj)
def extract_email_features(email_task):
    """
    From the given task, extract email content information

    Expects that the given task has the following attributes:
    * task_input (dict containing email_id)
    * task_output (optional, dict containing total emails sent)
    * requester, the user who executed the task

    With this information, gets the corresponding email object from the
    bulk emails table, and loads up a dict containing the following:
    * created, the time the email was sent displayed in default time display
    * sent_to, the group the email was delivered to
    * email, dict containing the subject, id, and html_message of an email
    * number_sent, int number of emails sent
    * requester, the user who sent the emails
    If task_input cannot be loaded, then the email cannot be loaded
    and None is returned for these fields.
    """
    # Load the task input info to get email id
    try:
        task_input_information = json.loads(email_task.task_input)
    except ValueError:
        log.error("Could not parse task input as valid json; task input: %s",
                  email_task.task_input)
        return email_error_information()

    email = CourseEmail.objects.get(id=task_input_information['email_id'])
    email_feature_dict = {
        'created': get_default_time_display(email.created),
        'sent_to': [target.long_display() for target in email.targets.all()],
        'requester': str(email_task.requester),
    }
    features = ['subject', 'html_message', 'id']
    email_info = {
        feature: unicode(getattr(email, feature))
        for feature in features
    }

    # Pass along email as an object with the information we desire
    email_feature_dict['email'] = email_info

    # Translators: number sent refers to the number of emails sent
    number_sent = _('0 sent')
    if hasattr(email_task,
               'task_output') and email_task.task_output is not None:
        try:
            task_output = json.loads(email_task.task_output)
        except ValueError:
            log.error(
                "Could not parse task output as valid json; task output: %s",
                email_task.task_output)
        else:
            if 'succeeded' in task_output and task_output['succeeded'] > 0:
                num_emails = task_output['succeeded']
                number_sent = ungettext(
                    "{num_emails} sent", "{num_emails} sent",
                    num_emails).format(num_emails=num_emails)

            if 'failed' in task_output and task_output['failed'] > 0:
                num_emails = task_output['failed']
                number_sent += ", "
                number_sent += ungettext(
                    "{num_emails} failed", "{num_emails} failed",
                    num_emails).format(num_emails=num_emails)

    email_feature_dict['number_sent'] = number_sent
    return email_feature_dict
Exemple #58
0
def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=False, include_child_info=False,
                       course_outline=False, include_children_predicate=NEVER, parent_xblock=None, graders=None):
    """
    Creates the information needed for client-side XBlockInfo.

    If data or metadata are not specified, their information will not be added
    (regardless of whether or not the xblock actually has data or metadata).

    There are three optional boolean parameters:
      include_ancestor_info - if true, ancestor info is added to the response
      include_child_info - if true, direct child info is included in the response
      course_outline - if true, the xblock is being rendered on behalf of the course outline.
        There are certain expensive computations that do not need to be included in this case.

    In addition, an optional include_children_predicate argument can be provided to define whether or
    not a particular xblock should have its children included.
    """

    def safe_get_username(user_id):
        """
        Guard against bad user_ids, like the infamous "**replace_user**".
        Note that this will ignore our special known IDs (ModuleStoreEnum.UserID).
        We should consider adding special handling for those values.

        :param user_id: the user id to get the username of
        :return: username, or None if the user does not exist or user_id is None
        """
        if user_id:
            try:
                return User.objects.get(id=user_id).username
            except:  # pylint: disable=bare-except
                pass

        return None

    is_xblock_unit = is_unit(xblock, parent_xblock)
    has_changes = modulestore().has_changes(xblock)

    if graders is None:
        graders = CourseGradingModel.fetch(xblock.location.course_key).graders

    # Compute the child info first so it can be included in aggregate information for the parent
    should_visit_children = include_child_info and (course_outline and not is_xblock_unit or not course_outline)
    if should_visit_children and xblock.has_children:
        child_info = _create_xblock_child_info(
            xblock,
            course_outline,
            graders,
            include_children_predicate=include_children_predicate,
        )
    else:
        child_info = None

    # Treat DEFAULT_START_DATE as a magic number that means the release date has not been set
    release_date = get_default_time_display(xblock.start) if xblock.start != DEFAULT_START_DATE else None
    if xblock.category != 'course':
        visibility_state = _compute_visibility_state(xblock, child_info, is_xblock_unit and has_changes)
    else:
        visibility_state = None
    published = modulestore().has_published_version(xblock)

    xblock_info = {
        "id": unicode(xblock.location),
        "display_name": xblock.display_name_with_default,
        "category": xblock.category,
        "edited_on": get_default_time_display(xblock.subtree_edited_on) if xblock.subtree_edited_on else None,
        "published": published,
        "published_on": get_default_time_display(xblock.published_date) if xblock.published_date else None,
        "studio_url": xblock_studio_url(xblock, parent_xblock),
        "released_to_students": datetime.now(UTC) > xblock.start,
        "release_date": release_date,
        "visibility_state": visibility_state,
        "has_explicit_staff_lock": xblock.fields['visible_to_staff_only'].is_set_on(xblock),
        "start": xblock.fields['start'].to_json(xblock.start),
        "graded": xblock.graded,
        "due_date": get_default_time_display(xblock.due),
        "due": xblock.fields['due'].to_json(xblock.due),
        "format": xblock.format,
        "course_graders": json.dumps([grader.get('type') for grader in graders]),
        "has_changes": has_changes,
    }
    if data is not None:
        xblock_info["data"] = data
    if metadata is not None:
        xblock_info["metadata"] = metadata
    if include_ancestor_info:
        xblock_info['ancestor_info'] = _create_xblock_ancestor_info(xblock, course_outline)
    if child_info:
        xblock_info['child_info'] = child_info
    if visibility_state == VisibilityState.staff_only:
        xblock_info["ancestor_has_staff_lock"] = ancestor_has_staff_lock(xblock, parent_xblock)
    else:
        xblock_info["ancestor_has_staff_lock"] = False

    # Currently, 'edited_by', 'published_by', and 'release_date_from' are only used by the
    # container page when rendering a unit. Since they are expensive to compute, only include them for units
    # that are not being rendered on the course outline.
    if is_xblock_unit and not course_outline:
        xblock_info["edited_by"] = safe_get_username(xblock.subtree_edited_by)
        xblock_info["published_by"] = safe_get_username(xblock.published_by)
        xblock_info["currently_visible_to_students"] = is_currently_visible_to_students(xblock)
        if release_date:
            xblock_info["release_date_from"] = _get_release_date_from(xblock)
        if visibility_state == VisibilityState.staff_only:
            xblock_info["staff_lock_from"] = _get_staff_lock_from(xblock)
        else:
            xblock_info["staff_lock_from"] = None
    if course_outline:
        if xblock_info["has_explicit_staff_lock"]:
            xblock_info["staff_only_message"] = True
        elif child_info and child_info["children"]:
            xblock_info["staff_only_message"] = all([child["staff_only_message"] for child in child_info["children"]])
        else:
            xblock_info["staff_only_message"] = False

    return xblock_info
Exemple #59
0
    def get(
        self, request, course_id,
        always_show_payment=False,
        current_step=None,
        message=FIRST_TIME_VERIFY_MSG
    ):
        """Render the pay/verify requirements page.

        Arguments:
            request (HttpRequest): The request object.
            course_id (unicode): The ID of the course the user is trying
                to enroll in.

        Keyword Arguments:
            always_show_payment (bool): If True, show the payment steps
                even if the user has already paid.  This is useful
                for users returning to the flow after paying.
            current_step (string): The current step in the flow.
            message (string): The messaging to display.

        Returns:
            HttpResponse

        Raises:
            Http404: The course does not exist or does not
                have a verified mode.

        """
        # Parse the course key
        # The URL regex should guarantee that the key format is valid.
        course_key = CourseKey.from_string(course_id)
        course = modulestore().get_course(course_key)

        # Verify that the course exists and has a verified mode
        if course is None:
            log.warn(u"No course specified for verification flow request.")
            raise Http404

        # Check whether the user has access to this course
        # based on country access rules.
        redirect_url = embargo_api.redirect_if_blocked(
            course_key,
            user=request.user,
            ip_address=get_ip(request),
            url=request.path
        )
        if redirect_url:
            return redirect(redirect_url)

        expired_verified_course_mode, unexpired_paid_course_mode = self._get_expired_verified_and_paid_mode(course_key)

        # Check that the course has an unexpired paid mode
        if unexpired_paid_course_mode is not None:
            if CourseMode.is_verified_mode(unexpired_paid_course_mode):
                log.info(
                    u"Entering verified workflow for user '%s', course '%s', with current step '%s'.",
                    request.user.id, course_id, current_step
                )
        elif expired_verified_course_mode is not None:
            # Check if there is an *expired* verified course mode;
            # if so, we should show a message explaining that the verification
            # deadline has passed.
            log.info(u"Verification deadline for '%s' has passed.", course_id)
            context = {
                'course': course,
                'deadline': (
                    get_default_time_display(expired_verified_course_mode.expiration_datetime)
                    if expired_verified_course_mode.expiration_datetime else ""
                )
            }
            return render_to_response("verify_student/missed_verification_deadline.html", context)
        else:
            # Otherwise, there has never been a verified/paid mode,
            # so return a page not found response.
            log.warn(
                u"No paid/verified course mode found for course '%s' for verification/payment flow request",
                course_id
            )
            raise Http404

        # Check whether the user has verified, paid, and enrolled.
        # A user is considered "paid" if he or she has an enrollment
        # with a paid course mode (such as "verified").
        # For this reason, every paid user is enrolled, but not
        # every enrolled user is paid.
        # If the course mode is not verified(i.e only paid) then already_verified is always True
        already_verified = self._check_already_verified(request.user) \
            if CourseMode.is_verified_mode(unexpired_paid_course_mode) else True
        already_paid, is_enrolled = self._check_enrollment(request.user, course_key)

        # Redirect the user to a more appropriate page if the
        # messaging won't make sense based on the user's
        # enrollment / payment / verification status.
        redirect_response = self._redirect_if_necessary(
            message,
            already_verified,
            already_paid,
            is_enrolled,
            course_key
        )
        if redirect_response is not None:
            return redirect_response

        display_steps = self._display_steps(
            always_show_payment,
            already_verified,
            already_paid,
            unexpired_paid_course_mode
        )
        requirements = self._requirements(display_steps, request.user.is_active)

        if current_step is None:
            current_step = display_steps[0]['name']

        # Allow the caller to skip the first page
        # This is useful if we want the user to be able to
        # use the "back" button to return to the previous step.
        # This parameter should only work for known skip-able steps
        if request.GET.get('skip-first-step') and current_step in self.SKIP_STEPS:
            display_step_names = [step['name'] for step in display_steps]
            current_step_idx = display_step_names.index(current_step)
            if (current_step_idx + 1) < len(display_steps):
                current_step = display_steps[current_step_idx + 1]['name']

        courseware_url = ""
        if not course.start or course.start < datetime.datetime.today().replace(tzinfo=UTC):
            courseware_url = reverse(
                'course_root',
                kwargs={'course_id': unicode(course_key)}
            )

        full_name = (
            request.user.profile.name
            if request.user.profile.name
            else ""
        )

        # If the user set a contribution amount on another page,
        # use that amount to pre-fill the price selection form.
        contribution_amount = request.session.get(
            'donation_for_course', {}
        ).get(unicode(course_key), '')

        # Remember whether the user is upgrading
        # so we can fire an analytics event upon payment.
        request.session['attempting_upgrade'] = (message == self.UPGRADE_MSG)

        # Determine the photo verification status
        verification_good_until = self._verification_valid_until(request.user)

        # Render the top-level page
        context = {
            'contribution_amount': contribution_amount,
            'course': course,
            'course_key': unicode(course_key),
            'course_mode': unexpired_paid_course_mode,
            'courseware_url': courseware_url,
            'current_step': current_step,
            'disable_courseware_js': True,
            'display_steps': display_steps,
            'is_active': json.dumps(request.user.is_active),
            'message_key': message,
            'platform_name': settings.PLATFORM_NAME,
            'purchase_endpoint': get_purchase_endpoint(),
            'requirements': requirements,
            'user_full_name': full_name,
            'verification_deadline': (
                get_default_time_display(unexpired_paid_course_mode.expiration_datetime)
                if unexpired_paid_course_mode.expiration_datetime else ""
            ),
            'already_verified': already_verified,
            'verification_good_until': verification_good_until,
        }
        return render_to_response("verify_student/pay_and_verify.html", context)