Exemple #1
0
    def __init__(
        self,
        async_send_task,
        site,
        course_key,
        target_datetime,
        day_offset,
        override_recipient_email=None
    ):
        access_duration = MIN_DURATION
        discovery_course_details = get_course_run_details(course_key, ['weeks_to_complete'])
        expected_weeks = discovery_course_details.get('weeks_to_complete')
        if expected_weeks:
            access_duration = timedelta(weeks=expected_weeks)

        access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration))

        self.course_key = course_key

        super(ExpiryReminderResolver, self).__init__(
            async_send_task,
            site,
            target_datetime - access_duration,
            day_offset - access_duration.days,
            0,
            override_recipient_email,
        )
Exemple #2
0
def _get_catalog_data_for_course(course_key):
    """
    Retrieve data from the Discovery service necessary for rendering a certificate for a specific course.
    """
    course_certificate_settings = CertificateGenerationCourseSetting.get(
        course_key)
    if not course_certificate_settings:
        return {}

    catalog_data = {}
    course_run_fields = []
    if course_certificate_settings.language_specific_templates_enabled:
        course_run_fields.append('content_language')
    if course_certificate_settings.include_hours_of_effort:
        course_run_fields.extend(['weeks_to_complete', 'max_effort'])

    if course_run_fields:
        course_run_data = get_course_run_details(course_key, course_run_fields)
        if course_run_data.get('weeks_to_complete') and course_run_data.get(
                'max_effort'):
            try:
                weeks_to_complete = int(course_run_data['weeks_to_complete'])
                max_effort = int(course_run_data['max_effort'])
                catalog_data[
                    'hours_of_effort'] = weeks_to_complete * max_effort
            except ValueError:
                log.exception(
                    'Error occurred while parsing course run details')
        catalog_data['content_language'] = course_run_data.get(
            'content_language')
    log.info(
        "catalog data received for course:{course_key} is : {catalog_data}".
        format(course_key=course_key, catalog_data=catalog_data))
    return catalog_data
Exemple #3
0
def get_user_course_duration(user, course):
    """
    Return a timedelta measuring the duration of the course for a particular user.

    Business Logic:
      - Course access duration is bounded by the min and max duration.
      - If course fields are missing, default course access duration to MIN_DURATION.
    """

    access_duration = MIN_DURATION

    verified_mode = CourseMode.verified_mode_for_course(course=course,
                                                        include_expired=True)

    if not verified_mode:
        return None

    enrollment = CourseEnrollment.get_enrollment(user, course.id)
    if enrollment is None or enrollment.mode != CourseMode.AUDIT:
        return None

    # The user course expiration date is the content availability date
    # plus the weeks_to_complete field from course-discovery.
    discovery_course_details = get_course_run_details(course.id,
                                                      ['weeks_to_complete'])
    expected_weeks = discovery_course_details.get('weeks_to_complete')
    if expected_weeks:
        access_duration = timedelta(weeks=expected_weeks)

    # Course access duration is bounded by the min and max duration.
    access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration))

    return access_duration
Exemple #4
0
def _update_course_context(request, context, course, course_key, platform_name):
    """
    Updates context dictionary with course info.
    """
    context['full_course_image_url'] = request.build_absolute_uri(course_image_url(course))
    course_title_from_cert = context['certificate_data'].get('course_title', '')
    accomplishment_copy_course_name = course_title_from_cert if course_title_from_cert else course.display_name
    context['accomplishment_copy_course_name'] = accomplishment_copy_course_name
    course_number = course.display_coursenumber if course.display_coursenumber else course.number
    context['course_number'] = course_number
    if context['organization_long_name']:
        # Translators:  This text represents the description of course
        context['accomplishment_copy_course_description'] = _('a course of study offered by {partner_short_name}, '
                                                              'an online learning initiative of '
                                                              '{partner_long_name}.').format(
            partner_short_name=context['organization_short_name'],
            partner_long_name=context['organization_long_name'],
            platform_name=platform_name)
    else:
        # Translators:  This text represents the description of course
        context['accomplishment_copy_course_description'] = _('a course of study offered by '
                                                              '{partner_short_name}.').format(
            partner_short_name=context['organization_short_name'],
            platform_name=platform_name)
    # If language specific templates are enabled for the course, add course_run specific information to the context
    if CertificateGenerationCourseSetting.is_language_specific_templates_enabled_for_course(course_key):
        fields = ['start', 'end', 'max_effort', 'language']
        course_run_data = get_course_run_details(course_key, fields)
        context.update(course_run_data)
