Ejemplo n.º 1
0
def compose_and_send_activation_email(user, profile, user_registration=None):
    """
    Construct all the required params and send the activation email
    through celery task

    Arguments:
        user: current logged-in user
        profile: profile object of the current logged-in user
        user_registration: registration of the current logged-in user
    """
    dest_addr = user.email
    if user_registration is None:
        user_registration = Registration.objects.get(user=user)
    context = generate_activation_email_context(user, user_registration)
    subject = render_to_string('emails/activation_email_subject.txt', context)
    # Email subject *must not* contain newlines
    subject = ''.join(subject.splitlines())
    message_for_activation = render_to_string('emails/activation_email.txt', context)
    from_address = configuration_helpers.get_value('email_from_address', settings.DEFAULT_FROM_EMAIL)
    from_address = configuration_helpers.get_value('ACTIVATION_EMAIL_FROM_ADDRESS', from_address)
    if settings.FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
        dest_addr = settings.FEATURES['REROUTE_ACTIVATION_EMAIL']
        message_for_activation = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) +
                                  '-' * 80 + '\n\n' + message_for_activation)
    send_activation_email.delay(subject, message_for_activation, from_address, dest_addr)
Ejemplo n.º 2
0
 def __init__(self, org, course_id, run):
     # still need these for now b/c the client's screen shows these 3 fields
     self.org = org
     self.course_id = course_id  # This actually holds the course number.
     self.run = run
     self.start_date = None  # 'start'
     self.end_date = None  # 'end'
     self.enrollment_start = None
     self.enrollment_end = None
     self.syllabus = None  # a pdf file asset
     self.short_description = ""
     self.overview = ""  # html to render as the overview
     self.about_sidebar_html = ''
     self.pre_enrollment_email = render_to_string('emails/default_pre_enrollment_message.txt', {})
     self.post_enrollment_email = render_to_string('emails/default_post_enrollment_message.txt', {})
     self.pre_enrollment_email_subject = "Thanks for Enrolling in {}".format(self.course_id)
     self.post_enrollment_email_subject = "Thanks for Enrolling in {}".format(self.course_id)
     self.intro_video = None  # a video pointer
     self.effort = None  # int hours/week
     self.course_image_name = ""
     self.course_image_asset_path = ""  # URL of the course image
     self.enable_enrollment_email = False
     self.pre_requisite_courses = []  # pre-requisite courses
     self.entrance_exam_enabled = ""  # is entrance exam enabled
     self.entrance_exam_id = ""  # the content location for the entrance exam
     self.entrance_exam_minimum_score_pct = settings.FEATURES.get(
         'ENTRANCE_EXAM_MIN_SCORE_PCT',
         '50'
     )  # minimum passing score for entrance exam content module/tree
    def test_account_activation_message(self):
        """
        Verify that account correct activation message is displayed.

        If logged in user has not activated his/her account, make sure that an
        account activation message is displayed on dashboard sidebar.
        """
        # Log in with test user.
        self.login()
        expected_message = render_to_string(
            'registration/account_activation_sidebar_notice.html',
            {
                'email': self.user.email,
                'platform_name': self.platform_name,
                'activation_email_support_link': self.activation_email_support_link
            }
        )

        response = self.client.get(reverse('dashboard'))
        self.assertContains(response, expected_message, html=True)

        # Now make sure account activation message goes away when user activated the account
        self.user.is_active = True
        self.user.save()
        self.login()
        expected_message = render_to_string(
            'registration/account_activation_sidebar_notice.html',
            {
                'email': self.user.email,
                'platform_name': self.platform_name,
                'activation_email_support_link': self.activation_email_support_link
            }
        )
        response = self.client.get(reverse('dashboard'))
        self.assertNotContains(response, expected_message, html=True)
Ejemplo n.º 4
0
def send_admin_notification_callback(sender, **kwargs):
    """
    Callback for notifying admin of a user in the 'pending' state.
    """
    user = kwargs['user']

    studio_request_email = settings.FEATURES.get('STUDIO_REQUEST_EMAIL', '')
    studio_request_email_to = settings.FEATURES.get('STUDIO_REQUEST_EMAIL_TO', [])
    studio_domain = settings.CMS_BASE
    context = {'user_name': user.username,
               'user_email': user.email,
               'studio_domain': studio_domain
               }

    subject = render_to_string('emails/course_creator_admin_subject.txt', context)
    subject = ''.join(subject.splitlines())
    message = render_to_string('emails/course_creator_admin_user_pending.txt', context)

    try:
        send_mail(
            subject,
            message,
            studio_request_email,
            studio_request_email_to,
            fail_silently=False
        )
    except SMTPException:
        log.warning("Failure sending 'pending state' e-mail for %s to %s", user.email, studio_request_email)
    def test_account_activation_message_disabled(self):
        """
        Verify that old account activation message is displayed when
        DISPLAY_ACCOUNT_ACTIVATION_MESSAGE_ON_SIDEBAR is disabled.
        """
        # Log in with test user.
        self.login()
        expected_message = render_to_string(
            'registration/activate_account_notice.html',
            {'email': self.user.email}
        )

        response = self.client.get(reverse('dashboard'))
        self.assertContains(response, expected_message, html=True)

        # Now make sure account activation message goes away when user activated the account
        self.user.is_active = True
        self.user.save()
        self.login()
        expected_message = render_to_string(
            'registration/activate_account_notice.html',
            {'email': self.user.email}
        )
        response = self.client.get(reverse('dashboard'))
        self.assertNotContains(response, expected_message, html=True)
Ejemplo n.º 6
0
def send_mail_to_student(student, param_dict):
    """
    Construct the email using templates and then send it.
    `student` is the student's email address (a `str`),

    `param_dict` is a `dict` with keys [
    `site_name`: name given to edX instance (a `str`)
    `registration_url`: url for registration (a `str`)
    `course_key`: id of course (a CourseKey)
    `auto_enroll`: user input option (a `str`)
    `course_url`: url of course (a `str`)
    `email_address`: email of student (a `str`)
    `full_name`: student full name (a `str`)
    `message`: type of email to send and template to use (a `str`)
    `is_shib_course`: (a `boolean`)
                                        ]
    Returns a boolean indicating whether the email was sent successfully.
    """

    # add some helpers and microconfig subsitutions
    if 'course' in param_dict:
        param_dict['course_name'] = param_dict['course'].display_name_with_default
    param_dict['site_name'] = microsite.get_value(
        'SITE_NAME',
        param_dict.get('site_name', '')
    )

    subject = None
    message = None

    message_type = param_dict['message']

    email_template_dict = {
        'allowed_enroll': ('emails/enroll_email_allowedsubject.txt', 'emails/enroll_email_allowedmessage.txt'),
        'enrolled_enroll': ('emails/enroll_email_enrolledsubject.txt', 'emails/enroll_email_enrolledmessage.txt'),
        'allowed_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_allowedmessage.txt'),
        'enrolled_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_enrolledmessage.txt'),
    }

    subject_template, message_template = email_template_dict.get(message_type, (None, None))
    if subject_template is not None and message_template is not None:
        subject = render_to_string(subject_template, param_dict)
        message = render_to_string(message_template, param_dict)

    if subject and message:
        # Remove leading and trailing whitespace from body
        message = message.strip()

        # Email subject *must not* contain newlines
        subject = ''.join(subject.splitlines())
        from_address = microsite.get_value(
            'email_from_address',
            settings.DEFAULT_FROM_EMAIL
        )

        send_mail(subject, message, from_address, [student], fail_silently=False)

        return True
    else:
        return False
Ejemplo n.º 7
0
def send_user_notification_callback(sender, **kwargs):
    """
    Callback for notifying user about course creator status change.
    """
    user = kwargs['user']
    updated_state = kwargs['state']

    studio_request_email = settings.FEATURES.get('STUDIO_REQUEST_EMAIL', '')
    studio_domain = settings.CMS_BASE
    context = {'studio_request_email': studio_request_email,
               'studio_domain': studio_domain
               }

    subject = render_to_string('emails/course_creator_subject.txt', context)
    subject = ''.join(subject.splitlines())
    if updated_state == CourseCreator.GRANTED:
        message_template = 'emails/course_creator_granted.txt'
    elif updated_state == CourseCreator.DENIED:
        message_template = 'emails/course_creator_denied.txt'
    else:
        # changed to unrequested or pending
        message_template = 'emails/course_creator_revoked.txt'
    message = render_to_string(message_template, context)

    try:
        user.email_user(subject, message, studio_request_email)
    except:
        log.warning("Unable to send course creator status e-mail to %s", user.email)
Ejemplo n.º 8
0
def get_subject_and_message(subject_template, message_template, param_dict):
    """
    Return the rendered subject and message with the appropriate parameters.
    """
    subject = render_to_string(subject_template, param_dict)
    message = render_to_string(message_template, param_dict)
    return subject, message
Ejemplo n.º 9
0
def send_mail_to_student(student, param_dict):
    """
    Check parameters, set text template and send email to student
    """
    if "course" in param_dict:
        param_dict["course_name"] = param_dict["course"].display_name

    param_dict["site_name"] = microsite.get_value("SITE_NAME", param_dict["site_name"])

    subject = None
    message = None

    message_type = param_dict["message"]

    email_template_dict = {
        "allowed_enroll": ("ccx/enroll_email_allowedsubject.txt", "ccx/enroll_email_allowedmessage.txt"),
        "enrolled_enroll": ("ccx/enroll_email_enrolledsubject.txt", "ccx/enroll_email_enrolledmessage.txt"),
        "allowed_unenroll": ("ccx/unenroll_email_subject.txt", "ccx/unenroll_email_allowedmessage.txt"),
        "enrolled_unenroll": ("ccx/unenroll_email_subject.txt", "ccx/unenroll_email_enrolledmessage.txt"),
    }

    subject_template, message_template = email_template_dict.get(message_type, (None, None))
    if subject_template is not None and message_template is not None:
        subject = render_to_string(subject_template, param_dict)
        message = render_to_string(message_template, param_dict)

    if subject and message:
        message = message.strip()

        subject = "".join(subject.splitlines())
        from_address = microsite.get_value("email_from_address", settings.DEFAULT_FROM_EMAIL)

        send_mail(subject, message, from_address, [student], fail_silently=False)
Ejemplo n.º 10
0
def send_mail_to_student(student, param_dict):
    """
    Check parameters, set text template and send email to student
    """
    if 'course' in param_dict:
        param_dict['course_name'] = param_dict['course'].display_name

    param_dict['site_name'] = microsite.get_value(
        'SITE_NAME',
        param_dict['site_name']
    )

    subject = None
    message = None

    message_type = param_dict['message']

    email_template_dict = {
        'allowed_enroll': (
            'ccx/enroll_email_allowedsubject.txt',
            'ccx/enroll_email_allowedmessage.txt'
        ),
        'enrolled_enroll': (
            'ccx/enroll_email_enrolledsubject.txt',
            'ccx/enroll_email_enrolledmessage.txt'
        ),
        'allowed_unenroll': (
            'ccx/unenroll_email_subject.txt',
            'ccx/unenroll_email_allowedmessage.txt'
        ),
        'enrolled_unenroll': (
            'ccx/unenroll_email_subject.txt',
            'ccx/unenroll_email_enrolledmessage.txt'
        ),
    }

    subject_template, message_template = email_template_dict.get(
        message_type, (None, None)
    )
    if subject_template is not None and message_template is not None:
        subject = render_to_string(subject_template, param_dict)
        message = render_to_string(message_template, param_dict)

    if subject and message:
        message = message.strip()

        subject = ''.join(subject.splitlines())
        from_address = microsite.get_value(
            'email_from_address',
            settings.DEFAULT_FROM_EMAIL
        )

        send_mail(
            subject,
            message,
            from_address,
            [student],
            fail_silently=False
        )
Ejemplo n.º 11
0
def xblock_view_handler(request, package_id, view_name, tag=None, branch=None, version_guid=None, block=None):
    """
    The restful handler for requests for rendered xblock views.

    Returns a json object containing two keys:
        html: The rendered html of the view
        resources: A list of tuples where the first element is the resource hash, and
            the second is the resource description
    """
    locator = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
    if not has_course_access(request.user, locator):
        raise PermissionDenied()
    old_location = loc_mapper().translate_locator_to_location(locator)

    accept_header = request.META.get('HTTP_ACCEPT', 'application/json')

    if 'application/x-fragment+json' in accept_header:
        store = get_modulestore(old_location)
        component = store.get_item(old_location)

        # wrap the generated fragment in the xmodule_editor div so that the javascript
        # can bind to it correctly
        component.runtime.wrappers.append(partial(wrap_xblock, 'StudioRuntime'))

        if view_name == 'studio_view':
            try:
                fragment = component.render('studio_view')
            # catch exceptions indiscriminately, since after this point they escape the
            # dungeon and surface as uneditable, unsaveable, and undeletable
            # component-goblins.
            except Exception as exc:                          # pylint: disable=w0703
                log.debug("unable to render studio_view for %r", component, exc_info=True)
                fragment = Fragment(render_to_string('html_error.html', {'message': str(exc)}))

            store.save_xmodule(component)

        elif view_name == 'student_view':
            fragment = get_preview_fragment(request, component)
            fragment.content = render_to_string('component.html', {
                'preview': fragment.content,
                'label': component.display_name or component.scope_ids.block_type,
            })
        else:
            raise Http404

        hashed_resources = OrderedDict()
        for resource in fragment.resources:
            hashed_resources[hash_resource(resource)] = resource

        return JsonResponse({
            'html': fragment.content,
            'resources': hashed_resources.items()
        })

    else:
        return HttpResponse(status=406)
