def entitlements(self, request, pk=None):  # pylint: disable=invalid-name,unused-argument
        """
        Retrieve the list of entitlements available to this learner.

        Only those entitlements are returned that satisfy enterprise customer's data sharing setting.

        Arguments:
            request (HttpRequest): Reference to in-progress request instance.
            pk (Int): Primary key value of the selected enterprise learner.

        Returns:
            (HttpResponse): Response object containing a list of learner's entitlements.
        """
        enterprise_customer_user = self.get_object()
        instance = {"entitlements": enterprise_customer_user.entitlements}
        serializer = serializers.EnterpriseCustomerUserEntitlementSerializer(instance, context={'request': request})
        return Response(serializer.data)

Retrieve the list of entitlements available to this learner .


def contains_content_items(self, request, pk, course_run_ids, program_uuids):
        """
        Return whether or not the EnterpriseCustomerCatalog contains the specified content.

        Multiple course_run_ids and/or program_uuids query parameters can be sent to this view to check
        for their existence in the EnterpriseCustomerCatalog. At least one course run key
        or program UUID value must be included in the request.
        """
        enterprise_customer_catalog = self.get_object()

        # Maintain plus characters in course key.
        course_run_ids = [unquote(quote_plus(course_run_id)) for course_run_id in course_run_ids]

        contains_content_items = True
        if course_run_ids:
            contains_content_items = enterprise_customer_catalog.contains_courses(course_run_ids)
        if program_uuids:
            contains_content_items = (
                contains_content_items and
                enterprise_customer_catalog.contains_programs(program_uuids)
            )

        return Response({'contains_content_items': contains_content_items})

Return whether or not the EnterpriseCustomerCatalog contains the specified content .


def course_detail(self, request, pk, course_key):  # pylint: disable=invalid-name,unused-argument
        """
        Return the metadata for the specified course.

        The course needs to be included in the specified EnterpriseCustomerCatalog
        in order for metadata to be returned from this endpoint.
        """
        enterprise_customer_catalog = self.get_object()
        course = enterprise_customer_catalog.get_course(course_key)
        if not course:
            raise Http404

        context = self.get_serializer_context()
        context['enterprise_customer_catalog'] = enterprise_customer_catalog
        serializer = serializers.CourseDetailSerializer(course, context=context)
        return Response(serializer.data)

Return the metadata for the specified course .


def course_run_detail(self, request, pk, course_id):  # pylint: disable=invalid-name,unused-argument
        """
        Return the metadata for the specified course run.

        The course run needs to be included in the specified EnterpriseCustomerCatalog
        in order for metadata to be returned from this endpoint.
        """
        enterprise_customer_catalog = self.get_object()
        course_run = enterprise_customer_catalog.get_course_run(course_id)
        if not course_run:
            raise Http404

        context = self.get_serializer_context()
        context['enterprise_customer_catalog'] = enterprise_customer_catalog
        serializer = serializers.CourseRunDetailSerializer(course_run, context=context)
        return Response(serializer.data)

Return the metadata for the specified course run .


def program_detail(self, request, pk, program_uuid):  # pylint: disable=invalid-name,unused-argument
        """
        Return the metadata for the specified program.

        The program needs to be included in the specified EnterpriseCustomerCatalog
        in order for metadata to be returned from this endpoint.
        """
        enterprise_customer_catalog = self.get_object()
        program = enterprise_customer_catalog.get_program(program_uuid)
        if not program:
            raise Http404

        context = self.get_serializer_context()
        context['enterprise_customer_catalog'] = enterprise_customer_catalog
        serializer = serializers.ProgramDetailSerializer(program, context=context)
        return Response(serializer.data)

Return the metadata for the specified program .


def list(self, request):
        """
        DRF view to list all catalogs.

        Arguments:
            request (HttpRequest): Current request

        Returns:
            (Response): DRF response object containing course catalogs.
        """
        catalog_api = CourseCatalogApiClient(request.user)
        catalogs = catalog_api.get_paginated_catalogs(request.GET)
        self.ensure_data_exists(request, catalogs)
        serializer = serializers.ResponsePaginationSerializer(catalogs)
        return get_paginated_response(serializer.data, request)

def list ( self , request ) : catalog_api = CourseCatalogApiClient ( request . user ) catalogs = catalog_api . get_paginated_catalogs ( request . GET ) self . ensure_data_exists ( request , catalogs ) serializer = serializers . ResponsePaginationSerializer ( catalogs ) return get_paginated_response ( serializer . data , request )


def retrieve(self, request, pk=None):  # pylint: disable=invalid-name
        """
        DRF view to get catalog details.

        Arguments:
            request (HttpRequest): Current request
            pk (int): Course catalog identifier

        Returns:
            (Response): DRF response object containing course catalogs.
        """
        catalog_api = CourseCatalogApiClient(request.user)
        catalog = catalog_api.get_catalog(pk)
        self.ensure_data_exists(
            request,
            catalog,
            error_message=(
                "Unable to fetch API response for given catalog from endpoint '/catalog/{pk}/'. "
                "The resource you are looking for does not exist.".format(pk=pk)
            )
        )
        serializer = self.serializer_class(catalog)
        return Response(serializer.data)


def courses(self, request, enterprise_customer, pk=None):  # pylint: disable=invalid-name
        """
        Retrieve the list of courses contained within this catalog.

        Only courses with active course runs are returned. A course run is considered active if it is currently
        open for enrollment, or will open in the future.
        """
        catalog_api = CourseCatalogApiClient(request.user, enterprise_customer.site)
        courses = catalog_api.get_paginated_catalog_courses(pk, request.GET)

        # If the API returned an empty response, that means pagination has ended.
        # An empty response can also mean that there was a problem fetching data from catalog API.
        self.ensure_data_exists(
            request,
            courses,
            error_message=(
                "Unable to fetch API response for catalog courses from endpoint '{endpoint}'. "
                "The resource you are looking for does not exist.".format(endpoint=request.get_full_path())
            )
        )
        serializer = serializers.EnterpriseCatalogCoursesReadOnlySerializer(courses)

        # Add enterprise related context for the courses.
        serializer.update_enterprise_courses(enterprise_customer, catalog_id=pk)
        return get_paginated_response(serializer.data, request)

Retrieve the list of courses contained within this catalog .


def get_required_query_params(self, request):
        """
        Gets ``email``, ``enterprise_name``, and ``number_of_codes``,
        which are the relevant parameters for this API endpoint.

        :param request: The request to this endpoint.
        :return: The ``email``, ``enterprise_name``, and ``number_of_codes`` from the request.
        """
        email = get_request_value(request, self.REQUIRED_PARAM_EMAIL, '')
        enterprise_name = get_request_value(request, self.REQUIRED_PARAM_ENTERPRISE_NAME, '')
        number_of_codes = get_request_value(request, self.OPTIONAL_PARAM_NUMBER_OF_CODES, '')
        if not (email and enterprise_name):
            raise CodesAPIRequestError(
                self.get_missing_params_message([
                    (self.REQUIRED_PARAM_EMAIL, bool(email)),
                    (self.REQUIRED_PARAM_ENTERPRISE_NAME, bool(enterprise_name)),
                ])
            )
        return email, enterprise_name, number_of_codes

def get_required_query_params ( self , request ) : email = get_request_value ( request , self . REQUIRED_PARAM_EMAIL , '' ) enterprise_name = get_request_value ( request , self . REQUIRED_PARAM_ENTERPRISE_NAME , '' ) number_of_codes = get_request_value ( request , self . OPTIONAL_PARAM_NUMBER_OF_CODES , '' ) if not ( email and enterprise_name ) : raise CodesAPIRequestError ( self . get_missing_params_message ( [ ( self . REQUIRED_PARAM_EMAIL , bool ( email ) ) , ( self . REQUIRED_PARAM_ENTERPRISE_NAME , bool ( enterprise_name ) ) , ] ) ) return email , enterprise_name , number_of_codes

Gets email enterprise_name and number_of_codes which are the relevant parameters for this API endpoint .


def get_missing_params_message(self, parameter_state):
        """
        Get a user-friendly message indicating a missing parameter for the API endpoint.
        """
        params = ', '.join(name for name, present in parameter_state if not present)
        return self.MISSING_REQUIRED_PARAMS_MSG.format(params)

def
get_missing_params_message
(
self
,
parameter_state
)
:
params
=
', '
.
join
(
name
for
name
,
present
in
parameter_state
if
not
present
)
return
self
.
MISSING_REQUIRED_PARAMS_MSG
.
format
(
params
)

Get a user - friendly message indicating a missing parameter for the API endpoint .


def transform_title(self, content_metadata_item):
        """
        Return the title of the content item.
        """
        title_with_locales = []

        for locale in self.enterprise_configuration.get_locales():
            title_with_locales.append({
                'locale': locale,
                'value': content_metadata_item.get('title', '')
            })

        return title_with_locales

def transform_title ( self , content_metadata_item ) : title_with_locales = [ ] for locale in self . enterprise_configuration . get_locales ( ) : title_with_locales . append ( { 'locale' : locale , 'value' : content_metadata_item . get ( 'title' , '' ) } ) return title_with_locales


def transform_description(self, content_metadata_item):
        """
        Return the description of the content item.
        """
        description_with_locales = []

        for locale in self.enterprise_configuration.get_locales():
            description_with_locales.append({
                'locale': locale,
                'value': (
                    content_metadata_item.get('full_description') or
                    content_metadata_item.get('short_description') or
                    content_metadata_item.get('title', '')
                )
            })

        return description_with_locales

def transform_description ( self , content_metadata_item ) : description_with_locales = [ ] for locale in self . enterprise_configuration . get_locales ( ) : description_with_locales . append ( { 'locale' : locale , 'value' : ( content_metadata_item . get ( 'full_description' ) or content_metadata_item . get ( 'short_description' ) or content_metadata_item . get ( 'title' , '' ) ) } ) return description_with_locales

Return the description of the content item .


def transform_image(self, content_metadata_item):
        """
        Return the image URI of the content item.
        """
        image_url = ''
        if content_metadata_item['content_type'] in ['course', 'program']:
            image_url = content_metadata_item.get('card_image_url')
        elif content_metadata_item['content_type'] == 'courserun':
            image_url = content_metadata_item.get('image_url')

        return image_url

def transform_image ( self , content_metadata_item ) : image_url = '' if content_metadata_item [ 'content_type' ] in [ 'course' , 'program' ] : image_url = content_metadata_item . get ( 'card_image_url' ) elif content_metadata_item [ 'content_type' ] == 'courserun' : image_url = content_metadata_item . get ( 'image_url' ) return image_url

Return the image URI of the content item .