def _get_catalog_data_for_course(course_key):
    """
    Retrieve data from the Discovery service necessary for rendering a certificate for a specific course.
    """
    course_certificate_settings = CertificateGenerationCourseSetting.get(course_key)
    if not course_certificate_settings:
        return {}

    catalog_data = {}
    course_run_fields = []
    if course_certificate_settings.language_specific_templates_enabled:
        course_run_fields.append('content_language')
    if course_certificate_settings.include_hours_of_effort:
        course_run_fields.extend(['weeks_to_complete', 'max_effort'])

    if course_run_fields:
        course_run_data = get_course_run_details(course_key, course_run_fields)
        if course_run_data.get('weeks_to_complete') and course_run_data.get('max_effort'):
            try:
                weeks_to_complete = int(course_run_data['weeks_to_complete'])
                max_effort = int(course_run_data['max_effort'])
                catalog_data['hours_of_effort'] = weeks_to_complete * max_effort
            except ValueError:
                log.exception('Error occurred while parsing course run details')
        catalog_data['content_language'] = course_run_data.get('content_language')
    log.info(
        u"catalog data received for course: %s is : %s",
        course_key,
        catalog_data,
    )
    return catalog_data
Exemple #6
0
    def __init__(
        self,
        async_send_task,
        site,
        course_key,
        target_datetime,
        day_offset,
        override_recipient_email=None
    ):
        access_duration = MIN_DURATION
        discovery_course_details = get_course_run_details(course_key, ['weeks_to_complete'])
        expected_weeks = discovery_course_details.get('weeks_to_complete')
        if expected_weeks:
            access_duration = timedelta(weeks=expected_weeks)

        access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration))

        self.course_key = course_key

        super(ExpiryReminderResolver, self).__init__(
            async_send_task,
            site,
            target_datetime - access_duration,
            day_offset - access_duration.days,
            0,
            override_recipient_email,
        )
Exemple #7
0
def _update_context_with_catalog_data(context, course_key):
    """
    Updates context dictionary with relevant course run info from Discovery.
    """
    course_certificate_settings = CertificateGenerationCourseSetting.get(
        course_key)
    if course_certificate_settings:
        course_run_fields = []
        if course_certificate_settings.language_specific_templates_enabled:
            course_run_fields.append('content_language')
        if course_certificate_settings.include_hours_of_effort:
            course_run_fields.extend(['weeks_to_complete', 'max_effort'])
        if course_run_fields:
            course_run_data = get_course_run_details(course_key,
                                                     course_run_fields)
            if course_run_data.get(
                    'weeks_to_complete') and course_run_data.get('max_effort'):
                try:
                    weeks_to_complete = int(
                        course_run_data['weeks_to_complete'])
                    max_effort = int(course_run_data['max_effort'])
                    context['hours_of_effort'] = weeks_to_complete * max_effort
                except ValueError:
                    log.exception(
                        'Error occurred while parsing course run details')
            context['content_language'] = course_run_data.get(
                'content_language')
Exemple #8
0
 def _get_pseudo_course_overview(self, course_key):
     """
     Returns a pseudo course overview object for deleted courses.
     """
     course_run = get_course_run_details(course_key, ['title'])
     return CourseOverview(display_name=course_run.get('title'),
                           display_org_with_default=course_key.org,
                           certificates_show_before_end=True)
    def date(self):
        if self.course.self_paced and RELATIVE_DATES_FLAG.is_enabled(self.course_id):
            weeks_to_complete = get_course_run_details(self.course.id, ['weeks_to_complete']).get('weeks_to_complete')
            if weeks_to_complete:
                course_duration = datetime.timedelta(weeks=weeks_to_complete)
                if self.course.end < (self.current_time + course_duration):
                    return self.course.end
                return None

        return self.course.end
Exemple #10
0
 def test_get_course_run_details(self, mock_get_edx_api_data):
     """
     Test retrieval of details about a specific course run
     """
     course_run = CourseRunFactory()
     course_run_details = {
         'content_language': course_run['content_language'],
         'weeks_to_complete': course_run['weeks_to_complete'],
         'max_effort': course_run['max_effort']
     }
     mock_get_edx_api_data.return_value = course_run_details
     data = get_course_run_details(course_run['key'], ['content_language', 'weeks_to_complete', 'max_effort'])
     self.assertTrue(mock_get_edx_api_data.called)
     self.assertEqual(data, course_run_details)
 def test_get_course_run_details(self, mock_get_edx_api_data):
     """
     Test retrieval of details about a specific course run
     """
     course_run = CourseRunFactory()
     course_run_details = {
         'content_language': course_run['content_language'],
         'weeks_to_complete': course_run['weeks_to_complete'],
         'max_effort': course_run['max_effort']
     }
     mock_get_edx_api_data.return_value = course_run_details
     data = get_course_run_details(course_run['key'], ['content_language', 'weeks_to_complete', 'max_effort'])
     self.assertTrue(mock_get_edx_api_data.called)
     self.assertEqual(data, course_run_details)