Ejemplo n.º 12
0
def do_email_change_request(user, new_email, activation_key=None):
    """
    Given a new email for a user, does some basic verification of the new address and sends an activation message
    to the new address. If any issues are encountered with verification or sending the message, a ValueError will
    be thrown.
    """
    pec_list = PendingEmailChange.objects.filter(user=user)
    if len(pec_list) == 0:
        pec = PendingEmailChange()
        pec.user = user
    else:
        pec = pec_list[0]

    # if activation_key is not passing as an argument, generate a random key
    if not activation_key:
        activation_key = uuid.uuid4().hex

    pec.new_email = new_email
    pec.activation_key = activation_key
    pec.save()

    context = {
        'key': pec.activation_key,
        'old_email': user.email,
        'new_email': pec.new_email
    }

    subject = render_to_string('emails/email_change_subject.txt', context)
    subject = ''.join(subject.splitlines())

    message = render_to_string('emails/email_change.txt', context)

    from_address = configuration_helpers.get_value(
        'email_from_address',
        settings.DEFAULT_FROM_EMAIL
    )
    try:
        mail.send_mail(subject, message, from_address, [pec.new_email])
    except Exception:
        log.error(u'Unable to send email activation link to user from "%s"', from_address, exc_info=True)
        raise ValueError(_('Unable to send email activation link. Please try again later.'))

    # When the email address change is complete, a "edx.user.settings.changed" event will be emitted.
    # But because changing the email address is multi-step, we also emit an event here so that we can
    # track where the request was initiated.
    tracker.emit(
        SETTING_CHANGE_INITIATED,
        {
            "setting": "email",
            "old": context['old_email'],
            "new": context['new_email'],
            "user_id": user.id,
        }
    )
Ejemplo n.º 13
0
def student_approve(request,post_override=None):
    """
    This view will allow the Institute coordinater to approve registered students
    """
    if not request.user.is_authenticated:
        raise Http404

    post_vars = post_override if post_override else request.POST
    student_id = post_vars['student_id']
    status = post_vars['student_status']

    try:
        Student_object = Student_Institute.objects.filter(id=student_id)[0]
        status_id = int ( Institute_Status.objects.filter(name=status)[0].id )
        Student_object.status_id = status_id
        Student_object.save() 
         
    except:
        raise

    context= { }
    if status=="Approved" :
        # composes Approve Student email
        subject = render_to_string('emails/approve_student_email_subject.txt',context)
        # Email subject *must not* contain newlines
        subject = ''.join(subject.splitlines())
        message = render_to_string('emails/approve_student_email_body.txt',context)
    else :
        #composes Reject Student email
        subject = render_to_string('emails/reject_student_email_subject.txt',context)
        # Email subject *must not* contain newlines
        subject = ''.join(subject.splitlines())
        message = render_to_string('emails/reject_student_email_body.txt',context)


    # don't send email if we are doing load testing or random user generation for some reason
    if not (settings.FEATURES.get('AUTOMATIC_AUTH_FOR_TESTING')):
        from_address = MicrositeConfiguration.get_microsite_configuration_value(
            'email_from_address',
            settings.DEFAULT_FROM_EMAIL
        )
        try:
            _res = Student_object.user.email_user(subject, message, from_address)

        except:
            log.warning('Unable to send Approve/Reject email to user', exc_info=True)
            js['value'] = _('Could not send Approve/Reject e-mail.')
            # What is the correct status code to use here? I think it's 500, because
            # the problem is on the server's end -- but also, the account was created.
            # Seems like the core part of the request was successful.
            return JsonResponse(js, status=500)     


    return HttpResponse(json.dumps({'success': True}))
Ejemplo n.º 14
0
def send_mail_to_student(student, param_dict):
    """
    Construct the email using templates and then send it.
    `student` is the student's email address (a `str`),

    `param_dict` is a `dict` with keys
    [
        `site_name`: name given to edX instance (a `str`)
        `registration_url`: url for registration (a `str`)
        `course_id`: id of course (a `str`)
        `auto_enroll`: user input option (a `str`)
        `course_url`: url of course (a `str`)
        `email_address`: email of student (a `str`)
        `full_name`: student full name (a `str`)
        `message`: type of email to send and template to use (a `str`)
        `is_shib_course`: (a `boolean`)
    ]

    Returns a boolean indicating whether the email was sent successfully.
    """

    email_template_dict = {
        'allowed_enroll': ('emails/enroll_email_allowedsubject.txt',
                           'emails/enroll_email_allowedmessage.txt'),
        'enrolled_enroll': ('emails/enroll_email_enrolledsubject.txt',
                            'emails/enroll_email_enrolledmessage.txt'),
        'allowed_unenroll': ('emails/unenroll_email_subject.txt',
                             'emails/unenroll_email_allowedmessage.txt'),
        'enrolled_unenroll': ('emails/unenroll_email_subject.txt',
                              'emails/unenroll_email_enrolledmessage.txt')
    }

    subject_template, message_template = email_template_dict.get(
        param_dict['message'], (None, None))
    if subject_template is not None and message_template is not None:
        subject = render_to_string(subject_template, param_dict)
        message = render_to_string(message_template, param_dict)

        # Remove leading and trailing whitespace from body
        message = message.strip()

        # Email subject *must not* contain newlines
        subject = ''.join(subject.splitlines())
        send_mail(
            subject,
            message,
            settings.DEFAULT_FROM_EMAIL, [student],
            fail_silently=False)
Ejemplo n.º 15
0
def send_task_complete_email(self, task_name, task_state_text, dest_addr, detail_url):
    """
    Sending an email to the users when an async task completes.
    """
    retries = self.request.retries

    context = {
        'task_name': task_name,
        'task_status': task_state_text,
        'detail_url': detail_url
    }

    subject = render_to_string('emails/user_task_complete_email_subject.txt', context)
    # Eliminate any newlines
    subject = ''.join(subject.splitlines())
    message = render_to_string('emails/user_task_complete_email.txt', context)

    from_address = configuration_helpers.get_value(
        'email_from_address',
        settings.DEFAULT_FROM_EMAIL
    )

    try:
        mail.send_mail(subject, message, from_address, [dest_addr], fail_silently=False)
        LOGGER.info("Task complete email has been sent to User %s", dest_addr)
    except NoAuthHandlerFound:
        LOGGER.info(
            'Retrying sending email to user %s, attempt # %s of %s',
            dest_addr,
            retries,
            TASK_COMPLETE_EMAIL_MAX_RETRIES
        )
        try:
            self.retry(countdown=TASK_COMPLETE_EMAIL_TIMEOUT, max_retries=TASK_COMPLETE_EMAIL_MAX_RETRIES)
        except MaxRetriesExceededError:
            LOGGER.error(
                'Unable to send task completion email to user from "%s" to "%s"',
                from_address,
                dest_addr,
                exc_info=True
            )
    except Exception:  # pylint: disable=broad-except
        LOGGER.exception(
            'Unable to send task completion email to user from "%s" to "%s"',
            from_address,
            dest_addr,
            exc_info=True
        )
Ejemplo n.º 16
0
def render_purchase_form_html(cart, callback_url=None, extra_data=None):
    """
    Renders the HTML of the hidden POST form that must be used to initiate a purchase with CyberSource

    Args:
        cart (Order): The order model representing items in the user's cart.

    Keyword Args:
        callback_url (unicode): The URL that CyberSource should POST to when the user
            completes a purchase.  If not provided, then CyberSource will use
            the URL provided by the administrator of the account
            (CyberSource config, not LMS config).

        extra_data (list): Additional data to include as merchant-defined data fields.

    Returns:
        unicode: The rendered HTML form.

    """
    return render_to_string(
        "shoppingcart/cybersource_form.html",
        {
            "action": get_purchase_endpoint(),
            "params": get_signed_purchase_params(cart, callback_url=callback_url, extra_data=extra_data),
        },
    )
Ejemplo n.º 17
0
def add_inline_analytics(user, block, view, frag, context):  # pylint: disable=unused-argument
    """
    Adds a fragment for in-line analytics.

    Fragment consists of a button and some placeholder divs.

    Returns the wrapped fragment if the problem has a valid question (response). See get_responses_data function
    for valid responses.

    Otherwise, returns the fragment unchanged.
    """
    responses_data = get_responses_data(block)
    if responses_data:
        analytics_context = {
            'block_content': frag.content,
            'location': block.location.to_deprecated_string(),
            'element_id': block.location.html_id().replace('-', '_'),
            'answer_dist_url': reverse('get_analytics_answer_dist'),
            'responses_data': responses_data,
            'course_id': block.course_id.to_deprecated_string(),
        }
        return wrap_fragment(frag, render_to_string("inline_analytics.html", analytics_context))

    else:
        return frag
Ejemplo n.º 18
0
    def student_view_aside(self, block, context):  # pylint: disable=unused-argument
        """
        Display the tag selector with specific categories and allowed values,
        depending on the context.
        """
        if isinstance(block, CapaModule):
            tags = []
            for tag in self.get_available_tags():
                values = tag.get_values()
                current_value = self.saved_tags.get(tag.name, None)

                if current_value is not None and current_value not in values:
                    values.insert(0, current_value)

                tags.append({
                    'key': tag.name,
                    'title': tag.title,
                    'values': values,
                    'current_value': current_value
                })
            fragment = Fragment(render_to_string('structured_tags_block.html', {'tags': tags,
                                                                                'block_location': block.location}))
            fragment.add_javascript_url(self._get_studio_resource_url('/js/xblock_asides/structured_tags.js'))
            fragment.initialize_js('StructuredTagsInit')
            return fragment
        else:
            return Fragment(u'')
Ejemplo n.º 19
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
        except Exception:  # pylint: disable=broad-except
            html = render_to_string('courseware/error-message.html', None)
            log.exception(
                u"Error rendering course={course}, section_key={section_key}".format(
                    course=course, section_key=section_key
                ))

    return html
Ejemplo n.º 20
0
def render_accordion(request, course, chapter, section, field_data_cache):
    """
    Draws navigation bar. Takes current position in accordion as
    parameter.

    If chapter and section are '' or None, renders a default accordion.

    course, chapter, and section are the url_names.

    Returns the html string
    """

    # grab the table of contents
    user = User.objects.prefetch_related("groups").get(id=request.user.id)
    request.user = user  # keep just one instance of User
    toc = toc_for_course(user, request, course, chapter, section, field_data_cache)

    context = dict(
        [
            ("toc", toc),
            ("course_id", course.id),
            ("csrf", csrf(request)["csrf_token"]),
            ("due_date_display_format", course.due_date_display_format),
        ]
        + template_imports.items()
    )
    return render_to_string("courseware/accordion.html", context)
Ejemplo n.º 21
0
def get_static_tab_contents(request, course, tab):
    """
    Returns the contents for the given static tab
    """
    loc = course.id.make_usage_key(
        tab.type,
        tab.url_slug,
    )
    field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
        course.id, request.user, modulestore().get_item(loc), depth=0
    )
    tab_module = get_module(
        request.user, request, loc, field_data_cache, static_asset_path=course.static_asset_path
    )

    logging.debug('course_module = {0}'.format(tab_module))

    html = ''
    if tab_module is not None:
        try:
            html = tab_module.render(STUDENT_VIEW).content
        except Exception:  # pylint: disable=broad-except
            html = render_to_string('courseware/error-message.html', None)
            log.exception(
                u"Error rendering course={course}, tab={tab_url}".format(course=course, tab_url=tab['url_slug'])
            )

    return html
Ejemplo n.º 22
0
    def get_html(self, *args, **kwargs):
        """
        Returns raw html for the component.
        """
        # Import is placed here to avoid model import at project startup.
        from edxnotes.helpers import generate_uid, get_edxnotes_id_token, get_public_endpoint, get_token_url, is_feature_enabled
        is_studio = getattr(self.system, "is_author_mode", False)
        course = self.descriptor.runtime.modulestore.get_course(self.runtime.course_id)

        # Must be disabled:
        # - in Studio;
        # - when Harvard Annotation Tool is enabled for the course;
        # - when the feature flag or `edxnotes` setting of the course is set to False.
        if is_studio or not is_feature_enabled(course):
            return original_get_html(self, *args, **kwargs)
        else:
            return render_to_string("edxnotes_wrapper.html", {
                "content": original_get_html(self, *args, **kwargs),
                "uid": generate_uid(),
                "edxnotes_visibility": json.dumps(
                    getattr(self, 'edxnotes_visibility', course.edxnotes_visibility)
                ),
                "params": {
                    # Use camelCase to name keys.
                    "usageId": unicode(self.scope_ids.usage_id).encode("utf-8"),
                    "courseId": unicode(self.runtime.course_id).encode("utf-8"),
                    "token": get_edxnotes_id_token(self.runtime.get_real_user(self.runtime.anonymous_student_id)),
                    "tokenUrl": get_token_url(self.runtime.course_id),
                    "endpoint": get_public_endpoint(),
                    "debug": settings.DEBUG,
                    "eventStringLimit": settings.TRACK_MAX_EVENT / 6,
                },
            })
Ejemplo n.º 23
0
    def test_marketing_contact_link(self, _email_user):
        context = {
            'site': 'edx.org',
            'old_email': '*****@*****.**',
            'new_email': '*****@*****.**',
        }

        confirm_email_body = render_to_string('emails/confirm_email_change.txt', context)
        # With marketing site disabled, should link to the LMS contact static page.
        # The http(s) part was omitted keep the test focused.
        self.assertIn('://edx.org/contact', confirm_email_body)

        with patch.dict(settings.FEATURES, {'ENABLE_MKTG_SITE': True}):
            # Marketing site enabled, should link to the marketing site contact page.
            confirm_email_body = render_to_string('emails/confirm_email_change.txt', context)
            self.assertIn('https://dummy-root/help/contact-us', confirm_email_body)
Ejemplo n.º 24
0
def _send_decision_email(instance):
    """ Send an email to requesting user with the decision made about their request. """
    context = {
        'name': instance.user.username,
        'api_management_url': urlunsplit(
            (
                'https' if settings.HTTPS == 'on' else 'http',
                instance.site.domain,
                reverse('api_admin:api-status'),
                '',
                '',
            )
        ),
        'authentication_docs_url': settings.AUTH_DOCUMENTATION_URL,
        'api_docs_url': settings.API_DOCUMENTATION_URL,
        'support_email_address': settings.API_ACCESS_FROM_EMAIL,
        'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME)
    }

    message = render_to_string(
        'api_admin/api_access_request_email_{status}.txt'.format(status=instance.status),
        context
    )
    try:
        send_mail(
            _('API access request'),
            message,
            settings.API_ACCESS_FROM_EMAIL,
            [instance.user.email],
            fail_silently=False
        )
        instance.contacted = True
    except SMTPException:
        log.exception('Error sending API user notification email for request [%s].', instance.id)