def transform_launch_points(self, content_metadata_item):
        """
        Return the content metadata item launch points.

        SAPSF allows you to transmit an arry of content launch points which
        are meant to represent sections of a content item which a learner can
        launch into from SAPSF. Currently, we only provide a single launch
        point for a content item.
        """
        return [{
            'providerID': self.enterprise_configuration.provider_id,
            'launchURL': content_metadata_item['enrollment_url'],
            'contentTitle': content_metadata_item['title'],
            'contentID': self.get_content_id(content_metadata_item),
            'launchType': 3,  # This tells SAPSF to launch the course in a new browser window.
            'mobileEnabled': True,  # Always return True per ENT-1401
            'mobileLaunchURL': content_metadata_item['enrollment_url'],
        }]

Return the content metadata item launch points .


def transform_courserun_title(self, content_metadata_item):
        """
        Return the title of the courserun content item.
        """
        title = content_metadata_item.get('title') or ''
        course_run_start = content_metadata_item.get('start')

        if course_run_start:
            if course_available_for_enrollment(content_metadata_item):
                title += ' ({starts}: {:%B %Y})'.format(
                    parse_lms_api_datetime(course_run_start),
                    starts=_('Starts')
                )
            else:
                title += ' ({:%B %Y} - {enrollment_closed})'.format(
                    parse_lms_api_datetime(course_run_start),
                    enrollment_closed=_('Enrollment Closed')
                )

        title_with_locales = []
        content_metadata_language_code = transform_language_code(content_metadata_item.get('content_language', ''))
        for locale in self.enterprise_configuration.get_locales(default_locale=content_metadata_language_code):
            title_with_locales.append({
                'locale': locale,
                'value': title
            })

        return title_with_locales

def
transform_courserun_title
(
self
,
content_metadata_item
)
:
title
=
content_metadata_item
.
get
(
'title'
)
or
''
course_run_start
=
content_metadata_item
.
get
(
'start'
)
if
course_run_start
:
if
course_available_for_enrollment
(
content_metadata_item
)
:
title
+=
' ({starts}: {:%B %Y})'
.
format
(
parse_lms_api_datetime
(
course_run_start
)
,
starts
=
_
(
'Starts'
)
)
else
:
title
+=
' ({:%B %Y} - {enrollment_closed})'
.
format
(
parse_lms_api_datetime
(
course_run_start
)
,
enrollment_closed
=
_
(
'Enrollment Closed'
)
)
title_with_locales
=
[
]
content_metadata_language_code
=
transform_language_code
(
content_metadata_item
.
get
(
'content_language'
,
''
)
)
for
locale
in
self
.
enterprise_configuration
.
get_locales
(
default_locale
=
content_metadata_language_code
)
:
title_with_locales
.
append
(
{
'locale'
:
locale
,
'value'
:
title
}
)
return
title_with_locales

Return the title of the courserun content item .


def transform_courserun_description(self, content_metadata_item):
        """
        Return the description of the courserun content item.
        """
        description_with_locales = []
        content_metadata_language_code = transform_language_code(content_metadata_item.get('content_language', ''))
        for locale in self.enterprise_configuration.get_locales(default_locale=content_metadata_language_code):
            description_with_locales.append({
                'locale': locale,
                'value': (
                    content_metadata_item['full_description'] or
                    content_metadata_item['short_description'] or
                    content_metadata_item['title'] or
                    ''
                )
            })

        return description_with_locales

def transform_courserun_description ( self , content_metadata_item ) : description_with_locales = [ ] content_metadata_language_code = transform_language_code ( content_metadata_item . get ( 'content_language' , '' ) ) for locale in self . enterprise_configuration . get_locales ( default_locale = content_metadata_language_code ) : description_with_locales . append ( { 'locale' : locale , 'value' : ( content_metadata_item [ 'full_description' ] or content_metadata_item [ 'short_description' ] or content_metadata_item [ 'title' ] or '' ) } ) return description_with_locales

Return the description of the courserun content item .


def transform_courserun_schedule(self, content_metadata_item):
        """
        Return the schedule of the courseun content item.
        """
        start = content_metadata_item.get('start') or UNIX_MIN_DATE_STRING
        end = content_metadata_item.get('end') or UNIX_MAX_DATE_STRING
        return [{
            'startDate': parse_datetime_to_epoch_millis(start),
            'endDate': parse_datetime_to_epoch_millis(end),
            'active': current_time_is_in_interval(start, end)
        }]

def transform_courserun_schedule ( self , content_metadata_item ) : start = content_metadata_item . get ( 'start' ) or UNIX_MIN_DATE_STRING end = content_metadata_item . get ( 'end' ) or UNIX_MAX_DATE_STRING return [ { 'startDate' : parse_datetime_to_epoch_millis ( start ) , 'endDate' : parse_datetime_to_epoch_millis ( end ) , 'active' : current_time_is_in_interval ( start , end ) } ]

Return the schedule of the courseun content item .


def get_content_id(self, content_metadata_item):
        """
        Return the id for the given content_metadata_item, `uuid` for programs or `key` for other content
        """
        content_id = content_metadata_item.get('key', '')
        if content_metadata_item['content_type'] == 'program':
            content_id = content_metadata_item.get('uuid', '')
        return content_id

def get_content_id ( self , content_metadata_item ) : content_id = content_metadata_item . get ( 'key' , '' ) if content_metadata_item [ 'content_type' ] == 'program' : content_id = content_metadata_item . get ( 'uuid' , '' ) return content_id

Return the id for the given content_metadata_item uuid for programs or key for other content


def parse_datetime_to_epoch(datestamp, magnitude=1.0):
    """
    Convert an ISO-8601 datetime string to a Unix epoch timestamp in some magnitude.

    By default, returns seconds.
    """
    parsed_datetime = parse_lms_api_datetime(datestamp)
    time_since_epoch = parsed_datetime - UNIX_EPOCH
    return int(time_since_epoch.total_seconds() * magnitude)

def parse_datetime_to_epoch ( datestamp , magnitude = 1.0 ) : parsed_datetime = parse_lms_api_datetime ( datestamp ) time_since_epoch = parsed_datetime - UNIX_EPOCH return int ( time_since_epoch . total_seconds ( ) * magnitude )

Convert an ISO - 8601 datetime string to a Unix epoch timestamp in some magnitude .


def chunks(dictionary, chunk_size):
    """
    Yield successive n-sized chunks from dictionary.
    """
    iterable = iter(dictionary)
    for __ in range(0, len(dictionary), chunk_size):
        yield {key: dictionary[key] for key in islice(iterable, chunk_size)}

def chunks ( dictionary , chunk_size ) : iterable = iter ( dictionary ) for __ in range ( 0 , len ( dictionary ) , chunk_size ) : yield { key : dictionary [ key ] for key in islice ( iterable , chunk_size ) }

Yield successive n - sized chunks from dictionary .


def strfdelta(tdelta, fmt='{D:02}d {H:02}h {M:02}m {S:02}s', input_type='timedelta'):
    """
    Convert a datetime.timedelta object or a regular number to a custom-formatted string.

    This function works like the strftime() method works for datetime.datetime
    objects.

    The fmt argument allows custom formatting to be specified.  Fields can
    include seconds, minutes, hours, days, and weeks.  Each field is optional.

    Arguments:
        tdelta (datetime.timedelta, int): time delta object containing the duration or an integer
            to go with the input_type.
        fmt (str): Expected format of the time delta. place holders can only be one of the following.
            1. D to extract days from time delta
            2. H to extract hours from time delta
            3. M to extract months from time delta
            4. S to extract seconds from timedelta
        input_type (str):  The input_type argument allows tdelta to be a regular number instead of the
            default, which is a datetime.timedelta object.
            Valid input_type strings:
                1. 's', 'seconds',
                2. 'm', 'minutes',
                3. 'h', 'hours',
                4. 'd', 'days',
                5. 'w', 'weeks'
    Returns:
        (str): timedelta object interpolated into a string following the given format.

    Examples:
        '{D:02}d {H:02}h {M:02}m {S:02}s' --> '05d 08h 04m 02s' (default)
        '{W}w {D}d {H}:{M:02}:{S:02}'     --> '4w 5d 8:04:02'
        '{D:2}d {H:2}:{M:02}:{S:02}'      --> ' 5d  8:04:02'
        '{H}h {S}s'                       --> '72h 800s'
    """
    # Convert tdelta to integer seconds.
    if input_type == 'timedelta':
        remainder = int(tdelta.total_seconds())
    elif input_type in ['s', 'seconds']:
        remainder = int(tdelta)
    elif input_type in ['m', 'minutes']:
        remainder = int(tdelta) * 60
    elif input_type in ['h', 'hours']:
        remainder = int(tdelta) * 3600
    elif input_type in ['d', 'days']:
        remainder = int(tdelta) * 86400
    elif input_type in ['w', 'weeks']:
        remainder = int(tdelta) * 604800
    else:
        raise ValueError(
            'input_type is not valid. Valid input_type strings are: "timedelta", "s", "m", "h", "d", "w"'
        )

    f = Formatter()
    desired_fields = [field_tuple[1] for field_tuple in f.parse(fmt)]
    possible_fields = ('W', 'D', 'H', 'M', 'S')
    constants = {'W': 604800, 'D': 86400, 'H': 3600, 'M': 60, 'S': 1}
    values = {}

    for field in possible_fields:
        if field in desired_fields and field in constants:
            values[field], remainder = divmod(remainder, constants[field])

    return f.format(fmt, **values)

Convert a datetime . timedelta object or a regular number to a custom - formatted string .


def transform_description(self, content_metadata_item):
        """
        Return the transformed version of the course description.

        We choose one value out of the course's full description, short description, and title
        depending on availability and length limits.
        """
        full_description = content_metadata_item.get('full_description') or ''
        if 0 < len(full_description) <= self.LONG_STRING_LIMIT:  # pylint: disable=len-as-condition
            return full_description
        return content_metadata_item.get('short_description') or content_metadata_item.get('title') or ''

def
transform_description
(
self
,
content_metadata_item
)
:
full_description
=
content_metadata_item
.
get
(
'full_description'
)
or
''
if
0
<
len
(
full_description
)
<=
self
.
LONG_STRING_LIMIT
:
# pylint: disable=len-as-condition
return
full_description
return
content_metadata_item
.
get
(
'short_description'
)
or
content_metadata_item
.
get
(
'title'
)
or
''

Return the transformed version of the course description .


def logo_path(instance, filename):
    """
    Delete the file if it already exist and returns the enterprise customer logo image path.

    Arguments:
        instance (:class:`.EnterpriseCustomerBrandingConfiguration`): EnterpriseCustomerBrandingConfiguration object
        filename (str): file to upload

    Returns:
        path: path of image file e.g. enterprise/branding/<model.id>/<model_id>_logo.<ext>.lower()

    """
    extension = os.path.splitext(filename)[1].lower()
    instance_id = str(instance.id)
    fullname = os.path.join("enterprise/branding/", instance_id, instance_id + "_logo" + extension)
    if default_storage.exists(fullname):
        default_storage.delete(fullname)
    return fullname