Exemple #12
0
def get_user_course_expiration_date(user, course):
    """
    Return expiration date for given user course pair.
    Return None if the course does not expire.

    Business Logic:
      - Course access duration is bounded by the min and max duration.
      - If course fields are missing, default course access duration to MIN_DURATION.
    """
    access_duration = MIN_DURATION

    verified_mode = CourseMode.verified_mode_for_course(course=course,
                                                        include_expired=True)

    if not verified_mode:
        return None

    enrollment = CourseEnrollment.get_enrollment(user, course.id)
    if enrollment is None or enrollment.mode != CourseMode.AUDIT:
        return None

    try:
        # Content availability date is equivalent to max(enrollment date, course start date)
        # for most people. Using the schedule date will provide flexibility to deal with
        # more complex business rules in the future.
        content_availability_date = enrollment.schedule.start
        # We have anecdotally observed a case where the schedule.start was
        # equal to the course start, but should have been equal to the enrollment start
        # https://openedx.atlassian.net/browse/PROD-58
        # This section is meant to address that case
        if enrollment.created and course.start:
            if (content_availability_date.date() == course.start.date()
                    and course.start < enrollment.created < timezone.now()):
                content_availability_date = enrollment.created
    except CourseEnrollment.schedule.RelatedObjectDoesNotExist:
        content_availability_date = max(enrollment.created, course.start)

    # The user course expiration date is the content availability date
    # plus the weeks_to_complete field from course-discovery.
    discovery_course_details = get_course_run_details(course.id,
                                                      ['weeks_to_complete'])
    expected_weeks = discovery_course_details.get('weeks_to_complete')
    if expected_weeks:
        access_duration = timedelta(weeks=expected_weeks)

    # Course access duration is bounded by the min and max duration.
    access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration))

    return content_availability_date + access_duration
Exemple #13
0
def _update_course_context(request, context, course, course_key,
                           platform_name):
    """
    Updates context dictionary with course info.
    """
    context['full_course_image_url'] = request.build_absolute_uri(
        course_image_url(course))
    course_title_from_cert = context['certificate_data'].get(
        'course_title', '')
    accomplishment_copy_course_name = course_title_from_cert if course_title_from_cert else course.display_name
    context[
        'accomplishment_copy_course_name'] = accomplishment_copy_course_name
    course_number = course.display_coursenumber if course.display_coursenumber else course.number
    context['course_number'] = course_number
    if context['organization_long_name']:
        # Translators:  This text represents the description of course
        context['accomplishment_copy_course_description'] = _(
            'a course of study offered by {partner_short_name}, '
            'an online learning initiative of '
            '{partner_long_name}.').format(
                partner_short_name=context['organization_short_name'],
                partner_long_name=context['organization_long_name'],
                platform_name=platform_name)
    else:
        # Translators:  This text represents the description of course
        context['accomplishment_copy_course_description'] = _(
            'a course of study offered by '
            '{partner_short_name}.').format(
                partner_short_name=context['organization_short_name'],
                platform_name=platform_name)
    # If language specific templates are enabled for the course, add course_run specific information to the context
    if CertificateGenerationCourseSetting.is_language_specific_templates_enabled_for_course(
            course_key):
        fields = ['start', 'end', 'max_effort', 'content_language']
        course_run_data = get_course_run_details(course_key, fields)
        if course_run_data['start'] and course_run_data[
                'end'] and course_run_data['max_effort']:
            # Calculate duration of the course run in weeks, multiplied by max_effort for total Hours of Effort
            try:
                start = parser.parse(course_run_data['start'])
                end = parser.parse(course_run_data['end'])
                max_effort = int(course_run_data['max_effort'])
                context['hours_of_effort'] = (
                    (end - start).days / 7) * max_effort
            except ValueError:
                log.exception(
                    'Error occurred while parsing course run details')
        context['content_language'] = course_run_data['content_language']