Ejemplo n.º 25
0
    def assertReactivateEmailSent(self, email_user):
        """
        Assert that the correct reactivation email has been sent
        """
        context = generate_activation_email_context(self.user, self.registration)

        self.assertEmailUser(
            email_user,
            'emails/activation_email_subject.txt',
            context,
            'emails/activation_email.txt',
            context
        )

        # Thorough tests for safe_get_host are elsewhere; here we just want a quick URL sanity check
        request = RequestFactory().post('unused_url')
        request.user = self.user
        request.META['HTTP_HOST'] = "aGenericValidHostName"
        self.append_allowed_hosts("aGenericValidHostName")

        with patch('edxmako.request_context.get_current_request', return_value=request):
            body = render_to_string('emails/activation_email.txt', context)
            host = safe_get_host(request)

        self.assertIn(host, body)
Ejemplo n.º 26
0
def hint_manager(request, course_id):
    """
    The URL landing function for all calls to the hint manager, both POST and GET.
    """
    try:
        get_course_with_access(request.user, course_id, 'staff', depth=None)
    except Http404:
        out = 'Sorry, but students are not allowed to access the hint manager!'
        return HttpResponse(out)
    if request.method == 'GET':
        out = get_hints(request, course_id, 'mod_queue')
        out.update({'error': ''})
        return render_to_response('instructor/hint_manager.html', out)
    field = request.POST['field']
    if not (field == 'mod_queue' or field == 'hints'):
        # Invalid field.  (Don't let users continue - they may overwrite other db's)
        out = 'Error in hint manager - an invalid field was accessed.'
        return HttpResponse(out)

    switch_dict = {
        'delete hints': delete_hints,
        'switch fields': lambda *args: None,    # Takes any number of arguments, returns None.
        'change votes': change_votes,
        'add hint': add_hint,
        'approve': approve,
    }

    # Do the operation requested, and collect any error messages.
    error_text = switch_dict[request.POST['op']](request, course_id, field)
    if error_text is None:
        error_text = ''
    render_dict = get_hints(request, course_id, field)
    render_dict.update({'error': error_text})
    rendered_html = render_to_string('instructor/hint_manager_inner.html', render_dict)
    return HttpResponse(json.dumps({'success': True, 'contents': rendered_html}))
Ejemplo n.º 27
0
    def assertChangeEmailSent(self, email_user):
        """
        Assert that the correct email was sent to confirm an email change
        """
        context = {
            'old_email': self.user.email,
            'new_email': self.pending_change_request.new_email,
        }
        self.assertEmailUser(
            email_user,
            'emails/email_change_subject.txt',
            context,
            'emails/confirm_email_change.txt',
            context
        )

        # Thorough tests for safe_get_host are elsewhere; here we just want a quick URL sanity check
        request = RequestFactory().post('unused_url')
        request.user = self.user
        request.META['HTTP_HOST'] = "aGenericValidHostName"
        self.append_allowed_hosts("aGenericValidHostName")

        with patch('edxmako.request_context.get_current_request', return_value=request):
            body = render_to_string('emails/confirm_email_change.txt', context)
            url = safe_get_host(request)

        self.assertIn(url, body)
Ejemplo n.º 28
0
    def assertReactivateEmailSent(self, email_user):
        """Assert that the correct reactivation email has been sent"""
        context = {
            'name': self.user.profile.name,
            'key': self.registration.activation_key
        }

        self.assertEmailUser(
            email_user,
            'emails/activation_email_subject.txt',
            context,
            'emails/activation_email.txt',
            context
        )

        # Thorough tests for safe_get_host are elsewhere; here we just want a quick URL sanity check
        request = RequestFactory().post('unused_url')
        request.user = self.user
        request.META['HTTP_HOST'] = "aGenericValidHostName"
        self.append_allowed_hosts("aGenericValidHostName")

        mako_middleware_process_request(request)
        body = render_to_string('emails/activation_email.txt', context)
        host = safe_get_host(request)

        self.assertIn(host, body)
Ejemplo n.º 29
0
def _send_new_pending_email(instance):
    """ Send an email to settings.API_ACCESS_MANAGER_EMAIL with the contents of this API access request. """
    context = {
        'approval_url': urlunsplit(
            (
                'https' if settings.HTTPS == 'on' else 'http',
                instance.site.domain,
                reverse('admin:api_admin_apiaccessrequest_change', args=(instance.id,)),
                '',
                '',
            )
        ),
        'api_request': instance
    }

    message = render_to_string('api_admin/api_access_request_email_new_request.txt', context)
    try:
        send_mail(
            _('API access request from {company}').format(company=instance.company_name),
            message,
            settings.API_ACCESS_FROM_EMAIL,
            [settings.API_ACCESS_MANAGER_EMAIL],
            fail_silently=False
        )
    except SMTPException:
        log.exception('Error sending API user notification email for request [%s].', instance.id)
Ejemplo n.º 30
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
Ejemplo n.º 31
0
def send_mail_to_student(student, param_dict):
    """
    Construct the email using templates and then send it.
    `student` is the student's email address (a `str`),

    `param_dict` is a `dict` with keys
    [
        `site_name`: name given to edX instance (a `str`)
        `registration_url`: url for registration (a `str`)
        `course_id`: id of course (a `str`)
        `auto_enroll`: user input option (a `str`)
        `course_url`: url of course (a `str`)
        `email_address`: email of student (a `str`)
        `full_name`: student full name (a `str`)
        `message`: type of email to send and template to use (a `str`)
        `is_shib_course`: (a `boolean`)
    ]

    Returns a boolean indicating whether the email was sent successfully.
    """

    # add some helpers and microconfig subsitutions
    if 'course' in param_dict:
        param_dict['course_name'] = param_dict['course'].display_name_with_default

    param_dict['site_name'] = MicrositeConfiguration.get_microsite_configuration_value(
        'SITE_NAME',
        param_dict['site_name']
    )

    subject = None
    message = None

    # see if we are running in a microsite and that there is an
    # activation email template definition available as configuration, if so, then render that
    message_type = param_dict['message']

    email_template_dict = {
        'allowed_enroll': (
            'emails/enroll_email_allowedsubject.txt',
            'emails/enroll_email_allowedmessage.txt'
        ),
        'enrolled_enroll': (
            'emails/enroll_email_enrolledsubject.txt',
            'emails/enroll_email_enrolledmessage.txt'
        ),
        'allowed_unenroll': (
            'emails/unenroll_email_subject.txt',
            'emails/unenroll_email_allowedmessage.txt'
        ),
        'enrolled_unenroll': (
            'emails/unenroll_email_subject.txt',
            'emails/unenroll_email_enrolledmessage.txt'
        )
    }

    subject_template, message_template = email_template_dict.get(message_type, (None, None))
    if subject_template is not None and message_template is not None:
        subject = render_to_string(subject_template, param_dict)
        message = render_to_string(message_template, param_dict)

    if subject and message:
        # Remove leading and trailing whitespace from body
        message = message.strip()

        # Email subject *must not* contain newlines
        subject = ''.join(subject.splitlines())
        from_address = MicrositeConfiguration.get_microsite_configuration_value(
            'email_from_address',
            settings.DEFAULT_FROM_EMAIL
        )

        send_mail(subject, message, from_address, [student], fail_silently=False)
Ejemplo n.º 32
0
def xblock_handler(request,
                   tag=None,
                   course_id=None,
                   branch=None,
                   version_guid=None,
                   block=None):
    """
    The restful handler for xblock requests.

    DELETE
        json: delete this xblock instance from the course. Supports query parameters "recurse" to delete
        all children and "all_versions" to delete from all (mongo) versions.
    GET
        json: returns representation of the xblock (locator id, data, and metadata).
              if ?fields=graderType, it returns the graderType for the unit instead of the above.
        html: returns HTML for rendering the xblock (which includes both the "preview" view and the "editor" view)
    PUT or POST
        json: if xblock locator is specified, update the xblock instance. The json payload can contain
              these fields, all optional:
                :data: the new value for the data.
                :children: the locator ids of children for this xblock.
                :metadata: new values for the metadata fields. Any whose values are None will be deleted not set
                       to None! Absent ones will be left alone.
                :nullout: which metadata fields to set to None
                :graderType: change how this unit is graded
                :publish: can be one of three values, 'make_public, 'make_private', or 'create_draft'
              The JSON representation on the updated xblock (minus children) is returned.

              if xblock locator is not specified, create a new xblock instance. The json playload can contain
              these fields:
                :parent_locator: parent for new xblock, required
                :category: type of xblock, required
                :display_name: name for new xblock, optional
                :boilerplate: template name for populating fields, optional
              The locator (and old-style id) for the created xblock (minus children) is returned.
    """
    if course_id is not None:
        locator = BlockUsageLocator(course_id=course_id,
                                    branch=branch,
                                    version_guid=version_guid,
                                    usage_id=block)
        if not has_access(request.user, locator):
            raise PermissionDenied()
        old_location = loc_mapper().translate_locator_to_location(locator)

        if request.method == 'GET':
            if 'application/json' in request.META.get('HTTP_ACCEPT',
                                                      'application/json'):
                fields = request.REQUEST.get('fields', '').split(',')
                if 'graderType' in fields:
                    # right now can't combine output of this w/ output of _get_module_info, but worthy goal
                    return JsonResponse(
                        CourseGradingModel.get_section_grader_type(locator))
                # TODO: pass fields to _get_module_info and only return those
                rsp = _get_module_info(locator)
                return JsonResponse(rsp)
            else:
                component = modulestore().get_item(old_location)
                # Wrap the generated fragment in the xmodule_editor div so that the javascript
                # can bind to it correctly
                component.runtime.wrappers.append(
                    partial(wrap_xblock, handler_prefix))

                try:
                    content = component.render('studio_view').content
                # catch exceptions indiscriminately, since after this point they escape the
                # dungeon and surface as uneditable, unsaveable, and undeletable
                # component-goblins.
                except Exception as exc:  # pylint: disable=W0703
                    log.debug("Unable to render studio_view for %r",
                              component,
                              exc_info=True)
                    content = render_to_string('html_error.html',
                                               {'message': str(exc)})

                return render_to_response(
                    'component.html', {
                        'preview': get_preview_html(request, component),
                        'editor': content
                    })
        elif request.method == 'DELETE':
            delete_children = str_to_bool(
                request.REQUEST.get('recurse', 'False'))
            delete_all_versions = str_to_bool(
                request.REQUEST.get('all_versions', 'False'))

            return _delete_item_at_location(old_location, delete_children,
                                            delete_all_versions)
        else:  # Since we have a course_id, we are updating an existing xblock.
            return _save_item(
                request,
                locator,
                old_location,
                data=request.json.get('data'),
                children=request.json.get('children'),
                metadata=request.json.get('metadata'),
                nullout=request.json.get('nullout'),
                grader_type=request.json.get('graderType'),
                publish=request.json.get('publish'),
            )
    elif request.method in ('PUT', 'POST'):
        return _create_item(request)
    else:
        return HttpResponseBadRequest(
            "Only instance creation is supported without a course_id.",
            content_type="text/plain")
Ejemplo n.º 33
0
 def render(self):
     """
     Return an HTML representation of this summary block.
     """
     return render_to_string('courseware/date_summary.html',
                             self.get_context())
Ejemplo n.º 34
0
def xblock_view_handler(request,
                        package_id,
                        view_name,
                        tag=None,
                        branch=None,
                        version_guid=None,
                        block=None):
    """
    The restful handler for requests for rendered xblock views.

    Returns a json object containing two keys:
        html: The rendered html of the view
        resources: A list of tuples where the first element is the resource hash, and
            the second is the resource description
    """
    locator = BlockUsageLocator(package_id=package_id,
                                branch=branch,
                                version_guid=version_guid,
                                block_id=block)
    if not has_course_access(request.user, locator):
        raise PermissionDenied()
    old_location = loc_mapper().translate_locator_to_location(locator)

    accept_header = request.META.get('HTTP_ACCEPT', 'application/json')

    if 'application/json' in accept_header:
        store = get_modulestore(old_location)
        component = store.get_item(old_location)

        # wrap the generated fragment in the xmodule_editor div so that the javascript
        # can bind to it correctly
        component.runtime.wrappers.append(partial(wrap_xblock,
                                                  'StudioRuntime'))

        if view_name == 'studio_view':
            try:
                fragment = component.render('studio_view')
            # catch exceptions indiscriminately, since after this point they escape the
            # dungeon and surface as uneditable, unsaveable, and undeletable
            # component-goblins.
            except Exception as exc:  # pylint: disable=w0703
                log.debug("unable to render studio_view for %r",
                          component,
                          exc_info=True)
                fragment = Fragment(
                    render_to_string('html_error.html', {'message': str(exc)}))

            store.save_xmodule(component)
        elif view_name == 'student_view' and component.has_children:
            # For non-leaf xblocks on the unit page, show the special rendering
            # which links to the new container page.
            course_location = loc_mapper().translate_locator_to_location(
                locator, True)
            course = store.get_item(course_location)
            html = render_to_string('unit_container_xblock_component.html', {
                'course': course,
                'xblock': component,
                'locator': locator
            })
            return JsonResponse({
                'html': html,
                'resources': [],
            })
        elif view_name in ('student_view', 'container_preview'):
            is_container_view = (view_name == 'container_preview')

            # Only show the new style HTML for the container view, i.e. for non-verticals
            # Note: this special case logic can be removed once the unit page is replaced
            # with the new container view.
            is_read_only_view = is_container_view
            context = {
                'container_view': is_container_view,
                'read_only': is_read_only_view,
                'root_xblock': component
            }

            fragment = get_preview_fragment(request, component, context)
            # For old-style pages (such as unit and static pages), wrap the preview with
            # the component div. Note that the container view recursively adds headers
            # into the preview fragment, so we don't want to add another header here.
            if not is_container_view:
                fragment.content = render_to_string(
                    'component.html',
                    {
                        'preview':
                        fragment.content,
                        'label':
                        component.display_name
                        or component.scope_ids.block_type,

                        # Native XBlocks are responsible for persisting their own data,
                        # so they are also responsible for providing save/cancel buttons.
                        'show_save_cancel':
                        isinstance(component,
                                   xmodule.x_module.XModuleDescriptor),
                    })
        else:
            raise Http404

        hashed_resources = OrderedDict()
        for resource in fragment.resources:
            hashed_resources[hash_resource(resource)] = resource

        return JsonResponse({
            'html': fragment.content,
            'resources': hashed_resources.items()
        })

    else:
        return HttpResponse(status=406)