def logo_path ( instance , filename ) : extension = os . path . splitext ( filename ) [ 1 ] . lower ( ) instance_id = str ( instance . id ) fullname = os . path . join ( "enterprise/branding/" , instance_id , instance_id + "_logo" + extension ) if default_storage . exists ( fullname ) : default_storage . delete ( fullname ) return fullname

Delete the file if it already exist and returns the enterprise customer logo image path .


def get_link_by_email(self, user_email):
        """
        Return link by email.
        """
        try:
            user = User.objects.get(email=user_email)
            try:
                return self.get(user_id=user.id)
            except EnterpriseCustomerUser.DoesNotExist:
                pass
        except User.DoesNotExist:
            pass

        try:
            return PendingEnterpriseCustomerUser.objects.get(user_email=user_email)
        except PendingEnterpriseCustomerUser.DoesNotExist:
            pass

        return None

def get_link_by_email ( self , user_email ) : try : user = User . objects . get ( email = user_email ) try : return self . get ( user_id = user . id ) except EnterpriseCustomerUser . DoesNotExist : pass except User . DoesNotExist : pass try : return PendingEnterpriseCustomerUser . objects . get ( user_email = user_email ) except PendingEnterpriseCustomerUser . DoesNotExist : pass return None


def link_user(self, enterprise_customer, user_email):
        """
        Link user email to Enterprise Customer.

        If :class:`django.contrib.auth.models.User` instance with specified email does not exist,
        :class:`.PendingEnterpriseCustomerUser` instance is created instead.
        """
        try:
            existing_user = User.objects.get(email=user_email)
            self.get_or_create(enterprise_customer=enterprise_customer, user_id=existing_user.id)
        except User.DoesNotExist:
            PendingEnterpriseCustomerUser.objects.get_or_create(enterprise_customer=enterprise_customer,
                                                                user_email=user_email)

def link_user ( self , enterprise_customer , user_email ) : try : existing_user = User . objects . get ( email = user_email ) self . get_or_create ( enterprise_customer = enterprise_customer , user_id = existing_user . id ) except User . DoesNotExist : PendingEnterpriseCustomerUser . objects . get_or_create ( enterprise_customer = enterprise_customer , user_email = user_email )

Link user email to Enterprise Customer .


def unlink_user(self, enterprise_customer, user_email):
        """
        Unlink user email from Enterprise Customer.

        If :class:`django.contrib.auth.models.User` instance with specified email does not exist,
        :class:`.PendingEnterpriseCustomerUser` instance is deleted instead.

        Raises EnterpriseCustomerUser.DoesNotExist if instance of :class:`django.contrib.auth.models.User` with
        specified email exists and corresponding :class:`.EnterpriseCustomerUser` instance does not.

        Raises PendingEnterpriseCustomerUser.DoesNotExist exception if instance of
        :class:`django.contrib.auth.models.User` with specified email exists and corresponding
        :class:`.PendingEnterpriseCustomerUser` instance does not.
        """
        try:
            existing_user = User.objects.get(email=user_email)
            # not capturing DoesNotExist intentionally to signal to view that link does not exist
            link_record = self.get(enterprise_customer=enterprise_customer, user_id=existing_user.id)
            link_record.delete()

            if update_user:
                # Remove the SailThru flags for enterprise learner.
                update_user.delay(
                    sailthru_vars={
                        'is_enterprise_learner': False,
                        'enterprise_name': None,
                    },
                    email=user_email
                )

        except User.DoesNotExist:
            # not capturing DoesNotExist intentionally to signal to view that link does not exist
            pending_link = PendingEnterpriseCustomerUser.objects.get(
                enterprise_customer=enterprise_customer, user_email=user_email
            )
            pending_link.delete()

        LOGGER.info(
            'Enterprise learner {%s} successfully unlinked from Enterprise Customer {%s}',
            user_email,
            enterprise_customer.name
        )

Unlink user email from Enterprise Customer .


def enterprise_customer_uuid(self):
        """Get the enterprise customer uuid linked to the user."""
        try:
            enterprise_user = EnterpriseCustomerUser.objects.get(user_id=self.user.id)
        except ObjectDoesNotExist:
            LOGGER.warning(
                'User {} has a {} assignment but is not linked to an enterprise!'.format(
                    self.__class__,
                    self.user.id
                ))
            return None
        except MultipleObjectsReturned:
            LOGGER.warning(
                'User {} is linked to multiple enterprises, which is not yet supported!'.format(self.user.id)
            )
            return None

        return str(enterprise_user.enterprise_customer.uuid)

Get the enterprise customer uuid linked to the user .


def get_data_sharing_consent(username, enterprise_customer_uuid, course_id=None, program_uuid=None):
    """
    Get the data sharing consent object associated with a certain user, enterprise customer, and other scope.

    :param username: The user that grants consent
    :param enterprise_customer_uuid: The consent requester
    :param course_id (optional): A course ID to which consent may be related
    :param program_uuid (optional): A program to which consent may be related
    :return: The data sharing consent object, or None if the enterprise customer for the given UUID does not exist.
    """
    EnterpriseCustomer = apps.get_model('enterprise', 'EnterpriseCustomer')  # pylint: disable=invalid-name
    try:
        if course_id:
            return get_course_data_sharing_consent(username, course_id, enterprise_customer_uuid)
        return get_program_data_sharing_consent(username, program_uuid, enterprise_customer_uuid)
    except EnterpriseCustomer.DoesNotExist:
        return None

def
get_data_sharing_consent
(
username
,
enterprise_customer_uuid
,
course_id
=
,
program_uuid
=
)
:
EnterpriseCustomer
=
apps
.
get_model
(
'enterprise'
,
'EnterpriseCustomer'
)
# pylint: disable=invalid-name
try
:
if
course_id
:
return
get_course_data_sharing_consent
(
username
,
course_id
,
enterprise_customer_uuid
)
return
get_program_data_sharing_consent
(
username
,
program_uuid
,
enterprise_customer_uuid
)
except
EnterpriseCustomer
.
DoesNotExist
:
return

Get the data sharing consent object associated with a certain user enterprise customer and other scope .


def get_course_data_sharing_consent(username, course_id, enterprise_customer_uuid):
    """
    Get the data sharing consent object associated with a certain user of a customer for a course.

    :param username: The user that grants consent.
    :param course_id: The course for which consent is granted.
    :param enterprise_customer_uuid: The consent requester.
    :return: The data sharing consent object
    """
    # Prevent circular imports.
    DataSharingConsent = apps.get_model('consent', 'DataSharingConsent')  # pylint: disable=invalid-name
    return DataSharingConsent.objects.proxied_get(
        username=username,
        course_id=course_id,
        enterprise_customer__uuid=enterprise_customer_uuid
    )

def
get_course_data_sharing_consent
(
username
,
course_id
,
enterprise_customer_uuid
)
:
# Prevent circular imports.
DataSharingConsent
=
apps
.
get_model
(
'consent'
,
'DataSharingConsent'
)
# pylint: disable=invalid-name
return
DataSharingConsent
.
objects
.
proxied_get
(
username
=
username
,
course_id
=
course_id
,
enterprise_customer__uuid
=
enterprise_customer_uuid
)

Get the data sharing consent object associated with a certain user of a customer for a course .


def get_program_data_sharing_consent(username, program_uuid, enterprise_customer_uuid):
    """
    Get the data sharing consent object associated with a certain user of a customer for a program.

    :param username: The user that grants consent.
    :param program_uuid: The program for which consent is granted.
    :param enterprise_customer_uuid: The consent requester.
    :return: The data sharing consent object
    """
    enterprise_customer = get_enterprise_customer(enterprise_customer_uuid)
    discovery_client = CourseCatalogApiServiceClient(enterprise_customer.site)
    course_ids = discovery_client.get_program_course_keys(program_uuid)
    child_consents = (
        get_data_sharing_consent(username, enterprise_customer_uuid, course_id=individual_course_id)
        for individual_course_id in course_ids
    )
    return ProxyDataSharingConsent.from_children(program_uuid, *child_consents)

Get the data sharing consent object associated with a certain user of a customer for a program .


def send_course_enrollment_statement(lrs_configuration, course_enrollment):
    """
    Send xAPI statement for course enrollment.

    Arguments:
         lrs_configuration (XAPILRSConfiguration): XAPILRSConfiguration instance where to send statements.
         course_enrollment (CourseEnrollment): Course enrollment object.
    """
    user_details = LearnerInfoSerializer(course_enrollment.user)
    course_details = CourseInfoSerializer(course_enrollment.course)

    statement = LearnerCourseEnrollmentStatement(
        course_enrollment.user,
        course_enrollment.course,
        user_details.data,
        course_details.data,
    )
    EnterpriseXAPIClient(lrs_configuration).save_statement(statement)

def send_course_enrollment_statement ( lrs_configuration , course_enrollment ) : user_details = LearnerInfoSerializer ( course_enrollment . user ) course_details = CourseInfoSerializer ( course_enrollment . course ) statement = LearnerCourseEnrollmentStatement ( course_enrollment . user , course_enrollment . course , user_details . data , course_details . data , ) EnterpriseXAPIClient ( lrs_configuration ) . save_statement ( statement )

Send xAPI statement for course enrollment .


def send_course_completion_statement(lrs_configuration, user, course_overview, course_grade):
    """
    Send xAPI statement for course completion.

    Arguments:
         lrs_configuration (XAPILRSConfiguration): XAPILRSConfiguration instance where to send statements.
         user (User): Django User object.
         course_overview (CourseOverview): Course over view object containing course details.
         course_grade (CourseGrade): course grade object.
    """
    user_details = LearnerInfoSerializer(user)
    course_details = CourseInfoSerializer(course_overview)

    statement = LearnerCourseCompletionStatement(
        user,
        course_overview,
        user_details.data,
        course_details.data,
        course_grade,
    )
    EnterpriseXAPIClient(lrs_configuration).save_statement(statement)

def send_course_completion_statement ( lrs_configuration , user , course_overview , course_grade ) : user_details = LearnerInfoSerializer ( user ) course_details = CourseInfoSerializer ( course_overview ) statement = LearnerCourseCompletionStatement ( user , course_overview , user_details . data , course_details . data , course_grade , ) EnterpriseXAPIClient ( lrs_configuration ) . save_statement ( statement )

Send xAPI statement for course completion .