Exemple #14
0
def get_user_course_expiration_date(user, course):
    """
    Return expiration date for given user course pair.
    Return None if the course does not expire.

    Business Logic:
      - Course access duration is bounded by the min and max duration.
      - If course fields are missing, default course access duration to MIN_DURATION.
    """
    access_duration = MIN_DURATION

    verified_mode = CourseMode.verified_mode_for_course(course=course, include_expired=True)

    if not verified_mode:
        return None

    enrollment = CourseEnrollment.get_enrollment(user, course.id)
    if enrollment is None or enrollment.mode != CourseMode.AUDIT:
        return None

    try:
        # Content availability date is equivalent to max(enrollment date, course start date)
        # for most people. Using the schedule date will provide flexibility to deal with
        # more complex business rules in the future.
        content_availability_date = enrollment.schedule.start
        # We have anecdotally observed a case where the schedule.start was
        # equal to the course start, but should have been equal to the enrollment start
        # https://openedx.atlassian.net/browse/PROD-58
        # This section is meant to address that case
        if enrollment.created and course.start:
            if (content_availability_date.date() == course.start.date() and
               course.start < enrollment.created < timezone.now()):
                content_availability_date = enrollment.created
    except CourseEnrollment.schedule.RelatedObjectDoesNotExist:
        content_availability_date = max(enrollment.created, course.start)

    # The user course expiration date is the content availability date
    # plus the weeks_to_complete field from course-discovery.
    discovery_course_details = get_course_run_details(course.id, ['weeks_to_complete'])
    expected_weeks = discovery_course_details.get('weeks_to_complete')
    if expected_weeks:
        access_duration = timedelta(weeks=expected_weeks)

    # Course access duration is bounded by the min and max duration.
    access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration))

    return content_availability_date + access_duration
Exemple #15
0
def get_user_course_expiration_date(user, course):
    """
    Return expiration date for given user course pair.
    Return None if the course does not expire.

    Business Logic:
      - Course access duration is bounded by the min and max duration.
      - If course fields are missing, default course access duration to MIN_DURATION.
    """

    access_duration = MIN_DURATION

    if not CourseMode.verified_mode_for_course(course.id):
        return None

    CourseEnrollment = apps.get_model('student.CourseEnrollment')
    enrollment = CourseEnrollment.get_enrollment(user, course.id)
    if enrollment is None or enrollment.mode != 'audit':
        return None

    # if the user is a beta tester their access should not expire
    if CourseBetaTesterRole(course.id).has_user(user):
        return None

    try:
        # Content availability date is equivalent to max(enrollment date, course start date)
        # for most people. Using the schedule date will provide flexibility to deal with
        # more complex business rules in the future.
        content_availability_date = enrollment.schedule.start
    except CourseEnrollment.schedule.RelatedObjectDoesNotExist:
        content_availability_date = max(enrollment.created, course.start)

    # The user course expiration date is the content availability date
    # plus the weeks_to_complete field from course-discovery.
    discovery_course_details = get_course_run_details(course.id,
                                                      ['weeks_to_complete'])
    expected_weeks = discovery_course_details.get('weeks_to_complete')
    if expected_weeks:
        access_duration = timedelta(weeks=expected_weeks)

    # Course access duration is bounded by the min and max duration.
    access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration))

    return content_availability_date + access_duration
Exemple #16
0
def get_expected_duration(course_id):
    """
    Return a `datetime.timedelta` defining the expected length of the supplied course.
    """

    access_duration = MIN_DURATION

    # The user course expiration date is the content availability date
    # plus the weeks_to_complete field from course-discovery.
    discovery_course_details = get_course_run_details(course_id,
                                                      ['weeks_to_complete'])
    expected_weeks = discovery_course_details.get('weeks_to_complete')
    if expected_weeks:
        access_duration = timedelta(weeks=expected_weeks)

    # Course access duration is bounded by the min and max duration.
    access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration))

    return access_duration
Exemple #17
0
def get_user_course_expiration_date(user, course):
    """
    Return expiration date for given user course pair.
    Return None if the course does not expire.

    Business Logic:
      - Course access duration is bounded by the min and max duration.
      - If course fields are missing, default course access duration to MIN_DURATION.
    """

    access_duration = MIN_DURATION

    CourseEnrollment = apps.get_model('student.CourseEnrollment')
    enrollment = CourseEnrollment.get_enrollment(user, course.id)
    if enrollment is None or enrollment.mode != 'audit':
        return None

    try:
        # Content availability date is equivalent to max(enrollment date, course start date)
        # for most people. Using the schedule date will provide flexibility to deal with
        # more complex business rules in the future.
        content_availability_date = enrollment.schedule.start
    except CourseEnrollment.schedule.RelatedObjectDoesNotExist:
        content_availability_date = max(enrollment.created, course.start)

    if course.self_paced:
        # The user course expiration date for self paced courses is the
        # content availability date plus the weeks_to_complete field from course-discovery.
        discovery_course_details = get_course_run_details(
            course.id, ['weeks_to_complete'])
        expected_weeks = discovery_course_details['weeks_to_complete'] or int(
            MIN_DURATION.days / 7)
        access_duration = timedelta(weeks=expected_weeks)
    elif not course.self_paced and course.end and course.start:
        # The user course expiration date for instructor paced courses is the
        # content availability date plus the duration of the course (course end date minus course start date).
        access_duration = course.end - course.start

    # Course access duration is bounded by the min and max duration.
    access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration))

    return content_availability_date + access_duration