Ejemplo n.º 35
0
def render_404(request):
    return HttpResponseNotFound(render_to_string('404.html', {}))
Ejemplo n.º 36
0
def add_staff_markup(user, has_instructor_access, block, view, frag, context):  # pylint: disable=unused-argument
    """
    Updates the supplied module with a new get_html function that wraps
    the output of the old get_html function with additional information
    for admin users only, including a histogram of student answers, the
    definition of the xmodule, and a link to view the module in Studio
    if it is a Studio edited, mongo stored course.

    Does nothing if module is a SequenceModule.
    """
    # TODO: make this more general, eg use an XModule attribute instead
    if isinstance(block, VerticalBlock) and (not context or not context.get('child_of_vertical', False)):
        # check that the course is a mongo backed Studio course before doing work
        is_mongo_course = modulestore().get_modulestore_type(block.location.course_key) != ModuleStoreEnum.Type.xml
        is_studio_course = block.course_edit_method == "Studio"

        if is_studio_course and is_mongo_course:
            # build edit link to unit in CMS. Can't use reverse here as lms doesn't load cms's urls.py
            edit_link = "//" + settings.CMS_BASE + '/container/' + unicode(block.location)

            # return edit link in rendered HTML for display
            return wrap_fragment(frag, render_to_string("edit_unit_link.html", {'frag_content': frag.content, 'edit_link': edit_link}))
        else:
            return frag

    if isinstance(block, SequenceModule):
        return frag

    block_id = block.location
    if block.has_score and settings.FEATURES.get('DISPLAY_HISTOGRAMS_TO_STAFF'):
        histogram = grade_histogram(block_id)
        render_histogram = len(histogram) > 0
    else:
        histogram = None
        render_histogram = False

    if settings.FEATURES.get('ENABLE_LMS_MIGRATION') and hasattr(block.runtime, 'filestore'):
        [filepath, filename] = getattr(block, 'xml_attributes', {}).get('filename', ['', None])
        osfs = block.runtime.filestore
        if filename is not None and osfs.exists(filename):
            # if original, unmangled filename exists then use it (github
            # doesn't like symlinks)
            filepath = filename
        data_dir = block.static_asset_path or osfs.root_path.rsplit('/')[-1]
        giturl = block.giturl or 'https://github.com/MITx'
        edit_link = "%s/%s/tree/master/%s" % (giturl, data_dir, filepath)
    else:
        edit_link = False
        # Need to define all the variables that are about to be used
        giturl = ""
        data_dir = ""

    source_file = block.source_file  # source used to generate the problem XML, eg latex or word

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

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

    field_contents = []
    for name, field in block.fields.items():
        try:
            field_contents.append((name, field.read_from(block)))
        except InvalidScopeError:
            log.warning("Unable to read field in Staff Debug information", exc_info=True)
            field_contents.append((name, "WARNING: Unable to read field"))

    staff_context = {'fields': field_contents,
                     'xml_attributes': getattr(block, 'xml_attributes', {}),
                     'location': block.location,
                     'xqa_key': block.xqa_key,
                     'source_file': source_file,
                     'source_url': '%s/%s/tree/master/%s' % (giturl, data_dir, source_file),
                     'category': str(block.__class__.__name__),
                     # Template uses element_id in js function names, so can't allow dashes
                     'element_id': block.location.html_id().replace('-', '_'),
                     'edit_link': edit_link,
                     'user': user,
                     'xqa_server': settings.FEATURES.get('USE_XQA_SERVER', 'http://*****:*****@content-qa.mitx.mit.edu/xqa'),
                     'histogram': json.dumps(histogram),
                     'render_histogram': render_histogram,
                     'block_content': frag.content,
                     'is_released': is_released,
                     'has_instructor_access': has_instructor_access,
                     }
    return wrap_fragment(frag, render_to_string("staff_problem_info.html", staff_context))
Ejemplo n.º 37
0
def submit_feedback(request):
    """
    Create a Zendesk ticket or if not available, send an email with the
    feedback form fields.

    If feedback submission is not enabled, any request will raise `Http404`.
    If any configuration parameter (`ZENDESK_URL`, `ZENDESK_USER`, or
    `ZENDESK_API_KEY`) is missing, any request will raise an `Exception`.
    The request must be a POST request specifying `subject` and `details`.
    If the user is not authenticated, the request must also specify `name` and
    `email`. If the user is authenticated, the `name` and `email` will be
    populated from the user's information. If any required parameter is
    missing, a 400 error will be returned indicating which field is missing and
    providing an error message. If Zendesk ticket creation fails, 500 error
    will be returned with no body; if ticket creation succeeds, an empty
    successful response (200) will be returned.
    """
    if not settings.FEATURES.get('ENABLE_FEEDBACK_SUBMISSION', False):
        raise Http404()
    if request.method != "POST":
        return HttpResponseNotAllowed(["POST"])

    def build_error_response(status_code, field, err_msg):
        return HttpResponse(json.dumps({
            "field": field,
            "error": err_msg
        }),
                            status=status_code)

    required_fields = ["subject", "details"]

    if not UserProfile.has_registered(request.user):
        required_fields += ["name", "email"]

    required_field_errs = {
        "subject": "Please provide a subject.",
        "details": "Please provide details.",
        "name": "Please provide your name.",
        "email": "Please provide a valid e-mail.",
    }
    for field in required_fields:
        if field not in request.POST or not request.POST[field]:
            return build_error_response(400, field, required_field_errs[field])

    if not UserProfile.has_registered(request.user):
        try:
            validate_email(request.POST["email"])
        except ValidationError:
            return build_error_response(400, "email",
                                        required_field_errs["email"])

    success = False
    context = get_feedback_form_context(request)

    #Update the tag info with 'enterprise_learner' if the user belongs to an enterprise customer.
    enterprise_learner_data = enterprise_api.get_enterprise_learner_data(
        site=request.site, user=request.user)
    if enterprise_learner_data:
        context["tags"]["learner_type"] = "enterprise_learner"

    support_backend = configuration_helpers.get_value(
        'CONTACT_FORM_SUBMISSION_BACKEND', SUPPORT_BACKEND_ZENDESK)

    if support_backend == SUPPORT_BACKEND_EMAIL:
        try:
            send_mail(subject=render_to_string(
                'emails/contact_us_feedback_email_subject.txt', context),
                      message=render_to_string(
                          'emails/contact_us_feedback_email_body.txt',
                          context),
                      from_email=context["support_email"],
                      recipient_list=[context["support_email"]],
                      fail_silently=False)
            success = True
        except SMTPException:
            log.exception(
                'Error sending feedback to contact_us email address.')
            success = False

    else:
        if not settings.ZENDESK_URL or not settings.ZENDESK_USER or not settings.ZENDESK_API_KEY:
            raise Exception("Zendesk enabled but not configured")

        custom_fields = None
        if settings.ZENDESK_CUSTOM_FIELDS:
            custom_field_context = _get_zendesk_custom_field_context(
                request, learner_data=enterprise_learner_data)
            custom_fields = _format_zendesk_custom_fields(custom_field_context)

        success = _record_feedback_in_zendesk(
            context["realname"],
            context["email"],
            context["subject"],
            context["details"],
            context["tags"],
            context["additional_info"],
            support_email=context["support_email"],
            custom_fields=custom_fields)

    _record_feedback_in_datadog(context["tags"])

    return HttpResponse(status=(200 if success else 500))
def user_details_force_sync(auth_entry,
                            strategy,
                            details,
                            user=None,
                            *args,
                            **kwargs):
    """
    Update normally protected user details using data from provider.

    This step in the pipeline is akin to `social_core.pipeline.user.user_details`, which updates
    the user details but has an unconfigurable protection over updating the username & email, and
    is unable to update information such as the user's full name which isn't on the user model, but
    rather on the user profile model.

    Additionally, because the email field is normally used to log in, if the email is changed by this
    forced synchronization, we send an email to both the old and new emails, letting the user know.

    This step is controlled by the `sync_learner_profile_data` flag on the provider's configuration.
    """
    current_provider = provider.Registry.get_from_pipeline({
        'backend':
        strategy.request.backend.name,
        'kwargs':
        kwargs
    })
    if user and current_provider.sync_learner_profile_data:
        # Keep track of which incoming values get applied.
        changed = {}

        # Map each incoming field from the provider to the name on the user model (by default, they always match).
        field_mapping = {
            field: (user, field)
            for field in details.keys() if hasattr(user, field)
        }

        # This is a special case where the field mapping should go to the user profile object and not the user object,
        # in some cases with differing field names (i.e. 'fullname' vs. 'name').
        field_mapping.update({
            'fullname': (user.profile, 'name'),
            'country': (user.profile, 'country'),
        })

        # Remove username from list of fields for update
        field_mapping.pop('username', None)

        # Track any fields that would raise an integrity error if there was a conflict.
        integrity_conflict_fields = {
            'email': user.email,
            'username': user.username
        }

        for provider_field, (model, field) in field_mapping.items():
            provider_value = details.get(provider_field)
            current_value = getattr(model, field)
            if provider_value is not None and current_value != provider_value:
                if field in integrity_conflict_fields and User.objects.filter(
                        **{
                            field: provider_value
                        }).exists():
                    logger.warning(
                        'User with ID [%s] tried to synchronize profile data through [%s] '
                        'but there was a conflict with an existing [%s]: [%s].',
                        user.id, current_provider.name, field, provider_value)
                    continue
                changed[provider_field] = current_value
                setattr(model, field, provider_value)

        if changed:
            logger.info(
                "User [%s] performed SSO through [%s] who synchronizes profile data, and the "
                "following fields were changed: %s",
                user.username,
                current_provider.name,
                changed.keys(),
            )

            # Save changes to user and user.profile models.
            strategy.storage.user.changed(user)
            user.profile.save()

            # Send an email to the old and new email to alert the user that their login email changed.
            if changed.get('email'):
                old_email = changed['email']
                new_email = user.email
                email_context = {
                    'old_email': old_email,
                    'new_email': new_email
                }
                # Subjects shouldn't have new lines.
                subject = ''.join(
                    render_to_string(
                        'emails/sync_learner_profile_data_email_change_subject.txt',
                        email_context).splitlines())
                body = render_to_string(
                    'emails/sync_learner_profile_data_email_change_body.txt',
                    email_context)
                from_email = configuration_helpers.get_value(
                    'email_from_address', settings.DEFAULT_FROM_EMAIL)

                email = EmailMessage(subject=subject,
                                     body=body,
                                     from_email=from_email,
                                     to=[old_email, new_email])
                email.content_subtype = "html"
                try:
                    email.send()
                except SMTPException:
                    logger.exception(
                        'Error sending IdP learner data sync-initiated email change '
                        'notification email for user [%s].', user.username)
Ejemplo n.º 39
0
    def purchase(self,
                 first='',
                 last='',
                 street1='',
                 street2='',
                 city='',
                 state='',
                 postalcode='',
                 country='',
                 ccnum='',
                 cardtype='',
                 processor_reply_dump=''):
        """
        Call to mark this order as purchased.  Iterates through its OrderItems and calls
        their purchased_callback

        `first` - first name of person billed (e.g. John)
        `last` - last name of person billed (e.g. Smith)
        `street1` - first line of a street address of the billing address (e.g. 11 Cambridge Center)
        `street2` - second line of a street address of the billing address (e.g. Suite 101)
        `city` - city of the billing address (e.g. Cambridge)
        `state` - code of the state, province, or territory of the billing address (e.g. MA)
        `postalcode` - postal code of the billing address (e.g. 02142)
        `country` - country code of the billing address (e.g. US)
        `ccnum` - last 4 digits of the credit card number of the credit card billed (e.g. 1111)
        `cardtype` - 3-digit code representing the card type used (e.g. 001)
        `processor_reply_dump` - all the parameters returned by the processor

        """
        if self.status == 'purchased':
            return
        self.status = 'purchased'
        self.purchase_time = datetime.now(pytz.utc)
        self.bill_to_first = first
        self.bill_to_last = last
        self.bill_to_city = city
        self.bill_to_state = state
        self.bill_to_country = country
        self.bill_to_postalcode = postalcode
        if settings.FEATURES['STORE_BILLING_INFO']:
            self.bill_to_street1 = street1
            self.bill_to_street2 = street2
            self.bill_to_ccnum = ccnum
            self.bill_to_cardtype = cardtype
            self.processor_reply_dump = processor_reply_dump

        # save these changes on the order, then we can tell when we are in an
        # inconsistent state
        self.save()
        # this should return all of the objects with the correct types of the
        # subclasses
        orderitems = OrderItem.objects.filter(order=self).select_subclasses()
        for item in orderitems:
            item.purchase_item()

        # send confirmation e-mail
        subject = _("Order Payment Confirmation")
        message = render_to_string(
            'emails/order_confirmation_email.txt', {
                'order': self,
                'order_items': orderitems,
                'has_billing_info': settings.FEATURES['STORE_BILLING_INFO']
            })
        try:
            from_address = microsite.get_value('email_from_address',
                                               settings.DEFAULT_FROM_EMAIL)

            send_mail(subject, message, from_address, [self.user.email])  # pylint: disable=E1101
        except (smtplib.SMTPException, BotoServerError
                ):  # sadly need to handle diff. mail backends individually
            log.error('Failed sending confirmation e-mail for order %d',
                      self.id)  # pylint: disable=E1101