def export(self):
        """
        Return the exported and transformed content metadata as a dictionary.
        """
        content_metadata_export = {}
        content_metadata_items = self.enterprise_api.get_content_metadata(self.enterprise_customer)
        LOGGER.info('Retrieved content metadata for enterprise [%s]', self.enterprise_customer.name)
        for item in content_metadata_items:
            transformed = self._transform_item(item)
            LOGGER.info(
                'Exporting content metadata item with plugin configuration [%s]: [%s]',
                self.enterprise_configuration,
                json.dumps(transformed, indent=4),
            )
            content_metadata_item_export = ContentMetadataItemExport(item, transformed)
            content_metadata_export[content_metadata_item_export.content_id] = content_metadata_item_export
        return OrderedDict(sorted(content_metadata_export.items()))

Return the exported and transformed content metadata as a dictionary .


def _transform_item(self, content_metadata_item):
        """
        Transform the provided content metadata item to the schema expected by the integrated channel.
        """
        content_metadata_type = content_metadata_item['content_type']
        transformed_item = {}
        for integrated_channel_schema_key, edx_data_schema_key in self.DATA_TRANSFORM_MAPPING.items():
            # Look for transformer functions defined on subclasses.
            # Favor content type-specific functions.
            transformer = (
                getattr(
                    self,
                    'transform_{content_type}_{edx_data_schema_key}'.format(
                        content_type=content_metadata_type,
                        edx_data_schema_key=edx_data_schema_key
                    ),
                    None
                )
                or
                getattr(
                    self,
                    'transform_{edx_data_schema_key}'.format(
                        edx_data_schema_key=edx_data_schema_key
                    ),
                    None
                )
            )
            if transformer:
                transformed_item[integrated_channel_schema_key] = transformer(content_metadata_item)
            else:
                # The concrete subclass does not define an override for the given field,
                # so just use the data key to index the content metadata item dictionary.
                try:
                    transformed_item[integrated_channel_schema_key] = content_metadata_item[edx_data_schema_key]
                except KeyError:
                    # There may be a problem with the DATA_TRANSFORM_MAPPING on
                    # the concrete subclass or the concrete subclass does not implement
                    # the appropriate field tranformer function.
                    LOGGER.exception(
                        'Failed to transform content metadata item field [%s] for [%s]: [%s]',
                        edx_data_schema_key,
                        self.enterprise_customer.name,
                        content_metadata_item,
                    )

        return transformed_item

Transform the provided content metadata item to the schema expected by the integrated channel .


def get_consent_record(self, request):
        """
        Get the consent record relevant to the request at hand.
        """
        username, course_id, program_uuid, enterprise_customer_uuid = self.get_required_query_params(request)
        return get_data_sharing_consent(
            username,
            enterprise_customer_uuid,
            course_id=course_id,
            program_uuid=program_uuid
        )

def get_consent_record ( self , request ) : username , course_id , program_uuid , enterprise_customer_uuid = self . get_required_query_params ( request ) return get_data_sharing_consent ( username , enterprise_customer_uuid , course_id = course_id , program_uuid = program_uuid )

Get the consent record relevant to the request at hand .


def get_required_query_params(self, request):
        """
        Gets ``username``, ``course_id``, and ``enterprise_customer_uuid``,
        which are the relevant query parameters for this API endpoint.

        :param request: The request to this endpoint.
        :return: The ``username``, ``course_id``, and ``enterprise_customer_uuid`` from the request.
        """
        username = get_request_value(request, self.REQUIRED_PARAM_USERNAME, '')
        course_id = get_request_value(request, self.REQUIRED_PARAM_COURSE_ID, '')
        program_uuid = get_request_value(request, self.REQUIRED_PARAM_PROGRAM_UUID, '')
        enterprise_customer_uuid = get_request_value(request, self.REQUIRED_PARAM_ENTERPRISE_CUSTOMER)
        if not (username and (course_id or program_uuid) and enterprise_customer_uuid):
            raise ConsentAPIRequestError(
                self.get_missing_params_message([
                    ("'username'", bool(username)),
                    ("'enterprise_customer_uuid'", bool(enterprise_customer_uuid)),
                    ("one of 'course_id' or 'program_uuid'", bool(course_id or program_uuid)),
                ])
            )
        return username, course_id, program_uuid, enterprise_customer_uuid

def
get_required_query_params
(
self
,
request
)
:
username
=
get_request_value
(
request
,
self
.
REQUIRED_PARAM_USERNAME
,
''
)
course_id
=
get_request_value
(
request
,
self
.
REQUIRED_PARAM_COURSE_ID
,
''
)
program_uuid
=
get_request_value
(
request
,
self
.
REQUIRED_PARAM_PROGRAM_UUID
,
''
)
enterprise_customer_uuid
=
get_request_value
(
request
,
self
.
REQUIRED_PARAM_ENTERPRISE_CUSTOMER
)
if
not
(
username
and
(
course_id
or
program_uuid
)
and
enterprise_customer_uuid
)
:
raise
ConsentAPIRequestError
(
self
.
get_missing_params_message
(
[
(
"'username'"
,
bool
(
username
)
)
,
(
"'enterprise_customer_uuid'"
,
bool
(
enterprise_customer_uuid
)
)
,
(
"one of 'course_id' or 'program_uuid'"
,
bool
(
course_id
or
program_uuid
)
)
,
]
)
)
return
username
,
course_id
,
program_uuid
,
enterprise_customer_uuid

Gets username course_id and enterprise_customer_uuid which are the relevant query parameters for this API endpoint .


def get_no_record_response(self, request):
        """
        Get an HTTPResponse that can be used when there's no related EnterpriseCustomer.
        """
        username, course_id, program_uuid, enterprise_customer_uuid = self.get_required_query_params(request)
        data = {
            self.REQUIRED_PARAM_USERNAME: username,
            self.REQUIRED_PARAM_ENTERPRISE_CUSTOMER: enterprise_customer_uuid,
            self.CONSENT_EXISTS: False,
            self.CONSENT_GRANTED: False,
            self.CONSENT_REQUIRED: False,
        }
        if course_id:
            data[self.REQUIRED_PARAM_COURSE_ID] = course_id

        if program_uuid:
            data[self.REQUIRED_PARAM_PROGRAM_UUID] = program_uuid

        return Response(data, status=HTTP_200_OK)

def get_no_record_response ( self , request ) : username , course_id , program_uuid , enterprise_customer_uuid = self . get_required_query_params ( request ) data = { self . REQUIRED_PARAM_USERNAME : username , self . REQUIRED_PARAM_ENTERPRISE_CUSTOMER : enterprise_customer_uuid , self . CONSENT_EXISTS : False , self . CONSENT_GRANTED : False , self . CONSENT_REQUIRED : False , } if course_id : data [ self . REQUIRED_PARAM_COURSE_ID ] = course_id if program_uuid : data [ self . REQUIRED_PARAM_PROGRAM_UUID ] = program_uuid return Response ( data , status = HTTP_200_OK )

Get an HTTPResponse that can be used when there s no related EnterpriseCustomer .


def ready(self):
        """
        Perform other one-time initialization steps.
        """
        from enterprise.signals import handle_user_post_save
        from django.db.models.signals import pre_migrate, post_save

        post_save.connect(handle_user_post_save, sender=self.auth_user_model, dispatch_uid=USER_POST_SAVE_DISPATCH_UID)
        pre_migrate.connect(self._disconnect_user_post_save_for_migrations)

Perform other one - time initialization steps .


def _disconnect_user_post_save_for_migrations(self, sender, **kwargs):  # pylint: disable=unused-argument
        """
        Handle pre_migrate signal - disconnect User post_save handler.
        """
        from django.db.models.signals import post_save
        post_save.disconnect(sender=self.auth_user_model, dispatch_uid=USER_POST_SAVE_DISPATCH_UID)

Handle pre_migrate signal - disconnect User post_save handler .


def get_actor(self, username, email):
        """
        Get actor for the statement.
        """
        return Agent(
            name=username,
            mbox='mailto:{email}'.format(email=email),
        )

def get_actor ( self , username , email ) : return Agent ( name = username , mbox = 'mailto:{email}' . format ( email = email ) , )


def get_object(self, name, description):
        """
        Get object for the statement.
        """
        return Activity(
            id=X_API_ACTIVITY_COURSE,
            definition=ActivityDefinition(
                name=LanguageMap({'en-US': (name or '').encode("ascii", "ignore").decode('ascii')}),
                description=LanguageMap({'en-US': (description or '').encode("ascii", "ignore").decode('ascii')}),
            ),
        )

def get_object ( self , name , description ) : return Activity ( id = X_API_ACTIVITY_COURSE , definition = ActivityDefinition ( name = LanguageMap ( { 'en-US' : ( name or '' ) . encode ( "ascii" , "ignore" ) . decode ( 'ascii' ) } ) , description = LanguageMap ( { 'en-US' : ( description or '' ) . encode ( "ascii" , "ignore" ) . decode ( 'ascii' ) } ) , ) , )


def parse_csv(file_stream, expected_columns=None):
    """
    Parse csv file and return a stream of dictionaries representing each row.

    First line of CSV file must contain column headers.

    Arguments:
         file_stream: input file
         expected_columns (set[unicode]): columns that are expected to be present

    Yields:
        dict: CSV line parsed into a dictionary.
    """
    reader = unicodecsv.DictReader(file_stream, encoding="utf-8")

    if expected_columns and set(expected_columns) - set(reader.fieldnames):
        raise ValidationError(ValidationMessages.MISSING_EXPECTED_COLUMNS.format(
            expected_columns=", ".join(expected_columns), actual_columns=", ".join(reader.fieldnames)
        ))

    # "yield from reader" would be nicer, but we're on python2.7 yet.
    for row in reader:
        yield row

Parse csv file and return a stream of dictionaries representing each row .


def validate_email_to_link(email, raw_email=None, message_template=None, ignore_existing=False):
    """
    Validate email to be linked to Enterprise Customer.

    Performs two checks:
        * Checks that email is valid
        * Checks that it is not already linked to any Enterprise Customer

    Arguments:
        email (str): user email to link
        raw_email (str): raw value as it was passed by user - used in error message.
        message_template (str): Validation error template string.
        ignore_existing (bool): If True to skip the check for an existing Enterprise Customer

    Raises:
        ValidationError: if email is invalid or already linked to Enterprise Customer.

    Returns:
        bool: Whether or not there is an existing record with the same email address.
    """
    raw_email = raw_email if raw_email is not None else email
    message_template = message_template if message_template is not None else ValidationMessages.INVALID_EMAIL
    try:
        validate_email(email)
    except ValidationError:
        raise ValidationError(message_template.format(argument=raw_email))

    existing_record = EnterpriseCustomerUser.objects.get_link_by_email(email)
    if existing_record and not ignore_existing:
        raise ValidationError(ValidationMessages.USER_ALREADY_REGISTERED.format(
            email=email, ec_name=existing_record.enterprise_customer.name
        ))
    return existing_record or False