Exemple #18
0
def _update_context_with_catalog_data(context, course_key):
    """
    Updates context dictionary with relevant course run info from Discovery.
    """
    course_certificate_settings = CertificateGenerationCourseSetting.get(course_key)
    if course_certificate_settings:
        course_run_fields = []
        if course_certificate_settings.language_specific_templates_enabled:
            course_run_fields.append('content_language')
        if course_certificate_settings.include_hours_of_effort:
            course_run_fields.extend(['weeks_to_complete', 'max_effort'])
        if course_run_fields:
            course_run_data = get_course_run_details(course_key, course_run_fields)
            if course_run_data.get('weeks_to_complete') and course_run_data.get('max_effort'):
                try:
                    weeks_to_complete = int(course_run_data['weeks_to_complete'])
                    max_effort = int(course_run_data['max_effort'])
                    context['hours_of_effort'] = weeks_to_complete * max_effort
                except ValueError:
                    log.exception('Error occurred while parsing course run details')
            context['content_language'] = course_run_data.get('content_language')
Exemple #19
0
def get_user_course_expiration_date(user, course):
    """
    Return expiration date for given user course pair.
    Return None if the course does not expire.

    Business Logic:
      - Course access duration is bounded by the min and max duration.
      - If course fields are missing, default course access duration to MIN_DURATION.
    """

    access_duration = MIN_DURATION

    if not CourseMode.verified_mode_for_course(course.id):
        return None

    CourseEnrollment = apps.get_model('student.CourseEnrollment')
    enrollment = CourseEnrollment.get_enrollment(user, course.id)
    if enrollment is None or enrollment.mode != 'audit':
        return None

    try:
        # Content availability date is equivalent to max(enrollment date, course start date)
        # for most people. Using the schedule date will provide flexibility to deal with
        # more complex business rules in the future.
        content_availability_date = enrollment.schedule.start
    except CourseEnrollment.schedule.RelatedObjectDoesNotExist:
        content_availability_date = max(enrollment.created, course.start)

    # The user course expiration date is the content availability date
    # plus the weeks_to_complete field from course-discovery.
    discovery_course_details = get_course_run_details(course.id, ['weeks_to_complete'])
    expected_weeks = discovery_course_details.get('weeks_to_complete')
    if expected_weeks:
        access_duration = timedelta(weeks=expected_weeks)

    # Course access duration is bounded by the min and max duration.
    access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration))

    return content_availability_date + access_duration
Exemple #20
0
def _update_course_context(request, context, course, course_key,
                           platform_name):
    """
    Updates context dictionary with course info.
    """
    context['full_course_image_url'] = request.build_absolute_uri(
        course_image_url(course))
    course_title_from_cert = context['certificate_data'].get(
        'course_title', '')
    accomplishment_copy_course_name = course_title_from_cert if course_title_from_cert else course.display_name
    context[
        'accomplishment_copy_course_name'] = accomplishment_copy_course_name
    course_number = course.display_coursenumber if course.display_coursenumber else course.number
    context['course_number'] = course_number
    if context['organization_long_name']:
        # Translators:  This text represents the description of course
        context['accomplishment_copy_course_description'] = _(
            'a course of study offered by {partner_short_name}, '
            'an online learning initiative of '
            '{partner_long_name}.').format(
                partner_short_name=context['organization_short_name'],
                partner_long_name=context['organization_long_name'],
                platform_name=platform_name)
    else:
        # Translators:  This text represents the description of course
        context['accomplishment_copy_course_description'] = _(
            'a course of study offered by '
            '{partner_short_name}.').format(
                partner_short_name=context['organization_short_name'],
                platform_name=platform_name)
    # If language specific templates are enabled for the course, add course_run specific information to the context
    if CertificateGenerationCourseSetting.is_language_specific_templates_enabled_for_course(
            course_key):
        fields = ['start', 'end', 'max_effort', 'language']
        course_run_data = get_course_run_details(course_key, fields)
        context.update(course_run_data)