Ejemplo n.º 40
0
def index(request, course_id, chapter=None, section=None, position=None):
    """
    Displays courseware accordion and associated content.  If course, chapter,
    and section are all specified, renders the page, or returns an error if they
    are invalid.

    If section is not specified, displays the accordion opened to the right chapter.

    If neither chapter or section are specified, redirects to user's most recent
    chapter, or the first chapter if this is the user's first visit.

    Arguments:

     - request    : HTTP request
     - course_id  : course id (str: ORG/course/URL_NAME)
     - chapter    : chapter url_name (str)
     - section    : section url_name (str)
     - position   : position in module, eg of <sequential> module (str)

    Returns:

     - HTTPresponse
    """
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    user = User.objects.prefetch_related("groups").get(id=request.user.id)
    request.user = user  # keep just one instance of User
    course = get_course_with_access(user, 'load', course_key, depth=2)
    staff_access = has_access(user, 'staff', course)
    registered = registered_for_course(course, user)
    if not registered:
        # TODO (vshnayder): do course instructors need to be registered to see course?
        log.debug(u'User %s tried to view course %s but is not enrolled', user,
                  course.location.to_deprecated_string())
        return redirect(
            reverse('about_course', args=[course_key.to_deprecated_string()]))

    masq = setup_masquerade(request, staff_access)

    try:
        field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
            course_key, user, course, depth=2)

        course_module = get_module_for_descriptor(user, request, course,
                                                  field_data_cache, course_key)
        if course_module is None:
            log.warning(
                u'If you see this, something went wrong: if we got this'
                u' far, should have gotten a course module for this user')
            return redirect(
                reverse('about_course',
                        args=[course_key.to_deprecated_string()]))

        studio_url = get_studio_url(course_key, 'course')

        context = {
            'csrf':
            csrf(request)['csrf_token'],
            'accordion':
            render_accordion(request, course, chapter, section,
                             field_data_cache),
            'COURSE_TITLE':
            course.display_name_with_default,
            'course':
            course,
            'init':
            '',
            'fragment':
            Fragment(),
            'staff_access':
            staff_access,
            'studio_url':
            studio_url,
            'masquerade':
            masq,
            'xqa_server':
            settings.FEATURES.get(
                'USE_XQA_SERVER',
                'http://*****:*****@content-qa.mitx.mit.edu/xqa'),
            'reverifications':
            fetch_reverify_banner_info(request, course_key),
        }

        has_content = course.has_children_at_depth(CONTENT_DEPTH)
        if not has_content:
            # Show empty courseware for a course with no units
            return render_to_response('courseware/courseware.html', context)
        elif chapter is None:
            # passing CONTENT_DEPTH avoids returning 404 for a course with an
            # empty first section and a second section with content
            return redirect_to_course_position(course_module, CONTENT_DEPTH)

        # Only show the chat if it's enabled by the course and in the
        # settings.
        show_chat = course.show_chat and settings.FEATURES['ENABLE_CHAT']
        if show_chat:
            context['chat'] = chat_settings(course, user)
            # If we couldn't load the chat settings, then don't show
            # the widget in the courseware.
            if context['chat'] is None:
                show_chat = False

        context['show_chat'] = show_chat

        chapter_descriptor = course.get_child_by(
            lambda m: m.location.name == chapter)
        if chapter_descriptor is not None:
            save_child_position(course_module, chapter)
        else:
            raise Http404(
                'No chapter descriptor found with name {}'.format(chapter))

        chapter_module = course_module.get_child_by(
            lambda m: m.location.name == chapter)
        if chapter_module is None:
            # User may be trying to access a chapter that isn't live yet
            if masq == 'student':  # if staff is masquerading as student be kinder, don't 404
                log.debug('staff masq as student: no chapter %s' % chapter)
                return redirect(
                    reverse('courseware',
                            args=[course.id.to_deprecated_string()]))
            raise Http404

        if section is not None:
            section_descriptor = chapter_descriptor.get_child_by(
                lambda m: m.location.name == section)
            if section_descriptor is None:
                # Specifically asked-for section doesn't exist
                if masq == 'student':  # if staff is masquerading as student be kinder, don't 404
                    log.debug('staff masq as student: no section %s' % section)
                    return redirect(
                        reverse('courseware',
                                args=[course.id.to_deprecated_string()]))
                raise Http404

            # cdodge: this looks silly, but let's refetch the section_descriptor with depth=None
            # which will prefetch the children more efficiently than doing a recursive load
            section_descriptor = modulestore().get_item(
                section_descriptor.location, depth=None)

            # Load all descendants of the section, because we're going to display its
            # html, which in general will need all of its children
            section_field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
                course_key, user, section_descriptor, depth=None)

            section_module = get_module_for_descriptor(
                request.user, request, section_descriptor,
                section_field_data_cache, course_key, position)

            if section_module is None:
                # User may be trying to be clever and access something
                # they don't have access to.
                raise Http404

            # Save where we are in the chapter
            save_child_position(chapter_module, section)
            context['fragment'] = section_module.render('student_view')
            context[
                'section_title'] = section_descriptor.display_name_with_default
        else:
            # section is none, so display a message
            studio_url = get_studio_url(course_key, 'course')
            prev_section = get_current_child(chapter_module)
            if prev_section is None:
                # Something went wrong -- perhaps this chapter has no sections visible to the user
                raise Http404
            prev_section_url = reverse('courseware_section',
                                       kwargs={
                                           'course_id':
                                           course_key.to_deprecated_string(),
                                           'chapter':
                                           chapter_descriptor.url_name,
                                           'section':
                                           prev_section.url_name
                                       })
            context['fragment'] = Fragment(content=render_to_string(
                'courseware/welcome-back.html', {
                    'course': course,
                    'studio_url': studio_url,
                    'chapter_module': chapter_module,
                    'prev_section': prev_section,
                    'prev_section_url': prev_section_url
                }))

        result = render_to_response('courseware/courseware.html', context)
    except Exception as e:
        if isinstance(e, Http404):
            # let it propagate
            raise

        # In production, don't want to let a 500 out for any reason
        if settings.DEBUG:
            raise
        else:
            log.exception(
                u"Error in index view: user={user}, course={course}, chapter={chapter}"
                u" section={section} position={position}".format(
                    user=user,
                    course=course,
                    chapter=chapter,
                    section=section,
                    position=position))
            try:
                result = render_to_response('courseware/courseware-error.html',
                                            {
                                                'staff_access': staff_access,
                                                'course': course
                                            })
            except:
                # Let the exception propagate, relying on global config to at
                # at least return a nice error message
                log.exception("Error while rendering courseware-error page")
                raise

    return result
Ejemplo n.º 41
0
def confirm_email_change(request, key):  # pylint: disable=unused-argument
    """
    User requested a new e-mail. This is called when the activation
    link is clicked. We confirm with the old e-mail, and update
    """
    if waffle().is_enabled(PREVENT_AUTH_USER_WRITES):
        return render_to_response('email_change_failed.html',
                                  {'err_msg': SYSTEM_MAINTENANCE_MSG})

    with transaction.atomic():
        try:
            pec = PendingEmailChange.objects.get(activation_key=key)
        except PendingEmailChange.DoesNotExist:
            response = render_to_response("invalid_email_key.html", {})
            transaction.set_rollback(True)
            return response

        user = pec.user
        address_context = {'old_email': user.email, 'new_email': pec.new_email}

        if len(User.objects.filter(email=pec.new_email)) != 0:
            response = render_to_response("email_exists.html", {})
            transaction.set_rollback(True)
            return response

        subject = render_to_string('emails/email_change_subject.txt',
                                   address_context)
        subject = ''.join(subject.splitlines())
        message = render_to_string('emails/confirm_email_change.txt',
                                   address_context)
        u_prof = UserProfile.objects.get(user=user)
        meta = u_prof.get_meta()
        if 'old_emails' not in meta:
            meta['old_emails'] = []
        meta['old_emails'].append(
            [user.email, datetime.datetime.now(UTC).isoformat()])
        u_prof.set_meta(meta)
        u_prof.save()
        # Send it to the old email...
        try:
            user.email_user(
                subject, message,
                configuration_helpers.get_value('email_from_address',
                                                settings.DEFAULT_FROM_EMAIL))
        except Exception:  # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to old address',
                        exc_info=True)
            response = render_to_response("email_change_failed.html",
                                          {'email': user.email})
            transaction.set_rollback(True)
            return response

        user.email = pec.new_email
        user.save()
        pec.delete()
        # And send it to the new email...
        try:
            user.email_user(
                subject, message,
                configuration_helpers.get_value('email_from_address',
                                                settings.DEFAULT_FROM_EMAIL))
        except Exception:  # pylint: disable=broad-except
            log.warning('Unable to send confirmation email to new address',
                        exc_info=True)
            response = render_to_response("email_change_failed.html",
                                          {'email': pec.new_email})
            transaction.set_rollback(True)
            return response

        response = render_to_response("email_change_successful.html",
                                      address_context)
        return response
Ejemplo n.º 42
0
def wrap_xblock(runtime_class,
                block,
                view,
                frag,
                context,
                usage_id_serializer,
                display_name_only=False,
                extra_data=None):  # pylint: disable=unused-argument
    """
    Wraps the results of rendering an XBlock view in a standard <section> with identifying
    data so that the appropriate javascript module can be loaded onto it.

    :param runtime_class: The name of the javascript runtime class to use to load this block
    :param block: An XBlock (that may be an XModule or XModuleDescriptor)
    :param view: The name of the view that rendered the fragment being wrapped
    :param frag: The :class:`Fragment` to be wrapped
    :param context: The context passed to the view being rendered
    :param usage_id_serializer: A function to serialize the block's usage_id for use by the
        front-end Javascript Runtime.
    :param display_name_only: If true, don't render the fragment content at all.
        Instead, just render the `display_name` of `block`
    :param extra_data: A dictionary with extra data values to be set on the wrapper
    """
    if extra_data is None:
        extra_data = {}

    # If any mixins have been applied, then use the unmixed class
    class_name = getattr(block, 'unmixed_class', block.__class__).__name__

    data = {}
    data.update(extra_data)
    css_classes = ['xblock', 'xblock-' + view]

    if isinstance(block, (XModule, XModuleDescriptor)):
        if view in PREVIEW_VIEWS:
            # The block is acting as an XModule
            css_classes.append('xmodule_display')
        elif view == STUDIO_VIEW:
            # The block is acting as an XModuleDescriptor
            css_classes.append('xmodule_edit')

        css_classes.append('xmodule_' + class_name)
        data['type'] = block.js_module_name
        shim_xmodule_js(frag)

    if frag.js_init_fn:
        data['init'] = frag.js_init_fn
        data['runtime-class'] = runtime_class
        data['runtime-version'] = frag.js_init_version
        data['block-type'] = block.scope_ids.block_type
        data['usage-id'] = usage_id_serializer(block.scope_ids.usage_id)

    template_context = {
        'content':
        block.display_name if display_name_only else frag.content,
        'classes':
        css_classes,
        'display_name':
        block.display_name_with_default,
        'data_attributes':
        u' '.join(u'data-{}="{}"'.format(key, value)
                  for key, value in data.iteritems()),
    }

    return wrap_fragment(
        frag, render_to_string('xblock_wrapper.html', template_context))
Ejemplo n.º 43
0
def _create_recent_enrollment_message(course_enrollments, course_modes):  # pylint: disable=invalid-name
    """
    Builds a recent course enrollment message.

    Constructs a new message template based on any recent course enrollments
    for the student.

    Args:
        course_enrollments (list[CourseEnrollment]): a list of course enrollments.
        course_modes (dict): Mapping of course ID's to course mode dictionaries.

    Returns:
        A string representing the HTML message output from the message template.
        None if there are no recently enrolled courses.

    """
    recently_enrolled_courses = _get_recently_enrolled_courses(
        course_enrollments)

    if recently_enrolled_courses:
        enrollments_count = len(recently_enrolled_courses)
        course_name_separator = ', '

        # If length of enrolled course 2, join names with 'and'
        if enrollments_count == 2:
            course_name_separator = _(' and ')

        course_names = course_name_separator.join([
            enrollment.course_overview.display_name
            for enrollment in recently_enrolled_courses
        ])

        allow_donations = any(
            _allow_donation(course_modes, enrollment.course_overview.id,
                            enrollment)
            for enrollment in recently_enrolled_courses)

        platform_name = configuration_helpers.get_value(
            'platform_name', settings.PLATFORM_NAME)

        return render_to_string(
            'enrollment/course_enrollment_message.html', {
                'course_names':
                recently_enrolled_courses[0].course_overview.display_name,
                'current_time':
                pytz.utc.localize(datetime.datetime.now()),
                'recently_enrolled_courses':
                recently_enrolled_courses,
                'course_date_start':
                recently_enrolled_courses[0].course_overview.
                dashboard_start_display,
                'started':
                recently_enrolled_courses[0].course_overview.has_started(),
                'enrollments_count':
                enrollments_count,
                'allow_donations':
                allow_donations,
                'platform_name':
                platform_name,
                'course_id':
                recently_enrolled_courses[0].course_overview.id
                if enrollments_count == 1 else None
            })