def validate_email_to_link ( email , raw_email = None , message_template = None , ignore_existing = False ) : raw_email = raw_email if raw_email is not None else email message_template = message_template if message_template is not None else ValidationMessages . INVALID_EMAIL try : validate_email ( email ) except ValidationError : raise ValidationError ( message_template . format ( argument = raw_email ) ) existing_record = EnterpriseCustomerUser . objects . get_link_by_email ( email ) if existing_record and not ignore_existing : raise ValidationError ( ValidationMessages . USER_ALREADY_REGISTERED . format ( email = email , ec_name = existing_record . enterprise_customer . name ) ) return existing_record or False

Validate email to be linked to Enterprise Customer .


def get_course_runs_from_program(program):
    """
    Return course runs from program data.

    Arguments:
        program(dict): Program data from Course Catalog API

    Returns:
        set: course runs in given program
    """
    course_runs = set()
    for course in program.get("courses", []):
        for run in course.get("course_runs", []):
            if "key" in run and run["key"]:
                course_runs.add(run["key"])

    return course_runs

def get_course_runs_from_program ( program ) : course_runs = set ( ) for course in program . get ( "courses" , [ ] ) : for run in course . get ( "course_runs" , [ ] ) : if "key" in run and run [ "key" ] : course_runs . add ( run [ "key" ] ) return course_runs


def get_earliest_start_date_from_program(program):
    """
    Get the earliest date that one of the courses in the program was available.
    For the sake of emails to new learners, we treat this as the program start date.

    Arguemnts:
        program (dict): Program data from Course Catalog API

    returns:
        datetime.datetime: The date and time at which the first course started
    """
    start_dates = []
    for course in program.get('courses', []):
        for run in course.get('course_runs', []):
            if run.get('start'):
                start_dates.append(parse_lms_api_datetime(run['start']))
    if not start_dates:
        return None
    return min(start_dates)

def get_earliest_start_date_from_program ( program ) : start_dates = [ ] for course in program . get ( 'courses' , [ ] ) : for run in course . get ( 'course_runs' , [ ] ) : if run . get ( 'start' ) : start_dates . append ( parse_lms_api_datetime ( run [ 'start' ] ) ) if not start_dates : return None return min ( start_dates )

Get the earliest date that one of the courses in the program was available . For the sake of emails to new learners we treat this as the program start date .


def paginated_list(object_list, page, page_size=25):
    """
    Returns paginated list.

    Arguments:
        object_list (QuerySet): A list of records to be paginated.
        page (int): Current page number.
        page_size (int): Number of records displayed in each paginated set.
        show_all (bool): Whether to show all records.

    Adopted from django/contrib/admin/templatetags/admin_list.py
    https://github.com/django/django/blob/1.11.1/django/contrib/admin/templatetags/admin_list.py#L50
    """
    paginator = CustomPaginator(object_list, page_size)
    try:
        object_list = paginator.page(page)
    except PageNotAnInteger:
        object_list = paginator.page(1)
    except EmptyPage:
        object_list = paginator.page(paginator.num_pages)

    page_range = []
    page_num = object_list.number

    # If there are 10 or fewer pages, display links to every page.
    # Otherwise, do some fancy
    if paginator.num_pages <= 10:
        page_range = range(paginator.num_pages)
    else:
        # Insert "smart" pagination links, so that there are always ON_ENDS
        # links at either end of the list of pages, and there are always
        # ON_EACH_SIDE links at either end of the "current page" link.
        if page_num > (PAGES_ON_EACH_SIDE + PAGES_ON_ENDS + 1):
            page_range.extend(range(1, PAGES_ON_ENDS + 1))
            page_range.append(DOT)
            page_range.extend(range(page_num - PAGES_ON_EACH_SIDE, page_num + 1))
        else:
            page_range.extend(range(1, page_num + 1))
        if page_num < (paginator.num_pages - PAGES_ON_EACH_SIDE - PAGES_ON_ENDS):
            page_range.extend(range(page_num + 1, page_num + PAGES_ON_EACH_SIDE + 1))
            page_range.append(DOT)
            page_range.extend(range(paginator.num_pages + 1 - PAGES_ON_ENDS, paginator.num_pages + 1))
        else:
            page_range.extend(range(page_num + 1, paginator.num_pages + 1))

        # Override page range to implement custom smart links.
        object_list.paginator.page_range = page_range

    return object_list


def clean_email_or_username(self):
        """
        Clean email form field

        Returns:
            str: the cleaned value, converted to an email address (or an empty string)
        """
        email_or_username = self.cleaned_data[self.Fields.EMAIL_OR_USERNAME].strip()

        if not email_or_username:
            # The field is blank; we just return the existing blank value.
            return email_or_username

        email = email_or_username__to__email(email_or_username)
        bulk_entry = len(split_usernames_and_emails(email)) > 1
        if bulk_entry:
            for email in split_usernames_and_emails(email):
                validate_email_to_link(
                    email,
                    None,
                    ValidationMessages.INVALID_EMAIL_OR_USERNAME,
                    ignore_existing=True
                )
            email = email_or_username
        else:
            validate_email_to_link(
                email,
                email_or_username,
                ValidationMessages.INVALID_EMAIL_OR_USERNAME,
                ignore_existing=True
            )

        return email


def clean_course(self):
        """
        Verify course ID and retrieve course details.
        """
        course_id = self.cleaned_data[self.Fields.COURSE].strip()
        if not course_id:
            return None
        try:
            client = EnrollmentApiClient()
            return client.get_course_details(course_id)
        except (HttpClientError, HttpServerError):
            raise ValidationError(ValidationMessages.INVALID_COURSE_ID.format(course_id=course_id))

def clean_course ( self ) : course_id = self . cleaned_data [ self . Fields . COURSE ] . strip ( ) if not course_id : return None try : client = EnrollmentApiClient ( ) return client . get_course_details ( course_id ) except ( HttpClientError , HttpServerError ) : raise ValidationError ( ValidationMessages . INVALID_COURSE_ID . format ( course_id = course_id ) )

Verify course ID and retrieve course details .


def clean_program(self):
        """
        Clean program.

        Try obtaining program treating form value as program UUID or title.

        Returns:
            dict: Program information if program found
        """
        program_id = self.cleaned_data[self.Fields.PROGRAM].strip()
        if not program_id:
            return None

        try:
            client = CourseCatalogApiClient(self._user, self._enterprise_customer.site)
            program = client.get_program_by_uuid(program_id) or client.get_program_by_title(program_id)
        except MultipleProgramMatchError as exc:
            raise ValidationError(ValidationMessages.MULTIPLE_PROGRAM_MATCH.format(program_count=exc.programs_matched))
        except (HttpClientError, HttpServerError):
            raise ValidationError(ValidationMessages.INVALID_PROGRAM_ID.format(program_id=program_id))

        if not program:
            raise ValidationError(ValidationMessages.INVALID_PROGRAM_ID.format(program_id=program_id))

        if program['status'] != ProgramStatuses.ACTIVE:
            raise ValidationError(
                ValidationMessages.PROGRAM_IS_INACTIVE.format(program_id=program_id, status=program['status'])
            )

        return program

def clean_program ( self ) : program_id = self . cleaned_data [ self . Fields . PROGRAM ] . strip ( ) if not program_id : return None try : client = CourseCatalogApiClient ( self . _user , self . _enterprise_customer . site ) program = client . get_program_by_uuid ( program_id ) or client . get_program_by_title ( program_id ) except MultipleProgramMatchError as exc : raise ValidationError ( ValidationMessages . MULTIPLE_PROGRAM_MATCH . format ( program_count = exc . programs_matched ) ) except ( HttpClientError , HttpServerError ) : raise ValidationError ( ValidationMessages . INVALID_PROGRAM_ID . format ( program_id = program_id ) ) if not program : raise ValidationError ( ValidationMessages . INVALID_PROGRAM_ID . format ( program_id = program_id ) ) if program [ 'status' ] != ProgramStatuses . ACTIVE : raise ValidationError ( ValidationMessages . PROGRAM_IS_INACTIVE . format ( program_id = program_id , status = program [ 'status' ] ) ) return program


def clean_notify(self):
        """
        Clean the notify_on_enrollment field.
        """
        return self.cleaned_data.get(self.Fields.NOTIFY, self.NotificationTypes.DEFAULT)

def clean_notify ( self ) : return self . cleaned_data . get ( self . Fields . NOTIFY , self . NotificationTypes . DEFAULT )


def clean(self):
        """
        Clean fields that depend on each other.

        In this case, the form can be used to link single user or bulk link multiple users. These are mutually
        exclusive modes, so this method checks that only one field is passed.
        """
        cleaned_data = super(ManageLearnersForm, self).clean()

        # Here we take values from `data` (and not `cleaned_data`) as we need raw values - field clean methods
        # might "invalidate" the value and set it to None, while all we care here is if it was provided at all or not
        email_or_username = self.data.get(self.Fields.EMAIL_OR_USERNAME, None)
        bulk_upload_csv = self.files.get(self.Fields.BULK_UPLOAD, None)

        if not email_or_username and not bulk_upload_csv:
            raise ValidationError(ValidationMessages.NO_FIELDS_SPECIFIED)

        if email_or_username and bulk_upload_csv:
            raise ValidationError(ValidationMessages.BOTH_FIELDS_SPECIFIED)

        if email_or_username:
            mode = self.Modes.MODE_SINGULAR
        else:
            mode = self.Modes.MODE_BULK

        cleaned_data[self.Fields.MODE] = mode
        cleaned_data[self.Fields.NOTIFY] = self.clean_notify()

        self._validate_course()
        self._validate_program()

        if self.data.get(self.Fields.PROGRAM, None) and self.data.get(self.Fields.COURSE, None):
            raise ValidationError(ValidationMessages.COURSE_AND_PROGRAM_ERROR)

        return cleaned_data

Clean fields that depend on each other .


def _validate_course(self):
        """
        Verify that the selected mode is valid for the given course .
        """
        # Verify that the selected mode is valid for the given course .
        course_details = self.cleaned_data.get(self.Fields.COURSE)
        if course_details:
            course_mode = self.cleaned_data.get(self.Fields.COURSE_MODE)
            if not course_mode:
                raise ValidationError(ValidationMessages.COURSE_WITHOUT_COURSE_MODE)
            valid_course_modes = course_details["course_modes"]
            if all(course_mode != mode["slug"] for mode in valid_course_modes):
                error = ValidationError(ValidationMessages.COURSE_MODE_INVALID_FOR_COURSE.format(
                    course_mode=course_mode,
                    course_id=course_details["course_id"],
                ))
                raise ValidationError({self.Fields.COURSE_MODE: error})