Ejemplo n.º 44
0
def xblock_view_handler(request, usage_key_string, view_name):
    """
    The restful handler for requests for rendered xblock views.

    Returns a json object containing two keys:
        html: The rendered html of the view
        resources: A list of tuples where the first element is the resource hash, and
            the second is the resource description
    """
    usage_key = UsageKey.from_string(usage_key_string)
    if not has_course_access(request.user, usage_key.course_key):
        raise PermissionDenied()

    accept_header = request.META.get('HTTP_ACCEPT', 'application/json')

    if 'application/json' in accept_header:
        store = get_modulestore(usage_key)
        xblock = store.get_item(usage_key)
        is_read_only = _is_xblock_read_only(xblock)
        container_views = [
            'container_preview', 'reorderable_container_child_preview'
        ]
        unit_views = ['student_view']

        # wrap the generated fragment in the xmodule_editor div so that the javascript
        # can bind to it correctly
        xblock.runtime.wrappers.append(
            partial(wrap_xblock, 'StudioRuntime', usage_id_serializer=unicode))

        if view_name == 'studio_view':
            try:
                fragment = xblock.render('studio_view')
            # catch exceptions indiscriminately, since after this point they escape the
            # dungeon and surface as uneditable, unsaveable, and undeletable
            # component-goblins.
            except Exception as exc:  # pylint: disable=w0703
                log.debug("unable to render studio_view for %r",
                          xblock,
                          exc_info=True)
                fragment = Fragment(
                    render_to_string('html_error.html', {'message': str(exc)}))

            # change not authored by requestor but by xblocks.
            store.update_item(xblock, None)

        elif view_name == 'student_view' and xblock_has_own_studio_page(
                xblock):
            context = {
                'runtime_type': 'studio',
                'container_view': False,
                'read_only': is_read_only,
                'root_xblock': xblock,
            }
            # For non-leaf xblocks on the unit page, show the special rendering
            # which links to the new container page.
            html = render_to_string(
                'container_xblock_component.html', {
                    'xblock_context': context,
                    'xblock': xblock,
                    'locator': usage_key,
                })
            return JsonResponse({
                'html': html,
                'resources': [],
            })
        elif view_name in (unit_views + container_views):
            is_container_view = (view_name in container_views)

            # Determine the items to be shown as reorderable. Note that the view
            # 'reorderable_container_child_preview' is only rendered for xblocks that
            # are being shown in a reorderable container, so the xblock is automatically
            # added to the list.
            reorderable_items = set()
            if view_name == 'reorderable_container_child_preview':
                reorderable_items.add(xblock.location)

            # Only show the new style HTML for the container view, i.e. for non-verticals
            # Note: this special case logic can be removed once the unit page is replaced
            # with the new container view.
            context = {
                'runtime_type': 'studio',
                'container_view': is_container_view,
                'read_only': is_read_only,
                'root_xblock': xblock if
                (view_name == 'container_preview') else None,
                'reorderable_items': reorderable_items
            }

            fragment = get_preview_fragment(request, xblock, context)
            # For old-style pages (such as unit and static pages), wrap the preview with
            # the component div. Note that the container view recursively adds headers
            # into the preview fragment, so we don't want to add another header here.
            if not is_container_view:
                fragment.content = render_to_string(
                    'component.html', {
                        'xblock_context':
                        context,
                        'preview':
                        fragment.content,
                        'label':
                        xblock.display_name or xblock.scope_ids.block_type,
                    })
        else:
            raise Http404

        hashed_resources = OrderedDict()
        for resource in fragment.resources:
            hashed_resources[hash_resource(resource)] = resource

        return JsonResponse({
            'html': fragment.content,
            'resources': hashed_resources.items()
        })

    else:
        return HttpResponse(status=406)
Ejemplo n.º 45
0
def get_course_about_section(course, section_key):
    """
    This returns the snippet of html to be rendered on the course about page,
    given the key for the section.

    Valid keys:
    - overview
    - title
    - university
    - number
    - short_description
    - description
    - key_dates (includes start, end, exams, etc)
    - video
    - course_staff_short
    - course_staff_extended
    - requirements
    - syllabus
    - textbook
    - faq
    - more_info
    - ocw_links
    """

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

    # TODO: Remove number, instructors from this list
    if section_key in ['short_description', 'description', 'key_dates', 'video',
                       'course_staff_short', 'course_staff_extended',
                       'requirements', 'syllabus', 'textbook', 'faq', 'more_info',
                       'number', 'instructors', 'overview',
                       'effort', 'end_date', 'prerequisites', 'ocw_links']:

        try:

            request = get_request_for_thread()

            loc = course.location.replace(category='about', name=section_key)

            # Use an empty cache
            field_data_cache = FieldDataCache([], course.id, request.user)
            about_module = get_module(
                request.user,
                request,
                loc,
                field_data_cache,
                log_if_not_found=False,
                wrap_xmodule_display=False,
                static_asset_path=course.static_asset_path
            )

            html = ''

            if about_module is not None:
                try:
                    html = about_module.render(STUDENT_VIEW).content
                except Exception:  # pylint: disable=broad-except
                    html = render_to_string('courseware/error-message.html', None)
                    log.exception(
                        u"Error rendering course={course}, section_key={section_key}".format(
                            course=course, section_key=section_key
                        ))
            return html

        except ItemNotFoundError:
            log.warning(
                u"Missing about section {key} in course {url}".format(key=section_key, url=course.location.to_deprecated_string())
            )
            return None
    elif section_key == "title":
        return course.display_name_with_default
    elif section_key == "university":
        return course.display_org_with_default
    elif section_key == "number":
        return course.display_number_with_default

    raise KeyError("Invalid about key " + str(section_key))
Ejemplo n.º 46
0
def render_500(request):
    return HttpResponseServerError(render_to_string('500.html', {}))
Ejemplo n.º 47
0
def render_from_lms(template_name, dictionary, context=None, namespace='main'):
    """
    Render a template using the LMS MAKO_TEMPLATES
    """
    return render_to_string(template_name, dictionary, context, namespace="lms." + namespace)
Ejemplo n.º 48
0
def send_credit_notifications(username, course_key):
    """Sends email notification to user on different phases during credit
    course e.g., credit eligibility, credit payment etc.
    """
    try:
        user = User.objects.get(username=username)
    except User.DoesNotExist:
        log.error('No user with %s exist', username)
        return

    course = modulestore().get_course(course_key, depth=0)
    course_display_name = course.display_name
    tracking_context = tracker.get_tracker().resolve_context()
    tracking_id = str(tracking_context.get('user_id'))
    client_id = str(tracking_context.get('client_id'))
    events = '&t=event&ec=email&ea=open'
    tracking_pixel = 'https://www.google-analytics.com/collect?v=1&tid' + tracking_id + '&cid' + client_id + events
    dashboard_link = _email_url_parser('dashboard')
    credit_course_link = _email_url_parser('courses', '?type=credit')

    # get attached branded logo
    logo_image = cache.get('credit.email.attached-logo')
    if logo_image is None:
        branded_logo = {
            'title': 'Logo',
            'path': settings.NOTIFICATION_EMAIL_EDX_LOGO,
            'cid': str(uuid.uuid4())
        }
        logo_image_id = branded_logo['cid']
        logo_image = attach_image(branded_logo, 'Header Logo')
        if logo_image:
            cache.set('credit.email.attached-logo', logo_image,
                      settings.CREDIT_NOTIFICATION_CACHE_TIMEOUT)
    else:
        # strip enclosing angle brackets from 'logo_image' cache 'Content-ID'
        logo_image_id = logo_image.get('Content-ID', '')[1:-1]

    context = {
        'full_name': user.get_full_name(),
        'platform_name': settings.PLATFORM_NAME,
        'course_name': course_display_name,
        'branded_logo': logo_image_id,
        'dashboard_link': dashboard_link,
        'credit_course_link': credit_course_link,
        'tracking_pixel': tracking_pixel,
    }

    # create the root email message
    notification_msg = MIMEMultipart('related')
    # add 'alternative' part to root email message to encapsulate the plain and
    # HTML versions, so message agents can decide which they want to display.
    msg_alternative = MIMEMultipart('alternative')
    notification_msg.attach(msg_alternative)
    # render the credit notification templates
    subject = _(u'Course Credit Eligibility')

    # add alternative plain text message
    email_body_plain = render_to_string(
        'credit_notifications/credit_eligibility_email.txt', context)
    msg_alternative.attach(
        MIMEText(email_body_plain, _subtype='plain', _charset='utf-8'))

    # add alternative html message
    email_body_content = cache.get('credit.email.css-email-body')
    if email_body_content is None:
        html_file_path = file_path_finder(
            'templates/credit_notifications/credit_eligibility_email.html')
        if html_file_path:
            with open(html_file_path, 'r') as cur_file:
                cur_text = cur_file.read()
                # use html parser to unescape html characters which are changed
                # by the 'pynliner' while adding inline css to html content
                html_parser = HTMLParser.HTMLParser()
                email_body_content = html_parser.unescape(
                    with_inline_css(cur_text))
                # cache the email body content before rendering it since the
                # email context will change for each user e.g., 'full_name'
                cache.set('credit.email.css-email-body', email_body_content,
                          settings.CREDIT_NOTIFICATION_CACHE_TIMEOUT)
        else:
            email_body_content = ''

    email_body = Template(email_body_content).render([context])
    msg_alternative.attach(
        MIMEText(email_body, _subtype='html', _charset='utf-8'))

    # attach logo image
    if logo_image:
        notification_msg.attach(logo_image)

    # add email addresses of sender and receiver
    from_address = microsite.get_value('default_from_email',
                                       settings.DEFAULT_FROM_EMAIL)
    to_address = user.email

    # send the root email message
    msg = EmailMessage(subject, None, from_address, [to_address])
    msg.attach(notification_msg)
    msg.send()
Ejemplo n.º 49
0
def wrap_xblock_aside(
        runtime_class,
        aside,
        view,
        frag,
        context,  # pylint: disable=unused-argument
        usage_id_serializer,
        request_token,  # pylint: disable=redefined-outer-name
        extra_data=None):
    """
    Wraps the results of rendering an XBlockAside view in a standard <section> with identifying
    data so that the appropriate javascript module can be loaded onto it.

    :param runtime_class: The name of the javascript runtime class to use to load this block
    :param aside: An XBlockAside
    :param view: The name of the view that rendered the fragment being wrapped
    :param frag: The :class:`Fragment` to be wrapped
    :param context: The context passed to the view being rendered
    :param usage_id_serializer: A function to serialize the block's usage_id for use by the
        front-end Javascript Runtime.
    :param request_token: An identifier that is unique per-request, so that only xblocks
        rendered as part of this request will have their javascript initialized.
    :param extra_data: A dictionary with extra data values to be set on the wrapper
    """

    if extra_data is None:
        extra_data = {}

    data = {}
    data.update(extra_data)

    css_classes = [
        'xblock-{}'.format(markupsafe.escape(view)), 'xblock-{}-{}'.format(
            markupsafe.escape(view),
            markupsafe.escape(aside.scope_ids.block_type),
        ), 'xblock_asides-v1'
    ]

    if frag.js_init_fn:
        data['init'] = frag.js_init_fn
        data['runtime-class'] = runtime_class
        data['runtime-version'] = frag.js_init_version

    data['block-type'] = aside.scope_ids.block_type
    data['usage-id'] = usage_id_serializer(aside.scope_ids.usage_id)
    data['request-token'] = request_token

    template_context = {
        'content':
        frag.content,
        'classes':
        css_classes,
        'data_attributes':
        u' '.join(u'data-{}="{}"'.format(markupsafe.escape(key),
                                         markupsafe.escape(value))
                  for key, value in data.iteritems()),
    }

    if hasattr(frag, 'json_init_args') and frag.json_init_args is not None:
        # Replace / with \/ so that "</script>" in the data won't break things.
        template_context['js_init_parameters'] = json.dumps(
            frag.json_init_args).replace("/", r"\/")
    else:
        template_context['js_init_parameters'] = ""

    return wrap_fragment(
        frag, render_to_string('xblock_wrapper.html', template_context))