def _validate_program(self):
        """
        Verify that selected mode is available for program and all courses in the program
        """
        program = self.cleaned_data.get(self.Fields.PROGRAM)
        if not program:
            return

        course_runs = get_course_runs_from_program(program)
        try:
            client = CourseCatalogApiClient(self._user, self._enterprise_customer.site)
            available_modes = client.get_common_course_modes(course_runs)
            course_mode = self.cleaned_data.get(self.Fields.COURSE_MODE)
        except (HttpClientError, HttpServerError):
            raise ValidationError(
                ValidationMessages.FAILED_TO_OBTAIN_COURSE_MODES.format(program_title=program.get("title"))
            )

        if not course_mode:
            raise ValidationError(ValidationMessages.COURSE_WITHOUT_COURSE_MODE)
        if course_mode not in available_modes:
            raise ValidationError(ValidationMessages.COURSE_MODE_NOT_AVAILABLE.format(
                mode=course_mode, program_title=program.get("title"), modes=", ".join(available_modes)
            ))

def
_validate_program
(
self
)
:
program
=
self
.
cleaned_data
.
get
(
self
.
Fields
.
PROGRAM
)
if
not
program
:
return
course_runs
=
get_course_runs_from_program
(
program
)
try
:
client
=
CourseCatalogApiClient
(
self
.
_user
,
self
.
_enterprise_customer
.
site
)
available_modes
=
client
.
get_common_course_modes
(
course_runs
)
course_mode
=
self
.
cleaned_data
.
get
(
self
.
Fields
.
COURSE_MODE
)
except
(
HttpClientError
,
HttpServerError
)
:
raise
ValidationError
(
ValidationMessages
.
FAILED_TO_OBTAIN_COURSE_MODES
.
format
(
program_title
=
program
.
get
(
"title"
)
)
)
if
not
course_mode
:
raise
ValidationError
(
ValidationMessages
.
COURSE_WITHOUT_COURSE_MODE
)
if
course_mode
not
in
available_modes
:
raise
ValidationError
(
ValidationMessages
.
COURSE_MODE_NOT_AVAILABLE
.
format
(
mode
=
course_mode
,
program_title
=
program
.
get
(
"title"
)
,
modes
=
", "
.
join
(
available_modes
)
)
)


def get_catalog_options(self):
        """
        Retrieve a list of catalog ID and name pairs.

        Once retrieved, these name pairs can be used directly as a value
        for the `choices` argument to a ChoiceField.
        """
        # TODO: We will remove the discovery service catalog implementation
        # once we have fully migrated customer's to EnterpriseCustomerCatalogs.
        # For now, this code will prevent an admin from creating a new
        # EnterpriseCustomer with a discovery service catalog. They will have to first
        # save the EnterpriseCustomer admin form and then edit the EnterpriseCustomer
        # to add a discovery service catalog.
        if hasattr(self.instance, 'site'):
            catalog_api = CourseCatalogApiClient(self.user, self.instance.site)
        else:
            catalog_api = CourseCatalogApiClient(self.user)
        catalogs = catalog_api.get_all_catalogs()
        # order catalogs by name.
        catalogs = sorted(catalogs, key=lambda catalog: catalog.get('name', '').lower())

        return BLANK_CHOICE_DASH + [
            (catalog['id'], catalog['name'],)
            for catalog in catalogs
        ]

Retrieve a list of catalog ID and name pairs .


def clean(self):
        """
        Clean form fields prior to database entry.

        In this case, the major cleaning operation is substituting a None value for a blank
        value in the Catalog field.
        """
        cleaned_data = super(EnterpriseCustomerAdminForm, self).clean()
        if 'catalog' in cleaned_data and not cleaned_data['catalog']:
            cleaned_data['catalog'] = None
        return cleaned_data

def clean ( self ) : cleaned_data = super ( EnterpriseCustomerAdminForm , self ) . clean ( ) if 'catalog' in cleaned_data and not cleaned_data [ 'catalog' ] : cleaned_data [ 'catalog' ] = None return cleaned_data

Clean form fields prior to database entry .


def clean(self):
        """
        Final validations of model fields.

        1. Validate that selected site for enterprise customer matches with the selected identity provider's site.
        """
        super(EnterpriseCustomerIdentityProviderAdminForm, self).clean()

        provider_id = self.cleaned_data.get('provider_id', None)
        enterprise_customer = self.cleaned_data.get('enterprise_customer', None)

        if provider_id is None or enterprise_customer is None:
            # field validation for either provider_id or enterprise_customer has already raised
            # a validation error.
            return

        identity_provider = utils.get_identity_provider(provider_id)
        if not identity_provider:
            # This should not happen, as identity providers displayed in drop down are fetched dynamically.
            message = _(
                "The specified Identity Provider does not exist. For more "
                "information, contact a system administrator.",
            )
            # Log message for debugging
            logger.exception(message)

            raise ValidationError(message)

        if identity_provider and identity_provider.site != enterprise_customer.site:
            raise ValidationError(
                _(
                    "The site for the selected identity provider "
                    "({identity_provider_site}) does not match the site for "
                    "this enterprise customer ({enterprise_customer_site}). "
                    "To correct this problem, select a site that has a domain "
                    "of '{identity_provider_site}', or update the identity "
                    "provider to '{enterprise_customer_site}'."
                ).format(
                    enterprise_customer_site=enterprise_customer.site,
                    identity_provider_site=identity_provider.site,
                ),
            )


def clean(self):
        """
        Override of clean method to perform additional validation
        """
        cleaned_data = super(EnterpriseCustomerReportingConfigAdminForm, self).clean()
        report_customer = cleaned_data.get('enterprise_customer')

        # Check that any selected catalogs are tied to the selected enterprise.
        invalid_catalogs = [
            '{} ({})'.format(catalog.title, catalog.uuid)
            for catalog in cleaned_data.get('enterprise_customer_catalogs')
            if catalog.enterprise_customer != report_customer
        ]

        if invalid_catalogs:
            message = _(
                'These catalogs for reporting do not match enterprise'
                'customer {enterprise_customer}: {invalid_catalogs}',
            ).format(
                enterprise_customer=report_customer,
                invalid_catalogs=invalid_catalogs,
            )
            self.add_error('enterprise_customer_catalogs', message)


def clean_channel_worker_username(self):
        """
        Clean enterprise channel worker user form field

        Returns:
            str: the cleaned value of channel user username for transmitting courses metadata.
        """
        channel_worker_username = self.cleaned_data['channel_worker_username'].strip()

        try:
            User.objects.get(username=channel_worker_username)
        except User.DoesNotExist:
            raise ValidationError(
                ValidationMessages.INVALID_CHANNEL_WORKER.format(
                    channel_worker_username=channel_worker_username
                )
            )

        return channel_worker_username

def clean_channel_worker_username ( self ) : channel_worker_username = self . cleaned_data [ 'channel_worker_username' ] . strip ( ) try : User . objects . get ( username = channel_worker_username ) except User . DoesNotExist : raise ValidationError ( ValidationMessages . INVALID_CHANNEL_WORKER . format ( channel_worker_username = channel_worker_username ) ) return channel_worker_username


def verify_edx_resources():
    """
    Ensure that all necessary resources to render the view are present.
    """
    required_methods = {
        'ProgramDataExtender': ProgramDataExtender,
    }

    for method in required_methods:
        if required_methods[method] is None:
            raise NotConnectedToOpenEdX(
                _("The following method from the Open edX platform is necessary for this view but isn't available.")
                + "\nUnavailable: {method}".format(method=method)
            )

Ensure that all necessary resources to render the view are present .


def get_global_context(request, enterprise_customer):
    """
    Get the set of variables that are needed by default across views.
    """
    platform_name = get_configuration_value("PLATFORM_NAME", settings.PLATFORM_NAME)
    # pylint: disable=no-member
    return {
        'enterprise_customer': enterprise_customer,
        'LMS_SEGMENT_KEY': settings.LMS_SEGMENT_KEY,
        'LANGUAGE_CODE': get_language_from_request(request),
        'tagline': get_configuration_value("ENTERPRISE_TAGLINE", settings.ENTERPRISE_TAGLINE),
        'platform_description': get_configuration_value(
            "PLATFORM_DESCRIPTION",
            settings.PLATFORM_DESCRIPTION,
        ),
        'LMS_ROOT_URL': settings.LMS_ROOT_URL,
        'platform_name': platform_name,
        'header_logo_alt_text': _('{platform_name} home page').format(platform_name=platform_name),
        'welcome_text': constants.WELCOME_TEXT.format(platform_name=platform_name),
        'enterprise_welcome_text': constants.ENTERPRISE_WELCOME_TEXT.format(
            enterprise_customer_name=enterprise_customer.name,
            platform_name=platform_name,
            strong_start='',
            strong_end='',
            line_break='
',
            privacy_policy_link_start="".format(
                pp_url=get_configuration_value('PRIVACY', 'https://www.edx.org/edx-privacy-policy', type='url'),
            ),
            privacy_policy_link_end="",
        ),
    }

Get the set of variables that are needed by default across views .


def render_page_with_error_code_message(request, context_data, error_code, log_message):
    """
    Return a 404 page with specified error_code after logging error and adding message to django messages.
    """
    LOGGER.error(log_message)
    messages.add_generic_error_message_with_code(request, error_code)
    return render(
        request,
        ENTERPRISE_GENERAL_ERROR_PAGE,
        context=context_data,
        status=404,
    )

def render_page_with_error_code_message ( request , context_data , error_code , log_message ) : LOGGER . error ( log_message ) messages . add_generic_error_message_with_code ( request , error_code ) return render ( request , ENTERPRISE_GENERAL_ERROR_PAGE , context = context_data , status = 404 , )

Return a 404 page with specified error_code after logging error and adding message to django messages .


def course_or_program_exist(self, course_id, program_uuid):
        """
        Return whether the input course or program exist.
        """
        course_exists = course_id and CourseApiClient().get_course_details(course_id)
        program_exists = program_uuid and CourseCatalogApiServiceClient().program_exists(program_uuid)
        return course_exists or program_exists

Return whether the input course or program exist .


def get_course_or_program_context(self, enterprise_customer, course_id=None, program_uuid=None):
        """
        Return a dict having course or program specific keys for data sharing consent page.
        """
        context_data = {}
        if course_id:
            context_data.update({'course_id': course_id, 'course_specific': True})
            if not self.preview_mode:
                try:
                    catalog_api_client = CourseCatalogApiServiceClient(enterprise_customer.site)
                except ImproperlyConfigured:
                    raise Http404

                course_run_details = catalog_api_client.get_course_run(course_id)
                course_start_date = ''
                if course_run_details['start']:
                    course_start_date = parse(course_run_details['start']).strftime('%B %d, %Y')

                context_data.update({
                    'course_title': course_run_details['title'],
                    'course_start_date': course_start_date,
                })
            else:
                context_data.update({
                    'course_title': 'Demo Course',
                    'course_start_date': datetime.datetime.now().strftime('%B %d, %Y'),
                })
        else:
            context_data.update({
                'program_uuid': program_uuid,
                'program_specific': True,
            })
        return context_data