Ejemplo n.º 50
0
def student_dashboard(request):
    """
    Provides the LMS dashboard view

    TODO: This is lms specific and does not belong in common code.

    Arguments:
        request: The request object.

    Returns:
        The dashboard response.

    """
    user = request.user
    if not UserProfile.objects.filter(user=user).exists():
        return redirect(reverse('account_settings'))

    platform_name = configuration_helpers.get_value("platform_name",
                                                    settings.PLATFORM_NAME)

    enable_verified_certificates = configuration_helpers.get_value(
        'ENABLE_VERIFIED_CERTIFICATES',
        settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES'))
    display_course_modes_on_dashboard = configuration_helpers.get_value(
        'DISPLAY_COURSE_MODES_ON_DASHBOARD',
        settings.FEATURES.get('DISPLAY_COURSE_MODES_ON_DASHBOARD', True))
    activation_email_support_link = configuration_helpers.get_value(
        'ACTIVATION_EMAIL_SUPPORT_LINK',
        settings.ACTIVATION_EMAIL_SUPPORT_LINK) or settings.SUPPORT_SITE_LINK

    # Get the org whitelist or the org blacklist for the current site
    site_org_whitelist, site_org_blacklist = get_org_black_and_whitelist_for_site(
    )
    course_enrollments = list(
        get_course_enrollments(user, site_org_whitelist, site_org_blacklist))

    # Get the entitlements for the user and a mapping to all available sessions for that entitlement
    # If an entitlement has no available sessions, pass through a mock course overview object
    (course_entitlements, course_entitlement_available_sessions,
     unfulfilled_entitlement_pseudo_sessions
     ) = get_filtered_course_entitlements(user, site_org_whitelist,
                                          site_org_blacklist)

    # Record how many courses there are so that we can get a better
    # understanding of usage patterns on prod.
    monitoring_utils.accumulate('num_courses', len(course_enrollments))

    # Sort the enrollment pairs by the enrollment date
    course_enrollments.sort(key=lambda x: x.created, reverse=True)

    # Retrieve the course modes for each course
    enrolled_course_ids = [
        enrollment.course_id for enrollment in course_enrollments
    ]
    __, unexpired_course_modes = CourseMode.all_and_unexpired_modes_for_courses(
        enrolled_course_ids)
    course_modes_by_course = {
        course_id: {mode.slug: mode
                    for mode in modes}
        for course_id, modes in iteritems(unexpired_course_modes)
    }

    # Check to see if the student has recently enrolled in a course.
    # If so, display a notification message confirming the enrollment.
    enrollment_message = _create_recent_enrollment_message(
        course_enrollments, course_modes_by_course)
    course_optouts = Optout.objects.filter(user=user).values_list('course_id',
                                                                  flat=True)

    sidebar_account_activation_message = ''
    banner_account_activation_message = ''
    display_account_activation_message_on_sidebar = configuration_helpers.get_value(
        'DISPLAY_ACCOUNT_ACTIVATION_MESSAGE_ON_SIDEBAR',
        settings.FEATURES.get('DISPLAY_ACCOUNT_ACTIVATION_MESSAGE_ON_SIDEBAR',
                              False))

    # Display activation message in sidebar if DISPLAY_ACCOUNT_ACTIVATION_MESSAGE_ON_SIDEBAR
    # flag is active. Otherwise display existing message at the top.
    if display_account_activation_message_on_sidebar and not user.is_active:
        sidebar_account_activation_message = render_to_string(
            'registration/account_activation_sidebar_notice.html', {
                'email': user.email,
                'platform_name': platform_name,
                'activation_email_support_link': activation_email_support_link
            })
    elif not user.is_active:
        banner_account_activation_message = render_to_string(
            'registration/activate_account_notice.html', {'email': user.email})

    enterprise_message = get_dashboard_consent_notification(
        request, user, course_enrollments)

    # Disable lookup of Enterprise consent_required_course due to ENT-727
    # Will re-enable after fixing WL-1315
    consent_required_courses = set()
    enterprise_customer_name = None

    # Account activation message
    account_activation_messages = [
        message for message in messages.get_messages(request)
        if 'account-activation' in message.tags
    ]

    # Global staff can see what courses encountered an error on their dashboard
    staff_access = False
    errored_courses = {}
    if has_access(user, 'staff', 'global'):
        # Show any courses that encountered an error on load
        staff_access = True
        errored_courses = modulestore().get_errored_courses()

    show_courseware_links_for = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if has_access(request.user, 'load', enrollment.course_overview))

    # Find programs associated with course runs being displayed. This information
    # is passed in the template context to allow rendering of program-related
    # information on the dashboard.
    meter = ProgramProgressMeter(request.site,
                                 user,
                                 enrollments=course_enrollments)
    ecommerce_service = EcommerceService()
    inverted_programs = meter.invert_programs()

    urls, programs_data = {}, {}
    bundles_on_dashboard_flag = WaffleFlag(
        WaffleFlagNamespace(name=u'student.experiments'),
        u'bundles_on_dashboard')

    # TODO: Delete this code and the relevant HTML code after testing LEARNER-3072 is complete
    if bundles_on_dashboard_flag.is_enabled(
    ) and inverted_programs and inverted_programs.items():
        if len(course_enrollments) < 4:
            for program in inverted_programs.values():
                try:
                    program_uuid = program[0]['uuid']
                    program_data = get_programs(request.site,
                                                uuid=program_uuid)
                    program_data = ProgramDataExtender(program_data,
                                                       request.user).extend()
                    skus = program_data.get('skus')
                    checkout_page_url = ecommerce_service.get_checkout_page_url(
                        *skus)
                    program_data[
                        'completeProgramURL'] = checkout_page_url + '&bundle=' + program_data.get(
                            'uuid')
                    programs_data[program_uuid] = program_data
                except:  # pylint: disable=bare-except
                    pass

    # Construct a dictionary of course mode information
    # used to render the course list.  We re-use the course modes dict
    # we loaded earlier to avoid hitting the database.
    course_mode_info = {
        enrollment.course_id: complete_course_mode_info(
            enrollment.course_id,
            enrollment,
            modes=course_modes_by_course[enrollment.course_id])
        for enrollment in course_enrollments
    }

    # Determine the per-course verification status
    # This is a dictionary in which the keys are course locators
    # and the values are one of:
    #
    # VERIFY_STATUS_NEED_TO_VERIFY
    # VERIFY_STATUS_SUBMITTED
    # VERIFY_STATUS_APPROVED
    # VERIFY_STATUS_MISSED_DEADLINE
    #
    # Each of which correspond to a particular message to display
    # next to the course on the dashboard.
    #
    # If a course is not included in this dictionary,
    # there is no verification messaging to display.
    verify_status_by_course = check_verify_status_by_course(
        user, course_enrollments)
    cert_statuses = {
        enrollment.course_id: cert_info(request.user,
                                        enrollment.course_overview)
        for enrollment in course_enrollments
    }

    # only show email settings for Mongo course and when bulk email is turned on
    show_email_settings_for = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if (BulkEmailFlag.feature_enabled(enrollment.course_id)))

    # Verification Attempts
    # Used to generate the "you must reverify for course x" banner
    verification_status, verification_error_codes = SoftwareSecurePhotoVerification.user_status(
        user)
    verification_errors = get_verification_error_reasons_for_display(
        verification_error_codes)

    # Gets data for midcourse reverifications, if any are necessary or have failed
    statuses = ["approved", "denied", "pending", "must_reverify"]
    reverifications = reverification_info(statuses)

    block_courses = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if is_course_blocked(
            request,
            CourseRegistrationCode.objects.filter(
                course_id=enrollment.course_id,
                registrationcoderedemption__redeemed_by=request.user),
            enrollment.course_id))

    enrolled_courses_either_paid = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if enrollment.is_paid_course())

    # If there are *any* denied reverifications that have not been toggled off,
    # we'll display the banner
    denied_banner = any(item.display for item in reverifications["denied"])

    # Populate the Order History for the side-bar.
    order_history_list = order_history(user,
                                       course_org_filter=site_org_whitelist,
                                       org_filter_out_set=site_org_blacklist)

    # get list of courses having pre-requisites yet to be completed
    courses_having_prerequisites = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if enrollment.course_overview.pre_requisite_courses)
    courses_requirements_not_met = get_pre_requisite_courses_not_completed(
        user, courses_having_prerequisites)

    if 'notlive' in request.GET:
        redirect_message = _(
            "The course you are looking for does not start until {date}."
        ).format(date=request.GET['notlive'])
    elif 'course_closed' in request.GET:
        redirect_message = _(
            "The course you are looking for is closed for enrollment as of {date}."
        ).format(date=request.GET['course_closed'])
    else:
        redirect_message = ''

    valid_verification_statuses = [
        'approved', 'must_reverify', 'pending', 'expired'
    ]
    display_sidebar_on_dashboard = len(
        order_history_list
    ) or verification_status in valid_verification_statuses

    # Filter out any course enrollment course cards that are associated with fulfilled entitlements
    for entitlement in [
            e for e in course_entitlements
            if e.enrollment_course_run is not None
    ]:
        course_enrollments = [
            enr for enr in course_enrollments
            if entitlement.enrollment_course_run.course_id != enr.course_id
        ]

    context = {
        'urls': urls,
        'programs_data': programs_data,
        'enterprise_message': enterprise_message,
        'consent_required_courses': consent_required_courses,
        'enterprise_customer_name': enterprise_customer_name,
        'enrollment_message': enrollment_message,
        'redirect_message': redirect_message,
        'account_activation_messages': account_activation_messages,
        'course_enrollments': course_enrollments,
        'course_entitlements': course_entitlements,
        'course_entitlement_available_sessions':
        course_entitlement_available_sessions,
        'unfulfilled_entitlement_pseudo_sessions':
        unfulfilled_entitlement_pseudo_sessions,
        'course_optouts': course_optouts,
        'banner_account_activation_message': banner_account_activation_message,
        'sidebar_account_activation_message':
        sidebar_account_activation_message,
        'staff_access': staff_access,
        'errored_courses': errored_courses,
        'show_courseware_links_for': show_courseware_links_for,
        'all_course_modes': course_mode_info,
        'cert_statuses': cert_statuses,
        'credit_statuses': _credit_statuses(user, course_enrollments),
        'show_email_settings_for': show_email_settings_for,
        'reverifications': reverifications,
        'verification_status': verification_status,
        'verification_status_by_course': verify_status_by_course,
        'verification_errors': verification_errors,
        'block_courses': block_courses,
        'denied_banner': denied_banner,
        'billing_email': settings.PAYMENT_SUPPORT_EMAIL,
        'user': user,
        'logout_url': reverse('logout'),
        'platform_name': platform_name,
        'enrolled_courses_either_paid': enrolled_courses_either_paid,
        'provider_states': [],
        'order_history_list': order_history_list,
        'courses_requirements_not_met': courses_requirements_not_met,
        'nav_hidden': True,
        'inverted_programs': inverted_programs,
        'show_program_listing': ProgramsApiConfig.is_enabled(),
        'show_dashboard_tabs': True,
        'disable_courseware_js': True,
        'display_course_modes_on_dashboard': enable_verified_certificates
        and display_course_modes_on_dashboard,
        'display_sidebar_on_dashboard': display_sidebar_on_dashboard,
    }

    if ecommerce_service.is_enabled(request.user):
        context.update({
            'use_ecommerce_payment_flow':
            True,
            'ecommerce_payment_page':
            ecommerce_service.payment_page_url(),
        })

    # Gather urls for course card resume buttons.
    resume_button_urls = _get_urls_for_resume_buttons(user, course_enrollments)
    # There must be enough urls for dashboard.html. Template creates course
    # cards for "enrollments + entitlements".
    resume_button_urls += ['' for entitlement in course_entitlements]
    context.update({'resume_button_urls': resume_button_urls})

    response = render_to_response('dashboard.html', context)
    set_user_info_cookie(response, request)
    return response
Ejemplo n.º 51
0
def submit_photos_for_verification(request):
    """Submit a photo verification attempt.

    Arguments:
        request (HttpRequest): The request to submit photos.

    Returns:
        HttpResponse: 200 on success, 400 if there are errors.

    """
    # Check the required parameters
    missing_params = set(['face_image', 'photo_id_image']) - set(
        request.POST.keys())
    if len(missing_params) > 0:
        msg = _("Missing required parameters: {missing}").format(
            missing=", ".join(missing_params))
        return HttpResponseBadRequest(msg)

    # If the user already has valid or pending request, the UI will hide
    # the verification steps.  For this reason, we reject any requests
    # for users that already have a valid or pending verification.
    if SoftwareSecurePhotoVerification.user_has_valid_or_pending(request.user):
        return HttpResponseBadRequest(
            _("You already have a valid or pending verification."))

    username = request.user.username

    # If the user wants to change his/her full name,
    # then try to do that before creating the attempt.
    if request.POST.get('full_name'):
        try:
            profile_api.update_profile(username,
                                       full_name=request.POST.get('full_name'))
        except profile_api.ProfileUserNotFound:
            return HttpResponseBadRequest(_("No profile found for user"))
        except profile_api.ProfileInvalidField:
            msg = _("Name must be at least {min_length} characters long."
                    ).format(min_length=profile_api.FULL_NAME_MIN_LENGTH)
            return HttpResponseBadRequest(msg)

    # Create the attempt
    attempt = SoftwareSecurePhotoVerification(user=request.user)
    try:
        b64_face_image = request.POST['face_image'].split(",")[1]
        b64_photo_id_image = request.POST['photo_id_image'].split(",")[1]
    except IndexError:
        msg = _("Image data is not valid.")
        return HttpResponseBadRequest(msg)

    attempt.upload_face_image(b64_face_image.decode('base64'))
    attempt.upload_photo_id_image(b64_photo_id_image.decode('base64'))
    attempt.mark_ready()
    attempt.submit()

    profile_dict = profile_api.profile_info(username)
    if profile_dict:
        # Send a confirmation email to the user
        context = {
            'full_name': profile_dict.get('full_name'),
            'platform_name': settings.PLATFORM_NAME
        }

        subject = _("Verification photos received")
        message = render_to_string('emails/photo_submission_confirmation.txt',
                                   context)
        from_address = microsite.get_value('default_from_email',
                                           settings.DEFAULT_FROM_EMAIL)
        to_address = profile_dict.get('email')

        send_mail(subject,
                  message,
                  from_address, [to_address],
                  fail_silently=False)

    return HttpResponse(200)
Ejemplo n.º 52
0
def get_course_about_section(request, course, section_key):
    """
    This returns the snippet of html to be rendered on the course about page,
    given the key for the section.

    Valid keys:
    - overview
    - about_sidebar_html
    - short_description
    - description
    - key_dates (includes start, end, exams, etc)
    - video
    - course_staff_short
    - course_staff_extended
    - requirements
    - syllabus
    - textbook
    - faq
    - effort
    - more_info
    - ocw_links
    """

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

    html_sections = {
        'short_description',
        'description',
        'key_dates',
        'video',
        'course_staff_short',
        'course_staff_extended',
        'requirements',
        'syllabus',
        'textbook',
        'faq',
        'more_info',
        'overview',
        'effort',
        'end_date',
        'prerequisites',
        'about_sidebar_html',
        'ocw_links'
    }

    if section_key in html_sections:
        try:
            loc = course.location.replace(category='about', name=section_key)

            # Use an empty cache
            field_data_cache = FieldDataCache([], course.id, request.user)
            about_module = get_module(
                request.user,
                request,
                loc,
                field_data_cache,
                log_if_not_found=False,
                wrap_xmodule_display=False,
                static_asset_path=course.static_asset_path,
                course=course
            )

            html = ''

            if about_module is not None:
                try:
                    html = about_module.render(STUDENT_VIEW).content
                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

        except ItemNotFoundError:
            log.warning(
                u"Missing about section %s in course %s",
                section_key, text_type(course.location)
            )
            return None

    raise KeyError("Invalid about key " + str(section_key))
Ejemplo n.º 53
0
def render_404(request):
    return HttpResponseNotFound(
        render_to_string('static_templates/404.html', {}, request=request))
Ejemplo n.º 54
0
def render_inline_js(package, js):
    context = package.extra_context
    context.update({'source': js})
    return render_to_string("mako/inline_js.html", context)
Ejemplo n.º 55
0
def render_500(request):
    return HttpResponseServerError(
        render_to_string('static_templates/server-error.html', {},
                         request=request))