def
get_course_or_program_context
(
self
,
enterprise_customer
,
course_id
=
,
program_uuid
=
)
:
context_data
=
{
}
if
course_id
:
context_data
.
update
(
{
'course_id'
:
course_id
,
'course_specific'
:
True
}
)
if
not
self
.
preview_mode
:
try
:
catalog_api_client
=
CourseCatalogApiServiceClient
(
enterprise_customer
.
site
)
except
ImproperlyConfigured
:
raise
Http404
course_run_details
=
catalog_api_client
.
get_course_run
(
course_id
)
course_start_date
=
''
if
course_run_details
[
'start'
]
:
course_start_date
=
parse
(
course_run_details
[
'start'
]
)
.
strftime
(
'%B %d, %Y'
)
context_data
.
update
(
{
'course_title'
:
course_run_details
[
'title'
]
,
'course_start_date'
:
course_start_date
,
}
)
else
:
context_data
.
update
(
{
'course_title'
:
'Demo Course'
,
'course_start_date'
:
datetime
.
datetime
.
now
(
)
.
strftime
(
'%B %d, %Y'
)
,
}
)
else
:
context_data
.
update
(
{
'program_uuid'
:
program_uuid
,
'program_specific'
:
True
,
}
)
return
context_data

Return a dict having course or program specific keys for data sharing consent page .


def post(self, request):
        """
        Process the above form.
        """
        enterprise_uuid = request.POST.get('enterprise_customer_uuid')
        success_url = request.POST.get('redirect_url')
        failure_url = request.POST.get('failure_url')
        course_id = request.POST.get('course_id', '')
        program_uuid = request.POST.get('program_uuid', '')

        enterprise_customer = get_enterprise_customer_or_404(enterprise_uuid)
        context_data = get_global_context(request, enterprise_customer)

        if not (enterprise_uuid and success_url and failure_url):
            error_code = 'ENTGDS005'
            log_message = (
                'Error: one or more of the following values was falsy: '
                'enterprise_uuid: {enterprise_uuid}, '
                'success_url: {success_url}, '
                'failure_url: {failure_url} for course_id {course_id}. '
                'The following error code was reported to the user {userid}: {error_code}'.format(
                    userid=request.user.id,
                    enterprise_uuid=enterprise_uuid,
                    success_url=success_url,
                    failure_url=failure_url,
                    error_code=error_code,
                    course_id=course_id,
                )
            )
            return render_page_with_error_code_message(request, context_data, error_code, log_message)

        if not self.course_or_program_exist(course_id, program_uuid):
            error_code = 'ENTGDS006'
            log_message = (
                'Neither the course with course_id: {course_id} '
                'or program with {program_uuid} exist for '
                'enterprise customer {enterprise_uuid}'
                'Error code {error_code} presented to user {userid}'.format(
                    course_id=course_id,
                    program_uuid=program_uuid,
                    error_code=error_code,
                    userid=request.user.id,
                    enterprise_uuid=enterprise_uuid,
                )
            )
            return render_page_with_error_code_message(request, context_data, error_code, log_message)

        consent_record = get_data_sharing_consent(
            request.user.username,
            enterprise_uuid,
            program_uuid=program_uuid,
            course_id=course_id
        )
        if consent_record is None:
            error_code = 'ENTGDS007'
            log_message = (
                'The was a problem with the consent record of user {userid} with '
                'enterprise_uuid {enterprise_uuid}. consent_record has a value '
                'of {consent_record} and a '
                'value for course_id {course_id}. '
                'Error code {error_code} presented to user'.format(
                    userid=request.user.id,
                    enterprise_uuid=enterprise_uuid,
                    consent_record=consent_record,
                    error_code=error_code,
                    course_id=course_id,
                )
            )
            return render_page_with_error_code_message(request, context_data, error_code, log_message)

        defer_creation = request.POST.get('defer_creation')
        consent_provided = bool(request.POST.get('data_sharing_consent', False))
        if defer_creation is None and consent_record.consent_required():
            if course_id:
                enterprise_customer_user, __ = EnterpriseCustomerUser.objects.get_or_create(
                    enterprise_customer=consent_record.enterprise_customer,
                    user_id=request.user.id
                )
                enterprise_customer_user.update_session(request)
                __, created = EnterpriseCourseEnrollment.objects.get_or_create(
                    enterprise_customer_user=enterprise_customer_user,
                    course_id=course_id,
                )
                if created:
                    track_enrollment('data-consent-page-enrollment', request.user.id, course_id, request.path)

            consent_record.granted = consent_provided
            consent_record.save()

        return redirect(success_url if consent_provided else failure_url)


def get(self, request, enterprise_uuid, course_id):
        """
        Handle the enrollment of enterprise learner in the provided course.

        Based on `enterprise_uuid` in URL, the view will decide which
        enterprise customer's course enrollment record should be created.

        Depending on the value of query parameter `course_mode` then learner
        will be either redirected to LMS dashboard for audit modes or
        redirected to ecommerce basket flow for payment of premium modes.
        """
        enrollment_course_mode = request.GET.get('course_mode')
        enterprise_catalog_uuid = request.GET.get('catalog')

        # Redirect the learner to LMS dashboard in case no course mode is
        # provided as query parameter `course_mode`
        if not enrollment_course_mode:
            return redirect(LMS_DASHBOARD_URL)

        enrollment_api_client = EnrollmentApiClient()
        course_modes = enrollment_api_client.get_course_modes(course_id)

        # Verify that the request user belongs to the enterprise against the
        # provided `enterprise_uuid`.
        enterprise_customer = get_enterprise_customer_or_404(enterprise_uuid)
        enterprise_customer_user = get_enterprise_customer_user(request.user.id, enterprise_customer.uuid)

        if not course_modes:
            context_data = get_global_context(request, enterprise_customer)
            error_code = 'ENTHCE000'
            log_message = (
                'No course_modes for course_id {course_id} for enterprise_catalog_uuid '
                '{enterprise_catalog_uuid}.'
                'The following error was presented to '
                'user {userid}: {error_code}'.format(
                    userid=request.user.id,
                    enterprise_catalog_uuid=enterprise_catalog_uuid,
                    course_id=course_id,
                    error_code=error_code
                )
            )
            return render_page_with_error_code_message(request, context_data, error_code, log_message)

        selected_course_mode = None
        for course_mode in course_modes:
            if course_mode['slug'] == enrollment_course_mode:
                selected_course_mode = course_mode
                break

        if not selected_course_mode:
            return redirect(LMS_DASHBOARD_URL)

        # Create the Enterprise backend database records for this course
        # enrollment
        __, created = EnterpriseCourseEnrollment.objects.get_or_create(
            enterprise_customer_user=enterprise_customer_user,
            course_id=course_id,
        )
        if created:
            track_enrollment('course-landing-page-enrollment', request.user.id, course_id, request.get_full_path())

        DataSharingConsent.objects.update_or_create(
            username=enterprise_customer_user.username,
            course_id=course_id,
            enterprise_customer=enterprise_customer_user.enterprise_customer,
            defaults={
                'granted': True
            },
        )

        audit_modes = getattr(settings, 'ENTERPRISE_COURSE_ENROLLMENT_AUDIT_MODES', ['audit', 'honor'])
        if selected_course_mode['slug'] in audit_modes:
            # In case of Audit course modes enroll the learner directly through
            # enrollment API client and redirect the learner to dashboard.
            enrollment_api_client.enroll_user_in_course(
                request.user.username, course_id, selected_course_mode['slug']
            )

            return redirect(LMS_COURSEWARE_URL.format(course_id=course_id))

        # redirect the enterprise learner to the ecommerce flow in LMS
        # Note: LMS start flow automatically detects the paid mode
        premium_flow = LMS_START_PREMIUM_COURSE_FLOW_URL.format(course_id=course_id)
        if enterprise_catalog_uuid:
            premium_flow += '?catalog={catalog_uuid}'.format(
                catalog_uuid=enterprise_catalog_uuid
            )

        return redirect(premium_flow)

Handle the enrollment of enterprise learner in the provided course .


def set_final_prices(self, modes, request):
        """
        Set the final discounted price on each premium mode.
        """
        result = []
        for mode in modes:
            if mode['premium']:
                mode['final_price'] = EcommerceApiClient(request.user).get_course_final_price(
                    mode=mode,
                    enterprise_catalog_uuid=request.GET.get(
                        'catalog'
                    ) if request.method == 'GET' else None,
                )
            result.append(mode)
        return result

def set_final_prices ( self , modes , request ) : result = [ ] for mode in modes : if mode [ 'premium' ] : mode [ 'final_price' ] = EcommerceApiClient ( request . user ) . get_course_final_price ( mode = mode , enterprise_catalog_uuid = request . GET . get ( 'catalog' ) if request . method == 'GET' else None , ) result . append ( mode ) return result

Set the final discounted price on each premium mode .


def get_available_course_modes(self, request, course_run_id, enterprise_catalog):
        """
        Return the available course modes for the course run.

        The provided EnterpriseCustomerCatalog is used to filter and order the
        course modes returned using the EnterpriseCustomerCatalog's
        field "enabled_course_modes".
        """
        modes = EnrollmentApiClient().get_course_modes(course_run_id)
        if not modes:
            LOGGER.warning('Unable to get course modes for course run id {course_run_id}.'.format(
                course_run_id=course_run_id
            ))
            messages.add_generic_info_message_for_error(request)

        if enterprise_catalog:
            # filter and order course modes according to the enterprise catalog
            modes = [mode for mode in modes if mode['slug'] in enterprise_catalog.enabled_course_modes]
            modes.sort(key=lambda course_mode: enterprise_catalog.enabled_course_modes.index(course_mode['slug']))
            if not modes:
                LOGGER.info(
                    'No matching course modes found for course run {course_run_id} in '
                    'EnterpriseCustomerCatalog [{enterprise_catalog_uuid}]'.format(
                        course_run_id=course_run_id,
                        enterprise_catalog_uuid=enterprise_catalog,
                    )
                )
                messages.add_generic_info_message_for_error(request)

        return modes

Return the available course modes for the course run .