Ejemplo n.º 56
0
def _index_bulk_op(request, user, course_key, chapter, section, position):
    course = get_course_with_access(user, 'load', course_key, depth=2)

    staff_access = has_access(user, 'staff', course)
    registered = registered_for_course(course, user)
    if not registered:
        # TODO (vshnayder): do course instructors need to be registered to see course?
        log.debug(u'User %s tried to view course %s but is not enrolled', user,
                  course.location.to_deprecated_string())
        return redirect(
            reverse('about_course', args=[course_key.to_deprecated_string()]))

    # check to see if there is a required survey that must be taken before
    # the user can access the course.
    if survey.utils.must_answer_survey(course, user):
        return redirect(reverse('course_survey', args=[unicode(course.id)]))

    masq = setup_masquerade(request, staff_access)

    try:
        field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
            course_key, user, course, depth=2)

        course_module = get_module_for_descriptor(user, request, course,
                                                  field_data_cache, course_key)
        if course_module is None:
            log.warning(
                u'If you see this, something went wrong: if we got this'
                u' far, should have gotten a course module for this user')
            return redirect(
                reverse('about_course',
                        args=[course_key.to_deprecated_string()]))

        studio_url = get_studio_url(course, 'course')

        context = {
            'csrf':
            csrf(request)['csrf_token'],
            'accordion':
            render_accordion(request, course, chapter, section,
                             field_data_cache),
            'COURSE_TITLE':
            course.display_name_with_default,
            'course':
            course,
            'init':
            '',
            'fragment':
            Fragment(),
            'staff_access':
            staff_access,
            'studio_url':
            studio_url,
            'masquerade':
            masq,
            'xqa_server':
            settings.FEATURES.get(
                'USE_XQA_SERVER',
                'http://*****:*****@content-qa.mitx.mit.edu/xqa'),
            'reverifications':
            fetch_reverify_banner_info(request, course_key),
        }

        now = datetime.now(UTC())
        effective_start = _adjust_start_date_for_beta_testers(
            user, course, course_key)
        if staff_access and now < effective_start:
            # Disable student view button if user is staff and
            # course is not yet visible to students.
            context['disable_student_access'] = True

        has_content = course.has_children_at_depth(CONTENT_DEPTH)
        if not has_content:
            # Show empty courseware for a course with no units
            return render_to_response('courseware/courseware.html', context)
        elif chapter is None:
            # passing CONTENT_DEPTH avoids returning 404 for a course with an
            # empty first section and a second section with content
            return redirect_to_course_position(course_module, CONTENT_DEPTH)

        # Only show the chat if it's enabled by the course and in the
        # settings.
        show_chat = course.show_chat and settings.FEATURES['ENABLE_CHAT']
        if show_chat:
            context['chat'] = chat_settings(course, user)
            # If we couldn't load the chat settings, then don't show
            # the widget in the courseware.
            if context['chat'] is None:
                show_chat = False

        context['show_chat'] = show_chat

        chapter_descriptor = course.get_child_by(
            lambda m: m.location.name == chapter)
        if chapter_descriptor is not None:
            save_child_position(course_module, chapter)
        else:
            raise Http404(
                'No chapter descriptor found with name {}'.format(chapter))

        chapter_module = course_module.get_child_by(
            lambda m: m.location.name == chapter)
        if chapter_module is None:
            # User may be trying to access a chapter that isn't live yet
            if masq == 'student':  # if staff is masquerading as student be kinder, don't 404
                log.debug('staff masq as student: no chapter %s' % chapter)
                return redirect(
                    reverse('courseware',
                            args=[course.id.to_deprecated_string()]))
            raise Http404

        if section is not None:
            section_descriptor = chapter_descriptor.get_child_by(
                lambda m: m.location.name == section)

            if section_descriptor is None:
                # Specifically asked-for section doesn't exist
                if masq == 'student':  # if staff is masquerading as student be kinder, don't 404
                    log.debug('staff masq as student: no section %s' % section)
                    return redirect(
                        reverse('courseware',
                                args=[course.id.to_deprecated_string()]))
                raise Http404

            ## Allow chromeless operation
            if section_descriptor.chrome:
                chrome = [
                    s.strip()
                    for s in section_descriptor.chrome.lower().split(",")
                ]
                if 'accordion' not in chrome:
                    context['disable_accordion'] = True
                if 'tabs' not in chrome:
                    context['disable_tabs'] = True

            if section_descriptor.default_tab:
                context['default_tab'] = section_descriptor.default_tab

            # cdodge: this looks silly, but let's refetch the section_descriptor with depth=None
            # which will prefetch the children more efficiently than doing a recursive load
            section_descriptor = modulestore().get_item(
                section_descriptor.location, depth=None)

            # Load all descendants of the section, because we're going to display its
            # html, which in general will need all of its children
            section_field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
                course_key, user, section_descriptor, depth=None)

            # Verify that position a string is in fact an int
            if position is not None:
                try:
                    int(position)
                except ValueError:
                    raise Http404(
                        "Position {} is not an integer!".format(position))

            section_module = get_module_for_descriptor(
                request.user, request, section_descriptor,
                section_field_data_cache, course_key, position)

            if section_module is None:
                # User may be trying to be clever and access something
                # they don't have access to.
                raise Http404

            # Save where we are in the chapter
            save_child_position(chapter_module, section)
            context['fragment'] = section_module.render(STUDENT_VIEW)
            context[
                'section_title'] = section_descriptor.display_name_with_default
        else:
            # section is none, so display a message
            studio_url = get_studio_url(course, 'course')
            prev_section = get_current_child(chapter_module)
            if prev_section is None:
                # Something went wrong -- perhaps this chapter has no sections visible to the user.
                # Clearing out the last-visited state and showing "first-time" view by redirecting
                # to courseware.
                course_module.position = None
                course_module.save()
                return redirect(
                    reverse('courseware',
                            args=[course.id.to_deprecated_string()]))
            prev_section_url = reverse('courseware_section',
                                       kwargs={
                                           'course_id':
                                           course_key.to_deprecated_string(),
                                           'chapter':
                                           chapter_descriptor.url_name,
                                           'section':
                                           prev_section.url_name
                                       })
            context['fragment'] = Fragment(content=render_to_string(
                'courseware/welcome-back.html', {
                    'course': course,
                    'studio_url': studio_url,
                    'chapter_module': chapter_module,
                    'prev_section': prev_section,
                    'prev_section_url': prev_section_url
                }))

        result = render_to_response('courseware/courseware.html', context)
    except Exception as e:

        # Doesn't bar Unicode characters from URL, but if Unicode characters do
        # cause an error it is a graceful failure.
        if isinstance(e, UnicodeEncodeError):
            raise Http404("URL contains Unicode characters")

        if isinstance(e, Http404):
            # let it propagate
            raise

        # In production, don't want to let a 500 out for any reason
        if settings.DEBUG:
            raise
        else:
            log.exception(
                u"Error in index view: user={user}, course={course}, chapter={chapter}"
                u" section={section} position={position}".format(
                    user=user,
                    course=course,
                    chapter=chapter,
                    section=section,
                    position=position))
            try:
                result = render_to_response('courseware/courseware-error.html',
                                            {
                                                'staff_access': staff_access,
                                                'course': course
                                            })
            except:
                # Let the exception propagate, relying on global config to at
                # at least return a nice error message
                log.exception("Error while rendering courseware-error page")
                raise

    return result
Ejemplo n.º 57
0
def wrap_xblock(runtime_class, block, view, frag, context, usage_id_serializer, request_token, display_name_only=False, extra_data=None):  # pylint: disable=unused-argument
    """
    Wraps the results of rendering an XBlock view in a standard <section> with identifying
    data so that the appropriate javascript module can be loaded onto it.

    :param runtime_class: The name of the javascript runtime class to use to load this block
    :param block: An XBlock (that may be an XModule or XModuleDescriptor)
    :param view: The name of the view that rendered the fragment being wrapped
    :param frag: The :class:`Fragment` to be wrapped
    :param context: The context passed to the view being rendered
    :param usage_id_serializer: A function to serialize the block's usage_id for use by the
        front-end Javascript Runtime.
    :param request_token: An identifier that is unique per-request, so that only xblocks
        rendered as part of this request will have their javascript initialized.
    :param display_name_only: If true, don't render the fragment content at all.
        Instead, just render the `display_name` of `block`
    :param extra_data: A dictionary with extra data values to be set on the wrapper
    """
    if extra_data is None:
        extra_data = {}

    # If any mixins have been applied, then use the unmixed class
    class_name = getattr(block, 'unmixed_class', block.__class__).__name__

    data = {}
    data.update(extra_data)

    css_classes = [
        'xblock',
        'xblock-{}'.format(markupsafe.escape(view)),
        'xblock-{}-{}'.format(
            markupsafe.escape(view),
            markupsafe.escape(block.scope_ids.block_type),
        )
    ]

    if isinstance(block, (XModule, XModuleDescriptor)):
        if view in PREVIEW_VIEWS:
            # The block is acting as an XModule
            css_classes.append('xmodule_display')
        elif view == STUDIO_VIEW:
            # The block is acting as an XModuleDescriptor
            css_classes.append('xmodule_edit')

        css_classes.append('xmodule_' + markupsafe.escape(class_name))
        data['type'] = block.js_module_name
        shim_xmodule_js(block, frag)

    if frag.js_init_fn:
        data['init'] = frag.js_init_fn
        data['runtime-class'] = runtime_class
        data['runtime-version'] = frag.js_init_version

    data['block-type'] = block.scope_ids.block_type
    data['usage-id'] = usage_id_serializer(block.scope_ids.usage_id)
    data['request-token'] = request_token

    if block.name:
        data['name'] = block.name

    template_context = {
        'content': block.display_name if display_name_only else frag.content,
        'classes': css_classes,
        'display_name': block.display_name_with_default,
        'data_attributes': u' '.join(u'data-{}="{}"'.format(markupsafe.escape(key), markupsafe.escape(value))
                                     for key, value in data.iteritems()),
    }

    if hasattr(frag, 'json_init_args') and frag.json_init_args is not None:
        # Replace / with \/ so that "</script>" in the data won't break things.
        template_context['js_init_parameters'] = json.dumps(frag.json_init_args).replace("/", r"\/")
    else:
        template_context['js_init_parameters'] = ""

    return wrap_fragment(frag, render_to_string('xblock_wrapper.html', template_context))
Ejemplo n.º 58
0
def xblock_view_handler(request, usage_key_string, view_name):
    """
    The restful handler for requests for rendered xblock views.

    Returns a json object containing two keys:
        html: The rendered html of the view
        resources: A list of tuples where the first element is the resource hash, and
            the second is the resource description
    """
    usage_key = usage_key_with_run(usage_key_string)
    if not has_course_access(request.user, usage_key.course_key):
        raise PermissionDenied()

    accept_header = request.META.get('HTTP_ACCEPT', 'application/json')

    if 'application/json' in accept_header:
        store = modulestore()
        xblock = store.get_item(usage_key)
        container_views = [
            'container_preview', 'reorderable_container_child_preview'
        ]

        # wrap the generated fragment in the xmodule_editor div so that the javascript
        # can bind to it correctly
        xblock.runtime.wrappers.append(
            partial(
                wrap_xblock,
                'StudioRuntime',
                usage_id_serializer=unicode,
                request_token=request_token(request),
            ))

        if view_name == STUDIO_VIEW:
            try:
                fragment = xblock.render(STUDIO_VIEW)
            # catch exceptions indiscriminately, since after this point they escape the
            # dungeon and surface as uneditable, unsaveable, and undeletable
            # component-goblins.
            except Exception as exc:  # pylint: disable=w0703
                log.debug("unable to render studio_view for %r",
                          xblock,
                          exc_info=True)
                fragment = Fragment(
                    render_to_string('html_error.html', {'message': str(exc)}))

            store.update_item(xblock, request.user.id)
        elif view_name in (PREVIEW_VIEWS + container_views):
            is_pages_view = view_name == STUDENT_VIEW  # Only the "Pages" view uses student view in Studio

            # Determine the items to be shown as reorderable. Note that the view
            # 'reorderable_container_child_preview' is only rendered for xblocks that
            # are being shown in a reorderable container, so the xblock is automatically
            # added to the list.
            reorderable_items = set()
            if view_name == 'reorderable_container_child_preview':
                reorderable_items.add(xblock.location)

            # Set up the context to be passed to each XBlock's render method.
            context = {
                'is_pages_view':
                is_pages_view,  # This setting disables the recursive wrapping of xblocks
                'is_unit_page': is_unit(xblock),
                'root_xblock': xblock if
                (view_name == 'container_preview') else None,
                'reorderable_items': reorderable_items
            }

            fragment = get_preview_fragment(request, xblock, context)

            # Note that the container view recursively adds headers into the preview fragment,
            # so only the "Pages" view requires that this extra wrapper be included.
            if is_pages_view:
                fragment.content = render_to_string(
                    'component.html', {
                        'xblock_context':
                        context,
                        'xblock':
                        xblock,
                        'locator':
                        usage_key,
                        'preview':
                        fragment.content,
                        'label':
                        xblock.display_name or xblock.scope_ids.block_type,
                    })
        else:
            raise Http404

        hashed_resources = OrderedDict()
        for resource in fragment.resources:
            hashed_resources[hash_resource(resource)] = resource

        return JsonResponse({
            'html': fragment.content,
            'resources': hashed_resources.items()
        })

    else:
        return HttpResponse(status=406)
Ejemplo n.º 59
0
def render_from_lms(template_name, dictionary, namespace='main'):
    """
    Render a template using the LMS Mako templates
    """
    return render_to_string(template_name, dictionary, namespace="lms." + namespace)
Ejemplo n.º 60
0
 def test_optional_include_mako(self):
     out = render_to_string("test_optional_include_mako.html", {})
     self.assertIn("Welcome to test_optional_include_mako.html", out)
     self.assertIn("This is test_exists.html", out)