def get(self, request, enterprise_uuid, course_id):
        """
        Show course track selection page for the enterprise.

        Based on `enterprise_uuid` in URL, the view will decide which
        enterprise customer's course enrollment page is to use.

        Unauthenticated learners will be redirected to enterprise-linked SSO.

        A 404 will be raised if any of the following conditions are met:
            * No enterprise customer uuid kwarg `enterprise_uuid` in request.
            * No enterprise customer found against the enterprise customer
                uuid `enterprise_uuid` in the request kwargs.
            * No course is found in database against the provided `course_id`.

        """
        # Check to see if access to the course run is restricted for this user.
        embargo_url = EmbargoApiClient.redirect_if_blocked([course_id], request.user, get_ip(request), request.path)
        if embargo_url:
            return redirect(embargo_url)

        enterprise_customer, course, course_run, modes = self.get_base_details(
            request, enterprise_uuid, course_id
        )
        enterprise_customer_user = get_enterprise_customer_user(request.user.id, enterprise_uuid)
        data_sharing_consent = DataSharingConsent.objects.proxied_get(
            username=enterprise_customer_user.username,
            course_id=course_id,
            enterprise_customer=enterprise_customer
        )

        enrollment_client = EnrollmentApiClient()
        enrolled_course = enrollment_client.get_course_enrollment(request.user.username, course_id)
        try:
            enterprise_course_enrollment = EnterpriseCourseEnrollment.objects.get(
                enterprise_customer_user__enterprise_customer=enterprise_customer,
                enterprise_customer_user__user_id=request.user.id,
                course_id=course_id
            )
        except EnterpriseCourseEnrollment.DoesNotExist:
            enterprise_course_enrollment = None

        if enrolled_course and enterprise_course_enrollment:
            # The user is already enrolled in the course through the Enterprise Customer, so redirect to the course
            # info page.
            return redirect(LMS_COURSEWARE_URL.format(course_id=course_id))

        return self.get_enterprise_course_enrollment_page(
            request,
            enterprise_customer,
            course,
            course_run,
            modes,
            enterprise_course_enrollment,
            data_sharing_consent,
        )

Show course track selection page for the enterprise .


def extend_course(course, enterprise_customer, request):
        """
        Extend a course with more details needed for the program landing page.

        In particular, we add the following:

        * `course_image_uri`
        * `course_title`
        * `course_level_type`
        * `course_short_description`
        * `course_full_description`
        * `course_effort`
        * `expected_learning_items`
        * `staff`
        """
        course_run_id = course['course_runs'][0]['key']
        try:
            catalog_api_client = CourseCatalogApiServiceClient(enterprise_customer.site)
        except ImproperlyConfigured:
            error_code = 'ENTPEV000'
            LOGGER.error(
                'CourseCatalogApiServiceClient is improperly configured. '
                'Returned error code {error_code} to user {userid} '
                'and enterprise_customer {enterprise_customer} '
                'for course_run_id {course_run_id}'.format(
                    error_code=error_code,
                    userid=request.user.id,
                    enterprise_customer=enterprise_customer.uuid,
                    course_run_id=course_run_id,
                )
            )
            messages.add_generic_error_message_with_code(request, error_code)
            return ({}, error_code)

        course_details, course_run_details = catalog_api_client.get_course_and_course_run(course_run_id)
        if not course_details or not course_run_details:
            error_code = 'ENTPEV001'
            LOGGER.error(
                'User {userid} of enterprise customer {enterprise_customer} encountered an error.'
                'No course_details or course_run_details found for '
                'course_run_id {course_run_id}. '
                'The following error code reported to the user: {error_code}'.format(
                    userid=request.user.id,
                    enterprise_customer=enterprise_customer.uuid,
                    course_run_id=course_run_id,
                    error_code=error_code,
                )
            )
            messages.add_generic_error_message_with_code(request, error_code)
            return ({}, error_code)

        weeks_to_complete = course_run_details['weeks_to_complete']
        course_run_image = course_run_details['image'] or {}
        course.update({
            'course_image_uri': course_run_image.get('src', ''),
            'course_title': course_run_details['title'],
            'course_level_type': course_run_details.get('level_type', ''),
            'course_short_description': course_run_details['short_description'] or '',
            'course_full_description': clean_html_for_template_rendering(course_run_details['full_description'] or ''),
            'expected_learning_items': course_details.get('expected_learning_items', []),
            'staff': course_run_details.get('staff', []),
            'course_effort': ungettext_min_max(
                '{} hour per week',
                '{} hours per week',
                '{}-{} hours per week',
                course_run_details['min_effort'] or None,
                course_run_details['max_effort'] or None,
            ) or '',
            'weeks_to_complete': ungettext(
                '{} week',
                '{} weeks',
                weeks_to_complete
            ).format(weeks_to_complete) if weeks_to_complete else '',
        })
        return course, None

Extend a course with more details needed for the program landing page .


def get(self, request, enterprise_uuid, program_uuid):
        """
        Show Program Landing page for the Enterprise's Program.

        Render the Enterprise's Program Enrollment page for a specific program.
        The Enterprise and Program are both selected by their respective UUIDs.

        Unauthenticated learners will be redirected to enterprise-linked SSO.

        A 404 will be raised if any of the following conditions are met:
            * No enterprise customer UUID query parameter ``enterprise_uuid`` found in request.
            * No enterprise customer found against the enterprise customer
                uuid ``enterprise_uuid`` in the request kwargs.
            * No Program can be found given ``program_uuid`` either at all or associated with
                the Enterprise..
        """
        verify_edx_resources()

        enterprise_customer = get_enterprise_customer_or_404(enterprise_uuid)
        context_data = get_global_context(request, enterprise_customer)
        program_details, error_code = self.get_program_details(request, program_uuid, enterprise_customer)
        if error_code:
            return render(
                request,
                ENTERPRISE_GENERAL_ERROR_PAGE,
                context=context_data,
                status=404,
            )
        if program_details['certificate_eligible_for_program']:
            # The user is already enrolled in the program, so redirect to the program's dashboard.
            return redirect(LMS_PROGRAMS_DASHBOARD_URL.format(uuid=program_uuid))

        # Check to see if access to any of the course runs in the program are restricted for this user.
        course_run_ids = []
        for course in program_details['courses']:
            for course_run in course['course_runs']:
                course_run_ids.append(course_run['key'])
        embargo_url = EmbargoApiClient.redirect_if_blocked(course_run_ids, request.user, get_ip(request), request.path)
        if embargo_url:
            return redirect(embargo_url)

        return self.get_enterprise_program_enrollment_page(request, enterprise_customer, program_details)

Show Program Landing page for the Enterprise s Program .


def get_path_variables(**kwargs):
        """
        Get the base variables for any view to route to.

        Currently gets:
        - `enterprise_uuid` - the UUID of the enterprise customer.
        - `course_run_id` - the ID of the course, if applicable.
        - `program_uuid` - the UUID of the program, if applicable.
        """
        enterprise_customer_uuid = kwargs.get('enterprise_uuid', '')
        course_run_id = kwargs.get('course_id', '')
        course_key = kwargs.get('course_key', '')
        program_uuid = kwargs.get('program_uuid', '')

        return enterprise_customer_uuid, course_run_id, course_key, program_uuid

def get_path_variables ( * * kwargs ) : enterprise_customer_uuid = kwargs . get ( 'enterprise_uuid' , '' ) course_run_id = kwargs . get ( 'course_id' , '' ) course_key = kwargs . get ( 'course_key' , '' ) program_uuid = kwargs . get ( 'program_uuid' , '' ) return enterprise_customer_uuid , course_run_id , course_key , program_uuid

Get the base variables for any view to route to .


def get_course_run_id(user, enterprise_customer, course_key):
        """
        User is requesting a course, we need to translate that into the current course run.

        :param user:
        :param enterprise_customer:
        :param course_key:
        :return: course_run_id
        """
        try:
            course = CourseCatalogApiServiceClient(enterprise_customer.site).get_course_details(course_key)
        except ImproperlyConfigured:
            raise Http404

        users_all_enrolled_courses = EnrollmentApiClient().get_enrolled_courses(user.username)
        users_active_course_runs = get_active_course_runs(
            course,
            users_all_enrolled_courses
        ) if users_all_enrolled_courses else []
        course_run = get_current_course_run(course, users_active_course_runs)
        if course_run:
            course_run_id = course_run['key']
            return course_run_id
        else:
            raise Http404

def get_course_run_id ( user , enterprise_customer , course_key ) : try : course = CourseCatalogApiServiceClient ( enterprise_customer . site ) . get_course_details ( course_key ) except ImproperlyConfigured : raise Http404 users_all_enrolled_courses = EnrollmentApiClient ( ) . get_enrolled_courses ( user . username ) users_active_course_runs = get_active_course_runs ( course , users_all_enrolled_courses ) if users_all_enrolled_courses else [ ] course_run = get_current_course_run ( course , users_active_course_runs ) if course_run : course_run_id = course_run [ 'key' ] return course_run_id else : raise Http404

User is requesting a course we need to translate that into the current course run .


def eligible_for_direct_audit_enrollment(self, request, enterprise_customer, resource_id, course_key=None):
        """
        Return whether a request is eligible for direct audit enrollment for a particular enterprise customer.

        'resource_id' can be either course_run_id or program_uuid.
        We check for the following criteria:
        - The `audit` query parameter.
        - The user's being routed to the course enrollment landing page.
        - The customer's catalog contains the course in question.
        - The audit track is an available mode for the course.
        """
        course_identifier = course_key if course_key else resource_id

        # Return it in one big statement to utilize short-circuiting behavior. Avoid the API call if possible.
        return request.GET.get('audit') and \
            request.path == self.COURSE_ENROLLMENT_VIEW_URL.format(enterprise_customer.uuid, course_identifier) and \
            enterprise_customer.catalog_contains_course(resource_id) and \
            EnrollmentApiClient().has_course_mode(resource_id, 'audit')

Return whether a request is eligible for direct audit enrollment for a particular enterprise customer .


def redirect(self, request, *args, **kwargs):
        """
        Redirects to the appropriate view depending on where the user came from.
        """
        enterprise_customer_uuid, course_run_id, course_key, program_uuid = RouterView.get_path_variables(**kwargs)
        resource_id = course_key or course_run_id or program_uuid
        # Replace enterprise UUID and resource ID with '{}', to easily match with a path in RouterView.VIEWS. Example:
        # /enterprise/fake-uuid/course/course-v1:cool+course+2017/enroll/ -> /enterprise/{}/course/{}/enroll/
        path = re.sub('{}|{}'.format(enterprise_customer_uuid, re.escape(resource_id)), '{}', request.path)

        # Remove course_key from kwargs if it exists because delegate views are not expecting it.
        kwargs.pop('course_key', None)

        return self.VIEWS[path].as_view()(request, *args, **kwargs)

Redirects to the appropriate view depending on where the user came from .